return both glyph and character bounding boxes for each letter, glyph is the actual outline of the letter whereas character is the surrounding space as defined in the pdf

This commit is contained in:
Eliot Jones
2018-04-18 19:24:10 +01:00
parent e5cfc20b30
commit 0e844d88df
17 changed files with 125 additions and 65 deletions

View File

@@ -70,7 +70,7 @@
break;
}
var myX = pageLetter.Rectangle.BottomLeft.X;
var myX = pageLetter.CharacterRectangle.Left;
var theirX = pdfBoxData[index].X;
var myLetter = pageLetter.Value;
@@ -87,7 +87,7 @@
Assert.Equal(theirX, myX, comparer);
Assert.Equal(pdfBoxData[index].Width, pageLetter.Rectangle.Width, comparer);
Assert.Equal(pdfBoxData[index].Width, pageLetter.CharacterRectangle.Width, comparer);
index++;
}
@@ -97,6 +97,8 @@
[Fact]
public void LetterPositionsAreCorrectXfinium()
{
var comparer = new DecimalComparer(1);
using (var document = PdfDocument.Open(GetFilename()))
{
var page = document.GetPage(1);
@@ -111,7 +113,7 @@
break;
}
var myX = pageLetter.Rectangle.Left;
var myX = pageLetter.CharacterRectangle.Left;
var theirX = positions[index].X;
var myLetter = pageLetter.Value;
@@ -123,9 +125,9 @@
}
Assert.Equal(theirLetter, myLetter);
Assert.Equal(theirX, myX, 2);
Assert.Equal(theirX, myX, comparer);
Assert.Equal(positions[index].Width, pageLetter.Rectangle.Width, 1);
Assert.Equal(positions[index].Width, pageLetter.CharacterRectangle.Width, 1);
index++;
}

View File

@@ -134,12 +134,12 @@ namespace UglyToad.PdfPig.Tests.Integration
}
Assert.Equal(datum.Text, letter.Value);
Assert.Equal(datum.X, letter.Rectangle.BottomLeft.X, 2);
Assert.Equal(datum.X, letter.CharacterRectangle.BottomLeft.X, 2);
var transformed = page.Height - letter.Rectangle.BottomLeft.Y;
var transformed = page.Height - letter.CharacterRectangle.BottomLeft.Y;
Assert.Equal(datum.Y, transformed, 2);
Assert.Equal(datum.Width, letter.Rectangle.Width, 2);
Assert.Equal(datum.Width, letter.CharacterRectangle.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.Rectangle.BottomLeft.X, 2);
Assert.Equal(datum.X, letter.CharacterRectangle.BottomLeft.X, 2);
var transformed = page.Height - letter.Rectangle.BottomLeft.Y;
var transformed = page.Height - letter.CharacterRectangle.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.Rectangle.Width) < 0.03m);
Assert.True(Math.Abs(datum.Width - letter.CharacterRectangle.Width) < 0.03m);
index++;
}

View File

