1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142 | 1
1
1
17695
145
145
118
118
13
105
118
48
48
8
8
8
8
1
1
1
8
8
566
566
47
178
178
8
8
8
8
8
8
8
8
8
8
8
8
8
2
6
1
1
1
1
8
8
8
8
8
8
8
8
8
48
16481
407
17015
1
| OTProcessor = require './OTProcessor'
GlyphInfo = require './GlyphInfo'
class GSUBProcessor extends OTProcessor
applyLookup: (lookupType, table) ->
switch lookupType
when 1 # Single Substitution
index = @coverageIndex table.coverage
return false if index is -1
glyph = @glyphIterator.cur
switch table.version
when 1
glyph.id = (glyph.id + table.deltaGlyphID) & 0xffff
when 2
glyph.id = table.substitute.get(index)
return true
when 2 # Multiple Substitution
index = @coverageIndex table.coverage
unless index is -1
sequence = table.sequences.get(index)
@glyphIterator.cur.id = sequence[0]
replacement = []
for gid in sequence[1...]
g = new GlyphInfo gid
g.features = @glyphIterator.cur.features
replacement.push g
@glyphs.splice @glyphIterator.index + 1, 0, replacement...
return true
when 3 # Alternate Substitution
index = @coverageIndex table.coverage
unless index is -1
USER_INDEX = 0 # TODO
@glyphIterator.cur.id = table.alternateSet.get(index)[USER_INDEX]
return true
when 4 # Ligature Substitution
index = @coverageIndex table.coverage
return false if index is -1
for ligature in table.ligatureSets.get(index)
matched = @sequenceMatchIndices 1, ligature.components
continue unless matched
curGlyph = @glyphIterator.cur
# Concatenate all of the characters the new ligature will represent
characters = [curGlyph.codePoints...]
for index in matched
characters.push @glyphs[index].codePoints...
# Create the replacement ligature glyph
ligatureGlyph = new GlyphInfo ligature.glyph, characters
ligatureGlyph.features = curGlyph.features
# From Harfbuzz:
# - If it *is* a mark ligature, we don't allocate a new ligature id, and leave
# the ligature to keep its old ligature id. This will allow it to attach to
# a base ligature in GPOS. Eg. if the sequence is: LAM,LAM,SHADDA,FATHA,HEH,
# and LAM,LAM,HEH for a ligature, they will leave SHADDA and FATHA with a
# ligature id and component value of 2. Then if SHADDA,FATHA form a ligature
# later, we don't want them to lose their ligature id/component, otherwise
# GPOS will fail to correctly position the mark ligature on top of the
# LAM,LAM,HEH ligature. See https://bugzilla.gnome.org/show_bug.cgi?id=676343
#
# - If a ligature is formed of components that some of which are also ligatures
# themselves, and those ligature components had marks attached to *their*
# components, we have to attach the marks to the new ligature component
# positions! Now *that*'s tricky! And these marks may be following the
# last component of the whole sequence, so we should loop forward looking
# for them and update them.
#
# Eg. the sequence is LAM,LAM,SHADDA,FATHA,HEH, and the font first forms a
# 'calt' ligature of LAM,HEH, leaving the SHADDA and FATHA with a ligature
# id and component == 1. Now, during 'liga', the LAM and the LAM-HEH ligature
# form a LAM-LAM-HEH ligature. We need to reassign the SHADDA and FATHA to
# the new ligature with a component value of 2.
#
# This in fact happened to a font... See https://bugzilla.gnome.org/show_bug.cgi?id=437633
ligatureGlyph.ligatureID = if ligatureGlyph.isMark then 0 else @ligatureID++
lastLigID = curGlyph.ligatureID
lastNumComps = curGlyph.codePoints.length
curComps = lastNumComps
idx = @glyphIterator.index + 1
# Set ligatureID and ligatureComponent on glyphs that were skipped in the matched sequence.
# This allows GPOS to attach marks to the correct ligature components.
for matchIndex in matched
# Don't assign new ligature components for mark ligatures (see above)
if ligatureGlyph.isMark
idx = matchIndex
else
while idx < matchIndex
ligatureComponent = curComps - lastNumComps + Math.min @glyphs[idx].ligatureComponent or 1, lastNumComps
@glyphs[idx].ligatureID = ligatureGlyph.ligatureID
@glyphs[idx].ligatureComponent = ligatureComponent
idx++
lastLigID = @glyphs[idx].ligatureID
lastNumComps = @glyphs[idx].codePoints.length
curComps += lastNumComps
idx++ # skip base glyph
# Adjust ligature components for any marks following
Iif lastLigID and not ligatureGlyph.isMark
for i in [idx...@glyphs.length] by 1
if @glyphs[i].ligatureID is lastLigID
ligatureComponent = curComps - lastNumComps + Math.min @glyphs[i].ligatureComponent or 1, lastNumComps
@glyphs[i].ligatureComponent = ligatureComponent
else
break
# Delete the matched glyphs, and replace the current glyph with the ligature glyph
for index in matched by -1
@glyphs.splice index, 1
@glyphs[@glyphIterator.index] = ligatureGlyph
return true
when 5 # Contextual Substitution
@applyContext table
when 6 # Chaining Contextual Substitution
@applyChainingContext table
when 7 # Extension Substitution
@applyLookup table.lookupType, table.extension
else
throw new Error "GSUB lookupType #{lookupType} is not supported"
return false
module.exports = GSUBProcessor
|