merge from upstream

This commit is contained in:
modest-as
2018-04-11 23:17:37 +01:00
19 changed files with 455 additions and 31 deletions

View File

@@ -122,7 +122,7 @@
name = "cvt ";
}
var match = font.Tables[name];
var match = font.TableHeaders[name];
var offset = long.Parse(parts[1]);
var length = long.Parse(parts[2]);

View File

@@ -85,6 +85,11 @@
return new PdfRectangle(0, 0, fromFont, 0);
}
public PdfRectangle GetBoundingBox(int characterCode)
{
throw new NotImplementedException();
}
public TransformationMatrix GetFontMatrix()
{
return CidFont.FontMatrix;

View File

@@ -17,6 +17,8 @@
PdfRectangle GetDisplacement(int characterCode);
PdfRectangle GetBoundingBox(int characterCode);
TransformationMatrix GetFontMatrix();
}
}

View File

@@ -70,7 +70,7 @@
Encoding encoding = encodingReader.Read(dictionary, isLenientParsing, descriptor);
return new TrueTypeSimpleFont(name, firstCharacter, lastCharacter, widths, descriptor, toUnicodeCMap, encoding);
return new TrueTypeSimpleFont(name, firstCharacter, lastCharacter, widths, descriptor, toUnicodeCMap, encoding, font);
}
private TrueTypeFont ParseTrueTypeFont(FontDescriptor descriptor)

View File

@@ -7,6 +7,7 @@
using Geometry;
using IO;
using Tokenization.Tokens;
using TrueType;
using Util.JetBrains.Annotations;
internal class TrueTypeSimpleFont : IFont
@@ -21,6 +22,8 @@
[CanBeNull]
private readonly Encoding encoding;
[CanBeNull]
private readonly TrueTypeFont font;
public NameToken Name { get; }
@@ -33,14 +36,16 @@
public TrueTypeSimpleFont(NameToken name, int firstCharacterCode, int lastCharacterCode, decimal[] widths,
FontDescriptor descriptor,
[CanBeNull]CMap toUnicodeCMap,
[CanBeNull]Encoding encoding)
[CanBeNull] CMap toUnicodeCMap,
[CanBeNull] Encoding encoding,
[CanBeNull]TrueTypeFont font)
{
this.firstCharacterCode = firstCharacterCode;
this.lastCharacterCode = lastCharacterCode;
this.widths = widths;
this.descriptor = descriptor;
this.encoding = encoding;
this.font = font;
Name = name;
IsVertical = false;
@@ -100,6 +105,23 @@
return new PdfRectangle(0, 0, widths[index], 0);
}
public PdfRectangle GetBoundingBox(int characterCode)
{
if (font?.CMapTable == null)
{
return descriptor.BoundingBox;
}
if (!font.CMapTable.TryGetGlyphIndex(characterCode, out var index))
{
return descriptor.BoundingBox;
}
var glyph = font.GlyphTable.Glyphs[index];
return glyph?.GlyphBounds ?? descriptor.BoundingBox;
}
public TransformationMatrix GetFontMatrix()
{
// TODO: should this also use units per em?

View File

@@ -96,6 +96,11 @@
return new PdfRectangle(0, 0, widths[characterCode - firstChar], 0);
}
public PdfRectangle GetBoundingBox(int characterCode)
{
throw new System.NotImplementedException();
}
public TransformationMatrix GetFontMatrix()
{
return fontMatrix;

View File

@@ -61,6 +61,11 @@
return new PdfRectangle(0, 0, metrics.WidthX, 0);
}
public PdfRectangle GetBoundingBox(int characterCode)
{
throw new NotImplementedException();
}
public TransformationMatrix GetFontMatrix()
{
return fontMatrix;

View File

@@ -78,6 +78,11 @@
return new PdfRectangle(0, 0, widths[characterCode - firstChar], 0); ;
}
public PdfRectangle GetBoundingBox(int characterCode)
{
throw new System.NotImplementedException();
}
public TransformationMatrix GetFontMatrix()
{
return fontMatrix;

View File

@@ -1,7 +1,5 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Parser
{
using System;
using System.Collections.Generic;
using Tables;
/// <summary>
@@ -22,5 +20,12 @@
public BasicMaximumProfileTable MaximumProfileTable { get; set; }
public PostScriptTable PostScriptTable { get; set; }
/// <summary>
/// Defines mapping of character codes to glyph index values in the font.
/// Can contain mutliple sub-tables to support multiple encoding schemes.
/// Where a character code isn't found it should map to index 0.
/// </summary>
public CMapTable CMapTable { get; set; }
}
}

