From 86f4c0458f0d02cbf0a6444f596d09f8d408a1b1 Mon Sep 17 00:00:00 2001 From: Ken Hill Date: Fri, 17 Nov 2023 17:40:36 -0500 Subject: [PATCH 1/2] Support alignment and line height in TextFormatter, provide visual unit testing --- .gitignore | 5 +- .../DrawMultiLineStringWithOverflow_1.png | Bin 0 -> 11932 bytes .../DrawMultiLineStringsWithAlignment_1.png | Bin 0 -> 17863 bytes .../DrawMultiLineStringsWithLineHeight_1.png | Bin 0 -> 29390 bytes .../DrawMultilineStringWithTruncate_1.png | Bin 0 -> 9880 bytes .../Drawing/Layout/DrawSingleLineString_1.png | Bin 0 -> 2759 bytes .../Drawing/Layout/XTextFormatterTest.cs | 127 ++++++++++++++++++ PdfSharpCore.Test/Helpers/PathHelper.cs | 9 +- PdfSharpCore.Test/Helpers/PdfHelper.cs | 116 ++++++++++++++++ PdfSharpCore.Test/PdfSharpCore.Test.csproj | 26 +--- PdfSharpCore/Drawing.Layout/XTextFormatter.cs | 95 ++++++++++--- .../enums/XVerticalAlignment.cs | 9 ++ 12 files changed, 338 insertions(+), 49 deletions(-) create mode 100644 PdfSharpCore.Test/Assets/Drawing/Layout/DrawMultiLineStringWithOverflow_1.png create mode 100644 PdfSharpCore.Test/Assets/Drawing/Layout/DrawMultiLineStringsWithAlignment_1.png create mode 100644 PdfSharpCore.Test/Assets/Drawing/Layout/DrawMultiLineStringsWithLineHeight_1.png create mode 100644 PdfSharpCore.Test/Assets/Drawing/Layout/DrawMultilineStringWithTruncate_1.png create mode 100644 PdfSharpCore.Test/Assets/Drawing/Layout/DrawSingleLineString_1.png create mode 100644 PdfSharpCore.Test/Drawing/Layout/XTextFormatterTest.cs create mode 100644 PdfSharpCore.Test/Helpers/PdfHelper.cs create mode 100644 PdfSharpCore/Drawing.Layout/enums/XVerticalAlignment.cs diff --git a/.gitignore b/.gitignore index 3c4efe20..a35345fb 100644 --- a/.gitignore +++ b/.gitignore @@ -258,4 +258,7 @@ paket-files/ # Python Tools for Visual Studio (PTVS) __pycache__/ -*.pyc \ No newline at end of file +*.pyc + +# OSX +.DS_Store diff --git a/PdfSharpCore.Test/Assets/Drawing/Layout/DrawMultiLineStringWithOverflow_1.png b/PdfSharpCore.Test/Assets/Drawing/Layout/DrawMultiLineStringWithOverflow_1.png new file mode 100644 index 0000000000000000000000000000000000000000..e3ddee487755088132802284d078c56c1e426ed0 GIT binary patch literal 11932 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 literal 0 HcmV?d00001 diff --git a/PdfSharpCore.Test/Assets/Drawing/Layout/DrawMultiLineStringsWithAlignment_1.png b/PdfSharpCore.Test/Assets/Drawing/Layout/DrawMultiLineStringsWithAlignment_1.png new file mode 100644 index 0000000000000000000000000000000000000000..00b316f8ee2b9268f5d239e50bba3f865170e28b GIT binary patch literal 17863 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 D9^V)R literal 0 HcmV?d00001 diff --git a/PdfSharpCore.Test/Assets/Drawing/Layout/DrawMultiLineStringsWithLineHeight_1.png b/PdfSharpCore.Test/Assets/Drawing/Layout/DrawMultiLineStringsWithLineHeight_1.png new file mode 100644 index 0000000000000000000000000000000000000000..abdf51fbce6edcc8773694091d82e6583e646de5 GIT binary patch literal 29390 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

