-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathot_layout.go
359 lines (306 loc) · 10.1 KB
/
ot_layout.go
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
package harfbuzz
import (
"github.com/benoitkugler/textlayout/fonts"
tt "github.com/benoitkugler/textlayout/fonts/truetype"
)
// ported from src/hb-ot-layout.cc, hb-ot-layout.hh
// Copyright © 1998-2004 David Turner and Werner Lemberg
// Copyright © 2006 2007,2008,2009 Red Hat, Inc. 2012,2013 Google, Inc. Behdad Esfahbod
// /**
// * SECTION:hb-ot-layout
// * @title: hb-ot-layout
// * @short_description: OpenType Layout
// * @include: hb-ot.h
// *
// * Functions for querying OpenType Layout features in the font face.
// **/
const maxNestingLevel = 6
func (c *otApplyContext) applyString(proxy otProxyMeta, accel *otLayoutLookupAccelerator) {
buffer := c.buffer
lookup := accel.lookup
if len(buffer.Info) == 0 || c.lookupMask == 0 {
return
}
c.setLookupProps(lookup.Props())
if !lookup.isReverse() {
// in/out forward substitution/positioning
if !proxy.inplace {
buffer.clearOutput()
}
buffer.idx = 0
c.applyForward(accel)
if !proxy.inplace {
buffer.swapBuffers()
}
} else {
/* in-place backward substitution/positioning */
// assert (!buffer->have_output);
buffer.idx = len(buffer.Info) - 1
c.applyBackward(accel)
}
}
func (c *otApplyContext) applyForward(accel *otLayoutLookupAccelerator) bool {
ret := false
buffer := c.buffer
for buffer.idx < len(buffer.Info) {
applied := false
if accel.digest.mayHave(buffer.cur(0).Glyph) &&
(buffer.cur(0).Mask&c.lookupMask) != 0 &&
c.checkGlyphProperty(buffer.cur(0), c.lookupProps) {
applied = accel.apply(c)
}
if applied {
ret = true
} else {
buffer.nextGlyph()
}
}
return ret
}
func (c *otApplyContext) applyBackward(accel *otLayoutLookupAccelerator) bool {
ret := false
buffer := c.buffer
for do := true; do; do = buffer.idx >= 0 {
if accel.digest.mayHave(buffer.cur(0).Glyph) &&
(buffer.cur(0).Mask&c.lookupMask != 0) &&
c.checkGlyphProperty(buffer.cur(0), c.lookupProps) {
applied := accel.apply(c)
ret = ret || applied
}
// the reverse lookup doesn't "advance" cursor (for good reason).
buffer.idx--
}
return ret
}
/*
* kern
*/
// tests whether a face includes any state-machine kerning in the 'kern' table.
//
// Does NOT examine the GPOS table.
func hasMachineKerning(kern tt.TableKernx) bool {
for _, subtable := range kern {
if _, isType1 := subtable.Data.(tt.Kern1); isType1 {
return true
}
}
return false
}
// tests whether a face has any cross-stream kerning (i.e., kerns
// that make adjustments perpendicular to the direction of the text
// flow: Y adjustments in horizontal text or X adjustments in
// vertical text) in the 'kern' table.
//
// Does NOT examine the GPOS table.
func hasCrossKerning(kern tt.TableKernx) bool {
for _, subtable := range kern {
if subtable.IsCrossStream() {
return true
}
}
return false
}
func (sp *otShapePlan) otLayoutKern(font *Font, buffer *Buffer) {
kern := font.otTables.Kern
c := newAatApplyContext(sp, font, buffer)
c.applyKernx(kern)
}
var otTagLatinScript = tt.NewTag('l', 'a', 't', 'n')
// SelectScript selects an OpenType script from the `scriptTags` array,
// returning its index in the Scripts slice and the script tag.
//
// If `table` does not have any of the requested scripts, then `DFLT`,
// `dflt`, and `latn` tags are tried in that order. If the table still does not
// have any of these scripts, NoScriptIndex is returned.
//
// An additional boolean if returned : it is `true` if one of the requested scripts is selected, or `false` if a fallback
// script is selected or if no scripts are selected.
func SelectScript(table *tt.TableLayout, scriptTags []tt.Tag) (int, tt.Tag, bool) {
for _, tag := range scriptTags {
if scriptIndex := table.FindScript(tag); scriptIndex != -1 {
return scriptIndex, tag, true
}
}
// try finding 'DFLT'
if scriptIndex := table.FindScript(tagDefaultScript); scriptIndex != -1 {
return scriptIndex, tagDefaultScript, false
}
// try with 'dflt'; MS site has had typos and many fonts use it now :(
if scriptIndex := table.FindScript(tagDefaultLanguage); scriptIndex != -1 {
return scriptIndex, tagDefaultLanguage, false
}
// try with 'latn'; some old fonts put their features there even though
// they're really trying to support Thai, for example :(
if scriptIndex := table.FindScript(otTagLatinScript); scriptIndex != -1 {
return scriptIndex, otTagLatinScript, false
}
return NoScriptIndex, NoScriptIndex, false
}
// SelectLanguage fetches the index of the first language tag from `languageTags` in the specified layout table,
// underneath `scriptIndex`.
// It not found, the `dflt` language tag is searched.
// Return `true` if the requested language tag is found, `false` otherwise.
// If `scriptIndex` is `NoScriptIndex` or if no language is found, `DefaultLanguageIndex` is returned.
func SelectLanguage(table *tt.TableLayout, scriptIndex int, languageTags []tt.Tag) (int, bool) {
if scriptIndex == NoScriptIndex {
return DefaultLanguageIndex, false
}
s := table.Scripts[scriptIndex]
for _, lang := range languageTags {
if languageIndex := s.FindLanguage(lang); languageIndex != -1 {
return languageIndex, true
}
}
// try finding 'dflt'
if languageIndex := s.FindLanguage(tagDefaultLanguage); languageIndex != -1 {
return languageIndex, false
}
return DefaultLanguageIndex, false
}
func findFeature(g *tt.TableLayout, featureTag tt.Tag) uint16 {
if index, ok := g.FindFeatureIndex(featureTag); ok {
return index
}
return NoFeatureIndex
}
// Fetches the index of a given feature tag in the specified face's GSUB table
// or GPOS table, underneath the specified script and language.
// Return `NoFeatureIndex` it the the feature is not found.
func FindFeatureForLang(table *tt.TableLayout, scriptIndex, languageIndex int, featureTag tt.Tag) uint16 {
if scriptIndex == NoScriptIndex {
return NoFeatureIndex
}
l := table.Scripts[scriptIndex].GetLangSys(uint16(languageIndex))
for _, fIndex := range l.Features {
if featureTag == table.Features[fIndex].Tag {
return fIndex
}
}
return NoFeatureIndex
}
// Fetches the tag of a requested feature index in the given layout table,
// underneath the specified script and language. Returns -1 if no feature is requested.
func getRequiredFeature(g *tt.TableLayout, scriptIndex, languageIndex int) (uint16, tt.Tag) {
if scriptIndex == NoScriptIndex || languageIndex == DefaultLanguageIndex {
return NoFeatureIndex, 0
}
l := g.Scripts[scriptIndex].Languages[languageIndex]
if l.RequiredFeatureIndex == 0xFFFF {
return NoFeatureIndex, 0
}
index := l.RequiredFeatureIndex
return index, g.Features[index].Tag
}
// getFeatureLookupsWithVar fetches a list of all lookups enumerated for the specified feature, in
// the given table, enabled at the specified variations index.
// it returns the basic feature if `variationsIndex == noVariationsIndex`
func getFeatureLookupsWithVar(table *tt.TableLayout, featureIndex uint16, variationsIndex int) []uint16 {
if featureIndex == NoFeatureIndex {
return nil
}
if variationsIndex == noVariationsIndex { // just fetch the feature
return table.Features[featureIndex].LookupIndices
}
// hook the variations
subs := table.FeatureVariations[variationsIndex].FeatureSubstitutions
for _, sub := range subs {
if sub.FeatureIndex == featureIndex {
return sub.AlternateFeature.LookupIndices
}
}
return nil
}
// tests whether a specified lookup index in the specified face would
// trigger a substitution on the given glyph sequence.
// zeroContext indicating whether substitutions should be context-free.
func otLayoutLookupWouldSubstitute(font *Font, lookupIndex uint16, glyphs []fonts.GID, zeroContext bool) bool {
gsub := font.otTables.GSUB
if int(lookupIndex) >= len(gsub.Lookups) {
return false
}
c := wouldApplyContext{font.face, glyphs, nil, zeroContext}
l := lookupGSUB(gsub.Lookups[lookupIndex])
return l.wouldApply(&c, &font.gsubAccels[lookupIndex])
}
// Called before substitution lookups are performed, to ensure that glyph
// class and other properties are set on the glyphs in the buffer.
func layoutSubstituteStart(font *Font, buffer *Buffer) {
gdef := font.otTables.GDEF
hasClass := gdef.Class != nil
for i := range buffer.Info {
if hasClass {
buffer.Info[i].glyphProps = gdef.GetGlyphProps(buffer.Info[i].Glyph)
}
buffer.Info[i].ligProps = 0
buffer.Info[i].syllable = 0
}
}
func otLayoutDeleteGlyphsInplace(buffer *Buffer, filter func(*GlyphInfo) bool) {
// Merge clusters and delete filtered glyphs.
var (
j int
info = buffer.Info
pos = buffer.Pos
)
for i := range info {
if filter(&info[i]) {
/* Merge clusters.
* Same logic as buffer.delete_glyph(), but for in-place removal. */
cluster := info[i].Cluster
if i+1 < len(buffer.Info) && cluster == info[i+1].Cluster {
/* Cluster survives; do nothing. */
continue
}
if j != 0 {
/* Merge cluster backward. */
if cluster < info[j-1].Cluster {
mask := info[i].Mask
oldCluster := info[j-1].Cluster
for k := j; k != 0 && info[k-1].Cluster == oldCluster; k-- {
info[k-1].setCluster(cluster, mask)
}
}
continue
}
if i+1 < len(buffer.Info) {
/* Merge cluster forward. */
buffer.mergeClusters(i, i+2)
}
continue
}
if j != i {
info[j] = info[i]
pos[j] = pos[i]
}
j++
}
buffer.Info = buffer.Info[:j]
buffer.Pos = buffer.Pos[:j]
}
// Called before positioning lookups are performed, to ensure that glyph
// attachment types and glyph-attachment chains are set for the glyphs in the buffer.
func otLayoutPositionStart(_ *Font, buffer *Buffer) {
positionStartGPOS(buffer)
}
// Called after positioning lookups are performed, to finish glyph offsets.
func otLayoutPositionFinishOffsets(_ *Font, buffer *Buffer) {
positionFinishOffsetsGPOS(buffer)
}
func clearSyllables(_ *otShapePlan, _ *Font, buffer *Buffer) {
info := buffer.Info
for i := range info {
info[i].syllable = 0
}
}
func glyphInfoSubstituted(info *GlyphInfo) bool {
return (info.glyphProps & substituted) != 0
}
func clearSubstitutionFlags(_ *otShapePlan, _ *Font, buffer *Buffer) {
info := buffer.Info
for i := range info {
info[i].glyphProps &= ^substituted
}
}
func reverseGraphemes(b *Buffer) {
b.reverseGroups(func(_, gi2 *GlyphInfo) bool { return gi2.isContinuation() }, b.ClusterLevel == MonotoneGraphemes)
}