Temporary solution to read woff2

This commit is contained in:
ema
2024-12-30 04:21:24 +08:00
parent ffecab95be
commit 4eb4251db5
91 changed files with 34874 additions and 4 deletions

View File

@@ -0,0 +1,63 @@
//Apache2, 2016-present, WinterDev
using System.IO;
namespace Typography.OpenFont.Tables
{
//https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2
//Attachment List Table
//The Attachment List table (AttachList) may be used to cache attachment point coordinates along with glyph bitmaps.
//The table consists of an offset to a Coverage table (Coverage) listing all glyphs that define attachment points in the GPOS table,
//a count of the glyphs with attachment points (GlyphCount), and an array of offsets to AttachPoint tables (AttachPoint).
//The array lists the AttachPoint tables, one for each glyph in the Coverage table, in the same order as the Coverage Index.
//AttachList table
//Type Name Description
//Offset16 Coverage Offset to Coverage table - from beginning of AttachList table
//unint16 GlyphCount Number of glyphs with attachment points
//Offset16 AttachPoint[GlyphCount] Array of offsets to AttachPoint tables-from beginning of AttachList table-in Coverage Index order
//An AttachPoint table consists of a count of the attachment points on a single glyph (PointCount) and
//an array of contour indices of those points (PointIndex), listed in increasing numerical order.
//Example 3 at the end of the chapter demonstrates an AttachList table that defines attachment points for two glyphs.
//AttachPoint table
//Type Name Description
//uint16 PointCount Number of attachment points on this glyph
//uint16 PointIndex[PointCount] Array of contour point indices -in increasing numerical order
class AttachmentListTable
{
AttachPoint[] _attachPoints;
public CoverageTable CoverageTable { get; private set; }
public static AttachmentListTable CreateFrom(BinaryReader reader, long beginAt)
{
AttachmentListTable attachmentListTable = new AttachmentListTable();
reader.BaseStream.Seek(beginAt, SeekOrigin.Begin);
//
ushort coverageOffset = reader.ReadUInt16();
ushort glyphCount = reader.ReadUInt16();
ushort[] attachPointOffsets = Utils.ReadUInt16Array(reader, glyphCount);
//-----------------------
attachmentListTable.CoverageTable = CoverageTable.CreateFrom(reader, beginAt + coverageOffset);
attachmentListTable._attachPoints = new AttachPoint[glyphCount];
for (int i = 0; i < glyphCount; ++i)
{
reader.BaseStream.Seek(beginAt + attachPointOffsets[i], SeekOrigin.Begin);
ushort pointCount = reader.ReadUInt16();
attachmentListTable._attachPoints[i] = new AttachPoint()
{
pointIndices = Utils.ReadUInt16Array(reader, pointCount)
};
}
return attachmentListTable;
}
struct AttachPoint
{
public ushort[] pointIndices;
}
}
}

View File

@@ -0,0 +1,637 @@
//Apache2, 2016-present, WinterDev
//https://docs.microsoft.com/en-us/typography/opentype/spec/base
//BASE - Baseline Table
//The Baseline table (BASE) provides information used to align glyphs of different scripts and sizes in a line of text,
//whether the glyphs are in the same font or in different fonts.
//To improve text layout, the Baseline table also provides minimum (min) and maximum (max) glyph extent values for each script,
//language system, or feature in a font.
using System.IO;
namespace Typography.OpenFont.Tables
{
public class BASE : TableEntry
{
public const string _N = "BASE";
public override string Name => _N;
public AxisTable _horizontalAxis;
public AxisTable _verticalAxis;
protected override void ReadContentFrom(BinaryReader reader)
{
//BASE Header
//The BASE table begins with a header that starts with a version number.
//Two versions are defined.
//Version 1.0 contains offsets to horizontal and vertical Axis tables(HorizAxis and VertAxis).
//Version 1.1 also includes an offset to an Item Variation Store table.
//Each Axis table stores all baseline information and min / max extents for one layout direction.
//The HorizAxis table contains Y values for horizontal text layout;
//the VertAxis table contains X values for vertical text layout.
// A font may supply information for both layout directions.
//If a font has values for only one text direction,
//the Axis table offset value for the other direction will be set to NULL.
//The optional Item Variation Store table is used in variable fonts to provide variation data
//for BASE metric values within the Axis tables.
// BASE Header, Version 1.0
//Type Name Description
//uint16 majorVersion Major version of the BASE table, = 1
//uint16 minorVersion Minor version of the BASE table, = 0
//Offset16 horizAxisOffset Offset to horizontal Axis table, from beginning of BASE table(may be NULL)
//Offset16 vertAxisOffset Offset to vertical Axis table, from beginning of BASE table(may be NULL)
//BASE Header, Version 1.1
//Type Name Description
//uint16 majorVersion Major version of the BASE table, = 1
//uint16 minorVersion Minor version of the BASE table, = 1
//Offset16 horizAxisOffset Offset to horizontal Axis table, from beginning of BASE table(may be NULL)
//Offset16 vertAxisOffset Offset to vertical Axis table, from beginning of BASE table(may be NULL)
//Offset32 itemVarStoreOffset Offset to Item Variation Store table, from beginning of BASE table(may be null)
long tableStartAt = reader.BaseStream.Position;
ushort majorVersion = reader.ReadUInt16();
ushort minorVersion = reader.ReadUInt16();
ushort horizAxisOffset = reader.ReadUInt16();
ushort vertAxisOffset = reader.ReadUInt16();
uint itemVarStoreOffset = 0;
if (minorVersion == 1)
{
itemVarStoreOffset = reader.ReadUInt32();
}
//Axis Tables: HorizAxis and VertAxis
if (horizAxisOffset > 0)
{
reader.BaseStream.Position = tableStartAt + horizAxisOffset;
_horizontalAxis = ReadAxisTable(reader);
_horizontalAxis.isVerticalAxis = false;
}
if (vertAxisOffset > 0)
{
reader.BaseStream.Position = tableStartAt + vertAxisOffset;
_verticalAxis = ReadAxisTable(reader);
_verticalAxis.isVerticalAxis = true;
}
if (itemVarStoreOffset > 0)
{
//TODO
}
}
public class AxisTable
{
public bool isVerticalAxis; //false = horizontal , true= verical axis
public string[] baseTagList;
public BaseScript[] baseScripts;
#if DEBUG
public override string ToString()
{
return isVerticalAxis ? "vertical_axis" : "horizontal_axis";
}
#endif
}
static AxisTable ReadAxisTable(BinaryReader reader)
{
//An Axis table is used to render scripts either horizontally or vertically.
//It consists of offsets, measured from the beginning of the Axis table,
//to a BaseTagList and a BaseScriptList:
//The BaseScriptList enumerates all scripts rendered in the text layout direction.
//The BaseTagList enumerates all baselines used to render the scripts in the text layout direction.
//If no baseline data is available for a text direction,
//the offset to the corresponding BaseTagList may be set to NULL.
//Axis Table
//Type Name Description
//Offset16 baseTagListOffset Offset to BaseTagList table, from beginning of Axis table(may be NULL)
//Offset16 baseScriptListOffset Offset to BaseScriptList table, from beginning of Axis table
long axisTableStartAt = reader.BaseStream.Position;
ushort baseTagListOffset = reader.ReadUInt16();
ushort baseScriptListOffset = reader.ReadUInt16();
AxisTable axisTable = new AxisTable();
if (baseTagListOffset > 0)
{
reader.BaseStream.Position = axisTableStartAt + baseTagListOffset;
axisTable.baseTagList = ReadBaseTagList(reader);
}
if (baseScriptListOffset > 0)
{
reader.BaseStream.Position = axisTableStartAt + baseScriptListOffset;
axisTable.baseScripts = ReadBaseScriptList(reader);
}
return axisTable;
}
static string ConvertToTagString(byte[] iden_tag_bytes)
{
return new string(new char[] {
(char)iden_tag_bytes[0],
(char)iden_tag_bytes[1],
(char)iden_tag_bytes[2],
(char)iden_tag_bytes[3]});
}
static string[] ReadBaseTagList(BinaryReader reader)
{
//BaseTagList Table
//The BaseTagList table identifies the baselines for all scripts in the font that are rendered in the same text direction.
//Each baseline is identified with a 4-byte baseline tag.
//The Baseline Tags section of the OpenType Layout Tag Registry lists currently registered baseline tags.
//The BaseTagList can define any number of baselines, and it may include baseline tags for scripts supported in other fonts.
//Each script in the BaseScriptList table must designate one of these BaseTagList baselines as its default,
//which the OpenType Layout Services use to align all glyphs in the script.
//Even though the BaseScriptList and the BaseTagList are defined independently of one another,
//the BaseTagList typically includes a tag for each different default baseline needed to render the scripts in the layout direction.
//If some scripts use the same default baseline, the BaseTagList needs to list the common baseline tag only once.
//The BaseTagList table consists of an array of baseline identification tags (baselineTags),
//listed alphabetically, and a count of the total number of baseline Tags in the array (baseTagCount).
//BaseTagList table
//Type Name Description
//uint16 baseTagCount Number of baseline identification tags in this text direction — may be zero (0)
//Tag baselineTags[baseTagCount] Array of 4-byte baseline identification tags — must be in alphabetical order
//see baseline tag => https://docs.microsoft.com/en-us/typography/opentype/spec/baselinetags
//Baseline Tag
//'hang'
//Baseline for HorizAxis: The hanging baseline.This is the horizontal line from which syllables seem to hang in Tibetan and other similar scripts.
//Baseline for VertAxis: The hanging baseline, (which now appears vertical) for Tibetan(or some other similar script) characters rotated 90 degrees clockwise,
// for vertical writing mode.
//------
//'icfb'
//HorizAxis: Ideographic character face bottom edge. (See Ideographic Character Face below for usage.)
//VertAxis: Ideographic character face left edge. (See Ideographic Character Face below for usage.)
//--------
//'icft'
//HorizAxis: Ideographic character face top edge. (See Ideographic Character Face below for usage.)
//VertAxis: Ideographic character face right edge. (See Ideographic Character Face below for usage.)
//-----
//'ideo'
//HorizAxis: Ideographic em-box bottom edge. (See Ideographic Em-Box below for usage.)
//VertAxis: Ideographic em-box left edge. If this tag is present in the VertAxis, the value must be set to 0. (See Ideographic Em - Box below for usage.)
//-------
//'idtp'
//HorizAxis: Ideographic em-box top edge baseline. (See Ideographic Em - Box below for usage.)
//VertAxis: Ideographic em-box right edge baseline.
// If this tag is present in the VertAxis,
// the value is strongly recommended to be set to head.unitsPerEm. (See Ideographic Em - Box below for usage.)
//-------
//'math'
//HorizAxis: The baseline about which mathematical characters are centered.
//VertAxis: The baseline about which mathematical characters, when rotated 90 degrees clockwise for vertical writing mode, are centered.
//-------
//'romn'
//HorizAxis: The baseline used by alphabetic scripts such as Latin, Cyrillic and Greek.
//VertAxis: The alphabetic baseline for characters rotated 90 degrees clockwise for vertical writing mode. (This would not apply to alphabetic characters that remain upright in vertical writing mode, since these characters are not rotated.)
ushort baseTagCount = reader.ReadUInt16();
string[] baselineTags = new string[baseTagCount];
for (int i = 0; i < baseTagCount; ++i)
{
baselineTags[i] = ConvertToTagString(reader.ReadBytes(4));
}
return baselineTags;
}
static BaseScript[] ReadBaseScriptList(BinaryReader reader)
{
//BaseScriptList Table
//The BaseScriptList table identifies all scripts in the font that are rendered in the same layout direction.
//If a script is not listed here, then
//the text-processing client will render the script using the layout information specified for the entire font.
//For each script listed in the BaseScriptList table,
//a BaseScriptRecord must be defined that identifies the script and references its layout data.
//BaseScriptRecords are stored in the baseScriptRecords array, ordered alphabetically by the baseScriptTag in each record.
//The baseScriptCount specifies the total number of BaseScriptRecords in the array.
//BaseScriptList table
//Type Name Description
//uint16 baseScriptCount Number of BaseScriptRecords defined
//BaseScriptRecord baseScriptRecords[baseScriptCount] Array of BaseScriptRecords, in alphabetical order by baseScriptTag
long baseScriptListStartAt = reader.BaseStream.Position;
ushort baseScriptCount = reader.ReadUInt16();
BaseScriptRecord[] baseScriptRecord_offsets = new BaseScriptRecord[baseScriptCount];
for (int i = 0; i < baseScriptCount; ++i)
{
//BaseScriptRecord
//A BaseScriptRecord contains a script identification tag (baseScriptTag),
//which must be identical to the ScriptTag used to define the script in the ScriptList of a GSUB or GPOS table.
//Each record also must include an offset to a BaseScript table that defines the baseline and min/max extent data for the script.
//BaseScriptRecord
//Type Name Description
//Tag baseScriptTag 4-byte script identification tag
//Offset16 baseScriptOffset Offset to BaseScript table, from beginning of BaseScriptList
baseScriptRecord_offsets[i] = new BaseScriptRecord(ConvertToTagString(reader.ReadBytes(4)), reader.ReadUInt16());
}
BaseScript[] baseScripts = new BaseScript[baseScriptCount];
for (int i = 0; i < baseScriptCount; ++i)
{
BaseScriptRecord baseScriptRecord = baseScriptRecord_offsets[i];
reader.BaseStream.Position = baseScriptListStartAt + baseScriptRecord.baseScriptOffset;
//
BaseScript baseScipt = ReadBaseScriptTable(reader);
baseScipt.ScriptIdenTag = baseScriptRecord.baseScriptTag;
baseScripts[i] = baseScipt;
}
return baseScripts;
}
readonly struct BaseScriptRecord
{
public readonly string baseScriptTag;
public readonly ushort baseScriptOffset;
public BaseScriptRecord(string scriptTag, ushort offset)
{
this.baseScriptTag = scriptTag;
this.baseScriptOffset = offset;
}
}
public readonly struct BaseLangSysRecord
{
public readonly string baseScriptTag;
public readonly ushort baseScriptOffset;
public BaseLangSysRecord(string scriptTag, ushort offset)
{
this.baseScriptTag = scriptTag;
this.baseScriptOffset = offset;
}
}
public class BaseScript
{
public string ScriptIdenTag;
public BaseValues baseValues;
public BaseLangSysRecord[] baseLangSysRecords;
public MinMax MinMax;
public BaseScript() { }
#if DEBUG
public override string ToString()
{
return ScriptIdenTag;
}
#endif
}
static BaseScript ReadBaseScriptTable(BinaryReader reader)
{
//BaseScript Table
//A BaseScript table organizes and specifies the baseline data and min/max extent data for one script.
//Within a BaseScript table, the BaseValues table contains baseline information,
//and one or more MinMax tables contain min/max extent data
//....
//A BaseScript table has four components:
//...
long baseScriptTableStartAt = reader.BaseStream.Position;
//BaseScript Table
//Type Name Description
//Offset16 baseValuesOffset Offset to BaseValues table, from beginning of BaseScript table (may be NULL)
//Offset16 defaultMinMaxOffset Offset to MinMax table, from beginning of BaseScript table (may be NULL)
//uint16 baseLangSysCount Number of BaseLangSysRecords defined — may be zero (0)
//BaseLangSysRecord baseLangSysRecords[baseLangSysCount] Array of BaseLangSysRecords, in alphabetical order by BaseLangSysTag
ushort baseValueOffset = reader.ReadUInt16();
ushort defaultMinMaxOffset = reader.ReadUInt16();
ushort baseLangSysCount = reader.ReadUInt16();
BaseLangSysRecord[] baseLangSysRecords = null;
if (baseLangSysCount > 0)
{
baseLangSysRecords = new BaseLangSysRecord[baseLangSysCount];
for (int i = 0; i < baseLangSysCount; ++i)
{
//BaseLangSysRecord
//A BaseLangSysRecord defines min/max extents for a language system or a language-specific feature.
//Each record contains an identification tag for the language system (baseLangSysTag) and an offset to a MinMax table (MinMax)
//that defines extent coordinate values for the language system and references feature-specific extent data.
//BaseLangSysRecord
//Type Name Description
//Tag baseLangSysTag 4-byte language system identification tag
//Offset16 minMaxOffset Offset to MinMax table, from beginning of BaseScript table
baseLangSysRecords[i] = new BaseLangSysRecord(ConvertToTagString(reader.ReadBytes(4)), reader.ReadUInt16());
}
}
BaseScript baseScript = new BaseScript();
baseScript.baseLangSysRecords = baseLangSysRecords;
//--------------------
if (baseValueOffset > 0)
{
reader.BaseStream.Position = baseScriptTableStartAt + baseValueOffset;
baseScript.baseValues = ReadBaseValues(reader);
}
if (defaultMinMaxOffset > 0)
{
reader.BaseStream.Position = baseScriptTableStartAt + defaultMinMaxOffset;
baseScript.MinMax = ReadMinMaxTable(reader);
}
return baseScript;
}
static BaseValues ReadBaseValues(BinaryReader reader)
{
//A BaseValues table lists the coordinate positions of all baselines named in the baselineTags array of the corresponding BaseTagList and
//identifies a default baseline for a script.
//...
//
//BaseValues table
//Type Name Description
//uint16 defaultBaselineIndex Index number of default baseline for this script — equals index position of baseline tag in baselineTags array of the BaseTagList
//uint16 baseCoordCount Number of BaseCoord tables defined — should equal baseTagCount in the BaseTagList
//Offset16 baseCoords[baseCoordCount] Array of offsets to BaseCoord tables, from beginning of BaseValues table — order matches baselineTags array in the BaseTagList
long baseValueTableStartAt = reader.BaseStream.Position;
//
ushort defaultBaselineIndex = reader.ReadUInt16();
ushort baseCoordCount = reader.ReadUInt16();
ushort[] baseCoords_Offset = Utils.ReadUInt16Array(reader, baseCoordCount);
BaseCoord[] baseCoords = new BaseCoord[baseCoordCount];
for (int i = 0; i < baseCoordCount; ++i)
{
baseCoords[i] = ReadBaseCoordTable(reader, baseValueTableStartAt + baseCoords_Offset[i]);
}
return new BaseValues(defaultBaselineIndex, baseCoords);
}
public readonly struct BaseValues
{
public readonly ushort defaultBaseLineIndex;
public readonly BaseCoord[] baseCoords;
public BaseValues(ushort defaultBaseLineIndex, BaseCoord[] baseCoords)
{
this.defaultBaseLineIndex = defaultBaseLineIndex;
this.baseCoords = baseCoords;
}
}
public readonly struct BaseCoord
{
public readonly ushort baseCoordFormat;
/// <summary>
/// X or Y value, in design units
/// </summary>
public readonly short coord;
public readonly ushort referenceGlyph; //found in format2
public readonly ushort baseCoordPoint; //found in format2
public BaseCoord(ushort baseCoordFormat, short coord)
{
this.baseCoordFormat = baseCoordFormat;
this.coord = coord;
this.referenceGlyph = this.baseCoordPoint = 0;
}
public BaseCoord(ushort baseCoordFormat, short coord, ushort referenceGlyph, ushort baseCoordPoint)
{
this.baseCoordFormat = baseCoordFormat;
this.coord = coord;
this.referenceGlyph = referenceGlyph;
this.baseCoordPoint = baseCoordPoint;
}
#if DEBUG
public override string ToString()
{
return "format:" + baseCoordFormat + ",coord=" + coord;
}
#endif
}
static BaseCoord ReadBaseCoordTable(BinaryReader reader, long pos)
{
reader.BaseStream.Position = pos;
//BaseCoord Tables
//Within the BASE table, a BaseCoord table defines baseline and min/max extent values.
//Each BaseCoord table defines one X or Y value:
//If defined within the HorizAxis table, then the BaseCoord table contains a Y value.
//If defined within the VertAxis table, then the BaseCoord table contains an X value.
//All values are defined in design units, which typically are scaled and rounded to the nearest integer when scaling the glyphs.
//Values may be negative.
//----------------------
//BaseCoord Format 1
//The first BaseCoord format (BaseCoordFormat1) consists of a format identifier,
//followed by a single design unit coordinate that specifies the BaseCoord value.
//This format has the benefits of small size and simplicity,
//but the BaseCoord value cannot be hinted for fine adjustments at different sizes or device resolutions.
//BaseCoordFormat1 table: Design units only
//Type Name Description
//uint16 baseCoordFormat Format identifier — format = 1
//int16 coordinate X or Y value, in design units
//----------------------
//BaseCoord Format 2
//The second BaseCoord format (BaseCoordFormat2) specifies the BaseCoord value in design units,
//but also supplies a glyph index and a contour point for reference. During font hinting,
//the contour point on the glyph outline may move.
//The points final position after hinting provides the final value for rendering a given font size.
//Note: Glyph positioning operations defined in the GPOS table do not affect the points final position.
//BaseCoordFormat2 table: Design units plus contour point
//Type Name Description
//uint16 baseCoordFormat Format identifier — format = 2
//int16 coordinate X or Y value, in design units
//uint16 referenceGlyph Glyph ID of control glyph
//uint16 baseCoordPoint Index of contour point on the reference glyph
//----------------------
//BaseCoord Format 3
//The third BaseCoord format (BaseCoordFormat3) also specifies the BaseCoord value in design units,
//but, in a non-variable font, it uses a Device table rather than a contour point to adjust the value.
//This format offers the advantage of fine-tuning the BaseCoord value for any font size and device resolution.
//(For more information about Device tables, see the chapter, Common Table Formats.)
//In a variable font, BaseCoordFormat3 must be used to reference variation data
//to adjust the X or Y value for different variation instances, if needed.
//In this case, BaseCoordFormat3 specifies an offset to a VariationIndex table,
//which is a variant of the Device table that is used for referencing variation data.
// Note: While separate VariationIndex table references are required for each Coordinate value that requires variation, two or more values that require the same variation-data values can have offsets that point to the same VariationIndex table, and two or more VariationIndex tables can reference the same variation data entries.
// Note: If no VariationIndex table is used for a particular X or Y value (the offset is zero, or a different BaseCoord format is used), then that value is used for all variation instances.
//BaseCoordFormat3 table: Design units plus Device or VariationIndex table
//Type Name Description
//uint16 baseCoordFormat Format identifier — format = 3
//int16 coordinate X or Y value, in design units
//Offset16 deviceTable Offset to Device table (non-variable font) / Variation Index table (variable font) for X or Y value, from beginning of BaseCoord table (may be NULL).
ushort baseCoordFormat = reader.ReadUInt16();
switch (baseCoordFormat)
{
default: throw new System.NotSupportedException();
case 1:
return new BaseCoord(1,
reader.ReadInt16());//coord
case 2:
return new BaseCoord(2,
reader.ReadInt16(), //coordinate
reader.ReadUInt16(), //referenceGlyph
reader.ReadUInt16()); //baseCoordPoint
case 3:
#if DEBUG
#endif
return new BaseCoord();
// //TODO: implement this...
// break;
}
}
static MinMax ReadMinMaxTable(BinaryReader reader)
{
//The MinMax table specifies extents for scripts and language systems.
//It also contains an array of FeatMinMaxRecords used to define feature-specific extents.
//...
//Text-processing clients should use the following procedure to access the script, language system, and feature-specific extent data:
//Determine script extents in relation to the text content.
//Select language-specific extent values with respect to the language system in use.
//Have the application or user choose feature-specific extent values.
//If no extent values are defined for a language system or for language-specific features,
//use the default min/max extent values for the script.
//MinMax table
//Type Name Description
//Offset16 minCoord Offset to BaseCoord table that defines the minimum extent value, from the beginning of MinMax table (may be NULL)
//Offset16 maxCoord Offset to BaseCoord table that defines maximum extent value, from the beginning of MinMax table (may be NULL)
//uint16 featMinMaxCount Number of FeatMinMaxRecords — may be zero (0)
//FeatMinMaxRecord featMinMaxRecords[featMinMaxCount] Array of FeatMinMaxRecords, in alphabetical order by featureTableTag
//FeatMinMaxRecord
//Type Name Description
//Tag featureTableTag 4 - byte feature identification tag — must match feature tag in FeatureList
//Offset16 minCoord Offset to BaseCoord table that defines the minimum extent value, from beginning of MinMax table(may be NULL)
//Offset16 maxCoord Offset to BaseCoord table that defines the maximum extent value, from beginning of MinMax table(may be NULL)
long startMinMaxTableAt = reader.BaseStream.Position;
//
MinMax minMax = new MinMax();
ushort minCoordOffset = reader.ReadUInt16();
ushort maxCoordOffset = reader.ReadUInt16();
ushort featMinMaxCount = reader.ReadUInt16();
FeatureMinMaxOffset[] minMaxFeatureOffsets = null;
if (featMinMaxCount > 0)
{
minMaxFeatureOffsets = new FeatureMinMaxOffset[featMinMaxCount];
for (int i = 0; i < featMinMaxCount; ++i)
{
minMaxFeatureOffsets[i] = new FeatureMinMaxOffset(
ConvertToTagString(reader.ReadBytes(4)), //featureTableTag
reader.ReadUInt16(), //minCoord offset
reader.ReadUInt16() //maxCoord offset
);
}
}
//----------
if (minCoordOffset > 0)
{
minMax.minCoord = ReadBaseCoordTable(reader, startMinMaxTableAt + minCoordOffset);
}
if (maxCoordOffset > 0)
{
minMax.maxCoord = ReadBaseCoordTable(reader, startMinMaxTableAt + maxCoordOffset);
}
if (minMaxFeatureOffsets != null)
{
var featureMinMaxRecords = new FeatureMinMax[minMaxFeatureOffsets.Length];
for (int i = 0; i < minMaxFeatureOffsets.Length; ++i)
{
FeatureMinMaxOffset featureMinMaxOffset = minMaxFeatureOffsets[i];
featureMinMaxRecords[i] = new FeatureMinMax(
featureMinMaxOffset.featureTableTag, //tag
ReadBaseCoordTable(reader, startMinMaxTableAt + featureMinMaxOffset.minCoord), //min
ReadBaseCoordTable(reader, startMinMaxTableAt + featureMinMaxOffset.maxCoord)); //max
}
minMax.featureMinMaxRecords = featureMinMaxRecords;
}
return minMax;
}
public class MinMax
{
public BaseCoord minCoord;
public BaseCoord maxCoord;
public FeatureMinMax[] featureMinMaxRecords;
}
public readonly struct FeatureMinMax
{
public readonly string featureTableTag;
public readonly BaseCoord minCoord;
public readonly BaseCoord maxCoord;
public FeatureMinMax(string tag, BaseCoord minCoord, BaseCoord maxCoord)
{
featureTableTag = tag;
this.minCoord = minCoord;
this.maxCoord = maxCoord;
}
}
readonly struct FeatureMinMaxOffset
{
public readonly string featureTableTag;
public readonly ushort minCoord;
public readonly ushort maxCoord;
public FeatureMinMaxOffset(string featureTableTag, ushort minCoord, ushort maxCoord)
{
this.featureTableTag = featureTableTag;
this.minCoord = minCoord;
this.maxCoord = maxCoord;
}
}
}
}