!K)hz7VezKo66#7nz;vX%-5e%upk#+&v)p>n0 zX&|$SZCvnO?5gLLh@Q=PV#9WuFA49=YJ>aP#iWqZ@X3iYL-iZjANnwE9%R{@vt@^A zIZCDd*zk*$!T8=;YQ;QfBjRVN3-w=wg1Poh#T*p_i##0>(}G559h21l+4qX?2Ar91 z{ah~^Tl5E$CiDiVx23|4WDT-6B012sHpN5;l;RHX&&$8)%pqB(2T>SpR9-r)^2=^c z)(mt9{ejV0CO?kY!-Mm_XM80h3mq-v7*o+<6VoroA&&68CEKxmsb|#9T#$goF^LNY z_;ScC*yNot!%Q!}p_ab(@cpM-gQ`5#n5yvpVr5ort$JmT>wC=L8q)e%AN64D1g7mI zQTHg(fQ9)g-`OgCIvz6?q!J2;d)pN|PBILOTdB1+2<%Jw-ihe0$OSPJLw}dp_7&A7 zUb9&itU2Ujpe|>@6?Qn<tM%7bI82z>Si3F@jYu z#G|QGu92;7gHE|D&Va+LqGgOO>C+Nk#l>NcHFCXcEx!daQP@u3{D6a@@q8QhZh`9| zPw?}ai``=Vh-@UFq46xNmRbJ0U3TY=yHx+kvaQ|bSC<<*twB1Ro6NSK0VTumP;6X4 zS9efXHwH)Xk+6H}oWQ#2B>8~4w+hzF;YUFX`E50}s??o9EasM9#8C^lr8V`#9omrP z!RlaLft-X;v`a^gfQ7(e4*aABRH zt { RootDir, "Assets" }; + segments.AddRange(names); + return Path.Combine(segments.ToArray()); } public string RootDir { get; } diff --git a/PdfSharpCore.Test/Helpers/PdfHelper.cs b/PdfSharpCore.Test/Helpers/PdfHelper.cs new file mode 100644 index 00000000..a21ba9d2 --- /dev/null +++ b/PdfSharpCore.Test/Helpers/PdfHelper.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.IO; +using ImageMagick; +using PdfSharpCore.Pdf; + +namespace PdfSharpCore.Test.Helpers +{ + public class PdfHelper + { + private static readonly string _rootPath = PathHelper.GetInstance().RootDir; + + ///

