fix problems with parsing the glyph table and other bugs with truetype parsing

This commit is contained in:
Eliot Jones
2017-12-17 09:51:37 +00:00
parent 7bde49d019
commit e5aa752248
9 changed files with 255 additions and 23 deletions

View File

@@ -78,6 +78,62 @@
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]
public void ParseEmbeddedSimpleGoogleDocssGautmi()
{

View File

@@ -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);
}
}
}

View File

@@ -1,16 +1,20 @@
namespace UglyToad.Pdf.Fonts.TrueType.Parser
{
using System.Collections.Generic;
using Tables;
internal class TrueTypeFont
{
public decimal Version { get; }
public IReadOnlyDictionary<string, TrueTypeHeaderTable> Tables { get; }
public HeaderTable HeaderTable { get; }
public TrueTypeFont(decimal version, HeaderTable headerTable)
public TrueTypeFont(decimal version, IReadOnlyDictionary<string, TrueTypeHeaderTable> tables, HeaderTable headerTable)
{
Version = version;
Tables = tables;
HeaderTable = headerTable;
}
}

View File

@@ -89,9 +89,16 @@
var indexToLocationTable =
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);
}
}
}

View File

@@ -1,6 +1,8 @@
namespace UglyToad.Pdf.Fonts.TrueType.Tables
{
using System;
using System.Collections.Generic;
using Util.JetBrains.Annotations;
/// <summary>
/// Describes the glyphs in the font.
@@ -11,6 +13,15 @@
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,
IndexToLocationTable indexToLocationTable)
{
@@ -22,7 +33,7 @@
var glyphCount = entryCount - 1;
var glyphs = new object[glyphCount];
var glyphs = new IGlyphDescription[glyphCount];
for (var i = 0; i < glyphCount; i++)
{
@@ -32,7 +43,7 @@
continue;
}
data.Seek(offsets[i] - 1);
data.Seek(offsets[i] - 1 + table.Offset);
var contourCount = data.ReadSignedShort();
@@ -50,20 +61,94 @@
}
else
{
}
}
throw new NotImplementedException();
return new GlyphDataTable(table, glyphs);
}
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>
/// The total number of bytes for instructions.
@@ -100,6 +185,12 @@
XCoordinates = xCoordinates;
YCoordinates = yCoordinates;
}
public bool IsSimple { get; } = true;
public SimpleGlyphDescription SimpleGlyph => this;
public object CompositeGlyph { get; } = null;
}
[Flags]

View File

@@ -33,27 +33,31 @@
var format = headerTable.IndexToLocFormat;
var glyphCount = maximumProfileTable.NumberOfGlyphs;
var glyphCount = maximumProfileTable.NumberOfGlyphs + 1;
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.
offsets[i] = data.ReadUnsignedShort() * 2;
case shortFormat:
{ // The local offset divided by 2 is stored.
for (int i = 0; i < glyphCount; i++)
{
offsets[i] = data.ReadUnsignedShort() * 2;
}
break;
case longFormat:
}
case longFormat:
{
// The actual offset is stored.
offsets[i] = data.ReadLong();
data.ReadUnsignedIntArray(offsets, glyphCount);
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);
}
}

View File

@@ -59,6 +59,13 @@
}
}
public byte ReadByte()
{
ReadBuffered(internalBuffer, 1);
return internalBuffer[0];
}
/// <summary>
/// Reads the 4 character tag from the TrueType file.
/// </summary>
@@ -78,7 +85,7 @@
{
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()
@@ -130,8 +137,39 @@
public int ReadSignedByte()
{
ReadBuffered(internalBuffer, 1);
var signedByte = internalBuffer[0];
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();
}
}
}
}

View File

@@ -185,5 +185,10 @@
Offset = offset;
Length = length;
}
public override string ToString()
{
return $"{Tag} {Offset} {Length} {CheckSum}";
}
}
}

View File

@@ -46,7 +46,7 @@
public void Seek(long position)
{
CurrentOffset = (int)position;
CurrentByte = bytes[CurrentOffset];
CurrentByte = CurrentOffset < 0 ? (byte)0 : bytes[CurrentOffset];
}
}
}