@@ -48,27 +48,27 @@
Assert.Equal("I", page.Letters[0].Value);
Assert.Equal(90.1m, page.Letters[0].Rectangle.BottomLeft.X, comparer);
Assert.Equal(709.2m, page.Letters[0].Rectangle.BottomLeft.Y, comparer);
Assert.Equal(90.1m, page.Letters[0].GlyphRectangle.BottomLeft.X, comparer);
Assert.Equal(709.2m, page.Letters[0].GlyphRectangle.BottomLeft.Y, comparer);
Assert.Equal(94.0m, page.Letters[0].Rectangle.TopRight.X, comparer);
Assert.Equal(719.89m, page.Letters[0].Rectangle.TopRight.Y, comparer);
Assert.Equal(94.0m, page.Letters[0].GlyphRectangle.TopRight.X, comparer);
Assert.Equal(719.89m, page.Letters[0].GlyphRectangle.TopRight.Y, comparer);
Assert.Equal("a", page.Letters[5].Value);
Assert.Equal(114.5m, page.Letters[5].Rectangle.BottomLeft.X, comparer);
Assert.Equal(709.2m, page.Letters[5].Rectangle.BottomLeft.Y, comparer);
Assert.Equal(114.5m, page.Letters[5].GlyphRectangle.BottomLeft.X, comparer);
Assert.Equal(709.2m, page.Letters[5].GlyphRectangle.BottomLeft.Y, comparer);
Assert.Equal(119.82m, page.Letters[5].Rectangle.TopRight.X, comparer);
Assert.Equal(714.89m, page.Letters[5].Rectangle.TopRight.Y, comparer);
Assert.Equal(119.82m, page.Letters[5].GlyphRectangle.TopRight.X, comparer);
Assert.Equal(714.89m, page.Letters[5].GlyphRectangle.TopRight.Y, comparer);
Assert.Equal("f", page.Letters[16].Value);
Assert.Equal(169.9m, page.Letters[16].Rectangle.BottomLeft.X, comparer);
Assert.Equal(709.2m, page.Letters[16].Rectangle.BottomLeft.Y, comparer);
Assert.Equal(169.9m, page.Letters[16].GlyphRectangle.BottomLeft.X, comparer);
Assert.Equal(709.2m, page.Letters[16].GlyphRectangle.BottomLeft.Y, comparer);
Assert.Equal(176.89m, page.Letters[16].Rectangle.TopRight.X, comparer);
Assert.Equal(719.89m, page.Letters[16].Rectangle.TopRight.Y, comparer);
Assert.Equal(176.89m, page.Letters[16].GlyphRectangle.TopRight.X, comparer);
Assert.Equal(719.89m, page.Letters[16].GlyphRectangle.TopRight.Y, comparer);
}
}

View File

@@ -57,8 +57,8 @@ namespace UglyToad.PdfPig.Tests.Integration
{
var page = document.GetPage(1);
Assert.True((bool) page.Letters.Any(x => x.Rectangle.Width != 0));
Assert.True((bool) page.Letters.Any(x => x.Rectangle.Height != 0));
Assert.True((bool) page.Letters.Any(x => x.GlyphRectangle.Width != 0));
Assert.True((bool) page.Letters.Any(x => x.GlyphRectangle.Height != 0));
}
}
}

View File

@@ -57,8 +57,8 @@
{
foreach (var word in page.Letters)
{
graphics.DrawRectangle(redPen, new Rectangle((int)word.Rectangle.Left,
792 - (int)word.Rectangle.Top, (int)Math.Max(1, word.Rectangle.Width), (int)word.Rectangle.Height));
graphics.DrawRectangle(redPen, new Rectangle((int)word.GlyphRectangle.Left,
792 - (int)word.GlyphRectangle.Top, (int)Math.Max(1, word.GlyphRectangle.Width), (int)word.GlyphRectangle.Height));
}
var imageName = $"{file}.jpg";

View File

@@ -13,9 +13,14 @@
public string Value { get; }
/// <summary>
/// Position of the bounding box.
/// Position of the bounding box for the glyph.
/// </summary>
public PdfRectangle Rectangle { get; }
public PdfRectangle GlyphRectangle { get; }
/// <summary>
/// The bounding box for the entire character.
/// </summary>
public PdfRectangle CharacterRectangle { 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.
@@ -35,13 +40,14 @@
/// <summary>
/// Create a new letter to represent some text drawn by the Tj operator.
/// </summary>
internal Letter(string value, PdfRectangle rectangle, decimal fontSize, string fontName, decimal pointSize)
internal Letter(string value, PdfRectangle glyphRectangle, PdfRectangle characterRectangle, decimal fontSize, string fontName, decimal pointSize)
{
Value = value;
Rectangle = rectangle;
GlyphRectangle = glyphRectangle;
FontSize = fontSize;
FontName = fontName;
PointSize = pointSize;
CharacterRectangle = characterRectangle;
}
/// <summary>
@@ -49,7 +55,7 @@
/// </summary>
public override string ToString()
{
return $"{Value} {Rectangle} {FontName} {PointSize}";
return $"{Value} {GlyphRectangle} {FontName} {PointSize}";
}
}
}

View File

@@ -42,6 +42,8 @@
decimal GetWidthFromDictionary(int cid);
decimal GetWidthFromFont(int characterIdentifier);
PdfRectangle GetBoundingBox(int characterIdentifier);
}
}

View File

