* 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

View file

@ -1,183 +1,213 @@
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
return lang
export : define [CreateEmptyTable] : new LayoutTable
export : define [CopyLanguage sink tag tagFrom] : begin
define langFrom : PickLanguage sink tagFrom
define langTo : PickLanguage sink tag
foreach [feat : items-of langFrom.features] : langTo.features.push feat
return langTo
class LayoutTable
public [new] : begin
set this.languages {.}
set this.features {.}
set this.lookups {.}
set this.lookupDep {}
set this.lookupOrder {}
export : define [AddLangFeature lang fea] : begin
define index : lang.features.indexOf fea.name
if (index < 0) : lang.features.push fea.name
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 [AddFeature sink tag] : begin
define lookupArray {}
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}
set n : n + 1
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 [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 [createFeature tag] : begin
local feature : new LayoutFeature tag
local n 0
while true : begin
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 [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
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 [AddFeatureLookup fea lookupName] : begin
define index : fea.lookups.indexOf lookupName
if (index < 0) : fea.lookups.push lookupName
dfltDflt.addFeature fea
latnDflt.addFeature fea
grekDflt.addFeature fea
cyrlDflt.addFeature fea
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
return fea
export : define [PickLookup sink name fallback] : begin
if sink.lookups.(name) : return sink.lookups.(name)
set sink.lookups.(name) fallback
return sink.lookups.(name)
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 [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'
public [pickLookup name fallback] : begin
if this.lookups.(name) : return this.lookups.(name)
return : this.addLookupNoCheck name fallback
AddLangFeature dfltDflt fea
AddLangFeature latnDflt fea
AddLangFeature grekDflt fea
AddLangFeature cyrlDflt fea
public [addLookupNoCheck name data] : begin
local lookup : Object.fromEntries : Object.entries data
set lookup.name name
set this.lookups.(name) lookup
return lookup
return fea
public [setDependency a b] : begin
if [not a.name] : throw : new Error "Invalid lookup"
if [not b.name] : throw : new Error "Invalid lookup"
define UtilityLookupPrefix '.utility-single.'
this.lookupDep.push { a.name b.name }
export : define [BeginLookupBlock sink] : begin
return : object
existingLookupNames : new Set : Object.keys sink.lookups
public [beginBlock] : begin
return : object
existingLookupNames : new Set : Object.keys this.lookups
define [IsUtilityLookupId name] : [name.slice 0 UtilityLookupPrefix.length] === UtilityLookupPrefix
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])
this.lookupDep.push { existing current }
export : define [EndLookupBlock rec sink] : begin
local currentLookupNames : new Set : Object.keys sink.lookups
foreach existing rec.existingLookupNames : foreach current currentLookupNames
if (![IsUtilityLookupId existing] && ![IsUtilityLookupId current] && ![rec.existingLookupNames.has current])
sink.lookupDep.push { existing current }
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])
this.lookupDep.push { current existing }
set EndLookupBlock.Front : lambda [rec sink] : begin
local currentLookupNames : new Set : Object.keys sink.lookups
foreach existing rec.existingLookupNames : foreach current currentLookupNames
if (![IsUtilityLookupId existing] && ![IsUtilityLookupId current] && ![rec.existingLookupNames.has current])
sink.lookupDep.push { current existing }
public [finalize] : begin
local lns : new Set : Object.keys this.lookups
foreach [front lns] : foreach [rear lns]
if (![IsUtilityLookupId front] && [IsUtilityLookupId rear])
this.lookupDep.push { front rear }
export : define [MoveBackUtilityLookups sink] : begin
local lns : new Set : Object.keys sink.lookups
foreach [front lns] : foreach [rear lns]
if (![IsUtilityLookupId front] && [IsUtilityLookupId rear])
sink.lookupDep.push { front rear }
set this.lookupOrder : toposort this.lookupDep
foreach [{key lang} : pairs-of this.languages] : begin
if lang.features : lang.features.sort
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
public [ChainRuleBuilder] : begin
local table this
define [createNewLookup f t] : begin
local subst {.}
foreach [j : range 0 f.length] : set subst.(f.(j)) t.(j)
return : table.createLookup {.type 'gsub_single' .substitutions subst} UtilityLookupPrefix
define [getSubLookup left right] : piecewise
[not right] null
([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 table.lookups].reverse
foreach [name : items-of lookupKeys] : if [IsUtilityLookupId name] : begin
local lookup table.lookups.(name)
local st lookup.substitutions
define [getSubLookup left right] : piecewise
[not right] null
([typeof right] === "string") right
(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 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
foreach [j : range 0 left.length] : set st.(left.(j)) right.(j)
return found
if found : begin
local st found.substitutions
foreach [j : range 0 left.length] : set st.(left.(j)) right.(j)
return found
return : createNewLookup left right
return : createNewLookup left right
define [chain-rule] : begin
local terms : [{}.slice.call arguments 0].map (x -> [if x.left x {.left x .right null}])
local rule {.match {} .apply {} .inputBegins 0 .inputEnds 0}
local foundi false
local founde false
foreach [j : range 0 terms.length] : if (!foundi && terms.(j).right) : begin
set rule.inputBegins j
set foundi true
foreach [j : range (terms.length - 1) downtill 0] : if (!founde && terms.(j).right) : begin
set rule.inputEnds (j + 1)
set founde true
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}
return rule
define [reverse-rule] : begin
local terms : [{}.slice.call arguments 0].map (x -> [if x.left x {.left x .right null}])
local rule {.match {} .to {} .inputIndex 0}
local foundi false
foreach [j : range 0 terms.length] : begin
local term terms.(j)
local subst : new Map
foreach [g : items-of term.left] : subst.set g g
if term.right : begin
if foundi : throw : new Error "Duplicate substitutions in one reverse rule"
define [chain-rule] : begin
local terms : [{}.slice.call arguments 0].map (x -> [if x.left x {.left x .right null}])
local rule {.match {} .apply {} .inputBegins 0 .inputEnds 0}
local foundi false
local founde false
foreach [j : range 0 terms.length] : if (!foundi && terms.(j).right) : begin
set rule.inputBegins j
set foundi true
set rule.inputIndex j
local toGlyphs : piecewise
(term.right <@ Function) [term.right term.left]
true term.right
foreach [k : range 0 term.left.length]
subst.set term.left.(k) (toGlyphs.(k) || term.left.(k))
foreach [j : range (terms.length - 1) downtill 0] : if (!founde && terms.(j).right) : begin
set rule.inputEnds (j + 1)
set founde true
foreach [j : range 0 terms.length] : begin
local term terms.(j)
rule.match.push : Array.from : new Set term.left
local lookup : getSubLookup term.left term.right
if lookup : rule.apply.push {.at j .lookup {.name lookup.name}}
return rule
set rule.to : Array.from [subst.values]
define [reverse-rule] : begin
local terms : [{}.slice.call arguments 0].map (x -> [if x.left x {.left x .right null}])
local rule {.match {} .to {} .inputIndex 0}
local foundi false
set rule.match.(j) : Array.from [subst.keys]
foreach [j : range 0 terms.length] : begin
local term terms.(j)
local subst : new Map
foreach [g : items-of term.left] : subst.set g g
return rule
if term.right : begin
if foundi : throw : new Error "Duplicate substitutions in one reverse rule"
set foundi true
set rule.inputIndex j
local toGlyphs : piecewise
(term.right <@ Function) [term.right term.left]
true term.right
foreach [k : range 0 term.left.length]
subst.set term.left.(k) (toGlyphs.(k) || term.left.(k))
return {chain-rule reverse-rule}
set rule.to : Array.from [subst.values]
set rule.match.(j) : Array.from [subst.keys]
return rule
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 {}