View File

@@ -0,0 +1,59 @@
//Apache2, 2017-present Sam Hocevar <sam@hocevar.net>, WinterDev
using System;
using System.Collections.Generic;
using System.IO;
namespace Typography.OpenFont.Tables
{
public class COLR : TableEntry
{
public const string _N = "COLR";
public override string Name => _N;
// Read the COLR table
// https://docs.microsoft.com/en-us/typography/opentype/spec/colr
protected override void ReadContentFrom(BinaryReader reader)
{
long offset = reader.BaseStream.Position;
//Type Name Description
//uint16 version Table version number(starts at 0).
//uint16 numBaseGlyphRecords Number of Base Glyph Records.
//Offset32 baseGlyphRecordsOffset Offset(from beginning of COLR table) to Base Glyph records.
//Offset32 layerRecordsOffset Offset(from beginning of COLR table) to Layer Records.
//uint16 numLayerRecords Number of Layer Records.
ushort version = reader.ReadUInt16();
ushort numBaseGlyphRecords = reader.ReadUInt16();
uint baseGlyphRecordsOffset = reader.ReadUInt32();
uint layerRecordsOffset = reader.ReadUInt32();
ushort numLayerRecords = reader.ReadUInt16();
GlyphLayers = new ushort[numLayerRecords];
GlyphPalettes = new ushort[numLayerRecords];
reader.BaseStream.Seek(offset + baseGlyphRecordsOffset, SeekOrigin.Begin);
for (int i = 0; i < numBaseGlyphRecords; ++i)
{
ushort gid = reader.ReadUInt16();
LayerIndices[gid] = reader.ReadUInt16();
LayerCounts[gid] = reader.ReadUInt16();
}
reader.BaseStream.Seek(offset + layerRecordsOffset, SeekOrigin.Begin);
for (int i = 0; i < GlyphLayers.Length; ++i)
{
GlyphLayers[i] = reader.ReadUInt16();
GlyphPalettes[i] = reader.ReadUInt16();
}
}
public ushort[] GlyphLayers { get; private set; }
public ushort[] GlyphPalettes { get; private set; }
public readonly Dictionary<ushort, ushort> LayerIndices = new Dictionary<ushort, ushort>();
public readonly Dictionary<ushort, ushort> LayerCounts = new Dictionary<ushort, ushort>();
}
}

View File

@@ -0,0 +1,93 @@
//Apache2, 2017-present Sam Hocevar <sam@hocevar.net>, WinterDev
using System.IO;
namespace Typography.OpenFont.Tables
{
public class CPAL : TableEntry
{
public const string _N = "CPAL";
public override string Name => _N;
//
byte[] _colorBGRABuffer;
// Palette Table Header
// Read the CPAL table
// https://docs.microsoft.com/en-us/typography/opentype/spec/cpal
protected override void ReadContentFrom(BinaryReader reader)
{
long beginAt = reader.BaseStream.Position;
//The CPAL table begins with a header that starts with a version number.
//Currently, only versions 0 and 1 are defined.
//CPAL version 0
//The CPAL header version 0 is organized as follows:
//CPAL version 0
//Type Name Description
//uint16 version Table version number (=0).
//uint16 numPaletteEntries Number of palette entries in each palette.
//uint16 numPalettes Number of palettes in the table.
//uint16 numColorRecords Total number of color records, combined for all palettes.
//Offset32 offsetFirstColorRecord Offset from the beginning of CPAL table to the first ColorRecord.
//uint16 colorRecordIndices[numPalettes] Index of each palettes first color record in the combined color record array.
//CPAL version 1
//The CPAL header version 1 adds three additional fields to the end of the table header and is organized as follows:
//CPAL version 1
//Type Name Description
//uint16 version Table version number (=1).
//uint16 numPaletteEntries Number of palette entries in each palette.
//uint16 numPalettes Number of palettes in the table.
//uint16 numColorRecords Total number of color records, combined for all palettes.
//Offset32 offsetFirstColorRecord Offset from the beginning of CPAL table to the first ColorRecord.
//uint16 colorRecordIndices[numPalettes] Index of each palettes first color record in the combined color record array.
//Offset32 offsetPaletteTypeArray Offset from the beginning of CPAL table to the Palette Type Array. Set to 0 if no array is provided.
//Offset32 offsetPaletteLabelArray Offset from the beginning of CPAL table to the Palette Labels Array. Set to 0 if no array is provided.
//Offset32 offsetPaletteEntryLabelArray Offset from the beginning of CPAL table to the Palette Entry Label Array. Set to 0 if no array is provided.
ushort version = reader.ReadUInt16();
ushort numPaletteEntries = reader.ReadUInt16(); // XXX: unused?
ushort numPalettes = reader.ReadUInt16();
ColorCount = reader.ReadUInt16(); //numColorRecords
uint offsetFirstColorRecord = reader.ReadUInt32(); //Offset from the beginning of CPAL table to the first ColorRecord.
Palettes = Utils.ReadUInt16Array(reader, numPalettes); //colorRecordIndices, Index of each palettes first color record in the combined color record array.
#if DEBUG
if (version == 1)
{
//Offset32 offsetPaletteTypeArray Offset from the beginning of CPAL table to the Palette Type Array. Set to 0 if no array is provided.
//Offset32 offsetPaletteLabelArray Offset from the beginning of CPAL table to the Palette Labels Array. Set to 0 if no array is provided.
//Offset32 offsetPaletteEntryLabelArray Offset from the beginning of CPAL table to the Palette Entry Label Array. Set to 0 if no array is provided.
}
#endif
//move to color records
reader.BaseStream.Seek(beginAt + offsetFirstColorRecord, SeekOrigin.Begin);
_colorBGRABuffer = reader.ReadBytes(4 * ColorCount);
}
public ushort[] Palettes { get; private set; }
public ushort ColorCount { get; private set; }
public void GetColor(int colorIndex, out byte r, out byte g, out byte b, out byte a)
{
//Each color record has BGRA values. The color space for these values is sRGB.
//Type Name Description
//uint8 blue Blue value(B0).
//uint8 green Green value(B1).
//uint8 red Red value(B2).
//uint8 alpha Alpha value(B3).
byte[] colorBGRABuffer = _colorBGRABuffer;
int startAt = colorIndex * 4;//bgra
b = colorBGRABuffer[startAt];
g = colorBGRABuffer[startAt + 1];
r = colorBGRABuffer[startAt + 2];
a = colorBGRABuffer[startAt + 3];
}
}
}

View File

