* Fix ss02, ss04, ss06, ss13, ss17's application on i and j (#2033).

This commit is contained in:
be5invis 2023-10-10 01:24:55 -07:00
parent 1f80407eed
commit 344d8e95d8
24 changed files with 441 additions and 402 deletions

1
changes/27.2.1.md Normal file
View file

@ -0,0 +1 @@
* Fix `ss02`, `ss04`, `ss06`, `ss13`, `ss17`'s application on `i` and `j` (#2033).

View file

@ -34,7 +34,11 @@ function markLookups(table, sink, sinkDirect) {
if (lookup.type === "gsub_chaining" || lookup.type === "gpos_chaining") {
for (let st of lookup.rules) {
if (!st || !st.apply) continue;
for (const app of st.apply) sink.add(app.lookup);
for (const app of st.apply) {
if (!app.lookup.name)
throw new Error("Unreachable: lookup name should be present");
sink.add(app.lookup.name);
}
}
}
}
@ -46,7 +50,7 @@ function markLookupsStart(table, sink, sinkDirect) {
for (let f in table.features) {
const feature = table.features[f];
if (!feature) continue;
for (const l of feature) {
for (const l of feature.lookups) {
sink.add(l);
sinkDirect.add(l);
}
@ -65,9 +69,15 @@ function sweepFeatures(table, accessibleLookupsIds) {
for (let f in table.features) {
const feature = table.features[f];
if (!feature) continue;
const featureFiltered = [];
for (const l of feature) if (accessibleLookupsIds.has(l)) featureFiltered.push(l);
if (!featureFiltered.length) continue;
const featureFiltered = {
name: feature.name,
tag: feature.tag,
lookups: []
};
for (const l of feature.lookups) {
if (accessibleLookupsIds.has(l)) featureFiltered.lookups.push(l);
}
if (!featureFiltered.lookups.length) continue;
features1[f] = featureFiltered;
}
table.features = features1;
@ -151,7 +161,11 @@ function markGlyphsByLookup(gsub, lid, markedGlyphs) {
if (!atLeastOneMatch) continue rules;
}
// If so traverse through the lookup applications
for (const app of rule.apply) markGlyphsByLookup(gsub, app.lookup, markedGlyphs);
for (const app of rule.apply) {
if (!app.lookup.name)
throw new Error("Unreachable: lookup name should be present");
markGlyphsByLookup(gsub, app.lookup.name, markedGlyphs);
}
}
break;
}

View file

@ -106,9 +106,9 @@ class FeatureStore {
return this.m_mapping.get(id);
}
fill(id, data) {
const tag = id.slice(0, 4);
const tag = data.tag;
const lookups = [];
for (const lid of data) {
for (const lid of data.lookups) {
const lookup = this.lookupStore.query(lid);
if (lookup) lookups.push(lookup);
}
@ -215,8 +215,9 @@ const GsubChainingHandler = {
const inputEnds = st.inputEnds;
const applications = [];
for (const ap of st.apply) {
const lookup = store.query(ap.lookup);
if (!lookup) continue out;
if (!ap.lookup.name) throw new Error("Unreachable: lookup name must not be null");
const lookup = store.query(ap.lookup.name);
if (!lookup) throw new Error(`Cannot find lookup '${ap.lookup.name}'`);
applications.push({ at: ap.at - inputBegins, apply: lookup });
}
dst.rules.push({ match, inputBegins, inputEnds, applications });

View file

@ -2,7 +2,7 @@
$$include '../../meta/macros.ptl'
import [linreg clamp mix fallback] from"../../support/utils.mjs"
import [getGrTree getGrMesh IsSuperscript IsSubscript] from"../../support/gr.mjs"
import [getGrTree IsSuperscript IsSubscript] from"../../support/gr.mjs"
import [AnyCv DotlessOrNot CvDecompose MathSansSerif] from"../../support/gr.mjs"
import [NumeratorForm DenominatorForm] from"../../support/gr.mjs"
import [Transform] from"../../support/geometry/transform.mjs"

View file

@ -1,7 +1,7 @@
$$include '../../meta/macros.ptl'
import [mix clamp fallback SuffixCfg] from"../../support/utils.mjs"
import [AnyCv getGrMesh VS01 Zero] from"../../support/gr.mjs"
import [VS01 Zero] from"../../support/gr.mjs"
glyph-module

View file

@ -1,7 +1,6 @@
$$include '../../meta/macros.ptl'
import [mix linreg clamp fallback] from"../../support/utils.mjs"
import [AnyCv getGrMesh] from"../../support/gr.mjs"
glyph-module

View file

@ -1,7 +1,6 @@
$$include '../../meta/macros.ptl'
import [mix linreg clamp fallback] from"../../support/utils.mjs"
import [AnyCv getGrMesh] from"../../support/gr.mjs"
glyph-module

View file

@ -1,7 +1,6 @@
$$include '../../meta/macros.ptl'
import [mix linreg clamp fallback] from"../../support/utils.mjs"
import [AnyCv getGrMesh] from"../../support/gr.mjs"
glyph-module

View file

@ -1,7 +1,6 @@
$$include '../../meta/macros.ptl'
import [mix linreg clamp fallback SuffixCfg] from"../../support/utils.mjs"
import [AnyCv getGrMesh] from"../../support/gr.mjs"
glyph-module

View file

@ -1,7 +1,6 @@
$$include '../../meta/macros.ptl'
import [mix linreg clamp fallback] from"../../support/utils.mjs"
import [AnyCv getGrMesh] from"../../support/gr.mjs"
glyph-module

View file

@ -1,7 +1,6 @@
$$include '../../meta/macros.ptl'
import [mix linreg clamp fallback] from"../../support/utils.mjs"
import [AnyCv getGrMesh] from"../../support/gr.mjs"
glyph-module

View file

@ -1,7 +1,6 @@
$$include '../../meta/macros.ptl'
import [mix linreg clamp fallback] from"../../support/utils.mjs"
import [AnyCv getGrMesh] from"../../support/gr.mjs"
glyph-module

View file

@ -1,7 +1,6 @@
$$include '../../meta/macros.ptl'
import [mix linreg clamp fallback] from"../../support/utils.mjs"
import [AnyCv getGrMesh] from"../../support/gr.mjs"
glyph-module

View file

@ -1,5 +1,3 @@
import [AddCommonFeature AddFeature AddLookup] from"./table-util.mjs"
extern Map
extern Set
@ -21,27 +19,22 @@ define MarkInteractions : new Map : list
define MkmkStackingLimit : new Set { 'above' 'below' }
export : define [buildMarkMkmk sink glyphStore markGlyphs] : begin
export : define [buildMarkMkmk gpos glyphStore markGlyphs] : begin
define validMarkClasses : new Set MarkClasses
define mark : object
feature : AddFeature sink 'mark'
feature : gpos.addCommonFeature : gpos.createFeature 'mark'
lookupMap : new Map
lookupNames : new Set
createLookup : function [] {.type 'gpos_mark_to_base' .marks {.} .bases {.}}
define mkmk : object
feature : AddFeature sink 'mkmk'
feature : gpos.addCommonFeature : gpos.createFeature 'mkmk'
lookupMap : new Map
lookupNames : new Set
createLookup : function [] {.type 'gpos_mark_to_mark' .marks {.} .bases {.}}
AddCommonFeature sink mark.feature
AddCommonFeature sink mkmk.feature
# Populate the marks
foreach cls [items-of MarkClasses] : begin
local markLookup : ensureLookup sink mark cls
local mkmkLookup : ensureLookup sink mkmk cls
local markLookup : ensureLookup gpos mark cls
local mkmkLookup : ensureLookup gpos mkmk cls
foreach { gn glyph } [glyphStore.namedEntries] : begin
if glyph.markAnchors.(cls) : begin
@ -52,8 +45,8 @@ export : define [buildMarkMkmk sink glyphStore markGlyphs] : begin
# Populate the bases
foreach cls [items-of MarkClasses] : begin
local markLookup : ensureLookup sink mark cls
local mkmkLookup : ensureLookup sink mkmk cls
local markLookup : ensureLookup gpos mark cls
local mkmkLookup : ensureLookup gpos mkmk cls
foreach { gn glyph } [glyphStore.namedEntries] : begin
if glyph.baseAnchors.(cls) : begin
@ -61,8 +54,8 @@ export : define [buildMarkMkmk sink glyphStore markGlyphs] : begin
: then : addBaseAnchor mkmkLookup gn cls glyph.baseAnchors.(cls)
: else : addBaseAnchor markLookup gn cls glyph.baseAnchors.(cls)
foreach lidMark mark.lookupNames : foreach lidMkmk mkmk.lookupNames
sink.lookupDep.push { lidMark lidMkmk }
foreach markLookup [mark.lookupMap.values] : foreach mkmkLookup [mkmk.lookupMap.values]
gpos.setDependency markLookup mkmkLookup
foreach { cls lookup } mkmk.lookupMap : begin
local interactionMarkSet : new Set ([MarkInteractions.get cls] || { cls })
@ -77,17 +70,14 @@ export : define [buildMarkMkmk sink glyphStore markGlyphs] : begin
if (interactionMarkSet.size > 1) : begin
markGlyphs.markGlyphSets.push : Array.from includeSet
define [ensureLookup sink feat cls] : begin
define [ensureLookup gpos feat cls] : begin
local existing : feat.lookupMap.get cls
if existing : return existing
local novel : feat.createLookup
local lid : AddLookup sink novel
feat.feature.lookups.push lid
feat.lookupNames.add lid
feat.lookupMap.set cls novel
return novel
local lookup : gpos.createLookup : feat.createLookup
feat.feature.addLookup lookup
feat.lookupMap.set cls lookup
return lookup
define [addMarkAnchor lookup gn cls anchor] : begin
local a : object

View file

@ -1,6 +1,6 @@
$$include '../meta/macros.ptl'
import [AddCommonFeature AddFeature AddLookup AddFeatureLookup ChainRuleBuilder BeginLookupBlock EndLookupBlock UkMapToLookup UkMap2ToLookup] from"./table-util.mjs"
import [UkMapToLookup UkMap2ToLookup] from"./table-util.mjs"
import [Dotless TieMark TieGlyph OgonekTrY IsSuperscript IsSubscript LeaningMark LeaningMarkSpacer] from"../support/gr.mjs"
import as UnicodeKnowledge from"../meta/unicode-knowledge.mjs"
@ -10,29 +10,28 @@ define-macro Ccmp-Group : syntax-rules
`[Ccmp-Group @description @body]
dirty `[$ExecCcmpGroup$ [function [export-lookup chain-rule] @[formOf body]]]
export : define [buildCCMP sink glyphStore markGlyphs] : begin
export : define [buildCCMP gsub glyphStore markGlyphs] : begin
local anyMark : Array.from markGlyphs.all
local aboveMark : filterMarkByClass markGlyphs 'above'
define ccmp : AddFeature sink 'ccmp'
AddCommonFeature sink ccmp
define ccmp : gsub.addCommonFeature : gsub.createFeature 'ccmp'
define [$ExecCcmpGroup$ fn] : begin
local addedLookups {}
define [export-lookup lookupName] : begin
ccmp.lookups.push lookupName
addedLookups.push lookupName
define [export-lookup lookup] : begin
ccmp.addLookup lookup
addedLookups.push lookup
local rec : BeginLookupBlock sink
define {chain-rule} : ChainRuleBuilder sink
local rec : gsub.beginBlock
define {chain-rule} : gsub.ChainRuleBuilder
fn export-lookup chain-rule
for [local j 1] (j < addedLookups.length) [inc j] : begin
sink.lookupDep.push {addedLookups.(j - 1) addedLookups.(j)}
gsub.setDependency addedLookups.(j - 1) addedLookups.(j)
EndLookupBlock rec sink
gsub.endBlock rec
Ccmp-Group "Mark transforms" : begin
@ -52,7 +51,7 @@ export : define [buildCCMP sink glyphStore markGlyphs] : begin
define [GrekUpperTonosTf] : UkMapToLookup UnicodeKnowledge.upperGrekMarkToTonosTf
# Dotless transform
export-lookup : AddLookup sink : object
export-lookup : gsub.createLookup : object
.type 'gsub_chaining'
.ignoreGlyphs [filterMarkByClassNegated markGlyphs 'above']
.rules : list
@ -60,17 +59,17 @@ export : define [buildCCMP sink glyphStore markGlyphs] : begin
chain-rule groupGrekUpperTonos [GrekUpperTonosTf]
# Iota transform
export-lookup : AddLookup sink : object
export-lookup : gsub.createLookup : object
.type 'gsub_chaining'
.ignoreGlyphs [filterMarkByClassNegated markGlyphs 'below']
.rules : list
chain-rule groupLF [IotaLF]
export-lookup : AddLookup sink : object
export-lookup : gsub.createLookup : object
.type 'gsub_ligature'
.substitutions : UkMap2ToLookup UnicodeKnowledge.markCompositionTf
export-lookup : AddLookup sink : object
export-lookup : gsub.createLookup : object
.type 'gsub_multiple'
.substitutions : object
'parenAbove' { 'leftParenAbove' 'rightParenAbove' }
@ -89,13 +88,13 @@ export : define [buildCCMP sink glyphStore markGlyphs] : begin
TieMarkFrom.push gid
TieMarkTo.push [TieMark.get g]
define lookupTieMarkLigature : AddLookup sink : object
define lookupTieMarkLigature : gsub.createLookup : object
.type 'gsub_ligature'
.substitutions : {}.concat
TieMarkFrom.map : lambda [gnFrom idx]
object [from {'cgj' gnFrom}] [to TieMarkTo.(idx)]
export-lookup : AddLookup sink : object
export-lookup : gsub.createLookup : object
.type 'gsub_chaining'
.rules : list : object
match {[TieGlyphs.concat TieMarkTo] {'cgj'} TieMarkFrom}
@ -105,7 +104,7 @@ export : define [buildCCMP sink glyphStore markGlyphs] : begin
Ccmp-Group "Special dot-above transformation" : begin
# b-dot, d-dot, h-dot, k-dot
export-lookup : AddLookup sink : object
export-lookup : gsub.createLookup : object
.type 'gsub_ligature'
.ignoreGlyphs [filterMarkByClassNegated markGlyphs 'above']
.substitutions : list
@ -134,11 +133,11 @@ export : define [buildCCMP sink glyphStore markGlyphs] : begin
markSpacer.push [LeaningMarkSpacer.get g]
splitMapping.push { gn {[LeaningMarkSpacer.get g] [LeaningMark.get g]} }
define lookupTurnMarkIntoLeaningAndSpacer : AddLookup sink : object
define lookupTurnMarkIntoLeaningAndSpacer : gsub.createLookup : object
.type 'gsub_multiple'
.substitutions : Object.fromEntries splitMapping
export-lookup : AddLookup sink : object
export-lookup : gsub.createLookup : object
.type 'gsub_chaining'
.ignoreGlyphs [filterMarkByClassNegated markGlyphs mkCenter]
.rules : list
@ -158,7 +157,7 @@ export : define [buildCCMP sink glyphStore markGlyphs] : begin
if [IsSuperscript.get g] : superscripts.push gn
if [IsSubscript.get g] : subscripts.push gn
export-lookup : AddLookup sink : object
export-lookup : gsub.createLookup : object
.type 'gsub_chaining'
.ignoreGlyphs anyMark
.rules : list
@ -230,7 +229,7 @@ export : define [buildCCMP sink glyphStore markGlyphs] : begin
e.push ('toneSandhiMid' + toneStart + toneMid + toneEnd)
return (f ~> e)
export-lookup : AddLookup sink : object
export-lookup : gsub.createLookup : object
.type 'gsub_chaining'
.rules : list
chain-rule [ToneToToneStart 0] {'tone0'}
@ -239,7 +238,7 @@ export : define [buildCCMP sink glyphStore markGlyphs] : begin
chain-rule [ToneToToneStart 3] {'tone3'}
chain-rule [ToneToToneStart 4] {'tone4'}
export-lookup : AddLookup sink : object
export-lookup : gsub.createLookup : object
.type 'gsub_chaining'
.rules : list
chain-rule [ToneStartOrMidAt 0] [ToneStartToToneMid 0]
@ -248,7 +247,7 @@ export : define [buildCCMP sink glyphStore markGlyphs] : begin
chain-rule [ToneStartOrMidAt 3] [ToneStartToToneMid 3]
chain-rule [ToneStartOrMidAt 4] [ToneStartToToneMid 4]
export-lookup : AddLookup sink : object
export-lookup : gsub.createLookup : object
.type 'gsub_chaining'
.rules : list
chain-rule [ToneStartOrMidAt 0] [ToneToToneEnd 0]
@ -257,7 +256,7 @@ export : define [buildCCMP sink glyphStore markGlyphs] : begin
chain-rule [ToneStartOrMidAt 3] [ToneToToneEnd 3]
chain-rule [ToneStartOrMidAt 4] [ToneToToneEnd 4]
export-lookup : AddLookup sink : object
export-lookup : gsub.createLookup : object
.type 'gsub_chaining'
.rules : list
chain-rule [ToneSandhiToToneStart 0] {'toneSandhi0'}
@ -266,7 +265,7 @@ export : define [buildCCMP sink glyphStore markGlyphs] : begin
chain-rule [ToneSandhiToToneStart 3] {'toneSandhi3'}
chain-rule [ToneSandhiToToneStart 4] {'toneSandhi4'}
export-lookup : AddLookup sink : object
export-lookup : gsub.createLookup : object
.type 'gsub_chaining'
.rules : list
chain-rule [ToneSandhiStartOrMidAt 0] [ToneSandhiStartToToneMid 0]
@ -275,7 +274,7 @@ export : define [buildCCMP sink glyphStore markGlyphs] : begin
chain-rule [ToneSandhiStartOrMidAt 3] [ToneSandhiStartToToneMid 3]
chain-rule [ToneSandhiStartOrMidAt 4] [ToneSandhiStartToToneMid 4]
export-lookup : AddLookup sink : object
export-lookup : gsub.createLookup : object
.type 'gsub_chaining'
.rules : list
chain-rule [ToneSandhiStartOrMidAt 0] [ToneSandhiToToneEnd 0]
@ -286,11 +285,9 @@ export : define [buildCCMP sink glyphStore markGlyphs] : begin
return ccmp
export : define [buildCCMPPostCvSs sink ccmpFeature glyphStore markGlyphs] : begin
export : define [buildCCMPPostCvSs gsub ccmp glyphStore markGlyphs] : begin
local anyMark : Array.from markGlyphs.all
local rec : BeginLookupBlock sink
define ccmp : AddFeature sink 'ccmp'
local rec : gsub.beginBlock
define triggerGlyphs_Normal { }
define triggerGlyphs_Y { }
@ -299,14 +296,14 @@ export : define [buildCCMPPostCvSs sink ccmpFeature glyphStore markGlyphs] : beg
[OgonekTrY.get g] : triggerGlyphs_Y.push gid
true : triggerGlyphs_Normal.push gid
define ogonekSpacerNormal : AddLookup sink : object
define ogonekSpacerNormal : gsub.createLookup : object
.type 'gsub_multiple'
.substitutions UnicodeKnowledge.ogonekBelowToTRTf
define ogonekSpacerY : AddLookup sink : object
define ogonekSpacerY : gsub.createLookup : object
.type 'gsub_single'
.substitutions UnicodeKnowledge.ogonekBelowToTRTf_Y
define lookupMarks1 : AddLookup sink : object
ccmp.addLookup : gsub.createLookup : object
.type 'gsub_chaining'
.ignoreGlyphs [filterMarkByClassNegated markGlyphs 'below']
.rules : list
@ -321,8 +318,7 @@ export : define [buildCCMPPostCvSs sink ccmpFeature glyphStore markGlyphs] : beg
.inputEnds 2
.apply {{.at 1 .lookup ogonekSpacerNormal}}
ccmpFeature.lookups.push lookupMarks1
EndLookupBlock rec sink
gsub.endBlock rec
define [objectIsNotEmpty obj] : obj && [Object.keys obj].length

View file

@ -1,6 +1,5 @@
$$include '../meta/macros.ptl'
import [AddLookup AddCommonFeature PickCommonFeature AddFeatureLookup PickLookup BeginLookupBlock EndLookupBlock ChainRuleBuilder] from"./table-util.mjs"
import [Cv AnyCv CvDecompose RightDependentLink RightDependentTrigger] from"../support/gr.mjs"
extern Map
@ -11,20 +10,68 @@ define look-around null
define [FeatureName tag] : tag + '_cvss'
define [SsLookupName tag] : 'lookup_ss_' + tag
define [CvLookupName tag] : 'lookup_cv_' + tag
define [CvSpecificLookupName tag rank] : 'lookup_cv_' + tag + '_' + rank
define [CvDecomposeLookupName tag] : 'lookup_cv_decompose_' + tag
export : define [buildCVSS sink para glyphStore] : begin
define {chain-rule} : ChainRuleBuilder sink
class CvLookupManager
public [new table tag feature] : begin
set this.table table
set this.tag tag
set this.feature feature
local rec : BeginLookupBlock sink
local cvLookupNameSet : new Set
local ssLookupNameSet : new Set
set this.decompositionLookups {}
# Build decomposition lookups
set this.substitutionLookups {}
set this.altrenatesLookup null
set this.singleSubstLookups {}
public [addDecompositionLookup lookup] : begin
this.decompositionLookups.push lookup
this.feature.addLookup lookup
public [alternateSubst] : begin
if this.altrenatesLookup : return this.altrenatesLookup
define lookupName : 'lookup_cv_2_alternates_' + this.tag
define lookup : this.table.pickLookup lookupName {.type 'gsub_alternate' .substitutions {.}}
this.feature.addLookup lookup
this.substitutionLookups.push lookup
set this.altrenatesLookup lookup
return lookup
public [singleSubst rank] : begin
if this.singleSubstLookups.(rank) : return this.singleSubstLookups.(rank)
define lookupName : 'lookup_cv_1_single_' + this.tag + '_' + rank
define lookup : this.table.pickLookup lookupName {.type 'gsub_single' .substitutions {.}}
this.feature.addLookup lookup
this.substitutionLookups.push lookup
set this.singleSubstLookups.(rank) lookup
return lookup
public [linkDeps] : begin
foreach a [items-of this.decompositionLookups] : begin
foreach b [items-of this.substitutionLookups] : begin
this.table.setDependency a b
for [local i 1] (i < this.substitutionLookups.length) [inc i]
this.table.setDependency this.substitutionLookups.(i - 1) this.substitutionLookups.(i)
export : define [buildCVSS gsub para glyphStore] : begin
local rec : gsub.beginBlock
local cvs : new Map
do "Initialize CV feature atlas"
foreach {name prime} para.variants.primes : if prime.tag : begin
define feature : gsub.addCommonFeature : gsub.createFeature prime.tag
local cvLookupManager : new CvLookupManager gsub prime.tag feature
cvs.set prime.tag cvLookupManager
do "Build decomposition lookups"
local decompositions : new Map
local cvDecompositionLookupNameSet : new Set
local cvTagToDecompositionLookups : new Map
foreach { gn glyph } [glyphStore.namedEntries] : if [CvDecompose.get glyph] : do
local decomp : object
parts : CvDecompose.get glyph
@ -38,73 +85,52 @@ export : define [buildCVSS sink para glyphStore] : begin
foreach { gn decomp } decompositions : if decomp.influences.size : do
define lookupName : CvDecomposeLookupName : [[Array.from decomp.influences].sort].join '/'
define lookup : PickLookup sink lookupName {.type 'gsub_multiple' .substitutions {.}}
cvDecompositionLookupNameSet.add lookupName
define lookup : gsub.pickLookup lookupName {.type 'gsub_multiple' .substitutions {.}}
set lookup.substitutions.(gn) decomp.parts
foreach cvTag decomp.influences : begin
local s : cvTagToDecompositionLookups.get cvTag
if [not s] : begin
set s : new Set
cvTagToDecompositionLookups.set cvTag s
s.add lookupName
foreach cvTag decomp.influences : [cvs.get cvTag].addDecompositionLookup lookup
define [addCvMapping tag src dst rank] : begin
define feature : PickCommonFeature sink [FeatureName tag]
define lookupName : CvLookupName tag
define lookup : PickLookup sink lookupName {.type 'gsub_alternate' .substitutions {.}}
do "cvxx"
local cvGrs {}
foreach {name prime} para.variants.primes : foreach {vn variant} prime.variants : begin
if (prime.tag && variant.rank) : cvGrs.push : Cv prime.tag variant.rank
cvGrs.sort Cv.compare
if [not : cvLookupNameSet.has lookupName] : begin
AddFeatureLookup feature lookupName
cvLookupNameSet.add lookupName
local decompLookups : cvTagToDecompositionLookups.get tag
if decompLookups : foreach d decompLookups : AddFeatureLookup feature d
if [not lookup.substitutions.(src)] : set lookup.substitutions.(src) { }
set lookup.substitutions.(src).(rank - 1) dst
define [addSsSubstitution tag decomp src dst] : begin
define feature : PickCommonFeature sink [FeatureName tag]
define lookupName : SsLookupName composition.tag
define lookup : PickLookup sink lookupName {.type 'gsub_single' .substitutions {.}}
if [not : ssLookupNameSet.has lookupName] : begin
AddFeatureLookup feature lookupName
ssLookupNameSet.add lookupName
foreach { prime pv } [items-of decomp] : if (pv.tag && pv.rank) : begin
local decompLookups : cvTagToDecompositionLookups.get pv.tag
if decompLookups : foreach d decompLookups : AddFeatureLookup feature d
set lookup.substitutions.(src) dst
# cvxx
foreach {gn glyph} [glyphStore.namedEntries] : if [not : CvDecompose.get glyph] : do
foreach [gr : items-of : AnyCv.query glyph] : if gr.tag : begin
addCvMapping gr.tag gn [glyphStore.ensureExists : gr.get glyph] gr.rank
# ssxx
foreach {name composition} para.variants.composites : if composition.tag : do
define decomp : composition.decompose para para.variants.selectorTree
foreach { prime pv } [items-of decomp] : if (pv.tag && pv.rank) : begin
local gr : Cv pv.tag pv.rank
foreach gr [items-of cvGrs] : begin
local cvAlt : [cvs.get gr.tag].alternateSubst
foreach {gn glyph} [glyphStore.namedEntries] : if [not : CvDecompose.get glyph] : begin
local substituted : gr.get glyph
if substituted : addSsSubstitution composition.tag decomp gn substituted
local subst : gr.get glyph
if (subst && subst != gn) : begin
if [not cvAlt.substitutions.(gn)] : set cvAlt.substitutions.(gn) { }
set cvAlt.substitutions.(gn).(gr.rank - 1) : glyphStore.ensureExists subst
# If there are holes in the alternates list, fill them
foreach lutn cvLookupNameSet : begin
local st [PickLookup sink lutn].substitutions
do "ssxx" : foreach {name composition} para.variants.composites : if composition.tag : begin
define feature : gsub.addCommonFeature : gsub.createFeature composition.tag
define decomp : composition.decompose para para.variants.selectorTree
local ssGrs {}
foreach { prime pv } [items-of decomp] : if (pv.tag && pv.rank) : begin
ssGrs.push : Cv pv.tag pv.rank
foreach lookup [items-of [cvs.get pv.tag].decompositionLookups] : begin
feature.addLookup lookup
ssGrs.sort Cv.compare
foreach gr [items-of ssGrs] : begin
local cvSingle : [cvs.get gr.tag].singleSubst gr.rank
feature.addLookup cvSingle
foreach {gn glyph} [glyphStore.namedEntries] : if [not : CvDecompose.get glyph] : begin
local subst : gr.get glyph
if (subst && subst != gn) : begin
set cvSingle.substitutions.(gn) : glyphStore.ensureExists subst
do "Cleanup and link dependency"
foreach cv [cvs.values] : begin
local st cv.altrenatesLookup.substitutions
foreach { k v } [pairs-of st] : foreach idx [range 0 v.length] : if [not v.(idx)]
set v.(idx) k
# Lookup dependency
foreach lutnDe cvDecompositionLookupNameSet : foreach lutnCv cvLookupNameSet : begin
sink.lookupDep.push { lutnDe lutnCv }
foreach lutnDe cvDecompositionLookupNameSet : foreach lutnSs ssLookupNameSet : begin
sink.lookupDep.push { lutnDe lutnSs }
foreach lutnCv cvLookupNameSet : foreach lutnSs ssLookupNameSet : begin
sink.lookupDep.push { lutnCv lutnSs }
cv.linkDeps
EndLookupBlock rec sink
gsub.endBlock rec
define [objectIsNotEmpty obj] : obj && [Object.keys obj].length

View file

@ -1,16 +1,15 @@
$$include '../meta/macros.ptl'
import [AddCommonFeature AddFeature AddLookup BeginLookupBlock EndLookupBlock ChainRuleBuilder] from"./table-util.mjs"
import [NumeratorForm DenominatorForm] from"../support/gr.mjs"
# Name-driven feature pairs
export : define [buildFrac sink glyphStore] : begin
local rec : BeginLookupBlock sink
export : define [buildFrac gsub glyphStore] : begin
local rec : gsub.beginBlock
define frac : AddFeature sink 'frac'
define { chain-rule reverse-rule } : ChainRuleBuilder sink
define frac : gsub.addCommonFeature : gsub.createFeature 'frac'
define { chain-rule reverse-rule } : gsub.ChainRuleBuilder
define subSolidus : AddLookup sink : object
define subSolidus : gsub.createLookup : object
.type 'gsub_single'
.substitutions : object ['solidus' 'fractionBar'] ['slash' 'fractionBar']
@ -26,24 +25,22 @@ export : define [buildFrac sink glyphStore] : begin
numSet.push numForm
denSet.push denForm
define subDen : AddLookup sink : object
define subDen : gsub.createLookup : object
.type 'gsub_chaining'
.rules : list
chain-rule [{'fractionBar'}.concat denSet] [digitSet ~> denSet]
define subNum : AddLookup sink : object
define subNum : gsub.createLookup : object
.type 'gsub_reverse'
.rules : list
reverse-rule [digitSet ~> numSet] [{'fractionBar'}.concat numSet]
frac.lookups.push subSolidus
frac.lookups.push subDen
frac.lookups.push subNum
sink.lookupDep.push {subSolidus subDen}
sink.lookupDep.push {subSolidus subNum}
frac.addLookup subSolidus
frac.addLookup subDen
frac.addLookup subNum
gsub.setDependency subSolidus subDen
gsub.setDependency subSolidus subNum
AddCommonFeature sink frac
EndLookupBlock rec sink
gsub.endBlock rec
define [objectIsNotEmpty obj] : obj && [Object.keys obj].length

View file

@ -1,8 +1,6 @@
import [AddCommonFeature AddFeature AddLookup BeginLookupBlock EndLookupBlock] from"./table-util.mjs"
# Name-driven feature pairs
export : define [buildGrFeature sink glyphStore gr] : begin
local rec : BeginLookupBlock sink
export : define [buildGrFeature gsub glyphStore gr] : begin
local rec : gsub.beginBlock
local mapping {.}
foreach { gnSrc glyph } [glyphStore.namedEntries] : begin
@ -12,11 +10,10 @@ export : define [buildGrFeature sink glyphStore gr] : begin
set mapping.(gnSrc) gnDst
if [objectIsNotEmpty mapping] : begin
define lookup1 : AddLookup sink {.type 'gsub_single' .substitutions mapping}
define feature1 : AddFeature sink gr.otlTag
feature1.lookups.push lookup1
AddCommonFeature sink feature1
define lookup1 : gsub.createLookup {.type 'gsub_single' .substitutions mapping}
define feature1 : gsub.addCommonFeature : gsub.createFeature gr.otlTag
feature1.addLookup lookup1
EndLookupBlock rec sink
gsub.endBlock rec
define [objectIsNotEmpty obj] : obj && [Object.keys obj].length

View file

@ -1,6 +1,5 @@
$$include '../meta/macros.ptl'
import [AddCommonFeature AddFeature ChainRuleBuilder BeginLookupBlock EndLookupBlock] from"./table-util.mjs"
extern Map
extern Set
@ -12,62 +11,52 @@ define look-around null
define advance : lambda [t] null
define ident : lambda [t] : t.map : lambda [x] x
export : define [buildLigations sink para plm] : begin
export : define [buildLigations gsub para plm] : begin
# Initialize features
define features : new Map
foreach [ { featureTag } : pairs-of plm] : begin
local feature : AddCommonFeature sink : AddFeature sink featureTag
local feature : gsub.addCommonFeature : gsub.createFeature featureTag
features.set featureTag feature
buildLigationsImpl sink para [DoLigGroupT sink plm features]
buildLigationsImpl gsub para [DoLigGroupT gsub plm features]
define [DoLigGroupT sink plm features] : function [F] : begin
define [DoLigGroupT gsub plm features] : function [F] : begin
define deDupeGroups : new Map
# Groupwise deduplicate of the lookups we just added
# Push the lookups if they are indeed new
define [deDupe lookupsToPush] : begin
local h ''
foreach obj [lookupsToPush.values] : begin
set h : h + [JSON.stringify obj] + '\n'
local h : JSON.stringify lookupsToPush
local g : deDupeGroups.get h
if g : return { false g }
deDupeGroups.set h lookupsToPush
return { true lookupsToPush }
# We now actually push the lookups
local results {}
local rec : gsub.beginBlock
local lastLookupInGroup null
foreach raw [items-of lookupsToPush] : begin
local lookup : gsub.createLookup raw
if lastLookupInGroup : gsub.setDependency lastLookupInGroup lookup
set lastLookupInGroup lookup
results.push lookup
gsub.endBlock rec
deDupeGroups.set h results
return { true results }
# Execute body function F, collect lookups and add them into the GSUB
foreach [ { featureTag groups } : pairs-of plm] : begin
define feature : features.get featureTag
define { fUnique lookupsToPush } : deDupe
DoFeatureLigGroup sink feature groups F
define { fUnique lookups } : deDupe : DoFeatureLigGroup gsub feature groups F
foreach lookup [items-of lookups] : feature.addLookup lookup
# Set feature lookups
foreach ln [lookupsToPush.keys] : feature.lookups.push ln
if fUnique : begin
define rec : BeginLookupBlock sink
# Add lookups into the sink
foreach { ln obj } lookupsToPush : begin
if sink.lookups.(ln) : throw : new Error "Lookup name conflict \(ln)"
set sink.lookups.(ln) obj
# Set in-group priority
local lastLookupInGroup null
foreach ln [lookupsToPush.keys] : begin
if lastLookupInGroup : sink.lookupDep.push { lastLookupInGroup ln }
set lastLookupInGroup ln
EndLookupBlock rec sink
define [DoFeatureLigGroup sink feature groups F] : begin
define [DoFeatureLigGroup gsub feature groups F] : begin
define [hasLG ln] : [groups.indexOf ln] >= 0
define lookupNamePrefix : 'lig_' + feature.tag + '_'
define lookupsToPush : new Map
define [AddLookup obj] : begin
lookupsToPush.set (lookupNamePrefix + (feature.lookups.length + lookupsToPush.size)) obj
define lookupsToPush {}
define [filterNulls _rules] : begin
if [not _rules] : return _rules
@ -77,13 +66,13 @@ define [DoFeatureLigGroup sink feature groups F] : begin
define [CreateLigationLookup _rules] : begin
define rules : filterNulls _rules
if (rules && rules.length) : AddLookup
if (rules && rules.length) : lookupsToPush.push
.type 'gsub_chaining'
.rules rules
define [CreateReverseLigationLookup _rules] : begin
define rules : filterNulls _rules
if (rules && rules.length) : AddLookup
if (rules && rules.length) : lookupsToPush.push
.type 'gsub_reverse'
.rules rules
@ -99,8 +88,8 @@ define-macro LigGroup : syntax-rules
###################################################################################################
###################################################################################################
define [buildLigationsImpl sink para $LigGroup$] : begin
define { chain-rule reverse-rule } : ChainRuleBuilder sink
define [buildLigationsImpl gsub para $LigGroup$] : begin
define { chain-rule reverse-rule } : gsub.ChainRuleBuilder
define less {'less'}
define lessAndEquiv {'less' 'less.lig.shift0' 'less.lig.shift0.anti'}

View file

@ -1,27 +1,26 @@
$$include '../meta/macros.ptl'
import [CopyLanguage AddFeature AddLookup BeginLookupBlock EndLookupBlock ChainRuleBuilder] from"./table-util.mjs"
import [CvDecompose] from"../support/gr.mjs"
extern Set
export : define [buildLOCL sink para glyphStore] : begin
local rec : BeginLookupBlock sink
export : define [buildLOCL gsub para glyphStore] : begin
local rec : gsub.beginBlock
define { chain-rule } : ChainRuleBuilder sink
define { chain-rule } : gsub.ChainRuleBuilder
define cyrlSRB : CopyLanguage sink 'cyrl_SRB ' 'cyrl_DFLT'
define cyrlMKD : CopyLanguage sink 'cyrl_MKD ' 'cyrl_DFLT'
define cyrlBGR : CopyLanguage sink 'cyrl_BGR ' 'cyrl_DFLT'
define latnVIT : CopyLanguage sink 'latn_VIT ' 'latn_DFLT'
define cyrlSRB : gsub.copyLanguage 'cyrl_SRB ' 'cyrl_DFLT'
define cyrlMKD : gsub.copyLanguage 'cyrl_MKD ' 'cyrl_DFLT'
define cyrlBGR : gsub.copyLanguage 'cyrl_BGR ' 'cyrl_DFLT'
define latnVIT : gsub.copyLanguage 'latn_VIT ' 'latn_DFLT'
# SRB
define loclSRB : AddFeature sink 'locl'
cyrlSRB.features.unshift loclSRB.name
cyrlMKD.features.unshift loclSRB.name
loclSRB.lookups.push : AddLookup sink : object
type 'gsub_single'
substitutions : if para.isItalic
define loclSRB : gsub.createFeature 'locl'
cyrlSRB.addFeature loclSRB
cyrlMKD.addFeature loclSRB
loclSRB.addLookup : gsub.createLookup
.type 'gsub_single'
.substitutions : if para.isItalic
object
'cyrl/be' : glyphStore.ensureExists 'cyrl/be.SRB'
'cyrl/ghe' : glyphStore.ensureExists 'cyrl/ghe.SRB'
@ -33,11 +32,10 @@ export : define [buildLOCL sink para glyphStore] : begin
'cyrl/be' : glyphStore.ensureExists 'cyrl/be.SRB'
# BGR
define loclBGR : AddFeature sink 'locl'
cyrlBGR.features.unshift loclBGR.name
loclBGR.lookups.push : AddLookup sink : object
type 'gsub_single'
substitutions : object
define loclBGR : cyrlBGR.addFeature : gsub.createFeature 'locl'
loclBGR.addLookup : gsub.createLookup
.type 'gsub_single'
.substitutions : object
'cyrl/ve' : glyphStore.ensureExists 'cyrl/ve.BGR'
'cyrl/ghe' : glyphStore.ensureExists 'cyrl/ghe.italic'
'cyrl/De' : glyphStore.ensureExists 'cyrl/De.BGR'
@ -64,15 +62,14 @@ export : define [buildLOCL sink para glyphStore] : begin
'cyrl/yeri' : glyphStore.ensureExists 'cyrl/yeri.BGR'
# VIT
define loclVIT : AddFeature sink 'locl'
latnVIT.features.unshift loclVIT.name
define loclVIT : latnVIT.addFeature : gsub.createFeature 'locl'
define [sx s] : lambda [t] : t.map : lambda [x] "\(x)/\(s)"
do "Decompose Vietnamese glyphs"
local decompositionSubstitutions {.}
loclVIT.lookups.push : AddLookup sink
loclVIT.addLookup : gsub.createLookup
.type 'gsub_multiple'
.substitutions decompositionSubstitutions
@ -87,7 +84,7 @@ export : define [buildLOCL sink para glyphStore] : begin
local decomp : CvDecompose.get g
set decompositionSubstitutions.(gn) decomp
loclVIT.lookups.push : AddLookup sink
loclVIT.addLookup : gsub.createLookup
.type 'gsub_chaining'
.rules : list
chain-rule
@ -99,4 +96,4 @@ export : define [buildLOCL sink para glyphStore] : begin
{'breveAbove'} ~> nothing
viShiftableMarks ~> [sx 'viCenter']
EndLookupBlock.Front rec sink
gsub.endBlockAtFront rec

View file

@ -1,12 +1,12 @@
$$include '../meta/macros.ptl'
import [AddCommonFeature AddFeature AddLookup ChainRuleBuilder QueryRelatedGlyphs BeginLookupBlock EndLookupBlock] from"./table-util.mjs"
import [QueryRelatedGlyphs] from"./table-util.mjs"
export : define [buildGsubThousands sink para] : begin
local rec : BeginLookupBlock sink
export : define [buildGsubThousands gsub para] : begin
local rec : gsub.beginBlock
define Thousand : AddFeature sink 'THND'
define {chain-rule reverse-rule} : ChainRuleBuilder sink
define Thousand : gsub.addCommonFeature : gsub.createFeature 'THND'
define {chain-rule reverse-rule} : gsub.ChainRuleBuilder
define numberGlyphIDs {
'zero.lnum' 'one.lnum' 'two.lnum' 'three.lnum' 'four.lnum'
'five.lnum' 'six.lnum' 'seven.lnum' 'eight.lnum' 'nine.lnum'
@ -14,7 +14,7 @@ export : define [buildGsubThousands sink para] : begin
define [nd s] : numberGlyphIDs.map : lambda [x] "\(x).nd\(s)"
define lookupThousand1 : AddLookup sink : object
define a : Thousand.addLookup : gsub.createLookup : object
.type 'gsub_chaining'
.rules : list
chain-rule ({'period'} ~> null) (numberGlyphIDs ~> [nd 2]) (numberGlyphIDs ~> null) (numberGlyphIDs ~> null)
@ -25,13 +25,13 @@ export : define [buildGsubThousands sink para] : begin
chain-rule ([nd 4] ~> null) (numberGlyphIDs ~> [nd 3])
chain-rule ([nd 3] ~> null) (numberGlyphIDs ~> [nd 2])
define lookupThousand2 : AddLookup sink : object
define b : Thousand.addLookup : gsub.createLookup : object
.type 'gsub_chaining'
.rules : list
chain-rule (numberGlyphIDs ~> [nd 0]) (numberGlyphIDs ~> null) (numberGlyphIDs ~> null) (numberGlyphIDs ~> null)
chain-rule ([nd 0] ~> null) (numberGlyphIDs ~> [nd 0])
define lookupThousand3 : AddLookup sink : object
define c : Thousand.addLookup : gsub.createLookup : object
.type 'gsub_reverse'
.rules : list
reverse-rule ([nd 0] ~> [nd 1]) ([nd 0] ~> null)
@ -42,7 +42,7 @@ export : define [buildGsubThousands sink para] : begin
reverse-rule ([nd 0] ~> [nd 6]) ([nd 5] ~> null)
reverse-rule ([nd 0] ~> [nd 1]) ([nd 6] ~> null)
gsub.setDependency a b
gsub.setDependency b c
Thousand.lookups.push lookupThousand1 lookupThousand2 lookupThousand3
AddCommonFeature sink Thousand
EndLookupBlock rec sink
gsub.endBlock rec

View file

@ -1,7 +1,7 @@
import as toposort from 'toposort'
import as Gr from"../support/gr.mjs"
import [CreateEmptyTable FinalizeTable MoveBackUtilityLookups] from"./table-util.mjs"
import [CreateEmptyTable] from"./table-util.mjs"
import [buildLigations] from"./gsub-ligation.mjs"
import [buildCCMP buildCCMPPostCvSs] from"./gsub-ccmp.mjs"
@ -70,15 +70,14 @@ define [buildGSUB para glyphStore markGlyphs] : begin
# Builds last, but the lookups are added into the beginning of the lookup list
buildLOCL gsub para glyphStore
MoveBackUtilityLookups gsub
FinalizeTable gsub
gsub.finalize
return gsub
# GPOS
define [buildGPOS para glyphStore markGlyphs] : begin
define gpos : CreateEmptyTable
buildMarkMkmk gpos glyphStore markGlyphs
FinalizeTable gpos
gpos.finalize
return gpos
# GDEF

View file

@ -1,133 +1,137 @@
import toposort from 'toposort'
import [AnyCv] from"../support/gr.mjs"
export : define [CreateEmptyTable] {.languages {.} .features {.} .lookups {.} .lookupDep {}}
extern Map
extern Set
export : define [PickLanguage sink tag] : begin
if sink.languages.(tag) : return sink.languages.(tag)
define lang {.features {}}
set sink.languages.(tag) lang
export : define [CreateEmptyTable] : new LayoutTable
class LayoutTable
public [new] : begin
set this.languages {.}
set this.features {.}
set this.lookups {.}
set this.lookupDep {}
set this.lookupOrder {}
public [pickLanguage tag] : begin
if this.languages.(tag) : return this.languages.(tag)
define lang : new LayoutLanguage tag
set this.languages.(tag) lang
return lang
export : define [CopyLanguage sink tag tagFrom] : begin
define langFrom : PickLanguage sink tagFrom
define langTo : PickLanguage sink tag
public [copyLanguage tag tagFrom] : begin
define langFrom : this.pickLanguage tagFrom
define langTo : this.pickLanguage tag
foreach [feat : items-of langFrom.features] : langTo.features.push feat
return langTo
export : define [AddLangFeature lang fea] : begin
define index : lang.features.indexOf fea.name
if (index < 0) : lang.features.push fea.name
export : define [AddFeature sink tag] : begin
define lookupArray {}
public [createFeature tag] : begin
local feature : new LayoutFeature tag
local n 0
while true : begin
if [not sink.features.(tag + '_' + n)] : begin
set sink.features.(tag + '_' + n) lookupArray
return {.tag tag .name (tag + '_' + n) .lookups lookupArray}
if [not this.features.(tag + '_' + n)] : begin
set feature.name : tag + '_' + n
set this.features.(tag + '_' + n) feature
return feature
set n : n + 1
export : define [PickFeature sink name] : begin
if sink.features.(name) : return { .name name .lookups sink.features.(name) }
define featObj { .name name .lookups {} }
set sink.features.(name) featObj.lookups
return featObj
public [addCommonFeature fea] : begin
define dfltDflt : this.pickLanguage 'DFLT_DFLT'
define latnDflt : this.pickLanguage 'latn_DFLT'
define grekDflt : this.pickLanguage 'grek_DFLT'
define cyrlDflt : this.pickLanguage 'cyrl_DFLT'
export : define [PickCommonFeature sink name] : begin
if sink.features.(name) : return { .name name .lookups sink.features.(name) }
define featObj { .name name .lookups {} }
set sink.features.(name) featObj.lookups
AddCommonFeature sink featObj
return featObj
export : define [AddFeatureLookup fea lookupName] : begin
define index : fea.lookups.indexOf lookupName
if (index < 0) : fea.lookups.push lookupName
export : define [AddLookup sink data _prefix] : begin
local prefix : _prefix || '_lut_'
local n 0
while true : begin
if [not sink.lookups.(prefix + n)] : begin
set sink.lookups.(prefix + n) data
return (prefix + n)
set n : n + 1
export : define [PickLookup sink name fallback] : begin
if sink.lookups.(name) : return sink.lookups.(name)
set sink.lookups.(name) fallback
return sink.lookups.(name)
export : define [AddCommonFeature sink fea] : begin
define dfltDflt : PickLanguage sink 'DFLT_DFLT'
define latnDflt : PickLanguage sink 'latn_DFLT'
define grekDflt : PickLanguage sink 'grek_DFLT'
define cyrlDflt : PickLanguage sink 'cyrl_DFLT'
AddLangFeature dfltDflt fea
AddLangFeature latnDflt fea
AddLangFeature grekDflt fea
AddLangFeature cyrlDflt fea
dfltDflt.addFeature fea
latnDflt.addFeature fea
grekDflt.addFeature fea
cyrlDflt.addFeature fea
return fea
define UtilityLookupPrefix '.utility-single.'
public [createLookup data _prefix] : begin
local prefix : _prefix || 'lookup/'
local n 0
while true : begin
if [not this.lookups.(prefix + n)] : begin
return : this.addLookupNoCheck (prefix + n) data
set n : n + 1
export : define [BeginLookupBlock sink] : begin
public [pickLookup name fallback] : begin
if this.lookups.(name) : return this.lookups.(name)
return : this.addLookupNoCheck name fallback
public [addLookupNoCheck name data] : begin
local lookup : Object.fromEntries : Object.entries data
set lookup.name name
set this.lookups.(name) lookup
return lookup
public [setDependency a b] : begin
if [not a.name] : throw : new Error "Invalid lookup"
if [not b.name] : throw : new Error "Invalid lookup"
this.lookupDep.push { a.name b.name }
public [beginBlock] : begin
return : object
existingLookupNames : new Set : Object.keys sink.lookups
existingLookupNames : new Set : Object.keys this.lookups
define [IsUtilityLookupId name] : [name.slice 0 UtilityLookupPrefix.length] === UtilityLookupPrefix
export : define [EndLookupBlock rec sink] : begin
local currentLookupNames : new Set : Object.keys sink.lookups
public [endBlock rec] : begin
local currentLookupNames : new Set : Object.keys this.lookups
foreach existing rec.existingLookupNames : foreach current currentLookupNames
if (![IsUtilityLookupId existing] && ![IsUtilityLookupId current] && ![rec.existingLookupNames.has current])
sink.lookupDep.push { existing current }
this.lookupDep.push { existing current }
set EndLookupBlock.Front : lambda [rec sink] : begin
local currentLookupNames : new Set : Object.keys sink.lookups
public [endBlockAtFront rec] : begin
local currentLookupNames : new Set : Object.keys this.lookups
foreach existing rec.existingLookupNames : foreach current currentLookupNames
if (![IsUtilityLookupId existing] && ![IsUtilityLookupId current] && ![rec.existingLookupNames.has current])
sink.lookupDep.push { current existing }
this.lookupDep.push { current existing }
export : define [MoveBackUtilityLookups sink] : begin
local lns : new Set : Object.keys sink.lookups
public [finalize] : begin
local lns : new Set : Object.keys this.lookups
foreach [front lns] : foreach [rear lns]
if (![IsUtilityLookupId front] && [IsUtilityLookupId rear])
sink.lookupDep.push { front rear }
this.lookupDep.push { front rear }
set this.lookupOrder : toposort this.lookupDep
foreach [{key lang} : pairs-of this.languages] : begin
if lang.features : lang.features.sort
public [ChainRuleBuilder] : begin
local table this
export : define [ChainRuleBuilder sink] : begin
define [createNewLookup f t] : begin
local subst {.}
foreach [j : range 0 f.length] : set subst.(f.(j)) t.(j)
return : AddLookup sink {.type 'gsub_single' .substitutions subst} UtilityLookupPrefix
return : table.createLookup {.type 'gsub_single' .substitutions subst} UtilityLookupPrefix
define [getSubLookup left right] : piecewise
[not right] null
([typeof right] === "string") right
([typeof right] === "string") : throw : new Error "Invalid substitution"
(right <@ Function) : getSubLookup left [right left]
true : begin
local found null
local maxMatch 0
local lookupKeys : [Object.keys sink.lookups].reverse
foreach [name : items-of lookupKeys] : begin
local st sink.lookups.(name).substitutions
if [IsUtilityLookupId name] : begin
local lookupKeys : [Object.keys table.lookups].reverse
foreach [name : items-of lookupKeys] : if [IsUtilityLookupId name] : begin
local lookup table.lookups.(name)
local st lookup.substitutions
local compatible true
local matchCount 0
foreach [j : range 0 left.length] : begin
if (st.(left.(j)) && st.(left.(j)) !== right.(j)) : set compatible false
if (st.(left.(j)) === right.(j)) : inc matchCount
if (compatible && (!found || matchCount > maxMatch)) : begin
set found name
set found lookup
set maxMatch matchCount
if found : begin
local st sink.lookups.(found).substitutions
local st found.substitutions
foreach [j : range 0 left.length] : set st.(left.(j)) right.(j)
return found
@ -147,8 +151,8 @@ export : define [ChainRuleBuilder sink] : begin
foreach [j : range 0 terms.length] : begin
local term terms.(j)
rule.match.push : Array.from : new Set term.left
local lutn : getSubLookup term.left term.right
if lutn : rule.apply.push {.at j .lookup lutn}
local lookup : getSubLookup term.left term.right
if lookup : rule.apply.push {.at j .lookup {.name lookup.name}}
return rule
define [reverse-rule] : begin
@ -179,6 +183,32 @@ export : define [ChainRuleBuilder sink] : begin
return {chain-rule reverse-rule}
class LayoutFeature
public [new tag] : begin
set this.tag tag
set this.name tag
set this.lookups {}
public [addLookup lookup] : begin
if [not lookup.name] : throw : new Error "Invalid lookup"
define index : this.lookups.indexOf lookup.name
if (index < 0) : this.lookups.push lookup.name
return lookup
class LayoutLanguage
public [new tag] : begin
set this.tag tag
set this.features {}
public [addFeature feature] : begin
if [not feature.name] : throw : new Error "Invalid feature"
define index : this.features.indexOf feature.name
if (index < 0) : this.features.push feature.name
return feature
define UtilityLookupPrefix '.utility-single/'
define [IsUtilityLookupId name] : [name.slice 0 UtilityLookupPrefix.length] === UtilityLookupPrefix
export : define [QueryRelatedGlyphs glyphs para entries] : begin
define sink {}
foreach [gid : items-of entries] : if glyphs.(gid) : begin

View file

@ -189,6 +189,14 @@ export function Cv(tag, rank, groupRank, description) {
return rel;
}
Cv.compare = function (a, b) {
if (a.tag < b.tag) return -1;
if (a.tag > b.tag) return 1;
if (a.rank < b.rank) return -1;
if (a.rank > b.rank) return 1;
return 0;
};
export const DotlessOrNot = {
query(glyph) {
if (Dotless.get(glyph)) return [Dotless];
@ -265,11 +273,13 @@ function getGrTreeImpl(gid, grSetList, fnGidToGlyph, sink) {
export function getGrMesh(gidList, grq, fnGidToGlyph) {
if (typeof gidList === "string" || !Array.isArray(gidList))
throw new TypeError(`glyphs must be a glyph array!`);
const allGrSet = new Set();
for (const g of gidList) {
for (const gr of grq.query(fnGidToGlyph(g))) allGrSet.add(gr);
}
const allGrList = Array.from(allGrSet);
const allGrList = Array.from(allGrSet).sort(Cv.compare).reverse();
let ret = [];
for (const gr of allGrList) {
const col = [];