@@ -12,7 +12,9 @@
bool TryGetBoundingBox(int characterIdentifier, Func<int, int> characterIdentifierToGlyphIndex, out PdfRectangle boundingBox);
bool TryGetBoundingAdvancedWidth(int characterCode, out decimal width);
bool TryGetBoundingAdvancedWidth(int characterIdentifier, Func<int, int> characterIdentifierToGlyphIndex, out decimal width);
bool TryGetBoundingAdvancedWidth(int characterIdentifier, out decimal width);
int GetFontMatrixMultiplier();
}

View File

@@ -46,10 +46,14 @@
FontMatrix = TransformationMatrix.FromValues(scale, 0, 0, scale, 0, 0);
}
public decimal GetWidthFromFont(int characterCode)
public decimal GetWidthFromFont(int characterIdentifier)
{
if (fontProgram.TryGetBoundingAdvancedWidth(characterIdentifier, cidToGid.GetGlyphIndex, out var width))
{
return width;
}
// TODO: Read the font width from the font program.
throw new System.NotImplementedException();
return GetWidthFromDictionary(characterIdentifier);
}
public decimal GetWidthFromDictionary(int characterIdentifier)

View File

@@ -69,22 +69,22 @@
return ToUnicode.TryGet(characterCode, out value);
}
public PdfRectangle GetBoundingBox(int characterCode)
public CharacterBoundingBox GetBoundingBox(int characterCode)
{
var matrix = GetFontMatrix();
var boundingBox = GetBoundingBoxInGlyphSpace(characterCode);
return matrix.Transform(boundingBox);
}
boundingBox = matrix.Transform(boundingBox);
public decimal GetWidth(int characterCode)
{
var cid = CMap.ConvertToCid(characterCode);
var characterIdentifier = CMap.ConvertToCid(characterCode);
var fromFont = CidFont.GetWidthFromDictionary(cid);
var width = CidFont.GetWidthFromFont(characterIdentifier);
return fromFont;
var advanceWidth = new PdfRectangle(0, 0, width, 0);
advanceWidth = matrix.Transform(advanceWidth);
return new CharacterBoundingBox(boundingBox, advanceWidth);
}
public PdfRectangle GetBoundingBoxInGlyphSpace(int characterCode)

View File

@@ -15,8 +15,21 @@
bool TryGetUnicode(int characterCode, out string value);
PdfRectangle GetBoundingBox(int characterCode);
CharacterBoundingBox GetBoundingBox(int characterCode);
TransformationMatrix GetFontMatrix();
}
internal class CharacterBoundingBox
{
public PdfRectangle GlyphBounds { get; }
public PdfRectangle CharacterBounds { get; }
public CharacterBoundingBox(PdfRectangle glyphBounds, PdfRectangle characterBounds)
{
GlyphBounds = glyphBounds;
CharacterBounds = characterBounds;
}
}
}

View File

@@ -1,6 +1,4 @@
using System;
namespace UglyToad.PdfPig.Fonts.Simple
namespace UglyToad.PdfPig.Fonts.Simple
{
using Cmap;
using Composite;
@@ -88,15 +86,32 @@ namespace UglyToad.PdfPig.Fonts.Simple
return true;
}
public PdfRectangle GetBoundingBox(int characterCode)
public CharacterBoundingBox GetBoundingBox(int characterCode)
{
var fontMatrix = GetFontMatrix();
var boundingBox = GetBoundingBoxInGlyphSpace(characterCode);
var result = fontMatrix.Transform(boundingBox);
boundingBox = fontMatrix.Transform(boundingBox);
return result;
decimal width;
if (font == null)
{
width = widths[characterCode];
}
else
{
if (!font.TryGetBoundingAdvancedWidth(characterCode, out width))
{
width = boundingBox.Width;
}
}
var advancedRectangle = new PdfRectangle(0, 0, width, 0);
advancedRectangle = fontMatrix.Transform(advancedRectangle);
return new CharacterBoundingBox(boundingBox, advancedRectangle);
}
private PdfRectangle GetBoundingBoxInGlyphSpace(int characterCode)

View File

