mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-09-19 10:47:56 +08:00
use correct numeric types when parsing truetype fonts
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
namespace UglyToad.PdfPig.Tests.Fonts.TrueType.Parser
|
||||
// ReSharper disable CompareOfFloatsByEqualityOperator
|
||||
namespace UglyToad.PdfPig.Tests.Fonts.TrueType.Parser
|
||||
{
|
||||
using System;
|
||||
using System.Globalization;
|
||||
@@ -159,7 +160,7 @@
|
||||
|
||||
Assert.Equal("Andada Regular", name);
|
||||
|
||||
Assert.Equal(1.001999m, font.TableRegister.HeaderTable.Revision);
|
||||
Assert.Equal(1.001999, font.TableRegister.HeaderTable.Revision, new DoubleComparer(5));
|
||||
|
||||
Assert.Equal(11, font.TableRegister.HeaderTable.Flags);
|
||||
|
||||
@@ -182,6 +183,8 @@
|
||||
var input = new TrueTypeDataBytes(new ByteArrayInputBytes(bytes));
|
||||
|
||||
var font = parser.Parse(input);
|
||||
|
||||
Assert.NotNull(font);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@@ -0,0 +1,55 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType.Parser
|
||||
{
|
||||
using System;
|
||||
using Tables;
|
||||
|
||||
internal class HorizontalHeaderTableParser : ITrueTypeTableParser<HorizontalHeaderTable>
|
||||
{
|
||||
public HorizontalHeaderTable Parse(TrueTypeHeaderTable header, TrueTypeDataBytes data, TableRegister.Builder register)
|
||||
{
|
||||
data.Seek(header.Offset);
|
||||
var majorVersion = data.ReadUnsignedShort();
|
||||
var minorVersion = data.ReadUnsignedShort();
|
||||
|
||||
var ascender = data.ReadSignedShort();
|
||||
var descender = data.ReadSignedShort();
|
||||
var lineGap = data.ReadSignedShort();
|
||||
|
||||
var advancedWidthMax = data.ReadUnsignedShort();
|
||||
|
||||
var minLeftSideBearing = data.ReadSignedShort();
|
||||
var minRightSideBearing = data.ReadSignedShort();
|
||||
var xMaxExtent = data.ReadSignedShort();
|
||||
|
||||
var caretSlopeRise = data.ReadSignedShort();
|
||||
var caretSlopeRun = data.ReadSignedShort();
|
||||
var caretOffset = data.ReadSignedShort();
|
||||
|
||||
// Reserved section
|
||||
data.ReadSignedShort();
|
||||
data.ReadSignedShort();
|
||||
data.ReadSignedShort();
|
||||
data.ReadSignedShort();
|
||||
|
||||
var metricDataFormat = data.ReadSignedShort();
|
||||
|
||||
if (metricDataFormat != 0)
|
||||
{
|
||||
throw new NotSupportedException("The metric data format for a horizontal header table should be 0.");
|
||||
}
|
||||
|
||||
var numberOfHeaderMetrics = data.ReadUnsignedShort();
|
||||
|
||||
return new HorizontalHeaderTable(header, majorVersion, minorVersion, ascender,
|
||||
descender, lineGap, advancedWidthMax,
|
||||
minLeftSideBearing,
|
||||
minRightSideBearing,
|
||||
xMaxExtent,
|
||||
caretSlopeRise,
|
||||
caretSlopeRun,
|
||||
caretOffset,
|
||||
metricDataFormat,
|
||||
numberOfHeaderMetrics);
|
||||
}
|
||||
}
|
||||
}
|
@@ -12,33 +12,36 @@
|
||||
data.Seek(header.Offset);
|
||||
var bytesRead = 0;
|
||||
|
||||
// The number of entries in the left side bearing field per entry is number of glyphs - number of metrics
|
||||
var additionalLeftSideBearingLength = glyphCount - metricCount;
|
||||
|
||||
var advancedWidths = new int[metricCount];
|
||||
var horizontalMetrics = new HorizontalMetricsTable.HorizontalMetric[metricCount];
|
||||
|
||||
// For bearings over the metric count, the width is the same as the last width in advanced widths.
|
||||
var leftSideBearings = new short[glyphCount];
|
||||
|
||||
for (var i = 0; i < metricCount; i++)
|
||||
{
|
||||
advancedWidths[i] = data.ReadUnsignedShort();
|
||||
leftSideBearings[i] = data.ReadSignedShort();
|
||||
var width = data.ReadUnsignedShort();
|
||||
var lsb = data.ReadSignedShort();
|
||||
|
||||
horizontalMetrics[i] = new HorizontalMetricsTable.HorizontalMetric(width, lsb);
|
||||
|
||||
bytesRead += 4;
|
||||
}
|
||||
|
||||
for (var i = 0; i < additionalLeftSideBearingLength; i++)
|
||||
// The number of entries in the left side bearing field per entry is number of glyphs - number of metrics
|
||||
// For bearings over the metric count, the width is the same as the last width in advanced widths.
|
||||
var additionalLeftSideBearings = new short[glyphCount - metricCount];
|
||||
|
||||
for (var i = 0; i < additionalLeftSideBearings.Length; i++)
|
||||
{
|
||||
if (bytesRead >= header.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
leftSideBearings[metricCount + i] = data.ReadSignedShort();
|
||||
additionalLeftSideBearings[i] = data.ReadSignedShort();
|
||||
bytesRead += 2;
|
||||
}
|
||||
|
||||
return new HorizontalMetricsTable(header, advancedWidths, leftSideBearings, metricCount);
|
||||
return new HorizontalMetricsTable(header, horizontalMetrics, additionalLeftSideBearings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,63 +1,11 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType.Parser
|
||||
{
|
||||
using System;
|
||||
using System.Text;
|
||||
using Names;
|
||||
using Tables;
|
||||
using Util;
|
||||
using Util.JetBrains.Annotations;
|
||||
|
||||
internal class HorizontalHeaderTableParser : ITrueTypeTableParser<HorizontalHeaderTable>
|
||||
{
|
||||
public HorizontalHeaderTable Parse(TrueTypeHeaderTable header, TrueTypeDataBytes data, TableRegister.Builder register)
|
||||
{
|
||||
data.Seek(header.Offset);
|
||||
var majorVersion = data.ReadUnsignedShort();
|
||||
var minorVersion = data.ReadUnsignedShort();
|
||||
|
||||
var ascender = data.ReadSignedShort();
|
||||
var descender = data.ReadSignedShort();
|
||||
var lineGap = data.ReadSignedShort();
|
||||
|
||||
var advancedWidthMax = data.ReadUnsignedShort();
|
||||
|
||||
var minLeftSideBearing = data.ReadSignedShort();
|
||||
var minRightSideBearing = data.ReadSignedShort();
|
||||
var xMaxExtent = data.ReadSignedShort();
|
||||
|
||||
var caretSlopeRise = data.ReadSignedShort();
|
||||
var caretSlopeRun = data.ReadSignedShort();
|
||||
var caretOffset = data.ReadSignedShort();
|
||||
|
||||
// Reserved section
|
||||
data.ReadSignedShort();
|
||||
data.ReadSignedShort();
|
||||
data.ReadSignedShort();
|
||||
data.ReadSignedShort();
|
||||
|
||||
var metricDataFormat = data.ReadSignedShort();
|
||||
|
||||
if (metricDataFormat != 0)
|
||||
{
|
||||
throw new NotSupportedException("The metric data format for a horizontal header table should be 0.");
|
||||
}
|
||||
|
||||
var numberOfHeaderMetrics = data.ReadUnsignedShort();
|
||||
|
||||
return new HorizontalHeaderTable(header, majorVersion, minorVersion, ascender,
|
||||
descender, lineGap, advancedWidthMax,
|
||||
minLeftSideBearing,
|
||||
minRightSideBearing,
|
||||
xMaxExtent,
|
||||
caretSlopeRise,
|
||||
caretSlopeRun,
|
||||
caretOffset,
|
||||
metricDataFormat,
|
||||
numberOfHeaderMetrics);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
internal class NameTableParser : ITrueTypeTableParser<NameTable>
|
||||
{
|
||||
public NameTable Parse(TrueTypeHeaderTable header, TrueTypeDataBytes data, TableRegister.Builder register)
|
||||
|
@@ -10,14 +10,14 @@
|
||||
{
|
||||
public TrueTypeFontProgram Parse(TrueTypeDataBytes data)
|
||||
{
|
||||
var version = (decimal)data.Read32Fixed();
|
||||
var version = data.Read32Fixed();
|
||||
int numberOfTables = data.ReadUnsignedShort();
|
||||
|
||||
// Read these data points to move to the correct data location.
|
||||
// ReSharper disable UnusedVariable
|
||||
int searchRange = data.ReadUnsignedShort();
|
||||
int entrySelector = data.ReadUnsignedShort();
|
||||
int rangeShift = data.ReadUnsignedShort();
|
||||
var searchRange = data.ReadUnsignedShort();
|
||||
var entrySelector = data.ReadUnsignedShort();
|
||||
var rangeShift = data.ReadUnsignedShort();
|
||||
// ReSharper restore UnusedVariable
|
||||
|
||||
var tables = new Dictionary<string, TrueTypeHeaderTable>(StringComparer.OrdinalIgnoreCase);
|
||||
@@ -54,7 +54,7 @@
|
||||
return new TrueTypeHeaderTable(tag, checksum, offset, length);
|
||||
}
|
||||
|
||||
private static TrueTypeFontProgram ParseTables(decimal version, IReadOnlyDictionary<string, TrueTypeHeaderTable> tables, TrueTypeDataBytes data)
|
||||
private static TrueTypeFontProgram ParseTables(float version, IReadOnlyDictionary<string, TrueTypeHeaderTable> tables, TrueTypeDataBytes data)
|
||||
{
|
||||
var isPostScript = tables.ContainsKey(TrueTypeHeaderTable.Cff);
|
||||
|
||||
|
@@ -14,17 +14,17 @@
|
||||
|
||||
public TrueTypeHeaderTable DirectoryTable { get; }
|
||||
|
||||
public decimal Version { get; }
|
||||
public float Version { get; }
|
||||
|
||||
public decimal Revision { get; }
|
||||
public float Revision { get; }
|
||||
|
||||
public long CheckSumAdjustment { get; }
|
||||
|
||||
public long MagicNumber { get; }
|
||||
|
||||
public int Flags { get; }
|
||||
public ushort Flags { get; }
|
||||
|
||||
public int UnitsPerEm { get; }
|
||||
public ushort UnitsPerEm { get; }
|
||||
|
||||
public DateTime Created { get; }
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
/// <summary>
|
||||
/// Smallest readable size in pixels.
|
||||
/// </summary>
|
||||
public int LowestRecommendedPpem { get; }
|
||||
public ushort LowestRecommendedPpem { get; }
|
||||
|
||||
public FontDirection FontDirectionHint { get; }
|
||||
|
||||
@@ -51,13 +51,13 @@
|
||||
/// </summary>
|
||||
public short GlyphDataFormat { get; }
|
||||
|
||||
public HeaderTable(TrueTypeHeaderTable directoryTable, decimal version, decimal revision, long checkSumAdjustment,
|
||||
long magicNumber, int flags, int unitsPerEm,
|
||||
public HeaderTable(TrueTypeHeaderTable directoryTable, float version, float revision, long checkSumAdjustment,
|
||||
long magicNumber, ushort flags, ushort unitsPerEm,
|
||||
DateTime created, DateTime modified,
|
||||
short xMin, short yMin,
|
||||
short xMax, short yMax,
|
||||
int macStyle,
|
||||
int lowestRecommendedPpem,
|
||||
ushort macStyle,
|
||||
ushort lowestRecommendedPpem,
|
||||
short fontDirectionHint,
|
||||
short indexToLocFormat,
|
||||
short glyphDataFormat)
|
||||
@@ -130,7 +130,7 @@
|
||||
var indexToLocFormat = data.ReadSignedShort();
|
||||
var glyphDataFormat = data.ReadSignedShort();
|
||||
|
||||
return new HeaderTable(table, (decimal)version, (decimal)fontRevision, checkSumAdjustment,
|
||||
return new HeaderTable(table, version, fontRevision, checkSumAdjustment,
|
||||
magicNumber, flags, unitsPerEm, created, modified,
|
||||
xMin, yMin, xMax, yMax, macStyle, lowestRecPpem,
|
||||
fontDirectionHint, indexToLocFormat, glyphDataFormat);
|
||||
|
@@ -1,7 +1,5 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType.Tables
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// The 'hhea' table contains information needed to layout fonts whose characters are written horizontally, that is, either left to right or right to left.
|
||||
/// This table contains information that is general to the font as a whole.
|
||||
@@ -25,12 +23,12 @@
|
||||
/// <summary>
|
||||
/// Distance from baseline to highest ascender.
|
||||
/// </summary>
|
||||
public short Ascender { get; }
|
||||
public short Ascent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Distance from baseline to lower descender.
|
||||
/// </summary>
|
||||
public short Descender { get; }
|
||||
public short Descent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The typographic line gap.
|
||||
@@ -40,7 +38,7 @@
|
||||
/// <summary>
|
||||
/// The maximum advance width value as given by the Horizontal Metrics table.
|
||||
/// </summary>
|
||||
public int AdvanceWidthMaximum { get; }
|
||||
public ushort AdvanceWidthMaximum { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The minimum left side bearing as given by the Horizontal Metrics table.
|
||||
@@ -80,15 +78,20 @@
|
||||
/// <summary>
|
||||
/// Number of horizontal metrics in the Horizontal Metrics table.
|
||||
/// </summary>
|
||||
public int NumberOfHeaderMetrics { get; }
|
||||
public ushort NumberOfHeaderMetrics { get; }
|
||||
|
||||
public HorizontalHeaderTable(TrueTypeHeaderTable directoryTable, int majorVersion, int minorVersion, short ascender, short descender, short lineGap, int advanceWidthMaximum, short minimumLeftSideBearing, short minimumRightSideBearing, short xMaxExtent, short caretSlopeRise, short caretSlopeRun, short caretOffset, short metricDataFormat, int numberOfHeaderMetrics)
|
||||
public HorizontalHeaderTable(TrueTypeHeaderTable directoryTable, int majorVersion, int minorVersion, short ascent, short descent,
|
||||
short lineGap, ushort advanceWidthMaximum,
|
||||
short minimumLeftSideBearing, short minimumRightSideBearing,
|
||||
short xMaxExtent, short caretSlopeRise,
|
||||
short caretSlopeRun, short caretOffset,
|
||||
short metricDataFormat, ushort numberOfHeaderMetrics)
|
||||
{
|
||||
DirectoryTable = directoryTable;
|
||||
MajorVersion = majorVersion;
|
||||
MinorVersion = minorVersion;
|
||||
Ascender = ascender;
|
||||
Descender = descender;
|
||||
Ascent = ascent;
|
||||
Descent = descent;
|
||||
LineGap = lineGap;
|
||||
AdvanceWidthMaximum = advanceWidthMaximum;
|
||||
MinimumLeftSideBearing = minimumLeftSideBearing;
|
||||
|
@@ -1,42 +1,66 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType.Tables
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using Parser;
|
||||
|
||||
/// <summary>
|
||||
/// The 'hmtx' table contains metric information for the horizontal layout each of the glyphs in the font.
|
||||
/// </summary>
|
||||
internal class HorizontalMetricsTable : ITable
|
||||
{
|
||||
private readonly short[] leftSideBearings;
|
||||
|
||||
private readonly int metricCount;
|
||||
|
||||
public string Tag => TrueTypeHeaderTable.Hmtx;
|
||||
|
||||
public TrueTypeHeaderTable DirectoryTable { get; }
|
||||
|
||||
public IReadOnlyList<int> AdvancedWidths { get; }
|
||||
public IReadOnlyList<HorizontalMetric> HorizontalMetrics { get; }
|
||||
|
||||
public HorizontalMetricsTable(TrueTypeHeaderTable directoryTable, int[] advancedWidths, short[] leftSideBearings, int metricCount)
|
||||
/// <summary>
|
||||
/// Some fonts may have an array of left side bearings following the <see cref="HorizontalMetrics"/>.
|
||||
/// Generally, this array of left side bearings is used for a run of monospaced glyphs.
|
||||
/// For example, it might be used for a Kanji font or for Courier.
|
||||
/// The corresponding glyphs are assumed to have the same advance width as that found in the last entry in the <see cref="HorizontalMetrics"/>.
|
||||
/// </summary>
|
||||
public IReadOnlyList<short> AdditionalLeftSideBearings { get; }
|
||||
|
||||
public HorizontalMetricsTable(TrueTypeHeaderTable directoryTable,
|
||||
IReadOnlyList<HorizontalMetric> horizontalMetrics,
|
||||
IReadOnlyList<short> additionalLeftSideBearings)
|
||||
{
|
||||
AdvancedWidths = advancedWidths;
|
||||
this.leftSideBearings = leftSideBearings;
|
||||
this.metricCount = metricCount;
|
||||
|
||||
DirectoryTable = directoryTable;
|
||||
HorizontalMetrics = horizontalMetrics;
|
||||
AdditionalLeftSideBearings = additionalLeftSideBearings;
|
||||
}
|
||||
|
||||
public int GetAdvanceWidth(int index)
|
||||
public ushort GetAdvanceWidth(int index)
|
||||
{
|
||||
if (index < metricCount)
|
||||
if (index < HorizontalMetrics.Count)
|
||||
{
|
||||
return AdvancedWidths[index];
|
||||
return HorizontalMetrics[index].AdvanceWidth;
|
||||
}
|
||||
|
||||
// monospaced fonts may not have a width for every glyph
|
||||
// the last one is for subsequent glyphs
|
||||
return AdvancedWidths[AdvancedWidths.Count - 1];
|
||||
// Monospaced fonts may not have a width for every glyph, the last metric is for subsequent glyphs.
|
||||
return HorizontalMetrics[HorizontalMetrics.Count - 1].AdvanceWidth;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The pair of horizontal metrics for an individual glyph.
|
||||
/// </summary>
|
||||
public struct HorizontalMetric
|
||||
{
|
||||
/// <summary>
|
||||
/// The advance width.
|
||||
/// </summary>
|
||||
public ushort AdvanceWidth { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The left side bearing.
|
||||
/// </summary>
|
||||
public short LeftSideBearing { get; }
|
||||
|
||||
internal HorizontalMetric(ushort advanceWidth, short leftSideBearing)
|
||||
{
|
||||
AdvanceWidth = advanceWidth;
|
||||
LeftSideBearing = leftSideBearing;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -179,7 +179,7 @@
|
||||
|
||||
for (var i = 0; i < namesLength; i++)
|
||||
{
|
||||
var numberOfCharacters = data.ReadUnsignedByte();
|
||||
var numberOfCharacters = data.ReadByte();
|
||||
nameArray[i] = data.ReadString(numberOfCharacters, Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
|
@@ -39,14 +39,6 @@
|
||||
return (ushort)((internalBuffer[0] << 8) + (internalBuffer[1] << 0));
|
||||
}
|
||||
|
||||
public int ReadUnsignedByte()
|
||||
{
|
||||
ReadBuffered(internalBuffer, 1);
|
||||
|
||||
// TODO: the cast from int -> byte -> int here suggest we are treating data incorrectly.
|
||||
return internalBuffer[0];
|
||||
}
|
||||
|
||||
private void ReadBuffered(byte[] buffer, int length)
|
||||
{
|
||||
var numberRead = 0;
|
||||
|
@@ -10,7 +10,7 @@
|
||||
|
||||
internal class TrueTypeFontProgram : ICidFontProgram
|
||||
{
|
||||
public decimal Version { get; }
|
||||
public float Version { get; }
|
||||
|
||||
public IReadOnlyDictionary<string, TrueTypeHeaderTable> TableHeaders { get; }
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
public ICMapSubTable WindowsSymbolCMap { get; }
|
||||
|
||||
public TrueTypeFontProgram(decimal version, IReadOnlyDictionary<string, TrueTypeHeaderTable> tableHeaders, TableRegister tableRegister)
|
||||
public TrueTypeFontProgram(float version, IReadOnlyDictionary<string, TrueTypeHeaderTable> tableHeaders, TableRegister tableRegister)
|
||||
{
|
||||
Version = version;
|
||||
TableHeaders = tableHeaders;
|
||||
|
@@ -76,8 +76,8 @@
|
||||
{ NameToken.Flags, new NumericToken((int)FontDescriptorFlags.Symbolic) },
|
||||
{ NameToken.FontBbox, GetBoundingBox(bbox, scaling) },
|
||||
{ NameToken.ItalicAngle, new NumericToken(postscript.ItalicAngle) },
|
||||
{ NameToken.Ascent, new NumericToken(hhead.Ascender * scaling) },
|
||||
{ NameToken.Descent, new NumericToken(hhead.Descender * scaling) },
|
||||
{ NameToken.Ascent, new NumericToken(hhead.Ascent * scaling) },
|
||||
{ NameToken.Descent, new NumericToken(hhead.Descent * scaling) },
|
||||
{ NameToken.CapHeight, new NumericToken(90) },
|
||||
{ NameToken.StemV, new NumericToken(90) },
|
||||
{ NameToken.FontFile2, new IndirectReferenceToken(fileRef.Number) }
|
||||
@@ -181,7 +181,7 @@
|
||||
|
||||
if (!font.TryGetBoundingAdvancedWidth(characterCode, out var width))
|
||||
{
|
||||
width = font.TableRegister.HorizontalMetricsTable.AdvancedWidths[0];
|
||||
width = font.TableRegister.HorizontalMetricsTable.HorizontalMetrics[0].AdvanceWidth;
|
||||
}
|
||||
|
||||
widths[pair.Key - firstCharacter] = new NumericToken((decimal)width * scaling);
|
||||
|
Reference in New Issue
Block a user