make rectangle a struct. add infrastructure for handling composite glyphs

This commit is contained in:
Eliot Jones
2018-04-14 14:11:10 +01:00
parent 08f11343d3
commit ebdda46098
12 changed files with 307 additions and 57 deletions

View File

@@ -63,11 +63,11 @@
Assert.Equal(13, font.HeaderTable.Modified.Minute); Assert.Equal(13, font.HeaderTable.Modified.Minute);
Assert.Equal(10, font.HeaderTable.Modified.Second); Assert.Equal(10, font.HeaderTable.Modified.Second);
Assert.Equal(-980, font.HeaderTable.XMin); Assert.Equal(-980, font.HeaderTable.Bounds.Left);
Assert.Equal(-555, font.HeaderTable.YMin); Assert.Equal(-555, font.HeaderTable.Bounds.Bottom);
Assert.Equal(2396, font.HeaderTable.XMax); Assert.Equal(2396, font.HeaderTable.Bounds.Right);
Assert.Equal(2163, font.HeaderTable.YMax); Assert.Equal(2163, font.HeaderTable.Bounds.Top);
Assert.Equal(HeaderTable.HeaderMacStyle.None, font.HeaderTable.MacStyle); Assert.Equal(HeaderTable.HeaderMacStyle.None, font.HeaderTable.MacStyle);
Assert.Equal(9, font.HeaderTable.LowestRecommendedPpem); Assert.Equal(9, font.HeaderTable.LowestRecommendedPpem);

View File

@@ -12,7 +12,7 @@
[NotNull] [NotNull]
public PdfRectangle Bounds { get; } public PdfRectangle Bounds { get; }
public CropBox(PdfRectangle bounds) public CropBox(PdfRectangle? bounds)
{ {
Bounds = bounds ?? throw new ArgumentNullException(nameof(bounds)); Bounds = bounds ?? throw new ArgumentNullException(nameof(bounds));
} }

View File

@@ -68,7 +68,7 @@
public PdfRectangle Bounds { get; } public PdfRectangle Bounds { get; }
public MediaBox(PdfRectangle bounds) public MediaBox(PdfRectangle? bounds)
{ {
Bounds = bounds ?? throw new ArgumentNullException(nameof(bounds)); Bounds = bounds ?? throw new ArgumentNullException(nameof(bounds));
} }

View File

@@ -82,7 +82,9 @@
var cid = CMap.ConvertToCid(characterCode); var cid = CMap.ConvertToCid(characterCode);
var fromFont = CidFont.GetWidthFromDictionary(cid); var fromFont = CidFont.GetWidthFromDictionary(cid);
var box = GetBoundingBox(characterCode);
return fromFont; return fromFont;
} }

View File

@@ -0,0 +1,49 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Tables
{
using System;
[Flags]
internal enum CompositeGlyphFlags : ushort
{
/// <summary>
/// If set arguments are words, otherwise they are bytes.
/// </summary>
Args1And2AreWords = 1,
/// <summary>
/// If set arguments are x y offset values, otherwise they are points.
/// </summary>
ArgsAreXAndYValues = 1 << 1,
/// <summary>
/// If arguments are x y offset values and this is set then the values are rounded to the closest grid lines before addition to the glyph.
/// </summary>
RoundXAndYToGrid = 1 << 2,
/// <summary>
/// If set the scale value is read in 2.14 format (between -2 to &lt; 2) and the glyph is scaled before grid-fitting. Otherwise scale is 1.
/// </summary>
WeHaveAScale = 1 << 3,
/// <summary>
/// Reserved for future use, should be set to 0.
/// </summary>
Reserved = 1 << 4,
/// <summary>
/// Indicates that there is a glyph following the current one.
/// </summary>
MoreComponents = 1 << 5,
/// <summary>
/// Indicates that X is scaled differently to Y.
/// </summary>
WeHaveAnXAndYScale = 1 << 6,
/// <summary>
/// Indicates that there is a 2 by 2 transformation used to scale the component.
/// </summary>
WeHaveATwoByTwo = 1 << 7,
/// <summary>
/// Indicates that there are instructions for the composite character following the last component.
/// </summary>
WeHaveInstructions = 1 << 8,
/// <summary>
/// If set this forces advance width and left side bearing for the composite to be equal to those from the original glyph.
/// </summary>
UseMyMetrics = 1 << 9
}
}