@@ -86,9 +86,13 @@
return true;
}
public PdfRectangle GetBoundingBox(int characterCode)
public CharacterBoundingBox GetBoundingBox(int characterCode)
{
return fontMatrix.Transform(GetBoundingBoxInGlyphSpace(characterCode));
var boundingBox = GetBoundingBoxInGlyphSpace(characterCode);
boundingBox = fontMatrix.Transform(boundingBox);
return new CharacterBoundingBox(boundingBox, boundingBox);
}
private PdfRectangle GetBoundingBoxInGlyphSpace(int characterCode)

View File

@@ -44,9 +44,13 @@
return true;
}
public PdfRectangle GetBoundingBox(int characterCode)
public CharacterBoundingBox GetBoundingBox(int characterCode)
{
return fontMatrix.Transform(GetBoundingBoxInGlyphSpace(characterCode));
var boundingBox = GetBoundingBoxInGlyphSpace(characterCode);
boundingBox = fontMatrix.Transform(boundingBox);
return new CharacterBoundingBox(boundingBox, boundingBox);
}
private PdfRectangle GetBoundingBoxInGlyphSpace(int characterCode)

View File

@@ -63,9 +63,13 @@
return true;
}
public PdfRectangle GetBoundingBox(int characterCode)
public CharacterBoundingBox GetBoundingBox(int characterCode)
{
return fontMatrix.Transform(GetBoundingBoxInGlyphSpace(characterCode));
var boundingBox = GetBoundingBoxInGlyphSpace(characterCode);
boundingBox = fontMatrix.Transform(boundingBox);
return new CharacterBoundingBox(boundingBox, boundingBox);
}
private PdfRectangle GetBoundingBoxInGlyphSpace(int characterCode)

View File

@@ -57,11 +57,12 @@
return true;
}
public bool TryGetBoundingAdvancedWidth(int characterCode, out decimal width)
public bool TryGetBoundingAdvancedWidth(int characterIdentifier, out decimal width) => TryGetBoundingAdvancedWidth(characterIdentifier, null, out width);
public bool TryGetBoundingAdvancedWidth(int characterIdentifier, Func<int, int> characterIdentifierToGlyphIndex, out decimal width)
{
width = 0m;
if (!TryGetGlyphIndex(characterCode, null, out var index))
if (!TryGetGlyphIndex(characterIdentifier, characterIdentifierToGlyphIndex, out var index))
{
return false;
}

View File

@@ -128,22 +128,25 @@
var boundingBox = font.GetBoundingBox(code);
var transformedDisplacement = transformationMatrix
var transformedGlyphBounds = transformationMatrix
.Transform(TextMatrices.TextMatrix
.Transform(renderingMatrix
.Transform(boundingBox)));
.Transform(boundingBox.GlyphBounds)));
var transformedGlyphOrigin = transformationMatrix
.Transform(TextMatrices.TextMatrix
.Transform(renderingMatrix.Transform(boundingBox.CharacterBounds)));
ShowGlyph(font, transformedDisplacement, unicode, fontSize, pointSize);
ShowGlyph(font, transformedGlyphBounds, transformedGlyphOrigin, unicode, fontSize, pointSize);
decimal tx, ty;
if (font.IsVertical)
{
tx = 0;
ty = boundingBox.Height * fontSize + characterSpacing + wordSpacing;
ty = boundingBox.CharacterBounds.Height * fontSize + characterSpacing + wordSpacing;
}
else
{
tx = (boundingBox.Width * fontSize + characterSpacing + wordSpacing) * horizontalScaling;
tx = (boundingBox.CharacterBounds.Width * fontSize + characterSpacing + wordSpacing) * horizontalScaling;
ty = 0;
}
@@ -211,9 +214,9 @@
TextMatrices.TextMatrix = newMatrix;
}
private void ShowGlyph(IFont font, PdfRectangle rectangle, string unicode, decimal fontSize, decimal pointSize)
private void ShowGlyph(IFont font, PdfRectangle glyphRectangle, PdfRectangle characterRectangle, string unicode, decimal fontSize, decimal pointSize)
{
var letter = new Letter(unicode, rectangle, fontSize, font.Name.Data, pointSize);
var letter = new Letter(unicode, glyphRectangle, characterRectangle, fontSize, font.Name.Data, pointSize);
Letters.Add(letter);
}