+ /// Rasterize all pages within a PDF to PNG images + /// + /// + /// + public static RasterizeOutput Rasterize(PdfDocument document) + { + var readerSettings = new MagickReadSettings + { + Density = new Density(300, 300), + BackgroundColor = MagickColors.White + }; + var images = new MagickImageCollection(); + + // Add all pages to the collection + using var ms = new MemoryStream(); + document.Save(ms); + + try + { + images.Read(ms, readerSettings); + } + catch (MagickDelegateErrorException ex) + { + throw new Exception("Ghostscript is not installed or is an incompatible version, unable to rasterize PDF", ex); + } + + // Remove transparency to guarantee a standard white background + foreach (var img in images) + { + img.Alpha(AlphaOption.Deactivate); + img.BackgroundColor = MagickColors.White; + } + + return new RasterizeOutput + { + ImageCollection = images, + }; + } + + public static List WriteImageCollection(MagickImageCollection images, string outDir, string filePrefix) + { + var outPaths = new List(); + for (var pageNum = 0; pageNum < images.Count; pageNum++) + { + var outPath = GetOutFilePath(outDir, $"{filePrefix}_{pageNum+1}.png"); + images[pageNum].Write(outPath); + outPaths.Add(outPath); + } + + return outPaths; + } + + public static string WriteImage(IMagickImage image, string outDir, string fileNameWithoutExtension) + { + var outPath = GetOutFilePath(outDir, $"{fileNameWithoutExtension}.png"); + image.Write(outPath); + return outPath; + } + + // Note: For diff to function properly, it requires the underlying image to be in the proper format + // For instance, actual and expected must both be sourced from .png files + public static DiffOutput Diff(string actualImagePath, string expectedImagePath, string outputPath = null, string filePrefix = null, int fuzzPct = 4) + { + var diffImg = new MagickImage(); + var actual = new MagickImage(actualImagePath); + var expected = new MagickImage(expectedImagePath); + + // Allow for subtle differences due to cross-platform rendering of the PDF fonts + actual.ColorFuzz = new Percentage(fuzzPct); + var diffVal = actual.Compare(expected, ErrorMetric.Absolute, diffImg); + + if (diffVal > 0 && outputPath != null && filePrefix != null) + { + WriteImage(diffImg, outputPath, $"{filePrefix}_diff.png"); + } + + return new DiffOutput + { + DiffValue = diffVal, + DiffImage = diffImg + }; + } + + private static string GetOutFilePath(string outDir, string name) + { + var dir = Path.Combine(_rootPath, outDir); + Directory.CreateDirectory(dir); + return Path.Combine(dir, name); + } + } + + public class RasterizeOutput + { + public List OutputPaths; + public MagickImageCollection ImageCollection; + } + + public class DiffOutput + { + public IMagickImage DiffImage; + public double DiffValue; + } +} \ No newline at end of file diff --git a/PdfSharpCore.Test/PdfSharpCore.Test.csproj b/PdfSharpCore.Test/PdfSharpCore.Test.csproj index 5db969bc..844f5748 100644 --- a/PdfSharpCore.Test/PdfSharpCore.Test.csproj +++ b/PdfSharpCore.Test/PdfSharpCore.Test.csproj @@ -16,6 +16,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -31,31 +32,10 @@ - + PreserveNewest - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - + PreserveNewest diff --git a/PdfSharpCore/Drawing.Layout/XTextFormatter.cs b/PdfSharpCore/Drawing.Layout/XTextFormatter.cs index 6e699209..b4e1e5bf 100644 --- a/PdfSharpCore/Drawing.Layout/XTextFormatter.cs +++ b/PdfSharpCore/Drawing.Layout/XTextFormatter.cs @@ -29,6 +29,7 @@ using System; using System.Collections.Generic; +using System.Linq; using PdfSharpCore.Drawing.Layout.enums; using PdfSharpCore.Pdf.IO; @@ -89,6 +90,10 @@ public XFont Font double _cyAscent; double _cyDescent; double _spaceWidth; + double _lineHeight; + + // Bounding box of the formatted text after layout + private XRect _textLayout; /// /// Gets or sets the bounding box of the layout. @@ -101,15 +106,31 @@ public XRect LayoutRectangle XRect _layoutRectangle; /// - /// Gets or sets the alignment of the text. + /// When true, ignore the height of text areas when rendering multiline strings + /// + public bool AllowVerticalOverflow { get; set; } = false; + + /// + /// Gets or sets the horizontal alignment of the text. + /// + public XParagraphAlignment Alignment { get; set; } = XParagraphAlignment.Left; + + /// + /// Gets or sets the vertical alignment of the text. /// - public XParagraphAlignment Alignment + public XVerticalAlignment VerticalAlignment { get; set; } = XVerticalAlignment.Top; + + /// + /// Set vertical and horizontal alignment + /// + /// + public void SetAlignment(TextFormatAlignment alignments) { - get { return _alignment; } - set { _alignment = value; } + Alignment = alignments.Horizontal; + VerticalAlignment = alignments.Vertical; } - XParagraphAlignment _alignment = XParagraphAlignment.Left; - + + /// /// Draws the text. /// @@ -117,9 +138,9 @@ public XParagraphAlignment Alignment /// The font. /// The text brush. /// The layout rectangle. - public void DrawString(string text, XFont font, XBrush brush, XRect layoutRectangle) + public void DrawString(string text, XFont font, XBrush brush, XRect layoutRectangle, XUnit? lineHeight = null) { - DrawString(text, font, brush, layoutRectangle, XStringFormats.TopLeft); + DrawString(text, font, brush, layoutRectangle, XStringFormats.TopLeft, lineHeight); } /// @@ -130,7 +151,7 @@ public void DrawString(string text, XFont font, XBrush brush, XRect layoutRectan /// The text brush. /// The layout rectangle. /// The format. Must be XStringFormat.TopLeft - public void DrawString(string text, XFont font, XBrush brush, XRect layoutRectangle, XStringFormat format) + public void DrawString(string text, XFont font, XBrush brush, XRect layoutRectangle, XStringFormat format, XUnit? lineHeight = null) { if (text == null) throw new ArgumentNullException("text"); @@ -144,6 +165,8 @@ public void DrawString(string text, XFont font, XBrush brush, XRect layoutRectan Text = text; Font = font; LayoutRectangle = layoutRectangle; + + _lineHeight = lineHeight?.Point ?? _lineSpace; if (text.Length == 0) return; @@ -154,6 +177,16 @@ public void DrawString(string text, XFont font, XBrush brush, XRect layoutRectan double dx = layoutRectangle.Location.X; double dy = layoutRectangle.Location.Y + _cyAscent; + + if (VerticalAlignment == XVerticalAlignment.Middle) + { + dy += layoutRectangle.Height / 2 - _layoutRectangle.Height / 2 - _cyDescent; + } + else if (VerticalAlignment == XVerticalAlignment.Bottom) + { + dy = layoutRectangle.Location.Y + layoutRectangle.Height - _layoutRectangle.Height + _lineHeight - _cyDescent; + } + int count = _blocks.Count; for (int idx = 0; idx < count; idx++) { @@ -238,11 +271,11 @@ void CreateLayout() { if (Alignment == XParagraphAlignment.Justify) _blocks[firstIndex].Alignment = XParagraphAlignment.Left; - AlignLine(firstIndex, idx - 1, rectWidth); + HorizontalAlignLine(firstIndex, idx - 1, rectWidth); firstIndex = idx + 1; x = 0; - y += _lineSpace; - if (y > rectHeight) + y += _lineHeight; + if (!AllowVerticalOverflow && y > rectHeight) { block.Stop = true; break; @@ -258,10 +291,12 @@ void CreateLayout() } else { - AlignLine(firstIndex, idx - 1, rectWidth); + HorizontalAlignLine(firstIndex, idx - 1, rectWidth); + + // Begin implicit line break firstIndex = idx; - y += _lineSpace; - if (y > rectHeight) + y += _lineHeight; + if (!AllowVerticalOverflow && y > rectHeight) { block.Stop = true; break; @@ -272,16 +307,28 @@ void CreateLayout() } } if (firstIndex < count && Alignment != XParagraphAlignment.Justify) - AlignLine(firstIndex, count - 1, rectWidth); + HorizontalAlignLine(firstIndex, count - 1, rectWidth); + + var minY = _blocks.Min(b => b.Location.Y); + var maxY = _blocks.Max(b => b.Location.Y + _lineHeight); + var minX = _blocks.Min(b => b.Location.X); + var maxX = _blocks.Max(b => b.Location.X + b.Width); + _layoutRectangle = new XRect + { + X = minX, + Y = minY, + Height = maxY - minY, + Width = maxX - minX + }; } /// /// Align center, right, or justify. /// - void AlignLine(int firstIndex, int lastIndex, double layoutWidth) + void HorizontalAlignLine(int firstIndex, int lastIndex, double layoutWidth) { XParagraphAlignment blockAlignment = _blocks[firstIndex].Alignment; - if (_alignment == XParagraphAlignment.Left || blockAlignment == XParagraphAlignment.Left) + if (Alignment == XParagraphAlignment.Left || blockAlignment == XParagraphAlignment.Left) return; int count = lastIndex - firstIndex + 1; @@ -294,9 +341,9 @@ void AlignLine(int firstIndex, int lastIndex, double layoutWidth) double dx = Math.Max(layoutWidth - totalWidth, 0); //Debug.Assert(dx >= 0); - if (_alignment != XParagraphAlignment.Justify) + if (Alignment != XParagraphAlignment.Justify) { - if (_alignment == XParagraphAlignment.Center) + if (Alignment == XParagraphAlignment.Center) dx /= 2; for (int idx = firstIndex; idx <= lastIndex; idx++) { @@ -319,7 +366,6 @@ void AlignLine(int firstIndex, int lastIndex, double layoutWidth) // TODO: // - more XStringFormat variations - // - calculate bounding box // - left and right indent // - first line indent // - margins and paddings @@ -329,9 +375,14 @@ void AlignLine(int firstIndex, int lastIndex, double layoutWidth) // - hyphens, soft hyphens, hyphenation // - kerning // - change font, size, text color etc. - // - line spacing // - underline and strike-out variation // - super- and sub-script // - ... } + + public class TextFormatAlignment + { + public XParagraphAlignment Horizontal { get; set; } = XParagraphAlignment.Left; + public XVerticalAlignment Vertical { get; set; } = XVerticalAlignment.Top; + } } diff --git a/PdfSharpCore/Drawing.Layout/enums/XVerticalAlignment.cs b/PdfSharpCore/Drawing.Layout/enums/XVerticalAlignment.cs new file mode 100644 index 00000000..c204864b --- /dev/null +++ b/PdfSharpCore/Drawing.Layout/enums/XVerticalAlignment.cs @@ -0,0 +1,9 @@ +namespace PdfSharpCore.Drawing.Layout.enums +{ + public enum XVerticalAlignment + { + Top, + Middle, + Bottom, + } +} \ No newline at end of file From 8d53b80325cdfa3548592ca46e290b978938c04a Mon Sep 17 00:00:00 2001 From: Ken Hill Date: Fri, 17 Nov 2023 17:56:11 -0500 Subject: [PATCH 2/2] Fix duplicate file extension in test diff output files --- PdfSharpCore.Test/Helpers/PathHelper.cs | 2 +- PdfSharpCore.Test/Helpers/PdfHelper.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/PdfSharpCore.Test/Helpers/PathHelper.cs b/PdfSharpCore.Test/Helpers/PathHelper.cs index 5772adfb..e77ca865 100644 --- a/PdfSharpCore.Test/Helpers/PathHelper.cs +++ b/PdfSharpCore.Test/Helpers/PathHelper.cs @@ -27,4 +27,4 @@ public static PathHelper GetInstance() private static PathHelper _instance; } -} \ No newline at end of file +} diff --git a/PdfSharpCore.Test/Helpers/PdfHelper.cs b/PdfSharpCore.Test/Helpers/PdfHelper.cs index a21ba9d2..e4c1781f 100644 --- a/PdfSharpCore.Test/Helpers/PdfHelper.cs +++ b/PdfSharpCore.Test/Helpers/PdfHelper.cs @@ -84,7 +84,7 @@ public static DiffOutput Diff(string actualImagePath, string expectedImagePath, if (diffVal > 0 && outputPath != null && filePrefix != null) { - WriteImage(diffImg, outputPath, $"{filePrefix}_diff.png"); + WriteImage(diffImg, outputPath, $"{filePrefix}_diff"); } return new DiffOutput @@ -113,4 +113,4 @@ public class DiffOutput public IMagickImage DiffImage; public double DiffValue; } -} \ No newline at end of file +}