Return bounding boxes for letters

This commit is contained in:
modest-as
2018-03-30 23:16:54 +01:00
parent ab60a15d4c
commit 564e32e072
15 changed files with 175 additions and 78 deletions

View File

@@ -70,7 +70,7 @@
break;
}
var myX = pageLetter.Location.X;
var myX = pageLetter.Rectangle.BottomLeft.X;
var theirX = pdfBoxData[index].X;
var myLetter = pageLetter.Value;

View File

@@ -134,12 +134,12 @@ namespace UglyToad.PdfPig.Tests.Integration
}
Assert.Equal(datum.Text, letter.Value);
Assert.Equal(datum.X, letter.Location.X, 2);
Assert.Equal(datum.X, letter.Rectangle.BottomLeft.X, 2);
var transformed = page.Height - letter.Location.Y;
var transformed = page.Height - letter.Rectangle.BottomLeft.Y;
Assert.Equal(datum.Y, transformed, 2);
Assert.Equal(datum.Width, letter.Width, 2);
Assert.Equal(datum.Width, letter.Rectangle.Width, 2);
Assert.Equal(datum.FontName, letter.FontName);
@@ -179,13 +179,13 @@ namespace UglyToad.PdfPig.Tests.Integration
}
Assert.Equal(datum.Text, letter.Value);
Assert.Equal(datum.X, letter.Location.X, 2);
Assert.Equal(datum.X, letter.Rectangle.BottomLeft.X, 2);
var transformed = page.Height - letter.Location.Y;
var transformed = page.Height - letter.Rectangle.BottomLeft.Y;
Assert.Equal(datum.Y, transformed, 2);
// Until we get width from glyphs we're a bit out.
Assert.True(Math.Abs(datum.Width - letter.Width) < 0.03m);
Assert.True(Math.Abs(datum.Width - letter.Rectangle.Width) < 0.03m);
index++;
}

View File

@@ -29,6 +29,7 @@
"UglyToad.PdfPig.ParsingOptions",
"UglyToad.PdfPig.Logging.ILog",
"UglyToad.PdfPig.Geometry.PdfPoint",
"UglyToad.PdfPig.Geometry.PdfRectangle",
"UglyToad.PdfPig.Fonts.Exceptions.InvalidFontFormatException",
"UglyToad.PdfPig.Exceptions.PdfDocumentFormatException",
"UglyToad.PdfPig.Content.Letter",

View File

@@ -13,14 +13,9 @@
public string Value { get; }
/// <summary>
/// The lower-left position of the letter. Letters with descenders will extend below this point.
/// Position of the bounding box.
/// </summary>
public PdfPoint Location { get; }
/// <summary>
/// The width of the letter.
/// </summary>
public decimal Width { get; }
public PdfRectangle Rectangle { get; }
/// <summary>
/// Size as defined in the PDF file. This is not equivalent to font size in points but is relative to other font sizes on the page.
@@ -40,11 +35,10 @@
/// <summary>
/// Create a new letter to represent some text drawn by the Tj operator.
/// </summary>
internal Letter(string value, PdfPoint location, decimal width, decimal fontSize, string fontName, decimal pointSize)
internal Letter(string value, PdfRectangle rectangle, decimal fontSize, string fontName, decimal pointSize)
{
Value = value;
Location = location;
Width = width;
Rectangle = rectangle;
FontSize = fontSize;
FontName = fontName;
PointSize = pointSize;
@@ -55,7 +49,7 @@
/// </summary>
public override string ToString()
{
return $"{Location} {Width} {Value} {FontName} {PointSize}";
return $"{Rectangle} {Value} {FontName} {PointSize}";
}
}
}

View File

