Files
QuickLook/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/ClassDefTable.cs
2024-12-30 04:21:24 +08:00

206 lines
9.2 KiB
C#

//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;
}
}
}
}
}