mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-10-14 19:05:01 +08:00
unify truetype glyphs to a single class. build composite glyphs from elements
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,7 @@
|
||||
|
||||
public static AssertablePositionData Parse(string line)
|
||||
{
|
||||
var parts = line.Split('\t', StringSplitOptions.None);
|
||||
var parts = line.Split('\t');
|
||||
|
||||
if (parts.Length < 6)
|
||||
{
|
||||
@@ -36,7 +36,8 @@
|
||||
Width = decimal.Parse(parts[2]),
|
||||
Text = parts[3],
|
||||
FontSize = decimal.Parse(parts[4]),
|
||||
FontName = parts[5]
|
||||
FontName = parts[5],
|
||||
Height = height
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -1,20 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs
|
||||
{
|
||||
using Geometry;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,25 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs
|
||||
{
|
||||
using Geometry;
|
||||
|
||||
internal class EmptyGlyph : IGlyphDescription
|
||||
{
|
||||
public bool IsSimple { get; } = true;
|
||||
|
||||
public SimpleGlyphDescription SimpleGlyph => new SimpleGlyphDescription(new byte[0], new int[0], new GlyphPoint[0], GlyphBounds);
|
||||
|
||||
public CompositeGlyphDescription CompositeGlyph { get; } = null;
|
||||
|
||||
public PdfRectangle GlyphBounds { get; }
|
||||
|
||||
public EmptyGlyph(PdfRectangle glyphBounds)
|
||||
{
|
||||
GlyphBounds = glyphBounds;
|
||||
}
|
||||
|
||||
public IGlyphDescription DeepClone()
|
||||
{
|
||||
return new EmptyGlyph(GlyphBounds);
|
||||
}
|
||||
}
|
||||
}
|
125
src/UglyToad.PdfPig/Fonts/TrueType/Glyphs/Glyph.cs
Normal file
125
src/UglyToad.PdfPig/Fonts/TrueType/Glyphs/Glyph.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs
|
||||
{
|
||||
using System;
|
||||
using Geometry;
|
||||
|
||||
internal class Glyph : IGlyphDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// The bounding rectangle for the character.
|
||||
/// </summary>
|
||||
public PdfRectangle Bounds { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The bytes of the instructions for this glyph.
|
||||
/// </summary>
|
||||
public byte[] Instructions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// An array of the last points of each contour.
|
||||
/// </summary>
|
||||
public int[] EndPointsOfContours { get; }
|
||||
|
||||
public GlyphPoint[] Points { get; }
|
||||
|
||||
public bool IsSimple { get; }
|
||||
|
||||
public Glyph(bool isSimple, byte[] instructions, int[] endPointsOfContours, GlyphPoint[] points,
|
||||
PdfRectangle bounds)
|
||||
{
|
||||
IsSimple = isSimple;
|
||||
Instructions = instructions;
|
||||
EndPointsOfContours = endPointsOfContours;
|
||||
Points = points;
|
||||
Bounds = bounds;
|
||||
}
|
||||
|
||||
public static IGlyphDescription Empty(PdfRectangle bounds)
|
||||
{
|
||||
return new Glyph(true, new byte[0], new int[0], new GlyphPoint[0], bounds);
|
||||
}
|
||||
|
||||
public IGlyphDescription DeepClone()
|
||||
{
|
||||
var clonedInstructions = new byte[Instructions.Length];
|
||||
Array.Copy(Instructions, clonedInstructions, Instructions.Length);
|
||||
|
||||
var clonedEndPoints = new int[EndPointsOfContours.Length];
|
||||
Array.Copy(EndPointsOfContours, clonedEndPoints, EndPointsOfContours.Length);
|
||||
|
||||
var clonedPoints = new GlyphPoint[Points.Length];
|
||||
Array.Copy(Points, clonedPoints, Points.Length);
|
||||
|
||||
return new Glyph(false, clonedInstructions, clonedEndPoints, clonedPoints, Bounds);
|
||||
}
|
||||
|
||||
public IGlyphDescription Merge(IGlyphDescription glyph)
|
||||
{
|
||||
var newPoints = MergePoints(glyph);
|
||||
var newEndpoints = MergeContourEndPoints(glyph);
|
||||
|
||||
return new Glyph(false, Instructions, newEndpoints, newPoints, Bounds);
|
||||
}
|
||||
|
||||
private GlyphPoint[] MergePoints(IGlyphDescription glyph)
|
||||
{
|
||||
var newPoints = new GlyphPoint[Points.Length + glyph.Points.Length];
|
||||
|
||||
for (int i = 0; i < Points.Length; i++)
|
||||
{
|
||||
newPoints[i] = Points[i];
|
||||
}
|
||||
|
||||
for (int i = 0; i < glyph.Points.Length; i++)
|
||||
{
|
||||
newPoints[i + Points.Length] = glyph.Points[i];
|
||||
}
|
||||
|
||||
return newPoints;
|
||||
}
|
||||
|
||||
private int[] MergeContourEndPoints(IGlyphDescription glyph)
|
||||
{
|
||||
var destinationLastEndPoint = EndPointsOfContours[EndPointsOfContours.Length - 1] + 1;
|
||||
|
||||
var endPoints = new int[EndPointsOfContours.Length + glyph.EndPointsOfContours.Length];
|
||||
|
||||
for (var i = 0; i < EndPointsOfContours.Length; i++)
|
||||
{
|
||||
endPoints[i] = EndPointsOfContours[i];
|
||||
}
|
||||
|
||||
for (var i = 0; i < glyph.EndPointsOfContours.Length; i++)
|
||||
{
|
||||
endPoints[i + EndPointsOfContours.Length] = glyph.EndPointsOfContours[i] + destinationLastEndPoint;
|
||||
}
|
||||
|
||||
return endPoints;
|
||||
}
|
||||
|
||||
public IGlyphDescription Transform(PdfMatrix3By2 matrix)
|
||||
{
|
||||
var newPoints = new GlyphPoint[Points.Length];
|
||||
|
||||
for (var i = Points.Length - 1; i >= 0; i--)
|
||||
{
|
||||
var point = Points[i];
|
||||
|
||||
var scaled = matrix.ScaleAndRotate(point.Point);
|
||||
|
||||
scaled = matrix.Translate(scaled);
|
||||
|
||||
newPoints[i] = new GlyphPoint(scaled, point.IsOnCurve);
|
||||
}
|
||||
|
||||
return new Glyph(IsSimple, Instructions, EndPointsOfContours, newPoints, Bounds);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var type = IsSimple ? "S" : "C";
|
||||
|
||||
return $"{type}: Width {Bounds.Width}, Height: {Bounds.Height}, Points: {Points.Length}";
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,16 +2,28 @@
|
||||
{
|
||||
using Geometry;
|
||||
|
||||
internal interface IGlyphDescription
|
||||
internal interface IGlyphDescription : IMergeableGlyph, ITransformableGlyph
|
||||
{
|
||||
bool IsSimple { get; }
|
||||
|
||||
SimpleGlyphDescription SimpleGlyph { get; }
|
||||
PdfRectangle Bounds { get; }
|
||||
|
||||
CompositeGlyphDescription CompositeGlyph { get; }
|
||||
byte[] Instructions { get; }
|
||||
|
||||
PdfRectangle GlyphBounds { get; }
|
||||
int[] EndPointsOfContours { get; }
|
||||
|
||||
GlyphPoint[] Points { get; }
|
||||
|
||||
IGlyphDescription DeepClone();
|
||||
}
|
||||
|
||||
internal interface IMergeableGlyph
|
||||
{
|
||||
IGlyphDescription Merge(IGlyphDescription glyph);
|
||||
}
|
||||
|
||||
internal interface ITransformableGlyph
|
||||
{
|
||||
IGlyphDescription Transform(PdfMatrix3By2 matrix);
|
||||
}
|
||||
}
|
@@ -1,54 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs
|
||||
{
|
||||
using System;
|
||||
using Geometry;
|
||||
|
||||
internal class SimpleGlyphDescription : IGlyphDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// The bounding rectangle for the character.
|
||||
/// </summary>
|
||||
public PdfRectangle GlyphBounds { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The total number of bytes for instructions.
|
||||
/// </summary>
|
||||
public byte[] Instructions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// An array of the last points of each contour.
|
||||
/// </summary>
|
||||
public int[] EndPointsOfContours { get; }
|
||||
|
||||
public GlyphPoint[] Points { get; set; }
|
||||
|
||||
public SimpleGlyphDescription(byte[] instructions, int[] endPointsOfContours, GlyphPoint[] points,
|
||||
PdfRectangle bounds)
|
||||
{
|
||||
Instructions = instructions;
|
||||
EndPointsOfContours = endPointsOfContours;
|
||||
Points = points;
|
||||
GlyphBounds = bounds;
|
||||
}
|
||||
|
||||
public bool IsSimple { get; } = true;
|
||||
|
||||
public SimpleGlyphDescription SimpleGlyph => this;
|
||||
|
||||
public CompositeGlyphDescription CompositeGlyph { get; } = null;
|
||||
|
||||
public IGlyphDescription DeepClone()
|
||||
{
|
||||
var clonedInstructions = new byte[Instructions.Length];
|
||||
Array.Copy(Instructions, clonedInstructions, Instructions.Length);
|
||||
|
||||
var clonedEndPoints = new int[EndPointsOfContours.Length];
|
||||
Array.Copy(EndPointsOfContours, clonedEndPoints, EndPointsOfContours.Length);
|
||||
|
||||
var clonedPoints = new GlyphPoint[Points.Length];
|
||||
Array.Copy(Points, clonedPoints, Points.Length);
|
||||
|
||||
return new SimpleGlyphDescription(clonedInstructions, clonedEndPoints, clonedPoints, GlyphBounds);
|
||||
}
|
||||
}
|
||||
}
|
@@ -39,7 +39,7 @@
|
||||
|
||||
var glyphs = new IGlyphDescription[glyphCount];
|
||||
|
||||
var emptyGlyph = new EmptyGlyph(tableRegister.HeaderTable.Bounds);
|
||||
var emptyGlyph = Glyph.Empty(tableRegister.HeaderTable.Bounds);
|
||||
|
||||
var compositeLocations = new Dictionary<int, TemporaryCompositeLocation>();
|
||||
|
||||
@@ -77,13 +77,13 @@
|
||||
// Build composite glyphs by combining simple and other composite glyphs.
|
||||
foreach (var compositeLocation in compositeLocations)
|
||||
{
|
||||
glyphs[compositeLocation.Key] = ReadCompositeGlyph(data, compositeLocation.Value, compositeLocations, glyphs);
|
||||
glyphs[compositeLocation.Key] = ReadCompositeGlyph(data, compositeLocation.Value, compositeLocations, glyphs, emptyGlyph);
|
||||
}
|
||||
|
||||
|
||||
return new GlyphDataTable(table, glyphs);
|
||||
}
|
||||
|
||||
private static SimpleGlyphDescription ReadSimpleGlyph(TrueTypeDataBytes data, short contourCount, PdfRectangle bounds)
|
||||
private static Glyph ReadSimpleGlyph(TrueTypeDataBytes data, short contourCount, PdfRectangle bounds)
|
||||
{
|
||||
var endPointsOfContours = data.ReadUnsignedShortArray(contourCount);
|
||||
|
||||
@@ -112,10 +112,11 @@
|
||||
points[i] = new GlyphPoint(xCoordinates[i], yCoordinates[i], isOnCurve);
|
||||
}
|
||||
|
||||
return new SimpleGlyphDescription(instructions, endPointsOfContours, points, bounds);
|
||||
return new Glyph(true, instructions, endPointsOfContours, points, bounds);
|
||||
}
|
||||
|
||||
private static CompositeGlyphDescription ReadCompositeGlyph(TrueTypeDataBytes data, TemporaryCompositeLocation compositeLocation, Dictionary<int, TemporaryCompositeLocation> compositeLocations, IGlyphDescription[] glyphs)
|
||||
private static IGlyphDescription ReadCompositeGlyph(TrueTypeDataBytes data, TemporaryCompositeLocation compositeLocation, Dictionary<int, TemporaryCompositeLocation> compositeLocations, IGlyphDescription[] glyphs,
|
||||
IGlyphDescription emptyGlyph)
|
||||
{
|
||||
bool HasFlag(CompositeGlyphFlags value, CompositeGlyphFlags target)
|
||||
{
|
||||
@@ -125,7 +126,8 @@
|
||||
data.Seek(compositeLocation.Position);
|
||||
|
||||
var components = new List<CompositeComponent>();
|
||||
|
||||
|
||||
// First recursively find all components and ensure they are available.
|
||||
CompositeGlyphFlags flags;
|
||||
do
|
||||
{
|
||||
@@ -141,13 +143,11 @@
|
||||
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);
|
||||
childGlyph = ReadCompositeGlyph(data, missingComposite, compositeLocations, glyphs, emptyGlyph);
|
||||
|
||||
glyphs[glyphIndex] = childGlyph;
|
||||
}
|
||||
|
||||
var cloned = childGlyph.DeepClone();
|
||||
|
||||
|
||||
short arg1, arg2;
|
||||
if (HasFlag(flags, CompositeGlyphFlags.Args1And2AreWords))
|
||||
{
|
||||
@@ -194,14 +194,27 @@
|
||||
|
||||
} while (HasFlag(flags, CompositeGlyphFlags.MoreComponents));
|
||||
|
||||
return new CompositeGlyphDescription();
|
||||
}
|
||||
// Now build the final glyph from the components.
|
||||
IGlyphDescription builderGlyph = null;
|
||||
foreach (var component in components)
|
||||
{
|
||||
var glyph = glyphs[component.Index];
|
||||
|
||||
private static decimal ReadTwoFourteenFormat(TrueTypeDataBytes data)
|
||||
{
|
||||
const decimal divisor = 1 << 14;
|
||||
var transformed = glyph.Transform(component.Transformation);
|
||||
|
||||
return data.ReadSignedShort() / divisor;
|
||||
if (builderGlyph == null)
|
||||
{
|
||||
builderGlyph = transformed;
|
||||
}
|
||||
else
|
||||
{
|
||||
builderGlyph = builderGlyph.Merge(transformed);
|
||||
}
|
||||
}
|
||||
|
||||
builderGlyph = builderGlyph ?? emptyGlyph;
|
||||
|
||||
return new Glyph(false, builderGlyph.Instructions, builderGlyph.EndPointsOfContours, builderGlyph.Points, compositeLocation.Bounds);
|
||||
}
|
||||
|
||||
private static SimpleGlyphFlags[] ReadFlags(TrueTypeDataBytes data, int pointCount)
|
||||
@@ -248,6 +261,13 @@
|
||||
return xs;
|
||||
}
|
||||
|
||||
private static decimal ReadTwoFourteenFormat(TrueTypeDataBytes data)
|
||||
{
|
||||
const decimal divisor = 1 << 14;
|
||||
|
||||
return data.ReadSignedShort() / divisor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
@@ -258,27 +278,18 @@
|
||||
/// Stores the position after reading the contour count and bounds.
|
||||
/// </summary>
|
||||
public long Position { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The bounds we read.
|
||||
/// </summary>
|
||||
public PdfRectangle Bounds { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of contours in this composite glyph. Should be less than zero.
|
||||
/// </summary>
|
||||
public short ContourCount { get; }
|
||||
|
||||
public PdfRectangle Bounds { get; set; }
|
||||
|
||||
public TemporaryCompositeLocation(long position, PdfRectangle bounds, short contourCount)
|
||||
{
|
||||
Position = position;
|
||||
Bounds = bounds;
|
||||
ContourCount = contourCount;
|
||||
|
||||
if (ContourCount >= 0 )
|
||||
if (contourCount >= 0 )
|
||||
{
|
||||
throw new ArgumentException($"A composite glyph should not have a positive contour count. Got: {contourCount}.", nameof(contourCount));
|
||||
}
|
||||
|
||||
Position = position;
|
||||
Bounds = bounds;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -54,12 +54,12 @@
|
||||
|
||||
var glyph = GlyphTable.Glyphs[index];
|
||||
|
||||
if (glyph?.GlyphBounds == null)
|
||||
if (glyph?.Bounds == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
boundingBox = glyph.GlyphBounds;
|
||||
boundingBox = glyph.Bounds;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@@ -27,5 +27,18 @@
|
||||
{
|
||||
return new PdfMatrix3By2(r0c0, r0c1, r1c0, r1c1, x, y);
|
||||
}
|
||||
|
||||
public PdfPoint ScaleAndRotate(PdfPoint source)
|
||||
{
|
||||
var newX = source.X * r0c0 + source.Y * r1c0;
|
||||
var newY = source.X * r0c1 + source.Y * r1c1;
|
||||
|
||||
return new PdfPoint(newX, newY);
|
||||
}
|
||||
|
||||
public PdfPoint Translate(PdfPoint source)
|
||||
{
|
||||
return new PdfPoint(source.X + r2c0, source.Y + r2c1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user