@@ -102,6 +102,17 @@
return new PdfVector(x, y);
}
[Pure]
public PdfRectangle Transform(PdfRectangle original)
{
return new PdfRectangle(
Transform(original.BottomLeft.ToVector()),
Transform(original.BottomLeft.ToVector()),
Transform(original.BottomLeft.ToVector()),
Transform(original.BottomLeft.ToVector())
);
}
public static TransformationMatrix FromValues(decimal a, decimal b, decimal c, decimal d, decimal e, decimal f)
=> FromArray(new[] {a, b, c, d, e, f});
public static TransformationMatrix FromArray(decimal[] values)
@@ -144,6 +155,26 @@
return new TransformationMatrix(result);
}
public TransformationMatrix Multiply(decimal scalar)
{
var result = new decimal[9];
for (int i = 0; i < Rows; i++)
{
for (int j = 0; j < Columns; j++)
{
var index = (i * Rows) + j;
for (int x = 0; x < Rows; x++)
{
result[index] += this[i, x] * scalar;
}
}
}
return new TransformationMatrix(result);
}
public decimal GetScalingFactorX()
{
var xScale = A;

View File

@@ -30,6 +30,8 @@
public bool IsVertical => CMap.WritingMode == WritingMode.Vertical;
private readonly TransformationMatrix fontMatrix = TransformationMatrix.FromValues(0.001m, 0, 0, 0.001m, 0, 0);
public Type0Font(NameToken baseFont, ICidFont cidFont, CMap cmap, CMap toUnicodeCMap)
{
BaseFont = baseFont ?? throw new ArgumentNullException(nameof(baseFont));
@@ -69,21 +71,18 @@
return ToUnicode.TryGet(characterCode, out value);
}
public PdfVector GetDisplacement(int characterCode)
public PdfRectangle GetDisplacement(int characterCode)
{
// This width is in units scaled up by 1000
var width = GetWidth(characterCode);
return new PdfVector(width / 1000, 0);
return fontMatrix.Transform(GetRectangle(characterCode));
}
public decimal GetWidth(int characterCode)
public PdfRectangle GetRectangle(int characterCode)
{
var cid = CMap.ConvertToCid(characterCode);
var fromFont = CidFont.GetWidthFromDictionary(cid);
return fromFont;
return new PdfRectangle(0, 0, fromFont, 0);
}
public TransformationMatrix GetFontMatrix()

View File

@@ -15,9 +15,7 @@
bool TryGetUnicode(int characterCode, out string value);
PdfVector GetDisplacement(int characterCode);
decimal GetWidth(int characterCode);
PdfRectangle GetDisplacement(int characterCode);
TransformationMatrix GetFontMatrix();
}

View File

@@ -11,12 +11,14 @@
internal class TrueTypeSimpleFont : IFont
{
private static readonly TransformationMatrix FontMatrix =
TransformationMatrix.FromValues(1/1000m, 0, 0, 1/1000m, 0, 0);
private readonly int firstCharacterCode;
private readonly int lastCharacterCode;
private readonly decimal[] widths;
private readonly FontDescriptor descriptor;
[CanBeNull]
private readonly Encoding encoding;
@@ -27,6 +29,8 @@
[NotNull]
public ToUnicodeCMap ToUnicode { get; set; }
private readonly TransformationMatrix fontMatrix = TransformationMatrix.FromValues(0.001m, 0, 0, 0.001m, 0, 0);
public TrueTypeSimpleFont(NameToken name, int firstCharacterCode, int lastCharacterCode, decimal[] widths,
FontDescriptor descriptor,
[CanBeNull]CMap toUnicodeCMap,
@@ -79,29 +83,27 @@
return true;
}
public PdfVector GetDisplacement(int characterCode)
public PdfRectangle GetDisplacement(int characterCode)
{
var tx = GetWidth(characterCode);
return new PdfVector(tx / 1000m, 0);
return fontMatrix.Transform(GetRectangle(characterCode));
}
public decimal GetWidth(int characterCode)
public PdfRectangle GetRectangle(int characterCode)
{
var index = characterCode - firstCharacterCode;
if (index < 0 || index >= widths.Length)
{
return descriptor.MissingWidth;
return new PdfRectangle(0, 0, descriptor.MissingWidth, 0);
}
return widths[index];
return new PdfRectangle(0, 0, widths[index], 0);
}
public TransformationMatrix GetFontMatrix()
{
// TODO: should this also use units per em?
return FontMatrix;
return fontMatrix;
}
}
}

View File

@@ -19,6 +19,7 @@
private readonly FontDescriptor fontDescriptor;
private readonly Encoding encoding;
private readonly ToUnicodeCMap toUnicodeCMap;
private readonly TransformationMatrix fontMatrix = TransformationMatrix.FromValues(0.001m, 0, 0, 0.001m, 0, 0);
public NameToken Name { get; }
@@ -80,19 +81,19 @@
return true;
}
public PdfVector GetDisplacement(int characterCode)
public PdfRectangle GetDisplacement(int characterCode)
{
return fontMatrix.Transform(new PdfVector(GetWidth(characterCode), 0));
return fontMatrix.Transform(GetRectangle(characterCode));
}
public decimal GetWidth(int characterCode)
public PdfRectangle GetRectangle(int characterCode)
{
if (characterCode < firstChar || characterCode > lastChar)
{
return 250;
return new PdfRectangle(0, 0, 250, 0);
}
return widths[characterCode - firstChar];
return new PdfRectangle(0, 0, widths[characterCode - firstChar], 0);
}
public TransformationMatrix GetFontMatrix()

