Browse Source

Keeping track of matrix operations in non-skia canvas

Marcin Ziąbek 1 year ago
parent
commit
ea098f4d7b

+ 31 - 3
Source/QuestPDF.Examples/DynamicPositionCapture.cs

@@ -16,16 +16,44 @@ namespace QuestPDF.Examples
     {
         public DynamicComponentComposeResult Compose(DynamicContext context)
         {
+            var containerLocation = context
+                .GetElementCapturedLocations("container")
+                .FirstOrDefault(x => x.PageNumber == context.PageNumber);
+
+            if (containerLocation == null)
+            {
+                return new DynamicComponentComposeResult
+                {
+                    Content = context.CreateElement(container => { }),
+                    HasMoreContent = false
+                };
+            }
+            
             var positions = Enumerable
                 .Range(0, 20)
                 .SelectMany(x => context.GetElementCapturedLocations($"capture_{x}"))
                 .ToList();
+
+            var onCurrentPage = positions.Where(x => x.PageNumber == context.PageNumber).ToList();
             
-            var visibleCount = positions.Count(x => x.PageNumber == context.PageNumber);
+            var content = context.CreateElement(container => container.Layers(layers =>
+            {
+                foreach (var position in onCurrentPage)
+                {
+                    layers.Layer()
+                        .TranslateX(position.X - containerLocation.X)
+                        .TranslateY(position.Y - containerLocation.Y)
+                        .Width(position.Width)
+                        .Height(position.Height)
+                        .Background(Placeholders.BackgroundColor());
+                }
 
+                layers.PrimaryLayer().Text($"{onCurrentPage.Count}");
+            }));
+            
             return new DynamicComponentComposeResult
             {
-                Content = context.CreateElement(container => container.Text(visibleCount.ToString())),
+                Content = content,
                 HasMoreContent = positions.Any(x => x.PageNumber > context.PageNumber + 1)
             };
         }
@@ -50,7 +78,7 @@ namespace QuestPDF.Examples
                         {
                             row.Spacing(25);
                             
-                            row.RelativeItem().Border(1).Column(column =>
+                            row.RelativeItem().Border(1).CaptureLocation("container").Column(column =>
                             {
                                 column.Spacing(25);
                                 

+ 1 - 4
Source/QuestPDF.LayoutTests/TestEngine/MockChild.cs

@@ -50,11 +50,8 @@ internal class ElementMock : Element
         HeightOffset += height;
         
         Canvas.DrawFilledRectangle(Position.Zero, size, Colors.Grey.Medium);
-        
-        if (Canvas is not SkiaCanvasBase canvasBase)
-            return;
 
-        var matrix = canvasBase.Canvas.GetCurrentTotalMatrix();
+        var matrix = Canvas.GetCurrentMatrix();
         
         DrawingCommands.Add(new MockDrawingCommand
         {

+ 1 - 0
Source/QuestPDF.UnitTests/TestEngine/MockCanvas.cs

@@ -16,6 +16,7 @@ namespace QuestPDF.UnitTests.TestEngine
 
         public void Save() => throw new NotImplementedException();
         public void Restore() => throw new NotImplementedException();
+        public CanvasMatrix GetCurrentMatrix() => throw new NotImplementedException();
         
         public void Translate(Position vector) => TranslateFunc(vector);
         public void Rotate(float angle) => RotateFunc(angle);

+ 1 - 0
Source/QuestPDF.UnitTests/TestEngine/OperationRecordingCanvas.cs

@@ -13,6 +13,7 @@ namespace QuestPDF.UnitTests.TestEngine
         
         public void Save() => throw new NotImplementedException();
         public void Restore() => throw new NotImplementedException();
+        public CanvasMatrix GetCurrentMatrix() => throw new NotImplementedException();
         
         public void Translate(Position vector) => Operations.Add(new CanvasTranslateOperation(vector));
         public void Rotate(float angle) => Operations.Add(new CanvasRotateOperation(angle));

+ 19 - 6
Source/QuestPDF/Drawing/FreeCanvas.cs

@@ -1,4 +1,5 @@
-using QuestPDF.Infrastructure;
+using System.Collections.Generic;
+using QuestPDF.Infrastructure;
 using QuestPDF.Skia;
 using QuestPDF.Skia.Text;
 
@@ -6,6 +7,13 @@ namespace QuestPDF.Drawing
 {
     internal sealed class FreeCanvas : ICanvas, IRenderingCanvas
     {
+        #region Internal State
+
+        private Stack<CanvasMatrix> MatrixStack { get; set; } = new();
+        private CanvasMatrix Matrix { get; set; } = new();
+
+        #endregion
+        
         #region IRenderingCanvas
 
         public bool DocumentContentHasLayoutOverflowIssues { get; set; }
@@ -41,17 +49,17 @@ namespace QuestPDF.Drawing
 
         public void Save()
         {
-            
+            MatrixStack.Push(Matrix);
         }
 
         public void Restore()
         {
-            
+            Matrix = MatrixStack.Pop();
         }
         
         public void Translate(Position vector)
         {
-            
+            Matrix = Matrix.Translate(vector.X, vector.Y);
         }
 
         public void DrawFilledRectangle(Position vector, Size size, Color color)
@@ -121,12 +129,17 @@ namespace QuestPDF.Drawing
 
         public void Rotate(float angle)
         {
-            
+            Matrix = Matrix.Rotate(angle);
         }
 
         public void Scale(float scaleX, float scaleY)
         {
-            
+            Matrix = Matrix.Scale(scaleX, scaleY);
+        }
+        
+        public CanvasMatrix GetCurrentMatrix()
+        {
+            return Matrix;
         }
 
         #endregion

+ 5 - 0
Source/QuestPDF/Drawing/ProxyCanvas.cs

@@ -97,4 +97,9 @@ internal class ProxyCanvas : ICanvas
     {
         Target.Scale(scaleX, scaleY);
     }
+    
+    public CanvasMatrix GetCurrentMatrix()
+    {
+        return Target.GetCurrentMatrix();
+    }
 }

+ 6 - 0
Source/QuestPDF/Drawing/SkiaCanvasBase.cs

@@ -67,6 +67,12 @@ namespace QuestPDF.Drawing
         {
             Canvas.Translate(vector.X, vector.Y);
         }
+        
+        public CanvasMatrix GetCurrentMatrix()
+        {
+            var matrix = Canvas.GetCurrentTotalMatrix();
+            return new CanvasMatrix(matrix.ScaleX, matrix.ScaleY, matrix.TranslateX, matrix.TranslateY, 0, 0); // TODO: capture skew value from native code
+        }
 
         public void DrawFilledRectangle(Position vector, Size size, Color color)
         {

+ 3 - 5
Source/QuestPDF/Elements/ElementLocationCapturer.cs

@@ -13,12 +13,10 @@ internal class ElementLocationCapturer : ContainerElement, IContentDirectionAwar
     {
         base.Draw(availableSpace);
         
-        var canvas = Canvas as SkiaCanvasBase;
-        
-        if (canvas == null)
+        if (!PageContext.IsInitialRenderingPhase)
             return;
-        
-        var matrix = canvas.Canvas.GetCurrentTotalMatrix();
+
+        var matrix = Canvas.GetCurrentMatrix();
         var size = Child?.Measure(availableSpace) ?? SpacePlan.Empty();
 
         var position = new PageElementLocation

+ 52 - 0
Source/QuestPDF/Infrastructure/CanvasMatrix.cs

@@ -0,0 +1,52 @@
+using System;
+
+namespace QuestPDF.Infrastructure;
+
+internal readonly struct CanvasMatrix
+{
+    public readonly float ScaleX;
+    public readonly float ScaleY;
+    
+    public readonly float TranslateX;
+    public readonly float TranslateY;
+    
+    public readonly float SkewX;
+    public readonly float SkewY;
+    
+    public CanvasMatrix(float scaleX, float scaleY, float translateX, float translateY, float skewX, float skewY)
+    {
+        ScaleX = scaleX;
+        ScaleY = scaleY;
+        TranslateX = translateX;
+        TranslateY = translateY;
+        SkewX = skewX;
+        SkewY = skewY;
+    }
+    
+    public CanvasMatrix Translate(float x, float y)
+    {
+        return new CanvasMatrix(ScaleX, ScaleY, TranslateX + x, TranslateY + y, SkewX, SkewY);
+    }
+    
+    public CanvasMatrix Scale(float x, float y)
+    {
+        return new CanvasMatrix(ScaleX * x, ScaleY * y, TranslateX, TranslateY, SkewX, SkewY);
+    }
+    
+    public CanvasMatrix Rotate(float angle)
+    {
+        var radians = angle * MathF.PI / 180;
+        
+        var sin = MathF.Sin(radians);
+        var cos = MathF.Cos(radians);
+        
+        return new CanvasMatrix(
+            ScaleX * cos - ScaleY * sin,
+            ScaleX * sin + ScaleY * cos,
+            TranslateX,
+            TranslateY,
+            SkewX,
+            SkewY
+        );
+    }
+}

+ 1 - 0
Source/QuestPDF/Infrastructure/ICanvas.cs

@@ -7,6 +7,7 @@ namespace QuestPDF.Infrastructure
     {
         void Save();
         void Restore();
+        CanvasMatrix GetCurrentMatrix();
         
         void Translate(Position vector);