From 69b54eed5da86b599d949c55361316cfc5e10814 Mon Sep 17 00:00:00 2001 From: MarcinZiabek Date: Sun, 12 Jun 2022 15:13:26 +0200 Subject: [PATCH] General concept of RTL support --- QuestPDF.Examples/TextShapingTests.cs | 84 ++++++------------- .../Layouts/StandardReport.cs | 3 +- QuestPDF/Drawing/DocumentGenerator.cs | 18 ++++ QuestPDF/Drawing/Proxy/DirectionProxy.cs | 9 ++ QuestPDF/Drawing/TextShaper.cs | 1 + QuestPDF/Elements/ContentDirection.cs | 9 ++ QuestPDF/Elements/Page.cs | 3 + QuestPDF/Elements/Text/TextBlock.cs | 12 ++- QuestPDF/Fluent/ContentDirectionExtensions.cs | 29 +++++++ QuestPDF/Fluent/PageExtensions.cs | 10 +++ .../Infrastructure/ContentDirectionType.cs | 8 ++ .../Infrastructure/HorizontalAlignment.cs | 5 +- .../Infrastructure/IContentDirectionAware.cs | 7 ++ 13 files changed, 135 insertions(+), 63 deletions(-) create mode 100644 QuestPDF/Drawing/Proxy/DirectionProxy.cs create mode 100644 QuestPDF/Elements/ContentDirection.cs create mode 100644 QuestPDF/Fluent/ContentDirectionExtensions.cs create mode 100644 QuestPDF/Infrastructure/ContentDirectionType.cs create mode 100644 QuestPDF/Infrastructure/IContentDirectionAware.cs diff --git a/QuestPDF.Examples/TextShapingTests.cs b/QuestPDF.Examples/TextShapingTests.cs index 90fa8a8..86e0306 100644 --- a/QuestPDF.Examples/TextShapingTests.cs +++ b/QuestPDF.Examples/TextShapingTests.cs @@ -8,58 +8,8 @@ namespace QuestPDF.Examples { public class TextShapingTests { - // [Test] - // public void ShapeText() - // { - // using var textPaint = new SKPaint - // { - // Color = SKColors.Black, - // Typeface = SKTypeface.CreateDefault(), - // IsAntialias = true, - // TextSize = 20 - // }; - // - // using var backgroundPaint = new SKPaint - // { - // Color = SKColors.LightGray - // }; - // - // RenderingTest - // .Create() - // .PageSize(550, 250) - // .ProduceImages() - // .ShowResults() - // .Render(container => - // { - // //var lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec odio ipsum, aliquam a neque a, lacinia vehicula lectus."; - // //var arabic = "ينا الألم. في بعض الأحيان ونظراً للالتزامات التي يفرضها علينا الواجب والعمل سنتنازل غالباً ونرفض الشعور"; - // - // var lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; - // var arabic = "ينا الألم. في بعض الأحيان ونظراً للالتزامات التي يفرضها علينا"; - // - // var text = arabic; - // var metrics = textPaint.FontMetrics; - // - // container - // .Padding(25) - // .Canvas((canvas, space) => - // { - // canvas.Translate(0, 20); - // - // var width = MeasureText(text, textPaint); - // var widthReal = textPaint.MeasureText(text); - // canvas.DrawRect(0, metrics.Descent, width, metrics.Ascent - metrics.Descent, backgroundPaint); - // - // canvas.DrawShapedText(text, 0, 0, textPaint); - // - // canvas.Translate(0, 40); - // canvas.DrawText(text, 0, 0, textPaint); - // }); - // }); - // } - [Test] - public void MeasureTest() + public void ShapeText() { using var textPaint = new SKPaint { @@ -68,12 +18,30 @@ namespace QuestPDF.Examples IsAntialias = true, TextSize = 20 }; - - var lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec odio ipsum, aliquam a neque a, lacinia vehicula lectus."; - var arabic = "ينا الألم. في بعض الأحيان ونظراً للالتزامات التي يفرضها علينا"; - // 012345678901234567890123456789012345678901234567890123456 - var shaper = new SKShaper(textPaint.Typeface); - var result = shaper.Shape(lorem, textPaint); - } + + using var backgroundPaint = new SKPaint + { + Color = SKColors.LightGray + }; + + RenderingTest + .Create() + .PageSize(550, 250) + .ProduceImages() + .ShowResults() + .Render(container => + { + //var lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec odio ipsum, aliquam a neque a, lacinia vehicula lectus."; + //var arabic = "ينا الألم. في بعض الأحيان ونظراً للالتزامات التي يفرضها علينا الواجب والعمل سنتنازل غالباً ونرفض الشعور"; + + var lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; + var arabic = "ينا الألم. في بعض (5000) الأحيان ونظراً للالتزامات التي يفرضها علينا"; + + container + .Padding(25) + .Text(arabic) + .FontSize(25); + }); + } } } \ No newline at end of file diff --git a/QuestPDF.ReportSample/Layouts/StandardReport.cs b/QuestPDF.ReportSample/Layouts/StandardReport.cs index a02730c..e1e5466 100644 --- a/QuestPDF.ReportSample/Layouts/StandardReport.cs +++ b/QuestPDF.ReportSample/Layouts/StandardReport.cs @@ -33,10 +33,11 @@ namespace QuestPDF.ReportSample.Layouts page.MarginHorizontal(50); page.Size(PageSizes.A4); + page.ContentDirectionRightToLeft(); page.Header().Element(ComposeHeader); page.Content().Element(ComposeContent); - + page.Footer().AlignCenter().Text(text => { text.CurrentPageNumber().Format(x => x?.FormatAsRomanNumeral() ?? "-----"); diff --git a/QuestPDF/Drawing/DocumentGenerator.cs b/QuestPDF/Drawing/DocumentGenerator.cs index 3fe373c..2fc5b6d 100644 --- a/QuestPDF/Drawing/DocumentGenerator.cs +++ b/QuestPDF/Drawing/DocumentGenerator.cs @@ -65,6 +65,7 @@ namespace QuestPDF.Drawing document.Compose(container); var content = container.Compose(); ApplyDefaultTextStyle(content, TextStyle.LibraryDefault); + ApplyContentDirection(content); var metadata = document.GetMetadata(); var pageContext = new PageContext(); @@ -199,5 +200,22 @@ namespace QuestPDF.Drawing foreach (var child in content.GetChildren()) ApplyDefaultTextStyle(child, targetTextStyle); } + + internal static void ApplyContentDirection(this Element? content, ContentDirectionType contentDirectionType = ContentDirectionType.LeftToRight) + { + if (content == null) + return; + + var targetDirection = contentDirectionType; + + if (content is ContentDirection contentDirection) + targetDirection = contentDirection.Direction; + + if (content is IContentDirectionAware contentDirectionAware) + contentDirectionAware.ContentDirection = targetDirection; + + foreach (var child in content.GetChildren()) + ApplyContentDirection(child, targetDirection); + } } } \ No newline at end of file diff --git a/QuestPDF/Drawing/Proxy/DirectionProxy.cs b/QuestPDF/Drawing/Proxy/DirectionProxy.cs new file mode 100644 index 0000000..14bbfe5 --- /dev/null +++ b/QuestPDF/Drawing/Proxy/DirectionProxy.cs @@ -0,0 +1,9 @@ +using QuestPDF.Infrastructure; + +namespace QuestPDF.Drawing.Proxy +{ + internal class DirectionProxy : ContainerElement + { + + } +} \ No newline at end of file diff --git a/QuestPDF/Drawing/TextShaper.cs b/QuestPDF/Drawing/TextShaper.cs index 8867f80..4b5831a 100644 --- a/QuestPDF/Drawing/TextShaper.cs +++ b/QuestPDF/Drawing/TextShaper.cs @@ -25,6 +25,7 @@ namespace QuestPDF.Drawing PopulateBufferWithText(buffer, text); buffer.GuessSegmentProperties(); + //buffer.Direction = Direction.RightToLeft; Font.Shape(buffer); diff --git a/QuestPDF/Elements/ContentDirection.cs b/QuestPDF/Elements/ContentDirection.cs new file mode 100644 index 0000000..3415c04 --- /dev/null +++ b/QuestPDF/Elements/ContentDirection.cs @@ -0,0 +1,9 @@ +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class ContentDirection : ContainerElement + { + public ContentDirectionType Direction { get; set; } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Page.cs b/QuestPDF/Elements/Page.cs index 275d876..f8fa8e9 100644 --- a/QuestPDF/Elements/Page.cs +++ b/QuestPDF/Elements/Page.cs @@ -19,6 +19,8 @@ namespace QuestPDF.Elements public float MarginBottom { get; set; } public string BackgroundColor { get; set; } = Colors.Transparent; + + public ContentDirectionType ContentDirection { get; set; } = ContentDirectionType.LeftToRight; public Element Background { get; set; } = Empty.Instance; public Element Foreground { get; set; } = Empty.Instance; @@ -30,6 +32,7 @@ namespace QuestPDF.Elements public void Compose(IContainer container) { container + .ContentDirection(ContentDirection) .Background(BackgroundColor) .Layers(layers => { diff --git a/QuestPDF/Elements/Text/TextBlock.cs b/QuestPDF/Elements/Text/TextBlock.cs index 5028f9e..ea7c7a8 100644 --- a/QuestPDF/Elements/Text/TextBlock.cs +++ b/QuestPDF/Elements/Text/TextBlock.cs @@ -8,11 +8,13 @@ using QuestPDF.Infrastructure; namespace QuestPDF.Elements.Text { - internal class TextBlock : Element, IStateResettable + internal class TextBlock : Element, IContentDirectionAware, IStateResettable { public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left; public List Items { get; set; } = new List(); + public ContentDirectionType ContentDirection { get; set; } + public string Text => string.Join(" ", Items.Where(x => x is TextBlockSpan).Cast().Select(x => x.Text)); private Queue RenderingQueue { get; set; } @@ -69,8 +71,12 @@ namespace QuestPDF.Elements.Text Canvas.Translate(new Position(alignmentOffset, 0)); Canvas.Translate(new Position(0, -line.Ascent)); - - foreach (var item in line.Elements) + + var elements = ContentDirection == ContentDirectionType.LeftToRight + ? line.Elements + : line.Elements.Reverse(); + + foreach (var item in elements) { var textDrawingRequest = new TextDrawingRequest { diff --git a/QuestPDF/Fluent/ContentDirectionExtensions.cs b/QuestPDF/Fluent/ContentDirectionExtensions.cs new file mode 100644 index 0000000..85c9aff --- /dev/null +++ b/QuestPDF/Fluent/ContentDirectionExtensions.cs @@ -0,0 +1,29 @@ +using System.Linq.Expressions; +using QuestPDF.Elements; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Fluent +{ + public static class ContentDirectionExtensions + { + internal static IContainer ContentDirection(this IContainer element, ContentDirectionType contentDirectionType) + { + var contentDirection = new ContentDirection + { + Direction = contentDirectionType + }; + + return element.Element(contentDirection); + } + + public static IContainer ContentDirectionLeftToRight(this IContainer element) + { + return element.ContentDirection(ContentDirectionType.LeftToRight); + } + + public static IContainer ContentDirectionRightToLeft(this IContainer element) + { + return element.ContentDirection(ContentDirectionType.RightToLeft); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Fluent/PageExtensions.cs b/QuestPDF/Fluent/PageExtensions.cs index 98d2b73..cd4aa69 100644 --- a/QuestPDF/Fluent/PageExtensions.cs +++ b/QuestPDF/Fluent/PageExtensions.cs @@ -88,6 +88,16 @@ namespace QuestPDF.Fluent DefaultTextStyle(handler(TextStyle.Default)); } + public void ContentDirectionLeftToRight() + { + Page.ContentDirection = ContentDirectionType.LeftToRight; + } + + public void ContentDirectionRightToLeft() + { + Page.ContentDirection = ContentDirectionType.RightToLeft; + } + public void PageColor(string color) { Page.BackgroundColor = color; diff --git a/QuestPDF/Infrastructure/ContentDirectionType.cs b/QuestPDF/Infrastructure/ContentDirectionType.cs new file mode 100644 index 0000000..8944259 --- /dev/null +++ b/QuestPDF/Infrastructure/ContentDirectionType.cs @@ -0,0 +1,8 @@ +namespace QuestPDF.Infrastructure +{ + public enum ContentDirectionType + { + LeftToRight, + RightToLeft + } +} \ No newline at end of file diff --git a/QuestPDF/Infrastructure/HorizontalAlignment.cs b/QuestPDF/Infrastructure/HorizontalAlignment.cs index 1341ca1..194a1e4 100644 --- a/QuestPDF/Infrastructure/HorizontalAlignment.cs +++ b/QuestPDF/Infrastructure/HorizontalAlignment.cs @@ -4,6 +4,9 @@ namespace QuestPDF.Infrastructure { Left, Center, - Right + Right, + + Start, + End } } \ No newline at end of file diff --git a/QuestPDF/Infrastructure/IContentDirectionAware.cs b/QuestPDF/Infrastructure/IContentDirectionAware.cs new file mode 100644 index 0000000..1b72241 --- /dev/null +++ b/QuestPDF/Infrastructure/IContentDirectionAware.cs @@ -0,0 +1,7 @@ +namespace QuestPDF.Infrastructure +{ + internal interface IContentDirectionAware + { + public ContentDirectionType ContentDirection { get; set; } + } +} \ No newline at end of file