View File

@@ -9,14 +9,14 @@
internal class Type1Standard14Font: IFont
{
private static readonly TransformationMatrix FontMatrix = TransformationMatrix.FromValues(0.001m, 0, 0, 0.001m, 0, 0);
private readonly FontMetrics standardFontMetrics;
private readonly Encoding encoding;
public NameToken Name { get; }
public bool IsVertical { get; }
private readonly TransformationMatrix fontMatrix = TransformationMatrix.FromValues(0.001m, 0, 0, 0.001m, 0, 0);
public Type1Standard14Font(FontMetrics standardFontMetrics)
{
this.standardFontMetrics = standardFontMetrics ?? throw new ArgumentNullException(nameof(standardFontMetrics));
@@ -44,26 +44,26 @@
return true;
}
public PdfVector GetDisplacement(int characterCode)
public PdfRectangle GetDisplacement(int characterCode)
{
return FontMatrix.Transform(new PdfVector(GetWidth(characterCode), 0));
return fontMatrix.Transform(GetRectangle(characterCode));
}
public decimal GetWidth(int characterCode)
public PdfRectangle GetRectangle(int characterCode)
{
var name = encoding.GetName(characterCode);
if (!standardFontMetrics.CharacterMetrics.TryGetValue(name, out var metrics))
{
return 250;
return new PdfRectangle(0, 0, 250, 0);
}
return metrics.WidthX;
return new PdfRectangle(0, 0, metrics.WidthX, 0);
}
public TransformationMatrix GetFontMatrix()
{
return FontMatrix;
return fontMatrix;
}
}
}

View File

@@ -63,19 +63,19 @@
return true;
}
public PdfVector GetDisplacement(int characterCode)
public PdfRectangle GetDisplacement(int characterCode)
{
return fontMatrix.Transform(new PdfVector(GetWidth(characterCode), 0));
return fontMatrix.Transform(GetRectangle(characterCode));
}
public decimal GetWidth(int characterCode)
public PdfRectangle GetRectangle(int characterCode)
{
if (characterCode < firstChar || characterCode > lastChar)
{
throw new InvalidFontFormatException($"The character code was not contained in the widths array: {characterCode}.");
}
return widths[characterCode - firstChar];
return new PdfRectangle(0, 0, widths[characterCode - firstChar], 0); ;
}
public TransformationMatrix GetFontMatrix()

View File

@@ -52,6 +52,11 @@
Y = (decimal)y;
}
internal PdfVector ToVector()
{
return new PdfVector(X, Y);
}
/// <summary>
/// Get a string representation of this point.
/// </summary>

View File

@@ -2,25 +2,68 @@
{
using System;
internal class PdfRectangle
/// <summary>
/// A rectangle in a PDF file.
/// </summary>
/// <remarks>
/// PDF coordinates are defined with the origin at the lower left (0, 0).
/// The Y-axis extends vertically upwards and the X-axis horizontally to the right.
/// Unless otherwise specified on a per-page basis, units in PDF space are equivalent to a typographic point (1/72 inch).
/// </remarks>
public class PdfRectangle
{
/// <summary>
/// Top left point of the rectangle.
/// </summary>
public PdfPoint TopLeft { get; }
public PdfPoint BottomRight { get; }
/// <summary>
/// Top right point of the rectangle.
/// </summary>
public PdfPoint TopRight { get; }
/// <summary>
/// Bottom right point of the rectangle.
/// </summary>
public PdfPoint BottomRight { get; }
/// <summary>
/// Bottom left point of the rectangle.
/// </summary>
public PdfPoint BottomLeft { get; }
/// <summary>
/// Width of the rectangle.
/// </summary>
public decimal Width { get; }
/// <summary>
/// Height of the rectangle.
/// </summary>
public decimal Height { get; }
/// <summary>
/// Area of the rectangle.
/// </summary>
public decimal Area { get; }
public PdfRectangle(PdfPoint point1, PdfPoint point2) : this(point1.X, point1.Y, point2.X, point2.Y) { }
public PdfRectangle(short x1, short y1, short x2, short y2) : this((decimal) x1, y1, x2, y2) { }
public PdfRectangle(decimal x1, decimal y1, decimal x2, decimal y2)
internal PdfRectangle(PdfVector topLeft, PdfVector topRight, PdfVector bottomLeft, PdfVector bottomRight)
{
TopLeft = topLeft.ToPoint();
TopRight = topRight.ToPoint();
BottomLeft = bottomLeft.ToPoint();
BottomRight = bottomRight.ToPoint();
Width = bottomRight.Subtract(bottomLeft).GetMagnitude();
Height = topLeft.Subtract(bottomLeft).GetMagnitude();
Area = Width * Height;
}
internal PdfRectangle(short x1, short y1, short x2, short y2) : this((decimal) x1, y1, x2, y2) { }
internal PdfRectangle(decimal x1, decimal y1, decimal x2, decimal y2)
{
var bottom = Math.Min(y1, y2);
var top = Math.Max(y1, y2);
@@ -36,12 +79,17 @@
Width = right - left;
Height = top - bottom;
Area = Width * Height;
}
/// <summary>
/// To string override.
/// </summary>
/// <returns></returns>
public override string ToString()
{
return $"[{TopLeft}, {BottomRight}]";
return $"[{TopLeft}, {Width}, {Height}]";
}
}
}

