LayoutOverflowVisualization.cs 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. using System;
  2. using System.Collections.Generic;
  3. using QuestPDF.Drawing;
  4. using QuestPDF.Helpers;
  5. using QuestPDF.Infrastructure;
  6. using SkiaSharp;
  7. namespace QuestPDF.Elements;
  8. internal class LayoutOverflowVisualization : ContainerElement, IContentDirectionAware
  9. {
  10. private const float BorderThickness = 1.5f;
  11. private const float StripeThickness = 1.5f;
  12. private const float StripeScale = 6f;
  13. private const string LineColor = Colors.Red.Medium;
  14. private const string AvailableAreaColor = Colors.Green.Medium;
  15. private const string OverflowAreaColor = Colors.Red.Medium;
  16. private const byte AreaOpacity = 64;
  17. public ContentDirection ContentDirection { get; set; }
  18. internal override SpacePlan Measure(Size availableSpace)
  19. {
  20. var childSize = base.Measure(availableSpace);
  21. if (childSize.Type == SpacePlanType.FullRender)
  22. return childSize;
  23. return SpacePlan.FullRender(availableSpace);
  24. }
  25. internal override void Draw(Size availableSpace)
  26. {
  27. // measure content area
  28. var childSize = base.Measure(availableSpace);
  29. if (childSize.Type == SpacePlanType.FullRender)
  30. {
  31. Child?.Draw(availableSpace);
  32. return;
  33. }
  34. if (Canvas is SkiaCanvasBase skiaCanvasBase)
  35. skiaCanvasBase.MarkCurrentPageAsHavingLayoutIssues();
  36. // check overflow area
  37. var contentSize =
  38. TryVerticalOverflow(availableSpace)
  39. ?? TryHorizontalOverflow(availableSpace)
  40. ?? TryUnconstrainedOverflow()
  41. ?? Size.Max;
  42. // draw content
  43. var translate = ContentDirection == ContentDirection.RightToLeft
  44. ? new Position(availableSpace.Width - contentSize.Width, 0)
  45. : Position.Zero;
  46. Canvas.Translate(translate);
  47. Child?.Draw(contentSize);
  48. Canvas.Translate(translate.Reverse());
  49. // draw overflow area
  50. var overflowTranslate = ContentDirection == ContentDirection.RightToLeft ? new Position(availableSpace.Width, 0) : Position.Zero;
  51. var overflowScale = ContentDirection == ContentDirection.RightToLeft ? -1 : 1;
  52. Canvas.Translate(overflowTranslate);
  53. Canvas.Scale(overflowScale, 1);
  54. DrawOverflowArea(availableSpace, contentSize);
  55. Canvas.Scale(overflowScale, 1);
  56. Canvas.Translate(overflowTranslate.Reverse());
  57. }
  58. private Size? TryOverflow(Size targetSpace)
  59. {
  60. var contentSize = base.Measure(targetSpace);
  61. return contentSize.Type == SpacePlanType.Wrap ? null : contentSize;
  62. }
  63. private Size? TryVerticalOverflow(Size availableSpace)
  64. {
  65. var overflowSpace = new Size(availableSpace.Width, Size.Infinity);
  66. return TryOverflow(overflowSpace);
  67. }
  68. private Size? TryHorizontalOverflow(Size availableSpace)
  69. {
  70. var overflowSpace = new Size(Size.Infinity, availableSpace.Height);
  71. return TryOverflow(overflowSpace);
  72. }
  73. private Size? TryUnconstrainedOverflow()
  74. {
  75. var overflowSpace = new Size(Size.Infinity, Size.Infinity);
  76. return TryOverflow(overflowSpace);
  77. }
  78. private void DrawOverflowArea(Size availableSpace, Size contentSize)
  79. {
  80. if (Canvas is not SkiaCanvasBase canvasBase)
  81. return;
  82. var skiaCanvas = canvasBase.Canvas;
  83. DrawAvailableSpaceBackground();
  84. skiaCanvas.Save();
  85. ClipOverflowAreaVisibility();
  86. DrawOverflowArea();
  87. DrawCheckerboardPattern();
  88. skiaCanvas.Restore();
  89. DrawContentAreaBorder();
  90. void DrawAvailableSpaceBackground()
  91. {
  92. using var paint = new SKPaint
  93. {
  94. Color = SKColor.Parse(AvailableAreaColor).WithAlpha(AreaOpacity)
  95. };
  96. skiaCanvas.DrawRect(0, 0, availableSpace.Width, availableSpace.Height, paint);
  97. }
  98. void DrawContentAreaBorder()
  99. {
  100. using var borderPaint = new SKPaint
  101. {
  102. Color = SKColor.Parse(LineColor),
  103. IsStroke = true,
  104. StrokeWidth = BorderThickness
  105. };
  106. skiaCanvas.DrawRect(0, 0, contentSize.Width, contentSize.Height, borderPaint);
  107. }
  108. void DrawOverflowArea()
  109. {
  110. using var areaPaint = new SKPaint
  111. {
  112. Color = SKColor.Parse(OverflowAreaColor).WithAlpha(AreaOpacity)
  113. };
  114. skiaCanvas.DrawRect(0, 0, contentSize.Width, contentSize.Height, areaPaint);
  115. }
  116. void DrawCheckerboardPattern()
  117. {
  118. var matrix = SKMatrix.CreateScale(StripeScale, StripeScale).PostConcat(SKMatrix.CreateRotation((float)(Math.PI / 4)));
  119. using var paint = new SKPaint
  120. {
  121. Color = SKColor.Parse(LineColor),
  122. PathEffect = SKPathEffect.Create2DLine(StripeThickness, matrix),
  123. IsAntialias = true
  124. };
  125. var targetArea = new SKRect(0,0,contentSize.Width, contentSize.Height);
  126. targetArea.Inflate(StripeScale * 2, StripeScale * 2);
  127. skiaCanvas.DrawRect(targetArea, paint);
  128. }
  129. void ClipOverflowAreaVisibility()
  130. {
  131. var path = new SKPath();
  132. path.AddRect(new SKRect(0, 0, contentSize.Width, contentSize.Height), SKPathDirection.Clockwise);
  133. path.AddRect(new SKRect(0, 0, Math.Min(availableSpace.Width, contentSize.Width), Math.Min(availableSpace.Height, contentSize.Height)), SKPathDirection.CounterClockwise);
  134. skiaCanvas.Save();
  135. skiaCanvas.ClipPath(path);
  136. }
  137. }
  138. }