2018-11-25 03:38:16 +08:00
|
|
|
|
namespace UglyToad.PdfPig.Writer
|
|
|
|
|
{
|
|
|
|
|
using System;
|
2018-11-28 04:00:38 +08:00
|
|
|
|
using System.Collections.Generic;
|
2018-12-09 02:04:02 +08:00
|
|
|
|
using Content;
|
2018-11-28 04:00:38 +08:00
|
|
|
|
using Core;
|
2018-11-25 03:38:16 +08:00
|
|
|
|
using Geometry;
|
2018-11-28 04:00:38 +08:00
|
|
|
|
using Graphics.Operations;
|
2018-12-12 08:09:15 +08:00
|
|
|
|
using Graphics.Operations.General;
|
|
|
|
|
using Graphics.Operations.PathConstruction;
|
2018-11-28 04:00:38 +08:00
|
|
|
|
using Graphics.Operations.TextObjects;
|
2018-12-03 00:14:55 +08:00
|
|
|
|
using Graphics.Operations.TextPositioning;
|
2018-11-28 04:00:38 +08:00
|
|
|
|
using Graphics.Operations.TextShowing;
|
|
|
|
|
using Graphics.Operations.TextState;
|
2018-11-25 03:38:16 +08:00
|
|
|
|
|
2018-12-25 18:37:00 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// A builder used to add construct a page in a PDF document.
|
|
|
|
|
/// </summary>
|
2018-12-29 00:55:46 +08:00
|
|
|
|
public class PdfPageBuilder
|
2018-11-25 03:38:16 +08:00
|
|
|
|
{
|
|
|
|
|
private readonly PdfDocumentBuilder documentBuilder;
|
2018-11-28 04:00:38 +08:00
|
|
|
|
private readonly List<IGraphicsStateOperation> operations = new List<IGraphicsStateOperation>();
|
2018-12-25 18:37:00 +08:00
|
|
|
|
internal IReadOnlyList<IGraphicsStateOperation> Operations => operations;
|
2018-11-25 03:38:16 +08:00
|
|
|
|
|
2018-12-25 18:37:00 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// The number of this page, 1-indexed.
|
|
|
|
|
/// </summary>
|
2018-11-25 03:38:16 +08:00
|
|
|
|
public int PageNumber { get; }
|
2018-12-25 18:37:00 +08:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The current size of the page.
|
|
|
|
|
/// </summary>
|
2018-11-25 03:38:16 +08:00
|
|
|
|
public PdfRectangle PageSize { get; set; }
|
|
|
|
|
|
2018-12-25 18:37:00 +08:00
|
|
|
|
internal PdfPageBuilder(int number, PdfDocumentBuilder documentBuilder)
|
2018-11-25 03:38:16 +08:00
|
|
|
|
{
|
|
|
|
|
this.documentBuilder = documentBuilder ?? throw new ArgumentNullException(nameof(documentBuilder));
|
|
|
|
|
PageNumber = number;
|
|
|
|
|
}
|
2018-11-25 21:56:27 +08:00
|
|
|
|
|
2018-12-25 18:37:00 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Draws a line on the current page between two points with the specified line width.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="from">The first point on the line.</param>
|
|
|
|
|
/// <param name="to">The last point on the line.</param>
|
|
|
|
|
/// <param name="lineWidth">The width of the line in user space units.</param>
|
2018-12-12 08:09:15 +08:00
|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-25 18:37:00 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Draws a rectangle on the current page starting at the specified point with the given width, height and line width.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="position">The position of the rectangle, for positive width and height this is the top-left corner.</param>
|
|
|
|
|
/// <param name="width">The width of the rectangle.</param>
|
|
|
|
|
/// <param name="height">The height of the rectangle.</param>
|
|
|
|
|
/// <param name="lineWidth">The width of the line border of the rectangle.</param>
|
2018-12-12 08:09:15 +08:00
|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-25 18:37:00 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Calculates the size and position of each letter in a given string in the provided font without changing the state of the page.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="text">The text to measure each letter of.</param>
|
|
|
|
|
/// <param name="fontSize">The size of the font in user space units.</param>
|
|
|
|
|
/// <param name="position">The position of the baseline (lower-left corner) to start drawing the text from.</param>
|
|
|
|
|
/// <param name="font">
|
|
|
|
|
/// A font added to the document using <see cref="PdfDocumentBuilder.AddTrueTypeFont"/>
|
|
|
|
|
/// or <see cref="PdfDocumentBuilder.AddStandard14Font"/> methods.
|
|
|
|
|
/// </param>
|
|
|
|
|
/// <returns>The letters from the input text with their corresponding size and position.</returns>
|
|
|
|
|
public IReadOnlyList<Letter> MeasureText(string text, decimal fontSize, PdfPoint position, PdfDocumentBuilder.AddedFont font)
|
2018-11-25 21:56:27 +08:00
|
|
|
|
{
|
|
|
|
|
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");
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-12 05:29:39 +08:00
|
|
|
|
var fm = fontProgram.GetFontMatrix();
|
|
|
|
|
|
2018-12-09 02:04:02 +08:00
|
|
|
|
var textMatrix = TransformationMatrix.FromValues(1, 0, 0, 1, position.X, position.Y);
|
2018-11-28 04:00:38 +08:00
|
|
|
|
|
2018-12-09 02:04:02 +08:00
|
|
|
|
var letters = DrawLetters(text, fontProgram, fm, fontSize, textMatrix);
|
2018-12-25 18:37:00 +08:00
|
|
|
|
|
|
|
|
|
return letters;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Draws the text in the provided font at the specified position and returns the letters which will be drawn.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="text">The text to draw to the page.</param>
|
|
|
|
|
/// <param name="fontSize">The size of the font in user space units.</param>
|
|
|
|
|
/// <param name="position">The position of the baseline (lower-left corner) to start drawing the text from.</param>
|
|
|
|
|
/// <param name="font">
|
|
|
|
|
/// A font added to the document using <see cref="PdfDocumentBuilder.AddTrueTypeFont"/>
|
|
|
|
|
/// or <see cref="PdfDocumentBuilder.AddStandard14Font"/> methods.
|
|
|
|
|
/// </param>
|
|
|
|
|
/// <returns>The letters from the input text with their corresponding size and position.</returns>
|
|
|
|
|
public IReadOnlyList<Letter> AddText(string text, decimal fontSize, PdfPoint position, PdfDocumentBuilder.AddedFont font)
|
|
|
|
|
{
|
|
|
|
|
if (font == null)
|
2018-11-28 04:00:38 +08:00
|
|
|
|
{
|
2018-12-25 18:37:00 +08:00
|
|
|
|
throw new ArgumentNullException(nameof(font));
|
|
|
|
|
}
|
2018-11-28 04:00:38 +08:00
|
|
|
|
|
2018-12-25 18:37:00 +08:00
|
|
|
|
if (text == null)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentNullException(nameof(text));
|
|
|
|
|
}
|
2018-11-28 04:00:38 +08:00
|
|
|
|
|
2018-12-25 18:37:00 +08:00
|
|
|
|
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));
|
2018-11-28 04:00:38 +08:00
|
|
|
|
}
|
2018-12-25 18:37:00 +08:00
|
|
|
|
|
|
|
|
|
if (fontSize <= 0)
|
2018-11-28 04:00:38 +08:00
|
|
|
|
{
|
2018-12-25 18:37:00 +08:00
|
|
|
|
throw new ArgumentOutOfRangeException(nameof(fontSize), "Font size must be greater than 0");
|
2018-11-28 04:00:38 +08:00
|
|
|
|
}
|
2018-11-25 21:56:27 +08:00
|
|
|
|
|
2018-12-25 18:37:00 +08:00
|
|
|
|
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);
|
|
|
|
|
|
2018-12-09 02:04:02 +08:00
|
|
|
|
return letters;
|
2018-11-25 21:56:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-12-09 02:04:02 +08:00
|
|
|
|
private static List<Letter> DrawLetters(string text, IWritingFont font, TransformationMatrix fontMatrix, decimal fontSize, TransformationMatrix textMatrix)
|
2018-11-25 21:56:27 +08:00
|
|
|
|
{
|
2018-12-09 02:04:02 +08:00
|
|
|
|
var horizontalScaling = 1;
|
|
|
|
|
var rise = 0;
|
|
|
|
|
var letters = new List<Letter>();
|
|
|
|
|
|
|
|
|
|
var renderingMatrix =
|
|
|
|
|
TransformationMatrix.FromValues(fontSize * horizontalScaling, 0, 0, fontSize, 0, rise);
|
|
|
|
|
|
2018-11-25 21:56:27 +08:00
|
|
|
|
var width = 0m;
|
2018-12-09 02:04:02 +08:00
|
|
|
|
|
2018-11-25 21:56:27 +08:00
|
|
|
|
for (var i = 0; i < text.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
var c = text[i];
|
|
|
|
|
|
2018-12-25 18:37:00 +08:00
|
|
|
|
if (!font.TryGetBoundingBox(c, out var rect))
|
2018-11-25 21:56:27 +08:00
|
|
|
|
{
|
|
|
|
|
throw new InvalidOperationException($"The font does not contain a character: {c}.");
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-09 02:04:02 +08:00
|
|
|
|
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);
|
2018-12-25 18:37:00 +08:00
|
|
|
|
|
2018-12-09 02:04:02 +08:00
|
|
|
|
var tx = advanceRect.Width * horizontalScaling;
|
|
|
|
|
var ty = 0;
|
|
|
|
|
|
|
|
|
|
var translate = TransformationMatrix.GetTranslationMatrix(tx, ty);
|
|
|
|
|
|
|
|
|
|
width += tx;
|
|
|
|
|
|
|
|
|
|
textMatrix = translate.Multiply(textMatrix);
|
2018-11-25 21:56:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-12-09 02:04:02 +08:00
|
|
|
|
return letters;
|
2018-11-25 21:56:27 +08:00
|
|
|
|
}
|
2018-11-25 03:38:16 +08:00
|
|
|
|
}
|
|
|
|
|
}
|