| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- using QuestPDF.Drawing;
- using QuestPDF.Fluent;
- using QuestPDF.Helpers;
- using QuestPDF.Infrastructure;
- using SkiaSharp;
- namespace QuestPDF.LayoutTests.TestEngine;
- internal static class LayoutTestResultVisualization
- {
- // output settings
- private const int OutputImageScale = 2;
- private const int Padding = 10;
-
- // document colors
- private const string DocumentBackgroundColor = Colors.Grey.Darken2;
- private const string PageBackgroundColor = Colors.Grey.Lighten1;
- private const string RequiredAreaBackgroundColor = Colors.White;
-
- // grid configuration
- private const float GridSize = 10;
- private const float GridLineThickness = 0.25f;
- private const byte GridLineTransparency = 48;
-
- // mock drawing settings
- private const byte OccludedMockBorderThickness = 5;
- private static readonly string[] DefaultElementColors =
- {
- Colors.DeepPurple.Lighten2,
- Colors.Blue.Lighten2,
- Colors.Cyan.Lighten2,
- Colors.Green.Lighten2,
- Colors.Lime.Lighten2,
- Colors.Amber.Lighten2,
- Colors.Brown.Lighten2,
-
- Colors.DeepPurple.Medium,
- Colors.Blue.Medium,
- Colors.Cyan.Medium,
- Colors.Green.Medium,
- Colors.Lime.Medium,
- Colors.Amber.Medium,
- Colors.Brown.Medium,
- };
-
- // implementations
- public static void Visualize(LayoutTestResult result, Stream stream)
- {
- // determine output dimenstions
- var numberOfPages = Math.Max(result.ActualLayout.Pages.Count, result.ExpectedLayout.Pages.Count);
- var canvasWidth = result.PageSize.Width * 2 + Padding * 4;
- var canvasHeight = result.PageSize.Height * numberOfPages + Padding * (numberOfPages + 2);
- // create PDF
- using var pdf = SKDocument.CreatePdf(stream);
- using var canvas = pdf.BeginPage(canvasWidth * OutputImageScale, canvasHeight * OutputImageScale);
-
- canvas.Scale(OutputImageScale, OutputImageScale);
- canvas.Clear(SKColor.Parse(DocumentBackgroundColor));
- // draw content
- var mockColors = AssignColorsToMocks();
- DrawDocument();
- // finish generation
- pdf.EndPage();
- pdf.Close();
- IDictionary<string, string> AssignColorsToMocks()
- {
- var mocks = Enumerable
- .Concat(result.ActualLayout.Pages, result.ExpectedLayout.Pages)
- .SelectMany(x => x.Mocks)
- .Select(x => x.MockId)
- .Distinct()
- .ToList();
- return Enumerable
- .Range(0, mocks.Count)
- .ToDictionary(i => mocks[i], i => DefaultElementColors[i]);
- }
- void DrawDocument()
- {
- canvas.Translate(Padding, Padding);
-
- // draw title
- using var textPaint = new SKPaint
- {
- TextSize = 8,
- Color = Colors.White.ColorToCode(),
- Typeface = SKTypeface.FromFamilyName("Calibri", SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright),
- TextAlign = SKTextAlign.Center
- };
-
- var actualHeaderPosition = new SKPoint(result.PageSize.Width / 2, textPaint.TextSize / 2);
- canvas.DrawText("ACTUAL", actualHeaderPosition, textPaint);
-
- var expectedHeaderPosition = new SKPoint(Padding * 2 + result.PageSize.Width * 1.5f, textPaint.TextSize / 2);
- canvas.DrawText("EXPECTED", expectedHeaderPosition, textPaint);
-
- // draw pages
- canvas.Save();
- canvas.Translate(0, Padding);
-
- foreach (var pageIndex in Enumerable.Range(0, numberOfPages))
- {
- var actualPage = result.ActualLayout.Pages.ElementAtOrDefault(pageIndex);
- var expectedPage = result.ExpectedLayout.Pages.ElementAtOrDefault(pageIndex);
-
- DrawPage(actualPage);
- DrawLayoutDifferences(actualPage, expectedPage);
-
- canvas.Translate(result.PageSize.Width + Padding, 0);
- canvas.DrawText((pageIndex + 1).ToString(), 0, textPaint.TextSize, textPaint);
-
- canvas.Translate(Padding, 0);
- DrawPage(expectedPage);
- DrawLayoutDifferences(expectedPage, actualPage);
-
- canvas.Translate(-result.PageSize.Width - Padding * 2, result.PageSize.Height + Padding);
- }
- canvas.Restore();
- }
-
- void DrawPage(LayoutTestResult.PageLayout? pageLayout)
- {
- // draw page
- using var availableAreaPaint = new SKPaint
- {
- Color = SKColor.Parse(PageBackgroundColor)
- };
-
- canvas.DrawRect(0, 0, result.PageSize.Width, result.PageSize.Height, availableAreaPaint);
-
- if (pageLayout == null)
- {
- DrawGridLines();
- return;
- }
-
- // draw required area
- using var requiredAreaPaint = new SKPaint
- {
- Color = SKColor.Parse(RequiredAreaBackgroundColor)
- };
-
- canvas.DrawRect(0, 0, pageLayout.RequiredArea.Width, pageLayout.RequiredArea.Height, requiredAreaPaint);
-
- // draw mocks
- foreach (var mock in pageLayout.Mocks)
- DrawMock(mock);
-
- foreach (var mock in pageLayout.Mocks.GetOverlappingItems())
- DrawOccludedMock(mock.Below);
-
- DrawGridLines();
- }
- void DrawMock(LayoutTestResult.MockLayoutPosition mock)
- {
- var color = mockColors[mock.MockId];
-
- using var mockAreaPaint = new SKPaint
- {
- Color = SKColor.Parse(color)
- };
-
- canvas.Save();
-
- canvas.Translate(mock.Position.X, mock.Position.Y);
- canvas.DrawRect(0, 0, mock.Size.Width, mock.Size.Height, mockAreaPaint);
- canvas.Restore();
- }
-
- void DrawOccludedMock(LayoutTestResult.MockLayoutPosition mock)
- {
- var color = mockColors[mock.MockId];
-
- using var mockBorderPaint = new SKPaint
- {
- Color = SKColor.Parse(color),
- IsStroke = true,
- StrokeWidth = OccludedMockBorderThickness
- };
- var borderPosition = new SKRect(0, 0, mock.Size.Width, mock.Size.Height);
- borderPosition.Inflate(-OccludedMockBorderThickness / 2f, -OccludedMockBorderThickness / 2f);
-
- canvas.Save();
- canvas.Translate(mock.Position.X, mock.Position.Y);
- canvas.DrawRect(borderPosition, mockBorderPaint);
- canvas.Restore();
- }
-
- void DrawGridLines()
- {
- using var paint = new SKPaint
- {
- Color = SKColor.Parse(Colors.Black).WithAlpha(GridLineTransparency),
- StrokeWidth = GridLineThickness
- };
- var verticalLineCount = (int)Math.Floor(result.PageSize.Width / GridSize);
- var horizontalLineCount = (int)Math.Floor(result.PageSize.Height / GridSize);
-
- foreach (var i in Enumerable.Range(1, verticalLineCount))
- canvas.DrawLine(new SKPoint(i * GridSize, 0), new SKPoint(i * GridSize, result.PageSize.Height), paint);
-
- foreach (var i in Enumerable.Range(1, horizontalLineCount))
- canvas.DrawLine(new SKPoint(0, i * GridSize), new SKPoint(result.PageSize.Width, i * GridSize), paint);
- }
- void DrawLayoutDifferences(LayoutTestResult.PageLayout? target, LayoutTestResult.PageLayout? compareWith)
- {
- using var targetPath = BuildPathFromLayout(target);
- using var compareWithPath = BuildPathFromLayout(compareWith);
- using var differencePath = targetPath.Op(compareWithPath, SKPathOp.Difference);
-
- AnnotateInvalidAreaHelper.Annotate(canvas, differencePath);
-
- SKPath BuildPathFromLayout(LayoutTestResult.PageLayout? layout)
- {
- var resultPath = new SKPath();
- if (layout == null)
- return resultPath;
-
- foreach (var mock in layout.Mocks)
- {
- var position = new SKRect(
- mock.Position.X,
- mock.Position.Y,
- mock.Position.X + mock.Size.Width,
- mock.Position.Y + mock.Size.Height);
-
- resultPath.AddRect(position);
- }
- return resultPath;
- }
- }
- }
- }
|