View File

@@ -11,9 +11,13 @@
{
var version = (decimal)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();
// ReSharper restore UnusedVariable
var tables = new Dictionary<string, TrueTypeHeaderTable>();
@@ -80,7 +84,6 @@
tableRegister.MaximumProfileTable = BasicMaximumProfileTable.Load(data, maxHeaderTable);
// post
var postScriptTable = default(PostScriptTable);
if (tables.TryGetValue(TrueTypeHeaderTable.Post, out var postscriptHeaderTable))
{
tableRegister.PostScriptTable = PostScriptTable.Load(data, table, tableRegister.MaximumProfileTable);
@@ -108,12 +111,16 @@
OptionallyParseTables(tables, data, tableRegister);
}
return new TrueTypeFont(version, tables, tableRegister.HeaderTable);
return new TrueTypeFont(version, tables, tableRegister);
}
private static void OptionallyParseTables(IReadOnlyDictionary<string, TrueTypeHeaderTable> tables, TrueTypeDataBytes data, TableRegister tableRegister)
{
// cmap
if (tables.TryGetValue(TrueTypeHeaderTable.Cmap, out var cmap))
{
tableRegister.CMapTable = CMapTable.Load(data, cmap, tableRegister);
}
// hmtx
if (tables.TryGetValue(TrueTypeHeaderTable.Hmtx, out var hmtxHeaderTable))
@@ -133,3 +140,4 @@
}
}
}

View File

@@ -1,15 +1,45 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Tables.CMapSubTables
{
/// <inheritdoc />
/// <summary>
/// The format 0 sub-total where character codes and glyph indices are restricted to a single bytes.
/// </summary>
internal class ByteEncodingCMapTable : ICMapSubTable
{
public static ByteEncodingCMapTable Load(TrueTypeDataBytes data)
private const int GlyphMappingLength = 256;
private readonly byte[] glyphMapping;
public int PlatformId { get; }
public int EncodingId { get; }
private ByteEncodingCMapTable(int platformId, int encodingId, byte[] glyphMapping)
{
this.glyphMapping = glyphMapping;
PlatformId = platformId;
EncodingId = encodingId;
}
public static ByteEncodingCMapTable Load(TrueTypeDataBytes data, int platformId, int encodingId)
{
// ReSharper disable UnusedVariable
var length = data.ReadUnsignedShort();
var version = data.ReadUnsignedShort();
// ReSharper restore UnusedVariable
var glyphMapping = data.ReadByteArray(256);
var glyphMapping = data.ReadByteArray(GlyphMappingLength);
return new ByteEncodingCMapTable();
return new ByteEncodingCMapTable(platformId, encodingId, glyphMapping);
}
public int CharacterCodeToGlyphIndex(int characterCode)
{
if (characterCode < 0 || characterCode >= GlyphMappingLength)
{
return 0;
}
return glyphMapping[characterCode];
}
}
}

View File