View File

@@ -1,4 +1,6 @@
namespace UglyToad.PdfPig.Geometry
using System;
namespace UglyToad.PdfPig.Geometry
{
internal struct PdfVector
{
@@ -17,6 +19,24 @@
return new PdfVector(X * scale, Y * scale);
}
public decimal GetMagnitude()
{
var doubleX = (double)X;
var doubleY = (double)Y;
return (decimal)Math.Sqrt(doubleX * doubleX + doubleY * doubleY);
}
public PdfVector Subtract(PdfVector vector)
{
return new PdfVector(X - vector.X, Y - vector.Y);
}
public PdfPoint ToPoint()
{
return new PdfPoint(X, Y);
}
public override string ToString()
{
return $"({X}, {Y})";

View File

@@ -96,8 +96,6 @@
var transformationMatrix = currentState.CurrentTransformationMatrix;
var fontMatrix = font.GetFontMatrix();
// TODO: this does not seem correct, produces the correct result for now but we need to revisit.
// see: https://stackoverflow.com/questions/48010235/pdf-specification-get-font-size-in-points
var pointSize = decimal.Round(fontSize * transformationMatrix.A, 2);
@@ -119,28 +117,31 @@
wordSpacing += GetCurrentState().FontState.WordSpacing;
}
var renderingMatrix = TextMatrices.GetRenderingMatrix(GetCurrentState());
if (font.IsVertical)
{
throw new NotImplementedException("Vertical fonts are# currently unsupported, please submit a pull request or issue with an example file.");
}
var displacement = font.GetDisplacement(code);
var width = displacement.X * fontSize * TextMatrices.TextMatrix.GetScalingFactorX() * transformationMatrix.A;
ShowGlyph(renderingMatrix, font, unicode, width, fontSize, pointSize);
var fontScaling = TransformationMatrix.Identity.Multiply(fontSize);
var transformedDisplacement = transformationMatrix
.Transform(TextMatrices.TextMatrix
.Transform(fontScaling
.Transform(displacement)));
ShowGlyph(font, transformedDisplacement, unicode, fontSize, pointSize);
decimal tx, ty;
if (font.IsVertical)
{
tx = 0;
ty = displacement.Y * fontSize + characterSpacing + wordSpacing;
ty = displacement.Height * fontSize + characterSpacing + wordSpacing;
}
else
{
tx = (displacement.X * fontSize + characterSpacing + wordSpacing) * horizontalScaling;
tx = (displacement.Width * fontSize + characterSpacing + wordSpacing) * horizontalScaling;
ty = 0;
}
@@ -208,12 +209,9 @@
TextMatrices.TextMatrix = newMatrix;
}
private void ShowGlyph(TransformationMatrix renderingMatrix, IFont font, string unicode, decimal width, decimal fontSize,
decimal pointSize)
private void ShowGlyph(IFont font, PdfRectangle rectangle, string unicode, decimal fontSize, decimal pointSize)
{
var location = new PdfPoint(renderingMatrix.E, renderingMatrix.F);
var letter = new Letter(unicode, location, width, fontSize, font.Name.Data, pointSize);
var letter = new Letter(unicode, rectangle, fontSize, font.Name.Data, pointSize);
Letters.Add(letter);
}