@@ -0,0 +1,206 @@
//Apache2, 2016-present, WinterDev
using System;
using System.IO;
namespace Typography.OpenFont.Tables
{
//https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2
//----------------------------
//Class Definition Table
//----------------------------
//
//In OpenType Layout, index values identify glyphs. For efficiency and ease of representation, a font developer can group glyph indices to form glyph classes.
//Class assignments vary in meaning from one lookup subtable to another.
//For example, in the GSUB and GPOS tables, classes are used to describe glyph contexts.
//GDEF tables also use the idea of glyph classes.
//Consider a substitution action that replaces only the lowercase ascender glyphs in a glyph string.
//To more easily describe the appropriate context for the substitution,
//the font developer might divide the font's lowercase glyphs into two classes,
//one that contains the ascenders and one that contains the glyphs without ascenders.
//A font developer can assign any glyph to any class, each identified with an integer called a class value.
//A Class Definition table (ClassDef) groups glyph indices by class,
//beginning with Class 1, then Class 2, and so on.
//All glyphs not assigned to a class fall into Class 0.
//Within a given class definition table, each glyph in the font belongs to exactly one class.
//The ClassDef table can have either of two formats:
//one that assigns a range of consecutive glyph indices to different classes,
//or one that puts groups of consecutive glyph indices into the same class.
//
//
//Class Definition Table Format 1
//
//
//The first class definition format (ClassDefFormat1) specifies
//a range of consecutive glyph indices and a list of corresponding glyph class values.
//This table is useful for assigning each glyph to a different class because
//the glyph indices in each class are not grouped together.
//A ClassDef Format 1 table begins with a format identifier (ClassFormat).
//The range of glyph indices (GlyphIDs) covered by the table is identified by two values: the GlyphID of the first glyph (StartGlyph),
//and the number of consecutive GlyphIDs (including the first one) that will be assigned class values (GlyphCount).
//The ClassValueArray lists the class value assigned to each GlyphID, starting with the class value for StartGlyph and
//following the same order as the GlyphIDs.
//Any glyph not included in the range of covered GlyphIDs automatically belongs to Class 0.
//Example 7 at the end of this chapter uses Format 1 to assign class values to the lowercase, x-height, ascender, and descender glyphs in a font.
//
//----------------------------
//ClassDefFormat1 table: Class array
//----------------------------
//Type Name Description
//uint16 ClassFormat Format identifier-format = 1
//uint16 StartGlyph First glyph ID of the ClassValueArray
//uint16 GlyphCount Size of the ClassValueArray
//uint16 ClassValueArray[GlyphCount] Array of Class Values-one per GlyphID
//----------------------------------
//
//
//Class Definition Table Format 2
//
//
//The second class definition format (ClassDefFormat2) defines multiple groups of glyph indices that belong to the same class.
//Each group consists of a discrete range of glyph indices in consecutive order (ranges cannot overlap).
//The ClassDef Format 2 table contains a format identifier (ClassFormat),
//a count of ClassRangeRecords that define the groups and assign class values (ClassRangeCount),
//and an array of ClassRangeRecords ordered by the GlyphID of the first glyph in each record (ClassRangeRecord).
//Each ClassRangeRecord consists of a Start glyph index, an End glyph index, and a Class value.
//All GlyphIDs in a range, from Start to End inclusive,
//constitute the class identified by the Class value.
//Any glyph not covered by a ClassRangeRecord is assumed to belong to Class 0.
//Example 8 at the end of this chapter uses Format 2 to assign class values to four types of glyphs in the Arabic script.
//---------------------------------------
//ClassDefFormat2 table: Class ranges
//---------------------------------------
//Type Name Description
//uint16 ClassFormat Format identifier-format = 2
//uint16 ClassRangeCount Number of ClassRangeRecords
//struct ClassRangeRecord[ClassRangeCount] Array of ClassRangeRecords-ordered by Start GlyphID
//---------------------------------------
//
//ClassRangeRecord
//---------------------------------------
//Type Name Descriptionc
//uint16 Start First glyph ID in the range
//uint16 End Last glyph ID in the range
//uint16 Class Applied to all glyphs in the range
//---------------------------------------
class ClassDefTable
{
public int Format { get; internal set; }
//----------------
//format 1
public ushort startGlyph;
public ushort[] classValueArray;
//---------------
//format2
public ClassRangeRecord[] records;
public static ClassDefTable CreateFrom(BinaryReader reader, long beginAt)
{
reader.BaseStream.Seek(beginAt, SeekOrigin.Begin);
//---------
ClassDefTable classDefTable = new ClassDefTable();
switch (classDefTable.Format = reader.ReadUInt16())
{
default: throw new OpenFontNotSupportedException();
case 1:
{
classDefTable.startGlyph = reader.ReadUInt16();
ushort glyphCount = reader.ReadUInt16();
classDefTable.classValueArray = Utils.ReadUInt16Array(reader, glyphCount);
}
break;
case 2:
{
ushort classRangeCount = reader.ReadUInt16();
ClassRangeRecord[] records = new ClassRangeRecord[classRangeCount];
for (int i = 0; i < classRangeCount; ++i)
{
records[i] = new ClassRangeRecord(
reader.ReadUInt16(), //start glyph id
reader.ReadUInt16(), //end glyph id
reader.ReadUInt16()); //classNo
}
classDefTable.records = records;
}
break;
}
return classDefTable;
}
internal readonly struct ClassRangeRecord
{
//---------------------------------------
//
//ClassRangeRecord
//---------------------------------------
//Type Name Descriptionc
//uint16 Start First glyph ID in the range
//uint16 End Last glyph ID in the range
//uint16 Class Applied to all glyphs in the range
//---------------------------------------
public readonly ushort startGlyphId;
public readonly ushort endGlyphId;
public readonly ushort classNo;
public ClassRangeRecord(ushort startGlyphId, ushort endGlyphId, ushort classNo)
{
this.startGlyphId = startGlyphId;
this.endGlyphId = endGlyphId;
this.classNo = classNo;
}
#if DEBUG
public override string ToString()
{
return "class=" + classNo + " [" + startGlyphId + "," + endGlyphId + "]";
}
#endif
}
public int GetClassValue(ushort glyphIndex)
{
switch (Format)
{
default: throw new OpenFontNotSupportedException();
case 1:
{
if (glyphIndex >= startGlyph &&
glyphIndex < classValueArray.Length)
{
return classValueArray[glyphIndex - startGlyph];
}
return -1;
}
case 2:
{
for (int i = 0; i < records.Length; ++i)
{
//TODO: review a proper method here again
//esp. binary search
ClassRangeRecord rec = records[i];
if (rec.startGlyphId <= glyphIndex)
{
if (glyphIndex <= rec.endGlyphId)
{
return rec.classNo;
}
}
else
{
return -1;//no need to go further
}
}
return -1;
}
}
}
}
}

View File

@@ -0,0 +1,162 @@
//Apache2, 2016-present, WinterDev
using System;
using System.Collections.Generic;
using System.IO;
namespace Typography.OpenFont.Tables
{
//https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2
public abstract class CoverageTable
{
public abstract int FindPosition(ushort glyphIndex);
public abstract IEnumerable<ushort> GetExpandedValueIter();
#if DEBUG
#endif
public static CoverageTable CreateFrom(BinaryReader reader, long beginAt)
{
reader.BaseStream.Seek(beginAt, SeekOrigin.Begin);
ushort format = reader.ReadUInt16();
switch (format)
{
default: throw new OpenFontNotSupportedException();
case 1: return CoverageFmt1.CreateFrom(reader);
case 2: return CoverageFmt2.CreateFrom(reader);
}
}
public static CoverageTable[] CreateMultipleCoverageTables(long initPos, ushort[] offsets, BinaryReader reader)
{
CoverageTable[] results = new CoverageTable[offsets.Length];
for (int i = 0; i < results.Length; ++i)
{
results[i] = CoverageTable.CreateFrom(reader, initPos + offsets[i]);
}
return results;
}
}
public class CoverageFmt1 : CoverageTable
{
public static CoverageFmt1 CreateFrom(BinaryReader reader)
{
// CoverageFormat1 table: Individual glyph indices
// Type Name Description
// uint16 CoverageFormat Format identifier-format = 1
// uint16 GlyphCount Number of glyphs in the GlyphArray
// uint16 GlyphArray[GlyphCount] Array of glyph IDs — in numerical order
ushort glyphCount = reader.ReadUInt16();
ushort[] glyphs = Utils.ReadUInt16Array(reader, glyphCount);
return new CoverageFmt1() { _orderedGlyphIdList = glyphs };
}
public override int FindPosition(ushort glyphIndex)
{
// "The glyph indices must be in numerical order for binary searching of the list"
// (https://www.microsoft.com/typography/otspec/chapter2.htm#coverageFormat1)
int n = Array.BinarySearch(_orderedGlyphIdList, glyphIndex);
return n < 0 ? -1 : n;
}
public override IEnumerable<ushort> GetExpandedValueIter() { return _orderedGlyphIdList; }
#if DEBUG
public override string ToString()
{
List<string> stringList = new List<string>();
foreach (ushort g in _orderedGlyphIdList)
{
stringList.Add(g.ToString());
}
return "CoverageFmt1: " + string.Join(",", stringList.ToArray());
}
#endif
internal ushort[] _orderedGlyphIdList;
}
public class CoverageFmt2 : CoverageTable
{
public override int FindPosition(ushort glyphIndex)
{
// Ranges must be in glyph ID order, and they must be distinct, with no overlapping.
// [...] quick calculation of the Coverage Index for any glyph in any range using the
// formula: Coverage Index (glyphID) = startCoverageIndex + glyphID - startGlyphID.
// (https://www.microsoft.com/typography/otspec/chapter2.htm#coverageFormat2)
int n = Array.BinarySearch(_endIndices, glyphIndex);
n = n < 0 ? ~n : n;
if (n >= RangeCount || glyphIndex < _startIndices[n])
{
return -1;
}
return _coverageIndices[n] + glyphIndex - _startIndices[n];
}
public override IEnumerable<ushort> GetExpandedValueIter()
{
for (int i = 0; i < RangeCount; ++i)
{
for (ushort n = _startIndices[i]; n <= _endIndices[i]; ++n)
{
yield return n;
}
}
}
public static CoverageFmt2 CreateFrom(BinaryReader reader)
{
// CoverageFormat2 table: Range of glyphs
// Type Name Description
// uint16 CoverageFormat Format identifier-format = 2
// uint16 RangeCount Number of RangeRecords
// struct RangeRecord[RangeCount] Array of glyph ranges — ordered by StartGlyphID.
//
// RangeRecord
// Type Name Description
// uint16 StartGlyphID First glyph ID in the range
// uint16 EndGlyphID Last glyph ID in the range
// uint16 StartCoverageIndex Coverage Index of first glyph ID in range
ushort rangeCount = reader.ReadUInt16();
ushort[] startIndices = new ushort[rangeCount];
ushort[] endIndices = new ushort[rangeCount];
ushort[] coverageIndices = new ushort[rangeCount];
for (int i = 0; i < rangeCount; ++i)
{
startIndices[i] = reader.ReadUInt16();
endIndices[i] = reader.ReadUInt16();
coverageIndices[i] = reader.ReadUInt16();
}
return new CoverageFmt2()
{
_startIndices = startIndices,
_endIndices = endIndices,
_coverageIndices = coverageIndices
};
}
#if DEBUG
public override string ToString()
{
List<string> stringList = new List<string>();
for (int i = 0; i < RangeCount; ++i)
{
stringList.Add(string.Format("{0}-{1}", _startIndices[i], _endIndices[i]));
}
return "CoverageFmt2: " + string.Join(",", stringList.ToArray());
}
#endif
internal ushort[] _startIndices;
internal ushort[] _endIndices;
internal ushort[] _coverageIndices;
private int RangeCount => _startIndices.Length;
}
}

View File

@@ -0,0 +1,195 @@
//Apache2, 2016-present, WinterDev
using System.Collections.Generic;
namespace Typography.OpenFont
{
//https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist
public sealed class FeatureInfo
{
public readonly string fullname;
public readonly string shortname;
public FeatureInfo(string fullname, string shortname)
{
this.fullname = fullname;
this.shortname = shortname;
}
}
public static class Features
{
static readonly Dictionary<string, FeatureInfo> s_features = new Dictionary<string, FeatureInfo>();
//
public static readonly FeatureInfo
aalt = _("aalt", "Access All Alternates"),
abvf = _("abvf", "Above-base Forms"),
abvm = _("abvm", "Above-base Mark Positioning"),
abvs = _("abvs", "Above-base Substitutions"),
afrc = _("afrc", "Alternative Fractions"),
akhn = _("akhn", "Akhands"),
//
blwf = _("blwf", "Below-base Forms"),
blwm = _("blwm", "Below-base Mark Positioning"),
blws = _("blws", "Below-base Substitutions"),
//
calt = _("calt", "Access All Alternates"),
case_ = _("case", "Above-base Forms"),
ccmp = _("ccmp", "Glyph Composition / Decomposition"),
cfar = _("cfar", "Conjunct Form After Ro"),
cjct = _("cjct", "Conjunct Forms"),
clig = _("clig", "Contextual Ligatures"),
cpct = _("cpct", "Centered CJK Punctuation"),
cpsp = _("cpsp", "Capital Spacing"),
cswh = _("cswh", "Contextual Swash"),
curs = _("curs", "Cursive Positioning"),
c2pc = _("c2pc", "Petite Capitals From Capitals"),
c2sc = _("c2sc", "Small Capitals From Capitals"),
//
dist = _("dist", "Distances"),
dlig = _("dlig", "Discretionary Ligatures"),
dnom = _("dnom", "Denominators"),
dtls = _("dtls", "Dotless Forms"),
//
expt = _("expt", "Expert Forms"),
//
falt = _("falt", "Final Glyph on Line Alternates"),
fin2 = _("fin2", "Terminal Forms #2"),
fin3 = _("fin3", "Terminal Forms #3"),
fina = _("fina", "Terminal Forms"),
flac = _("flac", "Flattened accent forms"),
frac = _("frac", "Fractions"),
fwid = _("fwid", "Full Widths"),
//
half = _("half", "Half Forms"),
haln = _("haln", "Halant Forms"),
halt = _("halt", "Alternate Half Widths"),
hist = _("hist", "Historical Forms"),
hkna = _("hkna", "Horizontal Kana Alternates"),
hlig = _("hlig", "Historical Ligatures"),
hngl = _("hngl", "Hangul"),
hojo = _("hojo", "Hojo Kanji Forms (JIS X 0212-1990 Kanji Forms)"),
hwid = _("hwid", "Half Widths"),
//
init = _("init", "Initial Forms"),
isol = _("isol", "Isolated Forms"),
ital = _("ital", "Italics"),
jalt = _("jalt", "Justification Alternates"),
jp78 = _("jp78", "JIS78 Forms"),
jp83 = _("jp83", "JIS83 Forms"),
jp90 = _("jp90", "JIS90 Forms"),
jp04 = _("jp04", "JIS2004 Forms"),
//
kern = _("kern", "Kerning"),
//
lfbd = _("lfbd", "Left Bounds"),
liga = _("liga", "Standard Ligatures"),
ljmo = _("ljmo", "Leading Jamo Forms"),
lnum = _("lnum", "Lining Figures"),
locl = _("locl", "Localized Forms"),
ltra = _("ltra", "Left-to-right alternates"),
ltrm = _("ltrm", "Left-to-right mirrored forms"),
//
mark = _("mark", "Mark Positioning"),
med2 = _("med2", "Medial Forms #2"),
medi = _("medi", "Medial Forms"),
mgrk = _("mgrk", "Mathematical Greek"),
mkmk = _("mkmk", "Mark to Mark Positioning"),
mset = _("mset", "Mark Positioning via Substitution"),
//
nalt = _("nalt", "Alternate Annotation Forms"),
nlck = _("nlck", "NLC Kanji Forms"),
nukt = _("nukt", "Nukta Forms"),
numr = _("numr", "Numerators"),
//
onum = _("onum", "Oldstyle Figures"),
opbd = _("opbd", "Optical Bounds"),
ordn = _("ordn", "Ordinals"),
ornm = _("ornm", "Ornaments"),
//
palt = _("palt", "Proportional Alternate Widths"),
pcap = _("pcap", "Petite Capitals"),
pkna = _("pkna", "Proportional Kana"),
pnum = _("pnum", "Proportional Figures"),
pref = _("pref", "Pre-Base Forms"),
pres = _("pres", "Pre-base Substitutions"),
pstf = _("pstf", "Post-base Forms"),
psts = _("psts", "Post-base Substitutions"),
pwid = _("pwid", "Proportional Widths"),
//
qwid = _("qwid", "Quarter Widths"),
//
rand = _("rand", "Randomize"),
rclt = _("rclt", "Required Contextual Alternates"),
rkrf = _("rkrf", "Rakar Forms"),
rlig = _("rlig", "Required Ligatures"),
rphf = _("rphf", "Reph Forms"),
rtbd = _("rtbd", "Right Bounds"),
rtla = _("rtla", "Right-to-left alternates"),
rtlm = _("rtlm", "Right-to-left mirrored forms"),
ruby = _("ruby", "Ruby Notation Forms"),
rvrn = _("rvrn", "Required Variation Alternates"),
//
salt = _("salt", "Stylistic Alternates"),
sinf = _("sinf", "Scientific Inferiors"),
size = _("size", "Optical size"),
smcp = _("smcp", "Small Capitals"),
smpl = _("smpl", "Simplified Forms"),
ssty = _("ssty", "Math script style alternates"),
stch = _("stch", "Stretching Glyph Decomposition"),
subs = _("subs", "Subscript"),
sups = _("sups", "Superscript"),
swsh = _("swsh", "Swash"),
//
titl = _("titl", "Titling"),
tjmo = _("tjmo", "Trailing Jamo Forms"),
tnam = _("tnam", "Traditional Name Forms"),
tnum = _("tnum", "Tabular Figures"),
trad = _("trad", "Traditional Forms"),
twid = _("twid", "Third Widths"),
//
unic = _("unic", "Unicase"),
//
valt = _("valt", "Alternate Vertical Metrics"),
vatu = _("vatu", "Vattu Variants"),
vert = _("vert", "Vertical Writing"),
vhal = _("vhal", "Alternate Vertical Half Metrics"),
vjmo = _("vjmo", "Vowel Jamo Forms"),
vkna = _("vkna", "Vertical Kana Alternates"),
vkrn = _("vkrn", "Vertical Kerning"),
vpal = _("vpal", "Proportional Alternate Vertical Metrics"),
vrt2 = _("vrt2", "Vertical Alternates and Rotation"),
vrtr = _("vrtr", "Vertical Alternates for Rotation")
;
static Features()
{
//
for (int i = 1; i < 9; ++i)
{
_("cv0" + i, "Character Variants" + i);
}
for (int i = 10; i < 100; ++i)
{
_("cv" + i, "Character Variants" + i);
}
//
for (int i = 1; i < 9; ++i)
{
_("ss0" + i, "Stylistic Set " + i);
}
for (int i = 10; i < 21; ++i)
{
_("ss" + i, "Stylistic Set " + i);
}
}
static FeatureInfo _(string shortname, string fullname)
{
var featureInfo = new FeatureInfo(fullname, shortname);
s_features.Add(shortname, featureInfo);
return featureInfo;
}
}
}

View File

