Skip to content

Instantly share code, notes, and snippets.

@pratikmmohite
Created March 3, 2024 10:39
Show Gist options
  • Save pratikmmohite/21bbe9d5c10fe32ffde084423b961986 to your computer and use it in GitHub Desktop.
Save pratikmmohite/21bbe9d5c10fe32ffde084423b961986 to your computer and use it in GitHub Desktop.
TTF GSUB table parser
/*
* Copyright (C) 2017, Pratik Mohite <dev.pratikm@gmail.com>
*
* Auther: Pratik Mohite (dev.pratikm@gmail.com)
* Source: https://github.com/pratikmmohite
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* Resources
https://learn.microsoft.com/en-us/typography/opentype/spec/gsub
https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2
*/
import 'dart:io';
import 'dart:typed_data';
typedef UpdatedPosition = void Function(int nextBytePos);
extension ByteDataExtensions on ByteData {
String readString(int byteOffset,
[int count = 4, UpdatedPosition? nextBytePosition]) {
assert(lengthInBytes > byteOffset + count, 'Overflow: lengthInBytes > byteOffset + $count');
var str = '';
for (var i = 0; i < count; i++) {
str += String.fromCharCode(getInt8(byteOffset + i));
}
if (nextBytePosition != null) {
nextBytePosition(byteOffset + count);
}
return str;
}
int readUint16(int byteOffset, [UpdatedPosition? nextBytePosition]) {
assert(lengthInBytes > byteOffset + 2, 'Overflow: lengthInBytes > byteOffset + 2');
if (nextBytePosition != null) {
nextBytePosition(byteOffset + 2);
}
return getUint16(byteOffset);
}
int readInt16(int byteOffset, [UpdatedPosition? nextBytePosition]) {
assert(lengthInBytes > byteOffset + 2, 'Overflow: lengthInBytes > byteOffset + 2');
if (nextBytePosition != null) {
nextBytePosition(byteOffset + 2);
}
return getInt16(byteOffset);
}
}
import 'dart:io';
class TtfComment{
List<String> comments = [];
void putComment(String str, int padLeft){
String prefix = '|${''.padLeft(padLeft*2, '-')}';
var comment = prefix+ str;
print(comment);
comments.add(comment);
}
void printComments(){
print(comments.join('\n'));
}
void saveComments(){
File file = File('ttf_log.txt');
file.writeAsStringSync(comments.join('\n'));
}
}
class GsubTable extends TtfComment {
final ByteData byteData;
final int gsubTablePos;
GsubTable(this.byteData, this.gsubTablePos);
void extractGsubTable() {
// Read GSUB table header
int majorVersion = byteData.getUint16(gsubTablePos);
int minorVersion = byteData.getUint16(gsubTablePos + 2);
int scriptListOffset = byteData.getUint16(gsubTablePos + 4);
int featureListOffset = byteData.getUint16(gsubTablePos + 6);
int lookupListOffset = byteData.getUint16(gsubTablePos + 8);
putComment('''GSUB Table Header: Version: $majorVersion.$minorVersion ,
Script List Offset: $scriptListOffset,
Feature List Offset: $featureListOffset,
Lookup List Offset: $lookupListOffset''', 0);
scriptList(byteData, gsubTablePos + scriptListOffset);
featureList(byteData, gsubTablePos + featureListOffset);
parseLookListTable(byteData, gsubTablePos + lookupListOffset);
saveComments();
}
//#region ScriptTable
void scriptList(ByteData bytes, int scriptListOffset) {
int currentPos = scriptListOffset;
int padLeft = 1;
putComment('Script List:', padLeft);
int scriptCount =
bytes.readUint16(currentPos, (nextBytePos) => currentPos = nextBytePos);
for (int i = 0; i < scriptCount; i++) {
var scriptTag = bytes.readString(
currentPos, 4, ((nextBytePos) => currentPos = nextBytePos));
int scriptOffset = bytes.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('Script Tag: $scriptTag, Offset: $scriptOffset', padLeft + 1);
parserScriptTable(bytes, scriptListOffset + scriptOffset);
}
}
void parserScriptTable(ByteData bytes, int scriptTableOffset) {
int currentPos = scriptTableOffset;
int padLeft = 2;
int defaultLangSysOffset =
bytes.readUint16(currentPos, (nextBytePos) => currentPos = nextBytePos);
int langSysCount =
bytes.readUint16(currentPos, (nextBytePos) => currentPos = nextBytePos);
putComment(
'defaultLangSysOffset: $defaultLangSysOffset langSysCount: $langSysCount',
padLeft);
parseLangTable(bytes, scriptTableOffset + defaultLangSysOffset);
for (int i = 0; i < langSysCount; i++) {
String langSysTag = bytes.readString(
currentPos, 4, (nextBytePos) => currentPos = nextBytePos);
int langSysOffset = bytes.readUint16(
currentPos, (nextBytePos) => currentPos = nextBytePos);
putComment(
'langSysTag: $langSysTag langSysOffset: $langSysOffset', padLeft + 1);
parseLangTable(bytes, scriptTableOffset + langSysOffset);
}
}
void parseLangTable(ByteData bytes, int langSysOffset) {
int currentPos = langSysOffset;
int padLeft = 3;
int lookupOrderOffset =
bytes.readUint16(currentPos, (nextBytePos) => currentPos = nextBytePos);
int requiredFeatureIndex =
bytes.readUint16(currentPos, (nextBytePos) => currentPos = nextBytePos);
int featureIndexCount =
bytes.readUint16(currentPos, (nextBytePos) => currentPos = nextBytePos);
putComment(
'lookupOrderOffset: $lookupOrderOffset, requiredFeatureIndex: $requiredFeatureIndex, featureIndexCount: $featureIndexCount',
padLeft);
for (int i = 0; i < featureIndexCount; i++) {
int featureIndice = bytes.readUint16(
currentPos, (nextBytePos) => currentPos = nextBytePos);
putComment('$featureIndice', padLeft + 1);
}
}
//#endregion ScriptTable
//#region FeatureList
void featureList(ByteData bytes, int featureListOffset) {
int currentPos = featureListOffset;
int padLeft = 1;
putComment('Feature List:', padLeft);
int featureCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
for (int i = 0; i < featureCount; i++) {
String featureTag = byteData.readString(
currentPos, 4, ((nextBytePos) => currentPos = nextBytePos));
int featureOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('featureTag: $featureTag, featureOffset: $featureOffset',
padLeft + 1);
parseFeatureTable(bytes, featureListOffset + featureOffset);
}
}
void parseFeatureTable(ByteData bytes, int featureOffset) {
int currentPos = featureOffset;
int padLeft = 3;
int featureParamsOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
int lookupIndexCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment(
'featureParamsOffset: $featureParamsOffset, lookupIndexCount: $lookupIndexCount',
padLeft);
for (int i = 0; i < lookupIndexCount; i++) {
int lookupListIndice = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('lookupListIndice: $lookupListIndice', padLeft + 1);
}
if (featureParamsOffset == 0) {
// TODO: Do nothing
}
}
//#endregion FeatureList
//#region LookupList
void parseLookListTable(ByteData bytes, int lookupListOffset) {
int currentPos = lookupListOffset;
int padLeft = 1;
putComment('Lookup List:', padLeft);
int lookupCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('lookupCount: $lookupCount', padLeft + 1);
for (int i = 0; i < lookupCount; i++) {
int lookupOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
parseLookupTable(byteData, lookupListOffset + lookupOffset);
}
}
void parseLookupTable(ByteData bytes, int lookupTableOffset) {
int currentPos = lookupTableOffset;
int padLeft = 3;
int lookupType = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
int lookupFlag = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
int subTableCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment(
'lookupType: $lookupType, lookupFlag: $lookupFlag, subTableCount: $subTableCount',
padLeft);
for (int i = 0; i < subTableCount; i++) {
int subtableOffsets = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
switch (lookupType) {
case 1:
parseLookupSingleSubstitutionFormat(
bytes, lookupTableOffset + subtableOffsets);
break;
case 2:
parseLookupMultipleSubstitutionFormat(
bytes, lookupTableOffset + subtableOffsets);
break;
case 3:
parseLookupMultipleAlternateSubstitutionFormat(
bytes, lookupTableOffset + subtableOffsets);
break;
case 4:
parseLookupLigatureSubstitutionSubtable(
bytes, lookupTableOffset + subtableOffsets);
break;
case 5:
parseLookupContextualSubstitutionSubtable(
bytes, lookupTableOffset + subtableOffsets);
break;
case 6:
parseLookupChainedContextsSubstitutionSubtable(
bytes, lookupTableOffset + subtableOffsets);
break;
case 7:
parseLookupExtensionSubstitutionSubtable(
bytes, lookupTableOffset + subtableOffsets);
break;
case 8:
parseLookupReverseChainingContextualSingleSubstitutionSubtable(
bytes, lookupTableOffset + subtableOffsets);
break;
}
}
int markFilteringSet = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('markFilteringSet: $markFilteringSet', padLeft);
}
// Single (format 1.1 1.2) Replace one glyph with one glyph
void parseLookupSingleSubstitutionFormat(
ByteData bytes, int lookupSubtableOffset) {
int currentPos = lookupSubtableOffset;
int padLeft = 4;
putComment('Single Substitution Format:', padLeft);
int substFormat = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
int coverageOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
parseCoverageTable(bytes, lookupSubtableOffset + coverageOffset);
switch (substFormat) {
case 1:
//1.1 Single Substitution Format 1
int deltaGlyphID = byteData.readInt16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment(
'substFormat: $substFormat, coverageOffset:$coverageOffset, deltaGlyphID: $deltaGlyphID',
padLeft + 1);
break;
case 2:
//1.2 Single Substitution Format 2
int glyphCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment(
'substFormat: $substFormat, coverageOffset:$coverageOffset, glyphCount: $glyphCount',
padLeft + 1);
// Ordered by coverage index
for (int i = 0; i < glyphCount; i++) {
int substituteGlyphID = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('substituteGlyphID: $substituteGlyphID', padLeft + 2);
}
break;
}
}
// Multiple (format 2.1) Replace one glyph with more than one glyph
void parseLookupMultipleSubstitutionFormat(
ByteData bytes, int lookupSubtableOffset) {
int currentPos = lookupSubtableOffset;
int padLeft = 4;
putComment('Multiple Substitution Format:', padLeft);
int substFormat = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
int coverageOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
parseCoverageTable(bytes, lookupSubtableOffset + coverageOffset);
switch (substFormat) {
case 1:
int sequenceCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment(
'substFormat: $substFormat, coverageOffset:$coverageOffset, sequenceCount: $sequenceCount',
padLeft + 1);
// Ordered by coverage index
for (int i = 0; i < sequenceCount; i++) {
int sequenceOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('sequenceOffset: $sequenceOffset', padLeft + 2);
parseSequenceTable(bytes, lookupSubtableOffset + sequenceOffset);
}
break;
}
}
void parseSequenceTable(ByteData bytes, int sequenceOffset) {
int currentPos = sequenceOffset;
int padLeft = 7;
putComment('SequenceTable:', padLeft);
int glyphCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('glyphCount: $glyphCount', padLeft + 1);
for (int i = 0; i < glyphCount; i++) {
int substituteGlyphID = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('substituteGlyphID: $substituteGlyphID', padLeft + 2);
}
}
// 3.1 Alternate Substitution Format 1
void parseLookupMultipleAlternateSubstitutionFormat(
ByteData bytes, int lookupSubtableOffset) {
int padLeft = 4;
putComment('Alternate Substitution Form:', padLeft);
int currentPos = lookupSubtableOffset;
int substFormat = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
int coverageOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
parseCoverageTable(bytes, lookupSubtableOffset + coverageOffset);
switch (substFormat) {
case 1:
int alternateSetCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment(
'substFormat: $substFormat, coverageOffset:$coverageOffset, alternateSetCount: $alternateSetCount',
padLeft + 1);
// Ordered by coverage index
for (int i = 0; i < alternateSetCount; i++) {
int alternateSetOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('alternateSetOffset: $alternateSetOffset', padLeft + 2);
parseAlternateSetTable(
bytes, lookupSubtableOffset + alternateSetOffset);
}
break;
}
}
void parseAlternateSetTable(ByteData bytes, int sequenceOffset) {
int padLeft = 7;
putComment('AlternateSetTable:', padLeft);
int currentPos = sequenceOffset;
int glyphCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('glyphCount: $glyphCount', padLeft + 1);
for (int i = 0; i < glyphCount; i++) {
int substituteGlyphID = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('alternateGlyphID: $substituteGlyphID', padLeft + 2);
}
}
// LookupType 4: Ligature Substitution Subtable
void parseLookupLigatureSubstitutionSubtable(
ByteData bytes, int lookupSubtableOffset) {
int padLeft = 4;
putComment('Ligature Substitution Subtable:', padLeft);
int currentPos = lookupSubtableOffset;
int substFormat = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
int coverageOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
parseCoverageTable(bytes, lookupSubtableOffset + coverageOffset);
switch (substFormat) {
case 1:
int ligatureSetCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment(
'substFormat: $substFormat, coverageOffset:$coverageOffset, ligatureSetCount: $ligatureSetCount',
padLeft + 1);
// Ordered by coverage index
for (int i = 0; i < ligatureSetCount; i++) {
int ligatureSetOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('ligatureSetOffset: $ligatureSetOffset', padLeft + 2);
parseLigatureSetTable(
bytes, lookupSubtableOffset + ligatureSetOffset);
}
break;
}
}
void parseLigatureSetTable(ByteData bytes, int sequenceOffset) {
int padLeft = 7;
putComment('LigatureSet Table:', padLeft);
int currentPos = sequenceOffset;
int ligatureCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('ligatureCount: $ligatureCount', padLeft + 1);
for (int i = 0; i < ligatureCount; i++) {
int ligatureOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
parseLigatureTable(bytes, sequenceOffset + ligatureOffset);
}
}
void parseLigatureTable(ByteData bytes, int ligatureOffset) {
int padLeft = 9;
putComment('Ligature Table:', padLeft);
int currentPos = ligatureOffset;
int ligatureGlyph = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
int componentCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('ligatureGlyph: $ligatureGlyph, componentCount: $componentCount',
padLeft + 1);
for (int i = 0; i < componentCount; i++) {
int componentGlyphID = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('componentGlyphID: $componentGlyphID', padLeft + 2);
}
}
// LookupType 5: Contextual Substitution Subtable
// Working on
void parseLookupContextualSubstitutionSubtable(
ByteData bytes, int lookupSubtableOffset) {
int padLeft = 4;
putComment('Contextual Substitution Subtable:', padLeft);
int currentPos = lookupSubtableOffset;
int format = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
switch (format) {
case 1:
int coverageOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment(
'format: $format, coverageOffset: $coverageOffset', padLeft + 1);
parseCoverageTable(
bytes, lookupSubtableOffset + coverageOffset, padLeft + 1);
int seqRuleSetCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('seqRuleSetCount: $seqRuleSetCount', padLeft + 1);
for (int i = 0; i < seqRuleSetCount; i++) {
int seqRuleSetOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
parseSequenceRuleSet(bytes, lookupSubtableOffset + seqRuleSetOffset);
}
break;
case 2:
int coverageOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment(
'format: $format, coverageOffset: $coverageOffset', padLeft + 1);
parseCoverageTable(
bytes, lookupSubtableOffset + coverageOffset, padLeft + 1);
int classDefOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
parseClassDefinitionTable(bytes, lookupSubtableOffset + classDefOffset);
int classSeqRuleSetCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
for (int i = 0; i < classSeqRuleSetCount; i++) {
int classSeqRuleSetOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment(
'classSeqRuleSetOffsets: $classSeqRuleSetOffset', padLeft + 2);
switch (classSeqRuleSetOffset) {
case 0:
putComment('null', padLeft + 3);
break;
default:
parseClassSequenceRuleSet(
bytes, lookupSubtableOffset + classSeqRuleSetOffset);
break;
}
}
break;
case 3:
int glyphCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
int seqLookupCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment(
'format: $format glyphCount:$glyphCount, seqLookupCount: $seqLookupCount',
padLeft + 1);
for (int i = 0; i < glyphCount; i++) {
int coverageOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
parseCoverageTable(
bytes, lookupSubtableOffset + coverageOffset, padLeft + 2);
}
parseSequenceLookupRecord(bytes, currentPos,
seqLookupCount: seqLookupCount,
nextBytePosition: ((nextBytePos) => currentPos = nextBytePos));
break;
}
}
// LookupType 5.1: Contextual Substitution Subtable Format 1
void parseSequenceRuleSet(ByteData bytes, int seqRuleSetOffset) {
int padLeft = 7;
putComment('SequenceRuleSet:', padLeft);
int currentPos = seqRuleSetOffset;
int seqRuleCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('seqRuleCount: $seqRuleCount', padLeft + 1);
for (int i = 0; i < seqRuleCount; i++) {
int seqRuleOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
parseSequenceRule(bytes, seqRuleSetOffset + seqRuleOffset);
}
}
void parseSequenceRule(ByteData bytes, int seqRuleOffset) {
int padLeft = 9;
putComment('Sequence Rule:', padLeft);
int currentPos = seqRuleOffset;
int glyphCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
int seqLookupCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('GlyphIds', padLeft + 1);
putComment('glyphCount: $glyphCount, seqLookupCount: $seqLookupCount',
padLeft + 2);
for (int i = 0; i < glyphCount - 1; i++) {
int inputSequence = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('GlyphId: $inputSequence', padLeft + 3);
}
putComment('SequenceLookupRecord', padLeft + 1);
parseSequenceLookupRecord(bytes, currentPos,
seqLookupCount: seqLookupCount,
nextBytePosition: ((nextBytePos) => currentPos = nextBytePos));
}
// LookupType 5.2: Contextual Substitution Subtable Format 2
// Class Definition Table
void parseClassDefinitionTable(ByteData bytes, int classDefOffset) {
int padLeft = 10;
int currentPos = classDefOffset;
putComment('Class Definition Table:', padLeft);
int classFormat = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('classFormat: $classFormat', padLeft + 1);
switch (classFormat) {
case 1:
int startGlyphID = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
int glyphCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment(
'startGlyphID: $startGlyphID glyphCount: $glyphCount', padLeft + 1);
for (int i = 0; i < glyphCount; i++) {
int glyphId = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('glyphId: $glyphId', padLeft + 2);
}
break;
case 2:
int classRangeCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('classRangeCount: $classRangeCount', padLeft + 2);
for (int i = 0; i < classRangeCount; i++) {
int startGlyphID = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
int endGlyphID = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
int classId = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment(
'startGlyphID: $startGlyphID, endGlyphID: $endGlyphID, classId: $classId',
padLeft + 3);
}
break;
}
}
void parseClassSequenceRuleSet(ByteData bytes, int classSeqRuleSetOffset) {
int padLeft = 12;
int currentPos = classSeqRuleSetOffset;
putComment('Class Sequence RuleSet Table:', padLeft);
int classSeqRuleCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('classSeqRuleCount: $classSeqRuleCount', padLeft + 1);
for (int i = 0; i < classSeqRuleCount; i++) {
int classSeqRuleOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
parseClassSequenceRule(bytes, classSeqRuleSetOffset + classSeqRuleOffset);
}
}
void parseClassSequenceRule(ByteData bytes, int seqRuleOffset) {
int padLeft = 9;
putComment('Class Sequence Rule:', padLeft);
int currentPos = seqRuleOffset;
int glyphCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
int seqLookupCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment(
'InputSequence: glyphCount: $glyphCount, seqLookupCount: $seqLookupCount',
padLeft + 2);
for (int i = 0; i < glyphCount - 1; i++) {
int inputSequence = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('GlyphId: $inputSequence', padLeft + 3);
}
putComment('SequenceLookupRecord', padLeft + 1);
parseSequenceLookupRecord(bytes, currentPos,
seqLookupCount: seqLookupCount,
nextBytePosition: ((nextBytePos) => currentPos = nextBytePos));
}
// LookupType 6: Chained Contexts Substitution Subtable
void parseLookupChainedContextsSubstitutionSubtable(
ByteData bytes, int lookupSubtableOffset) {
int padLeft = 4;
putComment('Chained Contexts Substitution Subtable:', padLeft);
int currentPos = lookupSubtableOffset;
int format = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
switch (format) {
case 1:
parseChainedSequenceContextFormat1(bytes, currentPos,
lookupSubtableOffset, ((nextBytePos) => currentPos = nextBytePos));
break;
case 2:
parseChainedSequenceContextFormat2(bytes, currentPos,
lookupSubtableOffset, ((nextBytePos) => currentPos = nextBytePos));
break;
case 3:
parseChainedSequenceContextFormat3(bytes, currentPos,
lookupSubtableOffset, ((nextBytePos) => currentPos = nextBytePos));
break;
}
}
// Format 1
void parseChainedSequenceContextFormat1(
ByteData bytes, int currentPos, int lookupSubtableOffset,
[UpdatedPosition? nextBytePosition]) {
int padLeft = 6;
int coverageOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('coverageOffset: $coverageOffset', padLeft + 1);
parseCoverageTable(bytes, lookupSubtableOffset + coverageOffset);
int chainedSeqRuleSetCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
for (int i = 0; i < chainedSeqRuleSetCount; i++) {
int chainedSeqRuleSetOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
switch (chainedSeqRuleSetOffset) {
case 0:
putComment('null', padLeft + 2);
break;
default:
parseChainedSequenceRuleSet(
bytes, lookupSubtableOffset + chainedSeqRuleSetOffset);
break;
}
}
}
void parseChainedSequenceRuleSet(
ByteData bytes, int chainedSeqRuleSetOffset) {
int padLeft = 6;
putComment('Chained Sequence Rule Set:', padLeft);
int currentPos = chainedSeqRuleSetOffset;
int chainedSeqRuleCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('chainedSeqRuleCount: $chainedSeqRuleCount', padLeft + 1);
for (int i = 0; i < chainedSeqRuleCount; i++) {
int chainedSeqRuleOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
parseChainedSequenceRule(
bytes, chainedSeqRuleSetOffset + chainedSeqRuleOffset);
}
}
void parseChainedSequenceRule(ByteData bytes, int chainedSeqRuleOffset) {
int padLeft = 7;
putComment('Chained Sequence Rule:', padLeft);
int currentPos = chainedSeqRuleOffset;
int backtrackGlyphCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
for (int i = 0; i < backtrackGlyphCount; i++) {
int backtrackSequence = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('backtrackSequence GlyphId: $backtrackSequence', padLeft + 1);
}
int inputGlyphCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
for (int i = 0; i < inputGlyphCount - 1; i++) {
int inputSequence = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('inputSequence GlyphId: $inputSequence', padLeft + 1);
}
int lookaheadGlyphCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
for (int i = 0; i < lookaheadGlyphCount; i++) {
int lookaheadSequence = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('lookaheadSequence GlyphId: $lookaheadSequence', padLeft + 1);
}
parseSequenceLookupRecord(bytes, currentPos,
nextBytePosition: ((nextBytePos) => currentPos = nextBytePos));
}
// Format 2
void parseChainedSequenceContextFormat2(
ByteData bytes, int currentPosition, int lookupSubtableOffset,
[UpdatedPosition? nextBytePosition]) {
int padLeft = 6;
int currentPos = currentPosition;
int coverageOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('coverageOffset: $coverageOffset', padLeft + 1);
parseCoverageTable(bytes, lookupSubtableOffset + coverageOffset);
int backtrackClassDefOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment(
'backtrackClassDefOffset: $backtrackClassDefOffset', padLeft + 2);
parseClassDefinitionTable(
bytes, lookupSubtableOffset + backtrackClassDefOffset);
int inputClassDefOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('inputClassDefOffset: $inputClassDefOffset', padLeft + 2);
parseClassDefinitionTable(
bytes, lookupSubtableOffset + inputClassDefOffset);
int lookaheadClassDefOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('lookaheadClassDefOffset: $inputClassDefOffset', padLeft + 2);
parseClassDefinitionTable(
bytes, lookupSubtableOffset + lookaheadClassDefOffset);
int chainedClassSeqRuleSetCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
for (int i = 0; i < chainedClassSeqRuleSetCount; i++) {
int chainedClassSeqRuleSetOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
switch (chainedClassSeqRuleSetOffset) {
case 0:
putComment('null', padLeft + 2);
break;
default:
parseChainedClassSequenceRuleSet(
bytes, lookupSubtableOffset + chainedClassSeqRuleSetOffset);
break;
}
}
}
void parseChainedClassSequenceRuleSet(
ByteData bytes, int chainedSeqRuleOffset) {
int padLeft = 6;
putComment('Chained Class Sequence Rule Set:', padLeft);
int currentPos = chainedSeqRuleOffset;
int chainedClassSeqRuleCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment(
'chainedClassSeqRuleCount: $chainedClassSeqRuleCount', padLeft + 1);
for (int i = 0; i < chainedClassSeqRuleCount; i++) {
int chainedClassSeqRuleOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
parseChainedClassSequenceRule(
bytes, chainedSeqRuleOffset + chainedClassSeqRuleOffset);
}
}
void parseChainedClassSequenceRule(
ByteData bytes, int chainedClassSeqRuleOffset) {
int padLeft = 7;
putComment('Chained Class Sequence Rule:', padLeft);
int currentPos = chainedClassSeqRuleOffset;
int backtrackGlyphCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
for (int i = 0; i < backtrackGlyphCount; i++) {
int backtrackSequence = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('backtrackSequence classes: $backtrackSequence', padLeft + 1);
}
int inputGlyphCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
for (int i = 0; i < inputGlyphCount - 1; i++) {
int inputSequence = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('inputSequence classes: $inputSequence', padLeft + 1);
}
int lookaheadGlyphCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
for (int i = 0; i < lookaheadGlyphCount; i++) {
int lookaheadSequence = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('lookaheadSequence classes: $lookaheadSequence', padLeft + 1);
}
parseSequenceLookupRecord(bytes, currentPos,
nextBytePosition: ((nextBytePos) => currentPos = nextBytePos));
}
// Format 3
void parseChainedSequenceContextFormat3(
ByteData bytes, int currentPosition, int lookupSubtableOffset,
[UpdatedPosition? nextBytePosition]) {
int padLeft = 6;
putComment('ChainedSequenceContextFormat3:', padLeft);
int currentPos = currentPosition;
int backtrackGlyphCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
for (int i = 0; i < backtrackGlyphCount; i++) {
int backtrackCoverageOffsets = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
parseCoverageTable(
bytes, lookupSubtableOffset + backtrackCoverageOffsets);
}
int inputGlyphCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
for (int i = 0; i < inputGlyphCount; i++) {
int inputCoverageOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
parseCoverageTable(bytes, lookupSubtableOffset + inputCoverageOffset);
}
int lookaheadGlyphCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
for (int i = 0; i < lookaheadGlyphCount; i++) {
int lookaheadCoverageOffsets = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
parseCoverageTable(
bytes, lookupSubtableOffset + lookaheadCoverageOffsets);
}
parseSequenceLookupRecord(bytes, currentPos,
nextBytePosition: ((nextBytePos) => currentPos = nextBytePos));
if (nextBytePosition != null) {
nextBytePosition(currentPos);
}
}
// LookupType 7: Extension Substitution Subtable
void parseLookupExtensionSubstitutionSubtable(
ByteData bytes, int lookupSubtableOffset) {
int padLeft = 4;
int currentPos = lookupSubtableOffset;
putComment('Extension Substitution Subtable:', padLeft);
int substFormat = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
switch (substFormat) {
case 1:
int extensionLookupType = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
int extensionOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment(
'substFormat: $substFormat, extensionLookupType: $extensionLookupType, extensionOffset: $extensionOffset',
padLeft + 2);
break;
}
}
// LookupType 8: Reverse Chaining Contextual Single Substitution Subtable
void parseLookupReverseChainingContextualSingleSubstitutionSubtable(
ByteData bytes, int lookupSubtableOffset) {
int padLeft = 4;
putComment(
' Reverse Chaining Contextual Single Substitution Subtable:', padLeft);
int currentPos = lookupSubtableOffset;
int substFormat = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
switch (substFormat) {
case 1:
int coverageOffset = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('coverageOffset: $coverageOffset', padLeft + 1);
parseCoverageTable(bytes, lookupSubtableOffset + coverageOffset);
int backtrackGlyphCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('backtrackGlyphCount: $backtrackGlyphCount', padLeft + 1);
for (int i = 0; i < backtrackGlyphCount; i++) {
int backtrackCoverageOffsets = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
parseCoverageTable(
bytes, lookupSubtableOffset + backtrackCoverageOffsets);
}
int lookaheadGlyphCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('lookaheadGlyphCount: $backtrackGlyphCount', padLeft + 1);
for (int i = 0; i < lookaheadGlyphCount; i++) {
int lookaheadCoverageOffsets = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
parseCoverageTable(
bytes, lookupSubtableOffset + lookaheadCoverageOffsets);
}
int glyphCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('glyphCount: $glyphCount', padLeft + 1);
for (int i = 0; i < glyphCount; i++) {
int substituteGlyphIDs = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('substituteGlyphIDs: $substituteGlyphIDs', padLeft + 2);
}
break;
}
}
// Coverage Table
void parseCoverageTable(ByteData bytes, int coverageOffset,
[int basePadLeft = 5]) {
int currentPos = coverageOffset;
int padLeft = basePadLeft;
putComment('Coverage:', padLeft);
int coverageFormat = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
switch (coverageFormat) {
case 1:
int glyphCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('coverageFormat: $coverageFormat glyphCount: $glyphCount',
padLeft + 1);
for (int i = 0; i < glyphCount; i++) {
int glyphId = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('glyphId: $glyphId', padLeft + 2);
}
break;
case 2:
int rangeCount = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment('coverageFormat: $coverageFormat rangeCount: $rangeCount',
padLeft + 1);
for (int i = 0; i < rangeCount; i++) {
// RangeRecord
int startGlyphID = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
int endGlyphID = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
int startCoverageIndex = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment(
'startGlyphID: $startGlyphID, endGlyphID: $endGlyphID, startCoverageIndex:$startCoverageIndex',
padLeft + 2);
}
break;
}
}
// Sequence Lookup Record
void parseSequenceLookupRecord(ByteData bytes, int currentPos,
{UpdatedPosition? nextBytePosition, int? seqLookupCount}) {
int padLeft = 8;
seqLookupCount ??= byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
for (int i = 0; i < seqLookupCount; i++) {
int sequenceIndex = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
int lookupListIndex = byteData.readUint16(
currentPos, ((nextBytePos) => currentPos = nextBytePos));
putComment(
'sequenceIndex:$sequenceIndex, lookupListIndex: $lookupListIndex',
padLeft + 2);
}
if (nextBytePosition != null) {
nextBytePosition(currentPos);
}
}
//#endregion LookupList
}
class GSub {
GSub();
static void parseGsub(ByteData bytes, int baseOffset) {
GsubTable gsubTable = GsubTable(bytes, baseOffset);
gsubTable.extractGsubTable();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment