693 lines
31 KiB
C#
693 lines
31 KiB
C#
// Copyright (c) Six Labors.
|
||
// Licensed under the Six Labors Split License.
|
||
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Text;
|
||
|
||
namespace SixLabors.Fonts.Tables.Cff {
|
||
/// <summary>
|
||
/// Parses a Compact Font Format (CFF) font program as described in The Compact Font Format specification (Adobe Technical Note #5176).
|
||
/// A CFF font may contain multiple fonts and achieves compression by sharing details between fonts in the set.
|
||
/// </summary>
|
||
internal class Cff1Parser : CffParserBase
|
||
{
|
||
/// <summary>
|
||
/// Latin 1 Encoding: ISO 8859-1 is a single-byte encoding that can represent the first 256 Unicode characters.
|
||
/// </summary>
|
||
private static readonly Encoding Iso88591 = Encoding.GetEncoding("ISO-8859-1");
|
||
|
||
private long offset;
|
||
private int charStringsOffset;
|
||
private int charsetOffset;
|
||
private int encodingOffset = -1;
|
||
private int privateDICTOffset;
|
||
private int privateDICTLength;
|
||
|
||
/// <summary>
|
||
/// Loads and parses a CFF1 font from the given reader at the specified offset.
|
||
/// </summary>
|
||
/// <param name="reader">The binary reader positioned at the CFF1 data.</param>
|
||
/// <param name="offset">The absolute offset of the CFF1 table in the font stream.</param>
|
||
/// <returns>The parsed <see cref="CffFont"/>.</returns>
|
||
public CffFont Load(BigEndianBinaryReader reader, long offset)
|
||
{
|
||
this.offset = offset;
|
||
|
||
string fontName = ReadNameIndex(reader);
|
||
|
||
List<CffDataDicEntry> dataDicEntries = this.ReadTopDictIndex(reader);
|
||
string[] stringIndex = ReadStringIndex(reader);
|
||
|
||
CffTopDictionary topDictionary = this.ResolveTopDictInfo(dataDicEntries, stringIndex);
|
||
|
||
byte[][] globalSubrRawBuffers = ReadGlobalSubrIndex(reader);
|
||
|
||
ReadFdSelect(reader, this.offset, topDictionary.CidFontInfo);
|
||
FontDict[] fontDicts = this.ReadFdArray(reader, this.offset, topDictionary.CidFontInfo.FDArray);
|
||
|
||
CffPrivateDictionary? privateDictionary = this.ReadPrivateDict(reader);
|
||
CffGlyphData[] glyphs = this.ReadCharStringsIndex(reader, topDictionary, globalSubrRawBuffers, fontDicts, privateDictionary);
|
||
|
||
this.ReadCharsets(reader, stringIndex, glyphs);
|
||
this.ReadEncodings(reader);
|
||
|
||
return new(fontName, topDictionary, glyphs);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Reads the Name INDEX, which contains the PostScript name of the font.
|
||
/// </summary>
|
||
/// <param name="reader">The binary reader.</param>
|
||
/// <returns>The font name string.</returns>
|
||
private static string ReadNameIndex(BigEndianBinaryReader reader)
|
||
{
|
||
if (!TryReadIndexDataOffsets(reader, out CffIndexOffset[]? offsets))
|
||
{
|
||
throw new InvalidFontFileException("No name index found.");
|
||
}
|
||
|
||
// For Open Type the Name INDEX in the CFF data must contain only one entry;
|
||
// that is, there must be only one font in the CFF FontSet.
|
||
CffIndexOffset offset = offsets[0];
|
||
return reader.ReadString(offset.Length, Iso88591);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Reads the Top DICT INDEX, which contains top-level dictionary entries for the font.
|
||
/// </summary>
|
||
/// <param name="reader">The binary reader.</param>
|
||
/// <returns>A list of parsed DICT entries.</returns>
|
||
private List<CffDataDicEntry> ReadTopDictIndex(BigEndianBinaryReader reader)
|
||
{
|
||
// 8. Top DICT INDEX
|
||
// This contains the top - level DICTs of all the fonts in the FontSet
|
||
// stored in an INDEX structure.Objects contained within this
|
||
// INDEX correspond to those in the Name INDEX in both order
|
||
// and number. Each object is a DICT structure that corresponds to
|
||
// the top-level dictionary of a PostScript font.
|
||
// A font is identified by an entry in the Name INDEX and its data
|
||
// is accessed via the corresponding Top DICT
|
||
if (!TryReadIndexDataOffsets(reader, out CffIndexOffset[]? offsets))
|
||
{
|
||
throw new InvalidFontFileException("No Top DICT index found.");
|
||
}
|
||
|
||
// 9. Top DICT Data
|
||
// The names of the Top DICT operators shown in
|
||
// Table 9 are, where possible, the same as the corresponding Type 1 dict key.
|
||
// Operators that have no corresponding Type1 dict key are noted
|
||
// in the table below along with a default value, if any. (Several
|
||
// operators have been derived from FontInfo dict keys but have
|
||
// been grouped together with the Top DICT operators for
|
||
// simplicity.The keys from the FontInfo dict are indicated in the
|
||
// Default, notes column of Table 9)
|
||
return this.ReadDictData(reader, offsets[0].Length);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Reads the String INDEX containing font-specific strings referenced by SID.
|
||
/// </summary>
|
||
/// <param name="reader">The binary reader.</param>
|
||
/// <returns>An array of strings from the String INDEX.</returns>
|
||
private static string[] ReadStringIndex(BigEndianBinaryReader reader)
|
||
{
|
||
if (!TryReadIndexDataOffsets(reader, out CffIndexOffset[]? offsets))
|
||
{
|
||
return [];
|
||
}
|
||
|
||
string[] stringIndex = new string[offsets.Length];
|
||
|
||
// Allow reusing the same buffer for shorter reads.
|
||
using Buffer<byte> buffer = new(512);
|
||
Span<byte> bufferSpan = buffer.GetSpan();
|
||
|
||
for (int i = 0; i < offsets.Length; ++i)
|
||
{
|
||
int length = offsets[i].Length;
|
||
if (length < bufferSpan.Length)
|
||
{
|
||
Span<byte> slice = bufferSpan[..length];
|
||
int actualRead = reader.BaseStream.Read(slice);
|
||
if (actualRead != length)
|
||
{
|
||
throw new InvalidFontFileException("Invalid string length.");
|
||
}
|
||
|
||
stringIndex[i] = Iso88591.GetString(slice);
|
||
}
|
||
else
|
||
{
|
||
stringIndex[i] = reader.ReadString(length, Iso88591);
|
||
}
|
||
}
|
||
|
||
return stringIndex;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Resolves a string identifier (SID) to its string value using the standard strings
|
||
/// table and the font-specific String INDEX.
|
||
/// </summary>
|
||
/// <param name="index">The SID to resolve.</param>
|
||
/// <param name="stringIndex">The font-specific String INDEX.</param>
|
||
/// <returns>The resolved string name.</returns>
|
||
private static string GetSid(int index, string[] stringIndex)
|
||
{
|
||
if (index >= 0 && index <= CffStandardStrings.Count - 1)
|
||
{
|
||
// Use standard name
|
||
return CffStandardStrings.GetName(index);
|
||
}
|
||
|
||
if (index - CffStandardStrings.Count < stringIndex.Length)
|
||
{
|
||
return stringIndex[index - CffStandardStrings.Count];
|
||
}
|
||
|
||
// Technically this maps to .notdef, but PDFBox uses this
|
||
return "SID" + index;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Resolves the Top DICT entries into a <see cref="CffTopDictionary"/> by interpreting operator-operand pairs.
|
||
/// </summary>
|
||
/// <param name="entries">The parsed DICT entries.</param>
|
||
/// <param name="stringIndex">The font-specific String INDEX for SID resolution.</param>
|
||
/// <returns>The populated <see cref="CffTopDictionary"/>.</returns>
|
||
private CffTopDictionary ResolveTopDictInfo(List<CffDataDicEntry> entries, string[] stringIndex)
|
||
{
|
||
// TODO: Is CID mandatory?
|
||
CffTopDictionary metrics = new();
|
||
foreach (CffDataDicEntry entry in entries)
|
||
{
|
||
switch (entry.Operator.Name)
|
||
{
|
||
default:
|
||
#if DEBUG
|
||
System.Diagnostics.Debug.WriteLine("topdic:" + entry.Operator.Name);
|
||
#endif
|
||
break;
|
||
case "XUID":
|
||
break; // nothing
|
||
case "version":
|
||
metrics.Version = GetSid((int)entry.Operands[0].RealNumValue, stringIndex);
|
||
break;
|
||
case "Notice":
|
||
metrics.Notice = GetSid((int)entry.Operands[0].RealNumValue, stringIndex);
|
||
break;
|
||
case "Copyright":
|
||
metrics.CopyRight = GetSid((int)entry.Operands[0].RealNumValue, stringIndex);
|
||
break;
|
||
case "FullName":
|
||
metrics.FullName = GetSid((int)entry.Operands[0].RealNumValue, stringIndex);
|
||
break;
|
||
case "FamilyName":
|
||
metrics.FamilyName = GetSid((int)entry.Operands[0].RealNumValue, stringIndex);
|
||
break;
|
||
case "Weight":
|
||
metrics.Weight = GetSid((int)entry.Operands[0].RealNumValue, stringIndex);
|
||
break;
|
||
case "UnderlinePosition":
|
||
metrics.UnderlinePosition = entry.Operands[0].RealNumValue;
|
||
break;
|
||
case "UnderlineThickness":
|
||
metrics.UnderlineThickness = entry.Operands[0].RealNumValue;
|
||
break;
|
||
case "FontBBox":
|
||
metrics.FontBBox =
|
||
[
|
||
entry.Operands[0].RealNumValue,
|
||
entry.Operands[1].RealNumValue,
|
||
entry.Operands[2].RealNumValue,
|
||
entry.Operands[3].RealNumValue
|
||
];
|
||
break;
|
||
case "CharStrings":
|
||
this.charStringsOffset = (int)entry.Operands[0].RealNumValue;
|
||
break;
|
||
case "charset":
|
||
this.charsetOffset = (int)entry.Operands[0].RealNumValue;
|
||
break;
|
||
case "Encoding":
|
||
this.encodingOffset = (int)entry.Operands[0].RealNumValue;
|
||
break;
|
||
case "Private":
|
||
// private DICT size and offset
|
||
this.privateDICTLength = (int)entry.Operands[0].RealNumValue;
|
||
this.privateDICTOffset = (int)entry.Operands[1].RealNumValue;
|
||
break;
|
||
case "ROS":
|
||
// http://wwwimages.adobe.com/www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5176.CFF.pdf
|
||
// A CFF CIDFont has the CIDFontName in the Name INDEX and a corresponding Top DICT.
|
||
// The Top DICT begins with ROS operator which specifies the Registry-Ordering - Supplement for the font.
|
||
// This will indicate to a CFF parser that special CID processing should be applied to this font. Specifically:
|
||
|
||
// ROS operator combines the Registry, Ordering, and Supplement keys together.
|
||
// see Adobe Cmap resource , https://github.com/adobe-type-tools/cmap-resources
|
||
metrics.CidFontInfo.ROS_Register = GetSid((int)entry.Operands[0].RealNumValue, stringIndex);
|
||
metrics.CidFontInfo.ROS_Ordering = GetSid((int)entry.Operands[1].RealNumValue, stringIndex);
|
||
metrics.CidFontInfo.ROS_Supplement = GetSid((int)entry.Operands[2].RealNumValue, stringIndex);
|
||
|
||
break;
|
||
case "CIDFontVersion":
|
||
metrics.CidFontInfo.CIDFontVersion = entry.Operands[0].RealNumValue;
|
||
break;
|
||
case "CIDCount":
|
||
metrics.CidFontInfo.CIDFountCount = (int)entry.Operands[0].RealNumValue;
|
||
break;
|
||
case "FDSelect":
|
||
metrics.CidFontInfo.FDSelect = (int)entry.Operands[0].RealNumValue;
|
||
break;
|
||
case "FDArray":
|
||
metrics.CidFontInfo.FDArray = (int)entry.Operands[0].RealNumValue;
|
||
break;
|
||
}
|
||
}
|
||
|
||
return metrics;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Reads the Global Subrs INDEX, which contains shared subroutine charstring programs.
|
||
/// </summary>
|
||
/// <param name="reader">The binary reader.</param>
|
||
/// <returns>An array of byte arrays, each containing a global subroutine charstring.</returns>
|
||
private static byte[][] ReadGlobalSubrIndex(BigEndianBinaryReader reader)
|
||
|
||
// 16. Local / Global Subrs INDEXes
|
||
// Both Type 1 and Type 2 charstrings support the notion of
|
||
// subroutines or subrs.
|
||
|
||
// A subr is typically a sequence of charstring
|
||
// bytes representing a sub - program that occurs in more than one
|
||
// place in a font’s charstring data.
|
||
|
||
// This subr may be stored once
|
||
// but referenced many times from within one or more charstrings
|
||
// by the use of the call subr operator whose operand is the
|
||
// number of the subr to be called.
|
||
|
||
// The subrs are local to a particular font and
|
||
// cannot be shared between fonts.
|
||
|
||
// Type 2 charstrings also permit global subrs which function in the same
|
||
// way but are called by the call gsubr operator and may be shared
|
||
// across fonts.
|
||
|
||
// Local subrs are stored in an INDEX structure which is located via
|
||
// the offset operand of the Subrs operator in the Private DICT.
|
||
// A font without local subrs has no Subrs operator in the Private DICT.
|
||
|
||
// Global subrs are stored in an INDEX structure which follows the
|
||
// String INDEX. A FontSet without any global subrs is represented
|
||
// by an empty Global Subrs INDEX.
|
||
=> ReadSubrBuffer(reader);
|
||
|
||
/// <summary>
|
||
/// Reads the Local Subrs INDEX, which contains font-private subroutine charstring programs.
|
||
/// </summary>
|
||
/// <param name="reader">The binary reader.</param>
|
||
/// <returns>An array of byte arrays, each containing a local subroutine charstring.</returns>
|
||
private static byte[][] ReadLocalSubrs(BigEndianBinaryReader reader) => ReadSubrBuffer(reader);
|
||
|
||
/// <summary>
|
||
/// Reads the encoding data for the font if an encoding offset is specified.
|
||
/// </summary>
|
||
/// <param name="reader">The binary reader.</param>
|
||
// TODO: We don't actually need this right now. Will be important though if we ever introduce subsetting.
|
||
private void ReadEncodings(BigEndianBinaryReader reader)
|
||
{
|
||
// Encoding data is located via the offset operand to the
|
||
// Encoding operator in the Top DICT.
|
||
|
||
// Only one Encoding operator can be
|
||
// specified per font except for CIDFonts which specify no
|
||
// encoding.
|
||
|
||
// A glyph’s encoding is specified by a 1 - byte code that
|
||
// permits values in the range 0 - 255.
|
||
|
||
// Each encoding is described by a format-type identifier byte
|
||
// followed by format-specific data.Two formats are currently
|
||
// defined as specified in Tables 11(Format 0) and 12(Format 1).
|
||
if (this.encodingOffset != -1)
|
||
{
|
||
byte encoding = reader.ReadByte();
|
||
switch (encoding)
|
||
{
|
||
case 0:
|
||
ReadFormat0Encoding(reader);
|
||
break;
|
||
case 1:
|
||
ReadFormat1Encoding(reader);
|
||
break;
|
||
default:
|
||
|
||
// TODO: Seek.
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Reads the charset data, which maps glyph indices to glyph names.
|
||
/// </summary>
|
||
/// <param name="reader">The binary reader.</param>
|
||
/// <param name="stringIndex">The font-specific String INDEX for SID resolution.</param>
|
||
/// <param name="glyphs">The glyph data array to populate with glyph names.</param>
|
||
private void ReadCharsets(BigEndianBinaryReader reader, string[] stringIndex, CffGlyphData[] glyphs)
|
||
{
|
||
// Charset data is located via the offset operand to the
|
||
// charset operator in the Top DICT.
|
||
|
||
// Each charset is described by a format-
|
||
// type identifier byte followed by format-specific data.
|
||
// Three formats are currently defined as shown in Tables
|
||
// 17, 18, and 20.
|
||
reader.BaseStream.Position = this.offset + this.charsetOffset;
|
||
switch (reader.ReadByte())
|
||
{
|
||
default:
|
||
throw new NotSupportedException();
|
||
case 0:
|
||
ReadCharsetsFormat0(reader, stringIndex, glyphs);
|
||
break;
|
||
case 1:
|
||
ReadCharsetsFormat1(reader, stringIndex, glyphs);
|
||
break;
|
||
case 2:
|
||
ReadCharsetsFormat2(reader, stringIndex, glyphs);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Reads charset data in format 0, where each glyph has an individual SID entry.
|
||
/// </summary>
|
||
/// <param name="reader">The binary reader.</param>
|
||
/// <param name="stringIndex">The font-specific String INDEX for SID resolution.</param>
|
||
/// <param name="glyphs">The glyph data array to populate with glyph names.</param>
|
||
private static void ReadCharsetsFormat0(BigEndianBinaryReader reader, string[] stringIndex, CffGlyphData[] glyphs)
|
||
{
|
||
// Table 17: Format 0
|
||
// Type Name Description
|
||
// Card8 format =0
|
||
// SID glyph[nGlyphs-1] Glyph name array
|
||
|
||
// Each element of the glyph array represents the name of the
|
||
// corresponding glyph. This format should be used when the SIDs
|
||
// are in a fairly random order. The number of glyphs (nGlyphs) is
|
||
// the value of the count field in the
|
||
// CharStrings INDEX. (There is
|
||
// one less element in the glyph name array than nGlyphs because
|
||
// the .notdef glyph name is omitted.)
|
||
for (int i = 1; i < glyphs.Length; ++i)
|
||
{
|
||
ref CffGlyphData data = ref glyphs[i];
|
||
data.GlyphName = GetSid(reader.ReadUInt16(), stringIndex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Reads charset data in format 1, using Range1 structures with 1-byte nLeft counts.
|
||
/// </summary>
|
||
/// <param name="reader">The binary reader.</param>
|
||
/// <param name="stringIndex">The font-specific String INDEX for SID resolution.</param>
|
||
/// <param name="glyphs">The glyph data array to populate with glyph names.</param>
|
||
private static void ReadCharsetsFormat1(BigEndianBinaryReader reader, string[] stringIndex, CffGlyphData[] glyphs)
|
||
{
|
||
// Table 18 Format 1
|
||
// Type Name Description
|
||
// Card8 format =1
|
||
// struct Range1[<varies>] Range1 array (see Table 19)
|
||
|
||
// Table 19 Range1 Format (Charset)
|
||
// Type Name Description
|
||
// SID first First glyph in range
|
||
// Card8 nLeft Glyphs left in range(excluding first)
|
||
|
||
// Each Range1 describes a group of sequential SIDs. The number
|
||
// of ranges is not explicitly specified in the font. Instead, software
|
||
// utilizing this data simply processes ranges until all glyphs in the
|
||
// font are covered. This format is particularly suited to charsets
|
||
// that are well ordered
|
||
for (int i = 1; i < glyphs.Length;)
|
||
{
|
||
int sid = reader.ReadUInt16(); // First glyph in range
|
||
int count = reader.ReadByte() + 1; // since it does not include first element.
|
||
do
|
||
{
|
||
ref CffGlyphData data = ref glyphs[i];
|
||
data.GlyphName = GetSid(sid, stringIndex);
|
||
|
||
count--;
|
||
i++;
|
||
sid++;
|
||
}
|
||
while (count > 0);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Reads charset data in format 2, using Range2 structures with 2-byte nLeft counts for large charsets.
|
||
/// </summary>
|
||
/// <param name="reader">The binary reader.</param>
|
||
/// <param name="stringIndex">The font-specific String INDEX for SID resolution.</param>
|
||
/// <param name="glyphs">The glyph data array to populate with glyph names.</param>
|
||
private static void ReadCharsetsFormat2(BigEndianBinaryReader reader, string[] stringIndex, CffGlyphData[] glyphs)
|
||
{
|
||
// note:eg, Adobe's source-code-pro font
|
||
|
||
// Table 20 Format 2
|
||
// Type Name Description
|
||
// Card8 format 2
|
||
// struct Range2[<varies>] Range2 array (see Table 21)
|
||
//
|
||
//-----------------------------------------------
|
||
// Table 21 Range2 Format
|
||
// Type Name Description
|
||
// SID first First glyph in range
|
||
// Card16 nLeft Glyphs left in range (excluding first)
|
||
//-----------------------------------------------
|
||
|
||
// Format 2 differs from format 1 only in the size of the nLeft field in each range.
|
||
// This format is most suitable for fonts with a large well - ordered charset — for example, for Asian CIDFonts.
|
||
for (int i = 1; i < glyphs.Length;)
|
||
{
|
||
int sid = reader.ReadUInt16(); // First glyph in range
|
||
int count = reader.ReadUInt16() + 1; // since it does not include first element.
|
||
do
|
||
{
|
||
ref CffGlyphData data = ref glyphs[i];
|
||
data.GlyphName = GetSid(sid, stringIndex);
|
||
|
||
count--;
|
||
i++;
|
||
sid++;
|
||
}
|
||
while (count > 0);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Reads the CharStrings INDEX and creates glyph data objects for all glyphs in the font.
|
||
/// </summary>
|
||
/// <param name="reader">The binary reader.</param>
|
||
/// <param name="topDictionary">The top-level dictionary containing font metadata.</param>
|
||
/// <param name="globalSubrBuffers">The global subroutine buffers.</param>
|
||
/// <param name="fontDicts">The Font DICT array for CID fonts.</param>
|
||
/// <param name="privateDictionary">The private dictionary containing local subroutine references.</param>
|
||
/// <returns>An array of <see cref="CffGlyphData"/> for each glyph.</returns>
|
||
private CffGlyphData[] ReadCharStringsIndex(
|
||
BigEndianBinaryReader reader,
|
||
CffTopDictionary topDictionary,
|
||
byte[][] globalSubrBuffers,
|
||
FontDict[] fontDicts,
|
||
CffPrivateDictionary? privateDictionary)
|
||
{
|
||
// 14. CharStrings INDEX
|
||
|
||
// This contains the charstrings of all the glyphs in a font stored in
|
||
// an INDEX structure.
|
||
|
||
// Charstring objects contained within this
|
||
// INDEX are accessed by GID.
|
||
|
||
// The first charstring(GID 0) must be
|
||
// the.notdef glyph.
|
||
|
||
// The number of glyphs available in a font may
|
||
// be determined from the count field in the INDEX.
|
||
|
||
//
|
||
|
||
// The format of the charstring data, and therefore the method of
|
||
// interpretation, is specified by the
|
||
// CharstringType operator in the Top DICT.
|
||
|
||
// The CharstringType operator has a default value
|
||
// of 2 indicating the Type 2 charstring format which was designed
|
||
// in conjunction with CFF.
|
||
|
||
// Type 1 charstrings are documented in
|
||
// the "Adobe Type 1 Font Format" published by Addison - Wesley.
|
||
|
||
// Type 2 charstrings are described in Adobe Technical Note #5177:
|
||
// "Type 2 Charstring Format." Other charstring types may also be
|
||
// supported by this method.
|
||
reader.BaseStream.Position = this.offset + this.charStringsOffset;
|
||
if (!TryReadIndexDataOffsets(reader, out CffIndexOffset[]? offsets))
|
||
{
|
||
throw new InvalidFontFileException("No glyph data found.");
|
||
}
|
||
|
||
int glyphCount = offsets.Length;
|
||
CffGlyphData[] glyphs = new CffGlyphData[glyphCount];
|
||
byte[][]? localSubBuffer = privateDictionary?.LocalSubrRawBuffers;
|
||
|
||
// Is the font a CID font?
|
||
FDRangeProvider fdRangeProvider = new(topDictionary.CidFontInfo);
|
||
bool isCidFont = topDictionary.CidFontInfo.FdRanges.Length > 0;
|
||
|
||
for (int i = 0; i < glyphCount; ++i)
|
||
{
|
||
CffIndexOffset offset = offsets[i];
|
||
byte[] charstringsBuffer = reader.ReadBytes(offset.Length);
|
||
|
||
// Now we can parse the raw glyph instructions
|
||
if (isCidFont)
|
||
{
|
||
// Select proper local private dict
|
||
fdRangeProvider.SetCurrentGlyphIndex((ushort)i);
|
||
localSubBuffer = fontDicts[fdRangeProvider.SelectedFDArray].LocalSubr;
|
||
}
|
||
|
||
glyphs[i] = new CffGlyphData(
|
||
(ushort)i,
|
||
globalSubrBuffers,
|
||
localSubBuffer ?? [],
|
||
privateDictionary?.NominalWidthX ?? 0,
|
||
charstringsBuffer,
|
||
1);
|
||
}
|
||
|
||
return glyphs;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Reads format 0 encoding data where each glyph has an individual code assignment.
|
||
/// </summary>
|
||
/// <param name="reader">The binary reader.</param>
|
||
private static void ReadFormat0Encoding(BigEndianBinaryReader reader)
|
||
{
|
||
// Table 11: Format 0
|
||
// Type Name Description
|
||
// Card8 format = 0
|
||
// Card8 nCodes Number of encoded glyphs
|
||
// Card8 code[nCodes] Code array
|
||
//-------
|
||
// Each element of the code array represents the encoding for the
|
||
// corresponding glyph. This format should be used when the
|
||
// codes are in a fairly random order
|
||
|
||
// we have read format field( 1st field) ..
|
||
// so start with 2nd field
|
||
int nCodes = reader.ReadByte();
|
||
byte[] codes = reader.ReadBytes(nCodes);
|
||
|
||
// TODO: Implement based on PDFPig
|
||
}
|
||
|
||
/// <summary>
|
||
/// Reads format 1 encoding data using Range1 structures for sequential code groups.
|
||
/// </summary>
|
||
/// <param name="reader">The binary reader.</param>
|
||
private static void ReadFormat1Encoding(BigEndianBinaryReader reader)
|
||
{
|
||
// Table 12 Format 1
|
||
// Type Name Description
|
||
// Card8 format = 1
|
||
// Card8 nRanges Number of code ranges
|
||
// struct Range1[nRanges] Range1 array(see Table 13)
|
||
//--------------
|
||
int nRanges = reader.ReadByte();
|
||
|
||
// Table 13 Range1 Format(Encoding)
|
||
// Type Name Description
|
||
// Card8 first First code in range
|
||
// Card8 nLeft Codes left in range(excluding first)
|
||
//--------------
|
||
// Each Range1 describes a group of sequential codes. For
|
||
// example, the codes 51 52 53 54 55 could be represented by the
|
||
// Range1: 51 4, and a perfectly ordered encoding of 256 codes can
|
||
// be described with the Range1: 0 255.
|
||
|
||
// This format is particularly suited to encodings that are well ordered.
|
||
|
||
// A few fonts have multiply - encoded glyphs which are not
|
||
// supported directly by any of the above formats. This situation is
|
||
// indicated by setting the high - order bit in the format byte and
|
||
// supplementing the encoding, regardless of format type, as
|
||
// shown in Table 14.
|
||
|
||
// Table 14 Supplemental Encoding Data
|
||
// Type Name Description
|
||
// Card8 nSups Number of supplementary mappings
|
||
// struct Supplement[nSups] Supplementary encoding array(see Table 15 below)
|
||
|
||
// Table 15 Supplement Format
|
||
// Type Name Description
|
||
// Card8 code Encoding
|
||
// SID glyph Name
|
||
}
|
||
|
||
/// <summary>
|
||
/// Reads the Private DICT data containing font-level hinting values and local subroutine references.
|
||
/// </summary>
|
||
/// <param name="reader">The binary reader.</param>
|
||
/// <returns>The parsed <see cref="CffPrivateDictionary"/>, or <see langword="null"/> if no Private DICT is present.</returns>
|
||
private CffPrivateDictionary? ReadPrivateDict(BigEndianBinaryReader reader)
|
||
{
|
||
// per-font
|
||
if (this.privateDICTLength == 0)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
reader.BaseStream.Position = this.offset + this.privateDICTOffset;
|
||
List<CffDataDicEntry> dicData = this.ReadDictData(reader, this.privateDICTLength);
|
||
byte[][] localSubrRawBuffers = [];
|
||
int defaultWidthX = 0;
|
||
int nominalWidthX = 0;
|
||
|
||
if (dicData.Count > 0)
|
||
{
|
||
// Interpret the values of private dict
|
||
foreach (CffDataDicEntry dicEntry in dicData)
|
||
{
|
||
switch (dicEntry.Operator.Name)
|
||
{
|
||
case "Subrs":
|
||
int localSubrsOffset = (int)dicEntry.Operands[0].RealNumValue;
|
||
reader.BaseStream.Position = this.offset + this.privateDICTOffset + localSubrsOffset;
|
||
localSubrRawBuffers = ReadLocalSubrs(reader);
|
||
break;
|
||
|
||
case "defaultWidthX":
|
||
defaultWidthX = (int)dicEntry.Operands[0].RealNumValue;
|
||
break;
|
||
|
||
case "nominalWidthX":
|
||
nominalWidthX = (int)dicEntry.Operands[0].RealNumValue;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
return new CffPrivateDictionary(localSubrRawBuffers, defaultWidthX, nominalWidthX);
|
||
}
|
||
}
|
||
}
|