@@ -0,0 +1,148 @@
//Apache2, 2016-present, WinterDev
using System.IO;
namespace Typography.OpenFont.Tables
{
//https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist
//The order for applying standard features encoded in OpenType fonts:
//Feature Feature function Layout operation Required
//---------------------
//Language based forms:
//---------------------
//ccmp Character composition/decomposition substitution GSUB
//---------------------
//Typographical forms:
//---------------------
//liga Standard ligature substitution GSUB
//clig Contextual ligature substitution GSUB
//Positioning features:
//kern Pair kerning GPOS
//mark Mark to base positioning GPOS X
//mkmk Mark to mark positioning GPOS X
//[GSUB = glyph substitution, GPOS = glyph positioning]
public class FeatureList
{
public FeatureTable[] featureTables;
public static FeatureList CreateFrom(BinaryReader reader, long beginAt)
{
//https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2
//------------------
//FeatureList table
//------------------
//Type Name Description
//uint16 FeatureCount Number of FeatureRecords in this table
//struct FeatureRecord[FeatureCount] Array of FeatureRecords-zero-based (first feature has FeatureIndex = 0)-listed alphabetically by FeatureTag
//------------------
//FeatureRecord
//------------------
//Type Name Description
//Tag FeatureTag 4-byte feature identification tag
//Offset16 Feature Offset to Feature table-from beginning of FeatureList
//----------------------------------------------------
reader.BaseStream.Seek(beginAt, SeekOrigin.Begin);
//
FeatureList featureList = new FeatureList();
ushort featureCount = reader.ReadUInt16();
FeatureRecord[] featureRecords = new FeatureRecord[featureCount];
for (int i = 0; i < featureCount; ++i)
{
//read script record
featureRecords[i] = new FeatureRecord(
reader.ReadUInt32(), //feature tag
reader.ReadUInt16()); //Offset16
}
//read each feature table
FeatureTable[] featureTables = featureList.featureTables = new FeatureTable[featureCount];
for (int i = 0; i < featureCount; ++i)
{
FeatureRecord frecord = featureRecords[i];
(featureTables[i] = FeatureTable.CreateFrom(reader, beginAt + frecord.offset)).FeatureTag = frecord.featureTag;
}
return featureList;
}
readonly struct FeatureRecord
{
public readonly uint featureTag;//4-byte ScriptTag identifier
public readonly ushort offset; //Script Offset to Script table-from beginning of ScriptList
public FeatureRecord(uint featureTag, ushort offset)
{
this.featureTag = featureTag;
this.offset = offset;
}
public string FeatureName => Utils.TagToString(featureTag);
#if DEBUG
public override string ToString()
{
return FeatureName + "," + offset;
}
#endif
}
//Feature Table
//A Feature table defines a feature with one or more lookups.
//The client uses the lookups to substitute or position glyphs.
//Feature tables defined within the GSUB table contain references to glyph substitution lookups,
//and feature tables defined within the GPOS table contain references to glyph positioning lookups.
//If a text-processing operation requires both glyph substitution and positioning,
//then both the GSUB and GPOS tables must each define a Feature table,
//and the tables must use the same FeatureTags.
//A Feature table consists of an offset to a Feature Parameters (FeatureParams) table
//(if one has been defined for this feature - see note in the following paragraph),
//a count of the lookups listed for the feature (LookupCount),
//and an arbitrarily ordered array of indices into a LookupList (LookupListIndex).
//The LookupList indices are references into an array of offsets to Lookup tables.
//The format of the Feature Parameters table is specific to a particular feature,
//and must be specified in the feature's entry in the Feature Tags section of the OpenType Layout Tag Registry.
//The length of the Feature Parameters table must be implicitly or explicitly specified in the Feature Parameters table itself.
//The FeatureParams field in the Feature Table records the offset relative to the beginning of the Feature Table.
//If a Feature Parameters table is not needed, the FeatureParams field must be set to NULL.
//To identify the features in a GSUB or GPOS table,
//a text-processing client reads the FeatureTag of each FeatureRecord referenced in a given LangSys table.
//Then the client selects the features it wants to implement and uses the LookupList to retrieve the Lookup indices of the chosen features.
//Next, the client arranges the indices in the LookupList order.
//Finally, the client applies the lookup data to substitute or position glyphs.
//Example 3 at the end of this chapter shows the FeatureList and Feature tables used to substitute ligatures in two languages.
//
public class FeatureTable
{
public static FeatureTable CreateFrom(BinaryReader reader, long beginAt)
{
reader.BaseStream.Seek(beginAt, SeekOrigin.Begin);
//
ushort featureParams = reader.ReadUInt16();
ushort lookupCount = reader.ReadUInt16();
FeatureTable featureTable = new FeatureTable();
featureTable.LookupListIndices = Utils.ReadUInt16Array(reader, lookupCount);
return featureTable;
}
public ushort[] LookupListIndices { get; private set; }
public uint FeatureTag { get; set; }
public string TagName => Utils.TagToString(this.FeatureTag);
#if DEBUG
public override string ToString()
{
return this.TagName;
}
#endif
}
}
}

View File

@@ -0,0 +1,328 @@
//Apache2, 2016-present, WinterDev
using System;
using System.IO;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//from https://www.microsoft.com/typography/developers/opentype/detail.htm
//GDEF Table
//As discussed in Part One,
//the most important tables for glyph processing are GSUB and GPOS,
//but both these tables make use of data in the Glyph Definition table.
//The GDEF table contains three kinds of information in subtables:
//1. glyph class definitions that classify different types of glyphs in a font;
//2. attachment point lists that identify glyph positioning attachments for each glyph;
//and 3. ligature caret lists that provide information for caret positioning and text selection involving ligatures.
//The Glyph Class Definition subtable identifies
//four glyph classes:
//1. simple glyphs,
//2. ligature glyphs (glyphs representing two or more glyph components),
//3. combining mark glyphs (glyphs that combine with other classes),
//and 4. glyph components (glyphs that represent individual parts of ligature glyphs).
//These classes are used by both GSUB and GPOS to differentiate glyphs in a string; for example,
//to distinguish between a base vowel (simple glyph)
//and the accent (combining mark glyph) that a GPOS feature will position above it.
//The Attachment Point List
//identifies all the glyph attachment points defined in the GPOS table.
//Clients that access this information in the GDEF table can cache attachment coordinates with the rasterized glyph bitmaps,
//and avoid having to recalculate the attachment points each time they display a glyph.
//Without this table,
//GPOS features could still be enabled,
//but processing speed would be slower because the client would need to decode the GPOS lookups
//that define the attachment points and compile its own list.
//The Ligature Caret List
//defines the positions for the caret to occupy in ligatures.
//This information, which can be fine tuned for particular bitmap sizes,
//makes it possible for the caret to step across the component characters of a ligature, and for the user to select text including parts of ligatures.
//In the example on the left, below, the caret is positioned between two components of a ligature; on the right, text is selected from within a ligature.
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//https://docs.microsoft.com/en-us/typography/opentype/spec/gdef
//GDEF — Glyph Definition Table
//The Glyph Definition (GDEF) table contains six types of information in six independent tables:
// The GlyphClassDef table classifies the different types of glyphs in the font.
// The AttachmentList table identifies all attachment points on the glyphs, which streamlines data access and bitmap caching.
// The LigatureCaretList table contains positioning data for ligature carets, which the text-processing client uses on screen to select and highlight the individual components of a ligature glyph.
// The MarkAttachClassDef table classifies mark glyphs, to help group together marks that are positioned similarly.
// The MarkGlyphSetsTable allows the enumeration of an arbitrary number of glyph sets that can be used as an extension of the mark attachment class definition to allow lookups to filter mark glyphs by arbitrary sets of marks.
// The ItemVariationStore table is used in variable fonts to contain variation data used for adjustment of values in the GDEF, GPOS or JSTF tables.
//The GSUB and GPOS tables may reference certain GDEF table information used for processing of lookup tables. See, for example, the LookupFlag bit enumeration in “OpenType Layout Common Table Formats”.
//In variable fonts, the GDEF, GPOS and JSTF tables may all reference variation data within the ItemVariationStore table contained within the GDEF table. See below for further discussion of variable fonts and the ItemVariationStore table.
///////////////////////////////////////////////////////////////////////////////
namespace Typography.OpenFont.Tables
{
class GDEF : TableEntry
{
public const string _N = "GDEF";
public override string Name => _N;
//
long _tableStartAt;
protected override void ReadContentFrom(BinaryReader reader)
{
_tableStartAt = reader.BaseStream.Position;
//-----------------------------------------
//GDEF Header, Version 1.0
//Type Name Description
//uint16 MajorVersion Major version of the GDEF table, = 1
//uint16 MinorVersion Minor version of the GDEF table, = 0
//Offset16 GlyphClassDef Offset to class definition table for glyph type, from beginning of GDEF header (may be NULL)
//Offset16 AttachList Offset to list of glyphs with attachment points, from beginning of GDEF header (may be NULL)
//Offset16 LigCaretList Offset to list of positioning points for ligature carets, from beginning of GDEF header (may be NULL)
//Offset16 MarkAttachClassDef Offset to class definition table for mark attachment type, from beginning of GDEF header (may be NULL)
//
//GDEF Header, Version 1.2
//Type Name Description
//uint16 MajorVersion Major version of the GDEF table, = 1
//uint16 MinorVersion Minor version of the GDEF table, = 2
//Offset16 GlyphClassDef Offset to class definition table for glyph type, from beginning of GDEF header (may be NULL)
//Offset16 AttachList Offset to list of glyphs with attachment points, from beginning of GDEF header (may be NULL)
//Offset16 LigCaretList Offset to list of positioning points for ligature carets, from beginning of GDEF header (may be NULL)
//Offset16 MarkAttachClassDef Offset to class definition table for mark attachment type, from beginning of GDEF header (may be NULL)
//Offset16 MarkGlyphSetsDef Offset to the table of mark set definitions, from beginning of GDEF header (may be NULL)
//
//GDEF Header, Version 1.3
//Type Name Description
//uint16 MajorVersion Major version of the GDEF table, = 1
//uint16 MinorVersion Minor version of the GDEF table, = 3
//Offset16 GlyphClassDef Offset to class definition table for glyph type, from beginning of GDEF header (may be NULL)
//Offset16 AttachList Offset to list of glyphs with attachment points, from beginning of GDEF header (may be NULL)
//Offset16 LigCaretList Offset to list of positioning points for ligature carets, from beginning of GDEF header (may be NULL)
//Offset16 MarkAttachClassDef Offset to class definition table for mark attachment type, from beginning of GDEF header (may be NULL)
//Offset16 MarkGlyphSetsDef Offset to the table of mark set definitions, from beginning of GDEF header (may be NULL)
//Offset32 ItemVarStore Offset to the Item Variation Store table, from beginning of GDEF header (may be NULL)
//common to 1.0, 1.2, 1.3...
this.MajorVersion = reader.ReadUInt16();
this.MinorVersion = reader.ReadUInt16();
//
ushort glyphClassDefOffset = reader.ReadUInt16();
ushort attachListOffset = reader.ReadUInt16();
ushort ligCaretListOffset = reader.ReadUInt16();
ushort markAttachClassDefOffset = reader.ReadUInt16();
ushort markGlyphSetsDefOffset = 0;
uint itemVarStoreOffset = 0;
//
switch (MinorVersion)
{
default:
Utils.WarnUnimplemented("GDEF Minor Version {0}", MinorVersion);
return;
case 0:
break;
case 2:
markGlyphSetsDefOffset = reader.ReadUInt16();
break;
case 3:
markGlyphSetsDefOffset = reader.ReadUInt16();
itemVarStoreOffset = reader.ReadUInt32();
break;
}
//---------------
this.GlyphClassDef = (glyphClassDefOffset == 0) ? null : ClassDefTable.CreateFrom(reader, _tableStartAt + glyphClassDefOffset);
this.AttachmentListTable = (attachListOffset == 0) ? null : AttachmentListTable.CreateFrom(reader, _tableStartAt + attachListOffset);
this.LigCaretList = (ligCaretListOffset == 0) ? null : LigCaretList.CreateFrom(reader, _tableStartAt + ligCaretListOffset);
//A Mark Attachment Class Definition Table defines the class to which a mark glyph may belong.
//This table uses the same format as the Class Definition table (for details, see the chapter, Common Table Formats ).
#if DEBUG
if (markAttachClassDefOffset == 2)
{
//temp debug invalid font
this.MarkAttachmentClassDef = (markAttachClassDefOffset == 0) ? null : ClassDefTable.CreateFrom(reader, reader.BaseStream.Position);
}
else
{
this.MarkAttachmentClassDef = (markAttachClassDefOffset == 0) ? null : ClassDefTable.CreateFrom(reader, _tableStartAt + markAttachClassDefOffset);
}
#else
this.MarkAttachmentClassDef = (markAttachClassDefOffset == 0) ? null : ClassDefTable.CreateFrom(reader, _tableStartAt + markAttachClassDefOffset);
#endif
this.MarkGlyphSetsTable = (markGlyphSetsDefOffset == 0) ? null : MarkGlyphSetsTable.CreateFrom(reader, _tableStartAt + markGlyphSetsDefOffset);
if (itemVarStoreOffset != 0)
{
//not supported
Utils.WarnUnimplemented("GDEF ItemVarStore");
reader.BaseStream.Seek(this.Header.Offset + itemVarStoreOffset, SeekOrigin.Begin);
}
}
public int MajorVersion { get; private set; }
public int MinorVersion { get; private set; }
public ClassDefTable GlyphClassDef { get; private set; }
public AttachmentListTable AttachmentListTable { get; private set; }
public LigCaretList LigCaretList { get; private set; }
public ClassDefTable MarkAttachmentClassDef { get; private set; }
public MarkGlyphSetsTable MarkGlyphSetsTable { get; private set; }
//------------------------
/// <summary>
/// fill gdef to each glyphs
/// </summary>
/// <param name="inputGlyphs"></param>
public void FillGlyphData(Glyph[] inputGlyphs)
{
//1.
FillClassDefs(inputGlyphs);
//2.
FillAttachPoints(inputGlyphs);
//3.
FillLigatureCarets(inputGlyphs);
//4.
FillMarkAttachmentClassDefs(inputGlyphs);
//5.
FillMarkGlyphSets(inputGlyphs);
}
void FillClassDefs(Glyph[] inputGlyphs)
{
//1. glyph def
ClassDefTable classDef = GlyphClassDef;
if (classDef == null) return;
//-----------------------------------------
switch (classDef.Format)
{
default:
Utils.WarnUnimplemented("GDEF GlyphClassDef Format {0}", classDef.Format);
break;
case 1:
{
ushort startGlyph = classDef.startGlyph;
ushort[] classValues = classDef.classValueArray;
int gIndex = startGlyph;
for (int i = 0; i < classValues.Length; ++i)
{
#if DEBUG
ushort classV = classValues[i];
if (classV > (ushort)GlyphClassKind.Component)
{
}
#endif
inputGlyphs[gIndex].GlyphClass = (GlyphClassKind)classValues[i];
gIndex++;
}
}
break;
case 2:
{
ClassDefTable.ClassRangeRecord[] records = classDef.records;
for (int n = 0; n < records.Length; ++n)
{
ClassDefTable.ClassRangeRecord rec = records[n];
#if DEBUG
if (rec.classNo > (ushort)GlyphClassKind.Component)
{
}
#endif
GlyphClassKind glyphKind = (GlyphClassKind)rec.classNo;
for (int i = rec.startGlyphId; i <= rec.endGlyphId; ++i)
{
inputGlyphs[i].GlyphClass = glyphKind;
}
}
}
break;
}
}
void FillAttachPoints(Glyph[] inputGlyphs)
{
AttachmentListTable attachmentListTable = this.AttachmentListTable;
if (attachmentListTable == null) { return; }
//-----------------------------------------
Utils.WarnUnimplemented("please implement GDEF.FillAttachPoints()");
}
void FillLigatureCarets(Glyph[] inputGlyphs)
{
//Console.WriteLine("please implement FillLigatureCarets()");
}
void FillMarkAttachmentClassDefs(Glyph[] inputGlyphs)
{
//Mark Attachment Class Definition Table
//A Mark Class Definition Table is used to assign mark glyphs into different classes
//that can be used in lookup tables within the GSUB or GPOS table to control how mark glyphs within a glyph sequence are treated by lookups.
//For more information on the use of mark attachment classes,
//see the description of lookup flags in the “Lookup Table” section of the chapter, OpenType Layout Common Table Formats.
ClassDefTable markAttachmentClassDef = this.MarkAttachmentClassDef;
if (markAttachmentClassDef == null) return;
//-----------------------------------------
switch (markAttachmentClassDef.Format)
{
default:
Utils.WarnUnimplemented("GDEF MarkAttachmentClassDef Table Format {0}", markAttachmentClassDef.Format);
break;
case 1:
{
ushort startGlyph = markAttachmentClassDef.startGlyph;
ushort[] classValues = markAttachmentClassDef.classValueArray;
int len = classValues.Length;
int gIndex = startGlyph;
for (int i = 0; i < len; ++i)
{
#if DEBUG
Glyph dbugTestGlyph = inputGlyphs[gIndex];
#endif
inputGlyphs[gIndex].MarkClassDef = classValues[i];
gIndex++;
}
}
break;
case 2:
{
ClassDefTable.ClassRangeRecord[] records = markAttachmentClassDef.records;
int len = records.Length;
for (int n = 0; n < len; ++n)
{
ClassDefTable.ClassRangeRecord rec = records[n];
for (int i = rec.startGlyphId; i <= rec.endGlyphId; ++i)
{
#if DEBUG
Glyph dbugTestGlyph = inputGlyphs[i];
#endif
inputGlyphs[i].MarkClassDef = rec.classNo;
}
}
}
break;
}
}
void FillMarkGlyphSets(Glyph[] inputGlyphs)
{
//Mark Glyph Sets Table
//A Mark Glyph Sets table is used to define sets of mark glyphs that can be used in lookup tables within the GSUB or GPOS table to control
//how mark glyphs within a glyph sequence are treated by lookups. For more information on the use of mark glyph sets,
//see the description of lookup flags in the “Lookup Table” section of the chapter, OpenType Layout Common Table Formats.
MarkGlyphSetsTable markGlyphSets = this.MarkGlyphSetsTable;
if (markGlyphSets == null) return;
//-----------------------------------------
Utils.WarnUnimplemented("please implement GDEF.FillMarkGlyphSets()");
}
}
}

View File

