From 94ecec64045ac43fe08deb8f6d84c49c6b2943f6 Mon Sep 17 00:00:00 2001 From: NLion74 Date: Tue, 26 Aug 2025 23:03:17 +0200 Subject: [PATCH 1/4] Added GSUB font table implementation in Table Types --- src/FontLib/Table/Type/gsub.php | 179 ++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 src/FontLib/Table/Type/gsub.php diff --git a/src/FontLib/Table/Type/gsub.php b/src/FontLib/Table/Type/gsub.php new file mode 100644 index 0000000..d7d9979 --- /dev/null +++ b/src/FontLib/Table/Type/gsub.php @@ -0,0 +1,179 @@ + self::uint16, + "minorVersion" => self::uint16, + "scriptList" => self::uint16, + "featureList" => self::uint16, + "lookupList" => self::uint16, + ); + + protected function _parse() { + $font = $this->getFont(); + $offset = $font->pos(); + + $header = $font->unpack(self::$header_format); + + $data = array( + "header" => $header, + "scriptList" => null, + "featureList" => null, + "lookupList" => null, + ); + + // ScriptList + if ($header["scriptList"]) { + $font->seek($offset + $header["scriptList"]); + $data["scriptList"] = $this->parseScriptList($font, $offset + $header["scriptList"]); + } + + // FeatureList + if ($header["featureList"]) { + $font->seek($offset + $header["featureList"]); + $data["featureList"] = $this->parseFeatureList($font, $offset + $header["featureList"]); + } + + // LookupList + if ($header["lookupList"]) { + $font->seek($offset + $header["lookupList"]); + $data["lookupList"] = $this->parseLookupList($font, $offset + $header["lookupList"]); + } + + $this->data = $data; + } + + private function parseScriptList($font, $baseOffset) { + $scriptCount = $font->readUInt16(); + $records = []; + for ($i = 0; $i < $scriptCount; $i++) { + $tag = $font->read(4); + $offset = $font->readUInt16(); + $records[] = ["tag" => $tag, "offset" => $offset]; + } + return ["scriptCount" => $scriptCount, "records" => $records]; + } + + private function parseFeatureList($font, $baseOffset) { + $featureCount = $font->readUInt16(); + $records = []; + for ($i = 0; $i < $featureCount; $i++) { + $tag = $font->read(4); + $offset = $font->readUInt16(); + $records[] = ["tag" => $tag, "offset" => $offset]; + } + return ["featureCount" => $featureCount, "records" => $records]; + } + + private function parseLookupList($font, $baseOffset) { + $lookupCount = $font->readUInt16(); + $lookups = []; + $offsets = $font->readUInt16Many($lookupCount); + + foreach ($offsets as $lookupOffset) { + $font->seek($baseOffset + $lookupOffset); + $lookups[] = $this->parseLookup($font, $baseOffset + $lookupOffset); + } + + return ["lookupCount" => $lookupCount, "lookups" => $lookups]; + } + + private function parseLookup($font, $baseOffset) { + $lookupType = $font->readUInt16(); + $lookupFlag = $font->readUInt16(); + $subtableCount = $font->readUInt16(); + $subtableOffsets = $font->readUInt16Many($subtableCount); + + $subtables = []; + foreach ($subtableOffsets as $off) { + $font->seek($baseOffset + $off); + $subtables[] = $this->parseSubtable($font, $baseOffset + $off, $lookupType); + } + + return [ + "lookupType" => $lookupType, + "lookupFlag" => $lookupFlag, + "subtableCount" => $subtableCount, + "subtables" => $subtables, + ]; + } + + private function parseSubtable($font, $baseOffset, $lookupType) { + switch ($lookupType) { + case 4: + return $this->parseLigatureSubst($font, $baseOffset); + default: + return ["raw" => "Unsupported lookupType $lookupType"]; + } + } + + private function parseLigatureSubst($font, $subtableBase) { + $substFormat = $font->readUInt16(); + if ($substFormat != 1) { + return ["format" => $substFormat, "unsupported" => true]; + } + + $coverageOffset = $font->readUInt16(); + $ligSetCount = $font->readUInt16(); + $ligSetOffsets = $font->readUInt16Many($ligSetCount); + + $ligSets = []; + foreach ($ligSetOffsets as $offset) { + $font->seek($subtableBase + $offset); + $ligSets[] = $this->parseLigatureSet($font, $subtableBase + $offset); + } + + return [ + "format" => $substFormat, + "coverageOffset" => $coverageOffset, + "ligSetCount" => $ligSetCount, + "ligSets" => $ligSets, + ]; + } + + private function parseLigatureSet($font, $baseOffset) { + $ligatureCount = $font->readUInt16(); + $ligatureOffsets = $font->readUInt16Many($ligatureCount); + + $ligatures = []; + foreach ($ligatureOffsets as $off) { + $font->seek($baseOffset + $off); + $ligatures[] = $this->parseLigature($font); + } + + return [ + "ligatureCount" => $ligatureCount, + "ligatures" => $ligatures, + ]; + } + + private function parseLigature($font) { + $ligGlyph = $font->readUInt16(); + $compCount = $font->readUInt16(); + $components = $font->readUInt16Many($compCount - 1); + + return [ + "ligatureGlyph" => $ligGlyph, + "compCount" => $compCount, + "components" => $components, + ]; + } + + function _encode() { + // This still needs to be implemented + throw new \Exception("Encoding GSUB not implemented yet."); + } +} From ad75beb0439c38f468b4642dad68f2fb2395027a Mon Sep 17 00:00:00 2001 From: NLion74 Date: Tue, 26 Aug 2025 23:32:13 +0200 Subject: [PATCH 2/4] Removed unneccessary comments and added coverageGlyphs to gsub.php --- src/FontLib/Table/Type/gsub.php | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/FontLib/Table/Type/gsub.php b/src/FontLib/Table/Type/gsub.php index d7d9979..b4761c7 100644 --- a/src/FontLib/Table/Type/gsub.php +++ b/src/FontLib/Table/Type/gsub.php @@ -35,19 +35,16 @@ protected function _parse() { "lookupList" => null, ); - // ScriptList if ($header["scriptList"]) { $font->seek($offset + $header["scriptList"]); $data["scriptList"] = $this->parseScriptList($font, $offset + $header["scriptList"]); } - // FeatureList if ($header["featureList"]) { $font->seek($offset + $header["featureList"]); $data["featureList"] = $this->parseFeatureList($font, $offset + $header["featureList"]); } - // LookupList if ($header["lookupList"]) { $font->seek($offset + $header["lookupList"]); $data["lookupList"] = $this->parseLookupList($font, $offset + $header["lookupList"]); @@ -136,14 +133,41 @@ private function parseLigatureSubst($font, $subtableBase) { $ligSets[] = $this->parseLigatureSet($font, $subtableBase + $offset); } + $font->seek($subtableBase + $coverageOffset); + $coverageGlyphs = $this->parseCoverage($font); + return [ "format" => $substFormat, "coverageOffset" => $coverageOffset, + "coverageGlyphs" => $coverageGlyphs, "ligSetCount" => $ligSetCount, "ligSets" => $ligSets, ]; } + private function parseCoverage($font) { + $coverageFormat = $font->readUInt16(); + $glyphs = []; + if ($coverageFormat == 1) { + $glyphCount = $font->readUInt16(); + for ($i = 0; $i < $glyphCount; $i++) { + $glyphs[] = $font->readUInt16(); + } + } elseif ($coverageFormat == 2) { + $rangeCount = $font->readUInt16(); + for ($i = 0; $i < $rangeCount; $i++) { + $startGlyph = $font->readUInt16(); + $endGlyph = $font->readUInt16(); + $startCoverageIndex = $font->readUInt16(); // can be ignored for mapping + for ($g = $startGlyph; $g <= $endGlyph; $g++) { + $glyphs[] = $g; + } + } + } + return $glyphs; +} + + private function parseLigatureSet($font, $baseOffset) { $ligatureCount = $font->readUInt16(); $ligatureOffsets = $font->readUInt16Many($ligatureCount); From dada2cea2c5c40b2295e6b065c44dc98391f77f9 Mon Sep 17 00:00:00 2001 From: NLion74 Date: Sat, 30 Aug 2025 20:45:37 +0200 Subject: [PATCH 3/4] Refactored gsub class methods to remove unused parameters and _encode entirely for now --- src/FontLib/Table/Type/gsub.php | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/FontLib/Table/Type/gsub.php b/src/FontLib/Table/Type/gsub.php index b4761c7..abe572a 100644 --- a/src/FontLib/Table/Type/gsub.php +++ b/src/FontLib/Table/Type/gsub.php @@ -53,7 +53,7 @@ protected function _parse() { $this->data = $data; } - private function parseScriptList($font, $baseOffset) { + private function parseScriptList($font) { $scriptCount = $font->readUInt16(); $records = []; for ($i = 0; $i < $scriptCount; $i++) { @@ -64,7 +64,7 @@ private function parseScriptList($font, $baseOffset) { return ["scriptCount" => $scriptCount, "records" => $records]; } - private function parseFeatureList($font, $baseOffset) { + private function parseFeatureList($font) { $featureCount = $font->readUInt16(); $records = []; for ($i = 0; $i < $featureCount; $i++) { @@ -112,9 +112,8 @@ private function parseSubtable($font, $baseOffset, $lookupType) { switch ($lookupType) { case 4: return $this->parseLigatureSubst($font, $baseOffset); - default: - return ["raw" => "Unsupported lookupType $lookupType"]; } + return array("lookupType" => $lookupType, "unsupported" => true); } private function parseLigatureSubst($font, $subtableBase) { @@ -158,7 +157,6 @@ private function parseCoverage($font) { for ($i = 0; $i < $rangeCount; $i++) { $startGlyph = $font->readUInt16(); $endGlyph = $font->readUInt16(); - $startCoverageIndex = $font->readUInt16(); // can be ignored for mapping for ($g = $startGlyph; $g <= $endGlyph; $g++) { $glyphs[] = $g; } @@ -195,9 +193,4 @@ private function parseLigature($font) { "components" => $components, ]; } - - function _encode() { - // This still needs to be implemented - throw new \Exception("Encoding GSUB not implemented yet."); - } } From 2d6f803fd42fd20073cf5aadeeae959a674d1f81 Mon Sep 17 00:00:00 2001 From: NLion74 Date: Mon, 1 Sep 2025 18:14:44 +0200 Subject: [PATCH 4/4] Fixed coverage glyph parsing --- src/FontLib/Table/Type/gsub.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/FontLib/Table/Type/gsub.php b/src/FontLib/Table/Type/gsub.php index abe572a..ff0b1fb 100644 --- a/src/FontLib/Table/Type/gsub.php +++ b/src/FontLib/Table/Type/gsub.php @@ -118,9 +118,7 @@ private function parseSubtable($font, $baseOffset, $lookupType) { private function parseLigatureSubst($font, $subtableBase) { $substFormat = $font->readUInt16(); - if ($substFormat != 1) { - return ["format" => $substFormat, "unsupported" => true]; - } + if ($substFormat != 1) return ["format" => $substFormat, "unsupported" => true]; $coverageOffset = $font->readUInt16(); $ligSetCount = $font->readUInt16(); @@ -135,6 +133,10 @@ private function parseLigatureSubst($font, $subtableBase) { $font->seek($subtableBase + $coverageOffset); $coverageGlyphs = $this->parseCoverage($font); + foreach ($ligSets as $i => &$ligSet) { + $ligSet['coverageGlyph'] = $coverageGlyphs[$i] ?? null; + } + return [ "format" => $substFormat, "coverageOffset" => $coverageOffset, @@ -157,14 +159,14 @@ private function parseCoverage($font) { for ($i = 0; $i < $rangeCount; $i++) { $startGlyph = $font->readUInt16(); $endGlyph = $font->readUInt16(); + $startCoverageIndex = $font->readUInt16(); for ($g = $startGlyph; $g <= $endGlyph; $g++) { $glyphs[] = $g; } } } return $glyphs; -} - + } private function parseLigatureSet($font, $baseOffset) { $ligatureCount = $font->readUInt16();