namespace UglyToad.PdfPig.Writer { using System; using System.Collections.Generic; using Content; using Core; using Geometry; using Graphics.Operations; using Graphics.Operations.General; using Graphics.Operations.PathConstruction; using Graphics.Operations.TextObjects; using Graphics.Operations.TextPositioning; using Graphics.Operations.TextShowing; using Graphics.Operations.TextState; /// /// A builder used to add construct a page in a PDF document. /// public class PdfPageBuilder { private readonly PdfDocumentBuilder documentBuilder; private readonly List operations = new List(); internal IReadOnlyList Operations => operations; /// /// The number of this page, 1-indexed. /// public int PageNumber { get; } /// /// The current size of the page. /// public PdfRectangle PageSize { get; set; } internal PdfPageBuilder(int number, PdfDocumentBuilder documentBuilder) { this.documentBuilder = documentBuilder ?? throw new ArgumentNullException(nameof(documentBuilder)); PageNumber = number; } /// /// Draws a line on the current page between two points with the specified line width. /// /// The first point on the line. /// The last point on the line. /// The width of the line in user space units. public void DrawLine(PdfPoint from, PdfPoint to, decimal lineWidth = 1) { if (lineWidth != 1) { operations.Add(new SetLineWidth(lineWidth)); } operations.Add(new BeginNewSubpath(from.X, from.Y)); operations.Add(new AppendStraightLineSegment(to.X, to.Y)); operations.Add(StrokePath.Value); if (lineWidth != 1) { operations.Add(new SetLineWidth(1)); } } /// /// Draws a rectangle on the current page starting at the specified point with the given width, height and line width. /// /// The position of the rectangle, for positive width and height this is the top-left corner. /// The width of the rectangle. /// The height of the rectangle. /// The width of the line border of the rectangle. public void DrawRectangle(PdfPoint position, decimal width, decimal height, decimal lineWidth = 1) { if (lineWidth != 1) { operations.Add(new SetLineWidth(lineWidth)); } operations.Add(new AppendRectangle(position.X, position.Y, width, height)); operations.Add(StrokePath.Value); if (lineWidth != 1) { operations.Add(new SetLineWidth(lineWidth)); } } /// /// Calculates the size and position of each letter in a given string in the provided font without changing the state of the page. /// /// The text to measure each letter of. /// The size of the font in user space units. /// The position of the baseline (lower-left corner) to start drawing the text from. /// /// A font added to the document using /// or methods. /// /// The letters from the input text with their corresponding size and position. public IReadOnlyList MeasureText(string text, decimal fontSize, PdfPoint position, PdfDocumentBuilder.AddedFont font) { if (font == null) { throw new ArgumentNullException(nameof(font)); } if (text == null) { throw new ArgumentNullException(nameof(text)); } if (!documentBuilder.Fonts.TryGetValue(font.Id, out var fontProgram)) { throw new ArgumentException($"No font has been added to the PdfDocumentBuilder with Id: {font.Id}. " + $"Use {nameof(documentBuilder.AddTrueTypeFont)} to register a font.", nameof(font)); } if (fontSize <= 0) { throw new ArgumentOutOfRangeException(nameof(fontSize), "Font size must be greater than 0"); } var fm = fontProgram.GetFontMatrix(); var textMatrix = TransformationMatrix.FromValues(1, 0, 0, 1, position.X, position.Y); var letters = DrawLetters(text, fontProgram, fm, fontSize, textMatrix); return letters; } /// /// Draws the text in the provided font at the specified position and returns the letters which will be drawn. /// /// The text to draw to the page. /// The size of the font in user space units. /// The position of the baseline (lower-left corner) to start drawing the text from. /// /// A font added to the document using /// or methods. /// /// The letters from the input text with their corresponding size and position. public IReadOnlyList AddText(string text, decimal fontSize, PdfPoint position, PdfDocumentBuilder.AddedFont font) { if (font == null) { throw new ArgumentNullException(nameof(font)); } if (text == null) { throw new ArgumentNullException(nameof(text)); } if (!documentBuilder.Fonts.TryGetValue(font.Id, out var fontProgram)) { throw new ArgumentException($"No font has been added to the PdfDocumentBuilder with Id: {font.Id}. " + $"Use {nameof(documentBuilder.AddTrueTypeFont)} to register a font.", nameof(font)); } if (fontSize <= 0) { throw new ArgumentOutOfRangeException(nameof(fontSize), "Font size must be greater than 0"); } var fm = fontProgram.GetFontMatrix(); var textMatrix = TransformationMatrix.FromValues(1, 0, 0, 1, position.X, position.Y); var letters = DrawLetters(text, fontProgram, fm, fontSize, textMatrix); operations.Add(BeginText.Value); operations.Add(new SetFontAndSize(font.Name, fontSize)); operations.Add(new MoveToNextLineWithOffset(position.X, position.Y)); operations.Add(new ShowText(text)); operations.Add(EndText.Value); return letters; } private static List DrawLetters(string text, IWritingFont font, TransformationMatrix fontMatrix, decimal fontSize, TransformationMatrix textMatrix) { var horizontalScaling = 1; var rise = 0; var letters = new List(); var renderingMatrix = TransformationMatrix.FromValues(fontSize * horizontalScaling, 0, 0, fontSize, 0, rise); var width = 0m; for (var i = 0; i < text.Length; i++) { var c = text[i]; if (!font.TryGetBoundingBox(c, out var rect)) { throw new InvalidOperationException($"The font does not contain a character: {c}."); } if (!font.TryGetAdvanceWidth(c, out var charWidth)) { throw new InvalidOperationException($"The font does not contain a character: {c}."); } var advanceRect = new PdfRectangle(0, 0, charWidth, 0); advanceRect = textMatrix.Transform(renderingMatrix.Transform(fontMatrix.Transform(advanceRect))); var documentSpace = textMatrix.Transform(renderingMatrix.Transform(fontMatrix.Transform(rect))); var letter = new Letter(c.ToString(), documentSpace, advanceRect.BottomLeft, width, fontSize, font.Name, fontSize); letters.Add(letter); var tx = advanceRect.Width * horizontalScaling; var ty = 0; var translate = TransformationMatrix.GetTranslationMatrix(tx, ty); width += tx; textMatrix = translate.Multiply(textMatrix); } return letters; } } }