@@ -0,0 +1,888 @@
//Apache2, 2016-present, WinterDev
using System;
using System.IO;
using System.Text;
//https://docs.microsoft.com/en-us/typography/opentype/spec/gpos
namespace Typography.OpenFont.Tables
{
partial class GPOS
{
static PosRuleSetTable[] CreateMultiplePosRuleSetTables(long initPos, ushort[] offsets, BinaryReader reader)
{
int j = offsets.Length;
PosRuleSetTable[] results = new PosRuleSetTable[j];
for (int i = 0; i < j; ++i)
{
results[i] = PosRuleSetTable.CreateFrom(reader, initPos + offsets[i]);
}
return results;
}
static PosLookupRecord[] CreateMultiplePosLookupRecords(BinaryReader reader, int count)
{
PosLookupRecord[] results = new PosLookupRecord[count];
for (int n = 0; n < count; ++n)
{
results[n] = PosLookupRecord.CreateFrom(reader);
}
return results;
}
class PairSetTable
{
internal PairSet[] _pairSets;
public void ReadFrom(BinaryReader reader, ushort v1format, ushort v2format)
{
ushort rowCount = reader.ReadUInt16();
_pairSets = new PairSet[rowCount];
for (int i = 0; i < rowCount; ++i)
{
//GlyphID SecondGlyph GlyphID of second glyph in the pair-first glyph is listed in the Coverage table
//ValueRecord Value1 Positioning data for the first glyph in the pair
//ValueRecord Value2 Positioning data for the second glyph in the pair
ushort secondGlyph = reader.ReadUInt16();
ValueRecord v1 = ValueRecord.CreateFrom(reader, v1format);
ValueRecord v2 = ValueRecord.CreateFrom(reader, v2format);
//
_pairSets[i] = new PairSet(secondGlyph, v1, v2);
}
}
public bool FindPairSet(ushort secondGlyphIndex, out PairSet foundPairSet)
{
int j = _pairSets.Length;
for (int i = 0; i < j; ++i)
{
//TODO: binary search?
if (_pairSets[i].secondGlyph == secondGlyphIndex)
{
//found
foundPairSet = _pairSets[i];
return true;
}
}
//
foundPairSet = new PairSet();//empty
return false;
}
}
readonly struct PairSet
{
public readonly ushort secondGlyph;//GlyphID of second glyph in the pair-first glyph is listed in the Coverage table
public readonly ValueRecord value1;//Positioning data for the first glyph in the pair
public readonly ValueRecord value2;//Positioning data for the second glyph in the pair
public PairSet(ushort secondGlyph, ValueRecord v1, ValueRecord v2)
{
this.secondGlyph = secondGlyph;
this.value1 = v1;
this.value2 = v2;
}
#if DEBUG
public override string ToString()
{
return "second_glyph:" + secondGlyph;
}
#endif
}
class ValueRecord
{
//ValueRecord (all fields are optional)
//Value Type Description
//--------------------------------
//int16 XPlacement Horizontal adjustment for placement-in design units
//int16 YPlacement Vertical adjustment for placement, in design units
//int16 XAdvance Horizontal adjustment for advance, in design units (only used for horizontal writing)
//int16 YAdvance Vertical adjustment for advance, in design units (only used for vertical writing)
//Offset16 XPlaDevice Offset to Device table (non-variable font) / VariationIndex table (variable font) for horizontal placement, from beginning of PosTable (may be NULL)
//Offset16 YPlaDevice Offset to Device table (non-variable font) / VariationIndex table (variable font) for vertical placement, from beginning of PosTable (may be NULL)
//Offset16 XAdvDevice Offset to Device table (non-variable font) / VariationIndex table (variable font) for horizontal advance, from beginning of PosTable (may be NULL)
//Offset16 YAdvDevice Offset to Device table (non-variable font) / VariationIndex table (variable font) for vertical advance, from beginning of PosTable (may be NULL)
public short XPlacement;
public short YPlacement;
public short XAdvance;
public short YAdvance;
public ushort XPlaDevice;
public ushort YPlaDevice;
public ushort XAdvDevice;
public ushort YAdvDevice;
ushort valueFormat;
public void ReadFrom(BinaryReader reader, ushort valueFormat)
{
this.valueFormat = valueFormat;
if (HasFormat(valueFormat, FMT_XPlacement))
{
this.XPlacement = reader.ReadInt16();
}
if (HasFormat(valueFormat, FMT_YPlacement))
{
this.YPlacement = reader.ReadInt16();
}
if (HasFormat(valueFormat, FMT_XAdvance))
{
this.XAdvance = reader.ReadInt16();
}
if (HasFormat(valueFormat, FMT_YAdvance))
{
this.YAdvance = reader.ReadInt16();
}
if (HasFormat(valueFormat, FMT_XPlaDevice))
{
this.XPlaDevice = reader.ReadUInt16();
}
if (HasFormat(valueFormat, FMT_YPlaDevice))
{
this.YPlaDevice = reader.ReadUInt16();
}
if (HasFormat(valueFormat, FMT_XAdvDevice))
{
this.XAdvDevice = reader.ReadUInt16();
}
if (HasFormat(valueFormat, FMT_YAdvDevice))
{
this.YAdvDevice = reader.ReadUInt16();
}
}
static bool HasFormat(ushort value, int flags)
{
return (value & flags) == flags;
}
//Mask Name Description
//0x0001 XPlacement Includes horizontal adjustment for placement
//0x0002 YPlacement Includes vertical adjustment for placement
//0x0004 XAdvance Includes horizontal adjustment for advance
//0x0008 YAdvance Includes vertical adjustment for advance
//0x0010 XPlaDevice Includes Device table (non-variable font) / VariationIndex table (variable font) for horizontal placement
//0x0020 YPlaDevice Includes Device table (non-variable font) / VariationIndex table (variable font) for vertical placement
//0x0040 XAdvDevice Includes Device table (non-variable font) / VariationIndex table (variable font) for horizontal advance
//0x0080 YAdvDevice Includes Device table (non-variable font) / VariationIndex table (variable font) for vertical advance
//0xFF00 Reserved For future use (set to zero)
//check bits
const int FMT_XPlacement = 1;
const int FMT_YPlacement = 1 << 1;
const int FMT_XAdvance = 1 << 2;
const int FMT_YAdvance = 1 << 3;
const int FMT_XPlaDevice = 1 << 4;
const int FMT_YPlaDevice = 1 << 5;
const int FMT_XAdvDevice = 1 << 6;
const int FMT_YAdvDevice = 1 << 7;
public static ValueRecord CreateFrom(BinaryReader reader, ushort valueFormat)
{
if (valueFormat == 0)
return null;//empty
var v = new ValueRecord();
v.ReadFrom(reader, valueFormat);
return v;
}
#if DEBUG
public override string ToString()
{
StringBuilder stbuilder = new StringBuilder();
bool appendComma = false;
if (XPlacement != 0)
{
stbuilder.Append("XPlacement=" + XPlacement);
appendComma = true;
}
if (YPlacement != 0)
{
if (appendComma) { stbuilder.Append(','); }
stbuilder.Append(" YPlacement=" + YPlacement);
appendComma = true;
}
if (XAdvance != 0)
{
if (appendComma) { stbuilder.Append(','); }
stbuilder.Append(" XAdvance=" + XAdvance);
appendComma = true;
}
if (YAdvance != 0)
{
if (appendComma) { stbuilder.Append(','); }
stbuilder.Append(" YAdvance=" + YAdvance);
appendComma = true;
}
return stbuilder.ToString();
}
#endif
}
/// <summary>
/// To describe an anchor point
/// </summary>
class AnchorPoint
{
//Anchor Table
//A GPOS table uses anchor points to position one glyph with respect to another.
//Each glyph defines an anchor point, and the text-processing client attaches the glyphs by aligning their corresponding anchor points.
//To describe an anchor point, an Anchor table can use one of three formats.
//The first format uses design units to specify a location for the anchor point.
//The other two formats refine the location of the anchor point using contour points (Format 2) or Device tables (Format 3).
//In a variable font, the third format uses a VariationIndex table (a variant of a Device table) to
//reference variation data for adjustment of the anchor position for the current variation instance, as needed.
public ushort format;
public short xcoord;
public short ycoord;
/// <summary>
/// an index to a glyph contour point (AnchorPoint)
/// </summary>
public ushort refGlyphContourPoint;
public ushort xdeviceTableOffset;
public ushort ydeviceTableOffset;
public static AnchorPoint CreateFrom(BinaryReader reader, long beginAt)
{
AnchorPoint anchorPoint = new AnchorPoint();
reader.BaseStream.Seek(beginAt, SeekOrigin.Begin);
switch (anchorPoint.format = reader.ReadUInt16())
{
default: throw new OpenFontNotSupportedException();
case 1:
{
// AnchorFormat1 table: Design units only
//AnchorFormat1 consists of a format identifier (AnchorFormat) and a pair of design unit coordinates (XCoordinate and YCoordinate)
//that specify the location of the anchor point.
//This format has the benefits of small size and simplicity,
//but the anchor point cannot be hinted to adjust its position for different device resolutions.
//Value Type Description
//uint16 AnchorFormat Format identifier, = 1
//int16 XCoordinate Horizontal value, in design units
//int16 YCoordinate Vertical value, in design units
anchorPoint.xcoord = reader.ReadInt16();
anchorPoint.ycoord = reader.ReadInt16();
}
break;
case 2:
{
//Anchor Table: Format 2
//Like AnchorFormat1, AnchorFormat2 specifies a format identifier (AnchorFormat) and
//a pair of design unit coordinates for the anchor point (Xcoordinate and Ycoordinate).
//For fine-tuning the location of the anchor point,
//AnchorFormat2 also provides an index to a glyph contour point (AnchorPoint)
//that is on the outline of a glyph (AnchorPoint).***
//Hinting can be used to move the AnchorPoint. In the rendered text,
//the AnchorPoint will provide the final positioning data for a given ppem size.
//Example 16 at the end of this chapter uses AnchorFormat2.
//AnchorFormat2 table: Design units plus contour point
//Value Type Description
//uint16 AnchorFormat Format identifier, = 2
//int16 XCoordinate Horizontal value, in design units
//int16 YCoordinate Vertical value, in design units
//uint16 AnchorPoint Index to glyph contour point
anchorPoint.xcoord = reader.ReadInt16();
anchorPoint.ycoord = reader.ReadInt16();
anchorPoint.refGlyphContourPoint = reader.ReadUInt16();
}
break;
case 3:
{
//Anchor Table: Format 3
//Like AnchorFormat1, AnchorFormat3 specifies a format identifier (AnchorFormat) and
//locates an anchor point (Xcoordinate and Ycoordinate).
//And, like AnchorFormat 2, it permits fine adjustments in variable fonts to the coordinate values.
//However, AnchorFormat3 uses Device tables, rather than a contour point, for this adjustment.
//With a Device table, a client can adjust the position of the anchor point for any font size and device resolution.
//AnchorFormat3 can specify offsets to Device tables for the the X coordinate (XDeviceTable)
//and the Y coordinate (YDeviceTable).
//If only one coordinate requires adjustment,
//the offset to the Device table may be set to NULL for the other coordinate.
//In variable fonts, AnchorFormat3 must be used to reference variation data to adjust anchor points for different variation instances,
//if needed.
//In this case, AnchorFormat3 specifies an offset to a VariationIndex table,
//which is a variant of the Device table used for variations.
//If no VariationIndex table is used for a particular anchor point X or Y coordinate,
//then that value is used for all variation instances.
//While separate VariationIndex table references are required for each value that requires variation,
//two or more values that require the same variation-data values can have offsets that point to the same VariationIndex table, and two or more VariationIndex tables can reference the same variation data entries.
//Example 17 at the end of the chapter shows an AnchorFormat3 table.
//AnchorFormat3 table: Design units plus Device or VariationIndex tables
//Value Type Description
//uint16 AnchorFormat Format identifier, = 3
//int16 XCoordinate Horizontal value, in design units
//int16 YCoordinate Vertical value, in design units
//Offset16 XDeviceTable Offset to Device table (non-variable font) / VariationIndex table (variable font) for X coordinate, from beginning of Anchor table (may be NULL)
//Offset16 YDeviceTable Offset to Device table (non-variable font) / VariationIndex table (variable font) for Y coordinate, from beginning of Anchor table (may be NULL)
anchorPoint.xcoord = reader.ReadInt16();
anchorPoint.ycoord = reader.ReadInt16();
anchorPoint.xdeviceTableOffset = reader.ReadUInt16();
anchorPoint.ydeviceTableOffset = reader.ReadUInt16();
}
break;
}
return anchorPoint;
}
#if DEBUG
public override string ToString()
{
switch (format)
{
default: return "";
case 1:
return format + "(" + xcoord + "," + ycoord + ")";
case 2:
return format + "(" + xcoord + "," + ycoord + "), ref_point=" + refGlyphContourPoint;
case 3:
return format + "(" + xcoord + "," + ycoord + "), xy_device(" + xdeviceTableOffset + "," + ydeviceTableOffset + ")";
}
}
#endif
}
class MarkArrayTable
{
//Mark Array
//The MarkArray table defines the class and the anchor point for a mark glyph.
//Three GPOS subtables-MarkToBase, MarkToLigature, and MarkToMark Attachment
//use the MarkArray table to specify data for attaching marks.
//The MarkArray table contains a count of the number of mark records (MarkCount) and an array of those records (MarkRecord).
//Each mark record defines the class of the mark and an offset to the Anchor table that contains data for the mark.
//A class value can be 0 (zero), but the MarkRecord must explicitly assign that class value (this differs from the ClassDef table,
//in which all glyphs not assigned class values automatically belong to Class 0).
//The GPOS subtables that refer to MarkArray tables use the class assignments for indexing zero-based arrays that contain data for each mark class.
// MarkArray table
//-------------------
//Value Type Description
//uint16 MarkCount Number of MarkRecords
//struct MarkRecord[MarkCount] Array of MarkRecords in Coverage order
//
//MarkRecord
//Value Type Description
//-------------------
//uint16 Class Class defined for this mark
//Offset16 MarkAnchor Offset to Anchor table-from beginning of MarkArray table
internal MarkRecord[] _records;
internal AnchorPoint[] _anchorPoints;
public AnchorPoint GetAnchorPoint(int index)
{
return _anchorPoints[index];
}
public ushort GetMarkClass(int index)
{
return _records[index].markClass;
}
void ReadFrom(BinaryReader reader)
{
long markTableBeginAt = reader.BaseStream.Position;
ushort markCount = reader.ReadUInt16();
_records = new MarkRecord[markCount];
for (int i = 0; i < markCount; ++i)
{
//1 mark : 1 anchor
_records[i] = new MarkRecord(
reader.ReadUInt16(),//mark class
reader.ReadUInt16()); //offset to anchor table
}
//---------------------------
//read anchor
_anchorPoints = new AnchorPoint[markCount];
for (int i = 0; i < markCount; ++i)
{
MarkRecord markRec = _records[i];
//bug?
if (markRec.offset < 0)
{
//TODO: review here
//found err on Tahoma
continue;
}
//read table detail
_anchorPoints[i] = AnchorPoint.CreateFrom(reader, markTableBeginAt + markRec.offset);
}
}
#if DEBUG
public int dbugGetAnchorCount()
{
return _anchorPoints.Length;
}
#endif
public static MarkArrayTable CreateFrom(BinaryReader reader, long beginAt)
{
reader.BaseStream.Seek(beginAt, SeekOrigin.Begin);
//
var markArrTable = new MarkArrayTable();
markArrTable.ReadFrom(reader);
return markArrTable;
}
}
readonly struct MarkRecord
{
/// <summary>
/// Class defined for this mark,. A mark class is identified by a specific integer, called a class value
/// </summary>
public readonly ushort markClass;
/// <summary>
/// Offset to Anchor table-from beginning of MarkArray table
/// </summary>
public readonly ushort offset;
public MarkRecord(ushort markClass, ushort offset)
{
this.markClass = markClass;
this.offset = offset;
}
#if DEBUG
public override string ToString()
{
return "class " + markClass + ",offset=" + offset;
}
#endif
}
class Mark2ArrayTable
{
///Mark2Array table
//Value Type Description
//uint16 Mark2Count Number of Mark2 records
//struct Mark2Record[Mark2Count] Array of Mark2 records-in Coverage order
//Each Mark2Record contains an array of offsets to Anchor tables (Mark2Anchor).
//The array of zero-based offsets, measured from the beginning of the Mark2Array table,
//defines the entire set of Mark2 attachment points used to attach Mark1 glyphs to a specific Mark2 glyph.
//The Anchor tables in the Mark2Anchor array are ordered by Mark1 class value.
//A Mark2Record declares one Anchor table for each mark class (including Class 0)
//identified in the MarkRecords of the MarkArray.
//Each Anchor table specifies one Mark2 attachment point used to attach all
//the Mark1 glyphs in a particular class to the Mark2 glyph.
//Mark2Record
//Value Type Description
//Offset16 Mark2Anchor[ClassCount] Array of offsets (one per class) to Anchor tables-from beginning of Mark2Array table-zero-based array
public static Mark2ArrayTable CreateFrom(BinaryReader reader, long beginAt, ushort classCount)
{
reader.BaseStream.Seek(beginAt, SeekOrigin.Begin);
//---
ushort mark2Count = reader.ReadUInt16();
ushort[] offsets = Utils.ReadUInt16Array(reader, mark2Count * classCount);
//read mark2 anchors
AnchorPoint[] anchors = new AnchorPoint[mark2Count * classCount];
for (int i = 0; i < mark2Count * classCount; ++i)
{
anchors[i] = AnchorPoint.CreateFrom(reader, beginAt + offsets[i]);
}
return new Mark2ArrayTable(classCount, anchors);
}
public AnchorPoint GetAnchorPoint(int index, int markClassId)
{
return _anchorPoints[index * _classCount + markClassId];
}
public Mark2ArrayTable(ushort classCount, AnchorPoint[] anchorPoints)
{
_classCount = classCount;
_anchorPoints = anchorPoints;
}
internal readonly ushort _classCount;
internal readonly AnchorPoint[] _anchorPoints;
}
class BaseArrayTable
{
//BaseArray table
//Value Type Description
//uint16 BaseCount Number of BaseRecords
//struct BaseRecord[BaseCount] Array of BaseRecords-in order of BaseCoverage Index
//A BaseRecord declares one Anchor table for each mark class (including Class 0)
//identified in the MarkRecords of the MarkArray.
//Each Anchor table specifies one attachment point used to attach all the marks in a particular class to the base glyph.
//A BaseRecord contains an array of offsets to Anchor tables (BaseAnchor).
//The zero-based array of offsets defines the entire set of attachment points each base glyph uses to attach marks.
//The offsets to Anchor tables are ordered by mark class.
// Note: Anchor tables are not tagged with class value identifiers.
//Instead, the index value of an Anchor table in the array defines the class value represented by the Anchor table.
internal BaseRecord[] _records;
public BaseRecord GetBaseRecords(int index)
{
return _records[index];
}
public static BaseArrayTable CreateFrom(BinaryReader reader, long beginAt, ushort classCount)
{
reader.BaseStream.Seek(beginAt, SeekOrigin.Begin);
//---
var baseArrTable = new BaseArrayTable();
ushort baseCount = reader.ReadUInt16();
baseArrTable._records = new BaseRecord[baseCount];
// Read all baseAnchorOffsets in one go
ushort[] baseAnchorOffsets = Utils.ReadUInt16Array(reader, classCount * baseCount);
for (int i = 0; i < baseCount; ++i)
{
AnchorPoint[] anchors = new AnchorPoint[classCount];
BaseRecord baseRec = new BaseRecord(anchors);
//each base has anchor point for mark glyph'class
for (int n = 0; n < classCount; ++n)
{
ushort offset = baseAnchorOffsets[i * classCount + n];
if (offset <= 0)
{
//TODO: review here
//bug?
continue;
}
anchors[n] = AnchorPoint.CreateFrom(reader, beginAt + offset);
}
baseArrTable._records[i] = baseRec;
}
return baseArrTable;
}
#if DEBUG
public int dbugGetRecordCount()
{
return _records.Length;
}
#endif
}
readonly struct BaseRecord
{
//BaseRecord
//Value Type Description
//Offset16 BaseAnchor[ClassCount] Array of offsets (one per class) to
//Anchor tables-from beginning of BaseArray table-ordered by class-zero-based
public readonly AnchorPoint[] anchors;
public BaseRecord(AnchorPoint[] anchors)
{
this.anchors = anchors;
}
#if DEBUG
public override string ToString()
{
StringBuilder stbuilder = new StringBuilder();
if (anchors != null)
{
int i = 0;
foreach (AnchorPoint a in anchors)
{
if (i > 0)
{
stbuilder.Append(',');
}
if (a == null)
{
stbuilder.Append("null");
}
else
{
stbuilder.Append(a.ToString());
}
}
}
return stbuilder.ToString();
}
#endif
}
// LigatureArray table
//Value Type Description
//USHORT LigatureCount Number of LigatureAttach table offsets
//Offset LigatureAttach
//[LigatureCount] Array of offsets to LigatureAttach tables-from beginning of LigatureArray table-ordered by LigatureCoverage Index
//Each LigatureAttach table consists of an array (ComponentRecord) and count (ComponentCount) of the component glyphs in a ligature. The array stores the ComponentRecords in the same order as the components in the ligature. The order of the records also corresponds to the writing direction of the text. For text written left to right, the first component is on the left; for text written right to left, the first component is on the right.
//-------------------------------
//LigatureAttach table
//Value Type Description
//uint16 ComponentCount Number of ComponentRecords in this ligature
//struct ComponentRecord[ComponentCount] Array of Component records-ordered in writing direction
//-------------------------------
//A ComponentRecord, one for each component in the ligature, contains an array of offsets to the Anchor tables that define all the attachment points used to attach marks to the component (LigatureAnchor). For each mark class (including Class 0) identified in the MarkArray records, an Anchor table specifies the point used to attach all the marks in a particular class to the ligature base glyph, relative to the component.
//In a ComponentRecord, the zero-based LigatureAnchor array lists offsets to Anchor tables by mark class. If a component does not define an attachment point for a particular class of marks, then the offset to the corresponding Anchor table will be NULL.
//Example 8 at the end of this chapter shows a MarkLisPosFormat1 subtable used to attach mark accents to a ligature glyph in the Arabic script.
//-------------------
//ComponentRecord
//Value Type Description
//Offset16 LigatureAnchor[ClassCount] Array of offsets (one per class) to Anchor tables-from beginning of LigatureAttach table-ordered by class-NULL if a component does not have an attachment for a class-zero-based array
class LigatureArrayTable
{
LigatureAttachTable[] _ligatures;
public void ReadFrom(BinaryReader reader, ushort classCount)
{
long startPos = reader.BaseStream.Position;
ushort ligatureCount = reader.ReadUInt16();
ushort[] offsets = Utils.ReadUInt16Array(reader, ligatureCount);
_ligatures = new LigatureAttachTable[ligatureCount];
for (int i = 0; i < ligatureCount; ++i)
{
//each ligature table
reader.BaseStream.Seek(startPos + offsets[i], SeekOrigin.Begin);
_ligatures[i] = LigatureAttachTable.ReadFrom(reader, classCount);
}
}
public LigatureAttachTable GetLigatureAttachTable(int index) => _ligatures[index];
}
class LigatureAttachTable
{
//LigatureAttach table
//Value Type Description
//uint16 ComponentCount Number of ComponentRecords in this ligature
//struct ComponentRecord[ComponentCount] Array of Component records-ordered in writing direction
//-------------------------------
ComponentRecord[] _records;
public static LigatureAttachTable ReadFrom(BinaryReader reader, ushort classCount)
{
LigatureAttachTable table = new LigatureAttachTable();
ushort componentCount = reader.ReadUInt16();
ComponentRecord[] componentRecs = new ComponentRecord[componentCount];
table._records = componentRecs;
for (int i = 0; i < componentCount; ++i)
{
componentRecs[i] = new ComponentRecord(
Utils.ReadUInt16Array(reader, classCount));
}
return table;
}
public ComponentRecord GetComponentRecord(int index) => _records[index];
}
readonly struct ComponentRecord
{
//ComponentRecord
//Value Type Description
//Offset16 LigatureAnchor[ClassCount] Array of offsets(one per class) to Anchor tables-from beginning of LigatureAttach table-ordered by class-NULL if a component does not have an attachment for a class-zero-based array
public readonly ushort[] offsets;
public ComponentRecord(ushort[] offsets)
{
this.offsets = offsets;
}
}
//------
readonly struct PosLookupRecord
{
//PosLookupRecord
//Value Type Description
//USHORT SequenceIndex Index to input glyph sequence-first glyph = 0
//USHORT LookupListIndex Lookup to apply to that position-zero-based
public readonly ushort seqIndex;
public readonly ushort lookupListIndex;
public PosLookupRecord(ushort seqIndex, ushort lookupListIndex)
{
this.seqIndex = seqIndex;
this.lookupListIndex = lookupListIndex;
}
public static PosLookupRecord CreateFrom(BinaryReader reader)
{
return new PosLookupRecord(reader.ReadUInt16(), reader.ReadUInt16());
}
}
class PosRuleSetTable
{
//PosRuleSet table: All contexts beginning with the same glyph
// Value Type Description
//uint16 PosRuleCount Number of PosRule tables
//Offset16 PosRule[PosRuleCount] Array of offsets to PosRule tables-from beginning of PosRuleSet-ordered by preference
//
//A PosRule table consists of a count of the glyphs to be matched in the input context sequence (GlyphCount),
//including the first glyph in the sequence, and an array of glyph indices that describe the context (Input).
//The Coverage table specifies the index of the first glyph in the context, and the Input array begins with the second glyph in the context sequence. As a result, the first index position in the array is specified with the number one (1), not zero (0). The Input array lists the indices in the order the corresponding glyphs appear in the text. For text written from right to left, the right-most glyph will be first; conversely, for text written from left to right, the left-most glyph will be first.
//A PosRule table also contains a count of the positioning operations to be performed on the input glyph sequence (PosCount) and an array of PosLookupRecords (PosLookupRecord). Each record specifies a position in the input glyph sequence and a LookupList index to the positioning lookup to be applied there. The array should list records in design order, or the order the lookups should be applied to the entire glyph sequence.
//Example 10 at the end of this chapter demonstrates glyph kerning in context with a ContextPosFormat1 subtable.
PosRuleTable[] _posRuleTables;
void ReadFrom(BinaryReader reader)
{
long tableStartAt = reader.BaseStream.Position;
ushort posRuleCount = reader.ReadUInt16();
ushort[] posRuleTableOffsets = Utils.ReadUInt16Array(reader, posRuleCount);
int j = posRuleTableOffsets.Length;
_posRuleTables = new PosRuleTable[posRuleCount];
for (int i = 0; i < j; ++i)
{
//move to and read
reader.BaseStream.Seek(tableStartAt + posRuleTableOffsets[i], SeekOrigin.Begin);
var posRuleTable = new PosRuleTable();
posRuleTable.ReadFrom(reader);
_posRuleTables[i] = posRuleTable;
}
}
public static PosRuleSetTable CreateFrom(BinaryReader reader, long beginAt)
{
reader.BaseStream.Seek(beginAt, SeekOrigin.Begin);
//------------
var posRuleSetTable = new PosRuleSetTable();
posRuleSetTable.ReadFrom(reader);
return posRuleSetTable;
}
}
class PosRuleTable
{
//PosRule subtable
//Value Type Description
//uint16 GlyphCount Number of glyphs in the Input glyph sequence
//uint16 PosCount Number of PosLookupRecords
//uint16 Input[GlyphCount - 1] Array of input GlyphIDs-starting with the second glyph***
//struct PosLookupRecord[PosCount] Array of positioning lookups-in design order
PosLookupRecord[] _posLookupRecords;
ushort[] _inputGlyphIds;
public void ReadFrom(BinaryReader reader)
{
ushort glyphCount = reader.ReadUInt16();
ushort posCount = reader.ReadUInt16();
_inputGlyphIds = Utils.ReadUInt16Array(reader, glyphCount - 1);
_posLookupRecords = CreateMultiplePosLookupRecords(reader, posCount);
}
}
class PosClassSetTable
{
//PosClassSet table: All contexts beginning with the same class
//Value Type Description
//----------------------
//uint16 PosClassRuleCnt Number of PosClassRule tables
//Offset16 PosClassRule[PosClassRuleCnt] Array of offsets to PosClassRule tables-from beginning of PosClassSet-ordered by preference
//----------------------
//
//For each context, a PosClassRule table contains a count of the glyph classes in a given context (GlyphCount),
//including the first class in the context sequence.
//A class array lists the classes, beginning with the second class,
//that follow the first class in the context.
//The first class listed indicates the second position in the context sequence.
//Note: Text order depends on the writing direction of the text.
//For text written from right to left, the right-most glyph will be first.
//Conversely, for text written from left to right, the left-most glyph will be first.
//The values specified in the Class array are those defined in the ClassDef table.
//For example, consider a context consisting of the sequence: Class 2, Class 7, Class 5, Class 0.
//The Class array will read: Class[0] = 7, Class[1] = 5, and Class[2] = 0.
//The first class in the sequence, Class 2, is defined by the index into the PosClassSet array of offsets.
//The total number and sequence of glyph classes listed in the Class array must match the total number and sequence of glyph classes contained in the input context.
//A PosClassRule also contains a count of the positioning operations to be performed on the context (PosCount) and
//an array of PosLookupRecords (PosLookupRecord) that supply the positioning data.
//For each position in the context that requires a positioning operation,
//a PosLookupRecord specifies a LookupList index and a position in the input glyph class sequence where the lookup is applied.
//The PosLookupRecord array lists PosLookupRecords in design order, or the order in which lookups are applied to the entire glyph sequence.
//Example 11 at the end of this chapter demonstrates a ContextPosFormat2 subtable that uses glyph classes to modify accent positions in glyph strings.
//----------------------
//PosClassRule table: One class context definition
//----------------------
//Value Type Description
//uint16 GlyphCount Number of glyphs to be matched
//uint16 PosCount Number of PosLookupRecords
//uint16 Class[GlyphCount - 1] Array of classes-beginning with the second class-to be matched to the input glyph sequence
//struct PosLookupRecord[PosCount] Array of positioning lookups-in design order
//----------------------
public PosClassRule[] PosClassRules;
void ReadFrom(BinaryReader reader)
{
long tableStartAt = reader.BaseStream.Position;
//
ushort posClassRuleCnt = reader.ReadUInt16();
ushort[] posClassRuleOffsets = Utils.ReadUInt16Array(reader, posClassRuleCnt);
PosClassRules = new PosClassRule[posClassRuleCnt];
for (int i = 0; i < posClassRuleOffsets.Length; ++i)
{
//move to and read
PosClassRules[i] = PosClassRule.CreateFrom(reader, tableStartAt + posClassRuleOffsets[i]);
}
}
public static PosClassSetTable CreateFrom(BinaryReader reader, long beginAt)
{
reader.BaseStream.Seek(beginAt, SeekOrigin.Begin);
//--------
var posClassSetTable = new PosClassSetTable();
posClassSetTable.ReadFrom(reader);
return posClassSetTable;
}
}
class PosClassRule
{
public PosLookupRecord[] PosLookupRecords;
public ushort[] InputGlyphIds;
public static PosClassRule CreateFrom(BinaryReader reader, long beginAt)
{
//--------
reader.BaseStream.Seek(beginAt, SeekOrigin.Begin);
//--------
PosClassRule posClassRule = new PosClassRule();
ushort glyphCount = reader.ReadUInt16();
ushort posCount = reader.ReadUInt16();
if (glyphCount > 1)
{
posClassRule.InputGlyphIds = Utils.ReadUInt16Array(reader, glyphCount - 1);
}
posClassRule.PosLookupRecords = CreateMultiplePosLookupRecords(reader, posCount);
return posClassRule;
}
}
}
}

