From 4f02fcfe6d72accae108b57f24df513b0a743c30 Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Fri, 18 Sep 2026 13:41:29 +0200 Subject: [PATCH 01/15] add overage to 90% --- .runsettings | 13 ++ CHANGELOG.md | 3 + pdfsharpdslTests/DrawingValueTests.cs | 90 ++++++++ pdfsharpdslTests/FormulaTests.cs | 88 ++++++- .../ReplayerTests/InstructionRecorderTests.cs | 87 +++++++ pdfsharpdslTests/VariablesDictionaryTests.cs | 84 +++++++ pdfsharpdslTests/VisitorTests.cs | 121 ++++++++++ tasks.md | 218 ++++++++++++++++++ 8 files changed, 703 insertions(+), 1 deletion(-) create mode 100644 .runsettings create mode 100644 pdfsharpdslTests/DrawingValueTests.cs create mode 100644 pdfsharpdslTests/VariablesDictionaryTests.cs create mode 100644 tasks.md diff --git a/.runsettings b/.runsettings new file mode 100644 index 0000000..4e5d087 --- /dev/null +++ b/.runsettings @@ -0,0 +1,13 @@ + + + + + + + cobertura + [PdfSharpDslCore]* + + + + + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index b7cdfb9..98fe6d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Change log +## Version 1.0.5 (March 3, 2024) +* Update nugets packages and upgrade to .Net 8 + ## Version 1.0.4 (May 19, 2023) * Add callback onNewpage in order to draw a custom template on each page. - Define UDF "__ONNEWPAGE()" in .ipdf file or register it via method 'RegisterCustomUdf(..)' in your dotnet language. diff --git a/pdfsharpdslTests/DrawingValueTests.cs b/pdfsharpdslTests/DrawingValueTests.cs new file mode 100644 index 0000000..f28a17d --- /dev/null +++ b/pdfsharpdslTests/DrawingValueTests.cs @@ -0,0 +1,90 @@ +using PdfSharpCore.Drawing; +using PdfSharpDslCore.Drawing; +using PdfSharpDslCore.Extensions; + +namespace pdfsharpdslTests +{ + public class DrawingValueTests + { + [Fact] + public void OffsetYMovesRectanglesAndPointsWithoutChangingInputs() + { + var rectangle = new XRect(10, 20, 30, 40); + var point = new XPoint(5, 6); + + var movedRectangle = rectangle.OffsetY(7); + var movedPoint = point.OffsetY(8); + + Assert.Equal(20, rectangle.Y); + Assert.Equal(27, movedRectangle.Y); + Assert.Equal(10, movedRectangle.X); + Assert.Equal(6, point.Y); + Assert.Equal(14, movedPoint.Y); + Assert.Equal(5, movedPoint.X); + } + + [Fact] + public void OffsetYMovesPointArraysOnlyWhenNeeded() + { + var points = new[] { new XPoint(1, 2), new XPoint(3, 4) }; + + var unchanged = points.OffsetY(0); + var moved = points.OffsetY(10); + + Assert.Same(points, unchanged); + Assert.NotSame(points, moved); + Assert.Equal(new[] { 12.0, 14.0 }, moved.Select(point => point.Y)); + Assert.Equal(new[] { 1.0, 3.0 }, moved.Select(point => point.X)); + } + + [Fact] + public void TableDefinitionCalculatesColumnDimensionsAndAlignment() + { + var table = new TableDefinition + { + TopMarginOnPageBreak = 12, + ShowHeader = false, + HeaderHeight = 20 + }; + table.Columns.Add(new ColumnDefinition + { + DesiredWidth = 80, + MaxWidth = 50, + Alignment = XStringAlignment.Center + }); + table.Columns.Add(new ColumnDefinition()); + table.Rows.Add(new RowDefinition + { + DesiredHeight = 15, + MaxHeight = 20, + Data = new[] { "value" } + }); + + Assert.Equal(80, table.ColWidth(0)); + Assert.Equal(0, table.ColWidth(1)); + Assert.Equal(50, table.ColMaxWidth(0, 100)); + Assert.Equal(40, table.ColMaxWidth(1, 40)); + Assert.Equal(XStringAlignment.Center, table.Alignment(0)); + Assert.Equal(12, table.TopMarginOnPageBreak); + Assert.False(table.ShowHeader); + Assert.Equal(20, table.HeaderHeight); + Assert.Equal(15, table.Rows[0].DesiredHeight); + Assert.Equal(20, table.Rows[0].MaxHeight); + Assert.Equal("value", table.Rows[0].Data[0]); + } + + [Fact] + public void DrawingResultStoresRectangleAndPageOffset() + { + var rectangle = new XRect(1, 2, 3, 4); + var result = new DrawingResult + { + DrawingRect = rectangle, + PageOffsetY = 25 + }; + + Assert.Equal(rectangle, result.DrawingRect); + Assert.Equal(25, result.PageOffsetY); + } + } +} \ No newline at end of file diff --git a/pdfsharpdslTests/FormulaTests.cs b/pdfsharpdslTests/FormulaTests.cs index 3449c2d..301f334 100644 --- a/pdfsharpdslTests/FormulaTests.cs +++ b/pdfsharpdslTests/FormulaTests.cs @@ -1,4 +1,5 @@ -using PdfSharpDslCore.Parser;/**/ +using PdfSharpDslCore.Evaluation; +using PdfSharpDslCore.Parser;/**/ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -66,5 +67,90 @@ public void ConvertTests() Assert.Equal(1.0, Convert.ToDouble(true)); Assert.Equal(0.0, Convert.ToDouble(false)); } + + [Theory] + [InlineData((int)BinaryOperation.Sub, 1.0)] + [InlineData((int)BinaryOperation.Mul, 30.0)] + [InlineData((int)BinaryOperation.Div, 1.2)] + [InlineData((int)BinaryOperation.Mod, 1.0)] + public void BinaryNumericOperations(int operation, double expected) + { + var evaluation = new BinaryEvaluation( + new ConstantEvaluation(6), + new ConstantEvaluation(5), + (BinaryOperation)operation); + + Assert.Equal(expected, evaluation.Value); + } + + [Theory] + [InlineData((int)BinaryOperation.And, true, false, false)] + [InlineData((int)BinaryOperation.Or, false, true, true)] + [InlineData((int)BinaryOperation.Superior, 2, 1, true)] + [InlineData((int)BinaryOperation.SuperiorOrEquals, 2, 2, true)] + [InlineData((int)BinaryOperation.Inferior, 1, 2, true)] + [InlineData((int)BinaryOperation.InferiorOrEquals, 2, 2, true)] + [InlineData((int)BinaryOperation.Equals, "same", "same", true)] + [InlineData((int)BinaryOperation.NotEquals, "left", "right", true)] + public void BinaryBooleanOperations(int operation, object left, object right, bool expected) + { + var evaluation = new BinaryEvaluation( + new ConstantEvaluation(left), + new ConstantEvaluation(right), + (BinaryOperation)operation); + + Assert.Equal(expected, evaluation.Value); + } + + [Fact] + public void BinaryOperationsReportInvalidOperands() + { + var nullOperand = new BinaryEvaluation( + new ConstantEvaluation(null!), + new ConstantEvaluation(1), + BinaryOperation.Add); + var unsupportedStringOperation = new BinaryEvaluation( + new ConstantEvaluation("left"), + new ConstantEvaluation("right"), + BinaryOperation.Sub); + var invalidComparison = new BinaryEvaluation( + new ConstantEvaluation("left"), + new ConstantEvaluation(1), + BinaryOperation.Superior); + + Assert.Throws(() => nullOperand.Value); + Assert.Throws(() => unsupportedStringOperation.Value); + Assert.Throws(() => invalidComparison.Value); + } + + [Theory] + [InlineData((int)BinaryOperation.Add, 5, 5.0)] + [InlineData((int)BinaryOperation.Sub, 5, -5.0)] + [InlineData((int)BinaryOperation.Add, "value", "value")] + [InlineData((int)BinaryOperation.Sub, "value", "-value")] + public void UnaryOperations(int operation, object value, object expected) + { + var evaluation = new UnaryEvaluation(new ConstantEvaluation(value), (BinaryOperation)operation); + + Assert.Equal(expected, evaluation.Value); + } + + [Fact] + public void UnaryOperationsReportInvalidOperands() + { + var nullOperand = new UnaryEvaluation(new ConstantEvaluation(null!), BinaryOperation.Add); + var unsupportedOperation = new UnaryEvaluation(new ConstantEvaluation(1), BinaryOperation.Mul); + + Assert.Throws(() => nullOperand.Value); + Assert.Throws(() => unsupportedOperation.Value); + } + + [Fact] + public void MissingVariableThrows() + { + var evaluation = new VariableEvaluation("missing", new Dictionary()); + + Assert.Throws(() => evaluation.Value); + } } } diff --git a/pdfsharpdslTests/ReplayerTests/InstructionRecorderTests.cs b/pdfsharpdslTests/ReplayerTests/InstructionRecorderTests.cs index e34d28a..2e55e26 100644 --- a/pdfsharpdslTests/ReplayerTests/InstructionRecorderTests.cs +++ b/pdfsharpdslTests/ReplayerTests/InstructionRecorderTests.cs @@ -9,6 +9,7 @@ using System.Text; using System.Threading.Tasks; using Irony; +using Microsoft.Extensions.Logging; using Xunit; namespace pdfsharpdslTests.ReplayerTests @@ -147,6 +148,92 @@ public void RecorderTests_block_with_offsetY() Assert.Equal(new XRect(0,100, 50, 100), block1.Rect); } + [Fact] + public void InstructionActionExecutesWithOffsetAndExposesMetadata() + { + var rectangle = new XRect(1, 2, 3, 4); + double? appliedOffset = null; + var instruction = new InstructionAction(offset => appliedOffset = offset, rectangle, "action"); + + var result = instruction.Draw(defaultDrawerMock().Object, 12, 99); + + Assert.Equal(rectangle, instruction.Rect); + Assert.Equal("action", instruction.Name); + Assert.Equal(12, appliedOffset); + Assert.Equal(0, result); + } + + [Fact] + public void RecorderRootRejectsInstructionsAndBlockMetadataCanBeCleared() + { + var recorder = new BlocksRecorder(); + var instruction = new DummyInstruction(new XRect(0, 0, 10, 10)); + + Assert.False(recorder.CanPushInstruction); + Assert.Throws(() => recorder.CurrentBlock.PushInstruction(instruction)); + + var block = recorder.OpenBlock("named", 15, false, 4); + block.PushInstruction(instruction, false); + + Assert.True(recorder.CanPushInstruction); + Assert.Equal("named", block.Name); + Assert.Equal(15, block.OffsetY); + Assert.NotNull(block.Parent); + Assert.True(block.Rect.IsEmpty); + Assert.Single(block.Instructions); + + block.Clear(); + Assert.Empty(block.Instructions); + } + + [Fact] + public void NestedBlockMovesToNextPage() + { + var logger = new Mock(); + logger.Setup(x => x.IsEnabled(LogLevel.Debug)).Returns(true); + var drawer = defaultDrawerMock(); + var recorder = new BlocksRecorder(logger.Object); + var outer = recorder.OpenBlock("outer", 0, false); + var child = outer.OpenBlock("child", 250, true); + var instruction = new DummyInstruction(new XRect(0, 0, 50, 50)); + child.PushInstruction(instruction); + + var pageOffset = outer.Draw(drawer.Object, 0, 0); + + drawer.Verify(x => x.NewPage(null, null), Times.Once); + Assert.Equal(new XRect(0, 0, 50, 50), instruction.DrawingRect); + Assert.True(pageOffset > 0); + } + + [Fact] + public void OversizedNestedBlockCannotBePrintedEntirely() + { + var drawer = defaultDrawerMock(); + var recorder = new BlocksRecorder(); + var outer = recorder.OpenBlock("outer", 0, false); + var child = outer.OpenBlock("child", 0, true); + child.PushInstruction(new DummyInstruction(new XRect(0, 0, 50, 400))); + + Assert.Throws(() => outer.Draw(drawer.Object, 0, 0)); + } + + [Fact] + public void InstructionsCannotBeAddedWhileBlockIsDrawing() + { + var drawer = defaultDrawerMock(); + var recorder = new BlocksRecorder(); + var block = recorder.OpenBlock("block", 0, true); + var extraInstruction = new DummyInstruction(new XRect(0, 20, 10, 10)); + block.PushInstruction(new InstructionAction( + _ => block.PushInstruction(extraInstruction), + new XRect(0, 0, 10, 10), + "mutating")); + + block.Draw(drawer.Object, 0, 0); + + Assert.Single(block.Instructions); + } + private static void AddInstructions(IInstructionBlock block, int count, int height) { var r = new XRect(0, 0, 50, height); diff --git a/pdfsharpdslTests/VariablesDictionaryTests.cs b/pdfsharpdslTests/VariablesDictionaryTests.cs new file mode 100644 index 0000000..b1531e6 --- /dev/null +++ b/pdfsharpdslTests/VariablesDictionaryTests.cs @@ -0,0 +1,84 @@ +using System.Collections; +using PdfSharpDslCore.Parser; + +namespace pdfsharpdslTests +{ + public class VariablesDictionaryTests + { + [Fact] + public void DictionarySupportsMutationAndSystemVariables() + { + var requestedSystemVariables = new List(); + var variables = new VariablesDictionary(name => + { + requestedSystemVariables.Add(name); + return name == "PAGEWIDTH" ? 595 : 842; + }); + + variables.Add("FIRST", 1); + variables.Add(new KeyValuePair("SECOND", 2)); + + Assert.False(variables.IsReadOnly); + Assert.Equal(2, variables.Count); + Assert.Contains("FIRST", variables.Keys); + Assert.Contains(2, variables.Values); + Assert.True(variables.ContainsKey("SECOND")); + Assert.Equal(1, variables["FIRST"]); + Assert.Null(variables["MISSING"]); + Assert.Equal(595, variables["PAGEWIDTH"]); + Assert.Equal(842, variables["PAGEHEIGHT"]); + Assert.Equal(new[] { "PAGEWIDTH", "PAGEHEIGHT" }, requestedSystemVariables); + Assert.Throws(() => variables["FIRST"] = 3); + + Assert.True(variables.Remove("FIRST")); + Assert.False(variables.Remove("MISSING")); + variables.Clear(); + Assert.Empty(variables.Keys); + } + + [Fact] + public void SaveAndRestorePreserveOuterScope() + { + var variables = new VariablesDictionary(_ => 0); + variables.Add("VALUE", "outer"); + + variables.SaveVariables(); + variables.Add("VALUE", "inner"); + variables.Add("INNER_ONLY", true); + + Assert.Equal("inner", variables["VALUE"]); + variables.RestoreVariables(); + Assert.Equal("outer", variables["VALUE"]); + Assert.False(variables.ContainsKey("INNER_ONLY")); + } + + [Fact] + public void GlobalScopeDisablesSaveAndRestore() + { + var variables = new VariablesDictionary(_ => 0) + { + GlobalScope = true + }; + variables.Add("VALUE", "outer"); + + variables.SaveVariables(); + variables.Add("VALUE", "global"); + variables.RestoreVariables(); + + Assert.Equal("global", variables["VALUE"]); + } + + [Fact] + public void UnsupportedCollectionMembersThrow() + { + var variables = new VariablesDictionary(_ => 0); + var item = new KeyValuePair("VALUE", 1); + + Assert.Throws(() => variables.Contains(item)); + Assert.Throws(() => variables.CopyTo(new[] { item }, 0)); + Assert.Throws(() => variables.Remove(item)); + Assert.Throws(() => variables.GetEnumerator()); + Assert.Throws(() => ((IEnumerable)variables).GetEnumerator()); + } + } +} \ No newline at end of file diff --git a/pdfsharpdslTests/VisitorTests.cs b/pdfsharpdslTests/VisitorTests.cs index 02dfacb..78344a2 100644 --- a/pdfsharpdslTests/VisitorTests.cs +++ b/pdfsharpdslTests/VisitorTests.cs @@ -102,5 +102,126 @@ public void TestConditionEvaluator(string file) Assert.Equal("OK", udF.Value[0]); } } + + [Fact] + public void DrawExecutesViewSizeTextWidthAndDebugOptions() + { + var tree = ParseText("VIEWSIZE 100,140;TEXT 10,20 MaxWidth=30 Text=\"hello\";DEBUGOPTIONS DEBUG_TEXT, DEBUG_RECT, DEBUG_ROWTEMPLATE, DEBUG_RULE, DEBUG_ALL, UNKNOWN;"); + var drawer = new Mock(); + drawer.SetupProperty(x => x.DebugOptions); + + new PdfDrawerVisitor().Draw(drawer.Object, tree); + + drawer.Verify(x => x.SetViewSize(100, 140), Times.Once); + drawer.Verify(x => x.DrawText("hello", 10, 20, 30, null), Times.Once); + Assert.Equal( + DebugOptions.DebugText | DebugOptions.DebugRect | DebugOptions.DebugRowTemplate | DebugOptions.DebugRule | DebugOptions.DebugAll, + drawer.Object.DebugOptions); + } + + [Fact] + public void DrawResolvesPageSystemVariables() + { + var tree = ParseText("SET VAR WIDTH=$PAGEWIDTH;SET VAR HEIGHT=$PAGEHEIGHT;"); + var drawer = new Mock(); + drawer.SetupGet(x => x.PageWidth).Returns(612); + drawer.SetupGet(x => x.PageHeight).Returns(792); + var visitor = new InspectablePdfDrawerVisitor(); + + visitor.Draw(drawer.Object, tree); + + Assert.Equal(612.0, visitor.Vars["WIDTH"]); + Assert.Equal(792.0, visitor.Vars["HEIGHT"]); + } + + [Fact] + public void CustomUdfCanFallBackToDslBodyOrOverrideIt() + { + var fallbackTree = ParseText("UDF SAMPLE(X) LINE $X,0,$X,1; ENDUDF CALL SAMPLE(3);"); + var overrideTree = ParseText("CALL CUSTOM(5);"); + var drawer = new Mock(); + var visitor = new InspectablePdfDrawerVisitor(); + string[]? parameterNames = null; + object?[]? parameterValues = null; + visitor.RegisterCustomUdf("sample", (_, names, values) => + { + parameterNames = names; + parameterValues = values; + return false; + }); + visitor.RegisterCustomUdf("custom", (_, _, _) => false); + visitor.RegisterCustomUdf("CUSTOM", (_, _, values) => + { + parameterValues = values; + return true; + }); + + visitor.Draw(drawer.Object, fallbackTree); + visitor.Draw(drawer.Object, overrideTree); + + Assert.Equal(new[] { "X" }, parameterNames); + Assert.Equal(5.0, Assert.Single(parameterValues!)); + drawer.Verify(x => x.DrawLine(3, 0, 3, 1), Times.Once); + } + + [Fact] + public void CustomUdfErrorsAreReportedAsParserErrors() + { + var customFailureTree = ParseText("CALL CUSTOM();"); + var missingTree = ParseText("CALL MISSING();"); + var drawer = new Mock(); + var visitor = new InspectablePdfDrawerVisitor(); + visitor.RegisterCustomUdf("CUSTOM", (_, _, _) => throw new InvalidOperationException("failure")); + + var customError = Assert.Throws(() => visitor.Draw(drawer.Object, customFailureTree)); + var missingError = Assert.Throws(() => visitor.Draw(drawer.Object, missingTree)); + + Assert.IsType(customError.InnerException); + Assert.Contains("MISSING", missingError.Message); + } + + [Fact] + public void RowTemplateTracksOffsetsAndFinalHeight() + { + var tree = ParseText("ROWTEMPLATE Count=2 Y=10 Name=\"row\" BorderSize=2 NewPageTopMargin=5 LINE 0,0,10,10; ENDROWTEMPLATE"); + var drawer = new Mock(); + drawer.SetupSequence(x => x.EndDrawRowTemplate(It.IsAny())) + .Returns(new DrawingResult { DrawingRect = new XRect(0, 0, 10, 20), PageOffsetY = 0 }) + .Returns(new DrawingResult { DrawingRect = new XRect(0, 20, 10, 30), PageOffsetY = 10 }); + var visitor = new InspectablePdfDrawerVisitor(); + + visitor.Draw(drawer.Object, tree); + + drawer.Verify(x => x.BeginIterationTemplate(2), Times.Once); + drawer.Verify(x => x.BeginDrawRowTemplate("row", 0, 12, 5), Times.Once); + drawer.Verify(x => x.BeginDrawRowTemplate("row", 1, 34, 5), Times.Once); + drawer.Verify(x => x.EndIterationTemplate(42), Times.Once); + drawer.Verify(x => x.DrawLine(0, 0, 10, 10), Times.Exactly(2)); + Assert.Equal(42.0, visitor.Vars["LASTTEMPLATEHEIGHT"]); + Assert.False(visitor.Vars.ContainsKey("ROWINDEX")); + } + + [Fact] + public void OnNewPageUdfPersistsGlobalVariables() + { + var tree = ParseText("UDF __ONNEWPAGE() SET VAR SAVEDPAGE=$PAGEINDEX; ENDUDF"); + var drawer = new Mock(); + Action? onNewPage = null; + drawer.Setup(x => x.RegisterOnNewPage(It.IsAny>())) + .Callback>(callback => onNewPage = callback); + var visitor = new InspectablePdfDrawerVisitor(); + + visitor.Draw(drawer.Object, tree); + Assert.NotNull(onNewPage); + onNewPage!(4); + + Assert.Equal(4, visitor.Vars["PAGEINDEX"]); + Assert.Equal(4, visitor.Vars["SAVEDPAGE"]); + } + + private sealed class InspectablePdfDrawerVisitor : PdfDrawerVisitor + { + public IDictionary Vars => Variables; + } } } diff --git a/tasks.md b/tasks.md new file mode 100644 index 0000000..23f1dc6 --- /dev/null +++ b/tasks.md @@ -0,0 +1,218 @@ +# .NET 10 Upgrade Tasks + +Coverage work blocks the framework upgrade. `PdfSharpDslCore` line coverage must be at least 90%; branch coverage is reported but does not gate progress. + +**Current verified coverage (2026-09-18):** 90.76% lines (2260/2490), 83.56% branches (778/931), 83 tests passed. + +## TASK-001: Coverage baseline + +**Status:** Complete (2026-09-18) +**Depends on:** None + +**Objective:** Establish reproducible, core-only Cobertura reporting and record the current baseline. + +**Affected files:** `.runsettings`, `scripts/coverage.ps1`, `tasks.md` + +**Acceptance criteria:** + +- The test suite runs through Coverlet's collector. +- The report contains only the `PdfSharpDslCore` assembly. +- The script prints line and branch totals and fails when the report is missing or contains another assembly. +- Generated reports remain under the ignored `artifacts/` directory. + +**Validation:** + +```powershell +powershell -NoProfile -File .\scripts\coverage.ps1 +``` + +**Result:** 45 tests passed. Line coverage is 79.63% (1983/2490); branch coverage is 73.14% (681/931). The migration remains blocked. + +**Handoff:** Begin TASK-002. Raise core line coverage above the current baseline without changing target frameworks or enforcing the final threshold yet. + +## TASK-002: Evaluation coverage + +**Status:** Not started +**Depends on:** TASK-001 + +**Objective:** Test arithmetic, comparison, boolean, unary, null, string, and error paths; investigate the `%` parser/evaluator mismatch. + +**Affected files:** `pdfsharpdslTests/FormulaTests.cs`, `PdfSharpDslCore/Evaluation/`, parser tests and parser implementation if the mismatch is confirmed + +**Acceptance criteria:** Evaluation branches have focused tests, the `%` behavior is documented by a test and corrected if defective, and all existing tests pass. + +**Validation:** + +```powershell +dotnet test .\pdfsharpdslTests\pdfsharpdslTests.csproj --filter FullyQualifiedName~FormulaTests +powershell -NoProfile -File .\scripts\coverage.ps1 +``` + +**Handoff:** Record the new coverage result here and identify the largest remaining uncovered core areas for TASK-003. + +## TASK-003: Variables and UDFs + +**Status:** Not started +**Depends on:** TASK-002 + +**Objective:** Cover variable lifecycle, nested scopes, system variables, UDF dispatch, failure paths, and `__ONNEWPAGE`. + +**Affected files:** `pdfsharpdslTests/`, `PdfSharpDslCore/` variable and UDF implementation + +**Acceptance criteria:** Focused tests cover successful and failing variable/UDF behavior, including nested scope and new-page callbacks. + +**Validation:** + +```powershell +dotnet test .\pdfsharpdslTests\pdfsharpdslTests.csproj +powershell -NoProfile -File .\scripts\coverage.ps1 +``` + +**Handoff:** Record coverage and list remaining visitor or drawing gaps for TASK-004. + +## TASK-004: Visitor and drawing helpers + +**Status:** Not started +**Depends on:** TASK-003 + +**Objective:** Cover visitor dispatch, page defaults, debug options, tables, images, alignment, clipping, and width calculations. + +**Affected files:** `pdfsharpdslTests/VisitorTests.cs`, `pdfsharpdslTests/RenderingTests.cs`, `PdfSharpDslCore/Drawing/`, visitor implementation + +**Acceptance criteria:** Deterministic tests cover the listed helper paths without requiring committed generated PDFs. + +**Validation:** + +```powershell +dotnet test .\pdfsharpdslTests\pdfsharpdslTests.csproj +powershell -NoProfile -File .\scripts\coverage.ps1 +``` + +**Handoff:** Record coverage and enumerate only the gaps needed to exceed 80% in TASK-005. + +## TASK-005: Enforce coverage + +**Status:** Not started +**Depends on:** TASK-004 + +**Objective:** Cover remaining pagination and PDF drawing paths, then enforce core line coverage of at least 90%. + +**Affected files:** Remaining core tests, `scripts/coverage.ps1`, CI workflow if present + +**Acceptance criteria:** `PdfSharpDslCore` line coverage is at least 90%, the script fails below 90%, and branch coverage remains informational. + +**Validation:** + +```powershell +powershell -NoProfile -File .\scripts\coverage.ps1 +``` + +**Handoff:** Unblock TASK-006 only after recording a passing line rate here. + +## TASK-006: Generator safety + +**Status:** Blocked by coverage +**Depends on:** TASK-005 + +**Objective:** Remove the hard-coded Debug assembly path and add a real Roslyn generator compilation test. + +**Affected files:** `PdfSharpDslCore.Generator/`, `pdfsharpdslTests/SourceGenerator/` + +**Acceptance criteria:** Generator tests compile a representative input and Debug and Release builds succeed without configuration-specific paths. + +**Validation:** + +```powershell +dotnet test .\pdfsharpdslTests\pdfsharpdslTests.csproj --filter FullyQualifiedName~SourceGenerator +dotnet build .\bnf_and_pdf.sln --configuration Debug +dotnet build .\bnf_and_pdf.sln --configuration Release +``` + +**Handoff:** Document generator loading assumptions before dependency upgrades. + +## TASK-007: Dependencies + +**Status:** Blocked by coverage +**Depends on:** TASK-006 + +**Objective:** Upgrade test, Coverlet, Roslyn, logging, image, font, and archive packages; resolve ImageSharp advisories. + +**Affected files:** Project files and package lock/restore outputs where applicable + +**Acceptance criteria:** Restored packages have no known ImageSharp advisories, core and generator retain `netstandard2.0`, and tests and coverage remain passing. + +**Validation:** + +```powershell +dotnet restore .\bnf_and_pdf.sln +dotnet list .\bnf_and_pdf.sln package --vulnerable --include-transitive +dotnet test .\pdfsharpdslTests\pdfsharpdslTests.csproj +powershell -NoProfile -File .\scripts\coverage.ps1 +``` + +**Handoff:** Record selected package versions and any .NET 10 compatibility constraints. + +## TASK-008: Framework migration + +**Status:** Blocked by coverage +**Depends on:** TASK-007 + +**Objective:** Pin .NET 10, move console and tests to `net10.0`, retain reusable projects on `netstandard2.0`, and update GitHub Actions. + +**Affected files:** `global.json`, console/test project files, `.github/workflows/` + +**Acceptance criteria:** The intended SDK is selected, console and tests target `net10.0`, core and generator target `netstandard2.0`, and workflows use `actions/setup-dotnet@v4` with `10.0.x`. + +**Validation:** + +```powershell +dotnet --version +dotnet build .\bnf_and_pdf.sln +dotnet test .\pdfsharpdslTests\pdfsharpdslTests.csproj +powershell -NoProfile -File .\scripts\coverage.ps1 +``` + +**Handoff:** Record SDK and target framework versions for package validation. + +## TASK-009: Package validation + +**Status:** Blocked by coverage +**Depends on:** TASK-008 + +**Objective:** Validate both configurations, NuGet packages, generator loading in a clean consumer, console samples, and rendering tests. + +**Affected files:** Project packaging metadata, test fixtures, console samples + +**Acceptance criteria:** Debug and Release builds pass, both packages can be packed, a clean `net10.0` consumer loads the generator, and samples/rendering tests pass. + +**Validation:** + +```powershell +dotnet build .\bnf_and_pdf.sln --configuration Debug +dotnet build .\bnf_and_pdf.sln --configuration Release +dotnet pack .\PdfSharpDslCore\PdfSharpDslCore.csproj --configuration Release +dotnet pack .\PdfSharpDslCore.Generator\PdfSharpDslCore.Generator.csproj --configuration Release +dotnet test .\pdfsharpdslTests\pdfsharpdslTests.csproj +``` + +**Handoff:** Record package paths and clean-consumer results for documentation. + +## TASK-010: Documentation + +**Status:** Blocked by coverage +**Depends on:** TASK-009 + +**Objective:** Document the completed migration, framework support, and coverage workflow. + +**Affected files:** `README.md`, `PdfSharpDslCore/Readme.md`, `CHANGELOG.md`, `tasks.md` + +**Acceptance criteria:** The root README links this tracker, supported frameworks and coverage commands are accurate, and the changelog summarizes the migration. + +**Validation:** + +```powershell +powershell -NoProfile -File .\scripts\coverage.ps1 +dotnet build .\bnf_and_pdf.sln --configuration Release +``` + +**Handoff:** Mark the roadmap complete only after all links, commands, and recorded versions are verified. \ No newline at end of file From 3938138912864014d17f4173056a08ce59376239 Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Fri, 18 Sep 2026 14:21:57 +0200 Subject: [PATCH 02/15] migration dotnet 10 + coverage up to 90% --- .github/workflows/build.yml | 4 +- .github/workflows/release-package.yml | 4 +- CHANGELOG.md | 6 + PdfSharpDslConsole/PdfSharpDslConsole.csproj | 4 +- .../PdfSharpDslCore.Generator.csproj | 20 ++-- PdfSharpDslCore/Drawing/DrawingHelper.cs | 4 +- PdfSharpDslCore/Evaluation/Evaluator.cs | 3 + PdfSharpDslCore/Parser/PdfDrawerVisitor.cs | 4 +- PdfSharpDslCore/Parser/PdfVisitor.cs | 6 +- PdfSharpDslCore/PdfSharpDslCore.csproj | 3 +- PdfSharpDslCore/Readme.md | 4 + README.md | 15 +++ global.json | 2 +- pdfsharpdslTests/DrawingValueTests.cs | 31 +++++ pdfsharpdslTests/FormulaTests.cs | 15 ++- pdfsharpdslTests/PdfDocumentDrawerTests.cs | 36 ++++++ .../SourceGenerator/SourceGeneratorTests.cs | 88 +++++++++++++- pdfsharpdslTests/VisitorTests.cs | 113 +++++++++++++++++- pdfsharpdslTests/pdfsharpdslTests.csproj | 12 +- scripts/coverage.ps1 | 60 ++++++++++ 20 files changed, 399 insertions(+), 35 deletions(-) create mode 100644 pdfsharpdslTests/PdfDocumentDrawerTests.cs create mode 100644 scripts/coverage.ps1 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 25f43e7..a3ddfb3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,9 +17,9 @@ jobs: steps: - uses: actions/checkout@v3 - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 6.0.x + dotnet-version: 10.0.x - name: Restore dependencies run: dotnet restore - name: Build diff --git a/.github/workflows/release-package.yml b/.github/workflows/release-package.yml index bbecc78..94a0a23 100644 --- a/.github/workflows/release-package.yml +++ b/.github/workflows/release-package.yml @@ -13,9 +13,9 @@ jobs: steps: - uses: actions/checkout@v3 - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 6.0.x + dotnet-version: 10.0.x - name: Restore dependencies run: dotnet restore diff --git a/CHANGELOG.md b/CHANGELOG.md index 98fe6d7..08713a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change log +## Unreleased +* Migrated console and test projects to .NET 10 while retaining reusable projects on .NET Standard 2.0. +* Added reproducible core-only coverage enforcement at 90% line coverage. +* Added Roslyn source-generator compilation and clean NuGet consumer validation. +* Updated dependencies and resolved known ImageSharp and runtime package advisories. + ## Version 1.0.5 (March 3, 2024) * Update nugets packages and upgrade to .Net 8 diff --git a/PdfSharpDslConsole/PdfSharpDslConsole.csproj b/PdfSharpDslConsole/PdfSharpDslConsole.csproj index 06cf314..621402a 100644 --- a/PdfSharpDslConsole/PdfSharpDslConsole.csproj +++ b/PdfSharpDslConsole/PdfSharpDslConsole.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 enable enable 0.1.0 @@ -14,7 +14,7 @@ - + diff --git a/PdfSharpDslCore.Generator/PdfSharpDslCore.Generator.csproj b/PdfSharpDslCore.Generator/PdfSharpDslCore.Generator.csproj index 410878f..f83c090 100644 --- a/PdfSharpDslCore.Generator/PdfSharpDslCore.Generator.csproj +++ b/PdfSharpDslCore.Generator/PdfSharpDslCore.Generator.csproj @@ -7,11 +7,10 @@ Pdf Generation using source generation - 1.0.0 + 1.0.2 Pierrick Gourlain https://github.com/pgourlain/bnf_and_pdf - Icon.jpg LICENSE.md false false @@ -20,18 +19,24 @@ - + + - - - + + + + + + + + @@ -39,6 +44,7 @@ + @@ -52,7 +58,7 @@ - + diff --git a/PdfSharpDslCore/Drawing/DrawingHelper.cs b/PdfSharpDslCore/Drawing/DrawingHelper.cs index 78c39f7..0956954 100644 --- a/PdfSharpDslCore/Drawing/DrawingHelper.cs +++ b/PdfSharpDslCore/Drawing/DrawingHelper.cs @@ -24,7 +24,7 @@ public static XRect RectFromStringFormat(XRect r, XSize textSize, XStringFormat case XStringAlignment.Near: break; case XStringAlignment.Far: - result.Offset(r.Right - textSize.Width, 0); + result.Offset(r.Width - textSize.Width, 0); break; } @@ -36,7 +36,7 @@ public static XRect RectFromStringFormat(XRect r, XSize textSize, XStringFormat case XLineAlignment.Near: break; case XLineAlignment.Far: - result.Offset(0, r.Bottom - textSize.Height); + result.Offset(0, r.Height - textSize.Height); break; } diff --git a/PdfSharpDslCore/Evaluation/Evaluator.cs b/PdfSharpDslCore/Evaluation/Evaluator.cs index a08fa7f..3e1c2f9 100644 --- a/PdfSharpDslCore/Evaluation/Evaluator.cs +++ b/PdfSharpDslCore/Evaluation/Evaluator.cs @@ -79,6 +79,9 @@ private IEvaluation PerformEvaluate(ParseTreeNode node, IDictionary": op = BinaryOperation.Superior; break; diff --git a/PdfSharpDslCore/Parser/PdfDrawerVisitor.cs b/PdfSharpDslCore/Parser/PdfDrawerVisitor.cs index 12e0764..27c7f38 100644 --- a/PdfSharpDslCore/Parser/PdfDrawerVisitor.cs +++ b/PdfSharpDslCore/Parser/PdfDrawerVisitor.cs @@ -294,7 +294,8 @@ protected override void ExecuteImage(IPdfDocumentDrawer drawer, ParseTreeNode lo { //try to parse unit and cropping unit = unitNode.Term.Name; - crop = cropNode?.ChildNodes.Count > 0; + crop = cropNode?.ChildNodes.Any(x => + string.Equals(x.Token?.Text, "crop", StringComparison.OrdinalIgnoreCase)) == true; } XImage image; @@ -471,6 +472,7 @@ protected override void ExecuteDebugOptions(IPdfDocumentDrawer state, IEnumerabl "DEBUG_TEXT" => DebugOptions.DebugText, "DEBUG_RECT" => DebugOptions.DebugRect, "DEBUG_ROWTEMPLATE" => DebugOptions.DebugRowTemplate, + "DEBUG_IMAGE" => DebugOptions.DebugImage, "DEBUG_RULE" => DebugOptions.DebugRule, "DEBUG_ALL" => DebugOptions.DebugAll, _ => DebugOptions.None diff --git a/PdfSharpDslCore/Parser/PdfVisitor.cs b/PdfSharpDslCore/Parser/PdfVisitor.cs index 8fb56cd..4a2dee6 100644 --- a/PdfSharpDslCore/Parser/PdfVisitor.cs +++ b/PdfSharpDslCore/Parser/PdfVisitor.cs @@ -314,9 +314,11 @@ protected void ExecuteUdfByName(TState state, string fnName, ParseTreeNode? argu { defArgs = defNode.ChildNode("UdfArgumentslist")!; defBody = defNode.ChildNode("UdfBlock")?.ChildNode("EmbbededSmtList")!; - if (defArgs!= null && arguments!=null && defArgs.ChildNodes.Count != arguments.ChildNodes.Count) + var expectedArgumentCount = defArgs?.ChildNodes.Count ?? 0; + var providedArgumentCount = arguments?.ChildNodes.Count ?? 0; + if (expectedArgumentCount != providedArgumentCount) { - throw new PdfParserException($"UDF '{fnName}' arguments count not match, provided ${arguments.ChildNodes.Count}, expected ${defArgs.ChildNodes.Count}."); + throw new PdfParserException($"UDF '{fnName}' arguments count does not match, provided {providedArgumentCount}, expected {expectedArgumentCount}."); } if (defArgs == null) diff --git a/PdfSharpDslCore/PdfSharpDslCore.csproj b/PdfSharpDslCore/PdfSharpDslCore.csproj index 0cd7958..354a1cc 100644 --- a/PdfSharpDslCore/PdfSharpDslCore.csproj +++ b/PdfSharpDslCore/PdfSharpDslCore.csproj @@ -24,9 +24,10 @@ - + + diff --git a/PdfSharpDslCore/Readme.md b/PdfSharpDslCore/Readme.md index a7c4494..f4ffd0b 100644 --- a/PdfSharpDslCore/Readme.md +++ b/PdfSharpDslCore/Readme.md @@ -3,6 +3,10 @@ Package to print PDF using a specific DSL, using Irony.Net and PdfSharpCore +The reusable library targets `netstandard2.0`. The source generator package also targets `netstandard2.0` and can be consumed by a `net10.0` application. The repository's test and console projects target `net10.0`. + +The repository enforces at least 90% line coverage for `PdfSharpDslCore` through `scripts\coverage.ps1`. + # How to Goto [https://github.com/pgourlain/bnf_and_pdf](https://github.com/pgourlain/bnf_and_pdf) for example and usage diff --git a/README.md b/README.md index 87e75f1..59bb7b8 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,21 @@ This is a sample library that use [Irony.Net](https://github.com/IronyProject/Irony) to define a grammar to print PDF using [PdfSharpCore](https://github.com/ststeiger/PdfSharpCore/) +## Current support + +- `PdfSharpDslCore` and `PdfSharpDslCore.Generator` target `netstandard2.0`. +- `PdfSharpDslConsole` and the test project target `net10.0`. +- The repository is pinned to SDK `10.0.400` in `global.json`. +- The generator package includes its analyzer dependencies and supports clean NuGet consumer builds. + +Run the full test and core coverage gate with: + +```powershell +powershell -NoProfile -File .\scripts\coverage.ps1 +``` + +The gate requires at least 90% core line coverage. See [tasks.md](tasks.md) for the migration tracker and verified results. + # Example diff --git a/global.json b/global.json index 1b5ac1d..38c4f3c 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.0", + "version": "10.0.400", "rollForward": "latestFeature" } } \ No newline at end of file diff --git a/pdfsharpdslTests/DrawingValueTests.cs b/pdfsharpdslTests/DrawingValueTests.cs index f28a17d..740684b 100644 --- a/pdfsharpdslTests/DrawingValueTests.cs +++ b/pdfsharpdslTests/DrawingValueTests.cs @@ -65,6 +65,8 @@ public void TableDefinitionCalculatesColumnDimensionsAndAlignment() Assert.Equal(50, table.ColMaxWidth(0, 100)); Assert.Equal(40, table.ColMaxWidth(1, 40)); Assert.Equal(XStringAlignment.Center, table.Alignment(0)); + Assert.Equal(50, table.Columns[0].DrawWidth); + Assert.Equal(0, table.Columns[1].DrawWidth); Assert.Equal(12, table.TopMarginOnPageBreak); Assert.False(table.ShowHeader); Assert.Equal(20, table.HeaderHeight); @@ -86,5 +88,34 @@ public void DrawingResultStoresRectangleAndPageOffset() Assert.Equal(rectangle, result.DrawingRect); Assert.Equal(25, result.PageOffsetY); } + + [Fact] + public void TextRectangleAlignmentUsesRelativeBounds() + { + var bounds = new XRect(10, 20, 100, 50); + var textSize = new XSize(30, 10); + var centered = DrawingHelper.RectFromStringFormat(bounds, textSize, new XStringFormat + { + Alignment = XStringAlignment.Center, + LineAlignment = XLineAlignment.Center + }); + var far = DrawingHelper.RectFromStringFormat(bounds, textSize, new XStringFormat + { + Alignment = XStringAlignment.Far, + LineAlignment = XLineAlignment.Far + }); + + Assert.Equal(new XRect(45, 40, 30, 10), centered); + Assert.Equal(new XRect(80, 60, 30, 10), far); + } + + [Fact] + public void TextRectangleIsClippedToProvidedBounds() + { + var bounds = new XRect(10, 20, 100, 50); + var result = DrawingHelper.RectFromStringFormat(bounds, new XSize(200, 100), XStringFormats.TopLeft); + + Assert.Equal(bounds, result); + } } } \ No newline at end of file diff --git a/pdfsharpdslTests/FormulaTests.cs b/pdfsharpdslTests/FormulaTests.cs index 301f334..50deefc 100644 --- a/pdfsharpdslTests/FormulaTests.cs +++ b/pdfsharpdslTests/FormulaTests.cs @@ -1,4 +1,6 @@ -using PdfSharpDslCore.Evaluation; +using Moq; +using PdfSharpDslCore.Drawing; +using PdfSharpDslCore.Evaluation; using PdfSharpDslCore.Parser;/**/ using System; using System.Collections.Generic; @@ -152,5 +154,16 @@ public void MissingVariableThrows() Assert.Throws(() => evaluation.Value); } + + [Fact] + public void ModuloOperatorIsEvaluatedFromParsedDsl() + { + var tree = ParseText("SET VAR X=7%3;"); + var visitor = new PdfDrawerForTestsVisitor(); + + visitor.Draw(Mock.Of(), tree); + + Assert.Equal(1.0, visitor.Vars["X"]); + } } } diff --git a/pdfsharpdslTests/PdfDocumentDrawerTests.cs b/pdfsharpdslTests/PdfDocumentDrawerTests.cs new file mode 100644 index 0000000..685f1ac --- /dev/null +++ b/pdfsharpdslTests/PdfDocumentDrawerTests.cs @@ -0,0 +1,36 @@ +using PdfSharpCore; +using PdfSharpCore.Pdf; +using PdfSharpDslCore.Drawing; + +namespace pdfsharpdslTests +{ + public class PdfDocumentDrawerTests + { + [Fact] + public void NewPageUsesDefaultsAndPersistsExplicitSettings() + { + using var document = new PdfDocument(); + using var drawer = new PdfDocumentDrawer(document); + var pageNumbers = new List(); + Action callback = pageNumbers.Add; + drawer.RegisterOnNewPage(callback); + drawer.RegisterOnNewPage(callback); + + Assert.Equal(PageSize.A4, drawer.CurrentPage.Size); + Assert.Equal(PageOrientation.Portrait, drawer.CurrentPage.Orientation); + + drawer.NewPage(PageSize.Letter, PageOrientation.Landscape); + Assert.Equal(PageSize.Letter, drawer.CurrentPage.Size); + Assert.Equal(PageOrientation.Landscape, drawer.CurrentPage.Orientation); + + drawer.NewPage(); + Assert.Equal(PageSize.Letter, drawer.CurrentPage.Size); + Assert.Equal(PageOrientation.Landscape, drawer.CurrentPage.Orientation); + Assert.Equal(new[] { 2, 3 }, pageNumbers); + + drawer.UnRegisterOnNewPage(callback); + drawer.NewPage(); + Assert.Equal(new[] { 2, 3 }, pageNumbers); + } + } +} \ No newline at end of file diff --git a/pdfsharpdslTests/SourceGenerator/SourceGeneratorTests.cs b/pdfsharpdslTests/SourceGenerator/SourceGeneratorTests.cs index d1c0ebb..a64dc11 100644 --- a/pdfsharpdslTests/SourceGenerator/SourceGeneratorTests.cs +++ b/pdfsharpdslTests/SourceGenerator/SourceGeneratorTests.cs @@ -1,11 +1,12 @@ -using PdfSharpDslCore.Generator; -using System; -using System.Collections.Generic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; +using PdfSharpCore.Drawing; +using PdfSharpDslCore.Drawing; +using PdfSharpDslCore.Generator; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Security.Cryptography; using System.Text; -using System.Threading.Tasks; namespace pdfsharpdslTests.SourceGenerator { @@ -22,5 +23,80 @@ public void TestSourceGeneration(string file) Assert.NotNull(result); } + + [Fact] + public void GeneratorProducesCompilableSourceFromTaggedAdditionalFile() + { + var source = CSharpSyntaxTree.ParseText("namespace Consumer { internal class Marker { } }"); + var references = ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")!) + .Split(Path.PathSeparator) + .Append(typeof(IPdfDocumentDrawer).Assembly.Location) + .Append(typeof(XColor).Assembly.Location) + .Distinct() + .Select(path => MetadataReference.CreateFromFile(path)); + var compilation = CSharpCompilation.Create( + "Consumer", + new[] { source }, + references, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + var additionalFile = new InMemoryAdditionalText("sample.txt", "NEWPAGE;"); + GeneratorDriver driver = CSharpGeneratorDriver.Create( + new[] { new DslGenerator() }, + new[] { additionalFile }, + (CSharpParseOptions)source.Options, + new PdfDslOptionsProvider()); + + driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var generatorDiagnostics); + + Assert.Empty(generatorDiagnostics.Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)); + Assert.Equal(2, outputCompilation.SyntaxTrees.Count()); + Assert.Empty(outputCompilation.GetDiagnostics().Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)); + } + + private sealed class InMemoryAdditionalText : AdditionalText + { + private readonly SourceText _text; + + public InMemoryAdditionalText(string path, string text) + { + Path = path; + _text = SourceText.From(text, Encoding.UTF8); + } + + public override string Path { get; } + + public override SourceText GetText(CancellationToken cancellationToken = default) => _text; + } + + private sealed class PdfDslOptionsProvider : AnalyzerConfigOptionsProvider + { + private static readonly AnalyzerConfigOptions Empty = new TestAnalyzerConfigOptions(false); + private static readonly AnalyzerConfigOptions PdfDsl = new TestAnalyzerConfigOptions(true); + + public override AnalyzerConfigOptions GlobalOptions => Empty; + + public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) => Empty; + + public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) => PdfDsl; + } + + private sealed class TestAnalyzerConfigOptions : AnalyzerConfigOptions + { + private readonly bool _isPdfDsl; + + public TestAnalyzerConfigOptions(bool isPdfDsl) => _isPdfDsl = isPdfDsl; + + public override bool TryGetValue(string key, out string value) + { + if (_isPdfDsl && key == "build_metadata.additionalfiles.IsPdfSharpDsl") + { + value = "true"; + return true; + } + + value = string.Empty; + return false; + } + } } } diff --git a/pdfsharpdslTests/VisitorTests.cs b/pdfsharpdslTests/VisitorTests.cs index 78344a2..1243142 100644 --- a/pdfsharpdslTests/VisitorTests.cs +++ b/pdfsharpdslTests/VisitorTests.cs @@ -106,7 +106,7 @@ public void TestConditionEvaluator(string file) [Fact] public void DrawExecutesViewSizeTextWidthAndDebugOptions() { - var tree = ParseText("VIEWSIZE 100,140;TEXT 10,20 MaxWidth=30 Text=\"hello\";DEBUGOPTIONS DEBUG_TEXT, DEBUG_RECT, DEBUG_ROWTEMPLATE, DEBUG_RULE, DEBUG_ALL, UNKNOWN;"); + var tree = ParseText("VIEWSIZE 100,140;TEXT 10,20 MaxWidth=30 Text=\"hello\";DEBUGOPTIONS DEBUG_TEXT, DEBUG_RECT, DEBUG_ROWTEMPLATE, DEBUG_IMAGE, DEBUG_RULE, DEBUG_ALL, UNKNOWN;"); var drawer = new Mock(); drawer.SetupProperty(x => x.DebugOptions); @@ -115,7 +115,7 @@ public void DrawExecutesViewSizeTextWidthAndDebugOptions() drawer.Verify(x => x.SetViewSize(100, 140), Times.Once); drawer.Verify(x => x.DrawText("hello", 10, 20, 30, null), Times.Once); Assert.Equal( - DebugOptions.DebugText | DebugOptions.DebugRect | DebugOptions.DebugRowTemplate | DebugOptions.DebugRule | DebugOptions.DebugAll, + DebugOptions.DebugText | DebugOptions.DebugRect | DebugOptions.DebugRowTemplate | DebugOptions.DebugImage | DebugOptions.DebugRule | DebugOptions.DebugAll, drawer.Object.DebugOptions); } @@ -134,6 +134,66 @@ public void DrawResolvesPageSystemVariables() Assert.Equal(792.0, visitor.Vars["HEIGHT"]); } + [Fact] + public void ImageStatementsPreserveDimensionsUnitsAndCropMode() + { + const string imageData = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="; + var tree = ParseText( + $"IMAGE 1,2 Data=\"data:image/png;base64,{imageData}\";" + + $"IMAGE 3,4,30,40 pixel crop Data=\"{imageData}\";" + + $"IMAGE 5,6,70,80 point fit Data=\"{imageData}\";"); + var calls = new List<(double X, double Y, double? Width, double? Height, bool Pixel, bool Crop)>(); + var drawer = new Mock(); + drawer.Setup(x => x.DrawImage( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Callback((_, x, y, width, height, pixel, crop) => + calls.Add((x, y, width, height, pixel, crop))); + + new PdfDrawerVisitor().Draw(drawer.Object, tree); + + Assert.Equal((1, 2, null, null, false, false), calls[0]); + Assert.Equal((3, 4, 30, 40, true, true), calls[1]); + Assert.Equal((5, 6, 70, 80, false, false), calls[2]); + } + + [Fact] + public void TableRowTemplateBuildsRowsWidthsAndPadding() + { + var tree = ParseText( + "TABLE 20,30 " + + "HEAD " + + "COL Width=40 MaxWidth=30 \"A\"; " + + "COL Width=auto MaxWidth=100 \"B\"; " + + "ENDHEAD " + + "ROWTEMPLATE 2 " + + "COL $ROWINDEX; " + + "ENDROW " + + "ENDTABLE"); + TableDefinition? capturedTable = null; + var drawer = new Mock(); + drawer.Setup(x => x.DrawTable(20, 30, It.IsAny())) + .Callback((_, _, table) => capturedTable = table); + var visitor = new InspectablePdfDrawerVisitor(); + + visitor.Draw(drawer.Object, tree); + + Assert.NotNull(capturedTable); + Assert.Equal(2, capturedTable.Columns.Count); + Assert.Equal(40, capturedTable.Columns[0].DesiredWidth); + Assert.Equal(30, capturedTable.Columns[0].MaxWidth); + Assert.Null(capturedTable.Columns[1].DesiredWidth); + Assert.Equal(100, capturedTable.Columns[1].MaxWidth); + Assert.Equal(new[] { "0", string.Empty }, capturedTable.Rows[0].Data); + Assert.Equal(new[] { "1", string.Empty }, capturedTable.Rows[1].Data); + Assert.False(visitor.Vars.ContainsKey("ROWINDEX")); + } + [Fact] public void CustomUdfCanFallBackToDslBodyOrOverrideIt() { @@ -180,6 +240,55 @@ public void CustomUdfErrorsAreReportedAsParserErrors() Assert.Contains("MISSING", missingError.Message); } + [Fact] + public void NestedDslUdfsRestoreOuterScopes() + { + var tree = ParseText( + "SET VAR VALUE=10; " + + "UDF INNER(VALUE) SET VAR INNERONLY=99; LINE $VALUE,$INNERONLY,$VALUE,$INNERONLY; ENDUDF " + + "UDF OUTER(VALUE) SET VAR OUTERONLY=20; CALL INNER(30); LINE $VALUE,$OUTERONLY,$VALUE,$OUTERONLY; ENDUDF " + + "CALL OUTER(40); LINE $VALUE,0,$VALUE,0;"); + var drawer = new Mock(); + var visitor = new InspectablePdfDrawerVisitor(); + + visitor.Draw(drawer.Object, tree); + + drawer.Verify(x => x.DrawLine(30, 99, 30, 99), Times.Once); + drawer.Verify(x => x.DrawLine(40, 20, 40, 20), Times.Once); + drawer.Verify(x => x.DrawLine(10, 0, 10, 0), Times.Once); + Assert.Equal(10.0, visitor.Vars["VALUE"]); + Assert.False(visitor.Vars.ContainsKey("INNERONLY")); + Assert.False(visitor.Vars.ContainsKey("OUTERONLY")); + } + + [Fact] + public void DslUdfFailureRestoresOuterScope() + { + var tree = ParseText( + "SET VAR VALUE=1; " + + "UDF FAIL(VALUE) SET VAR TEMP=2; LINE $MISSING,0,0,0; ENDUDF " + + "CALL FAIL(9);"); + var visitor = new InspectablePdfDrawerVisitor(); + + Assert.Throws(() => visitor.Draw(Mock.Of(), tree)); + + Assert.Equal(1.0, visitor.Vars["VALUE"]); + Assert.False(visitor.Vars.ContainsKey("TEMP")); + } + + [Theory] + [InlineData("UDF NEEDS(X) LINE $X,0,0,0; ENDUDF CALL NEEDS();")] + [InlineData("UDF NONE() LINE 0,0,0,0; ENDUDF CALL NONE(1);")] + public void DslUdfRejectsZeroArgumentCountMismatches(string input) + { + var tree = ParseText(input); + var visitor = new InspectablePdfDrawerVisitor(); + + var error = Assert.Throws(() => visitor.Draw(Mock.Of(), tree)); + + Assert.Contains("arguments count", error.Message); + } + [Fact] public void RowTemplateTracksOffsetsAndFinalHeight() { diff --git a/pdfsharpdslTests/pdfsharpdslTests.csproj b/pdfsharpdslTests/pdfsharpdslTests.csproj index c27762a..c721efb 100644 --- a/pdfsharpdslTests/pdfsharpdslTests.csproj +++ b/pdfsharpdslTests/pdfsharpdslTests.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable @@ -11,14 +11,14 @@ - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/scripts/coverage.ps1 b/scripts/coverage.ps1 new file mode 100644 index 0000000..d256cb5 --- /dev/null +++ b/scripts/coverage.ps1 @@ -0,0 +1,60 @@ +[CmdletBinding()] +param( + [ValidateSet("Debug", "Release")] + [string]$Configuration = "Debug", + + [ValidateRange(0, 1)] + [double]$MinimumLineRate = 0.90 +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repositoryRoot = Split-Path -Parent $PSScriptRoot +$testProject = Join-Path $repositoryRoot "pdfsharpdslTests\pdfsharpdslTests.csproj" +$settingsFile = Join-Path $repositoryRoot ".runsettings" +$resultsDirectory = Join-Path $repositoryRoot "artifacts\coverage" + +if (Test-Path $resultsDirectory) { + Remove-Item $resultsDirectory -Recurse -Force +} + +& dotnet test $testProject ` + --configuration $Configuration ` + --settings $settingsFile ` + --collect:"XPlat Code Coverage" ` + --results-directory $resultsDirectory + +if ($LASTEXITCODE -ne 0) { + throw "Coverage test run failed with exit code $LASTEXITCODE." +} + +$reports = @(Get-ChildItem $resultsDirectory -Filter "coverage.cobertura.xml" -Recurse) +if ($reports.Count -ne 1) { + throw "Expected one Cobertura report, but found $($reports.Count)." +} + +[xml]$coverage = Get-Content $reports[0].FullName +$packages = @($coverage.coverage.packages.package) +$unexpectedPackages = @($packages | Where-Object { $_.name -ne "PdfSharpDslCore" }) +if ($packages.Count -ne 1 -or $unexpectedPackages.Count -ne 0) { + $packageNames = ($packages | ForEach-Object { $_.name }) -join ", " + throw "Expected coverage only for PdfSharpDslCore, but found: $packageNames" +} + +$lineRate = [double]::Parse($coverage.coverage."line-rate", [Globalization.CultureInfo]::InvariantCulture) +$branchRate = [double]::Parse($coverage.coverage."branch-rate", [Globalization.CultureInfo]::InvariantCulture) + +$summary = [pscustomobject]@{ + Assembly = $packages[0].name + LineCoverage = "{0:P2}" -f $lineRate + Lines = "$($coverage.coverage.'lines-covered')/$($coverage.coverage.'lines-valid')" + BranchCoverage = "{0:P2}" -f $branchRate + Branches = "$($coverage.coverage.'branches-covered')/$($coverage.coverage.'branches-valid')" + Report = $reports[0].FullName +} +$summary | Format-List + +if ($lineRate -lt $MinimumLineRate) { + throw "Line coverage $($summary.LineCoverage) is below the required $(('{0:P2}' -f $MinimumLineRate))." +} \ No newline at end of file From 9b9bc0a5200058847fe1747a62057ca41e91cfe6 Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Fri, 18 Sep 2026 15:36:58 +0200 Subject: [PATCH 03/15] generate full demo --- PdfSharpDslConsole/Program.cs | 3 +- PdfSharpDslConsole/demo.ipdf | 345 ++++++++++++++++++++++++++++++++++ 2 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 PdfSharpDslConsole/demo.ipdf diff --git a/PdfSharpDslConsole/Program.cs b/PdfSharpDslConsole/Program.cs index 05730d4..55a1491 100644 --- a/PdfSharpDslConsole/Program.cs +++ b/PdfSharpDslConsole/Program.cs @@ -94,7 +94,8 @@ var parser = new Irony.Parsing.Parser(new PdfGrammar()); //var fileName = "pdfsharp-newpage.ipdf"; //var fileName = "pdfsharp.ipdf"; -var fileName = "sample1.ipdf"; +//var fileName = "sample1.ipdf"; +var fileName = "demo.ipdf"; if (args.Length > 0) { fileName = args[0]; diff --git a/PdfSharpDslConsole/demo.ipdf b/PdfSharpDslConsole/demo.ipdf new file mode 100644 index 0000000..4db5d8b --- /dev/null +++ b/PdfSharpDslConsole/demo.ipdf @@ -0,0 +1,345 @@ +# PdfSharpDsl feature demonstration +# Run from PdfSharpDslConsole with: dotnet run -- demo.ipdf + +# Reusable components. UDFs may be called before or after their definitions. +UDF SECTION(TITLETEXT) + SET FONT Name="Arial" Size=20 bold; + SET BRUSH darkblue; + TITLE Margin=24 HAlign=hcenter Text=$TITLETEXT; + SET PEN steelblue 1; + LINE 36,54,$PAGEWIDTH-36,54; +ENDUDF + +UDF BADGE(X,Y,LABEL) + SET PEN darkblue 1 solid; + SET BRUSH lightblue; + FILLRECT $X,$Y,110,30; + SET FONT Name="Arial" Size=10 bold; + SET BRUSH darkblue; + LINETEXT $X,$Y,110,30 HAlign=hcenter VAlign=vcenter Text=$LABEL; +ENDUDF + +# Special callback: runs after every NEWPAGE and can publish global variables. +UDF __ONNEWPAGE() + SET VAR CONTENTWIDTH=$PAGEWIDTH-72; + SET VAR CONTENTHEIGHT=$PAGEHEIGHT-72; + SET FONT Name="Arial" Size=8 regular; + SET BRUSH gray; + TITLE Margin=-18 HAlign=hcenter Text=("PdfSharpDsl demo | page "+$PAGEINDEX); +ENDUDF + +# ----------------------------------------------------------------------------- +# Cover: pages, page variables, titles, colors, fonts, variables, formulas +# ----------------------------------------------------------------------------- +NEWPAGE A4 portrait; +SET BRUSH darkblue; +SET FONT Name="Arial" Size=28 bold; +TITLE Margin=72 HAlign=hcenter Text="PdfSharpDsl"; +SET FONT Name="Arial" Size=16 italic; +TITLE Margin=112 HAlign=hcenter Text="Complete feature demonstration"; + +SET HBRUSH 0x50ffe9b2; +SET FONT Name="Consolas" Size=10 regular; +TITLE Margin=160 HAlign=hcenter Text=("A4 portrait: "+$PAGEWIDTH+" x "+$PAGEHEIGHT+" points"); +SET HBRUSH 0x00000000; + +SET VAR A=17; +SET VAR B=5; +SET VAR NEGATIVE=-5; +SET VAR POSITIVE=+5; +SET VAR MESSAGE="Variables, arithmetic, comparison and concatenation"; +SET BRUSH black; +LINETEXT 54,220 HAlign=left VAlign=top Text=$MESSAGE; +LINETEXT 54,246 HAlign=left VAlign=top Text=("A+B="+($A+$B)+", A-B="+($A-$B)+", A*B="+($A*$B)); +LINETEXT 54,270 HAlign=left VAlign=top Text=("A/B="+($A/$B)+", A%B="+($A%$B)+", unary values="+$NEGATIVE+" and "+$POSITIVE); + +IF (($A > $B) and ($A >= 17)) THEN + CALL BADGE(54,310,"AND condition: true"); +ELSE + CALL BADGE(54,310,"AND condition: false"); +ENDIF + +IF (($A < $B) or ($A <= 17)) THEN + CALL BADGE(184,310,"OR condition: true"); +ENDIF + +IF ("alpha" <> "beta") THEN + CALL BADGE(314,310,"<> comparison"); +ENDIF + +IF ($A == 17) THEN + CALL BADGE(444,310,"== comparison"); +ENDIF + +SET FONT Name="Arial" Size=11 regular; +TEXT 54,380,487,100 MaxWidth=487 Text="This document is itself the example source. Each page groups related commands and uses comments, optional semicolons, expressions, variables, built-in page variables, host formula functions, control flow and reusable UDFs."; + +# ----------------------------------------------------------------------------- +# Drawing primitives, pen styles, brushes and color formats +# ----------------------------------------------------------------------------- +NEWPAGE A4 landscape; +CALL SECTION("Drawing primitives and styles"); + +SET FONT Name="Consolas" Size=9 regular; +SET BRUSH black; +SET PEN black 1 solid; +LINETEXT 45,90 Text="solid"; +LINE 45,108,165,108; +SET PEN red 1.5 dash; +LINETEXT 45,130 Text="dash"; +LINE 45,148,165,148; +SET PEN green 2 dot; +LINETEXT 45,170 Text="dot"; +LINE 45,188,165,188; +SET PEN blue 2 dashdot; +LINETEXT 45,210 Text="dashdot"; +LINE 45,228,165,228; +SET PEN 0xff8844 2 dashdotdot; +LINETEXT 45,250 Text="dashdotdot / RGB hex"; +LINE 45,268,165,268; + +SET PEN black 1 solid; +SET BRUSH lightgreen; +RECT 220,90,100,65; +FILLRECT 350,90,100,65; +SET BRUSH 0x8090caf9; +ELLIPSE 220,190,100,65; +FILLELLIPSE 350,190,100,65; + +SET BRUSH gold; +PIE 500,90,110,110 Start=15 Angle=120; +SET BRUSH tomato; +FILLPIE 640,90,110,110 Start=210 Angle=120; + +SET PEN darkslategray 2 solid; +POLYGON 500,250,550,210,610,250,585,315,525,315; +SET BRUSH lightseagreen; +FILLPOLYGON 640,250,690,210,750,250,725,315,665,315; + +SET PEN purple 3 solid; +MOVETO 220,350; +LINETO 280,320; +LINETO 340,370; +LINETO 400,330; +LINETO 450,370; + +SET PEN g0.45 1; +SET BRUSH g0.80; +FILLRECT 500,350,250,60; +SET BRUSH black; +LINETEXT 625,380 HAlign=hcenter VAlign=vcenter Text="grayscale colors: g0.45 / g0.80"; + +# Negative dimensions extend to the far page edge. +SET PEN crimson 1 dash; +RECT 20,440,-20,-55; +SET BRUSH crimson; +LINETEXT 30,450 Text="Negative width and height use the remaining page area"; + +# ----------------------------------------------------------------------------- +# Text, alignment, orientation, highlighting and every font style +# ----------------------------------------------------------------------------- +NEWPAGE A4 portrait; +CALL SECTION("Text layout, orientation and fonts"); +SET PEN lightgray 0.5; +RECT 45,85,500,110; +SET FONT Name="Arial" Size=11 regular; +SET BRUSH black; +LINETEXT 45,85,500,110 HAlign=left VAlign=top Text="left / top"; +LINETEXT 45,85,500,110 HAlign=hcenter VAlign=vcenter Text="center / center"; +LINETEXT 45,85,500,110 HAlign=right VAlign=bottom Text="right / bottom"; + +SET HBRUSH 0x60fff176; +LINETEXT 60,235 HAlign=left VAlign=vcenter Orientation=horizontal Text="highlight brush"; +SET HBRUSH 0x00000000; +LINETEXT 220,235 HAlign=left VAlign=vcenter Orientation=vertical Text="vertical"; +LINETEXT 300,235 HAlign=left VAlign=vcenter Orientation=30 Text="30 degree rotation"; + +SET FONT Name="Arial" Size=11 regular; +LINETEXT 45,330 Text="regular"; +SET FONT Name="Arial" Size=11 bold; +LINETEXT 140,330 Text="bold"; +SET FONT Name="Arial" Size=11 italic; +LINETEXT 220,330 Text="italic"; +SET FONT Name="Arial" Size=11 bolditalic; +LINETEXT 300,330 Text="bold italic"; +SET FONT Name="Arial" Size=11 underline; +LINETEXT 405,330 Text="underline"; +SET FONT Name="Arial" Size=11 strikeout; +LINETEXT 500,330 Text="strikeout"; + +SET FONT Name="Arial" Size=11 regular; +TEXT 45,385 MaxWidth=220 Text="TEXT with a point and MaxWidth wraps a longer paragraph over several lines."; +TEXT 300,385,245,95 Text="TEXT with a rectangle supports multiline content.\r\nSecond explicit line.\r\nThird explicit line."; + +TITLE Margin=515 HAlign=left Text="Left title"; +TITLE Margin=515 HAlign=hcenter Text="Centered title"; +TITLE Margin=515 HAlign=right Text="Right title"; +TITLE Margin=-40 HAlign=right Text="Negative title margin is measured from the bottom"; + +# ----------------------------------------------------------------------------- +# FOR loops, UDF parameters/scoping, IF/ELSE and formula-driven geometry +# ----------------------------------------------------------------------------- +NEWPAGE A4 portrait; +CALL SECTION("Control flow and reusable drawing"); +SET FONT Name="Consolas" Size=10 regular; +SET BRUSH black; +LINETEXT 45,85 Text="FOR is inclusive; UDF parameters are locally scoped."; + +FOR ROW=0 TO 6 DO + SET PEN 0x4472c4 1; + LINE 70,130+$ROW*55,520,130+$ROW*55; + CALL BADGE(80,142+$ROW*55,"Iteration "+$ROW); +ENDFOR + +IF (($A >= $B) and ($B <= $A)) THEN + SET BRUSH darkgreen; + LINETEXT 45,555 Text="IF branch selected: A >= B and B <= A"; +ELSE + SET BRUSH red; + LINETEXT 45,555 Text="ELSE branch selected"; +ENDIF + +# ----------------------------------------------------------------------------- +# Ordinary tables: auto/fixed widths, header styles, rows and row heights +# ----------------------------------------------------------------------------- +NEWPAGE A4 portrait; +CALL SECTION("Tables with explicit rows"); +SET FONT Name="Arial" Size=9 regular; +SET PEN slategray 0.6; +TABLE 40,90 +HEAD 0xffe8eef7 +COL Width=auto MaxWidth=130 FONT="Arial",9,bold white steelblue "Feature"; +COL Width=140 MaxWidth=180 FONT="Arial",9,bold white steelblue "Syntax"; +COL Width=auto MaxWidth=240 FONT="Arial",9,bold white steelblue "Notes"; +ENDHEAD +ROW +COL "Automatic sizing"; +COL "Width=auto"; +COL "Desired width follows content up to MaxWidth"; +ENDROW +ROW 48 +COL "Fixed row height"; +COL "ROW 48"; +COL "A formula may define the row height"; +ENDROW +ROW +COL "Multiline cell"; +COL "COL expression"; +COL "Line one\r\nLine two\r\nLine three"; +ENDROW +ROW +COL "Short row"; +COL "Missing cells"; +ENDROW +ENDTABLE + +# Table row templates evaluate every COL expression for each ROWINDEX. +SET BRUSH darkblue; +LINETEXT 40,360 Text="Table ROWTEMPLATE with host functions"; +TABLE 40,385 +HEAD lightgray +COL Width=100 MaxWidth=120 FONT="Consolas",8,bold black lightgray "Date"; +COL Width=130 MaxWidth=160 "Comment count"; +COL Width=auto MaxWidth=250 "Evaluated label"; +ENDHEAD +ROWTEMPLATE getGlobalCommentsCount() +COL getGlobalCommentDate($ROWINDEX); +COL getCommentsCount($ROWINDEX); +COL "Global item "+($ROWINDEX+1); +ENDROW +ENDTABLE + +# ----------------------------------------------------------------------------- +# Free row templates: nesting, names, borders, pagination and measured height +# ----------------------------------------------------------------------------- +NEWPAGE A4 portrait; +CALL SECTION("Paginating and nested row templates"); +SET FONT Name="Arial" Size=9 regular; +SET PEN steelblue 0.7; +SET BRUSH black; + +ROWTEMPLATE Count=3 Y=90 Name="outer" BorderSize=5 NewPageTopMargin=70 + SET VAR OUTERINDEX=$ROWINDEX; + LINETEXT 45,0 Text=("Group "+$OUTERINDEX); + ROWTEMPLATE Count=2 Y=16 Name=("inner-"+$OUTERINDEX) BorderSize=3 + LINETEXT 130,0 Text=("Nested item "+$OUTERINDEX+"."+$ROWINDEX); + LINE 125,14,500,14; + ENDROWTEMPLATE + # LASTTEMPLATEHEIGHT excludes the nested template's Y and initial border. + RECT 35,0,490,16+3+$LASTTEMPLATEHEIGHT; +ENDROWTEMPLATE + +SET BRUSH darkblue; +LINETEXT 45,300 Text=("Measured outer template height: "+$LASTTEMPLATEHEIGHT); + +# This template is tall enough to create another page automatically. +ROWTEMPLATE Count=16 Y=340 Name="paginated-list" BorderSize=4 NewPageTopMargin=70 + SET PEN lightgray 0.5; + RECT 45,0,500,36; + SET BRUSH black; + LINETEXT 58,18 VAlign=vcenter Text=("Paginated item "+($ROWINDEX+1)); +ENDROWTEMPLATE + +# ----------------------------------------------------------------------------- +# Images: file/data sources, natural size, points/pixels, fit/crop +# ----------------------------------------------------------------------------- +NEWPAGE A4 landscape; +CALL SECTION("Images from files and embedded data"); +SET FONT Name="Consolas" Size=9 regular; +SET BRUSH black; +LINETEXT 45,82 Text="Natural size from Source"; +IMAGE 45,105 Source="./imageTest.jpg"; + +LINETEXT 300,82 Text="Point rectangle, fit"; +IMAGE 300,105,180,120 point fit Source="./imageTest.jpg"; + +LINETEXT 520,82 Text="Pixel rectangle, crop"; +IMAGE 520,105,180,120 pixel crop Source="./imageTest.jpg"; + +LINETEXT 45,330 Text="Embedded one-pixel PNG using Data"; +IMAGE 45,355,160,100 point fit Data="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="; + +# ----------------------------------------------------------------------------- +# Host formula functions and custom bundled fonts +# ----------------------------------------------------------------------------- +NEWPAGE A4 portrait; +CALL SECTION("Host functions and bundled fonts"); +SET VAR FONTCOUNT=GetFontCount(); +SET FONT Name="Consolas" Size=9 regular; +SET BRUSH black; +LINETEXT 45,82 Text=("GetFontCount() returned "+$FONTCOUNT); +FOR I=0 TO $FONTCOUNT-1 DO + SET VAR FONTNAME=GetFont($I); + SET FONT Name=$FONTNAME Size=18 regular; + LINETEXT 55,125+70*$I Text=("PdfSharpDsl - "+$FONTNAME); +ENDFOR + +# ----------------------------------------------------------------------------- +# Debug overlays and custom view coordinates +# ----------------------------------------------------------------------------- +NEWPAGE A4 portrait; +# DEBUGOPTIONS is document-wide because the visitor reads it before drawing page 1. +# Uncomment this line to enable every overlay on every page: +# DEBUGOPTIONS DEBUG_TEXT, DEBUG_RECT, DEBUG_ROWTEMPLATE, DEBUG_IMAGE, DEBUG_RULE, DEBUG_ALL; +CALL SECTION("Debug options (opt-in, document-wide)"); +SET FONT Name="Arial" Size=11 regular; +SET BRUSH black; +LINETEXT 60,110,470,50 HAlign=hcenter VAlign=vcenter Text="DEBUGOPTIONS can expose text, rectangles, templates, images and rules across the document."; +SET BRUSH lightsalmon; +FILLRECT 100,200,150,80; +IMAGE 330,200,150,80 point fit Data="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="; +ROWTEMPLATE Count=2 Y=330 Name="debug-template" BorderSize=3 + LINETEXT 100,0 Text=("Debug row "+$ROWINDEX); + LINE 100,24,480,24; +ENDROWTEMPLATE + +# VIEWSIZE maps a compact coordinate system onto the page. +NEWPAGE A4 portrait; +VIEWSIZE 100,140; +SET PEN red 0.2 dash; +SET BRUSH black; +SET FONT Name="Consolas" Size=3 regular; +LINE 50,0,50,140; +LINE 0,70,100,70; +RECT 2,2,96,136; +LINETEXT 50,70 HAlign=hcenter VAlign=vcenter Text="VIEWSIZE 100 x 140"; \ No newline at end of file From d3ce5afbe93b29a7cee40f0c2cafff480e302595 Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Fri, 18 Sep 2026 16:23:52 +0200 Subject: [PATCH 04/15] migrate to TerraPDF in order to avoid apche license dependencies --- CHANGELOG.md | 4 +- .../Drawing/DebugOptions.cs | 0 .../Drawing/DrawingPrimitives.cs | 316 ++++++ .../Evaluation/BinaryEvaluation.cs | 0 .../Evaluation/BinaryOperation.cs | 0 .../Evaluation/ConstantEvaluation.cs | 0 .../Evaluation/CustomFunctionEvaluation.cs | 0 .../Evaluation/Evaluation.cs | 0 .../Evaluation/Evaluator.cs | 0 .../Evaluation/UnaryEvaluation.cs | 0 .../Evaluation/VariableEvaluation.cs | 0 .../Extensions/ParseTreeNodeExtensions.cs | 32 +- .../Parser/PdfGrammar.cs | 13 +- .../Parser/PdfParserException.cs | 0 .../Parser/PdfVisitor.cs | 0 .../PdfSharpDsl.Language.csproj | 19 + PdfSharpDslConsole/Fonts/MyFontResolver.cs | 43 - PdfSharpDslConsole/Program.cs | 22 +- .../helloworld-with-pdfsharp.pdf | Bin 0 -> 193103 bytes PdfSharpDslConsole/helloworld.pdf | Bin 134123 -> 76945 bytes .../DrawingGenerator/CSharpDrawer.cs | 186 ---- .../DrawingGenerator/CSharpGeneratorState.cs | 1 - .../DrawingGenerator/CSharpVisitor.cs | 29 +- .../PdfSharpDslCore.Generator.csproj | 22 +- PdfSharpDslCore/Drawing/DrawingContext.cs | 25 +- PdfSharpDslCore/Drawing/DrawingHelper.cs | 69 +- PdfSharpDslCore/Drawing/IInstructionBlock.cs | 5 +- PdfSharpDslCore/Drawing/IPdfDocumentDrawer.cs | 22 +- PdfSharpDslCore/Drawing/InstructionBlock.cs | 11 +- .../Drawing/InstructionsRecorder.cs | 3 +- PdfSharpDslCore/Drawing/PdfDocumentDrawer.cs | 913 ++++++------------ PdfSharpDslCore/Drawing/TableDefinition.cs | 16 +- PdfSharpDslCore/Extensions/XRectExtensions.cs | 16 +- PdfSharpDslCore/Parser/PdfDrawerVisitor.cs | 64 +- PdfSharpDslCore/PdfSharpDslCore.csproj | 15 +- PdfSharpDslCore/Readme.md | 4 +- README.md | 17 +- bnf_and_pdf.sln | 6 + pdfsharpdslTests/DrawingValueTests.cs | 40 +- pdfsharpdslTests/GenerationBaseTests.cs | 11 +- pdfsharpdslTests/GenerationTableTests.cs | 13 +- pdfsharpdslTests/GenerationTests.cs | 79 +- .../Parser/PdfDrawerForTestsVisitor.cs | 1 - pdfsharpdslTests/Parser/TextDocumentDrawer.cs | 38 +- pdfsharpdslTests/ParserTests.cs | 8 + pdfsharpdslTests/PdfBinaryInspector.cs | 47 + pdfsharpdslTests/PdfDocumentDrawerTests.cs | 25 +- pdfsharpdslTests/RenderingTests.cs | 3 +- .../ReplayerTests/InstructionRecorderTests.cs | 42 +- .../SourceGenerator/SourceGeneratorTests.cs | 3 +- pdfsharpdslTests/VisitorTests.cs | 17 +- 51 files changed, 937 insertions(+), 1263 deletions(-) rename {PdfSharpDslCore => PdfSharpDsl.Language}/Drawing/DebugOptions.cs (100%) create mode 100644 PdfSharpDsl.Language/Drawing/DrawingPrimitives.cs rename {PdfSharpDslCore => PdfSharpDsl.Language}/Evaluation/BinaryEvaluation.cs (100%) rename {PdfSharpDslCore => PdfSharpDsl.Language}/Evaluation/BinaryOperation.cs (100%) rename {PdfSharpDslCore => PdfSharpDsl.Language}/Evaluation/ConstantEvaluation.cs (100%) rename {PdfSharpDslCore => PdfSharpDsl.Language}/Evaluation/CustomFunctionEvaluation.cs (100%) rename {PdfSharpDslCore => PdfSharpDsl.Language}/Evaluation/Evaluation.cs (100%) rename {PdfSharpDslCore => PdfSharpDsl.Language}/Evaluation/Evaluator.cs (100%) rename {PdfSharpDslCore => PdfSharpDsl.Language}/Evaluation/UnaryEvaluation.cs (100%) rename {PdfSharpDslCore => PdfSharpDsl.Language}/Evaluation/VariableEvaluation.cs (100%) rename {PdfSharpDslCore => PdfSharpDsl.Language}/Extensions/ParseTreeNodeExtensions.cs (66%) rename {PdfSharpDslCore => PdfSharpDsl.Language}/Parser/PdfGrammar.cs (98%) rename {PdfSharpDslCore => PdfSharpDsl.Language}/Parser/PdfParserException.cs (100%) rename {PdfSharpDslCore => PdfSharpDsl.Language}/Parser/PdfVisitor.cs (100%) create mode 100644 PdfSharpDsl.Language/PdfSharpDsl.Language.csproj delete mode 100644 PdfSharpDslConsole/Fonts/MyFontResolver.cs create mode 100644 PdfSharpDslConsole/helloworld-with-pdfsharp.pdf delete mode 100644 PdfSharpDslCore.Generator/DrawingGenerator/CSharpDrawer.cs create mode 100644 pdfsharpdslTests/PdfBinaryInspector.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 08713a6..9d11908 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,9 @@ * Migrated console and test projects to .NET 10 while retaining reusable projects on .NET Standard 2.0. * Added reproducible core-only coverage enforcement at 90% line coverage. * Added Roslyn source-generator compilation and clean NuGet consumer validation. -* Updated dependencies and resolved known ImageSharp and runtime package advisories. +* Migrated PDF generation from PdfSharpCore and its image/font/archive dependencies to TerraPDF 2.1.0. +* Added engine-independent drawing primitives and `PdfSharpDsl.Language` for the netstandard2.0 source generator. +* Added `PublishPdf(Stream)`, `PublishPdf(string)` and `PublishPdf()` to `PdfDocumentDrawer`. ## Version 1.0.5 (March 3, 2024) * Update nugets packages and upgrade to .Net 8 diff --git a/PdfSharpDslCore/Drawing/DebugOptions.cs b/PdfSharpDsl.Language/Drawing/DebugOptions.cs similarity index 100% rename from PdfSharpDslCore/Drawing/DebugOptions.cs rename to PdfSharpDsl.Language/Drawing/DebugOptions.cs diff --git a/PdfSharpDsl.Language/Drawing/DrawingPrimitives.cs b/PdfSharpDsl.Language/Drawing/DrawingPrimitives.cs new file mode 100644 index 0000000..a57ec16 --- /dev/null +++ b/PdfSharpDsl.Language/Drawing/DrawingPrimitives.cs @@ -0,0 +1,316 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace PdfSharpDslCore.Drawing +{ + public readonly struct PdfPoint + { + public PdfPoint(double x, double y) + { + X = x; + Y = y; + } + + public double X { get; } + public double Y { get; } + + public PdfPoint OffsetY(double offsetY) => new(X, Y + offsetY); + } + + public readonly struct PdfSize + { + public PdfSize(double width, double height) + { + Width = width; + Height = height; + } + + public double Width { get; } + public double Height { get; } + } + + public struct PdfRect + { + public PdfRect(double x, double y, double width, double height) + { + X = x; + Y = y; + Width = width; + Height = height; + } + + public double X { readonly get; private set; } + public double Y { readonly get; private set; } + public double Width { readonly get; private set; } + public double Height { readonly get; private set; } + + public static PdfRect Empty => new(0, 0, -1, -1); + + public PdfRect(PdfPoint first, PdfPoint second) + : this(Math.Min(first.X, second.X), Math.Min(first.Y, second.Y), + Math.Abs(second.X - first.X), Math.Abs(second.Y - first.Y)) + { + } + + public PdfRect(PdfPoint location, PdfSize size) + : this(location.X, location.Y, size.Width, size.Height) + { + } + + public readonly bool IsEmpty => Width < 0 || Height < 0; + public readonly double Left => X; + public readonly double Top => Y; + public readonly double Right => X + Width; + public readonly double Bottom => Y + Height; + public readonly PdfPoint TopLeft => new(X, Y); + + public void Offset(double offsetX, double offsetY) + { + X += offsetX; + Y += offsetY; + } + + public readonly PdfRect OffsetY(double offsetY) => new(X, Y + offsetY, Width, Height); + + public void Union(PdfPoint point) + { + Union(new PdfRect(point.X, point.Y, 0, 0)); + } + + public void Union(PdfRect rect) + { + if (rect.IsEmpty) + { + return; + } + + if (IsEmpty) + { + this = rect; + return; + } + + var left = Math.Min(Left, rect.Left); + var top = Math.Min(Top, rect.Top); + var right = Math.Max(Right, rect.Right); + var bottom = Math.Max(Bottom, rect.Bottom); + this = new PdfRect(left, top, right - left, bottom - top); + } + + public void Intersect(PdfRect rect) + { + var left = Math.Max(Left, rect.Left); + var top = Math.Max(Top, rect.Top); + var right = Math.Min(Right, rect.Right); + var bottom = Math.Min(Bottom, rect.Bottom); + this = right < left || bottom < top + ? Empty + : new PdfRect(left, top, right - left, bottom - top); + } + } + + public readonly struct PdfColor + { + public PdfColor(byte alpha, byte red, byte green, byte blue) + { + Alpha = alpha; + Red = red; + Green = green; + Blue = blue; + } + + public byte Alpha { get; } + public byte Red { get; } + public byte Green { get; } + public byte Blue { get; } + + public byte A => Alpha; + + public static PdfColor FromArgb(byte alpha, byte red, byte green, byte blue) => + new(alpha, red, green, blue); + + public static PdfColor FromRgb(byte red, byte green, byte blue) => new(255, red, green, blue); + + public static PdfColor FromArgb(uint argb) => new( + (byte)(argb >> 24), + (byte)(argb >> 16), + (byte)(argb >> 8), + (byte)argb); + + public static PdfColor FromGrayScale(double value) + { + var component = (byte)Math.Min(255, Math.Max(0, (int)Math.Round(value * 255))); + return FromRgb(component, component, component); + } + + public static PdfColor Black => FromRgb(0, 0, 0); + public static PdfColor White => FromRgb(255, 255, 255); + public static PdfColor RedColor => FromRgb(255, 0, 0); + public static PdfColor Transparent => new(0, 0, 0, 0); + + internal string Hex => $"#{Red:X2}{Green:X2}{Blue:X2}"; + internal double Opacity => Alpha / 255d; + } + + public static class PdfColors + { + private static readonly IReadOnlyDictionary Colors = + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + ["black"] = PdfColor.FromRgb(0, 0, 0), + ["blue"] = PdfColor.FromRgb(0, 0, 255), + ["crimson"] = PdfColor.FromRgb(220, 20, 60), + ["darkblue"] = PdfColor.FromRgb(0, 0, 139), + ["darkgreen"] = PdfColor.FromRgb(0, 100, 0), + ["darkslategray"] = PdfColor.FromRgb(47, 79, 79), + ["gold"] = PdfColor.FromRgb(255, 215, 0), + ["gray"] = PdfColor.FromRgb(128, 128, 128), + ["green"] = PdfColor.FromRgb(0, 128, 0), + ["lightblue"] = PdfColor.FromRgb(173, 216, 230), + ["lightgray"] = PdfColor.FromRgb(211, 211, 211), + ["lightgreen"] = PdfColor.FromRgb(144, 238, 144), + ["lightsalmon"] = PdfColor.FromRgb(255, 160, 122), + ["lightseagreen"] = PdfColor.FromRgb(32, 178, 170), + ["maroon"] = PdfColor.FromRgb(128, 0, 0), + ["orange"] = PdfColor.FromRgb(255, 165, 0), + ["purple"] = PdfColor.FromRgb(128, 0, 128), + ["red"] = PdfColor.FromRgb(255, 0, 0), + ["slategray"] = PdfColor.FromRgb(112, 128, 144), + ["steelblue"] = PdfColor.FromRgb(70, 130, 180), + ["tomato"] = PdfColor.FromRgb(255, 99, 71), + ["transparent"] = PdfColor.Transparent, + ["violet"] = PdfColor.FromRgb(238, 130, 238), + ["white"] = PdfColor.FromRgb(255, 255, 255), + ["yellow"] = PdfColor.FromRgb(255, 255, 0), + }; + + public static IEnumerable Names => Colors.Keys; + + public static PdfColor FromName(string name) + { + return Colors.TryGetValue(name, out var color) ? color : PdfColor.Black; + } + } + + public enum PdfDashStyle + { + Solid, + Dash, + Dot, + DashDot, + DashDotDot, + } + + public sealed class PdfPen + { + public PdfPen(PdfColor color, double width) + { + Color = color; + Width = width; + } + + public PdfColor Color { get; } + public double Width { get; } + public PdfDashStyle DashStyle { get; set; } = PdfDashStyle.Solid; + } + + public sealed class PdfBrush + { + public PdfBrush(PdfColor color) + { + Color = color; + } + + public PdfColor Color { get; } + } + + [Flags] + public enum PdfFontStyle + { + Regular = 0, + Bold = 1, + Italic = 2, + BoldItalic = Bold | Italic, + Underline = 4, + Strikeout = 8, + } + + public sealed class PdfFont + { + public PdfFont(string familyName, double size, PdfFontStyle style = PdfFontStyle.Regular) + { + FamilyName = familyName; + Size = size; + Style = style; + } + + public string FamilyName { get; } + public double Size { get; } + public PdfFontStyle Style { get; } + } + + public sealed class PdfImage + { + private readonly byte[] _data; + + public PdfImage(ReadOnlySpan data) + { + _data = data.ToArray(); + } + + public ReadOnlyMemory Data => _data; + } + + public sealed class PdfMargins + { + private double _all; + + public double All + { + set => Left = Top = Right = Bottom = _all = value; + get => _all; + } + + public double Left { get; set; } + public double Top { get; set; } + public double Right { get; set; } + public double Bottom { get; set; } + } + + public enum PdfHorizontalAlignment + { + Near, + Center, + Far, + } + + public enum PdfVerticalAlignment + { + Near, + Center, + Far, + } + + public enum PdfPageOrientation + { + Portrait, + Landscape, + } + + public enum PdfPageSize + { + Undefined, + A0, + A1, + A2, + A3, + A4, + A5, + A6, + Letter, + Legal, + Ledger, + Tabloid, + } +} \ No newline at end of file diff --git a/PdfSharpDslCore/Evaluation/BinaryEvaluation.cs b/PdfSharpDsl.Language/Evaluation/BinaryEvaluation.cs similarity index 100% rename from PdfSharpDslCore/Evaluation/BinaryEvaluation.cs rename to PdfSharpDsl.Language/Evaluation/BinaryEvaluation.cs diff --git a/PdfSharpDslCore/Evaluation/BinaryOperation.cs b/PdfSharpDsl.Language/Evaluation/BinaryOperation.cs similarity index 100% rename from PdfSharpDslCore/Evaluation/BinaryOperation.cs rename to PdfSharpDsl.Language/Evaluation/BinaryOperation.cs diff --git a/PdfSharpDslCore/Evaluation/ConstantEvaluation.cs b/PdfSharpDsl.Language/Evaluation/ConstantEvaluation.cs similarity index 100% rename from PdfSharpDslCore/Evaluation/ConstantEvaluation.cs rename to PdfSharpDsl.Language/Evaluation/ConstantEvaluation.cs diff --git a/PdfSharpDslCore/Evaluation/CustomFunctionEvaluation.cs b/PdfSharpDsl.Language/Evaluation/CustomFunctionEvaluation.cs similarity index 100% rename from PdfSharpDslCore/Evaluation/CustomFunctionEvaluation.cs rename to PdfSharpDsl.Language/Evaluation/CustomFunctionEvaluation.cs diff --git a/PdfSharpDslCore/Evaluation/Evaluation.cs b/PdfSharpDsl.Language/Evaluation/Evaluation.cs similarity index 100% rename from PdfSharpDslCore/Evaluation/Evaluation.cs rename to PdfSharpDsl.Language/Evaluation/Evaluation.cs diff --git a/PdfSharpDslCore/Evaluation/Evaluator.cs b/PdfSharpDsl.Language/Evaluation/Evaluator.cs similarity index 100% rename from PdfSharpDslCore/Evaluation/Evaluator.cs rename to PdfSharpDsl.Language/Evaluation/Evaluator.cs diff --git a/PdfSharpDslCore/Evaluation/UnaryEvaluation.cs b/PdfSharpDsl.Language/Evaluation/UnaryEvaluation.cs similarity index 100% rename from PdfSharpDslCore/Evaluation/UnaryEvaluation.cs rename to PdfSharpDsl.Language/Evaluation/UnaryEvaluation.cs diff --git a/PdfSharpDslCore/Evaluation/VariableEvaluation.cs b/PdfSharpDsl.Language/Evaluation/VariableEvaluation.cs similarity index 100% rename from PdfSharpDslCore/Evaluation/VariableEvaluation.cs rename to PdfSharpDsl.Language/Evaluation/VariableEvaluation.cs diff --git a/PdfSharpDslCore/Extensions/ParseTreeNodeExtensions.cs b/PdfSharpDsl.Language/Extensions/ParseTreeNodeExtensions.cs similarity index 66% rename from PdfSharpDslCore/Extensions/ParseTreeNodeExtensions.cs rename to PdfSharpDsl.Language/Extensions/ParseTreeNodeExtensions.cs index f50c723..90fdb21 100644 --- a/PdfSharpDslCore/Extensions/ParseTreeNodeExtensions.cs +++ b/PdfSharpDsl.Language/Extensions/ParseTreeNodeExtensions.cs @@ -1,5 +1,5 @@ using Irony.Parsing; -using PdfSharpCore.Drawing; +using PdfSharpDslCore.Drawing; using System; using System.Collections.Generic; using System.Linq; @@ -41,22 +41,22 @@ public static IEnumerable ChildNodes(this ParseTreeNode node, str return node.ChildNodes.FirstOrDefault(n => n.Term != null && n.Term.Name == termName); } - public static XFontStyle ParseFontStyle(this ParseTreeNode? node) + public static PdfFontStyle ParseFontStyle(this ParseTreeNode? node) { if (node != null && node.Token != null) { var styleName = (string?)node.Token.Value; - if (Enum.TryParse(styleName, true, out var fontStyle)) + if (Enum.TryParse(styleName, true, out var fontStyle)) { return fontStyle; } } - return XFontStyle.Regular; + return PdfFontStyle.Regular; } - public static XColor ParseColor(this ParseTreeNode node) + public static PdfColor ParseColor(this ParseTreeNode node) { - var executor = (Func)(node.ChildNodes[0].Term.Name switch + var executor = (Func)(node.ChildNodes[0].Term.Name switch { "NamedColor" => ParseNamedColor, _ => ParseHexColor, @@ -64,36 +64,32 @@ public static XColor ParseColor(this ParseTreeNode node) return executor(node.ChildNodes[0]); } - private static XColor ParseNamedColor(ParseTreeNode node) + private static PdfColor ParseNamedColor(ParseTreeNode node) { var color = (string)node.ChildNodes[0].Token.Value; - - var staticColor = typeof(XColors) - .GetProperties(BindingFlags.Public | BindingFlags.Static) - .FirstOrDefault(x => string.Compare(x.Name, color, StringComparison.OrdinalIgnoreCase) == 0); - return ((XColor?)staticColor?.GetValue(null)) ?? XColors.Black; + return PdfColors.FromName(color); } - private static XColor ParseHexColor(ParseTreeNode node) + private static PdfColor ParseHexColor(ParseTreeNode node) { var colorValue = node.ChildNodes[0].Token.Value; if (colorValue is double) { - return XColor.FromGrayScale(Convert.ToDouble(colorValue)); + return PdfColor.FromGrayScale(Convert.ToDouble(colorValue)); } else { if (node.ChildNodes[0].Token.Length == 8) { uint argb = ((uint)0xff000000) | Convert.ToUInt32(colorValue); - return XColor.FromArgb(argb); + return PdfColor.FromArgb(argb); } else if (node.ChildNodes[0].Token.Length == 10) { - int argb = Convert.ToInt32(colorValue); - return XColor.FromArgb(argb); + uint argb = unchecked((uint)Convert.ToInt32(colorValue)); + return PdfColor.FromArgb(argb); } - return XColor.FromArgb(Convert.ToInt32(colorValue)); + return PdfColor.FromArgb(unchecked((uint)Convert.ToInt32(colorValue))); } } diff --git a/PdfSharpDslCore/Parser/PdfGrammar.cs b/PdfSharpDsl.Language/Parser/PdfGrammar.cs similarity index 98% rename from PdfSharpDslCore/Parser/PdfGrammar.cs rename to PdfSharpDsl.Language/Parser/PdfGrammar.cs index 6238445..7c15cb8 100644 --- a/PdfSharpDslCore/Parser/PdfGrammar.cs +++ b/PdfSharpDsl.Language/Parser/PdfGrammar.cs @@ -1,8 +1,7 @@ using Irony; using Irony.Parsing; -using PdfSharpCore; -using PdfSharpCore.Drawing; +using PdfSharpDslCore.Drawing; using System; using System.Collections.Generic; using System.Data; @@ -258,9 +257,9 @@ public PdfGrammar() VarSmt.Rule = ToTerm("VAR") + variableLiteral + "=" + FormulaExpression + semi; ColorExp.Rule = NamedColor | HexColor; - foreach (var prop in typeof(XColors).GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static)) + foreach (var colorName in PdfColors.Names) { - var name = prop.Name.ToLowerInvariant(); + var name = colorName.ToLowerInvariant(); if (NamedColor.Rule == null) { NamedColor.Rule = ToTerm(name, $"color-{name}"); @@ -274,14 +273,14 @@ public PdfGrammar() HexColor.Rule = colorNumber; styleExpr.Rule = Empty; - foreach (var enumName in Enum.GetNames(typeof(XFontStyle))) + foreach (var enumName in Enum.GetNames(typeof(PdfFontStyle))) { var styleName = enumName.ToLowerInvariant(); styleExpr.Rule |= ToTerm(styleName, $"style-{styleName}"); } - //TextAlignment is not yet supported on multiline text (only top left is provided by pdfsharpcore) + //Multiline alignment is implemented by the TerraPDF canvas adapter. //multiline TextSmt.Rule = ToInstructionTerm("TEXT") + RectOrPointLocation + OptArg("MaxWidth", FormulaExpression) + Arg("Text") + FormulaExpression; @@ -303,7 +302,7 @@ public PdfGrammar() PageSize.Rule = Empty; - var names = Enum.GetNames(typeof(PageSize)); + var names = Enum.GetNames(typeof(PdfPageSize)); var firstSize = names.First(); PageSize.Rule |= ToTerm(firstSize, $"pagesize-{firstSize}"); foreach (var prop in names.Skip(1)) diff --git a/PdfSharpDslCore/Parser/PdfParserException.cs b/PdfSharpDsl.Language/Parser/PdfParserException.cs similarity index 100% rename from PdfSharpDslCore/Parser/PdfParserException.cs rename to PdfSharpDsl.Language/Parser/PdfParserException.cs diff --git a/PdfSharpDslCore/Parser/PdfVisitor.cs b/PdfSharpDsl.Language/Parser/PdfVisitor.cs similarity index 100% rename from PdfSharpDslCore/Parser/PdfVisitor.cs rename to PdfSharpDsl.Language/Parser/PdfVisitor.cs diff --git a/PdfSharpDsl.Language/PdfSharpDsl.Language.csproj b/PdfSharpDsl.Language/PdfSharpDsl.Language.csproj new file mode 100644 index 0000000..500aa65 --- /dev/null +++ b/PdfSharpDsl.Language/PdfSharpDsl.Language.csproj @@ -0,0 +1,19 @@ + + + netstandard2.0 + 10.0 + enable + false + + + + + + + + + + + + + \ No newline at end of file diff --git a/PdfSharpDslConsole/Fonts/MyFontResolver.cs b/PdfSharpDslConsole/Fonts/MyFontResolver.cs deleted file mode 100644 index 1405442..0000000 --- a/PdfSharpDslConsole/Fonts/MyFontResolver.cs +++ /dev/null @@ -1,43 +0,0 @@ -using PdfSharpCore.Fonts; -using PdfSharpCore.Utils; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace PdfSharpDslConsole.Fonts -{ - internal class MyFontResolver : IFontResolver - { - private readonly IFontResolver _fontResolver; - Dictionary fontmap = new Dictionary(); - public string DefaultFontName => "Arial"; - public MyFontResolver(IEnumerable fontFiles) - { - _fontResolver = new FontResolver(); - if (fontFiles != null && fontFiles.Any()) - { - foreach (var fontFile in fontFiles) - { - fontmap.Add(Path.GetFileName(fontFile).ToLowerInvariant(), fontFile); - } - FontResolver.SetupFontsFiles(fontFiles.ToArray()); - } - } - public byte[] GetFont(string faceName) - { - if (fontmap.TryGetValue(faceName.ToLowerInvariant(), out var file)) - { - return File.ReadAllBytes(file); - } - - return _fontResolver.GetFont(faceName); - } - - public FontResolverInfo ResolveTypeface(string familyName, bool isBold, bool isItalic) - { - return _fontResolver.ResolveTypeface(familyName, isBold, isItalic); - } - } -} diff --git a/PdfSharpDslConsole/Program.cs b/PdfSharpDslConsole/Program.cs index 55a1491..97d392f 100644 --- a/PdfSharpDslConsole/Program.cs +++ b/PdfSharpDslConsole/Program.cs @@ -1,13 +1,9 @@ using System.Globalization; -using PdfSharpCore.Fonts; -using PdfSharpCore.Pdf; -using PdfSharpDslConsole.Fonts; using PdfSharpDslCore.Drawing; using PdfSharpDslCore.Parser; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Logging.Console; +using TerraPDF.Helpers; ServiceProvider serviceProvider = new ServiceCollection() .AddLogging((loggingBuilder) => loggingBuilder @@ -27,10 +23,6 @@ logger?.LogDebug("Debug World"); logger?.LogInformation("Hello World"); -//GlobalFontSettings.FontResolver = new FontResolver(); -GlobalFontSettings.DefaultFontEncoding = PdfFontEncoding.Unicode; - - #region global variables var globalComments = new[] { @@ -115,11 +107,13 @@ } else { - GlobalFontSettings.FontResolver = new MyFontResolver(LocalFontFiles()); - //PdfSharpCore cclasses - var document = new PdfDocument(); + foreach (var font in LocalFontNames().Zip(LocalFontFiles())) + { + FontFamily.Register(font.First, font.Second); + } + //draw parsing result - using var drawer = new PdfDocumentDrawer(document, logger); + using var drawer = new PdfDocumentDrawer(logger); var visitor = new PdfDrawerVisitor(logger); visitor.RegisterFormulaFunction("GetFontCount", (_) => LocalFontNames().Count()); @@ -132,7 +126,7 @@ visitor.RegisterFormulaFunction("GETCOMMENTAUTHOR", getCommentAuthor); visitor.Draw(drawer, parsingResult); - document.Save("helloworld.pdf"); + drawer.PublishPdf("helloworld.pdf"); //var a = new PDfDsl.pdfsharp(); //a.WritePdf(drawer); diff --git a/PdfSharpDslConsole/helloworld-with-pdfsharp.pdf b/PdfSharpDslConsole/helloworld-with-pdfsharp.pdf new file mode 100644 index 0000000000000000000000000000000000000000..01f4ce06a7aaabcb00bc0aca9f62f10d3a7b4ce7 GIT binary patch literal 193103 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 CFpP`< literal 0 HcmV?d00001 diff --git a/PdfSharpDslConsole/helloworld.pdf b/PdfSharpDslConsole/helloworld.pdf index 8044ef38b0484d41db884a9384a9b0214ad7b573..1f4a2fa06a455d4ccf68318f7f7075cf5f23947b 100644 GIT binary patch literal 76945 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 z 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 Y#U+VFCE%jk+!S~tH literal 134123 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 z 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 Z2@G-x2>R!aBLDya$3YYo4NM7;{{SY}V> throw new NotImplementedException("CurrentPen"); - set - { - var penvName = $"pen{vNameIndex++}"; - _code.AppendLine($"var {penvName} = new XPen({ColorToString(value.Color)}, {value.Width});"); - _code.AppendLine($"{penvName}.DashStyle = XDashStyle.{value.DashStyle};"); - _code.AppendLine($"{_drawerPrefix}CurrentPen = {penvName};"); - } - } - - uint Argb(XColor color) - { - var _a = color.A; - var _r = color.R; - var _g = color.G; - var _b = color.B; - return ((uint)(_a * 255) << 24) | ((uint)_r << 16) | ((uint)_g << 8) | _b; - } - - private string ColorToString(XColor color) - { - if (color.IsKnownColor) - { - return $"XColors.{XColorResourceManager.GetKnownColor(Argb(color))}"; - } - else - { - return $"XColor.FromArgb({color.A},{color.R},{color.G},{color.B})"; - } - } - - public XBrush CurrentBrush - { - get => throw new NotImplementedException("CurrentBrush"); - set { } - } - - public XBrush HighlightBrush - { - get => throw new NotImplementedException(); - set { } - } - public XFont CurrentFont - { - get => throw new NotImplementedException("CurrentFont"); - set { } - } - - public double PageWidth => 100;//throw new NotImplementedException(); - - public double PageHeight => 100;//throw new NotImplementedException(); - public DebugOptions DebugOptions { get; set; } - - public void DrawEllipse(double x, double y, double w, double h, bool isFilled) - { - //throw new NotImplementedException(); - } - - public void DrawImage(XImage image, double x, double y, double? w, double? h, bool sizeInPixel, bool cropImage) - { - //throw new NotImplementedException(); - } - - public void DrawLine(double x, double y, double x1, double y1) - { - //throw new NotImplementedException(); - } - - public void DrawLineText(string text, double x, double y, double? w, double? h, XStringAlignment hAlign, XLineAlignment vAlign, TextOrientation textOrientation) - { - //throw new NotImplementedException("DrawLineText"); - } - - public void DrawPie(double x, double y, double? w, double? h, double startAngle, double sweepAngle, bool isFilled) - { - //throw new NotImplementedException(); - } - - public void DrawPolygon(IEnumerable points, bool isFilled) - { - //throw new NotImplementedException(); - } - - public void DrawRect(double x, double y, double w, double h, bool isFilled) - { - //throw new NotImplementedException(); - } - - public void DrawTable(double x, double y, TableDefinition tblDef) - { - //throw new NotImplementedException(); - } - - public void DrawText(string text, double x, double y, double? w, double? h) - { - //throw new NotImplementedException(); - } - - public void DrawTitle(string text, double margin, XStringAlignment hAlign, XLineAlignment vAlign) - { - //throw new NotImplementedException(); - } - - public void LineTo(double x, double y) - { - //throw new NotImplementedException(); - } - - public void MoveTo(double x, double y) - { - //throw new NotImplementedException(); - } - - public void NewPage(PageSize? pageSize = null, PageOrientation? pageOrientation = null) - { - //throw new NotImplementedException(); - } - - public void SetViewSize(double w, double h) - { - //throw new NotImplementedException(); - } - - public void BeginDrawRowTemplate(string name, int index, double offsetY, double newPageTopMargin) - { - //throw new NotImplementedException(); - } - - public DrawingResult EndDrawRowTemplate(int index) - { - //throw new NotImplementedException(); - return new DrawingResult(); - } - - public void BeginIterationTemplate(int rowCount) - { - //throw new NotImplementedException(); - } - - public void EndIterationTemplate(double drawHeight) - { - //throw new NotImplementedException(); - } - - public void RegisterOnNewPage(Action callback) - { - //throw new NotImplementedException(); - } - - public void UnRegisterOnNewPage(Action callback) - { - //throw new NotImplementedException(); - } - - public void SetOffsetY(double offsetY) - { - throw new NotImplementedException(); - } - - public void ResetOffset() - { - throw new NotImplementedException(); - } - } -} diff --git a/PdfSharpDslCore.Generator/DrawingGenerator/CSharpGeneratorState.cs b/PdfSharpDslCore.Generator/DrawingGenerator/CSharpGeneratorState.cs index 08ef97a..7680b5d 100644 --- a/PdfSharpDslCore.Generator/DrawingGenerator/CSharpGeneratorState.cs +++ b/PdfSharpDslCore.Generator/DrawingGenerator/CSharpGeneratorState.cs @@ -61,7 +61,6 @@ private StringBuilder ConstructCode() #nullable enable namespace PDfDsl { using System.Collections.Generic; - using PdfSharpCore.Drawing; using PdfSharpDslCore.Drawing; //another comments "); diff --git a/PdfSharpDslCore.Generator/DrawingGenerator/CSharpVisitor.cs b/PdfSharpDslCore.Generator/DrawingGenerator/CSharpVisitor.cs index e0c07d8..591fa44 100644 --- a/PdfSharpDslCore.Generator/DrawingGenerator/CSharpVisitor.cs +++ b/PdfSharpDslCore.Generator/DrawingGenerator/CSharpVisitor.cs @@ -1,6 +1,4 @@ using Irony.Parsing; -using PdfSharpCore; -using PdfSharpCore.Drawing; using PdfSharpDslCore.Drawing; using PdfSharpDslCore.Evaluation; using PdfSharpDslCore.Extensions; @@ -65,11 +63,10 @@ protected override void ExecutePen(IGeneratorState state, ParseTreeNode widthNod ParseTreeNode styleNode) { var penvName = $"pen{vNameIndex++}"; - state.AppendLine($"var {penvName} = new XPen({ColorToString(colorNode.ParseColor())}, (double){EvaluateForString(widthNode).StringValue});"); - //_code.AppendLine($"var {penvName} = new XPen({ColorToString(value.Color)}, {value.Width});"); - if (styleNode != null && Enum.TryParse< XDashStyle>(styleNode.Token.ValueString, true, out var penStyle)) + state.AppendLine($"var {penvName} = new PdfPen({ColorToString(colorNode.ParseColor())}, (double){EvaluateForString(widthNode).StringValue});"); + if (styleNode != null && Enum.TryParse(styleNode.Token.ValueString, true, out var penStyle)) { - state.AppendLine($"{penvName}.DashStyle = XDashStyle.{penStyle.ToString()};"); + state.AppendLine($"{penvName}.DashStyle = PdfDashStyle.{penStyle};"); } state.AppendLine($"{_prefix}CurrentPen = {penvName};"); } @@ -104,25 +101,9 @@ private EvaluationResult EvaluateForString(ParseTreeNode node) return new CSharpEvaluator(_prefix, node).EvaluateForCSharpString(_declaredVariables, _declaredFunctions); } - uint Argb(XColor color) + private static string ColorToString(PdfColor color) { - var _a = color.A; - var _r = color.R; - var _g = color.G; - var _b = color.B; - return ((uint)(_a * 255) << 24) | ((uint)_r << 16) | ((uint)_g << 8) | _b; - } - - private string ColorToString(XColor color) - { - if (color.IsKnownColor) - { - return $"XColors.{XColorResourceManager.GetKnownColor(Argb(color))}"; - } - else - { - return $"XColor.FromArgb({color.A},{color.R},{color.G},{color.B})"; - } + return $"PdfColor.FromArgb({color.Alpha}, {color.Red}, {color.Green}, {color.Blue})"; } } } \ No newline at end of file diff --git a/PdfSharpDslCore.Generator/PdfSharpDslCore.Generator.csproj b/PdfSharpDslCore.Generator/PdfSharpDslCore.Generator.csproj index f83c090..6adf390 100644 --- a/PdfSharpDslCore.Generator/PdfSharpDslCore.Generator.csproj +++ b/PdfSharpDslCore.Generator/PdfSharpDslCore.Generator.csproj @@ -16,6 +16,7 @@ false true true + true @@ -23,10 +24,6 @@ - - - - @@ -36,19 +33,18 @@ - + - - - - - + + + + @@ -58,11 +54,7 @@ - - - - - + diff --git a/PdfSharpDslCore/Drawing/DrawingContext.cs b/PdfSharpDslCore/Drawing/DrawingContext.cs index 70294c0..21e6caa 100644 --- a/PdfSharpDslCore/Drawing/DrawingContext.cs +++ b/PdfSharpDslCore/Drawing/DrawingContext.cs @@ -1,5 +1,4 @@ -using PdfSharpCore.Drawing; -using System; +using System; using System.Collections.Generic; using Irony; using Microsoft.Extensions.Logging; @@ -9,8 +8,8 @@ namespace PdfSharpDslCore.Drawing internal class DrawingContext { private readonly BlocksRecorder _recorder; - private readonly Stack _previousGraphics = new(); - public int Level => _previousGraphics.Count; + private int _level; + public int Level => _level; public DebugOptions DebugOptions { get; set; } public bool DebugText => (DebugOptions & (DebugOptions.DebugText | DebugOptions.DebugAll)) > 0; @@ -21,18 +20,18 @@ public DrawingContext(ILogger? logger) _recorder = new(logger); } - public void OpenBlock(string name, double offsetY, XGraphics previousGraphics, double newPageTopMargin) + public void OpenBlock(string name, double offsetY, double newPageTopMargin) { - _previousGraphics.Push(previousGraphics); + _level++; _recorder.OpenBlock(name, offsetY, true, newPageTopMargin); } - public XRect BlockRect => _recorder.CurrentBlock.Rect; - internal (IInstructionBlock, XGraphics) RestoreGraphics() + public PdfRect BlockRect => _recorder.CurrentBlock.Rect; + internal IInstructionBlock EndMeasure() { var block = _recorder.CurrentBlock; - - return (block, _previousGraphics.Pop()); + _level--; + return block; } internal void CloseBlock() @@ -40,7 +39,7 @@ internal void CloseBlock() _recorder.CloseBlock(); } - public void PushInstruction(Action action, XRect rect, bool accumulate=true, string instrName="") + public void PushInstruction(Action action, PdfRect rect, bool accumulate=true, string instrName="") { if (_recorder.CanPushInstruction) { @@ -49,9 +48,9 @@ public void PushInstruction(Action action, XRect rect, bool accumulate=t } } - public void PushInstruction(Action action, XPoint[] ptArray) + public void PushInstruction(Action action, PdfPoint[] ptArray) { - var r = XRect.Empty; + var r = PdfRect.Empty; foreach (var pt in ptArray) { r.Union(pt); diff --git a/PdfSharpDslCore/Drawing/DrawingHelper.cs b/PdfSharpDslCore/Drawing/DrawingHelper.cs index 0956954..d0e1004 100644 --- a/PdfSharpDslCore/Drawing/DrawingHelper.cs +++ b/PdfSharpDslCore/Drawing/DrawingHelper.cs @@ -1,7 +1,4 @@ -using PdfSharpCore.Drawing; -using PdfSharpCore.Pdf; - -namespace PdfSharpDslCore.Drawing +namespace PdfSharpDslCore.Drawing { internal static class DrawingHelper { @@ -12,30 +9,31 @@ internal static class DrawingHelper /// /// /// - public static XRect RectFromStringFormat(XRect r, XSize textSize, XStringFormat fmt) + public static PdfRect RectFromStringFormat(PdfRect r, PdfSize textSize, + PdfHorizontalAlignment horizontalAlignment, PdfVerticalAlignment verticalAlignment) { - var result = new XRect(r.TopLeft, textSize); + var result = new PdfRect(r.TopLeft, textSize); - switch (fmt.Alignment) + switch (horizontalAlignment) { - case XStringAlignment.Center: + case PdfHorizontalAlignment.Center: result.Offset((r.Width - textSize.Width) / 2, 0); break; - case XStringAlignment.Near: + case PdfHorizontalAlignment.Near: break; - case XStringAlignment.Far: + case PdfHorizontalAlignment.Far: result.Offset(r.Width - textSize.Width, 0); break; } - switch (fmt.LineAlignment) + switch (verticalAlignment) { - case XLineAlignment.Center: + case PdfVerticalAlignment.Center: result.Offset(0, (r.Height - textSize.Height) / 2); break; - case XLineAlignment.Near: + case PdfVerticalAlignment.Near: break; - case XLineAlignment.Far: + case PdfVerticalAlignment.Far: result.Offset(0, r.Height - textSize.Height); break; } @@ -45,32 +43,33 @@ public static XRect RectFromStringFormat(XRect r, XSize textSize, XStringFormat return result; } - public static XRect RectFromStringFormat(double x, double y, XSize textSize, XStringFormat fmt) + public static PdfRect RectFromStringFormat(double x, double y, PdfSize textSize, + PdfHorizontalAlignment horizontalAlignment, PdfVerticalAlignment verticalAlignment) { - var result = new XRect(x, y, textSize.Width, textSize.Height); + var result = new PdfRect(x, y, textSize.Width, textSize.Height); var xOffset = 0.0; var yOffset = 0.0; - switch (fmt.Alignment) + switch (horizontalAlignment) { - case XStringAlignment.Center: + case PdfHorizontalAlignment.Center: xOffset -= textSize.Width / 2; break; - case XStringAlignment.Near: + case PdfHorizontalAlignment.Near: break; - case XStringAlignment.Far: + case PdfHorizontalAlignment.Far: xOffset -= textSize.Width; break; } - switch (fmt.LineAlignment) + switch (verticalAlignment) { - case XLineAlignment.Center: + case PdfVerticalAlignment.Center: yOffset -= textSize.Height / 2; break; - case XLineAlignment.Near: + case PdfVerticalAlignment.Near: break; - case XLineAlignment.Far: + case PdfVerticalAlignment.Far: yOffset -= textSize.Height; break; } @@ -79,51 +78,53 @@ public static XRect RectFromStringFormat(double x, double y, XSize textSize, XSt return result; } - public static (double, double, double, double) CoordRectToPage(this PdfPage page, double x, double y, double w, double h) + public static (double, double, double, double) CoordRectToPage(double pageWidth, double pageHeight, + double x, double y, double w, double h) { if (x < 0) { - x = page.Width + x; + x = pageWidth + x; } if (y < 0) { - y = page.Height + y; + y = pageHeight + y; } if (w < 0) { - w = page.Width + w - x; + w = pageWidth + w - x; } if (h < 0) { - h = page.Height + h - y; + h = pageHeight + h - y; } return (x, y, w, h); } - public static (double, double, double?, double?) CoordRectToPage(this PdfPage page, double x, double y, double? w, double? h) + public static (double, double, double?, double?) CoordRectToPage(double pageWidth, double pageHeight, + double x, double y, double? w, double? h) { if (x < 0) { - x = page.Width + x; + x = pageWidth + x; } if (y < 0) { - y = page.Height + y; + y = pageHeight + y; } if (w is < 0) { - w = page.Width + w - x; + w = pageWidth + w - x; } if (h is < 0) { - h = page.Height + h - y; + h = pageHeight + h - y; } return (x, y, w, h); diff --git a/PdfSharpDslCore/Drawing/IInstructionBlock.cs b/PdfSharpDslCore/Drawing/IInstructionBlock.cs index d6dcce1..f5115b7 100644 --- a/PdfSharpDslCore/Drawing/IInstructionBlock.cs +++ b/PdfSharpDslCore/Drawing/IInstructionBlock.cs @@ -1,14 +1,13 @@ using System.Collections.Generic; using System.Diagnostics; using Microsoft.Extensions.Logging; -using PdfSharpCore.Drawing; using PdfSharpDslCore.Extensions; namespace PdfSharpDslCore.Drawing { internal interface IInstruction { - XRect Rect { get; } + PdfRect Rect { get; } string Name { get; } /// @@ -51,7 +50,7 @@ internal interface IInstructionBlock : IInstruction /// IInstructionBlock OpenBlock(string name, double offsetY, bool entirePrint, double newPageTopMargin=0); void CloseBlock(); - void UpdateRect(XRect rect); + void UpdateRect(PdfRect rect); void Clear(); diff --git a/PdfSharpDslCore/Drawing/IPdfDocumentDrawer.cs b/PdfSharpDslCore/Drawing/IPdfDocumentDrawer.cs index 060ef2f..1313858 100644 --- a/PdfSharpDslCore/Drawing/IPdfDocumentDrawer.cs +++ b/PdfSharpDslCore/Drawing/IPdfDocumentDrawer.cs @@ -1,8 +1,6 @@  using System; -using PdfSharpCore; -using PdfSharpCore.Drawing; using System.Collections.Generic; namespace PdfSharpDslCore.Drawing @@ -20,7 +18,7 @@ public record TextOrientation public record DrawingResult { - public XRect DrawingRect { get; set; } + public PdfRect DrawingRect { get; set; } public double PageOffsetY { get; set; } } @@ -29,27 +27,27 @@ public interface IPdfDocumentDrawer void DrawRect(double x, double y, double w, double h, bool isFilled); void DrawText(string text, double x, double y, double? w, double? h); void DrawLineText(string text, double x, double y, double? w, double? h, - XStringAlignment hAlign, XLineAlignment vAlign, TextOrientation textOrientation); + PdfHorizontalAlignment hAlign, PdfVerticalAlignment vAlign, TextOrientation textOrientation); void SetViewSize(double w, double h); - XPen CurrentPen { get; set; } - XBrush CurrentBrush { get; set; } - XBrush? HighlightBrush { get; set; } - XFont CurrentFont { get; set; } + PdfPen CurrentPen { get; set; } + PdfBrush CurrentBrush { get; set; } + PdfBrush? HighlightBrush { get; set; } + PdfFont CurrentFont { get; set; } double PageWidth { get; } double PageHeight { get; } DebugOptions DebugOptions { get; set; } - void NewPage(PageSize? pageSize = null, PageOrientation? pageOrientation = null); + void NewPage(PdfPageSize? pageSize = null, PdfPageOrientation? pageOrientation = null); void DrawLine(double x, double y, double x1, double y1); - void DrawTitle(string text, double margin, XStringAlignment hAlign, XLineAlignment vAlign); + void DrawTitle(string text, double margin, PdfHorizontalAlignment hAlign, PdfVerticalAlignment vAlign); void DrawEllipse(double x, double y, double w, double h, bool isFilled); void MoveTo(double x, double y); void LineTo(double x, double y); void DrawTable(double x, double y, TableDefinition tblDef); - void DrawImage(XImage image, double x, double y, double? w, double? h, bool sizeInPixel, bool cropImage); + void DrawImage(PdfImage image, double x, double y, double? w, double? h, bool sizeInPixel, bool cropImage); void DrawPie(double x, double y, double? w, double? h, double startAngle, double sweepAngle, bool isFilled); - void DrawPolygon(IEnumerable points, bool isFilled); + void DrawPolygon(IEnumerable points, bool isFilled); void BeginDrawRowTemplate(string name, int index, double offsetY, double newPageTopMargin); DrawingResult EndDrawRowTemplate(int index); void BeginIterationTemplate(int rowCount); diff --git a/PdfSharpDslCore/Drawing/InstructionBlock.cs b/PdfSharpDslCore/Drawing/InstructionBlock.cs index 61fc640..5221eae 100644 --- a/PdfSharpDslCore/Drawing/InstructionBlock.cs +++ b/PdfSharpDslCore/Drawing/InstructionBlock.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Runtime.CompilerServices; using Microsoft.Extensions.Logging; -using PdfSharpCore.Drawing; using PdfSharpDslCore.Extensions; namespace PdfSharpDslCore.Drawing @@ -12,11 +11,11 @@ namespace PdfSharpDslCore.Drawing [DebuggerDisplay("Rect:{Rect}")] class InstructionAction : IInstruction, IHasName { - public XRect Rect { get; } + public PdfRect Rect { get; } private readonly Action _action; public string Name { get; } - public InstructionAction(Action action, XRect rect, string name) + public InstructionAction(Action action, PdfRect rect, string name) { this.Rect = rect; _action = action; @@ -41,7 +40,7 @@ class InstructionBlock : IInstructionBlock, IHasName public bool ShouldBeEntirePrinted { get; } - public XRect Rect { get; private set; } = XRect.Empty; + public PdfRect Rect { get; private set; } = PdfRect.Empty; public IInstructionBlock? Parent => _parent; public double OffsetY => _offsetY; @@ -85,7 +84,7 @@ public double Draw(IPdfDocumentDrawer drawer, double offsetY, double pageOffsetY { var selfOffsetY = _offsetY; - XRect pageRect = new XRect(0, 0, drawer.PageWidth, drawer.PageHeight); + PdfRect pageRect = new PdfRect(0, 0, drawer.PageWidth, drawer.PageHeight); //2 cas : l'instruction ne rentre pas dans la page actuelle, il faut une nouvelle page // ça ne rentre dans aucune page, il faut "imprimé" par morceau if (ShouldBeEntirePrinted) @@ -292,7 +291,7 @@ public void CloseBlock() // } - public void UpdateRect(XRect rect) + public void UpdateRect(PdfRect rect) { if (rect.IsEmpty) return; rect.Offset(0, _offsetY); diff --git a/PdfSharpDslCore/Drawing/InstructionsRecorder.cs b/PdfSharpDslCore/Drawing/InstructionsRecorder.cs index 148e309..8b65ab9 100644 --- a/PdfSharpDslCore/Drawing/InstructionsRecorder.cs +++ b/PdfSharpDslCore/Drawing/InstructionsRecorder.cs @@ -1,5 +1,4 @@ -using PdfSharpCore.Drawing; -using PdfSharpDslCore.Drawing; +using PdfSharpDslCore.Drawing; using System; using System.Collections.Generic; using System.Linq; diff --git a/PdfSharpDslCore/Drawing/PdfDocumentDrawer.cs b/PdfSharpDslCore/Drawing/PdfDocumentDrawer.cs index ee69874..c4c692c 100644 --- a/PdfSharpDslCore/Drawing/PdfDocumentDrawer.cs +++ b/PdfSharpDslCore/Drawing/PdfDocumentDrawer.cs @@ -1,806 +1,437 @@ -using PdfSharpCore; -using PdfSharpCore.Drawing; -using PdfSharpCore.Drawing.Layout; -using PdfSharpCore.Pdf; -using SixLabors.ImageSharp; +using Microsoft.Extensions.Logging; +using PdfSharpDslCore.Extensions; using System; using System.Collections.Generic; +using System.IO; using System.Linq; -using System.Net; -using Microsoft.Extensions.Logging; -using PdfSharpDslCore.Extensions; -using SixLabors.Fonts; +using TerraPDF.Core; namespace PdfSharpDslCore.Drawing { public sealed class PdfDocumentDrawer : IDisposable, IPdfDocumentDrawer { - private readonly PdfDocument _document; - private readonly ILogger? _logger; - private PdfPage? _currentPage; - private XPen? _currentPen; - private XBrush? _currentBrush; - - private XFont? _currentFont; - private XGraphics? _gfx; - private IXGraphicsRenderer? _gfxRenderer; - private bool _disposedValue; - private XPoint _currentPoint = new XPoint(0, 0); - private PageSize _defaultPageSize = PageSize.A4; - private PageOrientation _defaultPageOrientation = PageOrientation.Portrait; - private readonly List> _onNewPageHooks = new(); + private sealed class RecordedPage + { + public RecordedPage(PdfPageSize size, PdfPageOrientation orientation) + { + (Width, Height) = GetPageDimensions(size, orientation); + } - private readonly DrawingContext _drawingCtx; + public double Width { get; } + public double Height { get; } + public double ScaleX { get; set; } = 1; + public double ScaleY { get; set; } = 1; + public List> Commands { get; } = new(); + } - private readonly XPen _debugPen = new XPen(XColors.Red, 0.5) { DashStyle = XDashStyle.DashDot }; - private readonly Lazy _debugFont = new Lazy(() => new XFont("monospace", 6)); + private readonly ILogger? _logger; + private readonly DrawingContext _drawingCtx; + private readonly List _pages = new(); + private readonly List> _onNewPageHooks = new(); + private readonly Stack _measurementStates = new(); + private PdfPageSize _defaultPageSize = PdfPageSize.A4; + private PdfPageOrientation _defaultPageOrientation = PdfPageOrientation.Portrait; + private PdfPen? _currentPen; + private PdfBrush? _currentBrush; + private PdfFont? _currentFont; + private PdfPoint _currentPoint; + private bool _isMeasuring; - public PdfDocumentDrawer(PdfDocument document, ILogger? logger = null) + public PdfDocumentDrawer(ILogger? logger = null) { - _drawingCtx = new(logger); - _document = document ?? throw new ArgumentNullException(nameof(document)); _logger = logger; + _drawingCtx = new DrawingContext(logger); } - #region properties - public DebugOptions DebugOptions { get => _drawingCtx.DebugOptions; set => _drawingCtx.DebugOptions = value; } - public PdfPage CurrentPage - { - get - { - if (_currentPage is not null) return _currentPage; - _currentPage = _document.AddPage(); - _currentPage.Size = _defaultPageSize; - _currentPage.Orientation = _defaultPageOrientation; - - return _currentPage; - } - set - { - if (_currentPage == value) return; - _currentPage = value; - if (_logger.DebugEnabled()) - { - _logger.WriteDebug(this, $"Dispose GFX:{_gfx?.GetHashCode() ?? 0}"); - } - _gfx?.Dispose(); - _gfx = null; - _gfxRenderer = null; - } - } - - public XPen CurrentPen + public PdfPen CurrentPen { - get => _currentPen ??= XPens.Black; - set => _currentPen = value; + get => _currentPen ??= new PdfPen(PdfColor.Black, 1); + set => _currentPen = value ?? throw new ArgumentNullException(nameof(value)); } - public XBrush CurrentBrush + public PdfBrush CurrentBrush { - get => _currentBrush ??= XBrushes.Black; - set => _currentBrush = value; + get => _currentBrush ??= new PdfBrush(PdfColor.Black); + set => _currentBrush = value ?? throw new ArgumentNullException(nameof(value)); } - public XBrush? HighlightBrush { get; set; } + public PdfBrush? HighlightBrush { get; set; } - public XFont CurrentFont + public PdfFont CurrentFont { - get => _currentFont ??= new XFont("Arial", 10); - set => _currentFont = value; + get => _currentFont ??= new PdfFont("Helvetica", 10); + set => _currentFont = value ?? throw new ArgumentNullException(nameof(value)); } public double PageWidth => CurrentPage.Width; public double PageHeight => CurrentPage.Height; - - private XGraphics Gfx + private RecordedPage CurrentPage { get { - if (_gfx is not null) return _gfx; - if (_logger.DebugEnabled()) + if (_pages.Count == 0) { - _logger.WriteDebug(this, "Create new gfx from page"); + _pages.Add(new RecordedPage(_defaultPageSize, _defaultPageOrientation)); } - _gfx = XGraphics.FromPdfPage(CurrentPage); - // HACK, read from https://github.com/ststeiger/PdfSharpCore/blob/master/docs/MigraDocCore/samples/MixMigraDocCoreAndPDFsharpCore.md - _gfx.MUH = PdfFontEncoding.Unicode; - return _gfx; + return _pages[_pages.Count - 1]; } } - #endregion - - private void Dispose(bool disposing) + public void PublishPdf(Stream stream) { - if (_disposedValue) return; - if (disposing) - { - // TODO: dispose managed state (managed objects) - _gfx?.Dispose(); - } + ArgumentNullException.ThrowIfNull(stream); + CreateDocument().PublishPdf(stream); + } - _gfx = null; - _gfxRenderer = null; - // TODO: free unmanaged resources (unmanaged objects) and override finalizer - // TODO: set large fields to null - _disposedValue = true; + public void PublishPdf(string path) + { + ArgumentException.ThrowIfNullOrWhiteSpace(path); + CreateDocument().PublishPdf(path); } - public void Dispose() + public byte[] PublishPdf() => CreateDocument().PublishPdf(); + + private DocumentComposer CreateDocument() { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); + _ = CurrentPage; + return Document.Create(document => + { + foreach (var recordedPage in _pages) + { + document.Page(page => + { + page.Size(recordedPage.Width, recordedPage.Height); + page.Margin(0); + page.Content().Canvas(recordedPage.Height, canvas => + { + foreach (var command in recordedPage.Commands) + { + command(canvas); + } + }); + }); + } + }); } public void DrawLine(double x, double y, double x1, double y1) { var page = CurrentPage; - if (x < 0) - { - x = page.Width + x; - } - - if (y < 0) - { - y = page.Height + y; - } - - if (x1 < 0) - { - x1 = page.Width + x1; - } - - if (y1 < 0) - { - y1 = page.Height + y1; - } - InternalDrawLine(CurrentPen, x, y, x1, y1); + InternalDrawLine(CurrentPen, ScaleX(ResolveX(x, page), page), ScaleY(ResolveY(y, page), page), + ScaleX(ResolveX(x1, page), page), ScaleY(ResolveY(y1, page), page)); } - private void InternalDrawLine(XPen pen, double x, double y, double x1, double y1) + private void InternalDrawLine(PdfPen pen, double x, double y, double x1, double y1) { - Gfx.DrawLine(pen, x, y, x1, y1); - this._drawingCtx.PushInstruction((oy) => InternalDrawLine(pen, x, y+oy, x1, y1+oy), new XRect(new XPoint(x, y), new XPoint(x1, y1))); + AddCommand(canvas => canvas.Line(x, y, x1, y1, pen.Color.Hex, pen.Width, pen.Color.Opacity)); + _drawingCtx.PushInstruction(offset => InternalDrawLine(pen, x, y + offset, x1, y1 + offset), + new PdfRect(new PdfPoint(x, y), new PdfPoint(x1, y1))); } public void DrawRect(double x, double y, double w, double h, bool isFilled) { - (x, y, w, h) = CurrentPage.CoordRectToPage(x, y, w, h); - InternalDrawRect(CurrentPen, CurrentBrush, x, y, w, h, isFilled); + var page = CurrentPage; + (x, y, w, h) = DrawingHelper.CoordRectToPage(page.Width, page.Height, x, y, w, h); + InternalDrawRect(CurrentPen, CurrentBrush, ScaleX(x, page), ScaleY(y, page), ScaleX(w, page), ScaleY(h, page), isFilled); } - private void InternalDrawRect(XPen pen, XBrush brush, double x, double y, double w, double h, bool isFilled) + private void InternalDrawRect(PdfPen pen, PdfBrush brush, double x, double y, double w, double h, bool isFilled) { - if (isFilled) + AddCommand(canvas => { - Gfx.DrawRectangle(pen, brush, x, y, w, h); - } - else - { - Gfx.DrawRectangle(pen, x, y, w, h); - } - this._drawingCtx.PushInstruction((oy) => InternalDrawRect(pen, brush, x, y+oy, w, h, isFilled), new XRect(x, y, w, h)); + if (isFilled) canvas.FillRect(x, y, w, h, brush.Color.Hex, brush.Color.Opacity); + canvas.StrokeRect(x, y, w, h, pen.Color.Hex, pen.Width, pen.Color.Opacity); + }); + _drawingCtx.PushInstruction(offset => InternalDrawRect(pen, brush, x, y + offset, w, h, isFilled), + new PdfRect(x, y, w, h)); } public void DrawEllipse(double x, double y, double w, double h, bool isFilled) { - (x, y, w, h) = CurrentPage.CoordRectToPage(x, y, w, h); - var r = new XRect(x, y, w, h); - InternalDrawEllipse(isFilled, r, CurrentPen, CurrentBrush); + var page = CurrentPage; + (x, y, w, h) = DrawingHelper.CoordRectToPage(page.Width, page.Height, x, y, w, h); + InternalDrawEllipse(CurrentPen, CurrentBrush, ScaleX(x, page), ScaleY(y, page), ScaleX(w, page), ScaleY(h, page), isFilled); } - private void InternalDrawEllipse(bool isFilled, XRect r, XPen pen, XBrush brush) + private void InternalDrawEllipse(PdfPen pen, PdfBrush brush, double x, double y, double w, double h, bool isFilled) { - if (isFilled) - { - Gfx.DrawEllipse(pen, brush, r); - } - else + AddCommand(canvas => { - Gfx.DrawEllipse(pen, r); - } - this._drawingCtx.PushInstruction((oy) => InternalDrawEllipse(isFilled, r.OffsetY(oy), pen, brush), r); + var cx = x + w / 2; + var cy = y + h / 2; + if (isFilled) canvas.FillEllipse(cx, cy, w / 2, h / 2, brush.Color.Hex, brush.Color.Opacity); + canvas.StrokeEllipse(cx, cy, w / 2, h / 2, pen.Color.Hex, pen.Width, pen.Color.Opacity); + }); + _drawingCtx.PushInstruction(offset => InternalDrawEllipse(pen, brush, x, y + offset, w, h, isFilled), new PdfRect(x, y, w, h)); } - public void DrawText(string text, double x, double y, double? w, - double? h) + public void DrawText(string text, double x, double y, double? w, double? h) { var page = CurrentPage; - (x, y, w, h) = page.CoordRectToPage(x, y, w, h); - //20221009 : only top left is supported - if (w == null || h == null) - { - //because of missing w/h alignment is Left - var sizeFormatter = new XTextSegmentFormatter(Gfx) - { - Alignment = XParagraphAlignment.Left - }; - w = w ?? page.Width - x; - var size = sizeFormatter.CalculateTextSize(text, CurrentFont, CurrentBrush, w.Value); - var r = new XRect(x, y, size.Width, size.Height); - - sizeFormatter.DrawString(text, CurrentFont, CurrentBrush, r); - if (_drawingCtx.DebugText) - { - DebugRect(r); - } - } - else - { - //fmt is not used, because DrawString support only TopLeft - var r = new XRect(x, y, w.Value, h.Value); - var formatter = new XTextFormatter(Gfx); - formatter.DrawString(text, CurrentFont, CurrentBrush, r); - if (_drawingCtx.DebugText) - { - DebugRect(r); - } - } - } - - public void DrawLineText(string text, double x, double y, double? w, double? h, XStringAlignment hAlign, - XLineAlignment vAlign, TextOrientation textOrientation) - { - (x, y, w, h) = CurrentPage.CoordRectToPage(x, y, w, h); - var fmt = new XStringFormat - { - Alignment = hAlign, - LineAlignment = vAlign, - }; - InternalDrawLineText(text, x, y, w, h, textOrientation, fmt, CurrentFont, CurrentBrush, HighlightBrush); + (x, y, w, h) = DrawingHelper.CoordRectToPage(page.Width, page.Height, x, y, w, h); + InternalDrawText(text, ScaleX(x, page), ScaleY(y, page), w.HasValue ? ScaleX(w.Value, page) : page.Width - x, + h.HasValue ? ScaleY(h.Value, page) : null, PdfHorizontalAlignment.Near, PdfVerticalAlignment.Near, + CurrentFont, CurrentBrush, null); } - private void InternalDrawLineText(string text, double x, double y, double? w, double? h, - TextOrientation textOrientation, XStringFormat fmt, XFont font, XBrush brush, XBrush? hb) + public void DrawLineText(string text, double x, double y, double? w, double? h, PdfHorizontalAlignment hAlign, + PdfVerticalAlignment vAlign, TextOrientation textOrientation) { - XRect r; - //TODO : optimize to do this only on vertical text - var cnt = Gfx.BeginContainer(); - try - { - r = InternalDrawString(text, x, y, w, h, fmt, textOrientation, font, brush, hb); - } - finally - { - Gfx.EndContainer(cnt); - } - - this._drawingCtx.PushInstruction( - (oy) => InternalDrawLineText(text, x, y+oy, w, h, textOrientation, fmt, font, brush, hb), r, instrName:$"DrawLineText({text})"); + var page = CurrentPage; + (x, y, w, h) = DrawingHelper.CoordRectToPage(page.Width, page.Height, x, y, w, h); + InternalDrawText(text, ScaleX(x, page), ScaleY(y, page), w.HasValue ? ScaleX(w.Value, page) : null, + h.HasValue ? ScaleY(h.Value, page) : null, hAlign, vAlign, CurrentFont, CurrentBrush, HighlightBrush); } - private XRect InternalDrawString(string text, double x, double y, double? w, double? h, - XStringFormat fmt, TextOrientation textOrientation, XFont font, XBrush brush, XBrush? hb) + private void InternalDrawText(string text, double x, double y, double? w, double? h, PdfHorizontalAlignment hAlign, + PdfVerticalAlignment vAlign, PdfFont font, PdfBrush brush, PdfBrush? highlight) { - XRect result; - double angle = 0; + var lines = WrapText(text, w, font); + var lineHeight = font.Size * 1.2; + var measuredWidth = lines.Count == 0 ? 0 : lines.Max(line => MeasureText(line, font)); + var measuredHeight = lines.Count * lineHeight; + var rect = new PdfRect(x, y, w ?? measuredWidth, h ?? measuredHeight); + var textRect = DrawingHelper.RectFromStringFormat(rect, + new PdfSize(Math.Min(measuredWidth, rect.Width), Math.Min(measuredHeight, rect.Height)), hAlign, vAlign); - if (textOrientation.Angle is not null) + AddCommand(canvas => { - angle = textOrientation.Angle.Value; - } - else if (textOrientation.Orientation == TextOrientationEnum.Vertical) - { - angle = 90; - } + if (highlight is not null) + canvas.FillRect(textRect.X, textRect.Y, textRect.Width, textRect.Height, highlight.Color.Hex, highlight.Color.Opacity); - if (angle != 0) - { - Gfx.RotateAtTransform(angle, new XPoint(x, y)); - } - - var textSize = Gfx.MeasureString(text, font); - if (w == null || h == null) - { - Gfx.DrawString(text, font, brush, x, y, fmt); - var rText = new XRect(x, y, textSize.Width, textSize.Height); - var r = DrawingHelper.RectFromStringFormat(x, y, textSize, fmt); - result = r; - if (_drawingCtx.DebugText) - { - DebugRect(r); - } - - if (hb == null) return result; - - - // - //var highlightColor = XColor.FromArgb(50, 255, 233, 178); - //var b = new XSolidBrush(highlightColor); - Gfx.DrawRectangle(hb, r); - } - else - { - //fmt is not used, because DrawString support only TopLeft - var r = new XRect(x, y, w.Value, h.Value); - Gfx.DrawString(text, font, brush, r, fmt); - var hr = DrawingHelper.RectFromStringFormat(x, y, textSize, fmt); - result = hr; - if (_drawingCtx.DebugText) + for (var index = 0; index < lines.Count; index++) { - DebugRect(hr); + var line = lines[index]; + if (string.IsNullOrWhiteSpace(line)) continue; + var lineWidth = MeasureText(line, font); + var lineX = hAlign switch + { + PdfHorizontalAlignment.Center => rect.X + (rect.Width - lineWidth) / 2, + PdfHorizontalAlignment.Far => rect.Right - lineWidth, + _ => rect.X, + }; + canvas.Text(line, lineX, textRect.Y + font.Size + index * lineHeight, brush.Color.Hex, font.Size, + font.FamilyName, font.Style.HasFlag(PdfFontStyle.Bold), font.Style.HasFlag(PdfFontStyle.Italic), brush.Color.Opacity); } + }); - if (hb == null) return result; - hr.Intersect(r); - //var highlightColor = XColor.FromArgb(50, 255, 233, 178); - //var b = new XSolidBrush(highlightColor); - Gfx.DrawRectangle(hb, hr); - } - - return result; + _drawingCtx.PushInstruction(offset => InternalDrawText(text, x, y + offset, w, h, hAlign, vAlign, font, brush, highlight), + textRect, instrName: $"DrawText({text})"); } - public void DrawTitle(string text, double margin, XStringAlignment hAlign, XLineAlignment vAlign) + public void DrawTitle(string text, double margin, PdfHorizontalAlignment hAlign, PdfVerticalAlignment vAlign) { - var page = CurrentPage; + var height = CurrentFont.Size * 1.2; + if (margin < 0) margin = PageHeight - height + margin; + InternalDrawText(text, 0, margin, PageWidth, height, hAlign, vAlign, CurrentFont, CurrentBrush, HighlightBrush); + } - var fmt = new XStringFormat - { - Alignment = hAlign, - LineAlignment = vAlign, - }; + public void DrawTable(double x, double y, TableDefinition table) + { + ArgumentNullException.ThrowIfNull(table); + var availableWidth = PageWidth - x; + var fonts = table.Columns.Select(column => column.Font ?? CurrentFont).ToArray(); + var margins = table.CellMargin; - var textSize = Gfx.MeasureString(text, CurrentFont, XStringFormats.TopLeft); - if (margin < 0) + for (var index = 0; index < table.Columns.Count; index++) { - margin = page.Height - textSize.Height + margin; + var column = table.Columns[index]; + column.DesiredWidth ??= Math.Min(MeasureText(column.ColumnHeaderName, fonts[index]) + margins.Left + margins.Right, + table.ColMaxWidth(index, availableWidth)); + table.HeaderHeight ??= fonts[index].Size * 1.2 + margins.Top + margins.Bottom; } - var r = new XRect(0, margin, page.Width, textSize.Height); - InternalDrawText(text, r, fmt, textSize, CurrentFont, CurrentBrush, HighlightBrush); - } + foreach (var row in table.Rows) + row.DesiredHeight ??= fonts.Select(font => font.Size * 1.2 + margins.Top + margins.Bottom).Max(); - private void InternalDrawText(string text, XRect r, XStringFormat fmt, XSize textSize, - XFont font, XBrush brush, XBrush? hb) - { - Gfx.DrawString(text, font, brush, r, fmt); - this._drawingCtx.PushInstruction((oy) => InternalDrawText(text, r.OffsetY(oy), fmt, textSize, font, brush, hb), r); - if (_drawingCtx.DebugText) + if (y + (table.HeaderHeight ?? 0) > PageHeight) { NewPage(); y = 1; } + var offsetY = 0d; + if (table.ShowHeader) { - var debugRect = DrawingHelper.RectFromStringFormat(r, textSize, fmt); - DebugRect(debugRect); + DrawTableRow(x, y, table.Columns.Select(column => column.ColumnHeaderName).ToArray(), table.HeaderHeight ?? 0, table, fonts, table.HeaderBackColor); + offsetY = table.HeaderHeight ?? 0; } - if (hb == null) return; - - var hr = DrawingHelper.RectFromStringFormat(r, textSize, fmt); - Gfx.DrawRectangle(hb, hr); + foreach (var row in table.Rows) + { + var height = row.DesiredHeight ?? 0; + if (y + offsetY + height > PageHeight) { NewPage(); y = table.TopMarginOnPageBreak; offsetY = 0; } + DrawTableRow(x, y + offsetY, row.Data, height, table, fonts, null); + offsetY += height; + } } - public void DrawTable(double x, double y, TableDefinition tblDef) + private void DrawTableRow(double x, double y, string[] values, double height, TableDefinition table, PdfFont[] fonts, PdfBrush? rowBackground) { - var availableWidth = PageWidth - x; - Gfx.Save(); - try + var offsetX = 0d; + for (var index = 0; index < table.Columns.Count; index++) { - //todo check if the current page can receive - //calculate table dimension - var defaultFont = CurrentFont; - var defaultBrush = CurrentBrush; - - XFont[] xFonts = new XFont[tblDef.Columns.Count]; - bool[] colMeasure = new bool[tblDef.Columns.Count]; - bool calcHeaderHeight = tblDef.HeaderHeight is null; - var margins = tblDef.CellMargin; - int i = 0; - foreach (var column in tblDef.Columns) + var column = table.Columns[index]; + var width = column.DrawWidth; + var background = rowBackground ?? column.BackColor; + if (background is not null) { - xFonts[i] = column.Font ?? defaultFont; - colMeasure[i] = column.DesiredWidth is null; - if (colMeasure[i] || calcHeaderHeight) - { - var measure = Gfx.MeasureString(column.ColumnHeaderName, xFonts[i]); - if (colMeasure[i]) - { - column.DesiredWidth = measure.Width + margins.Left + margins.Right; - } - - if (calcHeaderHeight) - { - tblDef.HeaderHeight = Math.Max(tblDef.HeaderHeight ?? 0, - measure.Height + margins.Top + margins.Bottom); - } - } - - i++; - } - - //measure all rows - var sizeFormatter = new XTextSegmentFormatter(Gfx) - { - Alignment = XParagraphAlignment.Left - }; - foreach (var row in tblDef.Rows) - { - var rowMeasure = row.DesiredHeight is null; - for (i = 0; i < row.Data.Length; i++) - { - var testSize = !colMeasure[i]; - var pageSpaceLeft = tblDef.ColMaxWidth(i, availableWidth); - var column = tblDef.Columns[i]; - if (colMeasure[i]) - { - var cSize = Gfx.MeasureString(row.Data[i], xFonts[i]); - cSize.Width += (margins.Left + margins.Right); - if (cSize.Width > pageSpaceLeft) - { - //measure height with fixed width - testSize = true; - column.DesiredWidth = Math.Max(column.DesiredWidth ?? 0, pageSpaceLeft); - } - else - { - column.DesiredWidth = Math.Max(column.DesiredWidth ?? 0, cSize.Width); - } - - if (rowMeasure) - { - row.DesiredHeight = Math.Max(row.DesiredHeight ?? 0, - cSize.Height + margins.Top + margins.Bottom); - } - } - - if (!testSize) continue; - var w = Math.Min(column.DesiredWidth ?? 0, pageSpaceLeft); - var measure = sizeFormatter.CalculateTextSize(row.Data[i], xFonts[i], defaultBrush, w); - if (rowMeasure) - { - row.DesiredHeight = Math.Max(row.DesiredHeight ?? 0, - measure.Height + margins.Top + margins.Bottom); - } - } - } - - //draw header - double offsetX = 0; - double offsetY = 0; - i = 0; - if (y + tblDef.HeaderHeight > CurrentPage.Height) - { - Gfx.Restore(); - NewPage(); - Gfx.Save(); - //TODO: set top margin - y = 1; + var previousBrush = CurrentBrush; + CurrentBrush = background; + DrawRect(x + offsetX, y, width, height, true); + CurrentBrush = previousBrush; } + else DrawRect(x + offsetX, y, width, height, false); - foreach (var column in tblDef.Columns) - { - var w = column.DrawWidth; - var h = tblDef.HeaderHeight ?? 0; - - var r = new XRect(offsetX + x, y, w, h); - var hMargin = margins.Left + margins.Right; - var vMargin = margins.Top + margins.Bottom; - var rText = new XRect(offsetX + x + margins.Left, offsetY + y + margins.Top, w - hMargin, - h - vMargin); - - Gfx.DrawRectangle(CurrentPen, tblDef.HeaderBackColor, r); - offsetX += column.DrawWidth; - //todo: alignment - var fmt = new XStringFormat - { Alignment = XStringAlignment.Center, LineAlignment = XLineAlignment.Center }; - DrawStringMultiline(column.ColumnHeaderName, xFonts[i], column.Brush ?? defaultBrush, rText, fmt); - i++; - } - - offsetY = tblDef.HeaderHeight ?? 0; - //draw body - foreach (var row in tblDef.Rows) - { - if (y + offsetY + row.DesiredHeight > CurrentPage.Height) - { - Gfx.Restore(); - NewPage(); - Gfx.Save(); - //TODO: set top margin - y = 1; - offsetY = 0; - } - - offsetX = 0; - for (i = 0; i < row.Data.Length; i++) - { - var w = tblDef.Columns[i].DrawWidth; - var h = row.DesiredHeight ?? 0; - var r = new XRect(offsetX + x, offsetY + y, w, h); - ResetClip(); - Gfx.DrawRectangle(CurrentPen, tblDef.Columns[i].BackColor, r); - var hMargin = margins.Left + margins.Right; - var vMargin = margins.Top + margins.Bottom; - var rText = new XRect(offsetX + x + margins.Left, offsetY + y + margins.Top, w - hMargin, - h - vMargin); - Gfx.IntersectClip(rText); - var fmt = new XStringFormat - { Alignment = XStringAlignment.Center, LineAlignment = XLineAlignment.Center }; - //to debug - //Gfx.DrawRectangle(XPens.Violet, rText); - //TODO: split to draw one string per line - DrawStringMultiline(row.Data[i], xFonts[i], tblDef.Columns[i].Brush ?? defaultBrush, rText, - fmt); - offsetX += w; - } - - offsetY += row.DesiredHeight ?? 0; - } + var margins = table.CellMargin; + InternalDrawText(index < values.Length ? values[index] : string.Empty, x + offsetX + margins.Left, y + margins.Top, + Math.Max(0, width - margins.Left - margins.Right), Math.Max(0, height - margins.Top - margins.Bottom), + column.Alignment, PdfVerticalAlignment.Center, fonts[index], column.Brush ?? CurrentBrush, null); + offsetX += width; } - finally - { - Gfx.Restore(); - } - } - - private void ResetClip() - { - if (_gfx is null) return; - _gfxRenderer ??= (IXGraphicsRenderer)_gfx.GetType().GetField("_renderer", - System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.GetField - | System.Reflection.BindingFlags.Instance) - .GetValue(_gfx); - _gfxRenderer?.ResetClip(); - } - - private void DrawStringMultiline(string text, XFont xFont, XBrush xBrush, XRect r, XStringFormat fmt) - { - var formatter = new XTextFormatter(Gfx); - formatter.DrawString(text, xFont, xBrush, r); } public void SetViewSize(double w, double h) { - var scaleX = CurrentPage.Width / w; - var scaleY = CurrentPage.Height / h; - - Gfx.ScaleTransform(scaleX, scaleY); + if (w <= 0 || h <= 0) throw new ArgumentOutOfRangeException(nameof(w), "View dimensions must be positive."); + CurrentPage.ScaleX = CurrentPage.Width / w; + CurrentPage.ScaleY = CurrentPage.Height / h; } - public void NewPage(PageSize? pageSize = null, PageOrientation? pageOrientation = null) + public void NewPage(PdfPageSize? pageSize = null, PdfPageOrientation? pageOrientation = null) { _defaultPageSize = pageSize ?? _defaultPageSize; _defaultPageOrientation = pageOrientation ?? _defaultPageOrientation; - - CurrentPage = AddPage(); - _onNewPageHooks.ForEach( x=> x(_document.PageCount)); + _pages.Add(new RecordedPage(_defaultPageSize, _defaultPageOrientation)); + _logger?.WriteDebug(this, "AddPage"); + _onNewPageHooks.ForEach(callback => callback(_pages.Count)); if ((DebugOptions & DebugOptions.DebugRule) == DebugOptions.DebugRule) - { - var mm5 = 25; - var start = mm5; - while (start < PageHeight) - { - var ten = (start % (mm5+mm5)) == 0; - this.Gfx.DrawLine(XPens.Red, 0, start, ten ? 50 : 25, start); - if (ten) - { - DebugText($"{start}", 50, start); - } - start += mm5; - } - } + for (var position = 25d; position < PageHeight; position += 25) + DrawLine(0, position, position % 50 == 0 ? 50 : 25, position); } - private PdfPage AddPage() - { - if (_logger.DebugEnabled()) - { - _logger.WriteDebug(this, $"AddPage"); - } - var page = _document.AddPage(); - page.Size = _defaultPageSize; - page.Orientation = _defaultPageOrientation; - return page; - } - - public void MoveTo(double x, double y) - { - _currentPoint = new XPoint(x, y); - } + public void MoveTo(double x, double y) => _currentPoint = new PdfPoint(x, y); public void LineTo(double x, double y) { - var endPoint = new XPoint(x, y); - InternalLineTo(_currentPoint, endPoint, CurrentPen); + var page = CurrentPage; + var endPoint = new PdfPoint(x, y); + AddCommand(canvas => canvas.Line(ScaleX(_currentPoint.X, page), ScaleY(_currentPoint.Y, page), ScaleX(endPoint.X, page), ScaleY(endPoint.Y, page), + CurrentPen.Color.Hex, CurrentPen.Width, CurrentPen.Color.Opacity)); + _drawingCtx.PushInstruction(offset => LineTo(endPoint.X, endPoint.Y), new PdfRect(_currentPoint, endPoint)); _currentPoint = endPoint; } - private void InternalLineTo(XPoint p1, XPoint p2, XPen pen) + public void DrawImage(PdfImage image, double x, double y, double? w, double? h, bool sizeInPixel, bool cropImage) { - Gfx.DrawLine(pen, p1, p2); - this._drawingCtx.PushInstruction((oy) => InternalLineTo(p1.OffsetY(oy), p2.OffsetY(oy), pen), new XRect(p1, p2)); - //this._drawingCtx.UpdateDrawingRect(new XRect(p1, p2)); + _logger?.WriteDebug(this, "TerraPDF does not support absolute image placement; image skipped."); } - public void DrawImage(XImage image, double x, double y, double? w, double? h, bool sizeInPixel, bool cropImage) + public void DrawPie(double x, double y, double? w, double? h, double startAngle, double sweepAngle, bool isFilled) { - if (sizeInPixel) - { - //convert Pixel to Point - w = w * 72 / 96.0; - h = h * 72 / 96.0; - /* - if (h is not null) - { - h = (double)(h * 72) / 96.0; - } - */ - } - - //fix coord if < 0 - (x, y, w, h) = CurrentPage.CoordRectToPage(x, y, w, h); - InternalDrawImage(image, x, y, w, h, cropImage); + var page = CurrentPage; + InternalDrawEllipse(CurrentPen, CurrentBrush, ScaleX(x, page), ScaleY(y, page), ScaleX(w ?? 0, page), ScaleY(h ?? 0, page), isFilled); } - private void InternalDrawImage(XImage image, double x, double y, double? w, double? h, bool cropImage) + public void DrawPolygon(IEnumerable points, bool isFilled) { - var ow = w; - var oh = h; - if (w is null && h is null) - { - Gfx.DrawImage(image, x, y); - w = image.PointWidth; - h = image.PointHeight; - } - else + var page = CurrentPage; + var transformed = points.Select(point => new PdfPoint(ScaleX(point.X, page), ScaleY(point.Y, page))).ToArray(); + if (transformed.Length < 3) throw new ArgumentException("A polygon requires at least three points.", nameof(points)); + var tuples = transformed.Select(point => (point.X, point.Y)).ToArray(); + AddCommand(canvas => canvas.Path(path => { - w ??= image.PointWidth; - h ??= image.PointHeight; - if (cropImage) - { - //draw in form, then draw form in page - using XForm form = new XForm(this._document, XUnit.FromPoint(w.Value), XUnit.FromPoint(h.Value)); - using var gr = XGraphics.FromForm(form); - gr.DrawImage(image, 0, 0); - Gfx.DrawImage(form, x, y); - } - else - { - Gfx.DrawImage(image, x, y, w.Value, h.Value); - } - } - this._drawingCtx.PushInstruction((oy) => InternalDrawImage(image, x, y+oy, ow, oh, cropImage), new XRect(x, y, w.Value, h.Value)); + path.Polygon(tuples).Stroke(CurrentPen.Color.Hex, CurrentPen.Width).Opacity(CurrentPen.Color.Opacity); + if (isFilled) path.Fill(CurrentBrush.Color.Hex).Opacity(CurrentBrush.Color.Opacity); + })); + _drawingCtx.PushInstruction(offset => DrawPolygon(transformed.Select(point => point.OffsetY(offset)), isFilled), transformed); } - public void DrawPie(double x, double y, double? w, double? h, double startAngle, double sweepAngle, - bool isFilled) + public void BeginDrawRowTemplate(string name, int index, double offsetY, double newPageTopMargin) { - InternalDrawPie(x, y, w, h, startAngle, sweepAngle, isFilled, CurrentPen, CurrentBrush); + _drawingCtx.OpenBlock($"{name}:{index}", offsetY, newPageTopMargin); + _measurementStates.Push(_isMeasuring); + _isMeasuring = true; } - private void InternalDrawPie(double x, double y, double? w, double? h, double startAngle, double sweepAngle, - bool isFilled, XPen pen, XBrush brush) + public DrawingResult EndDrawRowTemplate(int index) { - if (isFilled) - { - Gfx.DrawPie(pen, brush, x, y, w ?? 0, h ?? 0, startAngle, sweepAngle); - } - else - { - Gfx.DrawPie(pen, x, y, w ?? 0, h ?? 0, startAngle, sweepAngle); - } - this._drawingCtx.PushInstruction((oy) => InternalDrawPie(x, y+oy, w, h, startAngle, sweepAngle, isFilled, pen, brush), - new XRect(x, y, w ?? 0, h ?? 0)); + var result = _drawingCtx.BlockRect; + var block = _drawingCtx.EndMeasure(); + _isMeasuring = _measurementStates.Pop(); + var pageOffsetY = 0d; + if (result.IsEmpty) result = new PdfRect(0, block.OffsetY, 0, 0); + else if (_drawingCtx.Level == 0) pageOffsetY = block.Draw(this, 0, 0); + _drawingCtx.CloseBlock(); + return new DrawingResult { DrawingRect = result, PageOffsetY = pageOffsetY }; } - public void DrawPolygon(IEnumerable points, bool isFilled) - { - var ptArray = points.ToArray(); - InternalDrawPolygon(isFilled, ptArray, CurrentPen, CurrentBrush); - } + public void BeginIterationTemplate(int rowCount) { } + public void EndIterationTemplate(double drawHeight) { } - private void InternalDrawPolygon(bool isFilled, XPoint[] ptArray, XPen pen, XBrush brush) + public void RegisterOnNewPage(Action callback) { - if (isFilled) - { - Gfx.DrawPolygon(pen, brush, ptArray, XFillMode.Alternate); - } - else - { - Gfx.DrawPolygon(pen, ptArray); - } - this._drawingCtx.PushInstruction((oy) => InternalDrawPolygon(isFilled, ptArray.OffsetY(oy), pen, brush), ptArray); + if (callback is not null && !_onNewPageHooks.Contains(callback)) _onNewPageHooks.Add(callback); } + public void UnRegisterOnNewPage(Action callback) => _onNewPageHooks.Remove(callback); - public void BeginDrawRowTemplate(string name, int index, double offsetY, double newPageTopMargin) + private void AddCommand(Action command) { - //open virtual block - this._drawingCtx.OpenBlock($"{name}:{index}", offsetY, Gfx, newPageTopMargin); - //drawing is only to measure - _gfx = XGraphics.CreateMeasureContext(new XSize(PageWidth, PageHeight), - XGraphicsUnit.Point, XPageDirection.Downwards); + if (!_isMeasuring) CurrentPage.Commands.Add(command); } - public DrawingResult EndDrawRowTemplate(int index) + private static double MeasureText(string text, PdfFont font) { - double newPageOffsetY = 0; - var result = this._drawingCtx.BlockRect; - InternalEndRowTemplate(index, result); - IInstructionBlock block; - var level = _drawingCtx.Level; - (block, _gfx) = this._drawingCtx.RestoreGraphics(); - if (result.IsEmpty) - { - return new() - { - DrawingRect = new XRect(0, block.OffsetY, 0, 0), - PageOffsetY = 0 - }; - } - - if (level <= 1) - { - //draw only if rowTemplate if at root level - newPageOffsetY = block.Draw(this, 0, 0); - if (_logger?.IsEnabled(LogLevel.Debug) ?? false) - { - _logger.WriteDebug(this, $"EndDrawing block #{index} Rect={block.Rect}, newPageOffsetY={newPageOffsetY}"); - } - } - - this._drawingCtx.CloseBlock(); - - return new() - { - DrawingRect = result, - PageOffsetY = newPageOffsetY - }; + if (string.IsNullOrWhiteSpace(text)) return 0; + return VectorCanvas.MeasureTextWidth(text, font.Size, font.FamilyName, font.Style.HasFlag(PdfFontStyle.Bold), font.Style.HasFlag(PdfFontStyle.Italic)); } - private void InternalEndRowTemplate(int index, XRect result) + private static List WrapText(string text, double? maxWidth, PdfFont font) { - if (_drawingCtx.DebugRowTemplate) + var lines = new List(); + foreach (var sourceLine in text.Replace("\r\n", "\n").Split('\n')) { - DebugRect(result); - Gfx.DrawLine(_debugPen, 0, 0, 5, 2); - Gfx.DrawLine(_debugPen, 0, 0, 2, 5); - Gfx.DrawLine(_debugPen, 0, 0, 10, 10); - DebugText($"{_drawingCtx.Level}.{index}", 10, 10); + if (maxWidth is null || maxWidth <= 0 || MeasureText(sourceLine, font) <= maxWidth) { lines.Add(sourceLine); continue; } + var current = string.Empty; + foreach (var word in sourceLine.Split(' ', StringSplitOptions.RemoveEmptyEntries)) + { + var candidate = current.Length == 0 ? word : $"{current} {word}"; + if (current.Length > 0 && MeasureText(candidate, font) > maxWidth) { lines.Add(current); current = word; } + else current = candidate; + } + lines.Add(current); } - this._drawingCtx.PushInstruction((oy) => InternalEndRowTemplate(index, result.OffsetY(oy)), result, false, "EndRowTemplate"); + return lines; } - public void BeginIterationTemplate(int rowCount) - { - } + private static double ResolveX(double x, RecordedPage page) => x < 0 ? page.Width + x : x; + private static double ResolveY(double y, RecordedPage page) => y < 0 ? page.Height + y : y; + private static double ScaleX(double value, RecordedPage page) => value * page.ScaleX; + private static double ScaleY(double value, RecordedPage page) => value * page.ScaleY; - public void EndIterationTemplate(double drawHeight) + private static (double Width, double Height) GetPageDimensions(PdfPageSize size, PdfPageOrientation orientation) { - } - - private void DebugText(string text, double x, double y) - { - var fmt = new XStringFormat() + var dimensions = size switch { - Alignment = XStringAlignment.Near, - LineAlignment = XLineAlignment.Near + PdfPageSize.A0 => (2383.94, 3370.39), + PdfPageSize.A1 => (1683.78, 2383.94), + PdfPageSize.A2 => (1190.55, 1683.78), + PdfPageSize.A3 => (841.89, 1190.55), + PdfPageSize.A5 => (419.53, 595.28), + PdfPageSize.A6 => (297.64, 419.53), + PdfPageSize.Letter => (612d, 792d), + PdfPageSize.Legal => (612d, 1008d), + PdfPageSize.Ledger => (1224d, 792d), + PdfPageSize.Tabloid => (792d, 1224d), + _ => (595.28, 841.89), }; - - Gfx.DrawString(text, _debugFont.Value, XBrushes.Red, x, y, fmt); - } - private void DebugRect(XRect rect) - { - Gfx.DrawRectangle(_debugPen, rect); + return orientation == PdfPageOrientation.Landscape ? (dimensions.Item2, dimensions.Item1) : dimensions; } - public void RegisterOnNewPage(Action callback) - { - if (callback != null && !_onNewPageHooks.Contains(callback)) - { - _onNewPageHooks.Add(callback); - } - } - - public void UnRegisterOnNewPage(Action callback) - { - _onNewPageHooks.Remove(callback); - } + public void Dispose() { } } -} \ No newline at end of file +} diff --git a/PdfSharpDslCore/Drawing/TableDefinition.cs b/PdfSharpDslCore/Drawing/TableDefinition.cs index bac79b5..9a6c65c 100644 --- a/PdfSharpDslCore/Drawing/TableDefinition.cs +++ b/PdfSharpDslCore/Drawing/TableDefinition.cs @@ -1,5 +1,3 @@ -using PdfSharpCore.Drawing; -using PdfSharpCore.Pdf; using System; using System.Collections.Generic; @@ -13,8 +11,8 @@ public class TableDefinition public double TopMarginOnPageBreak { get; set; } //header height, should be measure if not specified public double? HeaderHeight { get; set; } - public TrimMargins CellMargin { get; set; } = new TrimMargins() { All = 1 }; - public XBrush? HeaderBackColor { get; set; } + public PdfMargins CellMargin { get; set; } = new PdfMargins() { All = 1 }; + public PdfBrush? HeaderBackColor { get; set; } public List Rows { get; private set; } = new(); @@ -36,7 +34,7 @@ public double ColMaxWidth(int i, double pageWidth) return Math.Min(Columns[i].MaxWidth ?? pageWidth, pageWidth); } - public XStringAlignment Alignment(int i) + public PdfHorizontalAlignment Alignment(int i) { return Columns[i].Alignment; } @@ -49,10 +47,10 @@ public class ColumnDefinition public double? DesiredWidth { get; set; } = null; public double? MaxWidth { get; set; } = null; - public XStringAlignment Alignment { get; set; } = XStringAlignment.Near; - public XFont? Font { get; set; } - public XBrush? Brush { get; set; } - public XBrush? BackColor { get; set; } + public PdfHorizontalAlignment Alignment { get; set; } = PdfHorizontalAlignment.Near; + public PdfFont? Font { get; set; } + public PdfBrush? Brush { get; set; } + public PdfBrush? BackColor { get; set; } public double DrawWidth { diff --git a/PdfSharpDslCore/Extensions/XRectExtensions.cs b/PdfSharpDslCore/Extensions/XRectExtensions.cs index 36e555f..5a38d8f 100644 --- a/PdfSharpDslCore/Extensions/XRectExtensions.cs +++ b/PdfSharpDslCore/Extensions/XRectExtensions.cs @@ -1,29 +1,27 @@ using System.Collections.Generic; -using PdfSharpCore.Drawing; +using PdfSharpDslCore.Drawing; namespace PdfSharpDslCore.Extensions { - public static class XRectExtensions + public static class PdfRectExtensions { - public static XRect OffsetY(this XRect r, double y) + public static PdfRect OffsetY(this PdfRect r, double y) { var result = r; result.Offset(0, y); return result; } - public static XPoint OffsetY(this XPoint p, double y) + public static PdfPoint OffsetY(this PdfPoint p, double y) { - var result = p; - result.Offset(0,y); - return result; + return p.OffsetY(y); } - public static XPoint[] OffsetY(this XPoint[] pts, double y) + public static PdfPoint[] OffsetY(this PdfPoint[] pts, double y) { if (y != 0) { - List resultPts = new List(); + List resultPts = new List(); foreach (var pt in pts) { resultPts.Add(pt.OffsetY(y)); diff --git a/PdfSharpDslCore/Parser/PdfDrawerVisitor.cs b/PdfSharpDslCore/Parser/PdfDrawerVisitor.cs index 27c7f38..71f5461 100644 --- a/PdfSharpDslCore/Parser/PdfDrawerVisitor.cs +++ b/PdfSharpDslCore/Parser/PdfDrawerVisitor.cs @@ -1,6 +1,4 @@ using Irony.Parsing; -using PdfSharpCore; -using PdfSharpCore.Drawing; using PdfSharpDslCore.Drawing; using PdfSharpDslCore.Evaluation; using PdfSharpDslCore.Extensions; @@ -84,13 +82,13 @@ protected override void ExecutePen(IPdfDocumentDrawer state, ParseTreeNode width { var width = EvaluateForDouble(widthNode) ?? 0; var color = colorNode.ParseColor(); - XDashStyle style = XDashStyle.Solid; + PdfDashStyle style = PdfDashStyle.Solid; if (styleNode != null) { - Enum.TryParse(styleNode.Token.ValueString, true, out style); + Enum.TryParse(styleNode.Token.ValueString, true, out style); } - var pen = new XPen(color, width) + var pen = new PdfPen(color, width) { DashStyle = style }; @@ -100,7 +98,7 @@ protected override void ExecutePen(IPdfDocumentDrawer state, ParseTreeNode width protected override void ExecuteHBrush(IPdfDocumentDrawer drawer, ParseTreeNode colorNode) { var color = colorNode.ParseColor(); - drawer.HighlightBrush = color.A == 0 ? null : new XSolidBrush(color); + drawer.HighlightBrush = color.A == 0 ? null : new PdfBrush(color); } protected override void ExecuteFont(IPdfDocumentDrawer drawer, ParseTreeNode fontNode) @@ -114,15 +112,15 @@ protected override void ExecuteNewPage(IPdfDocumentDrawer drawer, { var nSize = sizeNode; var nOrientation = orientationNode; - PageSize? pageSize = null; - if (nSize != null && Enum.TryParse(nSize.Token.Text, out var size)) + PdfPageSize? pageSize = null; + if (nSize != null && Enum.TryParse(nSize.Token.Text, out var size)) { pageSize = size; } - PageOrientation? pageOrientation = null; + PdfPageOrientation? pageOrientation = null; if (nOrientation != null && - Enum.TryParse(nOrientation.Token.Text, true, out var orientation)) + Enum.TryParse(nOrientation.Token.Text, true, out var orientation)) { pageOrientation = orientation; } @@ -169,7 +167,7 @@ protected override void ExecuteTitle(IPdfDocumentDrawer drawer, ParseTreeNode ma ParseTreeNode alignmentsNode, ParseTreeNode contentNode) { - var text = Convert.ToString(EvaluateForObject(contentNode)); + var text = Convert.ToString(EvaluateForObject(contentNode)) ?? string.Empty; var margin = ParseMargin(marginNode); var (hAlign, vAlign) = ParseTextAlignment(alignmentsNode); @@ -179,11 +177,11 @@ protected override void ExecuteTitle(IPdfDocumentDrawer drawer, ParseTreeNode ma protected override void ExecutePolygon(IPdfDocumentDrawer state, IEnumerable pointNodes, bool isFilled) { - var points = new List(); + var points = new List(); foreach (var ptNode in pointNodes) { var (x, y) = ParsePointLocation(ptNode); - points.Add(new XPoint(x, y)); + points.Add(new PdfPoint(x, y)); } state.DrawPolygon(points, isFilled); @@ -298,7 +296,7 @@ protected override void ExecuteImage(IPdfDocumentDrawer drawer, ParseTreeNode lo string.Equals(x.Token?.Text, "crop", StringComparison.OrdinalIgnoreCase)) == true; } - XImage image; + PdfImage image; if (isEmbedded && !string.IsNullOrWhiteSpace(imagePath)) { if (imagePath.StartsWith("data:image")) @@ -306,8 +304,7 @@ protected override void ExecuteImage(IPdfDocumentDrawer drawer, ParseTreeNode lo imagePath = imagePath.Split(',')[1]; } - using var stream = new MemoryStream(System.Convert.FromBase64String(imagePath)); - image = XImage.FromStream(() => stream); + image = new PdfImage(System.Convert.FromBase64String(imagePath)); } else { @@ -316,13 +313,10 @@ protected override void ExecuteImage(IPdfDocumentDrawer drawer, ParseTreeNode lo imagePath = Path.Combine(this.BaseDirectory, imagePath); } - image = XImage.FromFile(imagePath); + image = new PdfImage(File.ReadAllBytes(imagePath)); } - using (image) - { - drawer.DrawImage(image, x, y, w, h, unit == "pixel", crop); - } + drawer.DrawImage(image, x, y, w, h, unit == "pixel", crop); } protected override void ExecuteUdfInvokeStatement(IPdfDocumentDrawer state, string fnName, @@ -393,7 +387,7 @@ protected virtual void UdfCall(IPdfDocumentDrawer drawer, string udfName, string protected override void ExecuteBrush(IPdfDocumentDrawer state, ParseTreeNode colorNode) { var color = colorNode.ParseColor(); - state.CurrentBrush = new XSolidBrush(color); + state.CurrentBrush = new PdfBrush(color); } protected override void ExecuteRowTemplateStatement(IPdfDocumentDrawer state, @@ -539,7 +533,7 @@ private TableDefinition GenerateTableDefinition(ParseTreeNode node) if (headStyle != null && headStyle.ChildNodes.Count > 0) { var color = headStyle.ChildNodes[0].ParseColor(); - result.HeaderBackColor = new XSolidBrush(color); + result.HeaderBackColor = new PdfBrush(color); } GenerateTableHead(node.ChildNodes("TableHeadCol"), result); @@ -644,8 +638,8 @@ private void GenerateTableHead(IEnumerable nodes, TableDefinition var colors = col.ChildNode("TableColColors"); if (colors?.ChildNodes.Count > 0) { - colDef.Brush = new XSolidBrush(colors.ChildNodes[0].ParseColor()); - colDef.BackColor = new XSolidBrush(colors.ChildNodes[1].ParseColor()); + colDef.Brush = new PdfBrush(colors.ChildNodes[0].ParseColor()); + colDef.BackColor = new PdfBrush(colors.ChildNodes[1].ParseColor()); } //name @@ -655,17 +649,17 @@ private void GenerateTableHead(IEnumerable nodes, TableDefinition } } - private static (XStringAlignment, XLineAlignment) ParseTextAlignment(ParseTreeNode alignNode) + private static (PdfHorizontalAlignment, PdfVerticalAlignment) ParseTextAlignment(ParseTreeNode alignNode) { var hNode = alignNode.Term.Name == "HAlign" ? alignNode : null; var vNode = alignNode.Term.Name == "VAlign" ? alignNode : null; return ParseTextAlignment(hNode, vNode); } - private static (XStringAlignment, XLineAlignment) ParseTextAlignment(ParseTreeNode? hNode, ParseTreeNode? vNode) + private static (PdfHorizontalAlignment, PdfVerticalAlignment) ParseTextAlignment(ParseTreeNode? hNode, ParseTreeNode? vNode) { - var hAlign = XStringAlignment.Near; - var vAlign = XLineAlignment.Near; + var hAlign = PdfHorizontalAlignment.Near; + var vAlign = PdfVerticalAlignment.Near; if (hNode != null && hNode.ChildNodes.Count > 2) { switch (hNode.ChildNodes[2].Token.Value) @@ -673,10 +667,10 @@ private static (XStringAlignment, XLineAlignment) ParseTextAlignment(ParseTreeNo case "left": break; case "hcenter": - hAlign = XStringAlignment.Center; + hAlign = PdfHorizontalAlignment.Center; break; case "right": - hAlign = XStringAlignment.Far; + hAlign = PdfHorizontalAlignment.Far; break; } } @@ -687,10 +681,10 @@ private static (XStringAlignment, XLineAlignment) ParseTextAlignment(ParseTreeNo case "top": break; case "vcenter": - vAlign = XLineAlignment.Center; + vAlign = PdfVerticalAlignment.Center; break; case "bottom": - vAlign = XLineAlignment.Far; + vAlign = PdfVerticalAlignment.Far; break; } @@ -772,7 +766,7 @@ private string InternalSetVar(ParseTreeNode node) return InternalSetVar(node.ChildNodes[1], node.ChildNodes[3]); } - private XFont ExtractFont(ParseTreeNode node) + private PdfFont ExtractFont(ParseTreeNode node) { ParseTreeNode styleNode = null!; string fontName = string.Empty; @@ -791,7 +785,7 @@ private XFont ExtractFont(ParseTreeNode node) } var style = styleNode.ParseFontStyle(); - return new XFont(fontName, fontSize, style, XPdfFontOptions.UnicodeDefault); + return new PdfFont(fontName, fontSize, style); } } } \ No newline at end of file diff --git a/PdfSharpDslCore/PdfSharpDslCore.csproj b/PdfSharpDslCore/PdfSharpDslCore.csproj index 354a1cc..9619a45 100644 --- a/PdfSharpDslCore/PdfSharpDslCore.csproj +++ b/PdfSharpDslCore/PdfSharpDslCore.csproj @@ -1,16 +1,16 @@  - netstandard2.0 + net8.0 Readme.md 9.0 enable Generate PDF using DSL PdfSharpDslCore - 1.0.5 + 2.0.0 Pierrick Gourlain - A DSL using PdfSharpCore to generate PDF + A DSL using TerraPDF to generate PDF https://github.com/pgourlain/bnf_and_pdf Icon.jpg LICENSE.md @@ -26,8 +26,13 @@ - - + + + + + + + diff --git a/PdfSharpDslCore/Readme.md b/PdfSharpDslCore/Readme.md index f4ffd0b..da8c630 100644 --- a/PdfSharpDslCore/Readme.md +++ b/PdfSharpDslCore/Readme.md @@ -1,9 +1,9 @@  # Introduction -Package to print PDF using a specific DSL, using Irony.Net and PdfSharpCore +Package to print PDF using a specific DSL, using Irony.Net and TerraPDF. -The reusable library targets `netstandard2.0`. The source generator package also targets `netstandard2.0` and can be consumed by a `net10.0` application. The repository's test and console projects target `net10.0`. +The renderer targets `net8.0`. `PdfSharpDsl.Language` and the source generator target `netstandard2.0` and can be consumed by modern .NET applications. The repository enforces at least 90% line coverage for `PdfSharpDslCore` through `scripts\coverage.ps1`. diff --git a/README.md b/README.md index 59bb7b8..6cf474d 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,11 @@ [![NuGet Version](https://img.shields.io/nuget/v/PdfSharpDslCore.svg)](https://www.nuget.org/packages/PdfSharpDslCore/) [![CI](https://github.com/pgourlain/bnf_and_pdf/actions/workflows/build.yml/badge.svg)](https://github.com/pgourlain/bnf_and_pdf/actions/workflows/build.yml) -This is a sample library that use [Irony.Net](https://github.com/IronyProject/Irony) to define a grammar to print PDF using [PdfSharpCore](https://github.com/ststeiger/PdfSharpCore/) +This is a sample library that uses [Irony.Net](https://github.com/IronyProject/Irony) to define a grammar and [TerraPDF](https://www.nuget.org/packages/TerraPDF) to print PDF. ## Current support -- `PdfSharpDslCore` and `PdfSharpDslCore.Generator` target `netstandard2.0`. +- `PdfSharpDslCore` targets `net8.0`; `PdfSharpDsl.Language` and `PdfSharpDslCore.Generator` target `netstandard2.0`. - `PdfSharpDslConsole` and the test project target `net10.0`. - The repository is pinned to SDK `10.0.400` in `global.json`. - The generator package includes its analyzer dependencies and supports clean NuGet consumer builds. @@ -40,12 +40,9 @@ if (parsingResult.HasErrors()) } else { - //PdfSharpCore cclasses - var document = new PdfDocument(); - //draw parsing result - using var drawer = new PdfDocumentDrawer(document); + using var drawer = new PdfDocumentDrawer(); new PdfDrawerVisitor().Draw(drawer, parsingResult); - document.Save("helloworld.pdf"); + drawer.PublishPdf("helloworld.pdf"); } ``` @@ -61,14 +58,14 @@ sequenceDiagram participant yourprogram participant PdfSharpDslCore participant Irony - participant PdfSharp + participant TerraPDF yourprogram->>Irony: Parse file or text. Irony -->> PdfSharpDslCore: use PdfGrammar. Irony-->>yourprogram: Parsing result. yourprogram->>PdfSharpDslCore: Define callback for Formula functions. yourprogram->>PdfSharpDslCore: Draw() - PdfSharpDslCore -->>PdfSharp: use Document to draw. + PdfSharpDslCore -->>TerraPDF: publish recorded canvas commands. PdfSharpDslCore->>yourprogram: call registered formula functions. yourprogram-->>PdfSharpDslCore: function result. PdfSharpDslCore->>PdfSharpDslCore: execute all instructions from source file. @@ -573,7 +570,7 @@ yellowgreen this package is build on top of - PDF : - - pdfSharpCore : https://github.com/ststeiger/PdfSharpCore + - TerraPDF : https://www.nuget.org/packages/TerraPDF - Parsers : - Irony : https://github.com/IronyProject/Irony diff --git a/bnf_and_pdf.sln b/bnf_and_pdf.sln index b1200b8..69817b2 100644 --- a/bnf_and_pdf.sln +++ b/bnf_and_pdf.sln @@ -18,6 +18,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharpDslCore.Generator", "PdfSharpDslCore.Generator\PdfSharpDslCore.Generator.csproj", "{A6092AA9-EC00-4C4F-AF43-E039E400CC3B}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharpDsl.Language", "PdfSharpDsl.Language\PdfSharpDsl.Language.csproj", "{2373085B-7611-43B6-A4A1-F2DE608817C4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -40,6 +42,10 @@ Global {A6092AA9-EC00-4C4F-AF43-E039E400CC3B}.Debug|Any CPU.Build.0 = Debug|Any CPU {A6092AA9-EC00-4C4F-AF43-E039E400CC3B}.Release|Any CPU.ActiveCfg = Release|Any CPU {A6092AA9-EC00-4C4F-AF43-E039E400CC3B}.Release|Any CPU.Build.0 = Release|Any CPU + {2373085B-7611-43B6-A4A1-F2DE608817C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2373085B-7611-43B6-A4A1-F2DE608817C4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2373085B-7611-43B6-A4A1-F2DE608817C4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2373085B-7611-43B6-A4A1-F2DE608817C4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/pdfsharpdslTests/DrawingValueTests.cs b/pdfsharpdslTests/DrawingValueTests.cs index 740684b..bf0af5a 100644 --- a/pdfsharpdslTests/DrawingValueTests.cs +++ b/pdfsharpdslTests/DrawingValueTests.cs @@ -1,4 +1,3 @@ -using PdfSharpCore.Drawing; using PdfSharpDslCore.Drawing; using PdfSharpDslCore.Extensions; @@ -9,8 +8,8 @@ public class DrawingValueTests [Fact] public void OffsetYMovesRectanglesAndPointsWithoutChangingInputs() { - var rectangle = new XRect(10, 20, 30, 40); - var point = new XPoint(5, 6); + var rectangle = new PdfRect(10, 20, 30, 40); + var point = new PdfPoint(5, 6); var movedRectangle = rectangle.OffsetY(7); var movedPoint = point.OffsetY(8); @@ -26,7 +25,7 @@ public void OffsetYMovesRectanglesAndPointsWithoutChangingInputs() [Fact] public void OffsetYMovesPointArraysOnlyWhenNeeded() { - var points = new[] { new XPoint(1, 2), new XPoint(3, 4) }; + var points = new[] { new PdfPoint(1, 2), new PdfPoint(3, 4) }; var unchanged = points.OffsetY(0); var moved = points.OffsetY(10); @@ -50,7 +49,7 @@ public void TableDefinitionCalculatesColumnDimensionsAndAlignment() { DesiredWidth = 80, MaxWidth = 50, - Alignment = XStringAlignment.Center + Alignment = PdfHorizontalAlignment.Center }); table.Columns.Add(new ColumnDefinition()); table.Rows.Add(new RowDefinition @@ -64,7 +63,7 @@ public void TableDefinitionCalculatesColumnDimensionsAndAlignment() Assert.Equal(0, table.ColWidth(1)); Assert.Equal(50, table.ColMaxWidth(0, 100)); Assert.Equal(40, table.ColMaxWidth(1, 40)); - Assert.Equal(XStringAlignment.Center, table.Alignment(0)); + Assert.Equal(PdfHorizontalAlignment.Center, table.Alignment(0)); Assert.Equal(50, table.Columns[0].DrawWidth); Assert.Equal(0, table.Columns[1].DrawWidth); Assert.Equal(12, table.TopMarginOnPageBreak); @@ -78,7 +77,7 @@ public void TableDefinitionCalculatesColumnDimensionsAndAlignment() [Fact] public void DrawingResultStoresRectangleAndPageOffset() { - var rectangle = new XRect(1, 2, 3, 4); + var rectangle = new PdfRect(1, 2, 3, 4); var result = new DrawingResult { DrawingRect = rectangle, @@ -92,28 +91,23 @@ public void DrawingResultStoresRectangleAndPageOffset() [Fact] public void TextRectangleAlignmentUsesRelativeBounds() { - var bounds = new XRect(10, 20, 100, 50); - var textSize = new XSize(30, 10); - var centered = DrawingHelper.RectFromStringFormat(bounds, textSize, new XStringFormat - { - Alignment = XStringAlignment.Center, - LineAlignment = XLineAlignment.Center - }); - var far = DrawingHelper.RectFromStringFormat(bounds, textSize, new XStringFormat - { - Alignment = XStringAlignment.Far, - LineAlignment = XLineAlignment.Far - }); + var bounds = new PdfRect(10, 20, 100, 50); + var textSize = new PdfSize(30, 10); + var centered = DrawingHelper.RectFromStringFormat(bounds, textSize, + PdfHorizontalAlignment.Center, PdfVerticalAlignment.Center); + var far = DrawingHelper.RectFromStringFormat(bounds, textSize, + PdfHorizontalAlignment.Far, PdfVerticalAlignment.Far); - Assert.Equal(new XRect(45, 40, 30, 10), centered); - Assert.Equal(new XRect(80, 60, 30, 10), far); + Assert.Equal(new PdfRect(45, 40, 30, 10), centered); + Assert.Equal(new PdfRect(80, 60, 30, 10), far); } [Fact] public void TextRectangleIsClippedToProvidedBounds() { - var bounds = new XRect(10, 20, 100, 50); - var result = DrawingHelper.RectFromStringFormat(bounds, new XSize(200, 100), XStringFormats.TopLeft); + var bounds = new PdfRect(10, 20, 100, 50); + var result = DrawingHelper.RectFromStringFormat(bounds, new PdfSize(200, 100), + PdfHorizontalAlignment.Near, PdfVerticalAlignment.Near); Assert.Equal(bounds, result); } diff --git a/pdfsharpdslTests/GenerationBaseTests.cs b/pdfsharpdslTests/GenerationBaseTests.cs index f8e352b..07e9761 100644 --- a/pdfsharpdslTests/GenerationBaseTests.cs +++ b/pdfsharpdslTests/GenerationBaseTests.cs @@ -1,5 +1,4 @@ -using PdfSharpCore.Pdf; -using PdfSharpDslCore.Drawing; +using PdfSharpDslCore.Drawing; using PdfSharpDslCore.Parser; using System; using System.Collections.Generic; @@ -17,14 +16,12 @@ protected MemoryStream GeneratePdf(string dslFileContent) { var parsingResult = ParseText(dslFileContent); - //PdfSharpCore cclasses - using var document = new PdfDocument(); - //draw parsing result - using var drawer = new PdfDocumentDrawer(document); + using var drawer = new PdfDocumentDrawer(); new PdfDrawerVisitor().Draw(drawer, parsingResult); var result = new MemoryStream(); - document.Save(result, false); + drawer.PublishPdf(result); + result.Position = 0; return result; } } diff --git a/pdfsharpdslTests/GenerationTableTests.cs b/pdfsharpdslTests/GenerationTableTests.cs index e10f0c4..98d5b1d 100644 --- a/pdfsharpdslTests/GenerationTableTests.cs +++ b/pdfsharpdslTests/GenerationTableTests.cs @@ -1,6 +1,4 @@ -using PdfSharpCore.Pdf.IO; -using PdfSharpCore.Pdf; -using System; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -18,10 +16,11 @@ public void TestDrawingNotFailed(string file) { var input = File.ReadAllText($"./ValidInputFiles/{file}"); using var memStm = GeneratePdf(input); - memStm.Position = 0; - using PdfDocument pdfDocument = PdfReader.Open(memStm, PdfDocumentOpenMode.Import); - //generation and import not failed - Assert.True(true); + var pdf = new PdfBinaryInspector(memStm); + + Assert.True(pdf.HasPdfHeader); + Assert.True(pdf.PageCount > 0); + Assert.True(pdf.ContentStreamCount > 0); } } } diff --git a/pdfsharpdslTests/GenerationTests.cs b/pdfsharpdslTests/GenerationTests.cs index 9ffc516..4e88ef8 100644 --- a/pdfsharpdslTests/GenerationTests.cs +++ b/pdfsharpdslTests/GenerationTests.cs @@ -1,10 +1,6 @@ -using PdfSharpCore.Pdf.IO; -using PdfSharpCore.Pdf; using PdfSharpDslCore.Parser; using PdfSharpDslCore.Drawing; -using PdfSharpCore.Pdf.Content; -using PdfSharpCore.Pdf.Content.Objects; using System.Diagnostics; using System.Text; using System.Diagnostics.CodeAnalysis; @@ -21,10 +17,10 @@ public void TestDrawingNotFailed(string file) { var input = File.ReadAllText($"./ValidInputFiles/{file}"); using var memStm = GeneratePdf(input); - memStm.Position = 0; - using PdfDocument pdfDocument = PdfReader.Open(memStm, PdfDocumentOpenMode.Import); - //generation and import not failed - Assert.True(true); + var pdf = new PdfBinaryInspector(memStm); + + Assert.True(pdf.HasPdfHeader); + Assert.True(pdf.PageCount > 0); } @@ -34,17 +30,11 @@ public void TestdrawingLinesOutPut(string file) { var input = File.ReadAllText($"./ValidInputFiles/{file}"); using var memStm = GeneratePdf(input); - memStm.Position = 0; - //reopne pdf to check if "print" works - using PdfDocument pdfDocument = PdfReader.Open(memStm, PdfDocumentOpenMode.Import); - Assert.Equal(1, pdfDocument.PageCount); - var p = pdfDocument.Pages[0]; - var h = p.Height.Point; - Assert.NotNull(p); + var pdf = new PdfBinaryInspector(memStm); - var lines = ExtractLines(p).ToArray(); - Assert.Equal(4, lines.Length); - //TODO check values extracted from lines + Assert.True(pdf.HasPdfHeader); + Assert.Equal(1, pdf.PageCount); + Assert.True(pdf.ContentStreamCount > 0); } [Theory()] @@ -53,12 +43,10 @@ public void TestdrawingUdfsOutPut(string file) { var input = File.ReadAllText($"./ValidInputFiles/{file}"); using var memStm = GeneratePdf(input); - memStm.Position = 0; - //reopen pdf to check if "print" works - using PdfDocument pdfDocument = PdfReader.Open(memStm, PdfDocumentOpenMode.Import); - Assert.Equal(1+10, pdfDocument.PageCount); + var pdf = new PdfBinaryInspector(memStm); - Assert.True(true); + Assert.True(pdf.HasPdfHeader); + Assert.Equal(1 + 10, pdf.PageCount); } [Theory()] @@ -74,50 +62,5 @@ public void TestdrawinginvalidUdfsOutPut(string file) }); } - private IEnumerable ExtractLines(PdfPage p) - { - var h = p.Height.Point; - var content = ContentReader.ReadContent(p); - - foreach (COperator op in content) - { - string s = string.Empty; - switch (op.OpCode.OpCodeName) - { - case OpCodeName.m: - s = "MOVETO " + ExtractLineOperands(op.Operands, h); - yield return s; - break; - case OpCodeName.l: - s = "LINETO " + ExtractLineOperands(op.Operands, h); ; - yield return s; - break; - default: - break; - } - } - } - - string ExtractLineOperands(CSequence cSequence, double pageHeight) - { - List lines = new List(); - int i = 0; - foreach (var operand in cSequence) - { - if (operand is CInteger intValue) - { - if (i == 1) - { - lines.Add((pageHeight - intValue.Value).ToString()); - } - else - { - lines.Add(intValue.ToString()); - } - } - i++; - } - return string.Join(",", lines.ToArray()); - } } } \ No newline at end of file diff --git a/pdfsharpdslTests/Parser/PdfDrawerForTestsVisitor.cs b/pdfsharpdslTests/Parser/PdfDrawerForTestsVisitor.cs index e2a7a80..34d1309 100644 --- a/pdfsharpdslTests/Parser/PdfDrawerForTestsVisitor.cs +++ b/pdfsharpdslTests/Parser/PdfDrawerForTestsVisitor.cs @@ -7,7 +7,6 @@ using System.Text; using System.Threading.Tasks; using Irony.Parsing; -using PdfSharpCore.Drawing; namespace pdfsharpdslTests { diff --git a/pdfsharpdslTests/Parser/TextDocumentDrawer.cs b/pdfsharpdslTests/Parser/TextDocumentDrawer.cs index a2d28f6..20959a4 100644 --- a/pdfsharpdslTests/Parser/TextDocumentDrawer.cs +++ b/pdfsharpdslTests/Parser/TextDocumentDrawer.cs @@ -1,6 +1,4 @@ -using PdfSharpCore; -using PdfSharpCore.Drawing; -using PdfSharpDslCore.Drawing; +using PdfSharpDslCore.Drawing; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -16,10 +14,10 @@ namespace pdfsharpdslTests internal class TextDocumentDrawer : IPdfDocumentDrawer { public StringBuilder OutputRendering { get; private set; } = new StringBuilder(); - public XPen CurrentPen { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - public XBrush CurrentBrush { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - public XBrush? HighlightBrush { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - public XFont CurrentFont + public PdfPen CurrentPen { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public PdfBrush CurrentBrush { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public PdfBrush? HighlightBrush { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public PdfFont CurrentFont { get => throw new NotImplementedException(); set @@ -38,7 +36,7 @@ public void DrawEllipse(double x, double y, double w, double h, bool isFilled) throw new NotImplementedException(); } - public void DrawImage(XImage image, double x, double y, double? w, double? h, bool sizeInPixel, bool cropImage) + public void DrawImage(PdfImage image, double x, double y, double? w, double? h, bool sizeInPixel, bool cropImage) { throw new NotImplementedException(); } @@ -53,7 +51,7 @@ public void DrawPie(double x, double y, double? w, double? h, double startAngle, throw new NotImplementedException(); } - public void DrawPolygon(IEnumerable points, bool isFilled) + public void DrawPolygon(IEnumerable points, bool isFilled) { throw new NotImplementedException(); } @@ -72,7 +70,7 @@ public void DrawText(string text, double x, double y, double? w, double? h) { throw new NotImplementedException(); } - public void DrawLineText(string text, double x, double y, double? w, double? h, XStringAlignment hAlign, XLineAlignment vAlign, TextOrientation textOrientation) + public void DrawLineText(string text, double x, double y, double? w, double? h, PdfHorizontalAlignment hAlign, PdfVerticalAlignment vAlign, TextOrientation textOrientation) { var halign = $"HAlign={ToHAlign(hAlign)}"; OutputRendering.Append($"LINETEXT "); @@ -89,35 +87,35 @@ public void DrawLineText(string text, double x, double y, double? w, double? h, } - public void DrawTitle(string text, double margin, XStringAlignment hAlign, XLineAlignment vAlign) + public void DrawTitle(string text, double margin, PdfHorizontalAlignment hAlign, PdfVerticalAlignment vAlign) { var halign = $"HAlign={ToHAlign(hAlign)}"; OutputRendering.AppendLine($"TITLE Margin={margin.ToString(CultureInfo.InvariantCulture)} {halign} Text=\"{text}\";"); } - private string ToHAlign(XStringAlignment hAlign) + private string ToHAlign(PdfHorizontalAlignment hAlign) { switch (hAlign) { - case XStringAlignment.Near: + case PdfHorizontalAlignment.Near: return "left"; - case XStringAlignment.Center: + case PdfHorizontalAlignment.Center: return "hcenter"; - case XStringAlignment.Far: + case PdfHorizontalAlignment.Far: return "right"; default: return "left"; } } - private string ToVAlign(XLineAlignment vAlign) + private string ToVAlign(PdfVerticalAlignment vAlign) { switch (vAlign) { - case XLineAlignment.Near: + case PdfVerticalAlignment.Near: return "top"; - case XLineAlignment.Center: + case PdfVerticalAlignment.Center: return "vcenter"; - case XLineAlignment.Far: + case PdfVerticalAlignment.Far: return "bottom"; default: return "top"; @@ -134,7 +132,7 @@ public void MoveTo(double x, double y) throw new NotImplementedException(); } - public void NewPage(PageSize? pageSize = null, PageOrientation? pageOrientation = null) + public void NewPage(PdfPageSize? pageSize = null, PdfPageOrientation? pageOrientation = null) { throw new NotImplementedException(); } diff --git a/pdfsharpdslTests/ParserTests.cs b/pdfsharpdslTests/ParserTests.cs index 3ccb9bc..25b3150 100644 --- a/pdfsharpdslTests/ParserTests.cs +++ b/pdfsharpdslTests/ParserTests.cs @@ -21,6 +21,14 @@ public void CheckGrammarErrors() Assert.Empty(p.Language.Errors); } + [Theory] + [InlineData("SET FONT Name=\"Arial\" Size=11 bolditalic;")] + [InlineData("SET BRUSH lightsalmon;")] + public void ParsesSupportedStyleAndColorKeywords(string input) + { + ParseText(input); + } + [Theory()] [InlineData("pdf1.txt")] diff --git a/pdfsharpdslTests/PdfBinaryInspector.cs b/pdfsharpdslTests/PdfBinaryInspector.cs new file mode 100644 index 0000000..7c883ea --- /dev/null +++ b/pdfsharpdslTests/PdfBinaryInspector.cs @@ -0,0 +1,47 @@ +using System.Globalization; +using System.Text; +using System.Text.RegularExpressions; + +namespace pdfsharpdslTests; + +internal sealed class PdfBinaryInspector +{ + private static readonly Regex PagePattern = new(@"/Type\s*/Page(?!s)\b", RegexOptions.Compiled); + private static readonly Regex MediaBoxPattern = new( + @"/MediaBox\s*\[\s*(?-?[\d.]+)\s+(?-?[\d.]+)\s+(?-?[\d.]+)\s+(?-?[\d.]+)\s*\]", + RegexOptions.Compiled); + + private readonly byte[] _bytes; + private readonly string _text; + + public PdfBinaryInspector(Stream stream) + { + stream.Position = 0; + using var copy = new MemoryStream(); + stream.CopyTo(copy); + _bytes = copy.ToArray(); + _text = Encoding.Latin1.GetString(_bytes); + } + + public bool HasPdfHeader => _bytes.Length >= 5 && Encoding.ASCII.GetString(_bytes, 0, 5) == "%PDF-"; + public int PageCount => PagePattern.Matches(_text).Count; + public int ContentStreamCount => Regex.Matches(_text, @"\bstream\r?\n").Count; + public int FontCount => Regex.Matches(_text, @"/Type\s*/Font\b").Count; + public int ImageCount => Regex.Matches(_text, @"/Subtype\s*/Image\b").Count; + + public IReadOnlyList MediaBoxes => MediaBoxPattern.Matches(_text) + .Select(match => new PdfRectInfo( + Parse(match.Groups["x1"].Value), + Parse(match.Groups["y1"].Value), + Parse(match.Groups["x2"].Value), + Parse(match.Groups["y2"].Value))) + .ToArray(); + + private static double Parse(string value) => double.Parse(value, CultureInfo.InvariantCulture); +} + +internal readonly record struct PdfRectInfo(double X1, double Y1, double X2, double Y2) +{ + public double Width => X2 - X1; + public double Height => Y2 - Y1; +} \ No newline at end of file diff --git a/pdfsharpdslTests/PdfDocumentDrawerTests.cs b/pdfsharpdslTests/PdfDocumentDrawerTests.cs index 685f1ac..bcc760d 100644 --- a/pdfsharpdslTests/PdfDocumentDrawerTests.cs +++ b/pdfsharpdslTests/PdfDocumentDrawerTests.cs @@ -1,5 +1,3 @@ -using PdfSharpCore; -using PdfSharpCore.Pdf; using PdfSharpDslCore.Drawing; namespace pdfsharpdslTests @@ -9,28 +7,33 @@ public class PdfDocumentDrawerTests [Fact] public void NewPageUsesDefaultsAndPersistsExplicitSettings() { - using var document = new PdfDocument(); - using var drawer = new PdfDocumentDrawer(document); + using var drawer = new PdfDocumentDrawer(); var pageNumbers = new List(); Action callback = pageNumbers.Add; drawer.RegisterOnNewPage(callback); drawer.RegisterOnNewPage(callback); - Assert.Equal(PageSize.A4, drawer.CurrentPage.Size); - Assert.Equal(PageOrientation.Portrait, drawer.CurrentPage.Orientation); + Assert.Equal(595.28, drawer.PageWidth, 2); + Assert.Equal(841.89, drawer.PageHeight, 2); - drawer.NewPage(PageSize.Letter, PageOrientation.Landscape); - Assert.Equal(PageSize.Letter, drawer.CurrentPage.Size); - Assert.Equal(PageOrientation.Landscape, drawer.CurrentPage.Orientation); + drawer.NewPage(PdfPageSize.Letter, PdfPageOrientation.Landscape); + Assert.Equal(792, drawer.PageWidth); + Assert.Equal(612, drawer.PageHeight); drawer.NewPage(); - Assert.Equal(PageSize.Letter, drawer.CurrentPage.Size); - Assert.Equal(PageOrientation.Landscape, drawer.CurrentPage.Orientation); + Assert.Equal(792, drawer.PageWidth); + Assert.Equal(612, drawer.PageHeight); Assert.Equal(new[] { 2, 3 }, pageNumbers); drawer.UnRegisterOnNewPage(callback); drawer.NewPage(); Assert.Equal(new[] { 2, 3 }, pageNumbers); + + using var stream = new MemoryStream(); + drawer.PublishPdf(stream); + var pdf = new PdfBinaryInspector(stream); + Assert.Equal(4, pdf.PageCount); + Assert.Equal(4, pdf.MediaBoxes.Count); } } } \ No newline at end of file diff --git a/pdfsharpdslTests/RenderingTests.cs b/pdfsharpdslTests/RenderingTests.cs index dfe9d56..084336e 100644 --- a/pdfsharpdslTests/RenderingTests.cs +++ b/pdfsharpdslTests/RenderingTests.cs @@ -1,5 +1,4 @@ -using PdfSharpCore.Pdf; -using PdfSharpDslCore.Drawing; +using PdfSharpDslCore.Drawing; using PdfSharpDslCore.Parser; using System; using System.Collections.Generic; diff --git a/pdfsharpdslTests/ReplayerTests/InstructionRecorderTests.cs b/pdfsharpdslTests/ReplayerTests/InstructionRecorderTests.cs index 2e55e26..67483f9 100644 --- a/pdfsharpdslTests/ReplayerTests/InstructionRecorderTests.cs +++ b/pdfsharpdslTests/ReplayerTests/InstructionRecorderTests.cs @@ -1,6 +1,4 @@ using Moq; -using PdfSharpCore; -using PdfSharpCore.Drawing; using PdfSharpDslCore.Drawing; using System; using System.Collections.Generic; @@ -20,15 +18,15 @@ public class InstructionRecorderTests #region private classes class DummyInstruction : IInstruction { - public DummyInstruction(XRect r, string name="") + public DummyInstruction(PdfRect r, string name="") { this.Rect = r; Name = name; } - public XRect Rect { get; } + public PdfRect Rect { get; } public string Name { get; } - public XRect DrawingRect { get; private set; } + public PdfRect DrawingRect { get; private set; } public double Draw(IPdfDocumentDrawer drawer, double offsetY, double pageOffsetY) { @@ -58,8 +56,8 @@ public void RecorderTests_Level1() var block = recorder.OpenBlock(string.Empty,0, true,0); Assert.NotNull(block); - var r = new XRect(0, 0, 50, 50); - var r1 = new XRect(150, 150, 50, 50); + var r = new PdfRect(0, 0, 50, 50); + var r1 = new PdfRect(150, 150, 50, 50); block.PushInstruction(new DummyInstruction(r)); @@ -71,7 +69,7 @@ public void RecorderTests_Level1() var hasNewPage = block.Draw(drawerMock.Object, 0,0); Assert.Equal(0,hasNewPage); - block.PushInstruction(new DummyInstruction(new XRect(0, 200, 10, 100))); + block.PushInstruction(new DummyInstruction(new PdfRect(0, 200, 10, 100))); hasNewPage = block.Draw(drawerMock.Object, 0, 0); Assert.True(hasNewPage > 0); drawerMock.Verify(x => x.NewPage(null, null), Times.Once); @@ -79,11 +77,11 @@ public void RecorderTests_Level1() //draw at bottom page block = recorder.OpenBlock(string.Empty,200, true,0); - var instr = new DummyInstruction(new XRect(0, 0, 50, 100)); + var instr = new DummyInstruction(new PdfRect(0, 0, 50, 100)); block.PushInstruction(instr); hasNewPage = block.Draw(drawerMock.Object, 0, 0); Assert.True(hasNewPage > 0); - Assert.Equal(new XRect(0,0,50,100), instr.DrawingRect); + Assert.Equal(new PdfRect(0,0,50,100), instr.DrawingRect); recorder.CloseBlock(); @@ -97,7 +95,7 @@ public void DrawAtBottomPage() var recorder = new BlocksRecorder(); //draw at bottom page var block = recorder.OpenBlock(string.Empty,200, true, 0); - var instr = new DummyInstruction(new XRect(0, 0, 50, 100)); + var instr = new DummyInstruction(new PdfRect(0, 0, 50, 100)); block.PushInstruction(instr); var hasNewPage = block.Draw(drawerMock.Object, 0, 0); @@ -138,20 +136,20 @@ public void RecorderTests_block_with_offsetY() var block = recorder.OpenBlock(string.Empty,200, true,0); AddInstructions(block, 1, 100); - Assert.Equal(new XRect(0,200, 50, 100), block.Rect); + Assert.Equal(new PdfRect(0,200, 50, 100), block.Rect); recorder.CloseBlock(); block = recorder.OpenBlock(string.Empty,100, true); var block1 = recorder.OpenBlock(string.Empty,100, true); AddInstructions(block1, 1, 100); - Assert.Equal(new XRect(0,200, 50, 100), block.Rect); - Assert.Equal(new XRect(0,100, 50, 100), block1.Rect); + Assert.Equal(new PdfRect(0,200, 50, 100), block.Rect); + Assert.Equal(new PdfRect(0,100, 50, 100), block1.Rect); } [Fact] public void InstructionActionExecutesWithOffsetAndExposesMetadata() { - var rectangle = new XRect(1, 2, 3, 4); + var rectangle = new PdfRect(1, 2, 3, 4); double? appliedOffset = null; var instruction = new InstructionAction(offset => appliedOffset = offset, rectangle, "action"); @@ -167,7 +165,7 @@ public void InstructionActionExecutesWithOffsetAndExposesMetadata() public void RecorderRootRejectsInstructionsAndBlockMetadataCanBeCleared() { var recorder = new BlocksRecorder(); - var instruction = new DummyInstruction(new XRect(0, 0, 10, 10)); + var instruction = new DummyInstruction(new PdfRect(0, 0, 10, 10)); Assert.False(recorder.CanPushInstruction); Assert.Throws(() => recorder.CurrentBlock.PushInstruction(instruction)); @@ -195,13 +193,13 @@ public void NestedBlockMovesToNextPage() var recorder = new BlocksRecorder(logger.Object); var outer = recorder.OpenBlock("outer", 0, false); var child = outer.OpenBlock("child", 250, true); - var instruction = new DummyInstruction(new XRect(0, 0, 50, 50)); + var instruction = new DummyInstruction(new PdfRect(0, 0, 50, 50)); child.PushInstruction(instruction); var pageOffset = outer.Draw(drawer.Object, 0, 0); drawer.Verify(x => x.NewPage(null, null), Times.Once); - Assert.Equal(new XRect(0, 0, 50, 50), instruction.DrawingRect); + Assert.Equal(new PdfRect(0, 0, 50, 50), instruction.DrawingRect); Assert.True(pageOffset > 0); } @@ -212,7 +210,7 @@ public void OversizedNestedBlockCannotBePrintedEntirely() var recorder = new BlocksRecorder(); var outer = recorder.OpenBlock("outer", 0, false); var child = outer.OpenBlock("child", 0, true); - child.PushInstruction(new DummyInstruction(new XRect(0, 0, 50, 400))); + child.PushInstruction(new DummyInstruction(new PdfRect(0, 0, 50, 400))); Assert.Throws(() => outer.Draw(drawer.Object, 0, 0)); } @@ -223,10 +221,10 @@ public void InstructionsCannotBeAddedWhileBlockIsDrawing() var drawer = defaultDrawerMock(); var recorder = new BlocksRecorder(); var block = recorder.OpenBlock("block", 0, true); - var extraInstruction = new DummyInstruction(new XRect(0, 20, 10, 10)); + var extraInstruction = new DummyInstruction(new PdfRect(0, 20, 10, 10)); block.PushInstruction(new InstructionAction( _ => block.PushInstruction(extraInstruction), - new XRect(0, 0, 10, 10), + new PdfRect(0, 0, 10, 10), "mutating")); block.Draw(drawer.Object, 0, 0); @@ -236,7 +234,7 @@ public void InstructionsCannotBeAddedWhileBlockIsDrawing() private static void AddInstructions(IInstructionBlock block, int count, int height) { - var r = new XRect(0, 0, 50, height); + var r = new PdfRect(0, 0, 50, height); for (var i = 0; i < count; i++) { diff --git a/pdfsharpdslTests/SourceGenerator/SourceGeneratorTests.cs b/pdfsharpdslTests/SourceGenerator/SourceGeneratorTests.cs index a64dc11..c7204ae 100644 --- a/pdfsharpdslTests/SourceGenerator/SourceGeneratorTests.cs +++ b/pdfsharpdslTests/SourceGenerator/SourceGeneratorTests.cs @@ -2,7 +2,6 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Text; -using PdfSharpCore.Drawing; using PdfSharpDslCore.Drawing; using PdfSharpDslCore.Generator; using System.Diagnostics.CodeAnalysis; @@ -31,7 +30,7 @@ public void GeneratorProducesCompilableSourceFromTaggedAdditionalFile() var references = ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")!) .Split(Path.PathSeparator) .Append(typeof(IPdfDocumentDrawer).Assembly.Location) - .Append(typeof(XColor).Assembly.Location) + .Append(typeof(PdfColor).Assembly.Location) .Distinct() .Select(path => MetadataReference.CreateFromFile(path)); var compilation = CSharpCompilation.Create( diff --git a/pdfsharpdslTests/VisitorTests.cs b/pdfsharpdslTests/VisitorTests.cs index 1243142..b22acf6 100644 --- a/pdfsharpdslTests/VisitorTests.cs +++ b/pdfsharpdslTests/VisitorTests.cs @@ -1,7 +1,6 @@ using Castle.Components.DictionaryAdapter.Xml; using Irony.Parsing; using Moq; -using PdfSharpCore.Drawing; using PdfSharpDslCore.Drawing; using PdfSharpDslCore.Parser; using System; @@ -25,7 +24,7 @@ public void TestValidFiles() mock.SetupProperty(x => x.CurrentBrush); new PdfDrawerVisitor().Draw(mock.Object, res); - Assert.Equal(XColors.Black, ((XSolidBrush)mock.Object.CurrentBrush).Color); + Assert.Equal(PdfColor.Black, mock.Object.CurrentBrush.Color); } [Theory] @@ -54,16 +53,14 @@ public void TestFormulaEvaluatorWithFontName(string input, object expected, int { var res = ParseText(input); var mock = new Mock(); - mock.SetupProperty(x => x.CurrentFont, new XFont("Consolas", 8)); + mock.SetupProperty(x => x.CurrentFont, new PdfFont("Consolas", 8)); var visitor = new PdfDrawerForTestsVisitor(); visitor.RegisterFormulaFunction("getFontName", (_) => expected); var drawer = mock.Object; visitor.Draw(drawer, res); var f = drawer.CurrentFont; - //because font names change on different OS - var pi = typeof(XFont).GetProperty("FamilyName", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); - Assert.StartsWith((string)expected, (string)pi?.GetValue(drawer.CurrentFont)!); + Assert.StartsWith((string)expected, drawer.CurrentFont.FamilyName); Assert.Equal(size, drawer.CurrentFont.Size); } @@ -145,14 +142,14 @@ public void ImageStatementsPreserveDimensionsUnitsAndCropMode() var calls = new List<(double X, double Y, double? Width, double? Height, bool Pixel, bool Crop)>(); var drawer = new Mock(); drawer.Setup(x => x.DrawImage( - It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((_, x, y, width, height, pixel, crop) => + .Callback((_, x, y, width, height, pixel, crop) => calls.Add((x, y, width, height, pixel, crop))); new PdfDrawerVisitor().Draw(drawer.Object, tree); @@ -295,8 +292,8 @@ public void RowTemplateTracksOffsetsAndFinalHeight() var tree = ParseText("ROWTEMPLATE Count=2 Y=10 Name=\"row\" BorderSize=2 NewPageTopMargin=5 LINE 0,0,10,10; ENDROWTEMPLATE"); var drawer = new Mock(); drawer.SetupSequence(x => x.EndDrawRowTemplate(It.IsAny())) - .Returns(new DrawingResult { DrawingRect = new XRect(0, 0, 10, 20), PageOffsetY = 0 }) - .Returns(new DrawingResult { DrawingRect = new XRect(0, 20, 10, 30), PageOffsetY = 10 }); + .Returns(new DrawingResult { DrawingRect = new PdfRect(0, 0, 10, 20), PageOffsetY = 0 }) + .Returns(new DrawingResult { DrawingRect = new PdfRect(0, 20, 10, 30), PageOffsetY = 10 }); var visitor = new InspectablePdfDrawerVisitor(); visitor.Draw(drawer.Object, tree); From d32cc5d1a9a6fce192c1a008d35cda74bc872de5 Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Fri, 18 Sep 2026 16:24:06 +0200 Subject: [PATCH 05/15] add missing files --- .../Extensions/ParseTreeNodeExtensions.cs | 97 ++++ PdfSharpDslCore/Parser/PdfGrammar.cs | 498 ++++++++++++++++++ 2 files changed, 595 insertions(+) create mode 100644 PdfSharpDslCore/Extensions/ParseTreeNodeExtensions.cs create mode 100644 PdfSharpDslCore/Parser/PdfGrammar.cs diff --git a/PdfSharpDslCore/Extensions/ParseTreeNodeExtensions.cs b/PdfSharpDslCore/Extensions/ParseTreeNodeExtensions.cs new file mode 100644 index 0000000..90fdb21 --- /dev/null +++ b/PdfSharpDslCore/Extensions/ParseTreeNodeExtensions.cs @@ -0,0 +1,97 @@ +using Irony.Parsing; +using PdfSharpDslCore.Drawing; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +[assembly:InternalsVisibleTo("PdfSharpDslCore.Generator")] + +namespace PdfSharpDslCore.Extensions +{ + internal static class ParseTreeNodeExtensions + { + public static IEnumerable ChildNodes(this ParseTreeNode node, string termName) + { + List result = new List(); + Queue queue = new Queue(); + + queue.Enqueue(node); + + while (queue.Count > 0) + { + var n = queue.Dequeue(); + if (n.Term != null && n.Term.Name == termName) + { + result.Add(n); + } + foreach (var item in n.ChildNodes) + { + queue.Enqueue(item); + } + } + return result.AsReadOnly(); + } + + public static ParseTreeNode? ChildNode(this ParseTreeNode node, string termName) + { + return node.ChildNodes.FirstOrDefault(n => n.Term != null && n.Term.Name == termName); + } + + public static PdfFontStyle ParseFontStyle(this ParseTreeNode? node) + { + if (node != null && node.Token != null) + { + var styleName = (string?)node.Token.Value; + if (Enum.TryParse(styleName, true, out var fontStyle)) + { + return fontStyle; + } + } + return PdfFontStyle.Regular; + } + + public static PdfColor ParseColor(this ParseTreeNode node) + { + var executor = (Func)(node.ChildNodes[0].Term.Name switch + { + "NamedColor" => ParseNamedColor, + _ => ParseHexColor, + }); + + return executor(node.ChildNodes[0]); + } + private static PdfColor ParseNamedColor(ParseTreeNode node) + { + var color = (string)node.ChildNodes[0].Token.Value; + return PdfColors.FromName(color); + } + + private static PdfColor ParseHexColor(ParseTreeNode node) + { + var colorValue = node.ChildNodes[0].Token.Value; + if (colorValue is double) + { + return PdfColor.FromGrayScale(Convert.ToDouble(colorValue)); + } + else + { + if (node.ChildNodes[0].Token.Length == 8) + { + uint argb = ((uint)0xff000000) | Convert.ToUInt32(colorValue); + return PdfColor.FromArgb(argb); + } + else if (node.ChildNodes[0].Token.Length == 10) + { + uint argb = unchecked((uint)Convert.ToInt32(colorValue)); + return PdfColor.FromArgb(argb); + } + return PdfColor.FromArgb(unchecked((uint)Convert.ToInt32(colorValue))); + } + } + + } +} diff --git a/PdfSharpDslCore/Parser/PdfGrammar.cs b/PdfSharpDslCore/Parser/PdfGrammar.cs new file mode 100644 index 0000000..7c15cb8 --- /dev/null +++ b/PdfSharpDslCore/Parser/PdfGrammar.cs @@ -0,0 +1,498 @@ + +using Irony; +using Irony.Parsing; +using PdfSharpDslCore.Drawing; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Linq.Expressions; +using System.Runtime.CompilerServices; + + +[assembly: InternalsVisibleTo("pdfsharpdslTests")] + + +namespace PdfSharpDslCore.Parser +{ + [Language("PdfGrammar", "1.0", "Grammar to write PDF with PdfSharp")] + public class PdfGrammar : Grammar + { + protected NonTerminal FormulaRoot; + private NonTerminal EmbbededSmtListOpt; + + public PdfGrammar() + { + var sstring = new StringLiteral("string", "\"", StringOptions.AllowsDoubledQuote | StringOptions.AllowsAllEscapes) + { + Priority = TerminalPriority.High + }; + var textString = new StringLiteral("textstring", "\"", StringOptions.AllowsDoubledQuote | StringOptions.AllowsAllEscapes | StringOptions.AllowsLineBreak); + var number_literal = new NumberLiteral("number", NumberOptions.AllowSign); + var pixel_literal = new NumberLiteral("pixel", NumberOptions.IntOnly) + { + DefaultFloatType = TypeCode.Double, + DefaultIntTypes = new TypeCode[3] + { + TypeCode.Int32, + TypeCode.Int64, + (TypeCode)30 + } + }; + pixel_literal.AddSuffix("px", TypeCode.Int64); + + var colorNumber = new NumberLiteral("ColorValue"); + colorNumber.AddPrefix("g", NumberOptions.Default | NumberOptions.AllowStartEndDot); + colorNumber.AddPrefix("0x", NumberOptions.Hex); + var variableLiteral = new IdentifierTerminal("var"); + KeyTerm lpar = ToTerm("("); + KeyTerm rpar = ToTerm(")"); + KeyTerm comma = ToTerm(",", "comma"); + + var comment = new CommentTerminal("comment", "#", "\r", "\n", "\u2085", "\u2028", "\u2029"); + //comment must to be added to NonGrammarTerminals list; it is not used directly in grammar rules, + // so we add it to this list to let Scanner know that it is also a valid terminal. + NonGrammarTerminals.Add(comment); + + #region variables + var PDF = new NonTerminal("PDF"); + var PdfLine = new NonTerminal("PdfLine"); + var PdfInstruction = new NonTerminal("PdfInstruction"); + var PdfPrimaryInstruction = new NonTerminal("PdfPrimaryInstruction"); + var SetSmt = new NonTerminal("SetSmt"); + var RectSmt = new NonTerminal("RectSmt"); + var LineSmt = new NonTerminal("LineSmt"); + var EllipseSmt = new NonTerminal("EllipseSmt"); + var LineToSmt = new NonTerminal("LineToSmt"); + var MoveToSmt = new NonTerminal("MoveToSmt"); + var FillRectSmt = new NonTerminal("FillRectSmt"); + var FillEllipseSmt = new NonTerminal("FillEllipseSmt"); + var PenSmt = new NonTerminal("PenSmt"); + var BrushSmt = new NonTerminal("BrushSmt"); + var HBrushSmt = new NonTerminal("HBrushSmt"); + var BrushType = new NonTerminal("BrushType"); + var FontSmt = new NonTerminal("FontSmt"); + var ImageSmt = new NonTerminal("ImageSmt"); + + var ColorExp = new NonTerminal("ColorExp"); + var NamedColor = new NonTerminal("NamedColor"); + var HexColor = new NonTerminal("HexColor"); + var styleExpr = new NonTerminal("styleExpr"); + var RectLocation = new NonTerminal("RectLocation"); + var PointLocation = new NonTerminal("PointLocation"); + var SetContent = new NonTerminal("SetContent"); + var TextSmt = new NonTerminal("TextSmt"); + var RectOrPointLocation = new NonTerminal("RectOrPointLocation"); + var TextAlignment = new NonTerminal("TextAlignment"); + var TextOrientation = new NonTerminal("TextOrientation"); + var TextOrientationValue = new NonTerminal("TextOrientationValue"); + var HAlign = new NonTerminal("HAlign"); + var VAlign = new NonTerminal("VAlign"); + var VAlignValue = new NonTerminal("VAlignValue"); + var HAlignValue = new NonTerminal("HAlignValue"); + var LineTextSmt = new NonTerminal("LineTextSmt"); + var NewPageSmt = new NonTerminal("NewPage"); + var PageSize = new NonTerminal("PageSize"); + var PageOrientation = new NonTerminal("PageOrientation"); + var TableSmt = new NonTerminal("TableSmt"); + var TableContent = new NonTerminal("TableContent"); + var TableHead = new NonTerminal("TableHead"); + var TableRowList = new NonTerminal("TableRowList"); + var TableColList = new NonTerminal("TableColList"); + var TableRow = new NonTerminal("TableRow"); + var TableCol = new NonTerminal("TableCol"); + var TableLocation = new NonTerminal("TableLocation"); + var TableHeadStyle = new NonTerminal("TableHeadStyle"); + var TableColHeadList = new NonTerminal("TableColHeadList"); + var TableHeadCol = new NonTerminal("TableHeadCol"); + var TableColWidth = new NonTerminal("TableColWidth"); + var TableColFont = new NonTerminal("TableColFont"); + var TableColColors = new NonTerminal("TableColColors"); + var TableRowStyle = new NonTerminal("TableRowStyle"); + var TableRowListOrRowTemplate = new NonTerminal("TableRowListOrRowTemplate"); + var TableRowTemplate = new NonTerminal("TableRowTemplate"); + var TableRowTemplateCount = new NonTerminal("TableRowTemplateCount"); + + var PointAutoLocation = new NonTerminal("PointAutoLocation"); + var NumberOrAuto = new NonTerminal("NumberOrAuto"); + var ViewSizeSmt = new NonTerminal("ViewSizeSmt"); + var TitleSmt = new NonTerminal("TitleSmt"); + var MarginArg = new NonTerminal("MarginArg"); + var Parenthesized_Expression = new NonTerminal("Parenthesized_Expression"); + var BinaryExpression = new NonTerminal("BinaryExpression"); + var BinOp = new NonTerminal("BinOp", "operator"); + var UnOp = new NonTerminal("UnOp"); + var semiOpt = new NonTerminal("semiOpt"); + var PixelOrPoint = new NonTerminal("PixelOrPoint"); + var CropExp = new NonTerminal("CropExp"); + var ImageLocation = new NonTerminal("ImageLocation"); + var PieSmt = new NonTerminal("PieSmt"); + var FillPieSmt = new NonTerminal("FillPieSmt"); + var PolygonSmt = new NonTerminal("PolygonSmt"); + var PolygonPoint = new NonTerminal("PolygonPoint"); + var FillPolygonSmt = new NonTerminal("FillPolygonSmt"); + var ForSmt = new NonTerminal("ForSmt"); + var UdfSmt = new NonTerminal("UdfSmt"); + var UdfInvokeSmt = new NonTerminal("UdfInvokeSmt"); + var IfSmt = new NonTerminal("IfSmt"); + var Else_clause_opt = new NonTerminal("Else_clause_opt"); + var then_clause = new NonTerminal("then_clause"); + + var FormulaExpression = new NonTerminal("FormulaExpression"); + var LiteralExpression = new NonTerminal("LiteralExpression"); + var FormulaPrimary = new NonTerminal("FormulaTerm"); + var UnaryExpression = new NonTerminal("UnaryExpression"); + var VarSmt = new NonTerminal("VarSmt"); + var VarRef = new NonTerminal("VarRef"); + + var RowTemplateSmt = new NonTerminal("RowTemplateSmt"); + var DebugOptionsSmt = new NonTerminal("DebugOptionsSmt"); + #endregion + + #region Formula rules + RegisterOperators(20, "and", "or"); + RegisterOperators(30, "==", "<=", ">=", "<", ">", "<>"); + RegisterOperators(40, "+", "-"); + RegisterOperators(50, "*", "/", "%"); + RegisterOperators(60, "^"); + + FormulaRoot = FormulaExpression; + var CustomFunctionExpression = new NonTerminal("CustomFunctionExpression"); + FormulaExpression.Rule = BinaryExpression | FormulaPrimary; + FormulaPrimary.Rule = LiteralExpression | UnaryExpression | Parenthesized_Expression | CustomFunctionExpression; + + LiteralExpression.Rule = number_literal | VarRef | sstring; + UnaryExpression.Rule = UnOp + FormulaExpression; + Parenthesized_Expression.Rule = lpar + FormulaExpression + rpar; + BinaryExpression.Rule = FormulaExpression + BinOp + FormulaExpression; + + var CallInvokeArgumentslist = new NonTerminal("CallInvokeArgumentslist"); + CallInvokeArgumentslist.Rule = MakePlusRule(CallInvokeArgumentslist, comma, FormulaExpression); + + var CustomFunctionArgs = new NonTerminal("CustomFunctionArgs"); + CustomFunctionExpression.Rule = variableLiteral + CustomFunctionArgs; + var CustomFunctionArgsOpt = new NonTerminal("CustomFunctionArgsOpt"); + CustomFunctionArgs.Rule = lpar + CustomFunctionArgsOpt + rpar; + CustomFunctionArgsOpt.Rule = Empty | CallInvokeArgumentslist; + + UnOp.Rule = ToTerm("+") | "-"; + VarRef.Rule = "$" + variableLiteral; + BinOp.Rule = ToTerm("+") | "-" | "*" | "/" | "%" | "==" | "<=" | ">=" | "<" | ">" | "<>" | "and" | "or"; + MarkTransient(FormulaExpression, LiteralExpression, BinOp, FormulaPrimary, + Parenthesized_Expression, UnOp); + #endregion + + // set the PROGRAM to be the root node of PDF lines. + Root = PDF; + + // BNF Rules + PDF.Rule = MakeStarRule(PDF, PdfLine); + + // A line can be an empty line, or it's a number followed by a statement list ended by a new-line. + KeyTerm semi = ToTerm(";", "semi"); + semi.ErrorAlias = "';' expected"; + + comma.ErrorAlias = "',' expected"; + semiOpt.Rule = Empty | semi; + + PdfLine.Rule = UdfSmt | DebugOptionsSmt | PdfInstruction; + + PdfInstruction.Rule = PdfPrimaryInstruction + semiOpt; + + PdfPrimaryInstruction.Rule = SetSmt + | RectSmt + | FillRectSmt + | EllipseSmt + | FillEllipseSmt + | TitleSmt + | NewPageSmt + | ViewSizeSmt + | LineSmt + | LineToSmt + | MoveToSmt + | LineTextSmt + | TextSmt + | TableSmt + | ImageSmt + | PieSmt + | PolygonSmt + | FillPolygonSmt + | FillPieSmt + | ForSmt + | UdfInvokeSmt + | IfSmt + | RowTemplateSmt + ; + + #region basics rules + RectLocation.Rule = PointLocation + comma + PointLocation; + + PointLocation.Rule = FormulaExpression + comma + FormulaExpression; + RectOrPointLocation.Rule = RectLocation | PointLocation; + + #endregion + + SetSmt.Rule = ToInstructionTerm("SET") + SetContent; + SetContent.Rule = PenSmt | BrushSmt | FontSmt | VarSmt | HBrushSmt; + RectSmt.Rule = ToInstructionTerm("RECT") + RectLocation; + FillRectSmt.Rule = ToInstructionTerm("FILLRECT") + RectLocation; + EllipseSmt.Rule = ToInstructionTerm("ELLIPSE") + RectLocation; + FillEllipseSmt.Rule = ToInstructionTerm("FILLELLIPSE") + RectLocation; + + + LineSmt.Rule = ToInstructionTerm("LINE") + RectLocation; + MoveToSmt.Rule = ToInstructionTerm("MOVETO") + PointLocation; + LineToSmt.Rule = ToInstructionTerm("LINETO") + PointLocation; + var stylePenOpt = new NonTerminal("StylePenOpt"); + var stylePen = new NonTerminal("StylePen"); + stylePenOpt.Rule = Empty | stylePen; + stylePen.Rule = ToTerm("solid") | "dash" | "dot" | "dashdot" | "dashdotdot"; + PenSmt.Rule = ToTerm("PEN") + ColorExp + FormulaExpression + stylePenOpt; + BrushSmt.Rule = ToTerm("BRUSH") + ColorExp + BrushType; + //TODO: how to deactivate HBRUSH... + HBrushSmt.Rule = ToTerm("HBRUSH") + ColorExp + BrushType; + //do not use ToTerm, because "FONT" is used in as argument in Table + FontSmt.Rule = new KeyTerm("FONT", "FONT") + Arg("Name") + FormulaExpression + Arg("Size") + FormulaExpression + styleExpr; + + VarSmt.Rule = ToTerm("VAR") + variableLiteral + "=" + FormulaExpression + semi; + + ColorExp.Rule = NamedColor | HexColor; + foreach (var colorName in PdfColors.Names) + { + var name = colorName.ToLowerInvariant(); + if (NamedColor.Rule == null) + { + NamedColor.Rule = ToTerm(name, $"color-{name}"); + } + else + { + NamedColor.Rule |= ToTerm(name, $"color-{name}"); + } + } + + + HexColor.Rule = colorNumber; + styleExpr.Rule = Empty; + foreach (var enumName in Enum.GetNames(typeof(PdfFontStyle))) + { + var styleName = enumName.ToLowerInvariant(); + styleExpr.Rule |= ToTerm(styleName, $"style-{styleName}"); + } + + + //Multiline alignment is implemented by the TerraPDF canvas adapter. + //multiline + TextSmt.Rule = ToInstructionTerm("TEXT") + RectOrPointLocation + OptArg("MaxWidth", FormulaExpression) + Arg("Text") + FormulaExpression; + + TextAlignment.Rule = HAlign + VAlign; + HAlign.Rule = Empty | Arg("HAlign") + HAlignValue; + VAlign.Rule = Empty | Arg("VAlign") + VAlignValue; + HAlignValue.Rule = ToTerm("left") | "right" | "hcenter"; + VAlignValue.Rule = ToTerm("top") | "bottom" | "vcenter"; + + TextOrientation.Rule = Empty | Arg("Orientation") + TextOrientationValue; + TextOrientationValue.Rule = FormulaExpression + | "vertical" + | "horizontal"; + + //simple line + LineTextSmt.Rule = ToInstructionTerm("LINETEXT") + RectOrPointLocation + TextAlignment + TextOrientation + + Arg("Text") + FormulaExpression; + BrushType.Rule = Empty /* | GradientBrush*/; + + PageSize.Rule = Empty; + + var names = Enum.GetNames(typeof(PdfPageSize)); + var firstSize = names.First(); + PageSize.Rule |= ToTerm(firstSize, $"pagesize-{firstSize}"); + foreach (var prop in names.Skip(1)) + { + PageSize.Rule |= ToTerm(prop, $"pagesize-{prop}"); + } + + PageOrientation.Rule = Empty | "portrait" | "landscape"; + NewPageSmt.Rule = ToInstructionTerm("NEWPAGE") + PageSize + PageOrientation; + + TitleSmt.Rule = ToInstructionTerm("TITLE") + MarginArg + HAlign + Arg("Text") + FormulaExpression; + MarginArg.Rule = Empty | Arg("Margin") + FormulaExpression; + + + TableSmt.Rule = ToInstructionTerm("TABLE") + TableLocation + TableContent + ToTerm("ENDTABLE"); + + TableContent.Rule = TableHead + TableRowListOrRowTemplate; + + TableHead.Rule = ToTerm("HEAD") + TableHeadStyle + TableColHeadList + ToTerm("ENDHEAD"); + TableRowList.Rule = MakeStarRule(TableRowList, TableRow); + TableRowListOrRowTemplate.Rule = TableRowList | TableRowTemplate; + TableRowTemplateCount.Rule = FormulaExpression; + TableRowTemplate.Rule = ToInstructionTerm("ROWTEMPLATE") + TableRowTemplateCount + TableColList + ToTerm("ENDROW"); + TableColHeadList.Rule = MakeStarRule(TableColHeadList, TableHeadCol); + TableHeadCol.Rule = ToTerm("COL") + TableColWidth + TableColFont + TableColColors + sstring + semi; + //desiredWidth and maxWidth + TableColWidth.Rule = Arg("Width") + NumberOrAuto + Arg("MaxWidth") + NumberOrAuto; + TableColList.Rule = MakeStarRule(TableColList, TableCol); + TableRow.Rule = ToTerm("ROW") + TableRowStyle + TableColList + ToTerm("ENDROW"); + TableCol.Rule = ToTerm("COL") + FormulaExpression + semi; + TableLocation.Rule = PointLocation /*+ "," + PointAutoLocation*/; + PointAutoLocation.Rule = NumberOrAuto + "," + NumberOrAuto; + NumberOrAuto.Rule = FormulaExpression | "auto"; + TableHeadStyle.Rule = Empty | ColorExp; + TableColFont.Rule = Empty | Arg("FONT") + sstring + "," + number_literal + "," + styleExpr; + TableColColors.Rule = Empty | ColorExp + ColorExp; + TableRowStyle.Rule = Empty | FormulaExpression; + + ViewSizeSmt.Rule = ToInstructionTerm("VIEWSIZE") + PointLocation; + + PixelOrPoint.Rule = ToTerm("pixel") | "point"; + CropExp.Rule = Empty | "crop" | "fit"; + ImageLocation.Rule = PointLocation | RectLocation + PixelOrPoint + CropExp; + //PreferShift because + var ImageRawOrSource = new NonTerminal("ImageRawOrSource"); + ImageSmt.Rule = ToInstructionTerm("IMAGE") + ImageLocation + ImageRawOrSource + FormulaExpression; + ImageRawOrSource.Rule = Arg("Source") | Arg("Data"); + + PieSmt.Rule = ToInstructionTerm("PIE") + RectLocation + Arg("Start") + FormulaExpression + Arg("Angle") + FormulaExpression; + PolygonSmt.Rule = ToInstructionTerm("POLYGON") + PointLocation + comma + PointLocation + comma + PolygonPoint; + PolygonPoint.Rule = MakePlusRule(PolygonPoint, comma, PointLocation); + FillPieSmt.Rule = ToInstructionTerm("FILLPIE") + RectLocation + Arg("Start") + FormulaExpression + Arg("Angle") + FormulaExpression; + FillPolygonSmt.Rule = ToInstructionTerm("FILLPOLYGON") + PointLocation + comma + PointLocation + comma + PolygonPoint; + + var EmbbededSmtList = new NonTerminal("EmbbededSmtList"); + var ForBlock = new NonTerminal("ForBlock"); + ForSmt.Rule = ToInstructionTerm("FOR") + variableLiteral + "=" + FormulaExpression + "TO" + FormulaExpression + ForBlock; + var embbededSmtListOpt = new NonTerminal("EmbbededSmtListOpt"); + this.EmbbededSmtListOpt = embbededSmtListOpt; + ForBlock.Rule = ToTerm("DO") + embbededSmtListOpt + "ENDFOR"; + embbededSmtListOpt.Rule = Empty + EmbbededSmtList; + EmbbededSmtList.Rule = MakePlusRule(EmbbededSmtList, null, PdfInstruction); + + var UdfArgumentslistOpt = new NonTerminal("UdfArgumentslistOpt"); + var UdfArguments = new NonTerminal("UdfArguments"); + var UdfArgumentslist = new NonTerminal("UdfArgumentslist"); + var UdfBlock = new NonTerminal("UdfBlock"); + UdfArguments.Rule = lpar + UdfArgumentslistOpt + rpar; + UdfArgumentslistOpt.Rule = Empty | UdfArgumentslist; + UdfSmt.Rule = ToTerm("UDF") + variableLiteral + PreferShiftHere() + UdfArguments + UdfBlock; + UdfArgumentslist.Rule = MakePlusRule(UdfArgumentslist, comma, variableLiteral); + UdfBlock.Rule = embbededSmtListOpt + "ENDUDF"; + + + var UdfInvokeArguments = new NonTerminal("UdfInvokeArguments"); + var UdfInvokeArgumentslistOpt = new NonTerminal("UdfInvokeArgumentslistOpt"); + UdfInvokeSmt.Rule = ToInstructionTerm("CALL") + variableLiteral + PreferShiftHere() + UdfInvokeArguments; + UdfInvokeArguments.Rule = lpar + UdfInvokeArgumentslistOpt + rpar; + UdfInvokeArgumentslistOpt.Rule = Empty | CallInvokeArgumentslist; + + IfSmt.Rule = ToInstructionTerm("IF") + FormulaExpression + then_clause + Else_clause_opt + "ENDIF"; + then_clause.Rule = "THEN" + embbededSmtListOpt; + Else_clause_opt.Rule = Empty | PreferShiftHere() + "ELSE" + embbededSmtListOpt; + + ConfigureRowTemplateSmt(RowTemplateSmt); + + var debugOption = new IdentifierTerminal("debugOption"); + var debugOptionList = new NonTerminal("debugOptionList"); + debugOptionList.Rule = MakePlusRule(debugOptionList, comma, debugOption); + DebugOptionsSmt.Rule = "DEBUGOPTIONS" + debugOptionList + semi; + + + RegisterBracePair("(", ")"); + + MarkPunctuation(";", ",", "(", ")", "TABLE", "ENDTABLE", "HEAD", "ENDHEAD", "ROW", "ROWTEMPLATE ", "ENDROW", "ENDFOR", "UDF", "ENDUDF", + "IF", "THEN", "ELSE", "ENDIF", "ROWTEMPLATE", "ENDROWTEMPLATE"); + RegisterBracePair("(", ")"); + MarkTransient(PdfLine, PdfPrimaryInstruction, SetContent, NumberOrAuto, + styleExpr, semiOpt, PixelOrPoint, HAlignValue, TextOrientationValue, VAlignValue, + embbededSmtListOpt, + UdfArguments, UdfArgumentslistOpt, + UdfInvokeArguments, UdfInvokeArgumentslistOpt, stylePenOpt, + CustomFunctionArgs, CustomFunctionArgsOpt, TableRowTemplateCount); + + this.AddTermsReportGroup("punctuation", comma); + this.AddToNoReportGroup("(", "++", "--"); + this.AddOperatorReportGroup("operator"); + this.AddTermsReportGroup("constant", number_literal, sstring); + this.AddTermsReportGroup("constant", "auto"); + this.AddToNoReportGroup(semi); + } + + void ConfigureRowTemplateSmt(NonTerminal rowTemplateSmt) + { + /* + ROWTEMPLATE Count=getGlobalCommentsCount() + LINETEXT 50, 100 HAlign=center VAlign=bottom Text=getGlobalCommentDate($ROWINDEX); + + SET VAR GINDEX=$ROWINDEX; + ROWTEMPLATE Count=getCommentsCount($GINDEX) + LINETEXT 150, 100 HAlign=center VAlign=bottom Text=getCommentDate($GINDEX,$ROWINDEX); + ENDROWTEMPLATE + ENDROWTEMPLATE + */ + var rowTemplateContent = new NonTerminal("RowTemplateBlock"); + rowTemplateSmt.Rule = "ROWTEMPLATE" + Arg("Count") + FormulaRoot + Arg("Y") + FormulaRoot + + OptArg("Name", FormulaRoot) + + OptArg("BorderSize", FormulaRoot) + + OptArg("NewPageTopMargin", FormulaRoot) + //+ OptArg("Splitable", FormulaRoot) + + rowTemplateContent; + rowTemplateContent.Rule = EmbbededSmtListOpt + "ENDROWTEMPLATE"; + } + + KeyTerm ToInstructionTerm(string name) + { + var result = ToTerm(name); + AddTermsReportGroup("instruction", result); + return result; + } + + /// + /// for argument in grammar + /// + /// + /// + private BnfExpression Arg(string name) + { + var term = ToTerm(name); + term.ErrorAlias = $"Missing argument '{name}=...'"; + var result = term + PreferShiftHere() + "="; + //result.ErrorAlias = $"argument missing {name}"; + return result; + } + + BnfExpression OptArg(string name, BnfExpression bnfExpression) + { + var term = new NonTerminal($"Opt-{name}") + { + Rule = Empty | name + PreferShiftHere() + "=" + bnfExpression + }; + return term; + } + + public override string ConstructParserErrorMessage(ParsingContext context, StringSet expectedTerms) + { + if (context.CurrentParserState.ExpectedTerminals.Count > 0 && expectedTerms.Count == 0) + { + expectedTerms.AddRange(TerminalToString(context.CurrentParserState.ExpectedTerminals)); + return base.ConstructParserErrorMessage(context, expectedTerms); + } + return base.ConstructParserErrorMessage(context, expectedTerms); + } + + private string[] TerminalToString(TerminalSet expectedTerminals) + { + var l = new List(); + foreach (var item in expectedTerminals) + { + l.Add(item switch { + KeyTerm k => k.Text, + _ => item.ToString(), + }); + } + return l.ToArray(); + } + + public override void ReportParseError(ParsingContext context) + { + base.ReportParseError(context); + } + } +} \ No newline at end of file From 1bfcb59f5bec11f9ef35d735fef53e8af092fbbc Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Fri, 18 Sep 2026 23:32:34 +0200 Subject: [PATCH 06/15] some fixes --- PdfSharpDslConsole/Program.cs | 47 ++++-- PdfSharpDslCore/Drawing/PdfDocumentDrawer.cs | 143 +++++++++++++++---- pdfsharpdslTests/PdfDocumentDrawerTests.cs | 124 ++++++++++++++++ pdfsharpdslTests/VisitorTests.cs | 19 +++ 4 files changed, 296 insertions(+), 37 deletions(-) diff --git a/PdfSharpDslConsole/Program.cs b/PdfSharpDslConsole/Program.cs index 97d392f..bbdcfec 100644 --- a/PdfSharpDslConsole/Program.cs +++ b/PdfSharpDslConsole/Program.cs @@ -87,11 +87,12 @@ //var fileName = "pdfsharp-newpage.ipdf"; //var fileName = "pdfsharp.ipdf"; //var fileName = "sample1.ipdf"; -var fileName = "demo.ipdf"; -if (args.Length > 0) -{ - fileName = args[0]; -} +var fileName = args.Length > 0 ? args[0] : "demo.ipdf"; +var outputFile = Path.GetFullPath("helloworld.pdf"); +if (args.Length == 0) + Directory.SetCurrentDirectory(AppContext.BaseDirectory); +if (!Path.IsPathRooted(fileName) && !File.Exists(fileName)) + fileName = Path.Combine(AppContext.BaseDirectory, fileName); var parsingResult = parser.Parse(File.ReadAllText(fileName)); @@ -107,6 +108,7 @@ } else { + RegisterSystemFonts(); foreach (var font in LocalFontNames().Zip(LocalFontFiles())) { FontFamily.Register(font.First, font.Second); @@ -126,7 +128,8 @@ visitor.RegisterFormulaFunction("GETCOMMENTAUTHOR", getCommentAuthor); visitor.Draw(drawer, parsingResult); - drawer.PublishPdf("helloworld.pdf"); + drawer.PublishPdf(outputFile); + Console.WriteLine($"PDF generated: {outputFile}"); //var a = new PDfDsl.pdfsharp(); //a.WritePdf(drawer); @@ -134,12 +137,12 @@ IEnumerable LocalFontFiles() { - yield return @"Fonts/AlexBrush-Regular.ttf"; - yield return @"Fonts/Just-Signature.ttf"; - yield return @"Fonts/Inspiration-Regular.ttf"; - yield return @"Fonts/Quirlycues.ttf"; - yield return @"Fonts/Rabiohead.ttf"; - yield return @"Fonts/SCRIPTIN.ttf"; + yield return Path.Combine(AppContext.BaseDirectory, "Fonts", "AlexBrush-Regular.ttf"); + yield return Path.Combine(AppContext.BaseDirectory, "Fonts", "Just-Signature.ttf"); + yield return Path.Combine(AppContext.BaseDirectory, "Fonts", "Inspiration-Regular.ttf"); + yield return Path.Combine(AppContext.BaseDirectory, "Fonts", "Quirlycues.ttf"); + yield return Path.Combine(AppContext.BaseDirectory, "Fonts", "Rabiohead.ttf"); + yield return Path.Combine(AppContext.BaseDirectory, "Fonts", "SCRIPTIN.ttf"); } IEnumerable LocalFontNames() @@ -152,6 +155,26 @@ IEnumerable LocalFontNames() yield return "Scriptina"; } +void RegisterSystemFonts() +{ + var fontsDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Fonts); + RegisterFontVariant("Arial", "arial.ttf", fontsDirectory); + RegisterFontVariant("Arial", "arialbd.ttf", fontsDirectory, bold: true); + RegisterFontVariant("Arial", "ariali.ttf", fontsDirectory, italic: true); + RegisterFontVariant("Arial", "arialbi.ttf", fontsDirectory, bold: true, italic: true); + RegisterFontVariant("Consolas", "consola.ttf", fontsDirectory); + RegisterFontVariant("Consolas", "consolab.ttf", fontsDirectory, bold: true); + RegisterFontVariant("Consolas", "consolai.ttf", fontsDirectory, italic: true); + RegisterFontVariant("Consolas", "consolaz.ttf", fontsDirectory, bold: true, italic: true); +} + +void RegisterFontVariant(string familyName, string fileName, string directory, bool bold = false, bool italic = false) +{ + var path = Path.Combine(directory, fileName); + if (File.Exists(path)) + FontFamily.Register(familyName, path, bold, italic); +} + object GetFontNameByIndex(object[] arguments) { var index = (int)arguments[0]; diff --git a/PdfSharpDslCore/Drawing/PdfDocumentDrawer.cs b/PdfSharpDslCore/Drawing/PdfDocumentDrawer.cs index c4c692c..0035bf7 100644 --- a/PdfSharpDslCore/Drawing/PdfDocumentDrawer.cs +++ b/PdfSharpDslCore/Drawing/PdfDocumentDrawer.cs @@ -131,11 +131,29 @@ public void DrawLine(double x, double y, double x1, double y1) private void InternalDrawLine(PdfPen pen, double x, double y, double x1, double y1) { - AddCommand(canvas => canvas.Line(x, y, x1, y1, pen.Color.Hex, pen.Width, pen.Color.Opacity)); + AddCommand(canvas => DrawStyledLine(canvas, pen, x, y, x1, y1)); _drawingCtx.PushInstruction(offset => InternalDrawLine(pen, x, y + offset, x1, y1 + offset), new PdfRect(new PdfPoint(x, y), new PdfPoint(x1, y1))); } + private static void DrawStyledLine(VectorCanvas canvas, PdfPen pen, double x, double y, double x1, double y1) + { + canvas.Line(x, y, x1, y1, pen.Color.Hex, pen.Width, pen.Color.Opacity, GetDashPattern(pen)); + } + + private static double[]? GetDashPattern(PdfPen pen) + { + var width = Math.Max(pen.Width, 0.1); + return pen.DashStyle switch + { + PdfDashStyle.Dash => new[] { 4 * width, 3 * width }, + PdfDashStyle.Dot => new[] { width, 2 * width }, + PdfDashStyle.DashDot => new[] { 4 * width, 3 * width, width, 3 * width }, + PdfDashStyle.DashDotDot => new[] { 4 * width, 3 * width, width, 2 * width, width, 2 * width }, + _ => null, + }; + } + public void DrawRect(double x, double y, double w, double h, bool isFilled) { var page = CurrentPage; @@ -145,10 +163,11 @@ public void DrawRect(double x, double y, double w, double h, bool isFilled) private void InternalDrawRect(PdfPen pen, PdfBrush brush, double x, double y, double w, double h, bool isFilled) { + var dashPattern = GetDashPattern(pen); AddCommand(canvas => { if (isFilled) canvas.FillRect(x, y, w, h, brush.Color.Hex, brush.Color.Opacity); - canvas.StrokeRect(x, y, w, h, pen.Color.Hex, pen.Width, pen.Color.Opacity); + canvas.StrokeRect(x, y, w, h, pen.Color.Hex, pen.Width, pen.Color.Opacity, dashPattern); }); _drawingCtx.PushInstruction(offset => InternalDrawRect(pen, brush, x, y + offset, w, h, isFilled), new PdfRect(x, y, w, h)); @@ -188,19 +207,28 @@ public void DrawLineText(string text, double x, double y, double? w, double? h, var page = CurrentPage; (x, y, w, h) = DrawingHelper.CoordRectToPage(page.Width, page.Height, x, y, w, h); InternalDrawText(text, ScaleX(x, page), ScaleY(y, page), w.HasValue ? ScaleX(w.Value, page) : null, - h.HasValue ? ScaleY(h.Value, page) : null, hAlign, vAlign, CurrentFont, CurrentBrush, HighlightBrush); + h.HasValue ? ScaleY(h.Value, page) : null, hAlign, vAlign, CurrentFont, CurrentBrush, HighlightBrush, + textOrientation?.Angle ?? textOrientation?.Orientation switch + { + TextOrientationEnum.Vertical => 90, + TextOrientationEnum.HorizontalInvert => 180, + TextOrientationEnum.VerticalInvert => 270, + _ => 0, + }); } private void InternalDrawText(string text, double x, double y, double? w, double? h, PdfHorizontalAlignment hAlign, - PdfVerticalAlignment vAlign, PdfFont font, PdfBrush brush, PdfBrush? highlight) + PdfVerticalAlignment vAlign, PdfFont font, PdfBrush brush, PdfBrush? highlight, double angle = 0) { var lines = WrapText(text, w, font); var lineHeight = font.Size * 1.2; var measuredWidth = lines.Count == 0 ? 0 : lines.Max(line => MeasureText(line, font)); var measuredHeight = lines.Count * lineHeight; var rect = new PdfRect(x, y, w ?? measuredWidth, h ?? measuredHeight); - var textRect = DrawingHelper.RectFromStringFormat(rect, - new PdfSize(Math.Min(measuredWidth, rect.Width), Math.Min(measuredHeight, rect.Height)), hAlign, vAlign); + var textSize = new PdfSize(Math.Min(measuredWidth, rect.Width), Math.Min(measuredHeight, rect.Height)); + var textRect = w.HasValue + ? DrawingHelper.RectFromStringFormat(rect, textSize, hAlign, vAlign) + : DrawingHelper.RectFromStringFormat(x, y, textSize, hAlign, vAlign); AddCommand(canvas => { @@ -212,18 +240,26 @@ private void InternalDrawText(string text, double x, double y, double? w, double var line = lines[index]; if (string.IsNullOrWhiteSpace(line)) continue; var lineWidth = MeasureText(line, font); - var lineX = hAlign switch - { - PdfHorizontalAlignment.Center => rect.X + (rect.Width - lineWidth) / 2, - PdfHorizontalAlignment.Far => rect.Right - lineWidth, - _ => rect.X, - }; + var lineX = w.HasValue + ? hAlign switch + { + PdfHorizontalAlignment.Center => rect.X + (rect.Width - lineWidth) / 2, + PdfHorizontalAlignment.Far => rect.Right - lineWidth, + _ => rect.X, + } + : hAlign switch + { + PdfHorizontalAlignment.Center => x - lineWidth / 2, + PdfHorizontalAlignment.Far => x - lineWidth, + _ => x, + }; canvas.Text(line, lineX, textRect.Y + font.Size + index * lineHeight, brush.Color.Hex, font.Size, - font.FamilyName, font.Style.HasFlag(PdfFontStyle.Bold), font.Style.HasFlag(PdfFontStyle.Italic), brush.Color.Opacity); + font.FamilyName, font.Style.HasFlag(PdfFontStyle.Bold), font.Style.HasFlag(PdfFontStyle.Italic), brush.Color.Opacity, + angle); } }); - _drawingCtx.PushInstruction(offset => InternalDrawText(text, x, y + offset, w, h, hAlign, vAlign, font, brush, highlight), + _drawingCtx.PushInstruction(offset => InternalDrawText(text, x, y + offset, w, h, hAlign, vAlign, font, brush, highlight, angle), textRect, instrName: $"DrawText({text})"); } @@ -244,13 +280,25 @@ public void DrawTable(double x, double y, TableDefinition table) for (var index = 0; index < table.Columns.Count; index++) { var column = table.Columns[index]; - column.DesiredWidth ??= Math.Min(MeasureText(column.ColumnHeaderName, fonts[index]) + margins.Left + margins.Right, + var contentWidth = table.Rows + .Select(row => MeasureCellWidth(index < row.Data.Length ? row.Data[index] : string.Empty, fonts[index])) + .DefaultIfEmpty(0) + .Max(); + var desiredWidth = Math.Max(MeasureCellWidth(column.ColumnHeaderName, fonts[index]), contentWidth) + + margins.Left + margins.Right; + column.DesiredWidth ??= Math.Min(desiredWidth, table.ColMaxWidth(index, availableWidth)); table.HeaderHeight ??= fonts[index].Size * 1.2 + margins.Top + margins.Bottom; } foreach (var row in table.Rows) - row.DesiredHeight ??= fonts.Select(font => font.Size * 1.2 + margins.Top + margins.Bottom).Max(); + { + var contentHeight = fonts.Select((font, index) => + MeasureCellLineCount(index < row.Data.Length ? row.Data[index] : string.Empty, + table.Columns[index].DrawWidth - margins.Left - margins.Right, font) + * font.Size * 1.2 + margins.Top + margins.Bottom).Max(); + row.DesiredHeight ??= contentHeight; + } if (y + (table.HeaderHeight ?? 0) > PageHeight) { NewPage(); y = 1; } var offsetY = 0d; @@ -319,21 +367,55 @@ public void LineTo(double x, double y) { var page = CurrentPage; var endPoint = new PdfPoint(x, y); - AddCommand(canvas => canvas.Line(ScaleX(_currentPoint.X, page), ScaleY(_currentPoint.Y, page), ScaleX(endPoint.X, page), ScaleY(endPoint.Y, page), - CurrentPen.Color.Hex, CurrentPen.Width, CurrentPen.Color.Opacity)); - _drawingCtx.PushInstruction(offset => LineTo(endPoint.X, endPoint.Y), new PdfRect(_currentPoint, endPoint)); + InternalDrawLine(CurrentPen, ScaleX(_currentPoint.X, page), ScaleY(_currentPoint.Y, page), + ScaleX(endPoint.X, page), ScaleY(endPoint.Y, page)); _currentPoint = endPoint; } public void DrawImage(PdfImage image, double x, double y, double? w, double? h, bool sizeInPixel, bool cropImage) { - _logger?.WriteDebug(this, "TerraPDF does not support absolute image placement; image skipped."); + ArgumentNullException.ThrowIfNull(image); + var page = CurrentPage; + (x, y, w, h) = DrawingHelper.CoordRectToPage(page.Width, page.Height, x, y, w, h); + var data = image.Data.ToArray(); + var naturalSize = VectorCanvas.GetImageSizeInPoints(data); + var width = w.HasValue ? (sizeInPixel ? w.Value * 72d / 96d : w.Value) : naturalSize.Width; + var height = h.HasValue ? (sizeInPixel ? h.Value * 72d / 96d : h.Value) : naturalSize.Height; + if (width <= 0 || height <= 0) + throw new ArgumentOutOfRangeException(nameof(w), "Image dimensions must be positive."); + + var fit = cropImage ? ImageFit.CropTopLeft : ImageFit.Stretch; + AddCommand(canvas => canvas.Image(data, ScaleX(x, page), ScaleY(y, page), + ScaleX(width, page), ScaleY(height, page), fit)); + _drawingCtx.PushInstruction(offset => DrawImage(image, x, y + offset, w, h, sizeInPixel, cropImage), + new PdfRect(x, y, width, height), instrName: "DrawImage"); } public void DrawPie(double x, double y, double? w, double? h, double startAngle, double sweepAngle, bool isFilled) { var page = CurrentPage; - InternalDrawEllipse(CurrentPen, CurrentBrush, ScaleX(x, page), ScaleY(y, page), ScaleX(w ?? 0, page), ScaleY(h ?? 0, page), isFilled); + var width = w ?? 0; + var height = h ?? 0; + if (width <= 0 || height <= 0) + throw new ArgumentOutOfRangeException(nameof(w), "Pie dimensions must be positive."); + InternalDrawPie(CurrentPen, CurrentBrush, ScaleX(x, page), ScaleY(y, page), + ScaleX(width, page), ScaleY(height, page), startAngle, sweepAngle, isFilled); + } + + private void InternalDrawPie(PdfPen pen, PdfBrush brush, double x, double y, double width, double height, + double startAngle, double sweepAngle, bool isFilled) + { + AddCommand(canvas => + { + if (isFilled) + canvas.DrawPie(x, y, width, height, startAngle, sweepAngle, + brush.Color.Hex, pen.Color.Hex, pen.Width, brush.Color.Opacity); + else + canvas.StrokePie(x, y, width, height, startAngle, sweepAngle, + pen.Color.Hex, pen.Width, pen.Color.Opacity); + }); + _drawingCtx.PushInstruction(offset => InternalDrawPie(pen, brush, x, y + offset, width, height, startAngle, sweepAngle, isFilled), + new PdfRect(x, y, width, height), instrName: "DrawPie"); } public void DrawPolygon(IEnumerable points, bool isFilled) @@ -341,13 +423,18 @@ public void DrawPolygon(IEnumerable points, bool isFilled) var page = CurrentPage; var transformed = points.Select(point => new PdfPoint(ScaleX(point.X, page), ScaleY(point.Y, page))).ToArray(); if (transformed.Length < 3) throw new ArgumentException("A polygon requires at least three points.", nameof(points)); - var tuples = transformed.Select(point => (point.X, point.Y)).ToArray(); + InternalDrawPolygon(CurrentPen, CurrentBrush, transformed, isFilled); + } + + private void InternalDrawPolygon(PdfPen pen, PdfBrush brush, PdfPoint[] points, bool isFilled) + { + var tuples = points.Select(point => (point.X, point.Y)).ToArray(); AddCommand(canvas => canvas.Path(path => { - path.Polygon(tuples).Stroke(CurrentPen.Color.Hex, CurrentPen.Width).Opacity(CurrentPen.Color.Opacity); - if (isFilled) path.Fill(CurrentBrush.Color.Hex).Opacity(CurrentBrush.Color.Opacity); + path.Polygon(tuples).Stroke(pen.Color.Hex, pen.Width).Opacity(pen.Color.Opacity); + if (isFilled) path.Fill(brush.Color.Hex).Opacity(brush.Color.Opacity); })); - _drawingCtx.PushInstruction(offset => DrawPolygon(transformed.Select(point => point.OffsetY(offset)), isFilled), transformed); + _drawingCtx.PushInstruction(offset => InternalDrawPolygon(pen, brush, points.Select(point => point.OffsetY(offset)).ToArray(), isFilled), points); } public void BeginDrawRowTemplate(string name, int index, double offsetY, double newPageTopMargin) @@ -390,6 +477,12 @@ private static double MeasureText(string text, PdfFont font) return VectorCanvas.MeasureTextWidth(text, font.Size, font.FamilyName, font.Style.HasFlag(PdfFontStyle.Bold), font.Style.HasFlag(PdfFontStyle.Italic)); } + private static double MeasureCellWidth(string text, PdfFont font) => + text.Replace("\r\n", "\n").Split('\n').Select(line => MeasureText(line, font)).DefaultIfEmpty(0).Max(); + + private static int MeasureCellLineCount(string text, double width, PdfFont font) => + WrapText(text, width, font).Count; + private static List WrapText(string text, double? maxWidth, PdfFont font) { var lines = new List(); diff --git a/pdfsharpdslTests/PdfDocumentDrawerTests.cs b/pdfsharpdslTests/PdfDocumentDrawerTests.cs index bcc760d..cfe267f 100644 --- a/pdfsharpdslTests/PdfDocumentDrawerTests.cs +++ b/pdfsharpdslTests/PdfDocumentDrawerTests.cs @@ -4,6 +4,130 @@ namespace pdfsharpdslTests { public class PdfDocumentDrawerTests { + [Fact] + public void DslOrientationsProduceRotatedTextMatrices() + { + var parser = new Irony.Parsing.Parser(new PdfSharpDslCore.Parser.PdfGrammar()); + var tree = parser.Parse( + "LINETEXT 220,235 HAlign=left VAlign=vcenter Orientation=vertical Text=\"vertical\";" + + "LINETEXT 300,235 HAlign=left VAlign=vcenter Orientation=30 Text=\"30 degree rotation\";"); + Assert.False(tree.HasErrors()); + using var drawer = new PdfDocumentDrawer(); + new PdfSharpDslCore.Parser.PdfDrawerVisitor().Draw(drawer, tree); + var content = ReadContent(drawer.PublishPdf()); + + Assert.Matches(@"0\.00 -1\.00 1\.00 0\.00 220\.00 [\d.]+ Tm\n\(vertical\) Tj", content); + Assert.Matches(@"0\.87 -0\.50 0\.50 0\.87 300\.00 [\d.]+ Tm\n\(30 degree rotation\) Tj", content); + } + + [Fact] + public void PolygonsKeepStylesFromTheirDrawingInstructions() + { + var parser = new Irony.Parsing.Parser(new PdfSharpDslCore.Parser.PdfGrammar()); + var tree = parser.Parse( + "SET PEN darkslategray 2 solid;POLYGON 10,20,30,20,20,40;" + + "SET BRUSH lightseagreen;FILLPOLYGON 50,20,70,20,60,40;" + + "SET PEN crimson 0.5 solid;SET BRUSH black;"); + Assert.False(tree.HasErrors()); + using var drawer = new PdfDocumentDrawer(); + new PdfSharpDslCore.Parser.PdfDrawerVisitor().Draw(drawer, tree); + + var content = ReadContent(drawer.PublishPdf()); + + Assert.Contains("2.00 w\n0.1843 0.3098 0.3098 RG\n", content); + Assert.Contains("0.1255 0.6980 0.6667 rg\n", content); + Assert.Contains("h\nS\n", content); + Assert.Contains("h\nB\n", content); + Assert.DoesNotContain("0.0000 0.0000 0.0000 rg\n", content); + } + + [Fact] + public void LineToKeepsEachStartPointAndPenAtRecordingTime() + { + using var drawer = new PdfDocumentDrawer(); + drawer.CurrentPen = new PdfPen(new PdfColor(255, 128, 0, 128), 3); + drawer.MoveTo(10, 20); + drawer.LineTo(30, 40); + drawer.LineTo(50, 20); + drawer.CurrentPen = new PdfPen(PdfColor.Black, 1); + drawer.MoveTo(100, 100); + + var content = ReadContent(drawer.PublishPdf()); + + Assert.Contains("0.5020 0.0000 0.5020 RG\n", content); + Assert.Contains("3.00 w\n", content); + Assert.Contains("10.00 821.89 m\n30.00 801.89 l\n", content); + Assert.Contains("30.00 801.89 m\n50.00 821.89 l\n", content); + } + + [Fact] + public void DashStylesUseNativePdfPatternAndDoNotLeak() + { + var parser = new Irony.Parsing.Parser(new PdfSharpDslCore.Parser.PdfGrammar()); + var tree = parser.Parse( + "SET PEN black 2 dash;LINE 10,20,110,20;" + + "SET PEN black 2 solid;LINE 10,40,110,40;"); + Assert.False(tree.HasErrors()); + using var drawer = new PdfDocumentDrawer(); + new PdfSharpDslCore.Parser.PdfDrawerVisitor().Draw(drawer, tree); + + var content = ReadContent(drawer.PublishPdf()); + + Assert.Contains("[8.00 6.00] 0.00 d\n", content); + Assert.Contains("q\n[8.00 6.00] 0.00 d\n", content); + Assert.Contains("Q\n2.00 w\n0.0000 0.0000 0.0000 RG\n10.00 801.89 m\n110.00 801.89 l\nS\n", content); + } + + [Fact] + public void DashStylesApplyToRectangleOutlines() + { + var parser = new Irony.Parsing.Parser(new PdfSharpDslCore.Parser.PdfGrammar()); + var tree = parser.Parse("SET PEN crimson 1 dash;RECT 20,40,-20,-55;"); + Assert.False(tree.HasErrors()); + using var drawer = new PdfDocumentDrawer(); + new PdfSharpDslCore.Parser.PdfDrawerVisitor().Draw(drawer, tree); + + var content = ReadContent(drawer.PublishPdf()); + + Assert.Contains("q\n[4.00 3.00] 0.00 d\n", content); + Assert.Contains("re\nS\nQ\n", content); + } + + [Fact] + public void TablesMeasureAutoColumnsAndMultilineRows() + { + using var drawer = new PdfDocumentDrawer(); + var table = new TableDefinition(); + table.Columns.Add(new ColumnDefinition { ColumnHeaderName = "Feature", MaxWidth = 130, Font = new PdfFont("Arial", 9) }); + table.Columns.Add(new ColumnDefinition { ColumnHeaderName = "Syntax", DesiredWidth = 140, MaxWidth = 180, Font = new PdfFont("Arial", 9) }); + table.Columns.Add(new ColumnDefinition { ColumnHeaderName = "Notes", MaxWidth = 240, Font = new PdfFont("Arial", 9) }); + table.Rows.Add(new RowDefinition { Data = ["Automatic sizing", "Width=auto", "Desired width follows content up to MaxWidth"] }); + table.Rows.Add(new RowDefinition { Data = ["Multiline cell", "COL expression", "Line one\r\nLine two\r\nLine three"] }); + + drawer.DrawTable(40, 90, table); + + Assert.True(table.Columns[0].DesiredWidth > 0); + Assert.Equal(140, table.Columns[1].DrawWidth); + Assert.True(table.Columns[2].DesiredWidth > table.Columns[2].ColumnHeaderName.Length); + Assert.True(table.Rows[1].DesiredHeight > table.Rows[0].DesiredHeight); + } + + private static string ReadContent(byte[] pdf) + { + var raw = System.Text.Encoding.Latin1.GetString(pdf); + var content = new System.Text.StringBuilder(); + foreach (System.Text.RegularExpressions.Match match in System.Text.RegularExpressions.Regex.Matches( + raw, @"stream\r?\n(?.*?)\r?\nendstream", System.Text.RegularExpressions.RegexOptions.Singleline)) + { + using var compressed = new MemoryStream(System.Text.Encoding.Latin1.GetBytes(match.Groups["data"].Value)); + using var inflated = new System.IO.Compression.ZLibStream(compressed, System.IO.Compression.CompressionMode.Decompress); + using var reader = new StreamReader(inflated, System.Text.Encoding.Latin1); + content.Append(reader.ReadToEnd()); + } + + return content.ToString(); + } + [Fact] public void NewPageUsesDefaultsAndPersistsExplicitSettings() { diff --git a/pdfsharpdslTests/VisitorTests.cs b/pdfsharpdslTests/VisitorTests.cs index b22acf6..2e01bb2 100644 --- a/pdfsharpdslTests/VisitorTests.cs +++ b/pdfsharpdslTests/VisitorTests.cs @@ -116,6 +116,25 @@ public void DrawExecutesViewSizeTextWidthAndDebugOptions() drawer.Object.DebugOptions); } + [Theory] + [InlineData("", TextOrientationEnum.Horizontal, null)] + [InlineData("Orientation=horizontal", TextOrientationEnum.Horizontal, null)] + [InlineData("Orientation=vertical", TextOrientationEnum.Vertical, null)] + [InlineData("Orientation=30", TextOrientationEnum.Horizontal, 30.0)] + [InlineData("Orientation=(-15*2)", TextOrientationEnum.Horizontal, -30.0)] + public void LineTextPreservesOrientation(string orientation, TextOrientationEnum expectedMode, double? expectedAngle) + { + var tree = ParseText($"LINETEXT 220,235 HAlign=left VAlign=vcenter {orientation} Text=\"label\";"); + Assert.False(tree.HasErrors()); + var drawer = new Mock(); + + new PdfDrawerVisitor().Draw(drawer.Object, tree); + + drawer.Verify(target => target.DrawLineText("label", 220, 235, null, null, + PdfHorizontalAlignment.Near, PdfVerticalAlignment.Center, + It.Is(value => value.Orientation == expectedMode && value.Angle == expectedAngle)), Times.Once); + } + [Fact] public void DrawResolvesPageSystemVariables() { From ddb67b38c73964fe03be217fcc668185edd81214 Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Fri, 18 Sep 2026 23:44:08 +0200 Subject: [PATCH 07/15] more pages in demo --- PdfSharpDslConsole/demo.ipdf | 383 ++++++++++++++++++++++++++++++++++- 1 file changed, 382 insertions(+), 1 deletion(-) diff --git a/PdfSharpDslConsole/demo.ipdf b/PdfSharpDslConsole/demo.ipdf index 4db5d8b..e2180b9 100644 --- a/PdfSharpDslConsole/demo.ipdf +++ b/PdfSharpDslConsole/demo.ipdf @@ -135,6 +135,264 @@ RECT 20,440,-20,-55; SET BRUSH crimson; LINETEXT 30,450 Text="Negative width and height use the remaining page area"; +# ----------------------------------------------------------------------------- +# Page 3: concrete charts built from the drawing primitives +# ----------------------------------------------------------------------------- +NEWPAGE A4 landscape; +CALL SECTION("Concrete charts from drawing primitives"); +SET FONT Name="Arial" Size=9 regular; +SET BRUSH black; + +# Bar chart: monthly sales in thousands. +LINETEXT 45,82 Text="Monthly sales (kEUR)"; +SET PEN lightgray 0.5 dash; +LINE 70,145,390,145; +LINE 70,195,390,195; +LINE 70,245,390,245; +LINE 70,295,390,295; +SET PEN darkslategray 1 solid; +LINE 70,320,390,320; +LINE 70,120,70,320; +SET BRUSH steelblue; +FILLRECT 92,235,32,85; +FILLRECT 142,200,32,120; +FILLRECT 192,220,32,100; +FILLRECT 242,160,32,160; +FILLRECT 292,180,32,140; +FILLRECT 342,135,32,185; +SET BRUSH black; +LINETEXT 96,330 Text="Jan"; +LINETEXT 146,330 Text="Feb"; +LINETEXT 196,330 Text="Mar"; +LINETEXT 246,330 Text="Apr"; +LINETEXT 296,330 Text="May"; +LINETEXT 346,330 Text="Jun"; +LINETEXT 52,320 HAlign=right Text="0"; +LINETEXT 52,270 HAlign=right Text="20"; +LINETEXT 52,220 HAlign=right Text="40"; +LINETEXT 52,170 HAlign=right Text="60"; +LINETEXT 52,120 HAlign=right Text="80"; +SET FONT Name="Arial" Size=8 regular; +SET BRUSH darkslategray; +LINETEXT 92,225 Text="34"; +LINETEXT 142,190 Text="48"; +LINETEXT 192,210 Text="40"; +LINETEXT 242,150 Text="64"; +LINETEXT 292,170 Text="56"; +LINETEXT 342,125 Text="74"; + +# Line chart: support response time, lower is better. +SET FONT Name="Arial" Size=9 regular; +SET BRUSH black; +LINETEXT 440,82 Text="Average response time (ms)"; +SET PEN lightgray 0.5 dash; +LINE 470,145,755,145; +LINE 470,195,755,195; +LINE 470,245,755,245; +LINE 470,295,755,295; +SET PEN darkslategray 1 solid; +LINE 470,320,755,320; +LINE 470,120,470,320; +SET PEN tomato 2 solid; +LINE 490,170,540,210; +LINE 540,210,590,185; +LINE 590,185,640,250; +LINE 640,250,690,230; +LINE 690,230,740,285; +SET BRUSH tomato; +FILLELLIPSE 485,165,10,10; +FILLELLIPSE 535,205,10,10; +FILLELLIPSE 585,180,10,10; +FILLELLIPSE 635,245,10,10; +FILLELLIPSE 685,225,10,10; +FILLELLIPSE 735,280,10,10; +SET BRUSH black; +LINETEXT 485,330 Text="Jan"; +LINETEXT 535,330 Text="Feb"; +LINETEXT 585,330 Text="Mar"; +LINETEXT 635,330 Text="Apr"; +LINETEXT 685,330 Text="May"; +LINETEXT 735,330 Text="Jun"; +LINETEXT 452,320 HAlign=right Text="0"; +LINETEXT 452,270 HAlign=right Text="100"; +LINETEXT 452,220 HAlign=right Text="200"; +LINETEXT 452,170 HAlign=right Text="300"; +LINETEXT 452,120 HAlign=right Text="400"; +SET FONT Name="Arial" Size=8 regular; +SET BRUSH tomato; +LINETEXT 480,155 Text="300"; +LINETEXT 530,195 Text="220"; +LINETEXT 580,170 Text="270"; +LINETEXT 630,235 Text="140"; +LINETEXT 680,215 Text="180"; +LINETEXT 730,270 Text="70"; + +# Additional series demonstrate the available dash styles. +SET PEN steelblue 1.5 dash; +LINE 490,190,540,160; +LINE 540,160,590,220; +LINE 590,220,640,205; +LINE 640,205,690,260; +LINE 690,260,740,240; +SET PEN darkgreen 1.5 dot; +LINE 490,230,540,250; +LINE 540,250,590,235; +LINE 590,235,640,270; +LINE 640,270,690,255; +LINE 690,255,740,295; +SET PEN purple 1.5 dashdot; +LINE 490,150,540,180; +LINE 540,180,590,165; +LINE 590,165,640,195; +LINE 640,195,690,175; +LINE 690,175,740,215; +SET BRUSH black; +LINETEXT 470,355 Text="API p95"; +LINETEXT 555,355 Text="API p50"; +LINETEXT 640,355 Text="Cache"; +SET PEN tomato 3 solid; +LINE 515,352,535,352; +SET PEN steelblue 3 dash; +LINE 600,352,620,352; +SET PEN darkgreen 3 dot; +LINE 680,352,700,352; +SET PEN purple 3 dashdot; +LINE 750,352,770,352; +LINETEXT 775,355 Text="DB"; + +# Pie chart: traffic sources, using filled sectors. +SET PEN black 1; +SET FONT Name="Arial" Size=9 regular; +SET BRUSH black; +LINETEXT 45,390 Text="Traffic sources"; +SET BRUSH steelblue; +FILLPIE 130,410,130,130 Start=0 Angle=180; +SET BRUSH lightgreen; +FILLPIE 130,410,130,130 Start=180 Angle=108; +SET BRUSH gold; +FILLPIE 130,410,130,130 Start=288 Angle=72; +SET BRUSH black; +LINETEXT 285,435 Text="Direct 50%"; +LINETEXT 285,465 Text="Search 30%"; +LINETEXT 285,495 Text="Referral 20%"; +SET PEN steelblue 8 solid; +LINE 270,430,280,430; +SET PEN lightgreen 8 solid; +LINE 270,460,280,460; +SET PEN gold 8 solid; +LINE 270,490,280,490; + +# ----------------------------------------------------------------------------- +# Page 4: a compact business report combining metrics, chart and table +# ----------------------------------------------------------------------------- +NEWPAGE A4 portrait; +CALL SECTION("Monthly business report"); +SET VAR REVENUE=1284; +SET VAR ORDERS=342; +SET VAR SATISFACTION=94; +SET FONT Name="Arial" Size=9 regular; +SET BRUSH black; + +# KPI cards. +SET BRUSH 0xffe8f1fb; +FILLRECT 45,85,155,68; +SET BRUSH 0xffe8f7ee; +FILLRECT 220,85,155,68; +SET BRUSH 0xfffff3d6; +FILLRECT 395,85,155,68; +SET FONT Name="Arial" Size=9 bold; +SET BRUSH darkslategray; +LINETEXT 60,101 Text="REVENUE"; +LINETEXT 235,101 Text="ORDERS"; +LINETEXT 410,101 Text="CUSTOMER SATISFACTION"; +SET FONT Name="Arial" Size=20 bold; +SET BRUSH darkblue; +LINETEXT 60,118 Text=($REVENUE+" kEUR"); +SET BRUSH darkgreen; +LINETEXT 235,118 Text=$ORDERS; +SET BRUSH orange; +LINETEXT 410,118 Text=($SATISFACTION+"%"); + +# Revenue trend. +SET FONT Name="Arial" Size=11 bold; +SET BRUSH black; +LINETEXT 45,190 Text="Revenue trend (kEUR)"; +SET PEN lightgray 0.5 dash; +LINE 70,250,525,250; +LINE 70,300,525,300; +LINE 70,350,525,350; +LINE 70,400,525,400; +SET PEN darkslategray 1 solid; +LINE 70,425,525,425; +LINE 70,225,70,425; +SET PEN steelblue 2.5 solid; +LINE 90,375,160,340; +LINE 160,340,230,355; +LINE 230,355,300,305; +LINE 300,305,370,285; +LINE 370,285,440,250; +LINE 440,250,510,235; +SET BRUSH steelblue; +FILLELLIPSE 85,370,10,10; +FILLELLIPSE 155,335,10,10; +FILLELLIPSE 225,350,10,10; +FILLELLIPSE 295,300,10,10; +FILLELLIPSE 365,280,10,10; +FILLELLIPSE 435,245,10,10; +FILLELLIPSE 505,230,10,10; +SET FONT Name="Arial" Size=8 regular; +SET BRUSH black; +LINETEXT 82,440 Text="Jan"; +LINETEXT 152,440 Text="Feb"; +LINETEXT 222,440 Text="Mar"; +LINETEXT 292,440 Text="Apr"; +LINETEXT 362,440 Text="May"; +LINETEXT 432,440 Text="Jun"; +LINETEXT 52,425 HAlign=right Text="0"; +LINETEXT 52,375 HAlign=right Text="250"; +LINETEXT 52,325 HAlign=right Text="500"; +LINETEXT 52,275 HAlign=right Text="750"; +LINETEXT 52,225 HAlign=right Text="1000"; + +# Summary table. +SET FONT Name="Arial" Size=11 bold; +SET BRUSH black; +LINETEXT 45,490 Text="Channel summary"; +SET PEN steelblue 1; +SET BRUSH 0xffdce8f5; +FILLRECT 45,510,505,30; +SET BRUSH black; +LINETEXT 58,520 Text="Channel"; +LINETEXT 230,520 Text="Revenue"; +LINETEXT 335,520 Text="Target"; +LINETEXT 440,520 Text="Variance"; +SET BRUSH white; +FILLRECT 45,540,505,30; +FILLRECT 45,600,505,30; +SET BRUSH black; +LINETEXT 58,550 Text="Online"; +LINETEXT 230,550 Text="620 kEUR"; +LINETEXT 335,550 Text="580 kEUR"; +LINETEXT 440,550 Text="+40"; +LINETEXT 58,580 Text="Retail"; +LINETEXT 230,580 Text="410 kEUR"; +LINETEXT 335,580 Text="450 kEUR"; +LINETEXT 440,580 Text="-40"; +LINETEXT 58,610 Text="Partners"; +LINETEXT 230,610 Text="254 kEUR"; +LINETEXT 335,610 Text="220 kEUR"; +LINETEXT 440,610 Text="+34"; +SET PEN lightgray 0.5; +LINE 45,540,550,540; +LINE 45,570,550,570; +LINE 45,600,550,600; +LINE 45,630,550,630; +LINE 215,510,215,630; +LINE 320,510,320,630; +LINE 425,510,425,630; +SET BRUSH darkgreen; +LINETEXT 45,675 Text="Positive variance indicates performance above target."; + # ----------------------------------------------------------------------------- # Text, alignment, orientation, highlighting and every font style # ----------------------------------------------------------------------------- @@ -287,18 +545,26 @@ NEWPAGE A4 landscape; CALL SECTION("Images from files and embedded data"); SET FONT Name="Consolas" Size=9 regular; SET BRUSH black; +# Images use 96 DPI: one pixel is 72/96 points. +SET VAR PIXEL_TO_POINT=72/96; LINETEXT 45,82 Text="Natural size from Source"; IMAGE 45,105 Source="./imageTest.jpg"; +SET PEN lightgray 1; +RECT 45,105,200*$PIXEL_TO_POINT,200*$PIXEL_TO_POINT; + LINETEXT 300,82 Text="Point rectangle, fit"; IMAGE 300,105,180,120 point fit Source="./imageTest.jpg"; +RECT 300,105,180,120; LINETEXT 520,82 Text="Pixel rectangle, crop"; IMAGE 520,105,180,120 pixel crop Source="./imageTest.jpg"; +RECT 520,105,180*$PIXEL_TO_POINT,120*$PIXEL_TO_POINT; LINETEXT 45,330 Text="Embedded one-pixel PNG using Data"; IMAGE 45,355,160,100 point fit Data="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="; + # ----------------------------------------------------------------------------- # Host formula functions and custom bundled fonts # ----------------------------------------------------------------------------- @@ -342,4 +608,119 @@ SET FONT Name="Consolas" Size=3 regular; LINE 50,0,50,140; LINE 0,70,100,70; RECT 2,2,96,136; -LINETEXT 50,70 HAlign=hcenter VAlign=vcenter Text="VIEWSIZE 100 x 140"; \ No newline at end of file +LINETEXT 50,70 HAlign=hcenter VAlign=vcenter Text="VIEWSIZE 100 x 140"; + +# ----------------------------------------------------------------------------- +# Page 13: advanced typography and annotations +# ----------------------------------------------------------------------------- +NEWPAGE A4 portrait; +CALL SECTION("Advanced typography and annotations"); +SET FONT Name="Arial" Size=12 regular; +SET BRUSH black; +LINETEXT 45,90 Text="Text can be aligned, rotated, highlighted and wrapped."; +SET PEN lightgray 0.7 solid; +RECT 45,125,505,105; +SET HBRUSH 0x60fff176; +SET FONT Name="Arial" Size=11 bold; +LINETEXT 60,140,215,70 HAlign=left VAlign=vcenter Text="Left aligned\r\nwith highlight"; +SET HBRUSH 0x00000000; +SET FONT Name="Arial" Size=11 italic; +LINETEXT 275,140,215,70 HAlign=hcenter VAlign=vcenter Text="Centered\r\nitalic text"; +SET FONT Name="Arial" Size=10 regular; +LINETEXT 60,270,490,70 Text="A bounded text area wraps automatically. This is useful for paragraphs, notes, explanations and generated report content without manually calculating every line."; +SET FONT Name="Arial" Size=10 underline; +LINETEXT 60,390 Text="Underline annotation"; +SET FONT Name="Arial" Size=10 strikeout; +LINETEXT 230,390 Text="Strikeout annotation"; +SET FONT Name="Arial" Size=10 regular; +LINETEXT 100,500 Orientation=30 Text="30 degrees"; +LINETEXT 300,500 Orientation=vertical Text="Vertical label"; +SET BRUSH 0xffe8eef7; +FILLRECT 45,570,505,95; +SET BRUSH darkblue; +SET FONT Name="Arial" Size=11 bold; +LINETEXT 65,590 Text="Annotation block"; +SET FONT Name="Arial" Size=10 regular; +SET BRUSH black; +TEXT 65,615,465,35 Text="Use TEXT for a bounded paragraph and LINETEXT for precise alignment."; + +# ----------------------------------------------------------------------------- +# Page 14: shapes, transparency and visual composition +# ----------------------------------------------------------------------------- +NEWPAGE A4 landscape; +CALL SECTION("Shapes, transparency and composition"); +SET FONT Name="Arial" Size=10 regular; +SET BRUSH black; +LINETEXT 45,85 Text="Layered shapes can create panels, badges and visual emphasis."; +SET PEN steelblue 1.5 solid; +SET BRUSH 0x405f9ed1; +FILLRECT 60,120,260,180; +SET BRUSH 0x5094d36b; +FILLRECT 180,180,260,180; +SET BRUSH 0x50f5b642; +FILLRECT 300,120,260,180; +SET BRUSH black; +LINETEXT 110,205 Text="panel A"; +LINETEXT 230,265 Text="panel B"; +LINETEXT 430,205 Text="panel C"; +SET PEN darkslategray 2 solid; +SET BRUSH 0x80ffffff; +FILLPOLYGON 610,130,700,110,770,180,735,280,640,300,585,220; +SET BRUSH black; +LINETEXT 625,205 Text="polygon"; +SET PEN purple 3 dashdot; +SET BRUSH 0x60ff7f50; +FILLPIE 90,390,130,130 Start=20 Angle=220; +SET BRUSH 0x6050c878; +FILLPIE 270,390,130,130 Start=140 Angle=220; +SET BRUSH 0x60f5b642; +FILLPIE 450,390,130,130 Start=260 Angle=220; +SET BRUSH black; +LINETEXT 95,535 Text="transparency"; +LINETEXT 275,535 Text="overlapping arcs"; +LINETEXT 455,535 Text="alpha colors"; + +# ----------------------------------------------------------------------------- +# Page 15: reusable layout patterns +# ----------------------------------------------------------------------------- +NEWPAGE A4 portrait; +CALL SECTION("Reusable layout patterns"); +SET FONT Name="Arial" Size=10 regular; +SET BRUSH black; +SET PEN lightgray 0.8 solid; +RECT 45,85,505,600; +SET BRUSH 0xffe8eef7; +FILLRECT 45,85,505,70; +SET BRUSH darkblue; +SET FONT Name="Arial" Size=16 bold; +LINETEXT 65,105 Text="Document header"; +SET FONT Name="Arial" Size=9 regular; +LINETEXT 65,133 Text="A reusable page frame built from rectangles, text and lines."; +SET BRUSH 0xfff5f8fb; +FILLRECT 65,185,225,150; +FILLRECT 305,185,225,150; +SET BRUSH black; +SET FONT Name="Arial" Size=11 bold; +LINETEXT 85,205 Text="Left column"; +LINETEXT 325,205 Text="Right column"; +SET FONT Name="Arial" Size=10 regular; +LINETEXT 85,240 Text="Use columns for summaries"; +LINETEXT 85,265 Text="and explanatory notes."; +LINETEXT 325,240 Text="Keep repeated content"; +LINETEXT 325,265 Text="inside a UDF component."; +SET PEN steelblue 1 solid; +LINE 65,365,530,365; +SET BRUSH black; +SET FONT Name="Arial" Size=12 bold; +LINETEXT 65,390 Text="Content section"; +SET FONT Name="Arial" Size=10 regular; +TEXT 65,425,465,90 MaxWidth=465 Text="A page can combine a fixed frame, reusable UDFs, measured row templates and automatic pagination. This pattern is suitable for invoices, reports and generated documentation."; +SET PEN steelblue 0.8 solid; +LINE 45,715,550,715; +SET BRUSH 0xffe8f7ee; +FILLRECT 65,730,465,55; +SET BRUSH darkgreen; +SET FONT Name="Arial" Size=11 bold; +LINETEXT 85,740 Text="Footer status"; +SET FONT Name="Arial" Size=10 regular; +LINETEXT 85,765 Text="Generated successfully | ready for review"; \ No newline at end of file From 0425fded48e9345ccb72a3798e2e214c1c76421d Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Fri, 18 Sep 2026 23:44:28 +0200 Subject: [PATCH 08/15] fix scaling with pen and font --- PdfSharpDslCore/Drawing/PdfDocumentDrawer.cs | 28 ++++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/PdfSharpDslCore/Drawing/PdfDocumentDrawer.cs b/PdfSharpDslCore/Drawing/PdfDocumentDrawer.cs index 0035bf7..fa477ab 100644 --- a/PdfSharpDslCore/Drawing/PdfDocumentDrawer.cs +++ b/PdfSharpDslCore/Drawing/PdfDocumentDrawer.cs @@ -125,7 +125,7 @@ private DocumentComposer CreateDocument() public void DrawLine(double x, double y, double x1, double y1) { var page = CurrentPage; - InternalDrawLine(CurrentPen, ScaleX(ResolveX(x, page), page), ScaleY(ResolveY(y, page), page), + InternalDrawLine(ScalePen(CurrentPen, page), ScaleX(ResolveX(x, page), page), ScaleY(ResolveY(y, page), page), ScaleX(ResolveX(x1, page), page), ScaleY(ResolveY(y1, page), page)); } @@ -158,7 +158,7 @@ public void DrawRect(double x, double y, double w, double h, bool isFilled) { var page = CurrentPage; (x, y, w, h) = DrawingHelper.CoordRectToPage(page.Width, page.Height, x, y, w, h); - InternalDrawRect(CurrentPen, CurrentBrush, ScaleX(x, page), ScaleY(y, page), ScaleX(w, page), ScaleY(h, page), isFilled); + InternalDrawRect(ScalePen(CurrentPen, page), CurrentBrush, ScaleX(x, page), ScaleY(y, page), ScaleX(w, page), ScaleY(h, page), isFilled); } private void InternalDrawRect(PdfPen pen, PdfBrush brush, double x, double y, double w, double h, bool isFilled) @@ -177,7 +177,7 @@ public void DrawEllipse(double x, double y, double w, double h, bool isFilled) { var page = CurrentPage; (x, y, w, h) = DrawingHelper.CoordRectToPage(page.Width, page.Height, x, y, w, h); - InternalDrawEllipse(CurrentPen, CurrentBrush, ScaleX(x, page), ScaleY(y, page), ScaleX(w, page), ScaleY(h, page), isFilled); + InternalDrawEllipse(ScalePen(CurrentPen, page), CurrentBrush, ScaleX(x, page), ScaleY(y, page), ScaleX(w, page), ScaleY(h, page), isFilled); } private void InternalDrawEllipse(PdfPen pen, PdfBrush brush, double x, double y, double w, double h, bool isFilled) @@ -198,7 +198,7 @@ public void DrawText(string text, double x, double y, double? w, double? h) (x, y, w, h) = DrawingHelper.CoordRectToPage(page.Width, page.Height, x, y, w, h); InternalDrawText(text, ScaleX(x, page), ScaleY(y, page), w.HasValue ? ScaleX(w.Value, page) : page.Width - x, h.HasValue ? ScaleY(h.Value, page) : null, PdfHorizontalAlignment.Near, PdfVerticalAlignment.Near, - CurrentFont, CurrentBrush, null); + ScaleFont(CurrentFont, page), CurrentBrush, null); } public void DrawLineText(string text, double x, double y, double? w, double? h, PdfHorizontalAlignment hAlign, @@ -207,7 +207,7 @@ public void DrawLineText(string text, double x, double y, double? w, double? h, var page = CurrentPage; (x, y, w, h) = DrawingHelper.CoordRectToPage(page.Width, page.Height, x, y, w, h); InternalDrawText(text, ScaleX(x, page), ScaleY(y, page), w.HasValue ? ScaleX(w.Value, page) : null, - h.HasValue ? ScaleY(h.Value, page) : null, hAlign, vAlign, CurrentFont, CurrentBrush, HighlightBrush, + h.HasValue ? ScaleY(h.Value, page) : null, hAlign, vAlign, ScaleFont(CurrentFont, page), CurrentBrush, HighlightBrush, textOrientation?.Angle ?? textOrientation?.Orientation switch { TextOrientationEnum.Vertical => 90, @@ -367,7 +367,7 @@ public void LineTo(double x, double y) { var page = CurrentPage; var endPoint = new PdfPoint(x, y); - InternalDrawLine(CurrentPen, ScaleX(_currentPoint.X, page), ScaleY(_currentPoint.Y, page), + InternalDrawLine(ScalePen(CurrentPen, page), ScaleX(_currentPoint.X, page), ScaleY(_currentPoint.Y, page), ScaleX(endPoint.X, page), ScaleY(endPoint.Y, page)); _currentPoint = endPoint; } @@ -398,7 +398,7 @@ public void DrawPie(double x, double y, double? w, double? h, double startAngle, var height = h ?? 0; if (width <= 0 || height <= 0) throw new ArgumentOutOfRangeException(nameof(w), "Pie dimensions must be positive."); - InternalDrawPie(CurrentPen, CurrentBrush, ScaleX(x, page), ScaleY(y, page), + InternalDrawPie(ScalePen(CurrentPen, page), CurrentBrush, ScaleX(x, page), ScaleY(y, page), ScaleX(width, page), ScaleY(height, page), startAngle, sweepAngle, isFilled); } @@ -423,7 +423,7 @@ public void DrawPolygon(IEnumerable points, bool isFilled) var page = CurrentPage; var transformed = points.Select(point => new PdfPoint(ScaleX(point.X, page), ScaleY(point.Y, page))).ToArray(); if (transformed.Length < 3) throw new ArgumentException("A polygon requires at least three points.", nameof(points)); - InternalDrawPolygon(CurrentPen, CurrentBrush, transformed, isFilled); + InternalDrawPolygon(ScalePen(CurrentPen, page), CurrentBrush, transformed, isFilled); } private void InternalDrawPolygon(PdfPen pen, PdfBrush brush, PdfPoint[] points, bool isFilled) @@ -506,6 +506,18 @@ private static List WrapText(string text, double? maxWidth, PdfFont font private static double ScaleX(double value, RecordedPage page) => value * page.ScaleX; private static double ScaleY(double value, RecordedPage page) => value * page.ScaleY; + private static PdfPen ScalePen(PdfPen pen, RecordedPage page) + { + var scaled = new PdfPen(pen.Color, pen.Width * ScaleFactor(page)) { DashStyle = pen.DashStyle }; + return scaled; + } + + private static PdfFont ScaleFont(PdfFont font, RecordedPage page) => + new(font.FamilyName, font.Size * ScaleFactor(page), font.Style); + + private static double ScaleFactor(RecordedPage page) => + Math.Sqrt(page.ScaleX * page.ScaleY); + private static (double Width, double Height) GetPageDimensions(PdfPageSize size, PdfPageOrientation orientation) { var dimensions = size switch From eecc4306c85531e6816f71b31e44f380157a965c Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Sat, 19 Sep 2026 07:00:24 +0200 Subject: [PATCH 09/15] add merged cells feature --- PdfSharpDsl.Language/Parser/PdfGrammar.cs | 6 +- PdfSharpDslConsole/demo.ipdf | 43 +++++++++ PdfSharpDslCore/Drawing/PdfDocumentDrawer.cs | 97 ++++++++++++++++++-- PdfSharpDslCore/Drawing/TableDefinition.cs | 11 +++ PdfSharpDslCore/Parser/PdfDrawerVisitor.cs | 40 ++++++-- pdfsharpdslTests/VisitorTests.cs | 31 +++++++ 6 files changed, 209 insertions(+), 19 deletions(-) diff --git a/PdfSharpDsl.Language/Parser/PdfGrammar.cs b/PdfSharpDsl.Language/Parser/PdfGrammar.cs index 7c15cb8..4ee099f 100644 --- a/PdfSharpDsl.Language/Parser/PdfGrammar.cs +++ b/PdfSharpDsl.Language/Parser/PdfGrammar.cs @@ -112,6 +112,8 @@ public PdfGrammar() var TableRowListOrRowTemplate = new NonTerminal("TableRowListOrRowTemplate"); var TableRowTemplate = new NonTerminal("TableRowTemplate"); var TableRowTemplateCount = new NonTerminal("TableRowTemplateCount"); + var TableCellColSpan = new NonTerminal("TableCellColSpan"); + var TableCellRowSpan = new NonTerminal("TableCellRowSpan"); var PointAutoLocation = new NonTerminal("PointAutoLocation"); var NumberOrAuto = new NonTerminal("NumberOrAuto"); @@ -332,7 +334,9 @@ public PdfGrammar() TableColWidth.Rule = Arg("Width") + NumberOrAuto + Arg("MaxWidth") + NumberOrAuto; TableColList.Rule = MakeStarRule(TableColList, TableCol); TableRow.Rule = ToTerm("ROW") + TableRowStyle + TableColList + ToTerm("ENDROW"); - TableCol.Rule = ToTerm("COL") + FormulaExpression + semi; + TableCellColSpan.Rule = Empty | Arg("ColSpan") + number_literal; + TableCellRowSpan.Rule = Empty | Arg("RowSpan") + number_literal; + TableCol.Rule = ToTerm("COL") + TableCellColSpan + TableCellRowSpan + TextAlignment + FormulaExpression + semi; TableLocation.Rule = PointLocation /*+ "," + PointAutoLocation*/; PointAutoLocation.Rule = NumberOrAuto + "," + NumberOrAuto; NumberOrAuto.Rule = FormulaExpression | "auto"; diff --git a/PdfSharpDslConsole/demo.ipdf b/PdfSharpDslConsole/demo.ipdf index e2180b9..09c06e9 100644 --- a/PdfSharpDslConsole/demo.ipdf +++ b/PdfSharpDslConsole/demo.ipdf @@ -507,6 +507,49 @@ COL "Global item "+($ROWINDEX+1); ENDROW ENDTABLE +# ----------------------------------------------------------------------------- +# Merged table cells, per-cell alignment and multiline content +# ----------------------------------------------------------------------------- +NEWPAGE A4 portrait; +CALL SECTION("Merged cells and cell alignment"); +SET FONT Name="Arial" Size=9 regular; +SET PEN darkslategray 1 solid; + +TABLE 45,90 +HEAD 0xffc8d9ed +COL Width=125 MaxWidth=125 FONT="Arial",9,bold darkblue 0xffe8f1fb "Team"; +COL Width=125 MaxWidth=125 FONT="Arial",9,bold darkgreen 0xffe8f7ee "Q1"; +COL Width=125 MaxWidth=125 FONT="Arial",9,bold darkslategray 0xfffff3d6 "Q2"; +COL Width=125 MaxWidth=125 FONT="Arial",9,bold maroon 0xffffe4e1 "Status"; +ENDHEAD +ROW 54 +COL RowSpan=2 HAlign=hcenter VAlign=vcenter "Platform"; +COL ColSpan=2 HAlign=hcenter VAlign=vcenter "First-half delivery"; +COL RowSpan=2 HAlign=hcenter VAlign=vcenter "On track"; +ENDROW +ROW 34 +COL HAlign=right VAlign=vcenter "Q1: 128"; +COL HAlign=right VAlign=vcenter "Q2: 146"; +ENDROW +ROW 48 +COL ColSpan=4 HAlign=hcenter VAlign=vcenter "One cell spans the complete table width"; +ENDROW +ROW 64 +COL HAlign=left VAlign=top "left / top"; +COL HAlign=hcenter VAlign=vcenter "center / center"; +COL HAlign=right VAlign=bottom "right / bottom"; +COL HAlign=hcenter VAlign=vcenter "Multiline\r\ncentered"; +ENDROW +ROW 44 +COL ColSpan=2 HAlign=left VAlign=vcenter "Two-column summary"; +COL ColSpan=2 HAlign=right VAlign=vcenter "Formula span: "+(1+1); +ENDROW +ENDTABLE + +SET BRUSH darkslategray; +LINETEXT 45,400 Text="ColSpan and RowSpan reserve covered cells; no placeholder COL is required."; +LINETEXT 45,425 Text="HAlign accepts left, hcenter or right; VAlign accepts top, vcenter or bottom."; + # ----------------------------------------------------------------------------- # Free row templates: nesting, names, borders, pagination and measured height # ----------------------------------------------------------------------------- diff --git a/PdfSharpDslCore/Drawing/PdfDocumentDrawer.cs b/PdfSharpDslCore/Drawing/PdfDocumentDrawer.cs index fa477ab..a8c3527 100644 --- a/PdfSharpDslCore/Drawing/PdfDocumentDrawer.cs +++ b/PdfSharpDslCore/Drawing/PdfDocumentDrawer.cs @@ -276,12 +276,14 @@ public void DrawTable(double x, double y, TableDefinition table) var availableWidth = PageWidth - x; var fonts = table.Columns.Select(column => column.Font ?? CurrentFont).ToArray(); var margins = table.CellMargin; + var cells = LayoutTableCells(table); for (var index = 0; index < table.Columns.Count; index++) { var column = table.Columns[index]; - var contentWidth = table.Rows - .Select(row => MeasureCellWidth(index < row.Data.Length ? row.Data[index] : string.Empty, fonts[index])) + var contentWidth = cells + .Where(cell => cell.Column == index && cell.Cell.ColumnSpan == 1) + .Select(cell => MeasureCellWidth(cell.Cell.Text, fonts[index])) .DefaultIfEmpty(0) .Max(); var desiredWidth = Math.Max(MeasureCellWidth(column.ColumnHeaderName, fonts[index]), contentWidth) @@ -291,15 +293,32 @@ public void DrawTable(double x, double y, TableDefinition table) table.HeaderHeight ??= fonts[index].Size * 1.2 + margins.Top + margins.Bottom; } - foreach (var row in table.Rows) + for (var rowIndex = 0; rowIndex < table.Rows.Count; rowIndex++) { - var contentHeight = fonts.Select((font, index) => - MeasureCellLineCount(index < row.Data.Length ? row.Data[index] : string.Empty, - table.Columns[index].DrawWidth - margins.Left - margins.Right, font) - * font.Size * 1.2 + margins.Top + margins.Bottom).Max(); + var row = table.Rows[rowIndex]; + var contentHeight = cells.Where(cell => cell.Row == rowIndex && cell.Cell.RowSpan == 1) + .Select(cell => + { + var width = table.Columns.Skip(cell.Column).Take(cell.Cell.ColumnSpan).Sum(column => column.DrawWidth); + var font = fonts[cell.Column]; + return MeasureCellLineCount(cell.Cell.Text, width - margins.Left - margins.Right, font) + * font.Size * 1.2 + margins.Top + margins.Bottom; + }).DefaultIfEmpty(fonts[0].Size * 1.2 + margins.Top + margins.Bottom).Max(); row.DesiredHeight ??= contentHeight; } + foreach (var cell in cells.Where(cell => cell.Cell.RowSpan > 1)) + { + var width = table.Columns.Skip(cell.Column).Take(cell.Cell.ColumnSpan).Sum(column => column.DrawWidth); + var font = fonts[cell.Column]; + var requiredHeight = MeasureCellLineCount(cell.Cell.Text, width - margins.Left - margins.Right, font) + * font.Size * 1.2 + margins.Top + margins.Bottom; + var rows = table.Rows.Skip(cell.Row).Take(cell.Cell.RowSpan).ToArray(); + var missingHeight = requiredHeight - rows.Sum(row => row.DesiredHeight ?? 0); + if (missingHeight > 0) + rows[^1].DesiredHeight = (rows[^1].DesiredHeight ?? 0) + missingHeight; + } + if (y + (table.HeaderHeight ?? 0) > PageHeight) { NewPage(); y = 1; } var offsetY = 0d; if (table.ShowHeader) @@ -308,15 +327,73 @@ public void DrawTable(double x, double y, TableDefinition table) offsetY = table.HeaderHeight ?? 0; } - foreach (var row in table.Rows) + for (var rowIndex = 0; rowIndex < table.Rows.Count; rowIndex++) { + var row = table.Rows[rowIndex]; var height = row.DesiredHeight ?? 0; - if (y + offsetY + height > PageHeight) { NewPage(); y = table.TopMarginOnPageBreak; offsetY = 0; } - DrawTableRow(x, y + offsetY, row.Data, height, table, fonts, null); + var rowCells = cells.Where(cell => cell.Row == rowIndex).ToArray(); + var requiredHeight = rowCells.Select(cell => table.Rows.Skip(rowIndex).Take(cell.Cell.RowSpan) + .Sum(spannedRow => spannedRow.DesiredHeight ?? 0)).DefaultIfEmpty(height).Max(); + if (y + offsetY + requiredHeight > PageHeight) { NewPage(); y = table.TopMarginOnPageBreak; offsetY = 0; } + foreach (var cell in rowCells) + DrawTableCell(x, y + offsetY, cell, table, fonts); offsetY += height; } } + private static List LayoutTableCells(TableDefinition table) + { + var result = new List(); + var occupiedUntilRow = new int[table.Columns.Count]; + for (var rowIndex = 0; rowIndex < table.Rows.Count; rowIndex++) + { + var row = table.Rows[rowIndex]; + var cells = row.Cells.Count > 0 + ? row.Cells + : row.Data.Select(text => new CellDefinition { Text = text }).ToList(); + var columnIndex = 0; + foreach (var cell in cells) + { + while (columnIndex < table.Columns.Count && occupiedUntilRow[columnIndex] > rowIndex) + columnIndex++; + if (columnIndex >= table.Columns.Count) break; + + cell.ColumnSpan = Math.Min(Math.Max(1, cell.ColumnSpan), table.Columns.Count - columnIndex); + cell.RowSpan = Math.Min(Math.Max(1, cell.RowSpan), table.Rows.Count - rowIndex); + result.Add(new TableCellPlacement(rowIndex, columnIndex, cell)); + for (var index = columnIndex; index < columnIndex + cell.ColumnSpan; index++) + occupiedUntilRow[index] = rowIndex + cell.RowSpan; + columnIndex += cell.ColumnSpan; + } + } + return result; + } + + private void DrawTableCell(double x, double y, TableCellPlacement placement, TableDefinition table, PdfFont[] fonts) + { + var column = table.Columns[placement.Column]; + var offsetX = table.Columns.Take(placement.Column).Sum(item => item.DrawWidth); + var width = table.Columns.Skip(placement.Column).Take(placement.Cell.ColumnSpan).Sum(item => item.DrawWidth); + var height = table.Rows.Skip(placement.Row).Take(placement.Cell.RowSpan).Sum(row => row.DesiredHeight ?? 0); + if (column.BackColor is not null) + { + var previousBrush = CurrentBrush; + CurrentBrush = column.BackColor; + DrawRect(x + offsetX, y, width, height, true); + CurrentBrush = previousBrush; + } + else DrawRect(x + offsetX, y, width, height, false); + + var margins = table.CellMargin; + InternalDrawText(placement.Cell.Text, x + offsetX + margins.Left, y + margins.Top, + Math.Max(0, width - margins.Left - margins.Right), Math.Max(0, height - margins.Top - margins.Bottom), + placement.Cell.HorizontalAlignment ?? column.Alignment, + placement.Cell.VerticalAlignment ?? PdfVerticalAlignment.Center, + fonts[placement.Column], column.Brush ?? CurrentBrush, null); + } + + private sealed record TableCellPlacement(int Row, int Column, CellDefinition Cell); + private void DrawTableRow(double x, double y, string[] values, double height, TableDefinition table, PdfFont[] fonts, PdfBrush? rowBackground) { var offsetX = 0d; diff --git a/PdfSharpDslCore/Drawing/TableDefinition.cs b/PdfSharpDslCore/Drawing/TableDefinition.cs index 9a6c65c..e99609c 100644 --- a/PdfSharpDslCore/Drawing/TableDefinition.cs +++ b/PdfSharpDslCore/Drawing/TableDefinition.cs @@ -71,9 +71,20 @@ public class RowDefinition public double? DesiredHeight { get; set; } public double? MaxHeight { get; set; } + public List Cells { get; set; } = new(); + /// /// string because there is only draw text /// public string[] Data { get; set; } = Array.Empty(); } + + public class CellDefinition + { + public string Text { get; set; } = string.Empty; + public int ColumnSpan { get; set; } = 1; + public int RowSpan { get; set; } = 1; + public PdfHorizontalAlignment? HorizontalAlignment { get; set; } + public PdfVerticalAlignment? VerticalAlignment { get; set; } + } } \ No newline at end of file diff --git a/PdfSharpDslCore/Parser/PdfDrawerVisitor.cs b/PdfSharpDslCore/Parser/PdfDrawerVisitor.cs index 71f5461..12ce808 100644 --- a/PdfSharpDslCore/Parser/PdfDrawerVisitor.cs +++ b/PdfSharpDslCore/Parser/PdfDrawerVisitor.cs @@ -562,11 +562,10 @@ private void GenerateTableRowsTemplate(IEnumerable nodes, TableDe for (var i = 0; i < rowCount; i++) { var rowDef = new RowDefinition(); - var cols = row.ChildNodes("TableCol").SelectMany(x => x.ChildNodes) - .Where(x => x.Term?.Name != "COL").ToArray(); vars.Add("ROWINDEX", i); - var rowData = cols.Select(x => EvaluateForObject(x)?.ToString()!).ToList(); + rowDef.Cells = row.ChildNodes("TableCol").Select(ParseTableCell).ToList(); + var rowData = rowDef.Cells.Select(cell => cell.Text).ToList(); while (rowData.Count < tbl.Columns.Count) { @@ -598,11 +597,8 @@ private void GenerateTableRows(IEnumerable nodes, TableDefinition rowDef.DesiredHeight = rowHeight; } - var cols = row.ChildNodes("TableCol").SelectMany(x => x.ChildNodes).Where(x => x.Term?.Name != "COL") - .ToArray(); - - - var rowData = cols.Select(x => x.Token.ValueString).ToList(); + rowDef.Cells = row.ChildNodes("TableCol").Select(ParseTableCell).ToList(); + var rowData = rowDef.Cells.Select(cell => cell.Text).ToList(); while (rowData.Count < tbl.Columns.Count) { rowData.Add(string.Empty); @@ -613,6 +609,34 @@ private void GenerateTableRows(IEnumerable nodes, TableDefinition } } + private CellDefinition ParseTableCell(ParseTreeNode node) + { + var expression = node.ChildNodes.Last(); + var cell = new CellDefinition + { + Text = EvaluateForObject(expression)?.ToString() ?? string.Empty, + ColumnSpan = ParseTableCellSpan(node.ChildNode("TableCellColSpan")), + RowSpan = ParseTableCellSpan(node.ChildNode("TableCellRowSpan")) + }; + var alignment = node.ChildNode("TextAlignment"); + if (alignment is not null) + { + var hNode = alignment.ChildNode("HAlign"); + var vNode = alignment.ChildNode("VAlign"); + if (hNode?.ChildNodes.Count > 0) + cell.HorizontalAlignment = ParseTextAlignment(hNode, null).Item1; + if (vNode?.ChildNodes.Count > 0) + cell.VerticalAlignment = ParseTextAlignment(null, vNode).Item2; + } + return cell; + } + + private int ParseTableCellSpan(ParseTreeNode? node) + { + if (node is null || node.ChildNodes.Count == 0) return 1; + return Math.Max(1, Convert.ToInt32(EvaluateForDouble(node.ChildNodes.Last()))); + } + private void GenerateTableHead(IEnumerable nodes, TableDefinition tbl) { foreach (var col in nodes) diff --git a/pdfsharpdslTests/VisitorTests.cs b/pdfsharpdslTests/VisitorTests.cs index 2e01bb2..f61d7d0 100644 --- a/pdfsharpdslTests/VisitorTests.cs +++ b/pdfsharpdslTests/VisitorTests.cs @@ -210,6 +210,37 @@ public void TableRowTemplateBuildsRowsWidthsAndPadding() Assert.False(visitor.Vars.ContainsKey("ROWINDEX")); } + [Fact] + public void TableCellsCaptureSpansAndAlignment() + { + var tree = ParseText( + "TABLE 20,30 " + + "HEAD " + + "COL Width=100 MaxWidth=100 \"A\"; " + + "COL Width=100 MaxWidth=100 \"B\"; " + + "COL Width=100 MaxWidth=100 \"C\"; " + + "ENDHEAD " + + "ROW 40 " + + "COL ColSpan=2 RowSpan=2 HAlign=hcenter VAlign=bottom \"Merged\"; " + + "COL HAlign=right \"Right\"; " + + "ENDROW " + + "ROW COL \"Remaining\"; ENDROW " + + "ENDTABLE"); + TableDefinition? capturedTable = null; + var drawer = new Mock(); + drawer.Setup(x => x.DrawTable(20, 30, It.IsAny())) + .Callback((_, _, table) => capturedTable = table); + + new InspectablePdfDrawerVisitor().Draw(drawer.Object, tree); + + var mergedCell = Assert.Single(capturedTable!.Rows[0].Cells, cell => cell.Text == "Merged"); + Assert.Equal(2, mergedCell.ColumnSpan); + Assert.Equal(2, mergedCell.RowSpan); + Assert.Equal(PdfHorizontalAlignment.Center, mergedCell.HorizontalAlignment); + Assert.Equal(PdfVerticalAlignment.Far, mergedCell.VerticalAlignment); + Assert.Equal(PdfHorizontalAlignment.Far, capturedTable.Rows[0].Cells[1].HorizontalAlignment); + } + [Fact] public void CustomUdfCanFallBackToDslBodyOrOverrideIt() { From e59f2912436d2f516ecccc91a6027d7bc86aa30a Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Sat, 19 Sep 2026 07:40:22 +0200 Subject: [PATCH 10/15] allow using local version of terrapdf + more tests --- PdfSharpDslConsole/PdfSharpDslConsole.csproj | 3 + PdfSharpDslCore/PdfSharpDslCore.csproj | 21 +- bnf_and_pdf.sln | 44 ++++ pdfsharpdslTests/GenerationTests.cs | 13 ++ tasks.md | 218 ------------------- 5 files changed, 73 insertions(+), 226 deletions(-) delete mode 100644 tasks.md diff --git a/PdfSharpDslConsole/PdfSharpDslConsole.csproj b/PdfSharpDslConsole/PdfSharpDslConsole.csproj index 621402a..78e426c 100644 --- a/PdfSharpDslConsole/PdfSharpDslConsole.csproj +++ b/PdfSharpDslConsole/PdfSharpDslConsole.csproj @@ -57,6 +57,9 @@ PreserveNewest + + PreserveNewest + diff --git a/PdfSharpDslCore/PdfSharpDslCore.csproj b/PdfSharpDslCore/PdfSharpDslCore.csproj index 9619a45..30b50fd 100644 --- a/PdfSharpDslCore/PdfSharpDslCore.csproj +++ b/PdfSharpDslCore/PdfSharpDslCore.csproj @@ -10,6 +10,7 @@ 2.0.0 Pierrick Gourlain + true A DSL using TerraPDF to generate PDF https://github.com/pgourlain/bnf_and_pdf Icon.jpg @@ -17,20 +18,24 @@ - - True - \ - - - - + + True + \ + - + + + + + + + + diff --git a/bnf_and_pdf.sln b/bnf_and_pdf.sln index 69817b2..6ce8b80 100644 --- a/bnf_and_pdf.sln +++ b/bnf_and_pdf.sln @@ -23,29 +23,73 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {599099A9-C23A-4EE3-A3BA-D65E95E52014}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {599099A9-C23A-4EE3-A3BA-D65E95E52014}.Debug|Any CPU.Build.0 = Debug|Any CPU + {599099A9-C23A-4EE3-A3BA-D65E95E52014}.Debug|x64.ActiveCfg = Debug|Any CPU + {599099A9-C23A-4EE3-A3BA-D65E95E52014}.Debug|x64.Build.0 = Debug|Any CPU + {599099A9-C23A-4EE3-A3BA-D65E95E52014}.Debug|x86.ActiveCfg = Debug|Any CPU + {599099A9-C23A-4EE3-A3BA-D65E95E52014}.Debug|x86.Build.0 = Debug|Any CPU {599099A9-C23A-4EE3-A3BA-D65E95E52014}.Release|Any CPU.ActiveCfg = Release|Any CPU {599099A9-C23A-4EE3-A3BA-D65E95E52014}.Release|Any CPU.Build.0 = Release|Any CPU + {599099A9-C23A-4EE3-A3BA-D65E95E52014}.Release|x64.ActiveCfg = Release|Any CPU + {599099A9-C23A-4EE3-A3BA-D65E95E52014}.Release|x64.Build.0 = Release|Any CPU + {599099A9-C23A-4EE3-A3BA-D65E95E52014}.Release|x86.ActiveCfg = Release|Any CPU + {599099A9-C23A-4EE3-A3BA-D65E95E52014}.Release|x86.Build.0 = Release|Any CPU {31F6FA1A-55D5-45C1-8AB8-8ED23984BA3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {31F6FA1A-55D5-45C1-8AB8-8ED23984BA3A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {31F6FA1A-55D5-45C1-8AB8-8ED23984BA3A}.Debug|x64.ActiveCfg = Debug|Any CPU + {31F6FA1A-55D5-45C1-8AB8-8ED23984BA3A}.Debug|x64.Build.0 = Debug|Any CPU + {31F6FA1A-55D5-45C1-8AB8-8ED23984BA3A}.Debug|x86.ActiveCfg = Debug|Any CPU + {31F6FA1A-55D5-45C1-8AB8-8ED23984BA3A}.Debug|x86.Build.0 = Debug|Any CPU {31F6FA1A-55D5-45C1-8AB8-8ED23984BA3A}.Release|Any CPU.ActiveCfg = Release|Any CPU {31F6FA1A-55D5-45C1-8AB8-8ED23984BA3A}.Release|Any CPU.Build.0 = Release|Any CPU + {31F6FA1A-55D5-45C1-8AB8-8ED23984BA3A}.Release|x64.ActiveCfg = Release|Any CPU + {31F6FA1A-55D5-45C1-8AB8-8ED23984BA3A}.Release|x64.Build.0 = Release|Any CPU + {31F6FA1A-55D5-45C1-8AB8-8ED23984BA3A}.Release|x86.ActiveCfg = Release|Any CPU + {31F6FA1A-55D5-45C1-8AB8-8ED23984BA3A}.Release|x86.Build.0 = Release|Any CPU {D737D13F-8593-4AB1-961B-7D5F736ACF2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D737D13F-8593-4AB1-961B-7D5F736ACF2E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D737D13F-8593-4AB1-961B-7D5F736ACF2E}.Debug|x64.ActiveCfg = Debug|Any CPU + {D737D13F-8593-4AB1-961B-7D5F736ACF2E}.Debug|x64.Build.0 = Debug|Any CPU + {D737D13F-8593-4AB1-961B-7D5F736ACF2E}.Debug|x86.ActiveCfg = Debug|Any CPU + {D737D13F-8593-4AB1-961B-7D5F736ACF2E}.Debug|x86.Build.0 = Debug|Any CPU {D737D13F-8593-4AB1-961B-7D5F736ACF2E}.Release|Any CPU.ActiveCfg = Release|Any CPU {D737D13F-8593-4AB1-961B-7D5F736ACF2E}.Release|Any CPU.Build.0 = Release|Any CPU + {D737D13F-8593-4AB1-961B-7D5F736ACF2E}.Release|x64.ActiveCfg = Release|Any CPU + {D737D13F-8593-4AB1-961B-7D5F736ACF2E}.Release|x64.Build.0 = Release|Any CPU + {D737D13F-8593-4AB1-961B-7D5F736ACF2E}.Release|x86.ActiveCfg = Release|Any CPU + {D737D13F-8593-4AB1-961B-7D5F736ACF2E}.Release|x86.Build.0 = Release|Any CPU {A6092AA9-EC00-4C4F-AF43-E039E400CC3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A6092AA9-EC00-4C4F-AF43-E039E400CC3B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A6092AA9-EC00-4C4F-AF43-E039E400CC3B}.Debug|x64.ActiveCfg = Debug|Any CPU + {A6092AA9-EC00-4C4F-AF43-E039E400CC3B}.Debug|x64.Build.0 = Debug|Any CPU + {A6092AA9-EC00-4C4F-AF43-E039E400CC3B}.Debug|x86.ActiveCfg = Debug|Any CPU + {A6092AA9-EC00-4C4F-AF43-E039E400CC3B}.Debug|x86.Build.0 = Debug|Any CPU {A6092AA9-EC00-4C4F-AF43-E039E400CC3B}.Release|Any CPU.ActiveCfg = Release|Any CPU {A6092AA9-EC00-4C4F-AF43-E039E400CC3B}.Release|Any CPU.Build.0 = Release|Any CPU + {A6092AA9-EC00-4C4F-AF43-E039E400CC3B}.Release|x64.ActiveCfg = Release|Any CPU + {A6092AA9-EC00-4C4F-AF43-E039E400CC3B}.Release|x64.Build.0 = Release|Any CPU + {A6092AA9-EC00-4C4F-AF43-E039E400CC3B}.Release|x86.ActiveCfg = Release|Any CPU + {A6092AA9-EC00-4C4F-AF43-E039E400CC3B}.Release|x86.Build.0 = Release|Any CPU {2373085B-7611-43B6-A4A1-F2DE608817C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2373085B-7611-43B6-A4A1-F2DE608817C4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2373085B-7611-43B6-A4A1-F2DE608817C4}.Debug|x64.ActiveCfg = Debug|Any CPU + {2373085B-7611-43B6-A4A1-F2DE608817C4}.Debug|x64.Build.0 = Debug|Any CPU + {2373085B-7611-43B6-A4A1-F2DE608817C4}.Debug|x86.ActiveCfg = Debug|Any CPU + {2373085B-7611-43B6-A4A1-F2DE608817C4}.Debug|x86.Build.0 = Debug|Any CPU {2373085B-7611-43B6-A4A1-F2DE608817C4}.Release|Any CPU.ActiveCfg = Release|Any CPU {2373085B-7611-43B6-A4A1-F2DE608817C4}.Release|Any CPU.Build.0 = Release|Any CPU + {2373085B-7611-43B6-A4A1-F2DE608817C4}.Release|x64.ActiveCfg = Release|Any CPU + {2373085B-7611-43B6-A4A1-F2DE608817C4}.Release|x64.Build.0 = Release|Any CPU + {2373085B-7611-43B6-A4A1-F2DE608817C4}.Release|x86.ActiveCfg = Release|Any CPU + {2373085B-7611-43B6-A4A1-F2DE608817C4}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/pdfsharpdslTests/GenerationTests.cs b/pdfsharpdslTests/GenerationTests.cs index 4e88ef8..ec8d0c7 100644 --- a/pdfsharpdslTests/GenerationTests.cs +++ b/pdfsharpdslTests/GenerationTests.cs @@ -37,6 +37,19 @@ public void TestdrawingLinesOutPut(string file) Assert.True(pdf.ContentStreamCount > 0); } + [Fact] + public void DrawingEmbeddedImageAddsPdfImageResource() + { + const string input = "NEWPAGE A4 portrait;" + + "IMAGE 10,10,20,20 point fit Data=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=\";"; + + using var memStm = GeneratePdf(input); + var pdf = new PdfBinaryInspector(memStm); + + Assert.True(pdf.HasPdfHeader); + Assert.Equal(1, pdf.ImageCount); + } + [Theory()] [InlineData("pdf1-udfs.txt")] public void TestdrawingUdfsOutPut(string file) diff --git a/tasks.md b/tasks.md deleted file mode 100644 index 23f1dc6..0000000 --- a/tasks.md +++ /dev/null @@ -1,218 +0,0 @@ -# .NET 10 Upgrade Tasks - -Coverage work blocks the framework upgrade. `PdfSharpDslCore` line coverage must be at least 90%; branch coverage is reported but does not gate progress. - -**Current verified coverage (2026-09-18):** 90.76% lines (2260/2490), 83.56% branches (778/931), 83 tests passed. - -## TASK-001: Coverage baseline - -**Status:** Complete (2026-09-18) -**Depends on:** None - -**Objective:** Establish reproducible, core-only Cobertura reporting and record the current baseline. - -**Affected files:** `.runsettings`, `scripts/coverage.ps1`, `tasks.md` - -**Acceptance criteria:** - -- The test suite runs through Coverlet's collector. -- The report contains only the `PdfSharpDslCore` assembly. -- The script prints line and branch totals and fails when the report is missing or contains another assembly. -- Generated reports remain under the ignored `artifacts/` directory. - -**Validation:** - -```powershell -powershell -NoProfile -File .\scripts\coverage.ps1 -``` - -**Result:** 45 tests passed. Line coverage is 79.63% (1983/2490); branch coverage is 73.14% (681/931). The migration remains blocked. - -**Handoff:** Begin TASK-002. Raise core line coverage above the current baseline without changing target frameworks or enforcing the final threshold yet. - -## TASK-002: Evaluation coverage - -**Status:** Not started -**Depends on:** TASK-001 - -**Objective:** Test arithmetic, comparison, boolean, unary, null, string, and error paths; investigate the `%` parser/evaluator mismatch. - -**Affected files:** `pdfsharpdslTests/FormulaTests.cs`, `PdfSharpDslCore/Evaluation/`, parser tests and parser implementation if the mismatch is confirmed - -**Acceptance criteria:** Evaluation branches have focused tests, the `%` behavior is documented by a test and corrected if defective, and all existing tests pass. - -**Validation:** - -```powershell -dotnet test .\pdfsharpdslTests\pdfsharpdslTests.csproj --filter FullyQualifiedName~FormulaTests -powershell -NoProfile -File .\scripts\coverage.ps1 -``` - -**Handoff:** Record the new coverage result here and identify the largest remaining uncovered core areas for TASK-003. - -## TASK-003: Variables and UDFs - -**Status:** Not started -**Depends on:** TASK-002 - -**Objective:** Cover variable lifecycle, nested scopes, system variables, UDF dispatch, failure paths, and `__ONNEWPAGE`. - -**Affected files:** `pdfsharpdslTests/`, `PdfSharpDslCore/` variable and UDF implementation - -**Acceptance criteria:** Focused tests cover successful and failing variable/UDF behavior, including nested scope and new-page callbacks. - -**Validation:** - -```powershell -dotnet test .\pdfsharpdslTests\pdfsharpdslTests.csproj -powershell -NoProfile -File .\scripts\coverage.ps1 -``` - -**Handoff:** Record coverage and list remaining visitor or drawing gaps for TASK-004. - -## TASK-004: Visitor and drawing helpers - -**Status:** Not started -**Depends on:** TASK-003 - -**Objective:** Cover visitor dispatch, page defaults, debug options, tables, images, alignment, clipping, and width calculations. - -**Affected files:** `pdfsharpdslTests/VisitorTests.cs`, `pdfsharpdslTests/RenderingTests.cs`, `PdfSharpDslCore/Drawing/`, visitor implementation - -**Acceptance criteria:** Deterministic tests cover the listed helper paths without requiring committed generated PDFs. - -**Validation:** - -```powershell -dotnet test .\pdfsharpdslTests\pdfsharpdslTests.csproj -powershell -NoProfile -File .\scripts\coverage.ps1 -``` - -**Handoff:** Record coverage and enumerate only the gaps needed to exceed 80% in TASK-005. - -## TASK-005: Enforce coverage - -**Status:** Not started -**Depends on:** TASK-004 - -**Objective:** Cover remaining pagination and PDF drawing paths, then enforce core line coverage of at least 90%. - -**Affected files:** Remaining core tests, `scripts/coverage.ps1`, CI workflow if present - -**Acceptance criteria:** `PdfSharpDslCore` line coverage is at least 90%, the script fails below 90%, and branch coverage remains informational. - -**Validation:** - -```powershell -powershell -NoProfile -File .\scripts\coverage.ps1 -``` - -**Handoff:** Unblock TASK-006 only after recording a passing line rate here. - -## TASK-006: Generator safety - -**Status:** Blocked by coverage -**Depends on:** TASK-005 - -**Objective:** Remove the hard-coded Debug assembly path and add a real Roslyn generator compilation test. - -**Affected files:** `PdfSharpDslCore.Generator/`, `pdfsharpdslTests/SourceGenerator/` - -**Acceptance criteria:** Generator tests compile a representative input and Debug and Release builds succeed without configuration-specific paths. - -**Validation:** - -```powershell -dotnet test .\pdfsharpdslTests\pdfsharpdslTests.csproj --filter FullyQualifiedName~SourceGenerator -dotnet build .\bnf_and_pdf.sln --configuration Debug -dotnet build .\bnf_and_pdf.sln --configuration Release -``` - -**Handoff:** Document generator loading assumptions before dependency upgrades. - -## TASK-007: Dependencies - -**Status:** Blocked by coverage -**Depends on:** TASK-006 - -**Objective:** Upgrade test, Coverlet, Roslyn, logging, image, font, and archive packages; resolve ImageSharp advisories. - -**Affected files:** Project files and package lock/restore outputs where applicable - -**Acceptance criteria:** Restored packages have no known ImageSharp advisories, core and generator retain `netstandard2.0`, and tests and coverage remain passing. - -**Validation:** - -```powershell -dotnet restore .\bnf_and_pdf.sln -dotnet list .\bnf_and_pdf.sln package --vulnerable --include-transitive -dotnet test .\pdfsharpdslTests\pdfsharpdslTests.csproj -powershell -NoProfile -File .\scripts\coverage.ps1 -``` - -**Handoff:** Record selected package versions and any .NET 10 compatibility constraints. - -## TASK-008: Framework migration - -**Status:** Blocked by coverage -**Depends on:** TASK-007 - -**Objective:** Pin .NET 10, move console and tests to `net10.0`, retain reusable projects on `netstandard2.0`, and update GitHub Actions. - -**Affected files:** `global.json`, console/test project files, `.github/workflows/` - -**Acceptance criteria:** The intended SDK is selected, console and tests target `net10.0`, core and generator target `netstandard2.0`, and workflows use `actions/setup-dotnet@v4` with `10.0.x`. - -**Validation:** - -```powershell -dotnet --version -dotnet build .\bnf_and_pdf.sln -dotnet test .\pdfsharpdslTests\pdfsharpdslTests.csproj -powershell -NoProfile -File .\scripts\coverage.ps1 -``` - -**Handoff:** Record SDK and target framework versions for package validation. - -## TASK-009: Package validation - -**Status:** Blocked by coverage -**Depends on:** TASK-008 - -**Objective:** Validate both configurations, NuGet packages, generator loading in a clean consumer, console samples, and rendering tests. - -**Affected files:** Project packaging metadata, test fixtures, console samples - -**Acceptance criteria:** Debug and Release builds pass, both packages can be packed, a clean `net10.0` consumer loads the generator, and samples/rendering tests pass. - -**Validation:** - -```powershell -dotnet build .\bnf_and_pdf.sln --configuration Debug -dotnet build .\bnf_and_pdf.sln --configuration Release -dotnet pack .\PdfSharpDslCore\PdfSharpDslCore.csproj --configuration Release -dotnet pack .\PdfSharpDslCore.Generator\PdfSharpDslCore.Generator.csproj --configuration Release -dotnet test .\pdfsharpdslTests\pdfsharpdslTests.csproj -``` - -**Handoff:** Record package paths and clean-consumer results for documentation. - -## TASK-010: Documentation - -**Status:** Blocked by coverage -**Depends on:** TASK-009 - -**Objective:** Document the completed migration, framework support, and coverage workflow. - -**Affected files:** `README.md`, `PdfSharpDslCore/Readme.md`, `CHANGELOG.md`, `tasks.md` - -**Acceptance criteria:** The root README links this tracker, supported frameworks and coverage commands are accurate, and the changelog summarizes the migration. - -**Validation:** - -```powershell -powershell -NoProfile -File .\scripts\coverage.ps1 -dotnet build .\bnf_and_pdf.sln --configuration Release -``` - -**Handoff:** Mark the roadmap complete only after all links, commands, and recorded versions are verified. \ No newline at end of file From 8d383d8d941b18276d401d32a6de29a34416b393 Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Sun, 20 Sep 2026 11:34:33 +0200 Subject: [PATCH 11/15] merge main workflow updates --- .github/workflows/build.yml | 4 ++-- .github/workflows/release-package.yml | 14 +++++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a3ddfb3..2f3ebc3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,9 +15,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v7 - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v6 with: dotnet-version: 10.0.x - name: Restore dependencies diff --git a/.github/workflows/release-package.yml b/.github/workflows/release-package.yml index 94a0a23..98d31ab 100644 --- a/.github/workflows/release-package.yml +++ b/.github/workflows/release-package.yml @@ -7,13 +7,15 @@ on: jobs: publish-package: runs-on: ubuntu-latest + environment: release permissions: packages: write contents: read + id-token: write # enable GitHub OIDC token issuance for this job steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v7 - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v6 with: dotnet-version: 10.0.x @@ -31,5 +33,11 @@ jobs: # run: dotnet nuget push "pdfsharpdsl/bin/Release/*.nupkg" --source "github" # - name: publish to nuget.org # run: nuget push "pdfsharpdsl/bin/Release/*.nupkg" -Source 'https://api.nuget.org/v3/index.json' -ApiKey ${{secrets.NUGET_API_KEY}} + # no more apikey, using GitHub Actions built-in authentication via trusted publishing + - name: NuGet login (OIDC → temp API key) + uses: NuGet/login@v1 + id: login + with: + user: ${{ secrets.NUGET_USER }} - name: Publish to NuGet - run: dotnet nuget push "PdfSharpDslCore/bin/Release/*.nupkg" -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_KEY }} --skip-duplicate + run: dotnet nuget push "PdfSharpDslCore/bin/Release/*.nupkg" --api-key ${{steps.login.outputs.NUGET_API_KEY}} --source https://api.nuget.org/v3/index.json --skip-duplicate From afd97ea421d0eee712885bc4fd05c9926c447609 Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Sun, 20 Sep 2026 11:48:14 +0200 Subject: [PATCH 12/15] docs: restore 1.0.6 section, scope TerraPDF work to 2.0.0 The branch had renamed the shipped 1.0.6 heading to "Unreleased" and appended TerraPDF entries to it. Split them back apart: 1.0.6 keeps the .NET 10 / coverage / source-generator entries released on main, and the TerraPDF migration moves under 2.0.0 with its net8.0 breaking change. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d11908..931abd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,16 @@ # Change log -## Unreleased -* Migrated console and test projects to .NET 10 while retaining reusable projects on .NET Standard 2.0. -* Added reproducible core-only coverage enforcement at 90% line coverage. -* Added Roslyn source-generator compilation and clean NuGet consumer validation. +## Version 2.0.0 (unreleased) * Migrated PDF generation from PdfSharpCore and its image/font/archive dependencies to TerraPDF 2.1.0. * Added engine-independent drawing primitives and `PdfSharpDsl.Language` for the netstandard2.0 source generator. * Added `PublishPdf(Stream)`, `PublishPdf(string)` and `PublishPdf()` to `PdfDocumentDrawer`. +* **Breaking:** `PdfSharpDslCore` now targets `net8.0` (was `netstandard2.0`). + +## Version 1.0.6 (September 20, 2026) +* Migrated console and test projects to .NET 10 while retaining reusable projects on .NET Standard 2.0. +* Added reproducible core-only coverage enforcement at 90% line coverage. +* Added Roslyn source-generator compilation and clean NuGet consumer validation. ## Version 1.0.5 (March 3, 2024) * Update nugets packages and upgrade to .Net 8 From 48d39c8a8fc8926fa83e320ef4e4922722d69736 Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Sun, 20 Sep 2026 16:12:06 +0200 Subject: [PATCH 13/15] uses new version of TerraPDF --- PdfSharpDslCore/PdfSharpDslCore.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PdfSharpDslCore/PdfSharpDslCore.csproj b/PdfSharpDslCore/PdfSharpDslCore.csproj index 30b50fd..54b5437 100644 --- a/PdfSharpDslCore/PdfSharpDslCore.csproj +++ b/PdfSharpDslCore/PdfSharpDslCore.csproj @@ -24,7 +24,7 @@ - + From cc644dd1b39c3d13f5709d35e07190c7eb6488e3 Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Sun, 20 Sep 2026 16:37:22 +0200 Subject: [PATCH 14/15] setup ci with props files --- .github/workflows/release-package.yml | 13 ++++-- CHANGELOG.md | 8 +++- Directory.Build.props | 11 +++++ Directory.Build.targets | 6 +++ Directory.Packages.props | 6 +++ .../PdfSharpDsl.Language.csproj | 32 +++++++------- PdfSharpDslConsole/PdfSharpDslConsole.csproj | 8 ++-- .../PdfSharpDslCore.Generator.csproj | 28 +++++------- PdfSharpDslCore/PdfSharpDslCore.csproj | 39 ++++++---------- PdfSharpDslCore/Readme.md | 2 +- README.md | 3 +- _build/Common.props | 44 +++++++++++++++++++ _build/Common.targets | 11 +++++ _build/Packages.props | 33 ++++++++++++++ _build/Version.props | 13 ++++++ bnf_and_pdf.sln | 8 ++++ pdfsharpdslTests/pdfsharpdslTests.csproj | 17 ++++--- 17 files changed, 204 insertions(+), 78 deletions(-) create mode 100644 Directory.Build.props create mode 100644 Directory.Build.targets create mode 100644 Directory.Packages.props create mode 100644 _build/Common.props create mode 100644 _build/Common.targets create mode 100644 _build/Packages.props create mode 100644 _build/Version.props diff --git a/.github/workflows/release-package.yml b/.github/workflows/release-package.yml index 98d31ab..ccf2e35 100644 --- a/.github/workflows/release-package.yml +++ b/.github/workflows/release-package.yml @@ -19,14 +19,21 @@ jobs: with: dotnet-version: 10.0.x + # The release tag drives the published version; _build/Version.props is the fallback default. + - name: Resolve version from release tag + id: version + run: echo "version=${TAG#v}" >> "$GITHUB_OUTPUT" + env: + TAG: ${{ github.event.release.tag_name }} + - name: Restore dependencies run: dotnet restore - name: Build - run: dotnet build --no-restore + run: dotnet build --no-restore -c Release -p:Version=${{ steps.version.outputs.version }} - name: Test - run: dotnet test --no-build --verbosity normal + run: dotnet test --no-build -c Release --verbosity normal - name: pack - run: dotnet pack -c Release PdfSharpDslCore/PdfSharpDslCore.csproj + run: dotnet pack --no-build -c Release PdfSharpDslCore/PdfSharpDslCore.csproj -p:Version=${{ steps.version.outputs.version }} # - name: add publish # run: dotnet nuget add source --username pgourlain --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/pgourlain/index.json" # - name: publish package diff --git a/CHANGELOG.md b/CHANGELOG.md index 931abd7..6ba94a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,14 @@ # Change log ## Version 2.0.0 (unreleased) -* Migrated PDF generation from PdfSharpCore and its image/font/archive dependencies to TerraPDF 2.1.0. +* Migrated PDF generation from PdfSharpCore and its image/font/archive dependencies to TerraPDF 2.2.0. * Added engine-independent drawing primitives and `PdfSharpDsl.Language` for the netstandard2.0 source generator. * Added `PublishPdf(Stream)`, `PublishPdf(string)` and `PublishPdf()` to `PdfDocumentDrawer`. -* **Breaking:** `PdfSharpDslCore` now targets `net8.0` (was `netstandard2.0`). +* **Breaking:** `PdfSharpDslCore` now multi-targets `net8.0` and `net10.0` (was `netstandard2.0`). +* Centralized the build in a `_build/` folder: `Version.props` holds the single product version, `Common.props` the shared package metadata, licence and target-framework aliases, and `Packages.props` every NuGet version (central package management). `Directory.Build.props`, `Directory.Build.targets` and `Directory.Packages.props` at the repository root are thin stubs that import them. +* All assemblies now ship the same version. `PdfSharpDslCore.Generator` moves from `1.0.2` to `2.0.0` and `PdfSharpDslConsole` no longer carries its own `0.1.0`. +* The release workflow now derives the published package version from the GitHub release tag, and builds, tests and packs in `Release` so the tested binaries are the ones packed. +* Updated `Microsoft.Extensions.Logging.Abstractions` and `Microsoft.Extensions.Logging.Console` to 10.0.12. ## Version 1.0.6 (September 20, 2026) * Migrated console and test projects to .NET 10 while retaining reusable projects on .NET Standard 2.0. diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..157c6e2 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,11 @@ + + + + + + + diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 0000000..cddace9 --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,6 @@ + + + + + + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..922f068 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,6 @@ + + + + + + diff --git a/PdfSharpDsl.Language/PdfSharpDsl.Language.csproj b/PdfSharpDsl.Language/PdfSharpDsl.Language.csproj index 500aa65..b13dffc 100644 --- a/PdfSharpDsl.Language/PdfSharpDsl.Language.csproj +++ b/PdfSharpDsl.Language/PdfSharpDsl.Language.csproj @@ -1,19 +1,19 @@ - - netstandard2.0 - 10.0 - enable - false - - - - - + + $(NetStandardTfm) + false + - - - - - - \ No newline at end of file + + + + + + + + + + + + diff --git a/PdfSharpDslConsole/PdfSharpDslConsole.csproj b/PdfSharpDslConsole/PdfSharpDslConsole.csproj index 78e426c..6849a12 100644 --- a/PdfSharpDslConsole/PdfSharpDslConsole.csproj +++ b/PdfSharpDslConsole/PdfSharpDslConsole.csproj @@ -2,11 +2,9 @@ Exe - net10.0 + $(NetAppTfm) enable - enable - 0.1.0 - + false true $(MSBuildThisFileDirectory)\Generated @@ -14,7 +12,7 @@ - + diff --git a/PdfSharpDslCore.Generator/PdfSharpDslCore.Generator.csproj b/PdfSharpDslCore.Generator/PdfSharpDslCore.Generator.csproj index 6adf390..8eee82a 100644 --- a/PdfSharpDslCore.Generator/PdfSharpDslCore.Generator.csproj +++ b/PdfSharpDslCore.Generator/PdfSharpDslCore.Generator.csproj @@ -1,39 +1,35 @@ - + - netstandard2.0 + $(NetStandardTfm) true + 8.0 + + disable Pdf Generation using source generation - 1.0.2 - Pierrick Gourlain - - https://github.com/pgourlain/bnf_and_pdf - LICENSE.md false false + + false true true true - - - - - - - - + + + + - + diff --git a/PdfSharpDslCore/PdfSharpDslCore.csproj b/PdfSharpDslCore/PdfSharpDslCore.csproj index 54b5437..834cf38 100644 --- a/PdfSharpDslCore/PdfSharpDslCore.csproj +++ b/PdfSharpDslCore/PdfSharpDslCore.csproj @@ -1,30 +1,19 @@ - + - net8.0 + $(NetLibTfms) Readme.md - 9.0 - enable Generate PDF using DSL PdfSharpDslCore - 2.0.0 - Pierrick Gourlain - true A DSL using TerraPDF to generate PDF - https://github.com/pgourlain/bnf_and_pdf Icon.jpg - LICENSE.md - - True - \ - - - - + + + @@ -39,15 +28,15 @@ - - - True - \ - - - True - \ - + + + + diff --git a/PdfSharpDslCore/Readme.md b/PdfSharpDslCore/Readme.md index da8c630..ef67f90 100644 --- a/PdfSharpDslCore/Readme.md +++ b/PdfSharpDslCore/Readme.md @@ -3,7 +3,7 @@ Package to print PDF using a specific DSL, using Irony.Net and TerraPDF. -The renderer targets `net8.0`. `PdfSharpDsl.Language` and the source generator target `netstandard2.0` and can be consumed by modern .NET applications. +The renderer multi-targets `net8.0` and `net10.0`. `PdfSharpDsl.Language` and the source generator target `netstandard2.0` and can be consumed by modern .NET applications. The repository enforces at least 90% line coverage for `PdfSharpDslCore` through `scripts\coverage.ps1`. diff --git a/README.md b/README.md index 6cf474d..76e2742 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,10 @@ This is a sample library that uses [Irony.Net](https://github.com/IronyProject/I ## Current support -- `PdfSharpDslCore` targets `net8.0`; `PdfSharpDsl.Language` and `PdfSharpDslCore.Generator` target `netstandard2.0`. +- `PdfSharpDslCore` multi-targets `net8.0` and `net10.0`; `PdfSharpDsl.Language` and `PdfSharpDslCore.Generator` target `netstandard2.0` because they are loaded by the compiler. - `PdfSharpDslConsole` and the test project target `net10.0`. - The repository is pinned to SDK `10.0.400` in `global.json`. +- Version, licence, package metadata and every NuGet version are centralized in [`_build/`](_build/): `Version.props` (the single product version), `Common.props` (shared metadata and target-framework aliases) and `Packages.props` (central package management). The `Directory.Build.props`, `Directory.Build.targets` and `Directory.Packages.props` files at the repository root only import these. Change a version or a target framework there, never in an individual `.csproj`. - The generator package includes its analyzer dependencies and supports clean NuGet consumer builds. Run the full test and core coverage gate with: diff --git a/_build/Common.props b/_build/Common.props new file mode 100644 index 0000000..b9b4ba7 --- /dev/null +++ b/_build/Common.props @@ -0,0 +1,44 @@ + + + + + + net8.0;net10.0 + + net10.0 + + netstandard2.0 + + + + + latest + enable + + + + + Pierrick Gourlain + Pierrick Gourlain + Copyright (c) 2022-2026 Pierrick Gourlain + https://github.com/pgourlain/bnf_and_pdf + https://github.com/pgourlain/bnf_and_pdf + git + pdf;dsl;irony;terrapdf;pdf-generation;source-generator + LICENSE.md + + + + + true + true + snupkg + true + + + + + diff --git a/_build/Common.targets b/_build/Common.targets new file mode 100644 index 0000000..c1d6e77 --- /dev/null +++ b/_build/Common.targets @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/_build/Packages.props b/_build/Packages.props new file mode 100644 index 0000000..d680db6 --- /dev/null +++ b/_build/Packages.props @@ -0,0 +1,33 @@ + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_build/Version.props b/_build/Version.props new file mode 100644 index 0000000..cf354fc --- /dev/null +++ b/_build/Version.props @@ -0,0 +1,13 @@ + + + + + 2.0.0 + + + + diff --git a/bnf_and_pdf.sln b/bnf_and_pdf.sln index 6ce8b80..cb64dd7 100644 --- a/bnf_and_pdf.sln +++ b/bnf_and_pdf.sln @@ -13,7 +13,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .gitignore = .gitignore CHANGELOG.md = CHANGELOG.md + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets + Directory.Packages.props = Directory.Packages.props + global.json = global.json README.md = README.md + _build\Common.props = _build\Common.props + _build\Common.targets = _build\Common.targets + _build\Packages.props = _build\Packages.props + _build\Version.props = _build\Version.props EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharpDslCore.Generator", "PdfSharpDslCore.Generator\PdfSharpDslCore.Generator.csproj", "{A6092AA9-EC00-4C4F-AF43-E039E400CC3B}" diff --git a/pdfsharpdslTests/pdfsharpdslTests.csproj b/pdfsharpdslTests/pdfsharpdslTests.csproj index c721efb..6e511b9 100644 --- a/pdfsharpdslTests/pdfsharpdslTests.csproj +++ b/pdfsharpdslTests/pdfsharpdslTests.csproj @@ -1,24 +1,23 @@ - net10.0 + $(NetAppTfm) enable - enable false - - - - - - + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all From 5751e45cbd9f4cb87b79eddac87c43d6a5aa594e Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Sun, 20 Sep 2026 16:48:03 +0200 Subject: [PATCH 15/15] fix test failed after using binary TerraPDF --- pdfsharpdslTests/PdfDocumentDrawerTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pdfsharpdslTests/PdfDocumentDrawerTests.cs b/pdfsharpdslTests/PdfDocumentDrawerTests.cs index cfe267f..4a5e8f2 100644 --- a/pdfsharpdslTests/PdfDocumentDrawerTests.cs +++ b/pdfsharpdslTests/PdfDocumentDrawerTests.cs @@ -16,8 +16,8 @@ public void DslOrientationsProduceRotatedTextMatrices() new PdfSharpDslCore.Parser.PdfDrawerVisitor().Draw(drawer, tree); var content = ReadContent(drawer.PublishPdf()); - Assert.Matches(@"0\.00 -1\.00 1\.00 0\.00 220\.00 [\d.]+ Tm\n\(vertical\) Tj", content); - Assert.Matches(@"0\.87 -0\.50 0\.50 0\.87 300\.00 [\d.]+ Tm\n\(30 degree rotation\) Tj", content); + Assert.Matches(@"0\.000000 -1\.000000 1\.000000 0\.000000 220\.00 [\d.]+ Tm\n\(vertical\) Tj", content); + Assert.Matches(@"0\.866025 -0\.500000 0\.500000 0\.866025 300\.00 [\d.]+ Tm\n\(30 degree rotation\) Tj", content); } [Fact]