View File

@@ -28,7 +28,6 @@
{ {
data.Seek(table.Offset); data.Seek(table.Offset);
var headerTable = tableRegister.HeaderTable;
var indexToLocationTable = tableRegister.IndexToLocationTable; var indexToLocationTable = tableRegister.IndexToLocationTable;
var offsets = indexToLocationTable.GlyphOffsets; var offsets = indexToLocationTable.GlyphOffsets;
@@ -39,11 +38,16 @@
var glyphs = new IGlyphDescription[glyphCount]; var glyphs = new IGlyphDescription[glyphCount];
var emptyGlyph = new EmptyGlyph(tableRegister.HeaderTable.Bounds);
var compositeLocations = new Dictionary<int, TemporaryCompositeLocation>();
for (var i = 0; i < glyphCount; i++) for (var i = 0; i < glyphCount; i++)
{ {
if (offsets[i] == offsets[i + 1]) if (offsets[i] == offsets[i + 1])
{ {
// empty glyph // empty glyph
glyphs[i] = emptyGlyph;
continue; continue;
} }
@@ -55,9 +59,9 @@
var minY = data.ReadSignedShort(); var minY = data.ReadSignedShort();
var maxX = data.ReadSignedShort(); var maxX = data.ReadSignedShort();
var maxY = data.ReadSignedShort(); var maxY = data.ReadSignedShort();
var bounds = new PdfRectangle(minX, minY, maxX, maxY); var bounds = new PdfRectangle(minX, minY, maxX, maxY);
// If the number of contours is greater than or equal zero it's a simple glyph. // If the number of contours is greater than or equal zero it's a simple glyph.
if (contourCount >= 0) if (contourCount >= 0)
{ {
@@ -65,10 +69,16 @@
} }
else else
{ {
compositeLocations.Add(i , new TemporaryCompositeLocation(data.Position, bounds, contourCount));
} }
} }
// Build composite glyphs by combining simple and other composite glyphs.
foreach (var compositeLocation in compositeLocations)
{
glyphs[compositeLocation.Key] = ReadCompositeGlyph(data, compositeLocation.Value, compositeLocations, glyphs);
}
return new GlyphDataTable(table, glyphs); return new GlyphDataTable(table, glyphs);
} }
@@ -97,6 +107,103 @@
return new SimpleGlyphDescription(instructionLength, endPointsOfContours, flags, xCoordinates, yCoordinates, bounds); return new SimpleGlyphDescription(instructionLength, endPointsOfContours, flags, xCoordinates, yCoordinates, bounds);
} }
private static CompositeGlyphDescription ReadCompositeGlyph(TrueTypeDataBytes data, TemporaryCompositeLocation compositeLocation, Dictionary<int, TemporaryCompositeLocation> compositeLocations, IGlyphDescription[] glyphs)
{
bool HasFlag(CompositeGlyphFlags value, CompositeGlyphFlags target)
{
return (value & target) == target;
}
data.Seek(compositeLocation.Position);
CompositeGlyphFlags flags;
do
{
flags = (CompositeGlyphFlags) data.ReadUnsignedShort();
var glyphIndex = data.ReadUnsignedShort();
var childGlyph = glyphs[glyphIndex];
if (childGlyph == null)
{
if (!compositeLocations.TryGetValue(glyphIndex, out var missingComposite))
{
throw new InvalidOperationException($"The composite glyph required a contour at index {glyphIndex} but there was no simple or composite glyph at this location.");
}
childGlyph = ReadCompositeGlyph(data, missingComposite, compositeLocations, glyphs);
glyphs[glyphIndex] = childGlyph;
}
var cloned = childGlyph.DeepClone();
short arg1, arg2;
if (HasFlag(flags, CompositeGlyphFlags.Args1And2AreWords))
{
arg1 = data.ReadSignedShort();
arg2 = data.ReadSignedShort();
}
else
{
arg1 = data.ReadByte();
arg2 = data.ReadByte();
}
float xscale = 1;
float scale01 = 0;
float scale10 = 0;
float yscale = 1;
bool hasScale, hasMatrix = false;
if (HasFlag(flags, CompositeGlyphFlags.WeHaveAScale))
{
xscale = ReadTwoFourteenFormat(data);
yscale = xscale;
hasScale = true;
}
else if (HasFlag(flags, CompositeGlyphFlags.WeHaveAnXAndYScale))
{
xscale = ReadTwoFourteenFormat(data);
yscale = ReadTwoFourteenFormat(data);
hasScale = true;
}
else if (HasFlag(flags, CompositeGlyphFlags.WeHaveATwoByTwo))
{
/*
* We build the 2 by 2 matrix:
* x 0
* 0 y
*/
xscale = ReadTwoFourteenFormat(data);
scale01 = ReadTwoFourteenFormat(data);
scale10 = ReadTwoFourteenFormat(data);
yscale = ReadTwoFourteenFormat(data);
hasScale = true;
hasMatrix = true;
}
if (HasFlag(flags, CompositeGlyphFlags.ArgsAreXAndYValues))
{
}
else
{
// TODO: Not implemented, it is unclear how to do this.
}
} while (HasFlag(flags, CompositeGlyphFlags.MoreComponents));
return new CompositeGlyphDescription();
}
private static float ReadTwoFourteenFormat(TrueTypeDataBytes data)
{
const float divisor = 1 << 14;
return data.ReadSignedShort() / divisor;
}
private static SimpleGlyphFlags[] ReadFlags(TrueTypeDataBytes data, int pointCount) private static SimpleGlyphFlags[] ReadFlags(TrueTypeDataBytes data, int pointCount)
{ {
var result = new SimpleGlyphFlags[pointCount]; var result = new SimpleGlyphFlags[pointCount];
@@ -140,36 +247,39 @@
return xs; return xs;
} }
}
[Flags]
internal enum SimpleGlyphFlags : byte
{
/// <summary> /// <summary>
/// The point is on the curve. /// Stores the composite glyph information we read when initially scanning the glyph table.
/// Once we have all composite glyphs we can start building them from simple glyphs.
/// </summary> /// </summary>
OnCurve = 1, private struct TemporaryCompositeLocation
/// <summary> {
/// The x-coordinate is 1 byte long instead of 2. /// <summary>
/// </summary> /// Stores the position after reading the contour count and bounds.
XShortVector = 1 << 1, /// </summary>
/// <summary> public long Position { get; }
/// The y-coordinate is 1 byte long instead of 2.
/// </summary> /// <summary>
YShortVector = 1 << 2, /// The bounds we read.
/// <summary> /// </summary>
/// The next byte specifies the number of times to repeat this set of flags. public PdfRectangle Bounds { get; }
/// </summary>
Repeat = 1 << 3, /// <summary>
/// <summary> /// The number of contours in this composite glyph. Should be less than zero.
/// If <see cref="XShortVector"/> is set this means the sign of the x-coordinate is positive. /// </summary>
/// If <see cref="XShortVector"/> is not set then the current x-coordinate is the same as the previous. public short ContourCount { get; }
/// </summary>
XSignOrSame = 1 << 4, public TemporaryCompositeLocation(long position, PdfRectangle bounds, short contourCount)
/// <summary> {
/// If <see cref="YShortVector"/> is set this means the sign of the y-coordinate is positive. Position = position;
/// If <see cref="YShortVector"/> is not set then the current y-coordinate is the same as the previous. Bounds = bounds;
/// </summary> ContourCount = contourCount;
YSignOrSame = 1 << 5
if (ContourCount >= 0 )
{
throw new ArgumentException($"A composite glyph should not have a positive contour count. Got: {contourCount}.", nameof(contourCount));
}
}
}
} }
} }