View File

@@ -0,0 +1,218 @@
//Apache2, 2016-present, WinterDev, Sam Hocevar <sam@hocevar.net>
using System.IO;
namespace Typography.OpenFont.Tables
{
//from https://docs.microsoft.com/en-us/typography/opentype/spec/otff#otttables
//Data Types
// The following data types are used in the OpenType font file.All OpenType fonts use Motorola-style byte ordering (Big Endian):
// Data Type Description
// uint8 8-bit unsigned integer.
// int8 8-bit signed integer.
// uint16 16-bit unsigned integer.
// int16 16-bit signed integer.
// uint24 24-bit unsigned integer.
// uint32 32-bit unsigned integer.
// int32 32-bit signed integer.
// Fixed 32-bit signed fixed-point number(16.16)
// FWORD int16 that describes a quantity in font design units.
// UFWORD uint16 that describes a quantity in font design units.
// F2DOT14 16 - bit signed fixed number with the low 14 bits of fraction(2.14).
// LONGDATETIME Date represented in number of seconds since 12:00 midnight, January 1, 1904.The value is represented as a signed 64 - bit integer.
// Tag Array of four uint8s(length = 32 bits) used to identify a script, language system, feature, or baseline
// Offset16 Short offset to a table, same as uint16, NULL offset = 0x0000
// Offset32 Long offset to a table, same as uint32, NULL offset = 0x00000000
// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos
// https://docs.microsoft.com/en-us/typography/opentype/spec/gsub
public abstract class GlyphShapingTableEntry : TableEntry
{
public ushort MajorVersion { get; private set; }
public ushort MinorVersion { get; private set; }
public ScriptList ScriptList { get; private set; }
public FeatureList FeatureList { get; private set; }
/// <summary>
/// read script_list, feature_list, and skip look up table
/// </summary>
internal bool OnlyScriptList { get; set; } //
protected override void ReadContentFrom(BinaryReader reader)
{
//-------------------------------------------
// GPOS/GSUB Header
// The GPOS/GSUB table begins with a header that contains a version number for the table. Two versions are defined.
// Version 1.0 contains offsets to three tables: ScriptList, FeatureList, and LookupList.
// Version 1.1 also includes an offset to a FeatureVariations table.
// For descriptions of these tables, see the chapter, OpenType Layout Common Table Formats .
// Example 1 at the end of this chapter shows a GPOS/GSUB Header table definition.
//
// GPOS/GSUB Header, Version 1.0
// Value Type Description
// uint16 MajorVersion Major version of the GPOS/GSUB table, = 1
// uint16 MinorVersion Minor version of the GPOS/GSUB table, = 0
// Offset16 ScriptList Offset to ScriptList table, from beginning of GPOS/GSUB table
// Offset16 FeatureList Offset to FeatureList table, from beginning of GPOS/GSUB table
// Offset16 LookupList Offset to LookupList table, from beginning of GPOS/GSUB table
//
// GPOS/GSUB Header, Version 1.1
// Value Type Description
// uint16 MajorVersion Major version of the GPOS/GSUB table, = 1
// uint16 MinorVersion Minor version of the GPOS/GSUB table, = 1
// Offset16 ScriptList Offset to ScriptList table, from beginning of GPOS/GSUB table
// Offset16 FeatureList Offset to FeatureList table, from beginning of GPOS/GSUB table
// Offset16 LookupList Offset to LookupList table, from beginning of GPOS/GSUB table
// Offset32 FeatureVariations Offset to FeatureVariations table, from beginning of GPOS/GSUB table (may be NULL)
long tableStartAt = reader.BaseStream.Position;
MajorVersion = reader.ReadUInt16();
MinorVersion = reader.ReadUInt16();
ushort scriptListOffset = reader.ReadUInt16(); // from beginning of table
ushort featureListOffset = reader.ReadUInt16(); // from beginning of table
ushort lookupListOffset = reader.ReadUInt16(); // from beginning of table
uint featureVariations = (MinorVersion == 1) ? reader.ReadUInt32() : 0; // from beginning of table
//-----------------------
//1. scriptlist
ScriptList = ScriptList.CreateFrom(reader, tableStartAt + scriptListOffset);
if (OnlyScriptList) return; //for preview script-list and feature list only
//-----------------------
//2. feature list
FeatureList = FeatureList.CreateFrom(reader, tableStartAt + featureListOffset);
//3. lookup list
ReadLookupListTable(reader, tableStartAt + lookupListOffset);
//-----------------------
//4. feature variations
if (featureVariations > 0)
{
ReadFeatureVariations(reader, tableStartAt + featureVariations);
}
}
void ReadLookupListTable(BinaryReader reader, long lookupListBeginAt)
{
//https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2
// -----------------------
// LookupList table
// -----------------------
// Type Name Description
// uint16 LookupCount Number of lookups in this table
// Offset16 Lookup[LookupCount] Array of offsets to Lookup tables-from beginning of LookupList -zero based (first lookup is Lookup index = 0)
// -----------------------
//
// Lookup Table
// A Lookup table (Lookup) defines the specific conditions, type,
// and results of a substitution or positioning action that is used to implement a feature.
// For example, a substitution operation requires a list of target glyph indices to be replaced,
// a list of replacement glyph indices, and a description of the type of substitution action.
// Each Lookup table may contain only one type of information (LookupType),
// determined by whether the lookup is part of a GSUB or GPOS table. GSUB supports eight LookupTypes,
// and GPOS supports nine LookupTypes (for details about LookupTypes, see the GSUB and GPOS chapters of the document).
//
// Each LookupType is defined with one or more subtables,
// and each subtable definition provides a different representation format.
// The format is determined by the content of the information required for an operation and by required storage efficiency.
// When glyph information is best presented in more than one format,
// a single lookup may contain more than one subtable, as long as all the subtables are the same LookupType.
// For example, within a given lookup, a glyph index array format may best represent one set of target glyphs,
// whereas a glyph index range format may be better for another set of target glyphs.
//
// During text processing, a client applies a lookup to each glyph in the string before moving to the next lookup.
// A lookup is finished for a glyph after the client makes the substitution/positioning operation.
// To move to the “next” glyph, the client will typically skip all the glyphs that participated in the lookup operation: glyphs
// that were substituted/positioned as well as any other glyphs that formed a context for the operation.
// However, in the case of pair positioning operations (i.e., kerning),
// the “next” glyph in a sequence may be the second glyph of the positioned pair (see pair positioning lookup for details).
//
// A Lookup table contains a LookupType, specified as an integer, that defines the type of information stored in the lookup.
// The LookupFlag specifies lookup qualifiers that assist a text-processing client in substituting or positioning glyphs.
// The SubTableCount specifies the total number of SubTables.
// The SubTable array specifies offsets, measured from the beginning of the Lookup table, to each SubTable enumerated in the SubTable array.
//
// Lookup table
// --------------------------------
// Type Name Description
// unit16 LookupType Different enumerations for GSUB and GPOS
// unit16 LookupFlag Lookup qualifiers
// unit16 SubTableCount Number of SubTables for this lookup
// Offset16 SubTable[SubTableCount] Array of offsets to SubTables-from beginning of Lookup table
// uint16 MarkFilteringSet Index (base 0) into GDEF mark glyph sets structure.
// *** This field is only present if bit UseMarkFilteringSet of lookup flags is set.
// --------------------------------
// --------------------------------
//The LookupFlag uses two bytes of data:
//Each of the first four bits can be set in order to specify additional instructions for applying a lookup to a glyph string.The LookUpFlag bit enumeration table provides details about the use of these bits.
//The fifth bit indicates the presence of a MarkFilteringSet field in the Lookup table.
//The next three bits are reserved for future use.
//The high byte is set to specify the type of mark attachment.
//LookupFlag bit enumeration
//Type Name Description
//0x0001 rightToLeft This bit relates only to the correct processing of the cursive attachment lookup type(GPOS lookup type 3).When this bit is set, the last glyph in a given sequence to which the cursive attachment lookup is applied, will be positioned on the baseline.
// Note: Setting of this bit is not intended to be used by operating systems or applications to determine text direction.
//0x0002 ignoreBaseGlyphs If set, skips over base glyphs
//0x0004 ignoreLigatures If set, skips over ligatures
//0x0008 ignoreMarks If set, skips over all combining marks
//0x0010 useMarkFilteringSet If set, indicates that the lookup table structure is followed by a MarkFilteringSet field.
// The layout engine skips over all mark glyphs not in the mark filtering set indicated.
//0x00E0 reserved For future use(Set to zero)
//0xFF00 markAttachmentType If not zero, skips over all marks of attachment type different from specified.
// --------------------------------
reader.BaseStream.Seek(lookupListBeginAt, SeekOrigin.Begin);
ushort lookupCount = reader.ReadUInt16();
ushort[] lookupTableOffsets = Utils.ReadUInt16Array(reader, lookupCount);
//----------------------------------------------
//load each sub table
foreach (ushort lookupTableOffset in lookupTableOffsets)
{
long lookupTablePos = lookupListBeginAt + lookupTableOffset;
reader.BaseStream.Seek(lookupTablePos, SeekOrigin.Begin);
ushort lookupType = reader.ReadUInt16(); //Each Lookup table may contain only one type of information (LookupType)
ushort lookupFlags = reader.ReadUInt16();
ushort subTableCount = reader.ReadUInt16();
//Each LookupType is defined with one or more subtables, and each subtable definition provides a different representation format
ushort[] subTableOffsets = Utils.ReadUInt16Array(reader, subTableCount);
ushort markFilteringSet =
((lookupFlags & 0x0010) == 0x0010) ? reader.ReadUInt16() : (ushort)0;
ReadLookupTable(reader,
lookupTablePos,
lookupType,
lookupFlags,
subTableOffsets, //Array of offsets to SubTables-from beginning of Lookup table
markFilteringSet);
}
}
protected abstract void ReadLookupTable(BinaryReader reader, long lookupTablePos,
ushort lookupType, ushort lookupFlags,
ushort[] subTableOffsets, ushort markFilteringSet);
protected abstract void ReadFeatureVariations(BinaryReader reader, long featureVariationsBeginAt);
}
}

