ContentOverflowDebugArea.cs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. using System;
  2. using System.Linq;
  3. using QuestPDF.Drawing;
  4. using QuestPDF.Infrastructure;
  5. using SkiaSharp;
  6. namespace QuestPDF.Elements;
  7. internal class ContentOverflowDebugArea : ContainerElement, IContentDirectionAware
  8. {
  9. public ContentDirection ContentDirection { get; set; }
  10. internal override SpacePlan Measure(Size availableSpace)
  11. {
  12. var childSize = base.Measure(availableSpace);
  13. if (childSize.Type == SpacePlanType.Wrap)
  14. return SpacePlan.FullRender(availableSpace);
  15. return childSize;
  16. }
  17. internal override void Draw(Size availableSpace)
  18. {
  19. // measure content area
  20. var childSize = base.Measure(availableSpace);
  21. if (childSize.Type != SpacePlanType.Wrap)
  22. {
  23. Child?.Draw(availableSpace);
  24. return;
  25. }
  26. // check overflow area
  27. var contentSize =
  28. TryVerticalOverflow(availableSpace)
  29. ?? TryHorizontalOverflow(availableSpace)
  30. ?? TryUnconstrainedOverflow(availableSpace)
  31. ?? Size.Max;
  32. // draw content
  33. var translate = ContentDirection == ContentDirection.RightToLeft
  34. ? new Position(availableSpace.Width - contentSize.Width, 0)
  35. : Position.Zero;
  36. Canvas.Translate(translate);
  37. Child?.Draw(contentSize);
  38. Canvas.Translate(translate.Reverse());
  39. // draw overflow area
  40. var overflowTranslate = ContentDirection == ContentDirection.RightToLeft ? new Position(availableSpace.Width, 0) : Position.Zero;
  41. var overflowScale = ContentDirection == ContentDirection.RightToLeft ? -1 : 1;
  42. Canvas.Translate(overflowTranslate);
  43. Canvas.Scale(overflowScale, 1);
  44. DrawTargetAreaBorder(contentSize);
  45. DrawOverflowArea(availableSpace, contentSize);
  46. Canvas.Scale(overflowScale, 1);
  47. Canvas.Translate(overflowTranslate.Reverse());
  48. }
  49. private Size? TryOverflow(Size targetSpace)
  50. {
  51. var contentSize = base.Measure(targetSpace);
  52. return contentSize.Type == SpacePlanType.Wrap ? null : contentSize;
  53. }
  54. private Size? TryVerticalOverflow(Size availableSpace)
  55. {
  56. var overflowSpace = new Size(availableSpace.Width, Size.Infinity);
  57. return TryOverflow(overflowSpace);
  58. }
  59. private Size? TryHorizontalOverflow(Size availableSpace)
  60. {
  61. var overflowSpace = new Size(Size.Infinity, availableSpace.Height);
  62. return TryOverflow(overflowSpace);
  63. }
  64. private Size? TryUnconstrainedOverflow(Size availableSpace)
  65. {
  66. var overflowSpace = new Size(Size.Infinity, Size.Infinity);
  67. return TryOverflow(overflowSpace);
  68. }
  69. private void DrawTargetAreaBorder(Size contentSize)
  70. {
  71. const float borderWidth = 2;
  72. const string borderColor = "#f44336";
  73. Canvas.DrawRectangle(
  74. new Position(-borderWidth/2, -borderWidth/2),
  75. new Size(contentSize.Width + borderWidth/2 + borderWidth/2, borderWidth),
  76. borderColor);
  77. Canvas.DrawRectangle(
  78. new Position(-borderWidth/2, -borderWidth/2),
  79. new Size(borderWidth, contentSize.Height + borderWidth/2 + borderWidth/2),
  80. borderColor);
  81. Canvas.DrawRectangle(
  82. new Position(-borderWidth/2, contentSize.Height-borderWidth/2),
  83. new Size(contentSize.Width + borderWidth/2 + borderWidth/2, borderWidth),
  84. borderColor);
  85. Canvas.DrawRectangle(
  86. new Position(contentSize.Width-borderWidth/2, -borderWidth/2),
  87. new Size(borderWidth, contentSize.Height + borderWidth/2 + borderWidth/2),
  88. borderColor);
  89. }
  90. private void DrawOverflowArea(Size availableSpace, Size contentSize)
  91. {
  92. if (Canvas is not SkiaCanvasBase canvasBase)
  93. return;
  94. var skiaCanvas = canvasBase.Canvas;
  95. skiaCanvas.Save();
  96. ClipOverflowAreaVisibility();
  97. DrawCheckerboardPattern();
  98. skiaCanvas.Restore();
  99. void DrawCheckerboardPattern()
  100. {
  101. const float checkerboardSize = 8;
  102. const string lightCellColor = "#44f44336";
  103. const string darkCellColor = "#88f44336";
  104. var boardSizeX = (int)Math.Ceiling(contentSize.Width / checkerboardSize);
  105. var boardSizeY = (int)Math.Ceiling(contentSize.Height / checkerboardSize);
  106. foreach (var x in Enumerable.Range(0, boardSizeX))
  107. {
  108. foreach (var y in Enumerable.Range(0, boardSizeY))
  109. {
  110. var cellColor = (x + y) % 2 == 0 ? lightCellColor : darkCellColor;
  111. var cellPosition = new Position(x * checkerboardSize, y * checkerboardSize);
  112. var cellSize = new Size(checkerboardSize, checkerboardSize);
  113. Canvas.DrawRectangle(cellPosition, cellSize, cellColor);
  114. }
  115. }
  116. }
  117. // creates and applies an L-shaped clipping mask
  118. void ClipOverflowAreaVisibility()
  119. {
  120. var path = new SKPath();
  121. path.AddRect(new SKRect(0, 0, contentSize.Width, contentSize.Height), SKPathDirection.Clockwise);
  122. path.AddRect(new SKRect(0, 0, Math.Min(availableSpace.Width, contentSize.Width), Math.Min(availableSpace.Height, contentSize.Height)), SKPathDirection.CounterClockwise);
  123. skiaCanvas.Save();
  124. skiaCanvas.ClipPath(path);
  125. }
  126. }
  127. }