@@ -0,0 +1,158 @@
// ReSharper disable UnusedVariable
namespace UglyToad.PdfPig.Fonts.TrueType.Tables.CMapSubTables
{
using System;
using System.Collections.Generic;
/// <inheritdoc />
/// <summary>
/// A format 4 CMap sub-table which defines gappy ranges of character code to glyph index mappings.
/// </summary>
internal class Format4CMapTable : ICMapSubTable
{
public int PlatformId { get; }
public int EncodingId { get; }
public int Language { get; }
public IReadOnlyList<Segment> Segments { get; }
public IReadOnlyList<int> GlyphIds { get; }
/// <summary>
/// Create a new <see cref="Format4CMapTable"/>.
/// </summary>
public Format4CMapTable(int platformId, int encodingId, int language, IReadOnlyList<Segment> segments, IReadOnlyList<int> glyphIds)
{
PlatformId = platformId;
EncodingId = encodingId;
Language = language;
Segments = segments ?? throw new ArgumentNullException(nameof(segments));
GlyphIds = glyphIds ?? throw new ArgumentNullException(nameof(glyphIds));
}
public int CharacterCodeToGlyphIndex(int characterCode)
{
for (var i = 0; i < Segments.Count; i++)
{
var segment = Segments[i];
if (segment.EndCode < characterCode || segment.StartCode > characterCode)
{
continue;
}
if (segment.IdRangeOffset == 0)
{
return (characterCode + segment.IdDelta) % ushort.MaxValue;
}
var offset = segment.IdRangeOffset / 2 + (characterCode - segment.StartCode);
return GlyphIds[offset - Segments.Count + i];
}
return 0;
}
public static Format4CMapTable Load(TrueTypeDataBytes data, int platformId, int encodingId)
{
// Length in bytes.
var length = data.ReadUnsignedShort();
// Used for sub-tables with a Macintosh platform ID.
var version = data.ReadUnsignedShort();
var doubleSegmentCount = data.ReadUnsignedShort();
// Defines the number of contiguous segments.
var segmentCount = doubleSegmentCount / 2;
// Some crazy sum.
var searchRange = data.ReadUnsignedShort();
var entrySelector = data.ReadUnsignedShort();
var rangeShift = data.ReadUnsignedShort();
// End character codes for each segment.
var endCounts = data.ReadUnsignedShortArray(segmentCount);
// Should be zero.
var reservedPad = data.ReadUnsignedShort();
// Start character codes for each segment.
var startCounts = data.ReadUnsignedShortArray(segmentCount);
// Delta for all character codes in the segment. Contrary to the spec this is actually a short[].
var idDeltas = data.ReadShortArray(segmentCount);
var idRangeOffsets = data.ReadUnsignedShortArray(segmentCount);
const int singleIntsRead = 8;
const int intArraysRead = 8;
// ReSharper disable once ArrangeRedundantParentheses
var remainingBytes = length - ((singleIntsRead * 2) + intArraysRead * segmentCount);
var remainingInts = remainingBytes / 2;
var glyphIndices = data.ReadUnsignedShortArray(remainingInts);
var segments = new Segment[endCounts.Length];
for (int i = 0; i < endCounts.Length; i++)
{
var start = startCounts[i];
var end = endCounts[i];
var delta = idDeltas[i];
var offsets = idRangeOffsets[i];
segments[i] = new Segment(start, end, delta, offsets);
}
return new Format4CMapTable(platformId, encodingId, version, segments, glyphIndices);
}
/// <summary>
/// A contiguous segment which maps character to glyph codes in a Format 4 CMap sub-table.
/// </summary>
public struct Segment
{
/// <summary>
/// The start character code in the range.
/// </summary>
public int StartCode { get; }
/// <summary>
/// The end character code in the range.
/// </summary>
public int EndCode { get; }
/// <summary>
/// The delta for the codes in the segment.
/// </summary>
public int IdDelta { get; }
/// <summary>
/// Offset in bytes to glyph index array.
/// </summary>
public int IdRangeOffset { get; }
/// <summary>
/// Create a new <see cref="Segment"/>.
/// </summary>
public Segment(int startCode, int endCode, int idDelta, int idRangeOffset)
{
StartCode = startCode;
EndCode = endCode;
IdDelta = idDelta;
IdRangeOffset = idRangeOffset;
}
public override string ToString()
{
return $"Start: {StartCode}, End: {EndCode}, Delta: {IdDelta}, Offset: {IdRangeOffset}";
}
}
}
}

View File