View File

@@ -0,0 +1,35 @@
//Apache2, 2016-present, WinterDev
namespace Typography.OpenFont.Tables
{
/// <summary>
/// replaceable glyph index list
/// </summary>
public interface IGlyphIndexList
{
int Count { get; }
ushort this[int index] { get; }
/// <summary>
/// remove:add_new 1:1
/// </summary>
/// <param name="index"></param>
/// <param name="newGlyphIndex"></param>
void Replace(int index, ushort newGlyphIndex);
/// <summary>
/// remove:add_new >=1:1
/// </summary>
/// <param name="index"></param>
/// <param name="removeLen"></param>
/// <param name="newGlyphIndex"></param>
void Replace(int index, int removeLen, ushort newGlyphIndex);
/// <summary>
/// remove: add_new 1:>=1
/// </summary>
/// <param name="index"></param>
/// <param name="newGlyphIndices"></param>
void Replace(int index, ushort[] newGlyphIndices);
}
}

View File

@@ -0,0 +1,290 @@
//MIT, 2019-present, WinterDev
using System;
using System.Collections.Generic;
using System.IO;
namespace Typography.OpenFont.Tables
{
/// <summary>
/// The Justification table
/// </summary>
public partial class JSTF : TableEntry
{
//https://docs.microsoft.com/en-us/typography/opentype/spec/jstf
public const string _N = "JSTF";
public override string Name => _N;
JstfScriptTable[] _jsftScriptTables;
//The Justification table(JSTF) provides font developers with additional control over glyph substitution and
//positioning in justified text.
//Text-processing clients now have more options to expand or
//shrink word and glyph spacing so text fills the specified line length.
protected override void ReadContentFrom(BinaryReader reader)
{
//test this with Arial font
//JSTF header
//Type Name Description
//uint16 majorVersion Major version of the JSTF table, = 1
//uint16 minorVersion Minor version of the JSTF table, = 0
//uint16 jstfScriptCount Number of JstfScriptRecords in this table
//JstfScriptRecord jstfScriptRecords[jstfScriptCount] Array of JstfScriptRecords, in alphabetical order by jstfScriptTag
//----------
//JstfScriptRecord
//Type Name Description
//Tag jstfScriptTag 4-byte JstfScript identification
//Offset16 jstfScriptOffset Offset to JstfScript table, from beginning of JSTF Header
long tableStartAt = reader.BaseStream.Position;
//
ushort majorVersion = reader.ReadUInt16();
ushort minorVersion = reader.ReadUInt16();
ushort jstfScriptCount = reader.ReadUInt16();
JstfScriptRecord[] recs = new JstfScriptRecord[jstfScriptCount];
for (int i = 0; i < recs.Length; ++i)
{
recs[i] = new JstfScriptRecord(
Utils.TagToString(reader.ReadUInt32()),
reader.ReadUInt16()
);
}
_jsftScriptTables = new JstfScriptTable[recs.Length];
for (int i = 0; i < recs.Length; ++i)
{
JstfScriptRecord rec = recs[i];
reader.BaseStream.Position = tableStartAt + rec.jstfScriptOffset;
JstfScriptTable jstfScriptTable = ReadJstfScriptTable(reader);
jstfScriptTable.ScriptTag = rec.jstfScriptTag;
_jsftScriptTables[i] = jstfScriptTable;
}
}
readonly struct JstfScriptRecord
{
public readonly string jstfScriptTag;
public readonly ushort jstfScriptOffset;
public JstfScriptRecord(string jstfScriptTag, ushort jstfScriptOffset)
{
this.jstfScriptTag = jstfScriptTag;
this.jstfScriptOffset = jstfScriptOffset;
}
}
public class JstfScriptTable
{
public ushort[] extenderGlyphs;
public JstfLangSysRecord defaultLangSys;
public JstfLangSysRecord[] other;
public JstfScriptTable()
{
}
public string ScriptTag { get; set; }
#if DEBUG
public override string ToString()
{
return ScriptTag;
}
#endif
}
static JstfScriptTable ReadJstfScriptTable(BinaryReader reader)
{
//A Justification Script(JstfScript) table describes the justification information for a single script.
//It consists of an offset to a table that defines extender glyphs(extenderGlyphOffset),
//an offset to a default justification table for the script (defJstfLangSysOffset),
//and a count of the language systems that define justification data(jstfLangSysCount).
//If a script uses the same justification information for all language systems,
//the font developer defines only the default JstfLangSys table and
//sets the jstfLangSysCount value to zero(0).
//However, if any language system has unique justification suggestions,
//jstfLangSysCount will be a positive value,
//and the JstfScript table must include an array of records(jstfLangSysRecords),
//one for each language system.Each JstfLangSysRecord contains a language system tag(jstfLangSysTag) and
//an offset to a justification language system table(jstfLangSysOffset).
//In the jstfLangSysRecords array, records are ordered alphabetically by jstfLangSysTag.
//JstfScript table
//Type Name Description
//Offset16 extenderGlyphOffset Offset to ExtenderGlyph table, from beginning of JstfScript table(may be NULL)
//Offset16 defJstfLangSysOffset Offset to default JstfLangSys table, from beginning of JstfScript table(may be NULL)
//uint16 jstfLangSysCount Number of JstfLangSysRecords in this table - may be zero(0)
//JstfLangSysRecord jstfLangSysRecords[jstfLangSysCount] Array of JstfLangSysRecords, in alphabetical order by JstfLangSysTag
JstfScriptTable jstfScriptTable = new JstfScriptTable();
long tableStartAt = reader.BaseStream.Position;
ushort extenderGlyphOffset = reader.ReadUInt16();
ushort defJstfLangSysOffset = reader.ReadUInt16();
ushort jstfLangSysCount = reader.ReadUInt16();
if (jstfLangSysCount > 0)
{
JstfLangSysRecord[] recs = new JstfLangSysRecord[jstfLangSysCount];
for (int i = 0; i < jstfLangSysCount; ++i)
{
recs[i] = ReadJstfLangSysRecord(reader);
}
jstfScriptTable.other = recs;
}
if (extenderGlyphOffset > 0)
{
reader.BaseStream.Position = tableStartAt + extenderGlyphOffset;
jstfScriptTable.extenderGlyphs = ReadExtenderGlyphTable(reader);
}
if (defJstfLangSysOffset > 0)
{
reader.BaseStream.Position = tableStartAt + defJstfLangSysOffset;
jstfScriptTable.defaultLangSys = ReadJstfLangSysRecord(reader);
}
return jstfScriptTable;
}
static ushort[] ReadExtenderGlyphTable(BinaryReader reader)
{
//Extender Glyph Table
//The Extender Glyph table(ExtenderGlyph) lists indices of glyphs, 3
//such as Arabic kashidas,
//that a client may insert to extend the length of the line for justification.
//The table consists of a count of the extender glyphs for the script (glyphCount) and
//an array of extender glyph indices(extenderGlyphs), arranged in increasing numerical order.
//ExtenderGlyph table
//Type Name Description
//uint16 glyphCount Number of extender glyphs in this script
//uint16 extenderGlyphs[glyphCount] Extender glyph IDs — in increasing numerical order
ushort glyphCount = reader.ReadUInt16();
return Utils.ReadUInt16Array(reader, glyphCount);
}
public struct JstfLangSysRecord
{
public JstfPriority[] jstfPriority;
}
static JstfLangSysRecord ReadJstfLangSysRecord(BinaryReader reader)
{
//Justification Language System Table
//The Justification Language System(JstfLangSys) table contains an array of justification suggestions,
//ordered by priority.
//A text-processing client doing justification should begin with the suggestion that has a zero(0) priority,
//and then-as necessary - apply suggestions of increasing priority until the text is justified.
//The font developer defines the number and the meaning of the priority levels.
//Each priority level stands alone; its suggestions are not added to the previous levels.
//The JstfLangSys table consists of a count of the number of priority levels(jstfPriorityCount) and
//an array of offsets to Justification Priority tables(jstfPriorityOffsets),
//stored in priority order.
//JstfLangSys table
//stfLangSys table
//Type Name Description
//uint16 jstfPriorityCount Number of JstfPriority tables
//Offset16 jstfPriorityOffsets[jstfPriorityCount] Array of offsets to JstfPriority tables, from beginning of JstfLangSys table, in priority order
long tableStartAt = reader.BaseStream.Position;
ushort jstfPriorityCount = reader.ReadUInt16();
ushort[] jstfPriorityOffsets = Utils.ReadUInt16Array(reader, jstfPriorityCount);
JstfPriority[] jstPriorities = new JstfPriority[jstfPriorityCount];
for (int i = 0; i < jstfPriorityOffsets.Length; ++i)
{
reader.BaseStream.Position = tableStartAt + jstfPriorityOffsets[i];
jstPriorities[i] = ReadJstfPriority(reader);
}
return new JstfLangSysRecord() { jstfPriority = jstPriorities };
}
public class JstfPriority
{
//JstfPriority table
//Type Name Description
//Offset16 shrinkageEnableGSUB Offset to shrinkage-enable JstfGSUBModList table, from beginning of JstfPriority table(may be NULL)
//Offset16 shrinkageDisableGSUB Offset to shrinkage-disable JstfGSUBModList table, from beginning of JstfPriority table(may be NULL)
public ushort shrinkageEnableGSUB;
public ushort shrinkageDisableGSUB;
//Offset16 shrinkageEnableGPOS Offset to shrinkage-enable JstfGPOSModList table, from beginning of JstfPriority table(may be NULL)
//Offset16 shrinkageDisableGPOS Offset to shrinkage-disable JstfGPOSModList table, from beginning of JstfPriority table(may be NULL)
public ushort shrinkageEnableGPOS;
public ushort shrinkageDisableGPOS;
//Offset16 shrinkageJstfMax Offset to shrinkage JstfMax table, from beginning of JstfPriority table(may be NULL)
public ushort shrinkageJstfMax;
//Offset16 extensionEnableGSUB Offset to extension-enable JstfGSUBModList table, from beginnning of JstfPriority table(may be NULL)
//Offset16 extensionDisableGSUB Offset to extension-disable JstfGSUBModList table, from beginning of JstfPriority table(may be NULL)
public ushort extensionEnableGSUB;
public ushort extensionDisableGSUB;
//Offset16 extensionEnableGPOS Offset to extension-enable JstfGPOSModList table, from beginning of JstfPriority table(may be NULL)
//Offset16 extensionDisableGPOS Offset to extension-disable JstfGPOSModList table, from beginning of JstfPriority table(may be NULL)
public ushort extensionEnableGPOS;
public ushort extensionDisableGPOS;
//Offset16 extensionJstfMax Offset to extension JstfMax table, from beginning of JstfPriority table(may be NULL)
public ushort extensionJstfMax;
}
static JstfPriority ReadJstfPriority(BinaryReader reader)
{
//Justification Priority Table
//A Justification Priority(JstfPriority)
//table defines justification suggestions for a single priority level.
//Each priority level specifies whether to enable or disable GSUB and GPOS lookups or
//apply text justification lookups to shrink and extend lines of text.
//JstfPriority has offsets to four tables with line shrinkage data:
//two are JstfGSUBModList tables for enabling and disabling glyph substitution lookups, and
//two are JstfGPOSModList tables for enabling and disabling glyph positioning lookups.
//Offsets to JstfGSUBModList and JstfGPOSModList tables also are defined for line extension.
return new JstfPriority()
{
shrinkageEnableGSUB = reader.ReadUInt16(),
shrinkageDisableGSUB = reader.ReadUInt16(),
shrinkageEnableGPOS = reader.ReadUInt16(),
shrinkageDisableGPOS = reader.ReadUInt16(),
shrinkageJstfMax = reader.ReadUInt16(),
extensionEnableGSUB = reader.ReadUInt16(),
extensionDisableGSUB = reader.ReadUInt16(),
extensionEnableGPOS = reader.ReadUInt16(),
extensionDisableGPOS = reader.ReadUInt16(),
extensionJstfMax = reader.ReadUInt16(),
};
}
}
}