View File

@@ -1,6 +1,7 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Tables namespace UglyToad.PdfPig.Fonts.TrueType.Tables
{ {
using System; using System;
using Geometry;
/// <summary> /// <summary>
/// Gives global information about the font. /// Gives global information about the font.
@@ -27,13 +28,7 @@
public DateTime Modified { get; } public DateTime Modified { get; }
public short XMin { get; } public PdfRectangle Bounds { get; }
public short YMin { get; }
public short XMax { get; }
public short YMax { get; }
public HeaderMacStyle MacStyle { get; } public HeaderMacStyle MacStyle { get; }
@@ -74,10 +69,7 @@
UnitsPerEm = unitsPerEm; UnitsPerEm = unitsPerEm;
Created = created; Created = created;
Modified = modified; Modified = modified;
XMin = xMin; Bounds = new PdfRectangle(xMin, yMin, xMax, yMax);
YMin = yMin;
XMax = xMax;
YMax = yMax;
MacStyle = (HeaderMacStyle)macStyle; MacStyle = (HeaderMacStyle)macStyle;
LowestRecommendedPpem = lowestRecommendedPpem; LowestRecommendedPpem = lowestRecommendedPpem;
FontDirectionHint = (FontDirection)fontDirectionHint; FontDirectionHint = (FontDirection)fontDirectionHint;

View File

@@ -1,5 +1,6 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Tables namespace UglyToad.PdfPig.Fonts.TrueType.Tables
{ {
using System;
using Geometry; using Geometry;
internal interface IGlyphDescription internal interface IGlyphDescription
@@ -8,8 +9,10 @@
SimpleGlyphDescription SimpleGlyph { get; } SimpleGlyphDescription SimpleGlyph { get; }
object CompositeGlyph { get; } CompositeGlyphDescription CompositeGlyph { get; }
PdfRectangle GlyphBounds { get; } PdfRectangle GlyphBounds { get; }
IGlyphDescription DeepClone();
} }
} }

