diff --git a/QuestPDF.Examples/ElementExamples.cs b/QuestPDF.Examples/ElementExamples.cs index 45a37fe..79db6d1 100644 --- a/QuestPDF.Examples/ElementExamples.cs +++ b/QuestPDF.Examples/ElementExamples.cs @@ -38,7 +38,7 @@ namespace QuestPDF.Examples .Background("#DDD") .Padding(10) .ExtendVertical() - .Text(TextPlaceholder.LoremIpsum()); + .Text(Helpers.Placeholders.LoremIpsum()); }); } diff --git a/QuestPDF.Examples/FrameExample.cs b/QuestPDF.Examples/FrameExample.cs index 6e4598c..ff2af3d 100644 --- a/QuestPDF.Examples/FrameExample.cs +++ b/QuestPDF.Examples/FrameExample.cs @@ -34,8 +34,8 @@ namespace QuestPDF.Examples { stack.Element().Row(row => { - row.RelativeColumn(2).LabelCell().Text(TextPlaceholder.Label()); - row.RelativeColumn(3).ValueCell().Text(TextPlaceholder.Paragraph()); + row.RelativeColumn(2).LabelCell().Text(Placeholders.Label()); + row.RelativeColumn(3).ValueCell().Text(Placeholders.Paragraph()); }); } }); diff --git a/QuestPDF.ReportSample/DataSource.cs b/QuestPDF.ReportSample/DataSource.cs index 4061670..1117ad9 100644 --- a/QuestPDF.ReportSample/DataSource.cs +++ b/QuestPDF.ReportSample/DataSource.cs @@ -52,7 +52,7 @@ namespace QuestPDF.ReportSample return new ReportSection { - Title = TextPlaceholder.Label(), + Title = Placeholders.Label(), Parts = Enumerable.Range(0, sectionLength).Select(x => GetRandomElement()).ToList() }; } @@ -74,8 +74,8 @@ namespace QuestPDF.ReportSample { return new ReportSectionText { - Label = TextPlaceholder.Label(), - Text = TextPlaceholder.Paragraph() + Label = Placeholders.Label(), + Text = Placeholders.Paragraph() }; } @@ -113,7 +113,7 @@ namespace QuestPDF.ReportSample { PhotoData = Helpers.GetPhoto($"{photoId}.jpg"), - Comments = TextPlaceholder.Paragraph(), + Comments = Placeholders.Paragraph(), Date = DateTime.Now - TimeSpan.FromDays(Helpers.Random.NextDouble() * 100), Location = Helpers.RandomLocation(), diff --git a/QuestPDF.ReportSample/Layouts/PhotoTemplate.cs b/QuestPDF.ReportSample/Layouts/PhotoTemplate.cs index 460f0b9..5f703a8 100644 --- a/QuestPDF.ReportSample/Layouts/PhotoTemplate.cs +++ b/QuestPDF.ReportSample/Layouts/PhotoTemplate.cs @@ -30,14 +30,14 @@ namespace QuestPDF.ReportSample.Layouts container .Row(row => { - row.RelativeColumn(2).Component(new ImageTemplate(Model.PhotoData)); + row.RelativeColumn(2).AspectRatio(4 / 3f).Image(Placeholders.Image); row.RelativeColumn().PaddingLeft(5).Stack(stack => { stack.Spacing(7f); - stack.Element().Component(new ImageTemplate(Model.MapDetailsSource)); - stack.Element().Component(new ImageTemplate(Model.MapContextSource)); + stack.Element().AspectRatio(4 / 3f).Image(Placeholders.Image); + stack.Element().AspectRatio(4 / 3f).Image(Placeholders.Image); }); }); } diff --git a/QuestPDF.ReportSample/Layouts/SectionTemplate.cs b/QuestPDF.ReportSample/Layouts/SectionTemplate.cs index bc1f790..0bc3582 100644 --- a/QuestPDF.ReportSample/Layouts/SectionTemplate.cs +++ b/QuestPDF.ReportSample/Layouts/SectionTemplate.cs @@ -59,7 +59,7 @@ namespace QuestPDF.ReportSample.Layouts { stack.Spacing(5); - stack.Element().Component(new ImageTemplate(model.ImageSource)); + stack.Element().MaxWidth(250).AspectRatio(4 / 3f).Image(Placeholders.Image); stack.Element().Text(model.Location.Format(), Typography.Normal); }); } @@ -72,12 +72,12 @@ namespace QuestPDF.ReportSample.Layouts return; } - container.Debug().Debug().Grid(grid => + container.Debug("Photo gallery").Grid(grid => { grid.Spacing(5); grid.Columns(3); - model.Photos.ForEach(x => grid.Element().Component(new ImageTemplate(x))); + model.Photos.ForEach(x => grid.Element().AspectRatio(4 / 3f).Image(Placeholders.Image)); }); } } diff --git a/QuestPDF.ReportSample/Tests.cs b/QuestPDF.ReportSample/Tests.cs index 34e6ef4..8d7bd3f 100644 --- a/QuestPDF.ReportSample/Tests.cs +++ b/QuestPDF.ReportSample/Tests.cs @@ -26,7 +26,7 @@ namespace QuestPDF.ReportSample public void PerformanceBenchmark() { // test size - const int testSize = 250; + const int testSize = 100; const decimal performanceTarget = 5; // documents per second // create report models diff --git a/QuestPDF.UnitTests/BackgroundTests.cs b/QuestPDF.UnitTests/BackgroundTests.cs index 14ad989..a99575b 100644 --- a/QuestPDF.UnitTests/BackgroundTests.cs +++ b/QuestPDF.UnitTests/BackgroundTests.cs @@ -1,5 +1,6 @@ using NUnit.Framework; using QuestPDF.Elements; +using QuestPDF.Helpers; using QuestPDF.Infrastructure; using QuestPDF.UnitTests.TestEngine; @@ -17,10 +18,10 @@ namespace QuestPDF.UnitTests TestPlan .For(x => new Background { - Color = "#F00" + Color = Colors.Red.Medium }) .DrawElement(new Size(400, 300)) - .ExpectCanvasDrawRectangle(new Position(0, 0), new Size(400, 300), "#F00") + .ExpectCanvasDrawRectangle(new Position(0, 0), new Size(400, 300), Colors.Red.Medium) .CheckDrawResult(); } @@ -30,11 +31,11 @@ namespace QuestPDF.UnitTests TestPlan .For(x => new Background { - Color = "#F00", + Color = Colors.Red.Medium, Child = x.CreateChild() }) .DrawElement(new Size(400, 300)) - .ExpectCanvasDrawRectangle(new Position(0, 0), new Size(400, 300), "#F00") + .ExpectCanvasDrawRectangle(new Position(0, 0), new Size(400, 300), Colors.Red.Medium) .ExpectChildDraw(new Size(400, 300)) .CheckDrawResult(); } diff --git a/QuestPDF.UnitTests/BorderTests.cs b/QuestPDF.UnitTests/BorderTests.cs index 26c481b..611ad11 100644 --- a/QuestPDF.UnitTests/BorderTests.cs +++ b/QuestPDF.UnitTests/BorderTests.cs @@ -1,6 +1,7 @@ using NUnit.Framework; using QuestPDF.Drawing.SpacePlan; using QuestPDF.Elements; +using QuestPDF.Helpers; using QuestPDF.Infrastructure; using QuestPDF.UnitTests.TestEngine; @@ -41,16 +42,16 @@ namespace QuestPDF.UnitTests Bottom = 30, Left = 40, - Color = "#FF0000", + Color = Colors.Red.Medium, Child = x.CreateChild() }) .DrawElement(new Size(400, 300)) .ExpectChildDraw(new Size(400, 300)) - .ExpectCanvasDrawRectangle(new Position(-20, -5), new Size(430, 10), "#FF0000") // top - .ExpectCanvasDrawRectangle(new Position(-20, -5), new Size(40, 320), "#FF0000") // left - .ExpectCanvasDrawRectangle(new Position(-20, 285), new Size(430, 30), "#FF0000") // bottom - .ExpectCanvasDrawRectangle(new Position(390, -5), new Size(20, 320), "#FF0000") // right + .ExpectCanvasDrawRectangle(new Position(-20, -5), new Size(430, 10), Colors.Red.Medium) // top + .ExpectCanvasDrawRectangle(new Position(-20, -5), new Size(40, 320), Colors.Red.Medium) // left + .ExpectCanvasDrawRectangle(new Position(-20, 285), new Size(430, 30), Colors.Red.Medium) // bottom + .ExpectCanvasDrawRectangle(new Position(390, -5), new Size(20, 320), Colors.Red.Medium) // right .ExpectChildDraw(new Size(400, 300)) .CheckDrawResult(); } diff --git a/QuestPDF.UnitTests/DebugTests.cs b/QuestPDF.UnitTests/DebugTests.cs index b81c1f5..a01b64d 100644 --- a/QuestPDF.UnitTests/DebugTests.cs +++ b/QuestPDF.UnitTests/DebugTests.cs @@ -7,7 +7,6 @@ namespace QuestPDF.UnitTests [TestFixture] public class DebugTests { - [Test] - public void Measure_ShouldHandleNullChild() => new Debug().MeasureWithoutChild(); + } } \ No newline at end of file diff --git a/QuestPDF/Elements/Background.cs b/QuestPDF/Elements/Background.cs index c8f9b97..6e0c31f 100644 --- a/QuestPDF/Elements/Background.cs +++ b/QuestPDF/Elements/Background.cs @@ -1,10 +1,11 @@ +using QuestPDF.Helpers; using QuestPDF.Infrastructure; namespace QuestPDF.Elements { internal class Background : ContainerElement { - public string Color { get; set; } = "#00000000"; + public string Color { get; set; } = Colors.Black; internal override void Draw(ICanvas canvas, Size availableSpace) { diff --git a/QuestPDF/Elements/Border.cs b/QuestPDF/Elements/Border.cs index 8420a1c..dd30ac0 100644 --- a/QuestPDF/Elements/Border.cs +++ b/QuestPDF/Elements/Border.cs @@ -1,10 +1,11 @@ +using QuestPDF.Helpers; using QuestPDF.Infrastructure; namespace QuestPDF.Elements { internal class Border : ContainerElement { - public string Color { get; set; } = "#000000"; + public string Color { get; set; } = Colors.Black; public float Top { get; set; } public float Right { get; set; } diff --git a/QuestPDF/Elements/Box.cs b/QuestPDF/Elements/Box.cs new file mode 100644 index 0000000..af4942b --- /dev/null +++ b/QuestPDF/Elements/Box.cs @@ -0,0 +1,17 @@ +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class Box : ContainerElement + { + internal override void Draw(ICanvas canvas, Size availableSpace) + { + var targetSize = Child?.Measure(availableSpace) as Size; + + if (targetSize == null) + return; + + Child?.Draw(canvas, targetSize); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Debug.cs b/QuestPDF/Elements/Debug.cs index bfdbadf..d321856 100644 --- a/QuestPDF/Elements/Debug.cs +++ b/QuestPDF/Elements/Debug.cs @@ -1,37 +1,36 @@ using QuestPDF.Fluent; +using QuestPDF.Helpers; using QuestPDF.Infrastructure; +using SkiaSharp; namespace QuestPDF.Elements { - internal class Debug : ContainerElement + internal class Debug : IComponent { - private static readonly TextStyle TextStyle = TextStyle.Default.Color("#F00").FontType("Consolas").Size(10); + public IElement? Child { get; set; } - internal override void Draw(ICanvas canvas, Size availableSpace) + public string Text { get; set; } + public string Color { get; set; } = Colors.Red.Medium; + public void Compose(IContainer container) { - Child?.Draw(canvas, availableSpace); - DrawBoundingBox(); - DrawDimensions(); - - void DrawBoundingBox() - { - // TODO: when layer element is done, move this code into fluent API - - var container = new Container(); - - container - .Border(1) - .BorderColor("#FF0000") - .Background("#33FF0000"); - - container.Draw(canvas, availableSpace); - } - - void DrawDimensions() - { - canvas.DrawText($"W: {availableSpace.Width:F1}", new Position(5, 12), TextStyle); - canvas.DrawText($"H: {availableSpace.Height:F1}", new Position(5, 22), TextStyle); - } + var backgroundColor = SKColor.Parse(Color).WithAlpha(75).ToString(); + + container + .Border(1) + .BorderColor(Color) + .Layers(layers => + { + layers.PrimaryLayer().Element(Child); + layers.Layer().Background(backgroundColor); + + layers + .Layer() + .ShowIf(!string.IsNullOrWhiteSpace(Text)) + .Box() + .Background(Colors.White) + .Padding(2) + .Text(Text, TextStyle.Default.Color(Color).FontType("Consolas").Size(8)); + }); } } } \ No newline at end of file diff --git a/QuestPDF/Elements/Placeholder.cs b/QuestPDF/Elements/Placeholder.cs index 1e2e3c1..ff4a5d6 100644 --- a/QuestPDF/Elements/Placeholder.cs +++ b/QuestPDF/Elements/Placeholder.cs @@ -1,10 +1,12 @@ using QuestPDF.Fluent; +using QuestPDF.Helpers; using QuestPDF.Infrastructure; namespace QuestPDF.Elements { internal class Placeholder : IComponent { + public string Text { get; set; } private static readonly byte[] ImageData; static Placeholder() @@ -15,11 +17,18 @@ namespace QuestPDF.Elements public void Compose(IContainer container) { container - .Background("CCC") + .Background(Colors.Grey.Lighten2) .AlignMiddle() .AlignCenter() + .Padding(5) .MaxHeight(32) - .Image(ImageData, ImageScaling.FitArea); + .Element(x => + { + if (string.IsNullOrWhiteSpace(Text)) + x.Image(ImageData, ImageScaling.FitArea); + else + x.Text(Text, TextStyle.Default.Size(14).SemiBold()); + }); } } } \ No newline at end of file diff --git a/QuestPDF/Fluent/DebugExtensions.cs b/QuestPDF/Fluent/DebugExtensions.cs new file mode 100644 index 0000000..a1660cb --- /dev/null +++ b/QuestPDF/Fluent/DebugExtensions.cs @@ -0,0 +1,33 @@ +using QuestPDF.Elements; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Fluent +{ + public static class DebugExtensions + { + public static IContainer Debug(this IContainer parent, string text, string color) + { + var container = new Container(); + + parent.Component(new Debug + { + Child = container, + Text = text, + Color = color + }); + + return container; + } + + public static IContainer Debug(this IContainer parent, string text) + { + return parent.Debug(text, Colors.Red.Medium); + } + + public static IContainer Debug(this IContainer parent) + { + return parent.Debug(string.Empty, Colors.Red.Medium); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Fluent/ElementExtensions.cs b/QuestPDF/Fluent/ElementExtensions.cs index c1981e4..1702dbf 100644 --- a/QuestPDF/Fluent/ElementExtensions.cs +++ b/QuestPDF/Fluent/ElementExtensions.cs @@ -1,6 +1,7 @@ using System; using QuestPDF.Drawing.Exceptions; using QuestPDF.Elements; +using QuestPDF.Helpers; using QuestPDF.Infrastructure; namespace QuestPDF.Fluent @@ -40,11 +41,6 @@ namespace QuestPDF.Fluent { return handler(parent); } - - public static IContainer Debug(this IContainer parent) - { - return parent.Element(new Debug()); - } public static void PageNumber(this IContainer element, string textFormat = "{number}", TextStyle? style = null) { @@ -71,10 +67,13 @@ namespace QuestPDF.Fluent Color = color }); } - - public static void Placeholder(this IContainer element) + + public static void Placeholder(this IContainer element, string? text = null) { - element.Component(); + element.Component(new Elements.Placeholder + { + Text = text ?? string.Empty + }); } public static IContainer ShowOnce(this IContainer element) @@ -159,5 +158,10 @@ namespace QuestPDF.Fluent Handler = handler }); } + + public static IContainer Box(this IContainer element) + { + return element.Element(new Box()); + } } } \ No newline at end of file diff --git a/QuestPDF/Fluent/ImageExtensions.cs b/QuestPDF/Fluent/ImageExtensions.cs index d327bfe..7a3fe4e 100644 --- a/QuestPDF/Fluent/ImageExtensions.cs +++ b/QuestPDF/Fluent/ImageExtensions.cs @@ -37,7 +37,7 @@ namespace QuestPDF.Fluent } } - public static void DynamicImage(this IContainer element, Func imageSource) + public static void Image(this IContainer element, Func imageSource) { element.Element(new DynamicImage { diff --git a/QuestPDF/Helpers/TextPlaceholder.cs b/QuestPDF/Helpers/Placeholders.cs similarity index 59% rename from QuestPDF/Helpers/TextPlaceholder.cs rename to QuestPDF/Helpers/Placeholders.cs index e057c80..c7e7bec 100644 --- a/QuestPDF/Helpers/TextPlaceholder.cs +++ b/QuestPDF/Helpers/Placeholders.cs @@ -1,9 +1,12 @@ using System; using System.Linq; +using QuestPDF.Drawing; +using QuestPDF.Infrastructure; +using SkiaSharp; namespace QuestPDF.Helpers { - public static class TextPlaceholder + public static class Placeholders { private static Random Random = new Random(); @@ -148,5 +151,126 @@ namespace QuestPDF.Helpers public static string Percent() => (Random.NextDouble() * 100).ToString("N0") + "%"; #endregion + + #region Visual + + private static readonly string[] BackgroundColors = + { + Colors.Red.Lighten2, + Colors.Pink.Lighten2, + Colors.Purple.Lighten2, + Colors.DeepPurple.Lighten2, + Colors.Indigo.Lighten2, + Colors.Blue.Lighten2, + Colors.LightBlue.Lighten2, + Colors.Cyan.Lighten2, + Colors.Teal.Lighten2, + Colors.Green.Lighten2, + Colors.LightGreen.Lighten2, + Colors.Lime.Lighten2, + Colors.Yellow.Lighten2, + Colors.Amber.Lighten2, + Colors.Orange.Lighten2, + Colors.DeepOrange.Lighten2, + Colors.Brown.Lighten2, + Colors.Grey.Lighten2, + Colors.BlueGrey.Lighten2 + }; + + public static string BackgroundColor() + { + var index = Random.Next(0, BackgroundColors.Length); + return BackgroundColors[index]; + } + + public static string Color() + { + var colors = new[] + { + Colors.Red.Medium, + Colors.Pink.Medium, + Colors.Purple.Medium, + Colors.DeepPurple.Medium, + Colors.Indigo.Medium, + Colors.Blue.Medium, + Colors.LightBlue.Medium, + Colors.Cyan.Medium, + Colors.Teal.Medium, + Colors.Green.Medium, + Colors.LightGreen.Medium, + Colors.Lime.Medium, + Colors.Yellow.Medium, + Colors.Amber.Medium, + Colors.Orange.Medium, + Colors.DeepOrange.Medium, + Colors.Brown.Medium, + Colors.Grey.Medium, + Colors.BlueGrey.Medium + }; + + var index = Random.Next(0, colors.Length); + return colors[index]; + } + + public static byte[] Image(Size size) + { + // shuffle corner positions + var targetPositions = new[] + { + new SKPoint(0, 0), + new SKPoint(size.Width, 0), + new SKPoint(0, size.Height), + new SKPoint(size.Width, size.Height) + }; + + var positions = targetPositions + .OrderBy(x => Random.Next()) + .ToList(); + + // rand and shuffle colors + var colors = BackgroundColors + .OrderBy(x => Random.Next()) + .Take(4) + .Select(SKColor.Parse) + .ToArray(); + + // create image with white background + var imageInfo = new SKImageInfo((int)size.Width, (int)size.Height); + using var surface = SKSurface.Create(imageInfo); + + using var backgroundPaint = new SKPaint + { + Color = SKColors.White + }; + + surface.Canvas.DrawRect(0, 0, size.Width, size.Height, backgroundPaint); + + // draw gradient + SKShader GetForegroundShader(int index) + { + var radius = Math.Max(size.Width, size.Height); + var color = colors[index]; + + return SKShader.CreateRadialGradient( + positions[index], radius, + new[] {color, color.WithAlpha(0)}, new[] {0, 1f}, + SKShaderTileMode.Decal); + } + + using var shaderPaint = new SKPaint + { + Shader = SKShader.CreateCompose( + SKShader.CreateCompose(GetForegroundShader(0), GetForegroundShader(1)), + SKShader.CreateCompose(GetForegroundShader(2), GetForegroundShader(3))) + }; + + surface.Canvas.DrawRect(0, 0, size.Width, size.Height, shaderPaint); + + // return result as an image + surface.Canvas.Save(); + return surface.Snapshot().Encode(SKEncodedImageFormat.Jpeg, 90).ToArray(); + } + + #endregion } } \ No newline at end of file diff --git a/QuestPDF/Infrastructure/TextStyle.cs b/QuestPDF/Infrastructure/TextStyle.cs index 4e3304b..d12e82b 100644 --- a/QuestPDF/Infrastructure/TextStyle.cs +++ b/QuestPDF/Infrastructure/TextStyle.cs @@ -1,9 +1,11 @@ -namespace QuestPDF.Infrastructure +using QuestPDF.Helpers; + +namespace QuestPDF.Infrastructure { public class TextStyle { - internal string Color { get; set; } = "#000000"; - internal string FontType { get; set; } = "Helvetica"; + internal string Color { get; set; } = Colors.Black; + internal string FontType { get; set; } = "Calibri"; internal float Size { get; set; } = 12; internal float LineHeight { get; set; } = 1.2f; internal HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;