View File

@@ -0,0 +1,215 @@
//Apache2, 2016-present, WinterDev
using System.IO;
namespace Typography.OpenFont.Tables
{
//from https://docs.microsoft.com/en-us/typography/opentype/spec/gdef
//Ligature Caret List Table
//The Ligature Caret List table (LigCaretList) defines caret positions for all the ligatures in a font.
//The table consists of an offset to a Coverage table that lists all the ligature glyphs (Coverage),
//a count of the defined ligatures (LigGlyphCount),
//and an array of offsets to LigGlyph tables (LigGlyph).
//The array lists the LigGlyph tables,
//one for each ligature in the Coverage table, in the same order as the Coverage Index.
//Example 4 at the end of this chapter shows a LigCaretList table.
//LigCaretList table
//Type Name Description
//Offset16 Coverage Offset to Coverage table - from beginning of LigCaretList table
//uint16 LigGlyphCount Number of ligature glyphs
//Offset16 LigGlyph[LigGlyphCount] Array of offsets to LigGlyph tables-from beginning of LigCaretList table-in Coverage Index order
/// <summary>
/// Ligature Caret List Table, defines caret positions for all the ligatures in a font
/// </summary>
class LigCaretList
{
LigGlyph[] _ligGlyphs;
CoverageTable _coverageTable;
public static LigCaretList CreateFrom(BinaryReader reader, long beginAt)
{
reader.BaseStream.Seek(beginAt, SeekOrigin.Begin);
//----
LigCaretList ligcaretList = new LigCaretList();
ushort coverageOffset = reader.ReadUInt16();
ushort ligGlyphCount = reader.ReadUInt16();
ushort[] ligGlyphOffsets = Utils.ReadUInt16Array(reader, ligGlyphCount);
LigGlyph[] ligGlyphs = new LigGlyph[ligGlyphCount];
for (int i = 0; i < ligGlyphCount; ++i)
{
ligGlyphs[i] = LigGlyph.CreateFrom(reader, beginAt + ligGlyphOffsets[i]);
}
ligcaretList._ligGlyphs = ligGlyphs;
ligcaretList._coverageTable = CoverageTable.CreateFrom(reader, beginAt + coverageOffset);
return ligcaretList;
}
}
//A Ligature Glyph table (LigGlyph) contains the caret coordinates for a single ligature glyph.
//The number of coordinate values, each defined in a separate CaretValue table,
//equals the number of components in the ligature minus one (1).***
//The LigGlyph table consists of a count of the number of CaretValue tables defined for the ligature (CaretCount) and
//an array of offsets to CaretValue tables (CaretValue).
//Example 4 at the end of the chapter shows a LigGlyph table.
//LigGlyph table
//Type Name Description
//uint16 CaretCount Number of CaretValues for this ligature (components - 1)
//Offset16 CaretValue[CaretCount] Array of offsets to CaretValue tables-from beginning of LigGlyph table-in increasing coordinate order Caret Values Table
/// <summary>
/// A Ligature Glyph table (LigGlyph) contains the caret coordinates for a single ligature glyph.
/// </summary>
class LigGlyph
{
ushort[] _caretValueOffsets;
public static LigGlyph CreateFrom(BinaryReader reader, long beginAt)
{
reader.BaseStream.Seek(beginAt, SeekOrigin.Begin);
//----------
LigGlyph ligGlyph = new LigGlyph();
ushort caretCount = reader.ReadUInt16();
ligGlyph._caretValueOffsets = Utils.ReadUInt16Array(reader, caretCount);
return ligGlyph;
}
}
//A Caret Values table (CaretValues), which defines caret positions for a ligature,
//can be any of three possible formats.
//One format uses design units to define the caret position.
//The other two formats use a contour point or (in non-variable fonts) a Device table to fine-tune a caret's position at specific font sizes
//and device resolutions.
//In a variable font, the third format uses a VariationIndex table (a variant of a Device table)
//to reference variation data for adjustment of the caret position for the current variation instance, as needed.
//Caret coordinates are either X or Y values, depending upon the text direction.
/// <summary>
/// A Caret Values table (CaretValues)
/// </summary>
class CaretValues
{
}
//-------------------------
//CaretValue Format 1
//-------------------------
//The first format (CaretValueFormat1) consists of a format identifier (CaretValueFormat),
//followed by a single coordinate for the caret position (Coordinate). The Coordinate is in design units.
//This format has the benefits of small size and simplicity, but the Coordinate value cannot be hinted for fine adjustments at different device resolutions.
//Example 4 at the end of this chapter shows a CaretValueFormat1 table.
//-------------------------
//CaretValueFormat1 table: Design units only
//-------------------------
//Type Name Description
//uint16 CaretValueFormat Format identifier-format = 1
//int16 Coordinate X or Y value, in design units
//-------------------------
//NOTE: int16
//
//
//CaretValue Format 2
//
//The second format (CaretValueFormat2) specifies the caret coordinate in terms of a contour point index on a specific glyph.
//During font hinting, the contour point on the glyph outline may move.
//The point's final position after hinting provides the final value for rendering a given font size.
//The table contains a format identifier (CaretValueFormat) and a contour point index (CaretValuePoint).
//Example 5 at the end of this chapter demonstrates a CaretValueFormat2 table.
//-------------------------
//CaretValueFormat2 table: Contour point
//Type Name Description
//uint16 CaretValueFormat Format identifier-format = 2
//uint16 CaretValuePoint Contour point index on glyph
//-------------------------
//
//CaretValue Format 3
//The third format (CaretValueFormat3) also specifies the value in design units, but,
//in non-variable fonts, it uses a Device table rather than a contour point to adjust the value.
//This format offers the advantage of fine-tuning the Coordinate value for any device resolution.
//(For more information about Device tables, see the chapter, Common Table Formats.)
//In variable fonts, CaretValueFormat3 must be used to reference variation data to adjust caret positions for different variation instances,
//if needed. In this case, CaretValueFormat3 specifies an offset to a VariationIndex table, which is a variant of the Device table used for variations.
// Note: While separate VariationIndex table references are required for each value that requires variation,
//two or more values that require the same variation-data values can have offsets that point to the same VariationIndex table,
//and two or more VariationIndex tables can reference the same variation data entries.
// Note: If no VariationIndex table is used for a particular caret position value, then that value is used for all variation instances.
//The format consists of a format identifier (CaretValueFormat), an X or Y value (Coordinate), and an offset to a Device or VariationIndex table.
//Example 6 at the end of this chapter shows a CaretValueFormat3 table.
//-------------------------
//CaretValueFormat3 table: Design units plus Device or VariationIndex table
//Type Name Description
//uint16 CaretValueFormat Format identifier-format = 3
//int16 Coordinate X or Y value, in design units
//Offset16 DeviceTable Offset to Device table (non-variable font) / Variation Index table (variable font) for X or Y value-from beginning of CaretValue table
//-------------------------------------------------------------------------------
//NOTE: Offset16
//-------------------------------------------------------------------------------
//
//Mark Attachment Class Definition Table
//A Mark Attachment Class Definition Table defines the class to which a mark glyph may belong.
//This table uses the same format as the Class Definition table (for details, see the chapter, Common Table Formats ).
//Example 7 in this document shows a MarkAttachClassDef table.
//Mark Glyph Sets Table
//Mark glyph sets are used in GSUB and GPOS lookups to filter which marks in a string are considered or ignored.
//Mark glyph sets are defined in a MarkGlyphSets table, which contains offsets to individual sets each represented by a standard Coverage table:
//---------------------------------------------------------
//MarkGlyphSetsTable
//---------------------------------------------------------
//Type Name Description
//uint16 MarkSetTableFormat Format identifier == 1
//uint16 MarkSetCount Number of mark sets defined
//Offset32 Coverage [MarkSetCount] Array of offsets to mark set coverage tables.
//---------------------------------------------------------
//Mark glyph sets are used for the same purpose as mark attachment classes, which is as filters for GSUB and GPOS lookups.
//Mark glyph sets differ from mark attachment classes, however,
//in that mark glyph sets may intersect as needed by the font developer.
//As for mark attachment classes, only one mark glyph set can be referenced in any given lookup.
//Note that the array of offsets for the Coverage tables uses ULONG, not Offset. ***
class MarkGlyphSetsTable
{
ushort _format;
uint[] _coverageOffset;
public static MarkGlyphSetsTable CreateFrom(BinaryReader reader, long beginAt)
{
reader.BaseStream.Seek(beginAt, SeekOrigin.Begin);
//
MarkGlyphSetsTable markGlyphSetsTable = new MarkGlyphSetsTable();
markGlyphSetsTable._format = reader.ReadUInt16();
ushort markSetCount = reader.ReadUInt16();
uint[] coverageOffset = markGlyphSetsTable._coverageOffset = new uint[markSetCount];
for (int i = 0; i < markSetCount; ++i)
{
//Note that the array of offsets for the Coverage tables uses ULONG
coverageOffset[i] = reader.ReadUInt32();//
}
return markGlyphSetsTable;
}
}
}

View File

@@ -0,0 +1,57 @@
//Apache2, 2016-present, WinterDev
using System.Collections.Generic;
using System.IO;
namespace Typography.OpenFont.Tables
{
public class ScriptList : Dictionary<uint, ScriptTable>
{
//https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2
// The ScriptList identifies the scripts in a font,
// each of which is represented by a Script table that contains script and language-system data.
// Language system tables reference features, which are defined in the FeatureList.
// Each feature table references the lookup data defined in the LookupList that describes how, when, and where to implement the feature.
private ScriptList() { }
public new ScriptTable this[uint tagName] => TryGetValue(tagName, out ScriptTable ret) ? ret : null;
public static ScriptList CreateFrom(BinaryReader reader, long beginAt)
{
// ScriptList table
// Type Name Description
// uint16 scriptCount Number of ScriptRecords
// ScriptRecord scriptRecords[scriptCount] Array of ScriptRecords,listed alphabetically by ScriptTag
//
// ScriptRecord
// Type Name Description
// Tag scriptTag 4-byte ScriptTag identifier
// Offset16 scriptOffset Offset to Script table-from beginning of ScriptList
reader.BaseStream.Seek(beginAt, SeekOrigin.Begin);
ushort scriptCount = reader.ReadUInt16();
ScriptList scriptList = new ScriptList();
// Read records (tags and table offsets)
uint[] scriptTags = new uint[scriptCount];
ushort[] scriptOffsets = new ushort[scriptCount];
for (int i = 0; i < scriptCount; ++i)
{
scriptTags[i] = reader.ReadUInt32();
scriptOffsets[i] = reader.ReadUInt16();
}
// Read each table and add it to the dictionary
for (int i = 0; i < scriptCount; ++i)
{
ScriptTable scriptTable = ScriptTable.CreateFrom(reader, beginAt + scriptOffsets[i]);
scriptTable.scriptTag = scriptTags[i];
scriptList.Add(scriptTags[i], scriptTable);
}
return scriptList;
}
}
}

View File

@@ -0,0 +1,173 @@
//Apache2, 2016-present, WinterDev
//https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#script-table-and-language-system-record
using System.IO;
namespace Typography.OpenFont.Tables
{
//Script Table and Language System Record
//A Script table identifies each language system that defines how to use the glyphs in a script for a particular language.
//It also references a default language system that defines how to use the script's glyphs in the absence of language-specific knowledge.
//A Script table begins with an offset to the Default Language System table (DefaultLangSys),
//which defines the set of features that regulate the default behavior of the script.
//Next, Language System Count (LangSysCount) defines the number of language systems (excluding the DefaultLangSys) that use the script.
//In addition, an array of Language System Records (LangSysRecord) defines each language system (excluding the default)
//with an identification tag (LangSysTag) and an offset to a Language System table (LangSys).
//The LangSysRecord array stores the records alphabetically by LangSysTag.
//If no language-specific script behavior is defined, the LangSysCount is set to zero (0), and no LangSysRecords are allocated.
//-----------------------
//Script table
//Type Name Description
//Offset16 defaultLangSys Offset to DefaultLangSys table-from beginning of Script table-may be NULL
//uint16 langSysCount Number of LangSysRecords for this script-excluding the DefaultLangSys
//LangSysRecord langSysRecords[langSysCount] Array of LangSysRecords-listed alphabetically by LangSysTag
//-----------------------
//LangSysRecord
//Type Name Description
//Tag langSysTag 4-byte LangSysTag identifier
//Offset16 langSysOffset Offset to LangSys table-from beginning of Script table
//-----------------------
//
//Language System Table
//-----------------------
//The Language System table (LangSys) identifies language-system features
//used to render the glyphs in a script. (The LookupOrder offset is reserved for future use.)
//Optionally, a LangSys table may define a Required Feature Index (ReqFeatureIndex) to specify one feature as required
//within the context of a particular language system. For example, in the Cyrillic script,
//the Serbian language system always renders certain glyphs differently than the Russian language system.
//Only one feature index value can be tagged as the ReqFeatureIndex.
//This is not a functional limitation, however, because the feature and lookup definitions in OpenType
//Layout are structured so that one feature table can reference many glyph substitution and positioning lookups.
//When no required features are defined, then the ReqFeatureIndex is set to 0xFFFF.
//All other features are optional. For each optional feature,
//a zero-based index value references a record (FeatureRecord) in the FeatureRecord array,
//which is stored in a Feature List table (FeatureList).
//The feature indices themselves (excluding the ReqFeatureIndex) are stored in arbitrary order in the FeatureIndex array.
//The FeatureCount specifies the total number of features listed in the FeatureIndex array.
//Features are specified in full in the FeatureList table, FeatureRecord, and Feature table,
//which are described later in this chapter.
//Example 2 at the end of this chapter shows a Script table, LangSysRecord, and LangSys table used for contextual positioning in the Arabic script.
//---------------------
//LangSys table
//Type Name Description
//Offset16 lookupOrder = NULL (reserved for an offset to a reordering table)
//uint16 requiredFeatureIndex Index of a feature required for this language system- if no required features = 0xFFFF
//uint16 featureIndexCount Number of FeatureIndex values for this language system-excludes the required feature
//uint16 featureIndices[featureIndexCount] Array of indices into the FeatureList-in arbitrary order
//---------------------
public class ScriptTable
{
public uint scriptTag { get; internal set; }
public LangSysTable defaultLang { get; private set; }// be NULL
public LangSysTable[] langSysTables { get; private set; }
public string ScriptTagName => Utils.TagToString(this.scriptTag);
public static ScriptTable CreateFrom(BinaryReader reader, long beginAt)
{
reader.BaseStream.Seek(beginAt, SeekOrigin.Begin);
//---------------
//Script table
//Type Name Description
//Offset16 defaultLangSys Offset to DefaultLangSys table-from beginning of Script table-may be NULL
//uint16 langSysCount Number of LangSysRecords for this script-excluding the DefaultLangSys
//LangSysRecord langSysRecords[langSysCount] Array of LangSysRecords-listed alphabetically by LangSysTag
//---------------
ScriptTable scriptTable = new ScriptTable();
ushort defaultLangSysOffset = reader.ReadUInt16();
ushort langSysCount = reader.ReadUInt16();
LangSysTable[] langSysTables = scriptTable.langSysTables = new LangSysTable[langSysCount];
for (int i = 0; i < langSysCount; ++i)
{
//-----------------------
//LangSysRecord
//Type Name Description
//Tag langSysTag 4-byte LangSysTag identifier
//Offset16 langSysOffset Offset to LangSys table-from beginning of Script table
//-----------------------
langSysTables[i] = new LangSysTable(
reader.ReadUInt32(), // 4-byte LangSysTag identifier
reader.ReadUInt16()); //offset
}
//-----------
if (defaultLangSysOffset > 0)
{
scriptTable.defaultLang = new LangSysTable(0, defaultLangSysOffset);
reader.BaseStream.Seek(beginAt + defaultLangSysOffset, SeekOrigin.Begin);
scriptTable.defaultLang.ReadFrom(reader);
}
//-----------
//read actual content of each table
for (int i = 0; i < langSysCount; ++i)
{
LangSysTable langSysTable = langSysTables[i];
reader.BaseStream.Seek(beginAt + langSysTable.offset, SeekOrigin.Begin);
langSysTable.ReadFrom(reader);
}
return scriptTable;
}
#if DEBUG
public override string ToString()
{
return Utils.TagToString(this.scriptTag);
}
#endif
public class LangSysTable
{
//The Language System table (LangSys) identifies language-system features
//used to render the glyphs in a script. (The LookupOrder offset is reserved for future use.)
//
public uint langSysTagIden { get; private set; }
internal readonly ushort offset;
//
public ushort[] featureIndexList { get; private set; }
public ushort RequiredFeatureIndex { get; private set; }
public LangSysTable(uint langSysTagIden, ushort offset)
{
this.offset = offset;
this.langSysTagIden = langSysTagIden;
}
public void ReadFrom(BinaryReader reader)
{
//---------------------
//LangSys table
//Type Name Description
//Offset16 lookupOrder = NULL (reserved for an offset to a reordering table)
//uint16 requiredFeatureIndex Index of a feature required for this language system- if no required features = 0xFFFF
//uint16 featureIndexCount Number of FeatureIndex values for this language system-excludes the required feature
//uint16 featureIndices[featureIndexCount] Array of indices into the FeatureList-in arbitrary order
//---------------------
ushort lookupOrder = reader.ReadUInt16();//reserve
RequiredFeatureIndex = reader.ReadUInt16();
ushort featureIndexCount = reader.ReadUInt16();
featureIndexList = Utils.ReadUInt16Array(reader, featureIndexCount);
}
public bool HasRequireFeature => RequiredFeatureIndex != 0xFFFF;
public string LangSysTagIdenString => (langSysTagIden == 0) ? "" : Utils.TagToString(langSysTagIden);
#if DEBUG
public override string ToString() => LangSysTagIdenString;
#endif
}
}
}