View File

@@ -1,5 +1,6 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Tables namespace UglyToad.PdfPig.Fonts.TrueType.Tables
{ {
using System;
using Geometry; using Geometry;
internal class SimpleGlyphDescription : IGlyphDescription internal class SimpleGlyphDescription : IGlyphDescription
@@ -13,7 +14,7 @@
/// The total number of bytes for instructions. /// The total number of bytes for instructions.
/// </summary> /// </summary>
public int InstructionLength { get; } public int InstructionLength { get; }
/// <summary> /// <summary>
/// An array of the last points of each contour. /// An array of the last points of each contour.
/// </summary> /// </summary>
@@ -29,7 +30,7 @@
/// the rest are relative to the previous point. /// the rest are relative to the previous point.
/// </summary> /// </summary>
public short[] XCoordinates { get; } public short[] XCoordinates { get; }
/// <summary> /// <summary>
/// The y-coordinates of the points in this glyph. The first coordinates are relative to the origin (0, 0) /// The y-coordinates of the points in this glyph. The first coordinates are relative to the origin (0, 0)
/// the rest are relative to the previous point. /// the rest are relative to the previous point.
@@ -51,6 +52,59 @@
public SimpleGlyphDescription SimpleGlyph => this; public SimpleGlyphDescription SimpleGlyph => this;
public object CompositeGlyph { get; } = null; public CompositeGlyphDescription CompositeGlyph { get; } = null;
public IGlyphDescription DeepClone()
{
var clonedEndPoints = new int[EndPointsOfContours.Length];
Array.Copy(EndPointsOfContours, clonedEndPoints, EndPointsOfContours.Length);
var clonedFlags = new SimpleGlyphFlags[Flags.Length];
Array.Copy(Flags, clonedFlags, Flags.Length);
var clonedXCoordinates = new short[XCoordinates.Length];
Array.Copy(XCoordinates, clonedXCoordinates, XCoordinates.Length);
var clonedYCoordinates = new short[YCoordinates.Length];
Array.Copy(YCoordinates, clonedYCoordinates, YCoordinates.Length);
return new SimpleGlyphDescription(InstructionLength, clonedEndPoints, clonedFlags, clonedXCoordinates, clonedYCoordinates, GlyphBounds);
}
}
internal class CompositeGlyphDescription : IGlyphDescription
{
public bool IsSimple { get; } = false;
public SimpleGlyphDescription SimpleGlyph { get; } = null;
public CompositeGlyphDescription CompositeGlyph => this;
public PdfRectangle GlyphBounds { get; }
public IGlyphDescription DeepClone()
{
return new CompositeGlyphDescription();
}
}
internal class EmptyGlyph : IGlyphDescription
{
public bool IsSimple { get; } = true;
public SimpleGlyphDescription SimpleGlyph => new SimpleGlyphDescription(0, new int[0], new SimpleGlyphFlags[0], new short[0], new short[0], GlyphBounds);
public CompositeGlyphDescription CompositeGlyph { get; } = null;
public PdfRectangle GlyphBounds { get; }
public EmptyGlyph(PdfRectangle glyphBounds)
{
GlyphBounds = glyphBounds;
}
public IGlyphDescription DeepClone()
{
return new EmptyGlyph(GlyphBounds);
}
} }
} }

