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 6a2c8ad..6ba94a0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,12 +1,21 @@
# Change log
-## Version 1.0.6 (September 20, 2024)
+## Version 2.0.0 (unreleased)
+* 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 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.
* 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
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/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/PdfSharpDsl.Language/Extensions/ParseTreeNodeExtensions.cs b/PdfSharpDsl.Language/Extensions/ParseTreeNodeExtensions.cs
new file mode 100644
index 0000000..90fdb21
--- /dev/null
+++ b/PdfSharpDsl.Language/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/PdfSharpDsl.Language/Parser/PdfGrammar.cs b/PdfSharpDsl.Language/Parser/PdfGrammar.cs
new file mode 100644
index 0000000..4ee099f
--- /dev/null
+++ b/PdfSharpDsl.Language/Parser/PdfGrammar.cs
@@ -0,0 +1,502 @@
+
+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 TableCellColSpan = new NonTerminal("TableCellColSpan");
+ var TableCellRowSpan = new NonTerminal("TableCellRowSpan");
+
+ 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");
+ 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";
+ 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
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..b13dffc
--- /dev/null
+++ b/PdfSharpDsl.Language/PdfSharpDsl.Language.csproj
@@ -0,0 +1,19 @@
+
+
+
+ $(NetStandardTfm)
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/PdfSharpDslConsole.csproj b/PdfSharpDslConsole/PdfSharpDslConsole.csproj
index 621402a..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 @@
-
+
@@ -57,6 +55,9 @@
PreserveNewest
+
+ PreserveNewest
+
diff --git a/PdfSharpDslConsole/Program.cs b/PdfSharpDslConsole/Program.cs
index 55a1491..bbdcfec 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[]
{
@@ -95,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));
@@ -115,11 +108,14 @@
}
else
{
- GlobalFontSettings.FontResolver = new MyFontResolver(LocalFontFiles());
- //PdfSharpCore cclasses
- var document = new PdfDocument();
+ RegisterSystemFonts();
+ 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 +128,8 @@
visitor.RegisterFormulaFunction("GETCOMMENTAUTHOR", getCommentAuthor);
visitor.Draw(drawer, parsingResult);
- document.Save("helloworld.pdf");
+ drawer.PublishPdf(outputFile);
+ Console.WriteLine($"PDF generated: {outputFile}");
//var a = new PDfDsl.pdfsharp();
//a.WritePdf(drawer);
@@ -140,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()
@@ -158,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/PdfSharpDslConsole/helloworld-with-pdfsharp.pdf b/PdfSharpDslConsole/helloworld-with-pdfsharp.pdf
new file mode 100644
index 0000000..01f4ce0
Binary files /dev/null and b/PdfSharpDslConsole/helloworld-with-pdfsharp.pdf differ
diff --git a/PdfSharpDslConsole/helloworld.pdf b/PdfSharpDslConsole/helloworld.pdf
index 8044ef3..1f4a2fa 100644
Binary files a/PdfSharpDslConsole/helloworld.pdf and b/PdfSharpDslConsole/helloworld.pdf differ
diff --git a/PdfSharpDslCore.Generator/DrawingGenerator/CSharpDrawer.cs b/PdfSharpDslCore.Generator/DrawingGenerator/CSharpDrawer.cs
deleted file mode 100644
index 0ebfd60..0000000
--- a/PdfSharpDslCore.Generator/DrawingGenerator/CSharpDrawer.cs
+++ /dev/null
@@ -1,186 +0,0 @@
-using PdfSharpCore;
-using PdfSharpCore.Drawing;
-using PdfSharpDslCore.Drawing;
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace PdfSharpDslCore.Generator.DrawingGenerator
-{
- internal class CSharpDrawer : IPdfDocumentDrawer
- {
- StringBuilder _code;
- string _drawerPrefix;
- int vNameIndex = 1;
- public CSharpDrawer(StringBuilder code, string drawerPrefix)
- {
- _code = code;
- _drawerPrefix = drawerPrefix;
- }
- public XPen CurrentPen
- {
- get => 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 31c8db4..8eee82a 100644
--- a/PdfSharpDslCore.Generator/PdfSharpDslCore.Generator.csproj
+++ b/PdfSharpDslCore.Generator/PdfSharpDslCore.Generator.csproj
@@ -1,54 +1,46 @@
-
+
- netstandard2.0
+ $(NetStandardTfm)
true
- 9.0
+
+ 8.0
+
+ disable
Pdf Generation using source generation
- 1.0.6
- Pierrick Gourlain
-
- https://github.com/pgourlain/bnf_and_pdf
- LICENSE.md
false
false
+
+ false
true
true
+ true
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
+
-
-
-
-
-
+
+
+
+
@@ -58,11 +50,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 1bc855c..a8c3527 100644
--- a/PdfSharpDslCore/Drawing/PdfDocumentDrawer.cs
+++ b/PdfSharpDslCore/Drawing/PdfDocumentDrawer.cs
@@ -1,940 +1,619 @@
-using PdfSharpCore;
-using PdfSharpCore.Drawing;
-using PdfSharpCore.Drawing.Layout;
-using PdfSharpCore.Drawing.Layout.enums;
-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(ScalePen(CurrentPen, page), 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 => 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)));
}
- public void DrawRect(double x, double y, double w, double h, bool isFilled)
+ private static void DrawStyledLine(VectorCanvas canvas, PdfPen pen, double x, double y, double x1, double y1)
{
- (x, y, w, h) = CurrentPage.CoordRectToPage(x, y, w, h);
- InternalDrawRect(CurrentPen, CurrentBrush, x, y, w, h, isFilled);
+ canvas.Line(x, y, x1, y1, pen.Color.Hex, pen.Width, pen.Color.Opacity, GetDashPattern(pen));
}
- private void InternalDrawRect(XPen pen, XBrush brush, double x, double y, double w, double h, bool isFilled)
+ private static double[]? GetDashPattern(PdfPen pen)
{
- if (isFilled)
- {
- Gfx.DrawRectangle(pen, brush, x, y, w, h);
- }
- else
+ var width = Math.Max(pen.Width, 0.1);
+ return pen.DashStyle switch
{
- 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));
+ 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 DrawEllipse(double x, double y, double w, double h, bool isFilled)
+ public void DrawRect(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);
+ InternalDrawRect(ScalePen(CurrentPen, page), 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 InternalDrawRect(PdfPen pen, PdfBrush brush, double x, double y, double w, double h, bool isFilled)
{
- if (isFilled)
+ var dashPattern = GetDashPattern(pen);
+ AddCommand(canvas =>
{
- Gfx.DrawEllipse(pen, brush, r);
- }
- else
- {
- Gfx.DrawEllipse(pen, r);
- }
- this._drawingCtx.PushInstruction((oy) => InternalDrawEllipse(isFilled, r.OffsetY(oy), pen, brush), r);
+ 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, dashPattern);
+ });
+ _drawingCtx.PushInstruction(offset => InternalDrawRect(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 DrawEllipse(double x, double y, double w, double h, bool isFilled)
{
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);
- }
- }
+ (x, y, w, h) = DrawingHelper.CoordRectToPage(page.Width, page.Height, x, y, w, h);
+ InternalDrawEllipse(ScalePen(CurrentPen, page), CurrentBrush, ScaleX(x, page), ScaleY(y, page), ScaleX(w, page), ScaleY(h, page), isFilled);
}
- public void DrawLineText(string text, double x, double y, double? w, double? h, XStringAlignment hAlign,
- XLineAlignment vAlign, TextOrientation textOrientation)
+ private void InternalDrawEllipse(PdfPen pen, PdfBrush brush, double x, double y, double w, double h, bool isFilled)
{
- (x, y, w, h) = CurrentPage.CoordRectToPage(x, y, w, h);
- var fmt = new XStringFormat
+ AddCommand(canvas =>
{
- Alignment = hAlign,
- LineAlignment = vAlign,
- };
- InternalDrawLineText(text, x, y, w, h, textOrientation, fmt, CurrentFont, CurrentBrush, HighlightBrush);
+ 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));
}
- 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 DrawText(string text, double x, double y, double? w, double? h)
{
- 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) : page.Width - x,
+ h.HasValue ? ScaleY(h.Value, page) : null, PdfHorizontalAlignment.Near, PdfVerticalAlignment.Near,
+ ScaleFont(CurrentFont, page), CurrentBrush, null);
}
- private XRect InternalDrawString(string text, double x, double y, double? w, double? h,
- XStringFormat fmt, TextOrientation textOrientation, 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 result;
- double angle = 0;
-
- if (textOrientation.Angle is not null)
- {
- angle = textOrientation.Angle.Value;
- }
- else if (textOrientation.Orientation == TextOrientationEnum.Vertical)
- {
- angle = 90;
- }
-
- 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)
+ 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, ScaleFont(CurrentFont, page), CurrentBrush, HighlightBrush,
+ textOrientation?.Angle ?? textOrientation?.Orientation switch
{
- DebugRect(hr);
- }
-
- 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;
+ TextOrientationEnum.Vertical => 90,
+ TextOrientationEnum.HorizontalInvert => 180,
+ TextOrientationEnum.VerticalInvert => 270,
+ _ => 0,
+ });
}
- public void DrawTitle(string text, double margin, XStringAlignment hAlign, XLineAlignment vAlign)
+ private void InternalDrawText(string text, double x, double y, double? w, double? h, PdfHorizontalAlignment hAlign,
+ PdfVerticalAlignment vAlign, PdfFont font, PdfBrush brush, PdfBrush? highlight, double angle = 0)
{
- var page = CurrentPage;
+ 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 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);
- var fmt = new XStringFormat
+ AddCommand(canvas =>
{
- Alignment = hAlign,
- LineAlignment = vAlign,
- };
+ if (highlight is not null)
+ canvas.FillRect(textRect.X, textRect.Y, textRect.Width, textRect.Height, highlight.Color.Hex, highlight.Color.Opacity);
- var textSize = Gfx.MeasureString(text, CurrentFont, XStringFormats.TopLeft);
- if (margin < 0)
- {
- margin = page.Height - textSize.Height + margin;
- }
-
- var r = new XRect(0, margin, page.Width, textSize.Height);
- InternalDrawText(text, r, fmt, textSize, CurrentFont, CurrentBrush, HighlightBrush);
- }
-
- 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)
- {
- var debugRect = DrawingHelper.RectFromStringFormat(r, textSize, fmt);
- DebugRect(debugRect);
- }
-
- if (hb == null) return;
-
- var hr = DrawingHelper.RectFromStringFormat(r, textSize, fmt);
- Gfx.DrawRectangle(hb, hr);
- }
-
- public void DrawTable(double x, double y, TableDefinition tblDef)
- {
- var availableWidth = PageWidth - x;
- Gfx.Save();
- try
- {
- //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)
+ for (var index = 0; index < lines.Count; index++)
{
- 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])
+ var line = lines[index];
+ if (string.IsNullOrWhiteSpace(line)) continue;
+ var lineWidth = MeasureText(line, font);
+ var lineX = w.HasValue
+ ? hAlign switch
{
- column.DesiredWidth = measure.Width + margins.Left + margins.Right;
+ PdfHorizontalAlignment.Center => rect.X + (rect.Width - lineWidth) / 2,
+ PdfHorizontalAlignment.Far => rect.Right - lineWidth,
+ _ => rect.X,
}
-
- if (calcHeaderHeight)
+ : hAlign switch
{
- tblDef.HeaderHeight = Math.Max(tblDef.HeaderHeight ?? 0,
- measure.Height + margins.Top + margins.Bottom);
- }
- }
-
- i++;
+ 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,
+ angle);
}
+ });
- var placements = LayoutTableCells(tblDef);
-
- //measure all rows
- var sizeFormatter = new XTextSegmentFormatter(Gfx)
- {
- Alignment = XParagraphAlignment.Left
- };
- for (var rowIndex = 0; rowIndex < tblDef.Rows.Count; rowIndex++)
- {
- var row = tblDef.Rows[rowIndex];
- var rowMeasure = row.DesiredHeight is null;
- foreach (var placement in placements)
- {
- //a spanning cell does not inflate a single column; measured separately below
- if (placement.Row != rowIndex || placement.ColumnSpan != 1) continue;
- var colIndex = placement.Column;
- var text = placement.Cell.Text;
- var testSize = !colMeasure[colIndex];
- var pageSpaceLeft = tblDef.ColMaxWidth(colIndex, availableWidth);
- var column = tblDef.Columns[colIndex];
- if (colMeasure[colIndex])
- {
- var cSize = Gfx.MeasureString(text, xFonts[colIndex]);
- 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 && placement.RowSpan == 1)
- {
- row.DesiredHeight = Math.Max(row.DesiredHeight ?? 0,
- cSize.Height + margins.Top + margins.Bottom);
- }
- }
-
- if (!testSize) continue;
- var w = Math.Max(0, Math.Min(column.DesiredWidth ?? 0, pageSpaceLeft));
- var measure = SafeCalculateTextSize(sizeFormatter, text, xFonts[colIndex], defaultBrush, w);
- if (rowMeasure && placement.RowSpan == 1)
- {
- row.DesiredHeight = Math.Max(row.DesiredHeight ?? 0,
- measure.Height + margins.Top + margins.Bottom);
- }
- }
- }
-
- //column widths are final: build x-offsets, then grow rows for cells spanning columns and/or rows
- var colX = new double[tblDef.Columns.Count + 1];
- for (i = 0; i < tblDef.Columns.Count; i++)
- {
- colX[i + 1] = colX[i] + tblDef.Columns[i].DrawWidth;
- }
-
- foreach (var placement in placements)
- {
- if (placement.ColumnSpan == 1 && placement.RowSpan == 1) continue;
- var spanWidth = colX[placement.Column + placement.ColumnSpan] - colX[placement.Column];
- var w = Math.Max(0, spanWidth - margins.Left - margins.Right);
- var measure = SafeCalculateTextSize(sizeFormatter, placement.Cell.Text, xFonts[placement.Column],
- defaultBrush, w);
- var requiredHeight = measure.Height + margins.Top + margins.Bottom;
- var spannedRows = tblDef.Rows.Skip(placement.Row).Take(placement.RowSpan).ToArray();
- var missingHeight = requiredHeight - spannedRows.Sum(r => r.DesiredHeight ?? 0);
- if (missingHeight > 0)
- {
- var lastSpannedRow = spannedRows[spannedRows.Length - 1];
- lastSpannedRow.DesiredHeight = (lastSpannedRow.DesiredHeight ?? 0) + missingHeight;
- }
- }
+ _drawingCtx.PushInstruction(offset => InternalDrawText(text, x, y + offset, w, h, hAlign, vAlign, font, brush, highlight, angle),
+ textRect, instrName: $"DrawText({text})");
+ }
- //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;
- }
+ public void DrawTitle(string text, double margin, PdfHorizontalAlignment hAlign, PdfVerticalAlignment vAlign)
+ {
+ var height = CurrentFont.Size * 1.2;
+ if (margin < 0) margin = PageHeight - height + margin;
+ InternalDrawText(text, 0, margin, PageWidth, height, hAlign, vAlign, CurrentFont, CurrentBrush, HighlightBrush);
+ }
- 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++;
- }
+ 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 cells = LayoutTableCells(table);
- offsetY = tblDef.HeaderHeight ?? 0;
- //draw body; a rowspan/colspan cell is drawn once, at the row/column where it starts
- for (var rowIndex = 0; rowIndex < tblDef.Rows.Count; rowIndex++)
- {
- var row = tblDef.Rows[rowIndex];
- var rowPlacements = placements.Where(p => p.Row == rowIndex).ToArray();
- //a row fully covered by a rowspan started above has no placements of its own: never split a rowspan block
- if (rowPlacements.Length > 0)
- {
- var blockHeight = rowPlacements.Max(p =>
- tblDef.Rows.Skip(p.Row).Take(p.RowSpan).Sum(sr => sr.DesiredHeight ?? 0));
- if (y + offsetY + blockHeight > CurrentPage.Height)
- {
- Gfx.Restore();
- NewPage();
- Gfx.Save();
- //TODO: set top margin
- y = 1;
- offsetY = 0;
- }
- }
+ for (var index = 0; index < table.Columns.Count; index++)
+ {
+ var column = table.Columns[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)
+ + 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 placement in rowPlacements)
+ for (var rowIndex = 0; rowIndex < table.Rows.Count; rowIndex++)
+ {
+ var row = table.Rows[rowIndex];
+ var contentHeight = cells.Where(cell => cell.Row == rowIndex && cell.Cell.RowSpan == 1)
+ .Select(cell =>
{
- DrawTableCell(x, y + offsetY, placement, tblDef, xFonts, defaultBrush, colX);
- }
-
- offsetY += row.DesiredHeight ?? 0;
- }
+ 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;
}
- finally
+
+ foreach (var cell in cells.Where(cell => cell.Cell.RowSpan > 1))
{
- Gfx.Restore();
+ 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;
}
- }
- private sealed class TableCellPlacement
- {
- public TableCellPlacement(int row, int column, int columnSpan, int rowSpan, CellDefinition cell)
+ if (y + (table.HeaderHeight ?? 0) > PageHeight) { NewPage(); y = 1; }
+ var offsetY = 0d;
+ if (table.ShowHeader)
{
- Row = row;
- Column = column;
- ColumnSpan = columnSpan;
- RowSpan = rowSpan;
- Cell = cell;
+ DrawTableRow(x, y, table.Columns.Select(column => column.ColumnHeaderName).ToArray(), table.HeaderHeight ?? 0, table, fonts, table.HeaderBackColor);
+ offsetY = table.HeaderHeight ?? 0;
}
- public int Row { get; }
- public int Column { get; }
- public int ColumnSpan { get; }
- public int RowSpan { get; }
- public CellDefinition Cell { get; }
+ for (var rowIndex = 0; rowIndex < table.Rows.Count; rowIndex++)
+ {
+ var row = table.Rows[rowIndex];
+ var height = row.DesiredHeight ?? 0;
+ 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 columnCount = table.Columns.Count;
- var occupiedUntilRow = new int[columnCount];
+ 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 < columnCount && occupiedUntilRow[columnIndex] > rowIndex)
+ while (columnIndex < table.Columns.Count && occupiedUntilRow[columnIndex] > rowIndex)
columnIndex++;
- if (columnIndex >= columnCount) break;
-
- var columnSpan = Math.Min(Math.Max(1, cell.ColumnSpan), columnCount - columnIndex);
- var rowSpan = Math.Min(Math.Max(1, cell.RowSpan), table.Rows.Count - rowIndex);
- result.Add(new TableCellPlacement(rowIndex, columnIndex, columnSpan, rowSpan, cell));
- for (var c = columnIndex; c < columnIndex + columnSpan; c++)
- occupiedUntilRow[c] = rowIndex + rowSpan;
- columnIndex += columnSpan;
- }
-
- //keep drawing borders for any column this (short) row never reached
- for (var c = 0; c < columnCount; c++)
- {
- if (occupiedUntilRow[c] > rowIndex) continue;
- result.Add(new TableCellPlacement(rowIndex, c, 1, 1, new CellDefinition()));
- occupiedUntilRow[c] = rowIndex + 1;
+ 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,
- XFont[] fonts, XBrush defaultBrush, double[] colX)
+ private void DrawTableCell(double x, double y, TableCellPlacement placement, TableDefinition table, PdfFont[] fonts)
{
var column = table.Columns[placement.Column];
- var margins = table.CellMargin;
- var w = colX[placement.Column + placement.ColumnSpan] - colX[placement.Column];
- var h = table.Rows.Skip(placement.Row).Take(placement.RowSpan).Sum(r => r.DesiredHeight ?? 0);
- var r = new XRect(x + colX[placement.Column], y, w, h);
- ResetClip();
- Gfx.DrawRectangle(CurrentPen, column.BackColor, r);
- var hMargin = margins.Left + margins.Right;
- var vMargin = margins.Top + margins.Bottom;
- var rText = new XRect(r.X + margins.Left, r.Y + margins.Top, w - hMargin, h - vMargin);
- Gfx.IntersectClip(rText);
- var fmt = new XStringFormat
+ 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)
{
- Alignment = placement.Cell.HorizontalAlignment ?? column.Alignment,
- LineAlignment = placement.Cell.VerticalAlignment ?? XLineAlignment.Near
- };
- DrawStringMultiline(placement.Cell.Text, fonts[placement.Column], column.Brush ?? defaultBrush, rText,
- fmt);
- }
-
- ///
- /// Some fallback fonts (e.g. a missing "Arial" on Linux CI resolving to a font with degenerate metrics)
- /// make PdfSharpCore's own wrap-height computation go negative and throw. Fall back to an unwrapped
- /// measurement, which does not exercise that code path, rather than crash the whole table.
- ///
- private XSize SafeCalculateTextSize(XTextSegmentFormatter formatter, string text, XFont font, XBrush brush,
- double width)
- {
- try
- {
- return formatter.CalculateTextSize(text, font, brush, width);
- }
- catch (ArgumentException)
- {
- return Gfx.MeasureString(text, font);
+ var previousBrush = CurrentBrush;
+ CurrentBrush = column.BackColor;
+ DrawRect(x + offsetX, y, width, height, true);
+ CurrentBrush = previousBrush;
}
- }
+ else DrawRect(x + offsetX, y, width, height, false);
- 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();
+ 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 void DrawStringMultiline(string text, XFont xFont, XBrush xBrush, XRect r, XStringFormat fmt)
+ 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 formatter = new XTextFormatter(Gfx)
+ var offsetX = 0d;
+ for (var index = 0; index < table.Columns.Count; index++)
{
- Alignment = fmt.Alignment switch
- {
- XStringAlignment.Center => XParagraphAlignment.Center,
- XStringAlignment.Far => XParagraphAlignment.Right,
- _ => XParagraphAlignment.Left
- },
- VerticalAlignment = fmt.LineAlignment switch
+ var column = table.Columns[index];
+ var width = column.DrawWidth;
+ var background = rowBackground ?? column.BackColor;
+ if (background is not null)
{
- XLineAlignment.Center => XVerticalAlignment.Middle,
- XLineAlignment.Far => XVerticalAlignment.Bottom,
- _ => XVerticalAlignment.Top
+ var previousBrush = CurrentBrush;
+ CurrentBrush = background;
+ DrawRect(x + offsetX, y, width, height, true);
+ CurrentBrush = previousBrush;
}
- };
- formatter.DrawString(text, xFont, xBrush, r);
+ else DrawRect(x + offsetX, y, width, height, false);
+
+ 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;
+ }
}
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);
+ InternalDrawLine(ScalePen(CurrentPen, page), ScaleX(_currentPoint.X, page), ScaleY(_currentPoint.Y, page),
+ ScaleX(endPoint.X, page), ScaleY(endPoint.Y, page));
_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));
+ 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 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;
+ var width = w ?? 0;
+ var height = h ?? 0;
+ if (width <= 0 || height <= 0)
+ throw new ArgumentOutOfRangeException(nameof(w), "Pie dimensions must be positive.");
+ InternalDrawPie(ScalePen(CurrentPen, page), CurrentBrush, ScaleX(x, page), ScaleY(y, page),
+ ScaleX(width, page), ScaleY(height, page), startAngle, sweepAngle, isFilled);
}
- private void InternalDrawImage(XImage image, double x, double y, double? w, double? h, bool cropImage)
+ private void InternalDrawPie(PdfPen pen, PdfBrush brush, double x, double y, double width, double height,
+ double startAngle, double sweepAngle, bool isFilled)
{
- var ow = w;
- var oh = h;
- if (w is null && h is null)
+ AddCommand(canvas =>
{
- Gfx.DrawImage(image, x, y);
- w = image.PointWidth;
- h = image.PointHeight;
- }
- else
- {
- 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);
- }
+ if (isFilled)
+ canvas.DrawPie(x, y, width, height, startAngle, sweepAngle,
+ brush.Color.Hex, pen.Color.Hex, pen.Width, brush.Color.Opacity);
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));
+ 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 DrawPie(double x, double y, double? w, double? h, double startAngle, double sweepAngle,
- bool isFilled)
+ public void DrawPolygon(IEnumerable points, bool isFilled)
{
- InternalDrawPie(x, y, w, h, startAngle, sweepAngle, isFilled, CurrentPen, CurrentBrush);
+ 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(ScalePen(CurrentPen, page), CurrentBrush, transformed, isFilled);
}
- private void InternalDrawPie(double x, double y, double? w, double? h, double startAngle, double sweepAngle,
- bool isFilled, XPen pen, XBrush brush)
+ private void InternalDrawPolygon(PdfPen pen, PdfBrush brush, PdfPoint[] points, bool isFilled)
{
- if (isFilled)
+ var tuples = points.Select(point => (point.X, point.Y)).ToArray();
+ AddCommand(canvas => canvas.Path(path =>
{
- 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));
+ 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 => InternalDrawPolygon(pen, brush, points.Select(point => point.OffsetY(offset)).ToArray(), isFilled), points);
}
- public void DrawPolygon(IEnumerable points, bool isFilled)
+ public void BeginDrawRowTemplate(string name, int index, double offsetY, double newPageTopMargin)
{
- var ptArray = points.ToArray();
- InternalDrawPolygon(isFilled, ptArray, CurrentPen, CurrentBrush);
+ _drawingCtx.OpenBlock($"{name}:{index}", offsetY, newPageTopMargin);
+ _measurementStates.Push(_isMeasuring);
+ _isMeasuring = true;
}
- private void InternalDrawPolygon(bool isFilled, XPoint[] ptArray, XPen pen, XBrush brush)
+ public DrawingResult EndDrawRowTemplate(int index)
{
- 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);
+ 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 BeginIterationTemplate(int rowCount) { }
+ public void EndIterationTemplate(double drawHeight) { }
- public void BeginDrawRowTemplate(string name, int index, double offsetY, double newPageTopMargin)
+ public void RegisterOnNewPage(Action callback)
{
- //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 (callback is not null && !_onNewPageHooks.Contains(callback)) _onNewPageHooks.Add(callback);
}
- public DrawingResult EndDrawRowTemplate(int index)
+ public void UnRegisterOnNewPage(Action callback) => _onNewPageHooks.Remove(callback);
+
+ private void AddCommand(Action command)
{
- 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 (!_isMeasuring) CurrentPage.Commands.Add(command);
+ }
- 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}");
- }
- }
+ private static double MeasureText(string text, PdfFont font)
+ {
+ if (string.IsNullOrWhiteSpace(text)) return 0;
+ return VectorCanvas.MeasureTextWidth(text, font.Size, font.FamilyName, font.Style.HasFlag(PdfFontStyle.Bold), font.Style.HasFlag(PdfFontStyle.Italic));
+ }
- this._drawingCtx.CloseBlock();
+ private static double MeasureCellWidth(string text, PdfFont font) =>
+ text.Replace("\r\n", "\n").Split('\n').Select(line => MeasureText(line, font)).DefaultIfEmpty(0).Max();
- return new()
- {
- DrawingRect = result,
- PageOffsetY = newPageOffsetY
- };
- }
+ private static int MeasureCellLineCount(string text, double width, PdfFont font) =>
+ WrapText(text, width, font).Count;
- 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 PdfPen ScalePen(PdfPen pen, RecordedPage page)
{
+ var scaled = new PdfPen(pen.Color, pen.Width * ScaleFactor(page)) { DashStyle = pen.DashStyle };
+ return scaled;
}
- private void DebugText(string text, double x, double y)
- {
- var fmt = new XStringFormat()
- {
- Alignment = XStringAlignment.Near,
- LineAlignment = XLineAlignment.Near
- };
+ private static PdfFont ScaleFont(PdfFont font, RecordedPage page) =>
+ new(font.FamilyName, font.Size * ScaleFactor(page), font.Style);
- Gfx.DrawString(text, _debugFont.Value, XBrushes.Red, x, y, fmt);
- }
- private void DebugRect(XRect rect)
- {
- Gfx.DrawRectangle(_debugPen, rect);
- }
+ private static double ScaleFactor(RecordedPage page) =>
+ Math.Sqrt(page.ScaleX * page.ScaleY);
- public void RegisterOnNewPage(Action callback)
+ private static (double Width, double Height) GetPageDimensions(PdfPageSize size, PdfPageOrientation orientation)
{
- if (callback != null && !_onNewPageHooks.Contains(callback))
+ var dimensions = size switch
{
- _onNewPageHooks.Add(callback);
- }
+ 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),
+ };
+ return orientation == PdfPageOrientation.Landscape ? (dimensions.Item2, dimensions.Item1) : dimensions;
}
- 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 b32ab07..e99609c 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();
@@ -33,10 +31,10 @@ public double ColWidth(int i)
///
public double ColMaxWidth(int i, double pageWidth)
{
- return Math.Max(0, Math.Min(Columns[i].MaxWidth ?? pageWidth, 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
{
@@ -73,12 +71,12 @@ 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 List Cells { get; set; } = new();
}
public class CellDefinition
@@ -86,7 +84,7 @@ public class CellDefinition
public string Text { get; set; } = string.Empty;
public int ColumnSpan { get; set; } = 1;
public int RowSpan { get; set; } = 1;
- public XStringAlignment? HorizontalAlignment { get; set; }
- public XLineAlignment? VerticalAlignment { get; set; }
+ public PdfHorizontalAlignment? HorizontalAlignment { get; set; }
+ public PdfVerticalAlignment? VerticalAlignment { get; set; }
}
}
\ No newline at end of file
diff --git a/PdfSharpDslCore/Extensions/ParseTreeNodeExtensions.cs b/PdfSharpDslCore/Extensions/ParseTreeNodeExtensions.cs
index f50c723..90fdb21 100644
--- a/PdfSharpDslCore/Extensions/ParseTreeNodeExtensions.cs
+++ b/PdfSharpDslCore/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/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 bd70d7f..12ce808 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);
@@ -615,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)
@@ -640,8 +662,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
@@ -651,45 +673,17 @@ private void GenerateTableHead(IEnumerable nodes, TableDefinition
}
}
- private CellDefinition ParseTableCell(ParseTreeNode node)
- {
- var cell = new CellDefinition
- {
- Text = EvaluateForObject(node.ChildNodes.Last())?.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");
- var (hAlign, vAlign) = ParseTextAlignment(hNode, vNode);
- if (hNode?.ChildNodes.Count > 2) cell.HorizontalAlignment = hAlign;
- if (vNode?.ChildNodes.Count > 2) cell.VerticalAlignment = vAlign;
- }
-
- 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 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)
@@ -697,10 +691,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;
}
}
@@ -711,10 +705,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;
}
@@ -796,7 +790,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;
@@ -815,7 +809,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/Parser/PdfGrammar.cs b/PdfSharpDslCore/Parser/PdfGrammar.cs
index 35aeda7..7c15cb8 100644
--- a/PdfSharpDslCore/Parser/PdfGrammar.cs
+++ b/PdfSharpDslCore/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;
@@ -113,8 +112,6 @@ 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");
@@ -260,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}");
@@ -276,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;
@@ -305,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))
@@ -335,9 +332,7 @@ public PdfGrammar()
TableColWidth.Rule = Arg("Width") + NumberOrAuto + Arg("MaxWidth") + NumberOrAuto;
TableColList.Rule = MakeStarRule(TableColList, TableCol);
TableRow.Rule = ToTerm("ROW") + TableRowStyle + TableColList + ToTerm("ENDROW");
- TableCellColSpan.Rule = Empty | Arg("ColSpan") + number_literal;
- TableCellRowSpan.Rule = Empty | Arg("RowSpan") + number_literal;
- TableCol.Rule = ToTerm("COL") + TableCellColSpan + TableCellRowSpan + TextAlignment + FormulaExpression + semi;
+ TableCol.Rule = ToTerm("COL") + FormulaExpression + semi;
TableLocation.Rule = PointLocation /*+ "," + PointAutoLocation*/;
PointAutoLocation.Rule = NumberOrAuto + "," + NumberOrAuto;
NumberOrAuto.Rule = FormulaExpression | "auto";
diff --git a/PdfSharpDslCore/PdfSharpDslCore.csproj b/PdfSharpDslCore/PdfSharpDslCore.csproj
index a1e2aa1..834cf38 100644
--- a/PdfSharpDslCore/PdfSharpDslCore.csproj
+++ b/PdfSharpDslCore/PdfSharpDslCore.csproj
@@ -1,43 +1,42 @@
-
+
- netstandard2.0
+ $(NetLibTfms)
Readme.md
- 9.0
- enable
Generate PDF using DSL
PdfSharpDslCore
- 1.0.6
- Pierrick Gourlain
-
- A DSL using PdfSharpCore to generate PDF
- https://github.com/pgourlain/bnf_and_pdf
+ true
+ A DSL using TerraPDF to generate PDF
Icon.jpg
- LICENSE.md
-
- True
- \
-
+
+
+
+
+
+
-
-
-
-
-
+
-
- True
- \
-
-
- True
- \
-
+
+
+
+
+
+
+
+
+
+
diff --git a/PdfSharpDslCore/Readme.md b/PdfSharpDslCore/Readme.md
index f4ffd0b..ef67f90 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 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 59bb7b8..76e2742 100644
--- a/README.md
+++ b/README.md
@@ -3,13 +3,14 @@
[](https://www.nuget.org/packages/PdfSharpDslCore/)
[](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` 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:
@@ -40,12 +41,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 +59,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 +571,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/_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 b1200b8..cb64dd7 100644
--- a/bnf_and_pdf.sln
+++ b/bnf_and_pdf.sln
@@ -13,33 +13,91 @@ 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}"
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
+ 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/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 4bd2112..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;
@@ -14,15 +12,15 @@ public class GenerationTableTests : GenerationBaseTests
{
[Theory]
[InlineData("pdf1-table.txt")]
- [InlineData("pdf1-table-merged.txt")]
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..ec8d0c7 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,24 @@ 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);
+
+ Assert.True(pdf.HasPdfHeader);
+ Assert.Equal(1, pdf.PageCount);
+ 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);
- var lines = ExtractLines(p).ToArray();
- Assert.Equal(4, lines.Length);
- //TODO check values extracted from lines
+ Assert.True(pdf.HasPdfHeader);
+ Assert.Equal(1, pdf.ImageCount);
}
[Theory()]
@@ -53,12 +56,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 +75,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..4a5e8f2 100644
--- a/pdfsharpdslTests/PdfDocumentDrawerTests.cs
+++ b/pdfsharpdslTests/PdfDocumentDrawerTests.cs
@@ -1,36 +1,163 @@
-using PdfSharpCore;
-using PdfSharpCore.Pdf;
using PdfSharpDslCore.Drawing;
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\.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]
+ 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()
{
- 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/ValidInputFiles/pdf1-table-merged.txt b/pdfsharpdslTests/ValidInputFiles/pdf1-table-merged.txt
deleted file mode 100644
index 3ca9744..0000000
--- a/pdfsharpdslTests/ValidInputFiles/pdf1-table-merged.txt
+++ /dev/null
@@ -1,35 +0,0 @@
-# merged cells, per-cell alignment and multiline content
-
-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
diff --git a/pdfsharpdslTests/VisitorTests.cs b/pdfsharpdslTests/VisitorTests.cs
index bce8941..f61d7d0 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);
}
@@ -119,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()
{
@@ -145,14 +161,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);
@@ -195,47 +211,34 @@ public void TableRowTemplateBuildsRowsWidthsAndPadding()
}
[Fact]
- public void TableColParsesColSpanRowSpanAndAlignment()
+ public void TableCellsCaptureSpansAndAlignment()
{
var tree = ParseText(
"TABLE 20,30 " +
"HEAD " +
- "COL Width=40 MaxWidth=30 \"A\"; " +
- "COL Width=auto MaxWidth=100 \"B\"; " +
+ "COL Width=100 MaxWidth=100 \"A\"; " +
+ "COL Width=100 MaxWidth=100 \"B\"; " +
+ "COL Width=100 MaxWidth=100 \"C\"; " +
"ENDHEAD " +
- "ROW " +
- "COL ColSpan=2 HAlign=hcenter VAlign=vcenter \"merged\"; " +
- "ENDROW " +
- "ROW " +
- "COL RowSpan=2 \"plain\"; " +
- "COL \"formula: \"+(1+1); " +
+ "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);
- var visitor = new InspectablePdfDrawerVisitor();
- visitor.Draw(drawer.Object, tree);
+ new InspectablePdfDrawerVisitor().Draw(drawer.Object, tree);
- Assert.NotNull(capturedTable);
- var mergedCell = capturedTable.Rows[0].Cells[0];
- Assert.Equal("merged", mergedCell.Text);
+ var mergedCell = Assert.Single(capturedTable!.Rows[0].Cells, cell => cell.Text == "Merged");
Assert.Equal(2, mergedCell.ColumnSpan);
- Assert.Equal(1, mergedCell.RowSpan);
- Assert.Equal(XStringAlignment.Center, mergedCell.HorizontalAlignment);
- Assert.Equal(XLineAlignment.Center, mergedCell.VerticalAlignment);
-
- var plainCell = capturedTable.Rows[1].Cells[0];
- Assert.Equal("plain", plainCell.Text);
- Assert.Equal(1, plainCell.ColumnSpan);
- Assert.Equal(2, plainCell.RowSpan);
- Assert.Null(plainCell.HorizontalAlignment);
- Assert.Null(plainCell.VerticalAlignment);
-
- var formulaCell = capturedTable.Rows[1].Cells[1];
- Assert.Equal("formula: 2", formulaCell.Text);
+ 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]
@@ -339,8 +342,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);
diff --git a/pdfsharpdslTests/pdfsharpdslTests.csproj b/pdfsharpdslTests/pdfsharpdslTests.csproj
index ba69c1c..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
@@ -60,9 +59,6 @@
PreserveNewest
-
- PreserveNewest
-
PreserveNewest
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