// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.Fonts.Tables.AdvancedTypographic.Variations;
using System.IO;
namespace SixLabors.Fonts.Tables.Cff {
///
/// Parses a Compact Font Format (CFF) version 2 font program.
///
///
internal class Cff2Parser : CffParserBase
{
private static readonly ItemVariationStore EmptyItemVariationStoreTable = new(VariationRegionList.EmptyVariationRegionList, []);
private long offset;
private double[]? fontMatrix;
private int charStringIndexOffset;
private int variationStoreOffset;
private int? fdArrayOffset;
private int? fdSelectOffset;
private ItemVariationStore? itemVariationStore;
///
/// Loads and parses a CFF2 font from the given reader.
///
/// The binary reader positioned after the CFF2 header.
/// The header size in bytes.
/// The length of the Top DICT data in bytes.
/// The PostScript font name.
/// The absolute offset of the CFF2 table in the font stream.
/// The parsed .
public Cff2Font Load(BigEndianBinaryReader reader, byte hdrSize, ushort topDictLength, string fontName, long offset)
{
this.offset = offset;
reader.Seek(hdrSize, SeekOrigin.Begin);
this.ReadTopDictData(reader, topDictLength);
reader.Seek(hdrSize + topDictLength, SeekOrigin.Begin);
CidFontInfo cidFontInfo = new()
{
FDArray = this.fdArrayOffset.GetValueOrDefault(),
FDSelect = this.fdSelectOffset.GetValueOrDefault(),
};
byte[][] globalSubrRawBuffers = ReadSubrBuffer(reader, cff2: true);
// The Item Variation Store is optional. When present, its offset is
// relative to the start of the CFF2 table.
if (this.variationStoreOffset > 0)
{
reader.Seek(this.variationStoreOffset, SeekOrigin.Begin);
ushort variationStoreLength = reader.ReadUInt16();
this.itemVariationStore = variationStoreLength == 0
? EmptyItemVariationStoreTable
: ItemVariationStore.Load(reader, this.variationStoreOffset + 2);
}
else
{
this.itemVariationStore = EmptyItemVariationStoreTable;
}
if (this.fdSelectOffset.HasValue)
{
ReadFdSelect(reader, this.offset, cidFontInfo);
}
CffIndexOffset[] charStringOffsets = this.ReadCharStringIndex(reader);
byte[][] charStringBuffers = ReadCharStringBuffers(reader, charStringOffsets);
int fdArrayOffset = this.fdArrayOffset.GetValueOrDefault();
FontDict[] fontDicts = this.ReadFdArray(reader, this.offset, fdArrayOffset, cff2: true);
CffTopDictionary topDictionary = new()
{
CidFontInfo = cidFontInfo,
FontMatrix = this.fontMatrix ?? [0.001, 0, 0, 0.001, 0, 0]
};
CffPrivateDictionary privateDictionary = fontDicts.Length > 0
? new(fontDicts[0].LocalSubr, 0, 0)
: new([], 0, 0);
int glyphCount = charStringOffsets.Length;
CffGlyphData[] glyphs = this.ReadCharStringsIndex(topDictionary, globalSubrRawBuffers, fontDicts, privateDictionary, charStringBuffers, glyphCount);
return new(fontName, topDictionary, glyphs, this.itemVariationStore);
}
///
/// Reads the CFF2 Top DICT data, extracting offsets for CharStrings, FDArray, FDSelect, and variation store.
///
/// The binary reader.
/// The length in bytes of the Top DICT data.
private void ReadTopDictData(BigEndianBinaryReader reader, ushort topDictLength)
{
long startPosition = reader.BaseStream.Position;
long maxPosition = startPosition + topDictLength;
while (reader.BaseStream.Position < maxPosition)
{
CffDataDicEntry dataDicEntry = this.ReadEntry(reader);
switch (dataDicEntry.Operator.Name)
{
case "FontMatrix":
this.fontMatrix = new double[dataDicEntry.Operands.Length];
for (int i = 0; i < dataDicEntry.Operands.Length; i++)
{
this.fontMatrix[i] = dataDicEntry.Operands[i].RealNumValue;
}
break;
case "CharStrings":
this.charStringIndexOffset = (int)dataDicEntry.Operands[0].RealNumValue;
break;
case "FDArray":
this.fdArrayOffset = (int)dataDicEntry.Operands[0].RealNumValue;
break;
case "FDSelect":
this.fdSelectOffset = (int)dataDicEntry.Operands[0].RealNumValue;
break;
case "vstore":
this.variationStoreOffset = (int)dataDicEntry.Operands[0].RealNumValue;
break;
default:
throw new InvalidFontFileException("Error parsing TopDictData.");
}
}
}
///
/// Reads the CharString INDEX offsets for CFF2.
///
/// The binary reader.
/// An array of representing each charstring's position and length.
private CffIndexOffset[] ReadCharStringIndex(BigEndianBinaryReader reader)
{
reader.BaseStream.Position = this.offset + this.charStringIndexOffset;
if (!TryReadIndexDataOffsets(reader, out CffIndexOffset[]? offsets, cff2: true))
{
throw new InvalidFontFileException("No glyph data found.");
}
return offsets;
}
///
/// Reads the raw charstring byte buffers for each glyph from the CharString INDEX.
///
/// The binary reader.
/// The charstring INDEX offsets.
/// An array of byte arrays, each containing a glyph's charstring data.
private static byte[][] ReadCharStringBuffers(BigEndianBinaryReader reader, CffIndexOffset[] offsets)
{
int glyphCount = offsets.Length;
byte[][] charStringBuffers = new byte[offsets.Length][];
for (int i = 0; i < glyphCount; ++i)
{
CffIndexOffset cffIndexOffset = offsets[i];
charStringBuffers[i] = reader.ReadBytes(cffIndexOffset.Length);
}
return charStringBuffers;
}
///
/// Creates glyph data objects for all glyphs from the pre-read charstring buffers.
///
/// The top-level dictionary containing font metadata.
/// The global subroutine buffers.
/// The Font DICT array for CID fonts.
/// The private dictionary containing local subroutine references.
/// The raw charstring byte buffers for each glyph.
/// The total number of glyphs.
/// An array of for each glyph.
private CffGlyphData[] ReadCharStringsIndex(
CffTopDictionary topDictionary,
byte[][] globalSubrBuffers,
FontDict[] fontDicts,
CffPrivateDictionary? privateDictionary,
byte[][] charStringBuffers,
int glyphCount)
{
// 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.
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;
int vsIndex = fontDicts.Length > 0 ? fontDicts[0].VsIndex : 0;
for (int i = 0; i < glyphCount; ++i)
{
byte[] charstringsBuffer = charStringBuffers[i];
// Now we can parse the raw glyph instructions
// Select proper local private dict.
if (isCidFont)
{
fdRangeProvider.SetCurrentGlyphIndex((ushort)i);
int fdIndex = fdRangeProvider.SelectedFDArray;
localSubBuffer = fontDicts[fdIndex].LocalSubr;
vsIndex = fontDicts[fdIndex].VsIndex;
}
glyphs[i] = new CffGlyphData(
(ushort)i,
globalSubrBuffers,
localSubBuffer ?? [],
privateDictionary?.NominalWidthX ?? 0,
charstringsBuffer,
2,
this.itemVariationStore,
vsIndex)
{
FontMatrix = topDictionary.FontMatrix
};
}
return glyphs;
}
}
}