View File

@@ -0,0 +1,35 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Tables
{
using System;
[Flags]
internal enum SimpleGlyphFlags : byte
{
/// <summary>
/// The point is on the curve.
/// </summary>
OnCurve = 1,
/// <summary>
/// The x-coordinate is 1 byte long instead of 2.
/// </summary>
XShortVector = 1 << 1,
/// <summary>
/// The y-coordinate is 1 byte long instead of 2.
/// </summary>
YShortVector = 1 << 2,
/// <summary>
/// The next byte specifies the number of times to repeat this set of flags.
/// </summary>
Repeat = 1 << 3,
/// <summary>
/// If <see cref="XShortVector"/> is set this means the sign of the x-coordinate is positive.
/// If <see cref="XShortVector"/> is not set then the current x-coordinate is the same as the previous.
/// </summary>
XSignOrSame = 1 << 4,
/// <summary>
/// If <see cref="YShortVector"/> is set this means the sign of the y-coordinate is positive.
/// If <see cref="YShortVector"/> is not set then the current y-coordinate is the same as the previous.
/// </summary>
YSignOrSame = 1 << 5
}
}

View File

@@ -148,7 +148,7 @@
encryptedPortionParser.Parse(eexecPortion); encryptedPortionParser.Parse(eexecPortion);
return new Type1Font(name, encoding, matrix, boundingBox); return new Type1Font(name, encoding, matrix, boundingBox ?? new PdfRectangle());
} }
/// <summary> /// <summary>
@@ -386,7 +386,7 @@
return null; return null;
} }
private static PdfRectangle GetBoundingBox(IReadOnlyList<DictionaryToken> dictionaries) private static PdfRectangle? GetBoundingBox(IReadOnlyList<DictionaryToken> dictionaries)
{ {
foreach (var dictionary in dictionaries) foreach (var dictionary in dictionaries)
{ {

View File

@@ -2,7 +2,7 @@
{ {
using System; using System;
internal class PdfRectangle internal struct PdfRectangle
{ {
public PdfPoint TopLeft { get; } public PdfPoint TopLeft { get; }
@@ -18,6 +18,11 @@
public decimal Area { get; } public decimal Area { get; }
public decimal Left => TopLeft.X;
public decimal Top => TopLeft.Y;
public decimal Right => BottomRight.X;
public decimal Bottom => BottomRight.Y;
public PdfRectangle(PdfPoint point1, PdfPoint point2) : this(point1.X, point1.Y, point2.X, point2.Y) { } 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(short x1, short y1, short x2, short y2) : this((decimal) x1, y1, x2, y2) { }
public PdfRectangle(decimal x1, decimal y1, decimal x2, decimal y2) public PdfRectangle(decimal x1, decimal y1, decimal x2, decimal y2)