@@ -3,12 +3,42 @@
using System;
using System.Collections.Generic;
/// <inheritdoc />
/// <summary>
/// A format 2 sub-table for Chinese, Japanese and Korean characters.
/// Contains mixed 8/16 bit encodings.
/// </summary>
internal class HighByteMappingCMapTable : ICMapSubTable
{
public static HighByteMappingCMapTable Load(TrueTypeDataBytes data, int numberOfGlyphs)
private readonly IReadOnlyDictionary<int, int> characterCodesToGlyphIndices;
public int PlatformId { get; }
public int EncodingId { get; }
private HighByteMappingCMapTable(int platformId, int encodingId, IReadOnlyDictionary<int, int> characterCodesToGlyphIndices)
{
this.characterCodesToGlyphIndices = characterCodesToGlyphIndices ?? throw new ArgumentNullException(nameof(characterCodesToGlyphIndices));
PlatformId = platformId;
EncodingId = encodingId;
}
public int CharacterCodeToGlyphIndex(int characterCode)
{
if (!characterCodesToGlyphIndices.TryGetValue(characterCode, out var index))
{
return 0;
}
return index;
}
public static HighByteMappingCMapTable Load(TrueTypeDataBytes data, int numberOfGlyphs, int platformId, int encodingId)
{
// ReSharper disable UnusedVariable
var length = data.ReadUnsignedShort();
var version = data.ReadUnsignedShort();
// ReSharper restore UnusedVariable
var subHeaderKeys = new int[256];
var maximumSubHeaderIndex = 0;
@@ -63,7 +93,7 @@
}
}
return new HighByteMappingCMapTable();
return new HighByteMappingCMapTable(platformId, encodingId, characterCodeToGlyphId);
}
public struct SubHeader

View File

@@ -1,7 +1,33 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Tables.CMapSubTables
{
/// <summary>
/// In a TrueType font the CMap table maps from character codes to glyph indices
/// A font which can run on multiple platforms will have multiple encoding tables. These are stored as multiple
/// sub-tables. The <see cref="ICMapSubTable"/> represents a single subtotal.
/// </summary>
internal interface ICMapSubTable
{
/// <summary>
/// The platform identifier.
/// </summary>
/// <remarks>
/// 0: Unicode
/// 1: Macintosh
/// 2: Reserved
/// 3: Microsoft
/// </remarks>
int PlatformId { get; }
/// <summary>
/// Platform specific encoding indentifier.
/// </summary>
int EncodingId { get; }
/// <summary>
/// Maps from a character code to the array index of the glyph in the font data.
/// </summary>
/// <param name="characterCode">The character code.</param>
/// <returns>The index of the glyph information for this character.</returns>
int CharacterCodeToGlyphIndex(int characterCode);
}
}

View File

@@ -1,10 +0,0 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Tables.CMapSubTables
{
internal class SegmentMappingDeltaValuesCMapTable : ICMapSubTable
{
public static SegmentMappingDeltaValuesCMapTable Load(TrueTypeDataBytes data)
{
return null;
}
}
}

View File

@@ -0,0 +1,60 @@
// ReSharper disable UnusedVariable
namespace UglyToad.PdfPig.Fonts.TrueType.Tables.CMapSubTables
{
using System;
/// <inheritdoc />
/// <summary>
/// A format 6 CMap sub-table which uses 2 bytes to map a contiguous range of character codes to glyph indices.
/// </summary>
internal class TrimmedTableMappingCMapTable : ICMapSubTable
{
private readonly int firstCharacterCode;
private readonly int entryCount;
private readonly int[] glyphIndices;
public int PlatformId { get; }
public int EncodingId { get; }
/// <summary>
/// Create a new <see cref="TrimmedTableMappingCMapTable"/>.
/// </summary>
public TrimmedTableMappingCMapTable(int platformId, int encodingId, int firstCharacterCode, int entryCount, int[] glyphIndices)
{
this.firstCharacterCode = firstCharacterCode;
this.entryCount = entryCount;
this.glyphIndices = glyphIndices ?? throw new ArgumentNullException(nameof(glyphIndices));
PlatformId = platformId;
EncodingId = encodingId;
}
public int CharacterCodeToGlyphIndex(int characterCode)
{
if (characterCode < firstCharacterCode || characterCode > firstCharacterCode + entryCount)
{
return 0;
}
var offset = characterCode - firstCharacterCode;
return glyphIndices[offset];
}
public static TrimmedTableMappingCMapTable Load(TrueTypeDataBytes data, int platformId, int encodingId)
{
var length = data.ReadUnsignedShort();
var language = data.ReadUnsignedShort();
// First character code in the range.
var firstCode = data.ReadUnsignedShort();
// Number of character codes in the range.
var entryCount = data.ReadUnsignedShort();
var glyphIndices = data.ReadUnsignedShortArray(entryCount);
return new TrimmedTableMappingCMapTable(platformId, encodingId, firstCode, entryCount, glyphIndices);
}
}
}

