mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-10-15 19:54:52 +08:00
fix problems with parsing the glyph table and other bugs with truetype parsing
This commit is contained in:
@@ -78,6 +78,62 @@
|
|||||||
Assert.Equal(0, font.HeaderTable.GlyphDataFormat);
|
Assert.Equal(0, font.HeaderTable.GlyphDataFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RobotoHeaderReadCorrectly()
|
||||||
|
{
|
||||||
|
var data = new[]
|
||||||
|
{
|
||||||
|
// key, offset, length, checksum
|
||||||
|
"DSIG 158596 8 1",
|
||||||
|
"GDEF 316 72 408950881",
|
||||||
|
"GPOS 388 35744 355098641",
|
||||||
|
"GSUB 36132 662 3357985284",
|
||||||
|
"OS/2 36796 96 3097700805",
|
||||||
|
"cmap 36892 1750 298470964",
|
||||||
|
"cvt 156132 38 119085513",
|
||||||
|
"fpgm 156172 2341 2494100564",
|
||||||
|
"gasp 156124 8 16",
|
||||||
|
"glyf 38644 88820 3302131736",
|
||||||
|
"head 127464 54 346075833",
|
||||||
|
"hhea 127520 36 217516755",
|
||||||
|
"hmtx 127556 4148 1859679943",
|
||||||
|
"kern 131704 12306 2002873469",
|
||||||
|
"loca 144012 2076 77421448",
|
||||||
|
"maxp 146088 32 89459325",
|
||||||
|
"name 146120 830 44343214",
|
||||||
|
"post 146952 9171 3638780613",
|
||||||
|
"prep 158516 77 251381919"
|
||||||
|
};
|
||||||
|
|
||||||
|
var bytes = GetFileBytes("Roboto-Regular");
|
||||||
|
|
||||||
|
var input = new TrueTypeDataBytes(new ByteArrayInputBytes(bytes));
|
||||||
|
|
||||||
|
var font = parser.Parse(input);
|
||||||
|
|
||||||
|
foreach (var s in data)
|
||||||
|
{
|
||||||
|
var parts = s.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
var name = parts[0];
|
||||||
|
|
||||||
|
if (name == "cvt")
|
||||||
|
{
|
||||||
|
name = "cvt ";
|
||||||
|
}
|
||||||
|
|
||||||
|
var match = font.Tables[name];
|
||||||
|
|
||||||
|
var offset = long.Parse(parts[1]);
|
||||||
|
var length = long.Parse(parts[2]);
|
||||||
|
var checksum = long.Parse(parts[3]);
|
||||||
|
|
||||||
|
Assert.Equal(offset, match.Offset);
|
||||||
|
Assert.Equal(length, match.Length);
|
||||||
|
Assert.Equal(checksum, match.CheckSum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ParseEmbeddedSimpleGoogleDocssGautmi()
|
public void ParseEmbeddedSimpleGoogleDocssGautmi()
|
||||||
{
|
{
|
||||||
|
@@ -0,0 +1,27 @@
|
|||||||
|
namespace UglyToad.Pdf.Tests.Fonts.TrueType
|
||||||
|
{
|
||||||
|
using IO;
|
||||||
|
using Pdf.Fonts.TrueType;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
public class TrueTypeDataBytesTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ReadUnsignedInt()
|
||||||
|
{
|
||||||
|
var input = new ByteArrayInputBytes(new byte[]
|
||||||
|
{
|
||||||
|
220,
|
||||||
|
43,
|
||||||
|
250,
|
||||||
|
6
|
||||||
|
});
|
||||||
|
|
||||||
|
var data = new TrueTypeDataBytes(input);
|
||||||
|
|
||||||
|
var result = data.ReadUnsignedInt();
|
||||||
|
|
||||||
|
Assert.Equal(3693869574L, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,16 +1,20 @@
|
|||||||
namespace UglyToad.Pdf.Fonts.TrueType.Parser
|
namespace UglyToad.Pdf.Fonts.TrueType.Parser
|
||||||
{
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
using Tables;
|
using Tables;
|
||||||
|
|
||||||
internal class TrueTypeFont
|
internal class TrueTypeFont
|
||||||
{
|
{
|
||||||
public decimal Version { get; }
|
public decimal Version { get; }
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<string, TrueTypeHeaderTable> Tables { get; }
|
||||||
|
|
||||||
public HeaderTable HeaderTable { get; }
|
public HeaderTable HeaderTable { get; }
|
||||||
|
|
||||||
public TrueTypeFont(decimal version, HeaderTable headerTable)
|
public TrueTypeFont(decimal version, IReadOnlyDictionary<string, TrueTypeHeaderTable> tables, HeaderTable headerTable)
|
||||||
{
|
{
|
||||||
Version = version;
|
Version = version;
|
||||||
|
Tables = tables;
|
||||||
HeaderTable = headerTable;
|
HeaderTable = headerTable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -89,9 +89,16 @@
|
|||||||
|
|
||||||
var indexToLocationTable =
|
var indexToLocationTable =
|
||||||
IndexToLocationTable.Load(data, indexToLocationHeaderTable, header, maximumProfile);
|
IndexToLocationTable.Load(data, indexToLocationHeaderTable, header, maximumProfile);
|
||||||
|
|
||||||
|
if (!tables.TryGetValue(TrueTypeHeaderTable.Glyf, out var glyphHeaderTable))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("The glpyh table is required for non-PostScript fonts.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var glyphTable = GlyphDataTable.Load(data, glyphHeaderTable, header, indexToLocationTable);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new TrueTypeFont(version, header);
|
return new TrueTypeFont(version, tables, header);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
namespace UglyToad.Pdf.Fonts.TrueType.Tables
|
namespace UglyToad.Pdf.Fonts.TrueType.Tables
|
||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Util.JetBrains.Annotations;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Describes the glyphs in the font.
|
/// Describes the glyphs in the font.
|
||||||
@@ -11,6 +13,15 @@
|
|||||||
|
|
||||||
public TrueTypeHeaderTable DirectoryTable { get; }
|
public TrueTypeHeaderTable DirectoryTable { get; }
|
||||||
|
|
||||||
|
[ItemCanBeNull]
|
||||||
|
public IReadOnlyList<IGlyphDescription> Glyphs { get; }
|
||||||
|
|
||||||
|
public GlyphDataTable(TrueTypeHeaderTable directoryTable, IReadOnlyList<IGlyphDescription> glyphs)
|
||||||
|
{
|
||||||
|
DirectoryTable = directoryTable;
|
||||||
|
Glyphs = glyphs ?? throw new ArgumentNullException(nameof(glyphs));
|
||||||
|
}
|
||||||
|
|
||||||
public static GlyphDataTable Load(TrueTypeDataBytes data, TrueTypeHeaderTable table, HeaderTable headerTable,
|
public static GlyphDataTable Load(TrueTypeDataBytes data, TrueTypeHeaderTable table, HeaderTable headerTable,
|
||||||
IndexToLocationTable indexToLocationTable)
|
IndexToLocationTable indexToLocationTable)
|
||||||
{
|
{
|
||||||
@@ -22,7 +33,7 @@
|
|||||||
|
|
||||||
var glyphCount = entryCount - 1;
|
var glyphCount = entryCount - 1;
|
||||||
|
|
||||||
var glyphs = new object[glyphCount];
|
var glyphs = new IGlyphDescription[glyphCount];
|
||||||
|
|
||||||
for (var i = 0; i < glyphCount; i++)
|
for (var i = 0; i < glyphCount; i++)
|
||||||
{
|
{
|
||||||
@@ -32,7 +43,7 @@
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
data.Seek(offsets[i] - 1);
|
data.Seek(offsets[i] - 1 + table.Offset);
|
||||||
|
|
||||||
var contourCount = data.ReadSignedShort();
|
var contourCount = data.ReadSignedShort();
|
||||||
|
|
||||||
@@ -50,20 +61,94 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new NotImplementedException();
|
return new GlyphDataTable(table, glyphs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SimpleGlyphDescription ReadSimpleGlyph(TrueTypeDataBytes data, short contourCount, TrueTypeGlyphBounds bounds)
|
private static SimpleGlyphDescription ReadSimpleGlyph(TrueTypeDataBytes data, short contourCount, TrueTypeGlyphBounds bounds)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("Reading simple glyphs not supported yet.");
|
var endPointsOfContours = data.ReadUnsignedShortArray(contourCount);
|
||||||
|
|
||||||
|
var instructionLength = data.ReadUnsignedShort();
|
||||||
|
|
||||||
|
data.ReadByteArray(instructionLength);
|
||||||
|
|
||||||
|
var pointCount = 0;
|
||||||
|
if (contourCount > 0)
|
||||||
|
{
|
||||||
|
pointCount = endPointsOfContours[contourCount - 1] + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var flags = ReadFlags(data, pointCount);
|
||||||
|
|
||||||
|
var xCoordinates = ReadCoordinates(data, pointCount, flags, SimpleGlyphFlags.XShortVector,
|
||||||
|
SimpleGlyphFlags.XSignOrSame);
|
||||||
|
|
||||||
|
var yCoordinates = ReadCoordinates(data, pointCount, flags, SimpleGlyphFlags.YShortVector,
|
||||||
|
SimpleGlyphFlags.YSignOrSame);
|
||||||
|
|
||||||
|
return new SimpleGlyphDescription(instructionLength, endPointsOfContours, flags, xCoordinates, yCoordinates);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SimpleGlyphFlags[] ReadFlags(TrueTypeDataBytes data, int pointCount)
|
||||||
|
{
|
||||||
|
var result = new SimpleGlyphFlags[pointCount];
|
||||||
|
|
||||||
|
for (var i = 0; i < pointCount; i++)
|
||||||
|
{
|
||||||
|
result[i] = (SimpleGlyphFlags)data.ReadByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static short[] ReadCoordinates(TrueTypeDataBytes data, int pointCount, SimpleGlyphFlags[] flags, SimpleGlyphFlags isByte, SimpleGlyphFlags signOrSame)
|
||||||
|
{
|
||||||
|
var xs = new short[pointCount];
|
||||||
|
var x = 0;
|
||||||
|
for (var i = 0; i < pointCount; i++)
|
||||||
|
{
|
||||||
|
int dx;
|
||||||
|
if (flags[i].HasFlag(isByte))
|
||||||
|
{
|
||||||
|
var b = data.ReadByte();
|
||||||
|
dx = flags[i].HasFlag(signOrSame) ? b : -b;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (flags[i].HasFlag(signOrSame))
|
||||||
|
{
|
||||||
|
dx = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dx = data.ReadSignedShort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
x += dx;
|
||||||
|
|
||||||
|
// TODO: overflow?
|
||||||
|
xs[i] = (short)x;
|
||||||
|
}
|
||||||
|
|
||||||
|
return xs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class SimpleGlyphDescription
|
internal interface IGlyphDescription
|
||||||
|
{
|
||||||
|
bool IsSimple { get; }
|
||||||
|
|
||||||
|
SimpleGlyphDescription SimpleGlyph { get; }
|
||||||
|
|
||||||
|
object CompositeGlyph { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class SimpleGlyphDescription : IGlyphDescription
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The total number of bytes for instructions.
|
/// The total number of bytes for instructions.
|
||||||
@@ -100,6 +185,12 @@
|
|||||||
XCoordinates = xCoordinates;
|
XCoordinates = xCoordinates;
|
||||||
YCoordinates = yCoordinates;
|
YCoordinates = yCoordinates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsSimple { get; } = true;
|
||||||
|
|
||||||
|
public SimpleGlyphDescription SimpleGlyph => this;
|
||||||
|
|
||||||
|
public object CompositeGlyph { get; } = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
|
@@ -33,27 +33,31 @@
|
|||||||
|
|
||||||
var format = headerTable.IndexToLocFormat;
|
var format = headerTable.IndexToLocFormat;
|
||||||
|
|
||||||
var glyphCount = maximumProfileTable.NumberOfGlyphs;
|
var glyphCount = maximumProfileTable.NumberOfGlyphs + 1;
|
||||||
|
|
||||||
var offsets = new long[glyphCount];
|
var offsets = new long[glyphCount];
|
||||||
|
|
||||||
for (int i = 0; i < glyphCount; i++)
|
switch (format)
|
||||||
{
|
{
|
||||||
switch (format)
|
case shortFormat:
|
||||||
{
|
{ // The local offset divided by 2 is stored.
|
||||||
case shortFormat:
|
for (int i = 0; i < glyphCount; i++)
|
||||||
// The local offset divided by 2 is stored.
|
{
|
||||||
offsets[i] = data.ReadUnsignedShort() * 2;
|
offsets[i] = data.ReadUnsignedShort() * 2;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case longFormat:
|
}
|
||||||
|
case longFormat:
|
||||||
|
{
|
||||||
// The actual offset is stored.
|
// The actual offset is stored.
|
||||||
offsets[i] = data.ReadLong();
|
data.ReadUnsignedIntArray(offsets, glyphCount);
|
||||||
break;
|
break;
|
||||||
default:
|
}
|
||||||
throw new InvalidOperationException($"The format {format} was invalid for the index to location (loca) table.");
|
default:
|
||||||
}
|
throw new InvalidOperationException($"The format {format} was invalid for the index to location (loca) table.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return new IndexToLocationTable(table, offsets);
|
return new IndexToLocationTable(table, offsets);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -59,6 +59,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte ReadByte()
|
||||||
|
{
|
||||||
|
ReadBuffered(internalBuffer, 1);
|
||||||
|
|
||||||
|
return internalBuffer[0];
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads the 4 character tag from the TrueType file.
|
/// Reads the 4 character tag from the TrueType file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -78,7 +85,7 @@
|
|||||||
{
|
{
|
||||||
ReadBuffered(internalBuffer, 4);
|
ReadBuffered(internalBuffer, 4);
|
||||||
|
|
||||||
return (internalBuffer[0] << 24) + (internalBuffer[1] << 16) + (internalBuffer[2] << 8) + (internalBuffer[3] << 0);
|
return ((long)internalBuffer[0] << 24) + ((long)internalBuffer[1] << 16) + (internalBuffer[2] << 8) + (internalBuffer[3] << 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int ReadSignedInt()
|
public int ReadSignedInt()
|
||||||
@@ -130,8 +137,39 @@
|
|||||||
public int ReadSignedByte()
|
public int ReadSignedByte()
|
||||||
{
|
{
|
||||||
ReadBuffered(internalBuffer, 1);
|
ReadBuffered(internalBuffer, 1);
|
||||||
|
|
||||||
var signedByte = internalBuffer[0];
|
var signedByte = internalBuffer[0];
|
||||||
|
|
||||||
return signedByte < 127 ? signedByte : signedByte - 256;
|
return signedByte < 127 ? signedByte : signedByte - 256;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int[] ReadUnsignedShortArray(int length)
|
||||||
|
{
|
||||||
|
var result = new int[length];
|
||||||
|
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
result[i] = ReadUnsignedShort();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] ReadByteArray(int length)
|
||||||
|
{
|
||||||
|
var result = new byte[length];
|
||||||
|
|
||||||
|
ReadBuffered(result, length);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReadUnsignedIntArray(long[] offsets, int length)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
offsets[i] = ReadUnsignedInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -185,5 +185,10 @@
|
|||||||
Offset = offset;
|
Offset = offset;
|
||||||
Length = length;
|
Length = length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{Tag} {Offset} {Length} {CheckSum}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -46,7 +46,7 @@
|
|||||||
public void Seek(long position)
|
public void Seek(long position)
|
||||||
{
|
{
|
||||||
CurrentOffset = (int)position;
|
CurrentOffset = (int)position;
|
||||||
CurrentByte = bytes[CurrentOffset];
|
CurrentByte = CurrentOffset < 0 ? (byte)0 : bytes[CurrentOffset];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user