mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-10-20 19:03:20 +08:00
merge from upstream
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
[NotNull]
|
||||
public PdfRectangle Bounds { get; }
|
||||
|
||||
public CropBox(PdfRectangle bounds)
|
||||
public CropBox(PdfRectangle? bounds)
|
||||
{
|
||||
Bounds = bounds ?? throw new ArgumentNullException(nameof(bounds));
|
||||
}
|
||||
|
@@ -68,7 +68,7 @@
|
||||
|
||||
public PdfRectangle Bounds { get; }
|
||||
|
||||
public MediaBox(PdfRectangle bounds)
|
||||
public MediaBox(PdfRectangle? bounds)
|
||||
{
|
||||
Bounds = bounds ?? throw new ArgumentNullException(nameof(bounds));
|
||||
}
|
||||
|
@@ -0,0 +1,49 @@
|
||||
namespace UglyToad.PdfPig.Fonts.CidFonts
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies mapping from character identifiers to glyph indices.
|
||||
/// Can either be defined as a name in which case it must be Identity or a stream which defines the mapping.
|
||||
/// </summary>
|
||||
internal class CharacterIdentifierToGlyphIndexMap
|
||||
{
|
||||
private readonly bool isIdentity;
|
||||
private readonly int[] map;
|
||||
|
||||
public CharacterIdentifierToGlyphIndexMap()
|
||||
{
|
||||
isIdentity = true;
|
||||
map = null;
|
||||
}
|
||||
|
||||
public CharacterIdentifierToGlyphIndexMap(byte[] streamBytes)
|
||||
{
|
||||
var numberOfEntries = streamBytes.Length / 2;
|
||||
|
||||
map = new int[numberOfEntries];
|
||||
var offset = 0;
|
||||
|
||||
for (var i = 0; i < numberOfEntries; i++)
|
||||
{
|
||||
var glyphIndex = (streamBytes[offset] << 8) | streamBytes[offset + 1];
|
||||
map[i] = glyphIndex;
|
||||
|
||||
offset += 2;
|
||||
}
|
||||
}
|
||||
|
||||
public int GetGlyphIndex(int characterIdentifier)
|
||||
{
|
||||
if (isIdentity)
|
||||
{
|
||||
return characterIdentifier;
|
||||
}
|
||||
|
||||
if (characterIdentifier >= map.Length || characterIdentifier < 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return map[characterIdentifier];
|
||||
}
|
||||
}
|
||||
}
|
@@ -42,6 +42,6 @@
|
||||
|
||||
decimal GetWidthFromDictionary(int cid);
|
||||
|
||||
PdfRectangle GetBoundingBox(int characterCode);
|
||||
PdfRectangle GetBoundingBox(int characterIdentifier);
|
||||
}
|
||||
}
|
@@ -1,5 +1,6 @@
|
||||
namespace UglyToad.PdfPig.Fonts.CidFonts
|
||||
{
|
||||
using System;
|
||||
using Geometry;
|
||||
|
||||
/// <summary>
|
||||
@@ -9,6 +10,8 @@
|
||||
{
|
||||
bool TryGetBoundingBox(int characterCode, out PdfRectangle boundingBox);
|
||||
|
||||
bool TryGetBoundingBox(int characterCode, Func<int, int> characterIdentifierToGlyphIndex, out PdfRectangle boundingBox);
|
||||
|
||||
bool TryGetBoundingAdvancedWidth(int characterCode, out decimal width);
|
||||
}
|
||||
}
|
||||
|
@@ -34,7 +34,7 @@
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public PdfRectangle GetBoundingBox(int characterCode)
|
||||
public PdfRectangle GetBoundingBox(int characterIdentifier)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
@@ -15,6 +15,7 @@
|
||||
private readonly ICidFontProgram fontProgram;
|
||||
private readonly VerticalWritingMetrics verticalWritingMetrics;
|
||||
private readonly IReadOnlyDictionary<int, decimal> widths;
|
||||
private readonly CharacterIdentifierToGlyphIndexMap cidToGid;
|
||||
|
||||
public NameToken Type { get; }
|
||||
public NameToken SubType { get; }
|
||||
@@ -24,10 +25,11 @@
|
||||
public CidFontType CidFontType => CidFontType.Type2;
|
||||
public FontDescriptor Descriptor { get; }
|
||||
|
||||
public Type2CidFont(NameToken type, NameToken subType, NameToken baseFont, CharacterIdentifierSystemInfo systemInfo,
|
||||
public Type2CidFont(NameToken type, NameToken subType, NameToken baseFont, CharacterIdentifierSystemInfo systemInfo,
|
||||
FontDescriptor descriptor, ICidFontProgram fontProgram,
|
||||
VerticalWritingMetrics verticalWritingMetrics,
|
||||
IReadOnlyDictionary<int, decimal> widths)
|
||||
IReadOnlyDictionary<int, decimal> widths,
|
||||
CharacterIdentifierToGlyphIndexMap cidToGid)
|
||||
{
|
||||
Type = type;
|
||||
SubType = subType;
|
||||
@@ -37,6 +39,7 @@
|
||||
this.fontProgram = fontProgram;
|
||||
this.verticalWritingMetrics = verticalWritingMetrics;
|
||||
this.widths = widths;
|
||||
this.cidToGid = cidToGid;
|
||||
|
||||
// TODO: This should maybe take units per em into account?
|
||||
var scale = 1 / 1000m;
|
||||
@@ -49,9 +52,9 @@
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public decimal GetWidthFromDictionary(int cid)
|
||||
public decimal GetWidthFromDictionary(int characterIdentifier)
|
||||
{
|
||||
if (widths.TryGetValue(cid, out var width))
|
||||
if (widths.TryGetValue(characterIdentifier, out var width))
|
||||
{
|
||||
return width;
|
||||
}
|
||||
@@ -59,14 +62,14 @@
|
||||
return Descriptor.MissingWidth;
|
||||
}
|
||||
|
||||
public PdfRectangle GetBoundingBox(int characterCode)
|
||||
public PdfRectangle GetBoundingBox(int characterIdentifier)
|
||||
{
|
||||
if (fontProgram == null)
|
||||
{
|
||||
return Descriptor.BoundingBox;
|
||||
}
|
||||
|
||||
if (fontProgram.TryGetBoundingBox(characterCode, out var result))
|
||||
if (fontProgram.TryGetBoundingBox(characterIdentifier, cidToGid.GetGlyphIndex, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
@@ -81,13 +81,17 @@
|
||||
var cid = CMap.ConvertToCid(characterCode);
|
||||
|
||||
var fromFont = CidFont.GetWidthFromDictionary(cid);
|
||||
|
||||
|
||||
var box = GetBoundingBox(characterCode);
|
||||
|
||||
return fromFont;
|
||||
}
|
||||
|
||||
public PdfRectangle GetBoundingBoxInGlyphSpace(int characterCode)
|
||||
{
|
||||
return CidFont.GetBoundingBox(characterCode);
|
||||
var cid = CMap.ConvertToCid(characterCode);
|
||||
|
||||
return CidFont.GetBoundingBox(cid);
|
||||
}
|
||||
|
||||
public TransformationMatrix GetFontMatrix()
|
||||
|
@@ -52,7 +52,7 @@
|
||||
var baseFont = dictionary.GetNameOrDefault(NameToken.BaseFont);
|
||||
|
||||
var systemInfo = GetSystemInfo(dictionary);
|
||||
|
||||
|
||||
var subType = dictionary.GetNameOrDefault(NameToken.Subtype);
|
||||
if (NameToken.CidFontType0.Equals(subType))
|
||||
{
|
||||
@@ -61,12 +61,14 @@
|
||||
|
||||
if (NameToken.CidFontType2.Equals(subType))
|
||||
{
|
||||
return new Type2CidFont(type, subType, baseFont, systemInfo, descriptor, fontProgram, verticalWritingMetrics, widths);
|
||||
var cidToGid = GetCharacterIdentifierToGlyphIndexMap(dictionary, isLenientParsing);
|
||||
|
||||
return new Type2CidFont(type, subType, baseFont, systemInfo, descriptor, fontProgram, verticalWritingMetrics, widths, cidToGid);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private bool TryGetFontDescriptor(DictionaryToken dictionary, out DictionaryToken descriptorDictionary)
|
||||
{
|
||||
descriptorDictionary = null;
|
||||
@@ -152,7 +154,7 @@
|
||||
return widths;
|
||||
}
|
||||
|
||||
private VerticalWritingMetrics ReadVerticalDisplacements(DictionaryToken dict)
|
||||
private static VerticalWritingMetrics ReadVerticalDisplacements(DictionaryToken dict)
|
||||
{
|
||||
var verticalDisplacements = new Dictionary<int, decimal>();
|
||||
var positionVectors = new Dictionary<int, PdfVector>();
|
||||
@@ -237,6 +239,30 @@
|
||||
return new CharacterIdentifierSystemInfo(registry, ordering, supplement);
|
||||
}
|
||||
|
||||
private CharacterIdentifierToGlyphIndexMap GetCharacterIdentifierToGlyphIndexMap(DictionaryToken dictionary, bool isLenientParsing)
|
||||
{
|
||||
if (!dictionary.TryGet(NameToken.CidToGidMap, out var entry))
|
||||
{
|
||||
return new CharacterIdentifierToGlyphIndexMap();
|
||||
}
|
||||
|
||||
if (entry is NameToken name)
|
||||
{
|
||||
if (!name.Equals(NameToken.CidToGidMap) && !isLenientParsing)
|
||||
{
|
||||
throw new InvalidOperationException($"The CIDToGIDMap in a Type 0 font should have the value /Identity, instead got: {name}.");
|
||||
}
|
||||
|
||||
return new CharacterIdentifierToGlyphIndexMap();
|
||||
}
|
||||
|
||||
var stream = DirectObjectFinder.Get<StreamToken>(entry, pdfScanner);
|
||||
|
||||
var bytes = stream.Decode(filterProvider);
|
||||
|
||||
return new CharacterIdentifierToGlyphIndexMap(bytes);
|
||||
}
|
||||
|
||||
private string SafeKeyAccess(DictionaryToken dictionary, NameToken keyName)
|
||||
{
|
||||
if (!dictionary.TryGet(keyName, out var token))
|
||||
|
@@ -0,0 +1,20 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs
|
||||
{
|
||||
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 < 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
|
||||
}
|
||||
}
|
25
src/UglyToad.PdfPig/Fonts/TrueType/Glyphs/EmptyGlyph.cs
Normal file
25
src/UglyToad.PdfPig/Fonts/TrueType/Glyphs/EmptyGlyph.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
23
src/UglyToad.PdfPig/Fonts/TrueType/Glyphs/GlyphPoint.cs
Normal file
23
src/UglyToad.PdfPig/Fonts/TrueType/Glyphs/GlyphPoint.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs
|
||||
{
|
||||
using Geometry;
|
||||
|
||||
internal struct GlyphPoint
|
||||
{
|
||||
public PdfPoint Point { get; }
|
||||
|
||||
public bool IsOnCurve { get; }
|
||||
|
||||
public GlyphPoint(decimal x, decimal y, bool isOnCurve) : this(new PdfPoint(x, y), isOnCurve) { }
|
||||
public GlyphPoint(PdfPoint point, bool isOnCurve)
|
||||
{
|
||||
Point = point;
|
||||
IsOnCurve = isOnCurve;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Point} | {IsOnCurve}";
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType.Tables
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs
|
||||
{
|
||||
using Geometry;
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
|
||||
SimpleGlyphDescription SimpleGlyph { get; }
|
||||
|
||||
object CompositeGlyph { get; }
|
||||
CompositeGlyphDescription CompositeGlyph { get; }
|
||||
|
||||
PdfRectangle GlyphBounds { get; }
|
||||
|
||||
IGlyphDescription DeepClone();
|
||||
}
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Geometry;
|
||||
using Glyphs;
|
||||
using Parser;
|
||||
using Util.JetBrains.Annotations;
|
||||
|
||||
@@ -28,7 +29,6 @@
|
||||
{
|
||||
data.Seek(table.Offset);
|
||||
|
||||
var headerTable = tableRegister.HeaderTable;
|
||||
var indexToLocationTable = tableRegister.IndexToLocationTable;
|
||||
|
||||
var offsets = indexToLocationTable.GlyphOffsets;
|
||||
@@ -39,11 +39,16 @@
|
||||
|
||||
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++)
|
||||
{
|
||||
if (offsets[i] == offsets[i + 1])
|
||||
{
|
||||
// empty glyph
|
||||
glyphs[i] = emptyGlyph;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -55,9 +60,9 @@
|
||||
var minY = data.ReadSignedShort();
|
||||
var maxX = data.ReadSignedShort();
|
||||
var maxY = data.ReadSignedShort();
|
||||
|
||||
|
||||
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 (contourCount >= 0)
|
||||
{
|
||||
@@ -65,10 +70,16 @@
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -78,7 +89,7 @@
|
||||
|
||||
var instructionLength = data.ReadUnsignedShort();
|
||||
|
||||
data.ReadByteArray(instructionLength);
|
||||
var instructions = data.ReadByteArray(instructionLength);
|
||||
|
||||
var pointCount = 0;
|
||||
if (contourCount > 0)
|
||||
@@ -94,7 +105,103 @@
|
||||
var yCoordinates = ReadCoordinates(data, pointCount, flags, SimpleGlyphFlags.YShortVector,
|
||||
SimpleGlyphFlags.YSignOrSame);
|
||||
|
||||
return new SimpleGlyphDescription(instructionLength, endPointsOfContours, flags, xCoordinates, yCoordinates, bounds);
|
||||
var points = new GlyphPoint[xCoordinates.Length];
|
||||
for (var i = xCoordinates.Length - 1; i >= 0; i--)
|
||||
{
|
||||
var isOnCurve = (flags[i] & SimpleGlyphFlags.OnCurve) == SimpleGlyphFlags.OnCurve;
|
||||
points[i] = new GlyphPoint(xCoordinates[i], yCoordinates[i], isOnCurve);
|
||||
}
|
||||
|
||||
return new SimpleGlyphDescription(instructions, endPointsOfContours, points, 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);
|
||||
|
||||
var components = new List<CompositeComponent>();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
decimal xscale = 1;
|
||||
decimal scale01 = 0;
|
||||
decimal scale10 = 0;
|
||||
decimal yscale = 1;
|
||||
|
||||
if (HasFlag(flags, CompositeGlyphFlags.WeHaveAScale))
|
||||
{
|
||||
xscale = ReadTwoFourteenFormat(data);
|
||||
yscale = xscale;
|
||||
}
|
||||
else if (HasFlag(flags, CompositeGlyphFlags.WeHaveAnXAndYScale))
|
||||
{
|
||||
xscale = ReadTwoFourteenFormat(data);
|
||||
yscale = ReadTwoFourteenFormat(data);
|
||||
}
|
||||
else if (HasFlag(flags, CompositeGlyphFlags.WeHaveATwoByTwo))
|
||||
{
|
||||
xscale = ReadTwoFourteenFormat(data);
|
||||
scale01 = ReadTwoFourteenFormat(data);
|
||||
scale10 = ReadTwoFourteenFormat(data);
|
||||
yscale = ReadTwoFourteenFormat(data);
|
||||
}
|
||||
|
||||
if (HasFlag(flags, CompositeGlyphFlags.ArgsAreXAndYValues))
|
||||
{
|
||||
components.Add(new CompositeComponent(glyphIndex, new PdfMatrix3By2(xscale, scale01, scale10, yscale, arg1, arg2)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Not implemented, it is unclear how to do this.
|
||||
}
|
||||
|
||||
} while (HasFlag(flags, CompositeGlyphFlags.MoreComponents));
|
||||
|
||||
return new CompositeGlyphDescription();
|
||||
}
|
||||
|
||||
private static decimal ReadTwoFourteenFormat(TrueTypeDataBytes data)
|
||||
{
|
||||
const decimal divisor = 1 << 14;
|
||||
|
||||
return data.ReadSignedShort() / divisor;
|
||||
}
|
||||
|
||||
private static SimpleGlyphFlags[] ReadFlags(TrueTypeDataBytes data, int pointCount)
|
||||
@@ -140,36 +247,52 @@
|
||||
|
||||
return xs;
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum SimpleGlyphFlags : byte
|
||||
{
|
||||
/// <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>
|
||||
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
|
||||
private struct TemporaryCompositeLocation
|
||||
{
|
||||
/// <summary>
|
||||
/// 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 TemporaryCompositeLocation(long position, PdfRectangle bounds, short contourCount)
|
||||
{
|
||||
Position = position;
|
||||
Bounds = bounds;
|
||||
ContourCount = contourCount;
|
||||
|
||||
if (ContourCount >= 0 )
|
||||
{
|
||||
throw new ArgumentException($"A composite glyph should not have a positive contour count. Got: {contourCount}.", nameof(contourCount));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CompositeComponent
|
||||
{
|
||||
public int Index { get; }
|
||||
|
||||
public PdfMatrix3By2 Transformation { get; }
|
||||
|
||||
public CompositeComponent(int index, PdfMatrix3By2 transformation)
|
||||
{
|
||||
Index = index;
|
||||
Transformation = transformation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType.Tables
|
||||
{
|
||||
using System;
|
||||
using Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// Gives global information about the font.
|
||||
@@ -27,13 +28,7 @@
|
||||
|
||||
public DateTime Modified { get; }
|
||||
|
||||
public short XMin { get; }
|
||||
|
||||
public short YMin { get; }
|
||||
|
||||
public short XMax { get; }
|
||||
|
||||
public short YMax { get; }
|
||||
public PdfRectangle Bounds { get; }
|
||||
|
||||
public HeaderMacStyle MacStyle { get; }
|
||||
|
||||
@@ -74,10 +69,7 @@
|
||||
UnitsPerEm = unitsPerEm;
|
||||
Created = created;
|
||||
Modified = modified;
|
||||
XMin = xMin;
|
||||
YMin = yMin;
|
||||
XMax = xMax;
|
||||
YMax = yMax;
|
||||
Bounds = new PdfRectangle(xMin, yMin, xMax, yMax);
|
||||
MacStyle = (HeaderMacStyle)macStyle;
|
||||
LowestRecommendedPpem = lowestRecommendedPpem;
|
||||
FontDirectionHint = (FontDirection)fontDirectionHint;
|
||||
|
@@ -1,56 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType.Tables
|
||||
{
|
||||
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 int InstructionLength { get; }
|
||||
|
||||
/// <summary>
|
||||
/// An array of the last points of each contour.
|
||||
/// </summary>
|
||||
public int[] EndPointsOfContours { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Array of flags for each coordinate in the outline.
|
||||
/// </summary>
|
||||
public SimpleGlyphFlags[] Flags { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The x-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.
|
||||
/// </summary>
|
||||
public short[] XCoordinates { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public short[] YCoordinates { get; }
|
||||
|
||||
public SimpleGlyphDescription(int instructionLength, int[] endPointsOfContours, SimpleGlyphFlags[] flags, short[] xCoordinates, short[] yCoordinates,
|
||||
PdfRectangle bounds)
|
||||
{
|
||||
InstructionLength = instructionLength;
|
||||
EndPointsOfContours = endPointsOfContours;
|
||||
Flags = flags;
|
||||
XCoordinates = xCoordinates;
|
||||
YCoordinates = yCoordinates;
|
||||
GlyphBounds = bounds;
|
||||
}
|
||||
|
||||
public bool IsSimple { get; } = true;
|
||||
|
||||
public SimpleGlyphDescription SimpleGlyph => this;
|
||||
|
||||
public object CompositeGlyph { get; } = null;
|
||||
}
|
||||
}
|
@@ -33,16 +33,23 @@
|
||||
GlyphTable = tableRegister.GlyphDataTable;
|
||||
}
|
||||
|
||||
public bool TryGetBoundingBox(int characterCode, out PdfRectangle boundingBox)
|
||||
public bool TryGetBoundingBox(int characterCode, out PdfRectangle boundingBox) => TryGetBoundingBox(characterCode, null, out boundingBox);
|
||||
public bool TryGetBoundingBox(int characterCode, Func<int, int> characterIdentifierToGlyphIndex, out PdfRectangle boundingBox)
|
||||
{
|
||||
boundingBox = default(PdfRectangle);
|
||||
|
||||
int index;
|
||||
|
||||
if (CMapTable == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (characterIdentifierToGlyphIndex == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CMapTable.TryGetGlyphIndex(characterCode, out var index))
|
||||
index = characterIdentifierToGlyphIndex(characterCode);
|
||||
}
|
||||
else if (!CMapTable.TryGetGlyphIndex(characterCode, out index))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@@ -148,7 +148,7 @@
|
||||
|
||||
encryptedPortionParser.Parse(eexecPortion);
|
||||
|
||||
return new Type1Font(name, encoding, matrix, boundingBox);
|
||||
return new Type1Font(name, encoding, matrix, boundingBox ?? new PdfRectangle());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -386,7 +386,7 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
private static PdfRectangle GetBoundingBox(IReadOnlyList<DictionaryToken> dictionaries)
|
||||
private static PdfRectangle? GetBoundingBox(IReadOnlyList<DictionaryToken> dictionaries)
|
||||
{
|
||||
foreach (var dictionary in dictionaries)
|
||||
{
|
||||
|
31
src/UglyToad.PdfPig/Geometry/PdfMatrix3By2.cs
Normal file
31
src/UglyToad.PdfPig/Geometry/PdfMatrix3By2.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace UglyToad.PdfPig.Geometry
|
||||
{
|
||||
internal struct PdfMatrix3By2
|
||||
{
|
||||
private readonly decimal r0c0;
|
||||
private readonly decimal r0c1;
|
||||
private readonly decimal r1c0;
|
||||
private readonly decimal r1c1;
|
||||
private readonly decimal r2c0;
|
||||
private readonly decimal r2c1;
|
||||
|
||||
public PdfMatrix3By2(decimal r0C0, decimal r0C1, decimal r1C0, decimal r1C1, decimal r2C0, decimal r2C1)
|
||||
{
|
||||
r0c0 = r0C0;
|
||||
r0c1 = r0C1;
|
||||
r1c0 = r1C0;
|
||||
r1c1 = r1C1;
|
||||
r2c0 = r2C0;
|
||||
r2c1 = r2C1;
|
||||
}
|
||||
|
||||
public static PdfMatrix3By2 Identity { get; } = new PdfMatrix3By2(1, 0, 0, 1, 0, 0);
|
||||
public static PdfMatrix3By2 CreateTranslation(PdfVector vector) => new PdfMatrix3By2(1, 0, 0, 1, vector.X, vector.Y);
|
||||
public static PdfMatrix3By2 CreateTranslation(decimal x, decimal y) => new PdfMatrix3By2(1, 0, 0, 1, x, y);
|
||||
|
||||
public PdfMatrix3By2 WithTranslation(decimal x, decimal y)
|
||||
{
|
||||
return new PdfMatrix3By2(r0c0, r0c1, r1c0, r1c1, x, y);
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,7 +10,7 @@
|
||||
/// 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
|
||||
public struct PdfRectangle
|
||||
{
|
||||
/// <summary>
|
||||
/// Top left point of the rectangle.
|
||||
@@ -47,22 +47,28 @@
|
||||
/// </summary>
|
||||
public decimal Area { get; }
|
||||
|
||||
internal PdfRectangle(PdfVector topLeft, PdfVector topRight, PdfVector bottomLeft, PdfVector bottomRight)
|
||||
{
|
||||
TopLeft = topLeft.ToPoint();
|
||||
TopRight = topRight.ToPoint();
|
||||
/// <summary>
|
||||
/// Left.
|
||||
/// </summary>
|
||||
public decimal Left => TopLeft.X;
|
||||
|
||||
BottomLeft = bottomLeft.ToPoint();
|
||||
BottomRight = bottomRight.ToPoint();
|
||||
/// <summary>
|
||||
/// Top.
|
||||
/// </summary>
|
||||
public decimal Top => TopLeft.Y;
|
||||
|
||||
Width = bottomRight.Subtract(bottomLeft).GetMagnitude();
|
||||
Height = topLeft.Subtract(bottomLeft).GetMagnitude();
|
||||
/// <summary>
|
||||
/// Right.
|
||||
/// </summary>
|
||||
public decimal Right => BottomRight.X;
|
||||
|
||||
Area = Width * Height;
|
||||
}
|
||||
/// <summary>
|
||||
/// Bottom.
|
||||
/// </summary>
|
||||
public decimal Bottom => BottomRight.Y;
|
||||
|
||||
internal PdfRectangle(PdfPoint point1, PdfPoint point2) : this(point1.X, point1.Y, point2.X, point2.Y) { }
|
||||
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);
|
||||
@@ -83,6 +89,20 @@
|
||||
Area = Width * Height;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To string override.
|
||||
/// </summary>
|
||||
|
Reference in New Issue
Block a user