View File

@@ -21,6 +21,28 @@
DirectoryTable = directoryTable;
}
public bool TryGetGlyphIndex(int characterCode, out int glyphIndex)
{
glyphIndex = 0;
if (subTables.Count == 0)
{
return false;
}
foreach (var subTable in subTables)
{
glyphIndex = subTable.CharacterCodeToGlyphIndex(characterCode);
if (glyphIndex != 0)
{
return true;
}
}
return false;
}
public static CMapTable Load(TrueTypeDataBytes data, TrueTypeHeaderTable table, TableRegister tableRegister)
{
data.Seek(table.Offset);
@@ -52,19 +74,47 @@
var format = data.ReadUnsignedShort();
/*
* There are 9 currently available formats:
* 0: Character code and glyph indices are restricted to a single byte. Rare.
* 2: Suitable for CJK characters. Contain mixed 8/16 byte encoding.
* 4: 2 byte encoding format. Used when character codes fall into (gappy) contiguous ranges.
* 6: 'Trimmed table mapping', used when character codes fall into a single contiguous range. This is dense mapping.
* 8: 16/32 bit coverage. Uses mixed length character codes.
* 10: Similar to format 6, trimmed table/array for 32 bits.
* 12: Segmented coverage, similar to format 4 but for 32 bit/4 byte.
* 13: Many to one mappings. Used by Apple for the LastResort font.
* 14: Unicode variation sequences.
*
* Many of the formats are obsolete or not really used. Modern fonts will tend to use formats 4, 6 and 12.
* For PDF we will support 0, 2 and 4 since these are in the original TrueType spec.
*/
switch (format)
{
case 0:
{
// Simple 1 to 1 mapping of character codes to glyph codes.
var item = ByteEncodingCMapTable.Load(data);
var item = ByteEncodingCMapTable.Load(data, header.PlatformId, header.EncodingId);
tables.Add(item);
break;
}
case 1:
case 2:
{
// Useful for CJK characters. Use mixed 8/16 bit encoding.
var item = HighByteMappingCMapTable.Load(data, numberofGlyphs);
var item = HighByteMappingCMapTable.Load(data, numberofGlyphs, header.PlatformId, header.EncodingId);
tables.Add(item);
break;
}
case 4:
{
// Microsoft's standard mapping table.
var item = Format4CMapTable.Load(data, header.PlatformId, header.EncodingId);
tables.Add(item);
break;
}
case 6:
{
var item = TrimmedTableMappingCMapTable.Load(data, header.PlatformId, header.EncodingId);
tables.Add(item);
break;
}

View File

@@ -173,5 +173,17 @@
offsets[i] = ReadUnsignedInt();
}
}
public short[] ReadShortArray(int length)
{
var result = new short[length];
for (int i = 0; i < length; i++)
{
result[i] = ReadSignedShort();
}
return result;
}
}
}

View File

@@ -1,22 +1,33 @@
namespace UglyToad.PdfPig.Fonts.TrueType
{
using System;
using System.Collections.Generic;
using CidFonts;
using Parser;
using Tables;
internal class TrueTypeFont : ICidFontProgram
{
public decimal Version { get; }
public IReadOnlyDictionary<string, TrueTypeHeaderTable> Tables { get; }
public IReadOnlyDictionary<string, TrueTypeHeaderTable> TableHeaders { get; }
public HeaderTable HeaderTable { get; }
public CMapTable CMapTable { get; }
public GlyphDataTable GlyphTable { get; }
public TrueTypeFont(decimal version, IReadOnlyDictionary<string, TrueTypeHeaderTable> tables, HeaderTable headerTable)
public TrueTypeFont(decimal version, IReadOnlyDictionary<string, TrueTypeHeaderTable> tableHeaders, TableRegister tableRegister)
{
if (tableRegister == null)
{
throw new ArgumentNullException(nameof(tableRegister));
}
Version = version;
Tables = tables;
HeaderTable = headerTable;
TableHeaders = tableHeaders;
HeaderTable = tableRegister.HeaderTable;
CMapTable = tableRegister.CMapTable;
GlyphTable = tableRegister.GlyphDataTable;
}
}
}