font-otc.lua /size: 33 Kb    last modification: 2021-10-28 13:50
1
if
not
modules
then
modules
=
{
}
end
modules
[
'
font-otc
'
]
=
{
2
version
=
1
.
001
,
3
comment
=
"
companion to font-ini.mkiv
"
,
4
author
=
"
Hans Hagen, PRAGMA-ADE, Hasselt NL
"
,
5
copyright
=
"
PRAGMA ADE / ConTeXt Development Team
"
,
6
license
=
"
see context related readme files
"
7
}
8 9
local
insert
,
sortedkeys
,
sortedhash
,
tohash
=
table
.
insert
,
table
.
sortedkeys
,
table
.
sortedhash
,
table
.
tohash
10
local
type
,
next
,
tonumber
=
type
,
next
,
tonumber
11
local
lpegmatch
=
lpeg
.
match
12
local
utfbyte
,
utflen
=
utf
.
byte
,
utf
.
len
13
local
sortedhash
=
table
.
sortedhash
14 15
-- we assume that the other otf stuff is loaded already
16 17
local
trace_loading
=
false
trackers
.
register
(
"
otf.loading
"
,
function
(
v
)
trace_loading
=
v
end
)
18
local
report_otf
=
logs
.
reporter
(
"
fonts
"
,
"
otf loading
"
)
19 20
local
fonts
=
fonts
21
local
otf
=
fonts
.
handlers
.
otf
22
local
registerotffeature
=
otf
.
features
.
register
23
local
setmetatableindex
=
table
.
setmetatableindex
24 25
local
fonthelpers
=
fonts
.
helpers
26
local
checkmerge
=
fonthelpers
.
checkmerge
27
local
checkflags
=
fonthelpers
.
checkflags
28
local
checksteps
=
fonthelpers
.
checksteps
29 30
local
normalized
=
{
31
substitution
=
"
substitution
"
,
32
single
=
"
substitution
"
,
33
ligature
=
"
ligature
"
,
34
alternate
=
"
alternate
"
,
35
multiple
=
"
multiple
"
,
36
kern
=
"
kern
"
,
37
pair
=
"
pair
"
,
38
single
=
"
single
"
,
39
chainsubstitution
=
"
chainsubstitution
"
,
40
chainposition
=
"
chainposition
"
,
41
}
42 43
local
types
=
{
44
substitution
=
"
gsub_single
"
,
45
ligature
=
"
gsub_ligature
"
,
46
alternate
=
"
gsub_alternate
"
,
47
multiple
=
"
gsub_multiple
"
,
48
kern
=
"
gpos_pair
"
,
49
pair
=
"
gpos_pair
"
,
50
single
=
"
gpos_single
"
,
51
chainsubstitution
=
"
gsub_contextchain
"
,
52
chainposition
=
"
gpos_contextchain
"
,
53
}
54 55
local
names
=
{
56
gsub_single
=
"
gsub
"
,
57
gsub_multiple
=
"
gsub
"
,
58
gsub_alternate
=
"
gsub
"
,
59
gsub_ligature
=
"
gsub
"
,
60
gsub_context
=
"
gsub
"
,
61
gsub_contextchain
=
"
gsub
"
,
62
gsub_reversecontextchain
=
"
gsub
"
,
63
gpos_single
=
"
gpos
"
,
64
gpos_pair
=
"
gpos
"
,
65
gpos_cursive
=
"
gpos
"
,
66
gpos_mark2base
=
"
gpos
"
,
67
gpos_mark2ligature
=
"
gpos
"
,
68
gpos_mark2mark
=
"
gpos
"
,
69
gpos_context
=
"
gpos
"
,
70
gpos_contextchain
=
"
gpos
"
,
71
}
72 73
setmetatableindex
(
types
,
function
(
t
,
k
)
t
[
k
]
=
k
return
k
end
)
-- "key"
74 75
local
everywhere
=
{
[
"
*
"
]
=
{
[
"
*
"
]
=
true
}
}
-- or: { ["*"] = { "*" } }
76
local
noflags
=
{
false
,
false
,
false
,
false
}
77 78
-- beware: shared, maybe we should copy the sequence
79 80
local
function
getrange
(
sequences
,
category
)
81
local
count
=
#
sequences
82
local
first
=
nil
83
local
last
=
nil
84
for
i
=
1
,
count
do
85
local
t
=
sequences
[
i
]
.
type
86
if
t
and
names
[
t
]
=
=
category
then
87
if
not
first
then
88
first
=
i
89
end
90
last
=
i
91
end
92
end
93
return
first
or
1
,
last
or
count
94
end
95 96
local
function
validspecification
(
specification
,
name
)
97
local
dataset
=
specification
.
dataset
98
if
dataset
then
99
-- okay
100
elseif
specification
[
1
]
then
101
dataset
=
specification
102
specification
=
{
dataset
=
dataset
}
103
else
104
dataset
=
{
{
data
=
specification
.
data
}
}
105
specification
.
data
=
nil
106
specification
.
coverage
=
dataset
107
specification
.
dataset
=
dataset
108
end
109
local
first
=
dataset
[
1
]
110
if
first
then
111
first
=
first
.
data
112
end
113
if
not
first
then
114
report_otf
(
"
invalid feature specification, no dataset
"
)
115
return
116
end
117
if
type
(
name
)
~
=
"
string
"
then
118
name
=
specification
.
name
or
first
.
name
119
end
120
if
type
(
name
)
~
=
"
string
"
then
121
report_otf
(
"
invalid feature specification, no name
"
)
122
return
123
end
124
local
n
=
#
dataset
125
if
n
>
0
then
126
for
i
=
1
,
n
do
127
setmetatableindex
(
dataset
[
i
]
,
specification
)
128
end
129
return
specification
,
name
130
end
131
end
132 133
local
function
addfeature
(
data
,
feature
,
specifications
)
134 135
-- todo: add some validator / check code so that we're more tolerant to
136
-- user errors
137 138
if
not
specifications
then
139
report_otf
(
"
missing specification
"
)
140
return
141
end
142 143
local
descriptions
=
data
.
descriptions
144
local
resources
=
data
.
resources
145
local
features
=
resources
.
features
146
local
sequences
=
resources
.
sequences
147 148
if
not
features
or
not
sequences
then
149
report_otf
(
"
missing specification
"
)
150
return
151
end
152 153
local
alreadydone
=
resources
.
alreadydone
154
if
not
alreadydone
then
155
alreadydone
=
{
}
156
resources
.
alreadydone
=
alreadydone
157
end
158
if
alreadydone
[
specifications
]
then
159
return
160
else
161
alreadydone
[
specifications
]
=
true
162
end
163 164
-- feature has to be unique but the name entry wins eventually
165 166
local
fontfeatures
=
resources
.
features
or
everywhere
167
local
unicodes
=
resources
.
unicodes
168
local
splitter
=
lpeg
.
splitter
(
"
"
,
unicodes
)
169
local
done
=
0
170
local
skip
=
0
171
local
aglunicodes
=
false
172
local
privateslot
=
fonthelpers
.
privateslot
173 174
local
specifications
=
validspecification
(
specifications
,
feature
)
175
if
not
specifications
then
176
-- report_otf("invalid specification")
177
return
178
end
179 180
local
p
=
lpeg
.
P
(
"
P
"
)
181
*
(
lpeg
.
patterns
.
hexdigit
^
1
/
function
(
s
)
return
tonumber
(
s
,
16
)
end
)
182
*
lpeg
.
P
(
-1
)
183 184
local
function
tounicode
(
code
)
185
if
not
code
then
186
return
187
end
188
if
type
(
code
)
=
=
"
number
"
then
189
return
code
190
end
191
local
u
=
unicodes
[
code
]
192
if
u
then
193
-- unicodes[code] = u
194
return
u
195
end
196
if
utflen
(
code
)
=
=
1
then
197
u
=
utfbyte
(
code
)
198
if
u
then
199
return
u
200
end
201
end
202
if
privateslot
then
203
u
=
privateslot
(
code
)
-- no creation !
204
if
u
then
205
-- unicodes[code] = u
206
return
u
207
end
208
end
209
local
u
=
lpegmatch
(
p
,
code
)
210
if
u
then
211
-- unicodes[code] = u
212
return
u
213
end
214
if
not
aglunicodes
then
215
aglunicodes
=
fonts
.
encodings
.
agl
.
unicodes
-- delayed
216
end
217
local
u
=
aglunicodes
[
code
]
218
if
u
then
219
-- unicodes[code] = u
220
return
u
221
end
222
end
223 224
local
coverup
=
otf
.
coverup
225
local
coveractions
=
coverup
.
actions
226
local
stepkey
=
coverup
.
stepkey
227
local
register
=
coverup
.
register
228 229
local
function
prepare_substitution
(
list
,
featuretype
,
nocheck
)
230
local
coverage
=
{
}
231
local
cover
=
coveractions
[
featuretype
]
232
for
code
,
replacement
in
next
,
list
do
233
local
unicode
=
tounicode
(
code
)
234
local
description
=
descriptions
[
unicode
]
235
if
not
nocheck
and
not
description
then
236
-- todo: trace !
237
skip
=
skip
+
1
238
else
239
if
type
(
replacement
)
=
=
"
table
"
then
240
replacement
=
replacement
[
1
]
241
end
242
replacement
=
tounicode
(
replacement
)
243
if
replacement
and
(
nocheck
or
descriptions
[
replacement
]
)
then
244
cover
(
coverage
,
unicode
,
replacement
)
245
done
=
done
+
1
246
else
247
skip
=
skip
+
1
248
end
249
end
250
end
251
return
coverage
252
end
253 254
local
function
prepare_alternate
(
list
,
featuretype
,
nocheck
)
255
local
coverage
=
{
}
256
local
cover
=
coveractions
[
featuretype
]
257
for
code
,
replacement
in
next
,
list
do
258
local
unicode
=
tounicode
(
code
)
259
local
description
=
descriptions
[
unicode
]
260
if
not
nocheck
and
not
description
then
261
skip
=
skip
+
1
262
elseif
type
(
replacement
)
=
=
"
table
"
then
263
local
r
=
{
}
264
for
i
=
1
,
#
replacement
do
265
local
u
=
tounicode
(
replacement
[
i
]
)
266
r
[
i
]
=
(
nocheck
or
descriptions
[
u
]
)
and
u
or
unicode
267
end
268
cover
(
coverage
,
unicode
,
r
)
269
done
=
done
+
1
270
else
271
local
u
=
tounicode
(
replacement
)
272
if
u
then
273
cover
(
coverage
,
unicode
,
{
u
}
)
274
done
=
done
+
1
275
else
276
skip
=
skip
+
1
277
end
278
end
279
end
280
return
coverage
281
end
282 283
local
function
prepare_multiple
(
list
,
featuretype
,
nocheck
)
284
local
coverage
=
{
}
285
local
cover
=
coveractions
[
featuretype
]
286
for
code
,
replacement
in
next
,
list
do
287
local
unicode
=
tounicode
(
code
)
288
local
description
=
descriptions
[
unicode
]
289
if
not
nocheck
and
not
description
then
290
skip
=
skip
+
1
291
elseif
type
(
replacement
)
=
=
"
table
"
then
292
local
r
=
{
}
293
local
n
=
0
294
for
i
=
1
,
#
replacement
do
295
local
u
=
tounicode
(
replacement
[
i
]
)
296
if
nocheck
or
descriptions
[
u
]
then
297
n
=
n
+
1
298
r
[
n
]
=
u
299
end
300
end
301
if
n
>
0
then
302
cover
(
coverage
,
unicode
,
r
)
303
done
=
done
+
1
304
else
305
skip
=
skip
+
1
306
end
307
else
308
local
u
=
tounicode
(
replacement
)
309
if
u
then
310
cover
(
coverage
,
unicode
,
{
u
}
)
311
done
=
done
+
1
312
else
313
skip
=
skip
+
1
314
end
315
end
316
end
317
return
coverage
318
end
319 320
local
function
prepare_ligature
(
list
,
featuretype
,
nocheck
)
321
local
coverage
=
{
}
322
local
cover
=
coveractions
[
featuretype
]
323
for
code
,
ligature
in
next
,
list
do
324
local
unicode
=
tounicode
(
code
)
325
local
description
=
descriptions
[
unicode
]
326
if
not
nocheck
and
not
description
then
327
skip
=
skip
+
1
328
else
329
if
type
(
ligature
)
=
=
"
string
"
then
330
ligature
=
{
lpegmatch
(
splitter
,
ligature
)
}
331
end
332
local
present
=
true
333
for
i
=
1
,
#
ligature
do
334
local
l
=
ligature
[
i
]
335
local
u
=
tounicode
(
l
)
336
if
nocheck
or
descriptions
[
u
]
then
337
ligature
[
i
]
=
u
338
else
339
present
=
false
340
break
341
end
342
end
343
if
present
then
344
cover
(
coverage
,
unicode
,
ligature
)
345
done
=
done
+
1
346
else
347
skip
=
skip
+
1
348
end
349
end
350
end
351
return
coverage
352
end
353 354
local
function
resetspacekerns
(
)
355
-- a bit of a hack, this nil setting but it forces a
356
-- rehash of the resources needed .. the feature itself
357
-- should be a kern (at least for now)
358
data
.
properties
.
hasspacekerns
=
true
359
data
.
resources
.
spacekerns
=
nil
360
end
361 362
local
function
prepare_kern
(
list
,
featuretype
)
363
local
coverage
=
{
}
364
local
cover
=
coveractions
[
featuretype
]
365
local
isspace
=
false
366
for
code
,
replacement
in
next
,
list
do
367
local
unicode
=
tounicode
(
code
)
368
local
description
=
descriptions
[
unicode
]
369
if
description
and
type
(
replacement
)
=
=
"
table
"
then
370
local
r
=
{
}
371
for
k
,
v
in
next
,
replacement
do
372
local
u
=
tounicode
(
k
)
373
if
u
then
374
r
[
u
]
=
v
375
if
u
=
=
32
then
376
isspace
=
true
377
end
378
end
379
end
380
if
next
(
r
)
then
381
cover
(
coverage
,
unicode
,
r
)
382
done
=
done
+
1
383
if
unicode
=
=
32
then
384
isspace
=
true
385
end
386
else
387
skip
=
skip
+
1
388
end
389
else
390
skip
=
skip
+
1
391
end
392
end
393
if
isspace
then
394
resetspacekerns
(
)
395
end
396
return
coverage
397
end
398 399
local
function
prepare_pair
(
list
,
featuretype
)
400
local
coverage
=
{
}
401
local
cover
=
coveractions
[
featuretype
]
402
if
cover
then
403
for
code
,
replacement
in
next
,
list
do
404
local
unicode
=
tounicode
(
code
)
405
local
description
=
descriptions
[
unicode
]
406
if
description
and
type
(
replacement
)
=
=
"
table
"
then
407
local
r
=
{
}
408
for
k
,
v
in
next
,
replacement
do
409
local
u
=
tounicode
(
k
)
410
if
u
then
411
r
[
u
]
=
v
412
if
u
=
=
32
then
413
isspace
=
true
414
end
415
end
416
end
417
if
next
(
r
)
then
418
cover
(
coverage
,
unicode
,
r
)
419
done
=
done
+
1
420
if
unicode
=
=
32
then
421
isspace
=
true
422
end
423
else
424
skip
=
skip
+
1
425
end
426
else
427
skip
=
skip
+
1
428
end
429
end
430
if
isspace
then
431
resetspacekerns
(
)
432
end
433
else
434
report_otf
(
"
unknown cover type %a
"
,
featuretype
)
435
end
436
return
coverage
437
end
438 439
local
prepare_single
=
prepare_pair
-- we could have a better test on the spec
440 441
local
function
hassteps
(
lookups
)
442
if
lookups
then
443
for
i
=
1
,
#
lookups
do
444
local
l
=
lookups
[
i
]
445
if
l
then
446
for
j
=
1
,
#
l
do
447
local
l
=
l
[
j
]
448
if
l
then
449
local
n
=
l
.
nofsteps
450
if
not
n
then
451
-- gsub_remove
452
return
true
453
elseif
n
>
0
then
454
return
true
455
end
456
end
457
end
458
end
459
end
460
end
461
return
false
462
end
463 464
local
function
prepare_chain
(
list
,
featuretype
,
sublookups
,
nocheck
)
465
-- todo: coveractions
466
local
rules
=
list
.
rules
467
local
coverage
=
{
}
468
if
rules
then
469
local
rulehash
=
{
}
470
local
rulesize
=
0
471
local
lookuptype
=
types
[
featuretype
]
472
for
nofrules
=
1
,
#
rules
do
473
local
rule
=
rules
[
nofrules
]
474
local
current
=
rule
.
current
475
local
before
=
rule
.
before
476
local
after
=
rule
.
after
477
local
replacements
=
rule
.
replacements
or
false
478
local
sequence
=
{
}
479
local
nofsequences
=
0
480
if
before
then
481
for
n
=
1
,
#
before
do
482
nofsequences
=
nofsequences
+
1
483
sequence
[
nofsequences
]
=
before
[
n
]
484
end
485
end
486
local
start
=
nofsequences
+
1
487
for
n
=
1
,
#
current
do
488
nofsequences
=
nofsequences
+
1
489
sequence
[
nofsequences
]
=
current
[
n
]
490
end
491
local
stop
=
nofsequences
492
if
after
then
493
for
n
=
1
,
#
after
do
494
nofsequences
=
nofsequences
+
1
495
sequence
[
nofsequences
]
=
after
[
n
]
496
end
497
end
498
local
lookups
=
rule
.
lookups
or
false
499
local
subtype
=
nil
500
if
lookups
and
sublookups
then
501
local
l
=
{
}
502
for
k
,
v
in
sortedhash
(
lookups
)
do
503
local
t
=
type
(
v
)
504
if
t
=
=
"
table
"
then
505
-- already ok
506
for
i
=
1
,
#
v
do
507
local
vi
=
v
[
i
]
508
if
type
(
vi
)
~
=
"
table
"
then
509
v
[
i
]
=
{
vi
}
510
end
511
end
512
l
[
k
]
=
v
513
elseif
t
=
=
"
number
"
then
514
local
lookup
=
sublookups
[
v
]
515
if
lookup
then
516
l
[
k
]
=
{
lookup
}
517
if
not
subtype
then
518
subtype
=
lookup
.
type
519
end
520
elseif
v
=
=
0
then
521
l
[
k
]
=
{
{
type
=
"
gsub_remove
"
,
nosteps
=
true
}
}
522
else
523
l
[
k
]
=
false
-- { false } -- new
524
end
525
else
526
l
[
k
]
=
false
-- { false } -- new
527
end
528
end
529
if
nocheck
then
530
-- fragile
531
rule
.
lookups
=
l
--no, because checking can spoil it
532
end
533
lookups
=
l
534
end
535
if
nofsequences
>
0
then
-- we merge coverage into one
536
-- we copy as we can have different fonts
537
if
hassteps
(
lookups
)
then
538
local
hashed
=
{
}
539
for
i
=
1
,
nofsequences
do
540
local
t
=
{
}
541
local
s
=
sequence
[
i
]
542
for
i
=
1
,
#
s
do
543
local
u
=
tounicode
(
s
[
i
]
)
544
if
u
then
545
t
[
u
]
=
true
546
end
547
end
548
hashed
[
i
]
=
t
549
end
550
sequence
=
hashed
551
rulesize
=
rulesize
+
1
552
rulehash
[
rulesize
]
=
{
553
nofrules
,
-- 1
554
lookuptype
,
-- 2
555
sequence
,
-- 3
556
start
,
-- 4
557
stop
,
-- 5
558
lookups
,
-- 6 (6/7 also signal of what to do)
559
replacements
,
-- 7
560
subtype
,
-- 8
561
}
562
for
unic
in
sortedhash
(
sequence
[
start
]
)
do
563
local
cu
=
coverage
[
unic
]
564
if
not
cu
then
565
coverage
[
unic
]
=
rulehash
-- can now be done cleaner i think
566
end
567
end
568
sequence
.
n
=
nofsequences
569
else
570
-- report_otf("no steps for %a",lookuptype) -- e.g. in primes feature
571
end
572
end
573
end
574
rulehash
.
n
=
rulesize
575
end
576
return
coverage
577
end
578 579
local
dataset
=
specifications
.
dataset
580 581
local
function
report
(
name
,
category
,
position
,
first
,
last
,
sequences
)
582
report_otf
(
"
injecting name %a of category %a at position %i in [%i,%i] of [%i,%i]
"
,
583
name
,
category
,
position
,
first
,
last
,
1
,
#
sequences
)
584
end
585 586
local
function
inject
(
specification
,
sequences
,
sequence
,
first
,
last
,
category
,
name
)
587
local
position
=
specification
.
position
or
false
588
if
not
position
then
589
position
=
specification
.
prepend
590
if
position
=
=
true
then
591
if
trace_loading
then
592
report
(
name
,
category
,
first
,
first
,
last
,
sequences
)
593
end
594
insert
(
sequences
,
first
,
sequence
)
595
return
596
end
597
end
598
if
not
position
then
599
position
=
specification
.
append
600
if
position
=
=
true
then
601
if
trace_loading
then
602
report
(
name
,
category
,
last
+
1
,
first
,
last
,
sequences
)
603
end
604
insert
(
sequences
,
last
+
1
,
sequence
)
605
return
606
end
607
end
608
local
kind
=
type
(
position
)
609
if
kind
=
=
"
string
"
then
610
local
index
=
false
611
for
i
=
first
,
last
do
612
local
s
=
sequences
[
i
]
613
local
f
=
s
.
features
614
if
f
then
615
for
k
in
sortedhash
(
f
)
do
-- next, f do
616
if
k
=
=
position
then
617
index
=
i
618
break
619
end
620
end
621
if
index
then
622
break
623
end
624
end
625
end
626
if
index
then
627
position
=
index
628
else
629
position
=
last
+
1
630
end
631
elseif
kind
=
=
"
number
"
then
632
if
position
<
0
then
633
position
=
last
-
position
+
1
634
end
635
if
position
>
last
then
636
position
=
last
+
1
637
elseif
position
<
first
then
638
position
=
first
639
end
640
else
641
position
=
last
+
1
642
end
643
if
trace_loading
then
644
report
(
name
,
category
,
position
,
first
,
last
,
sequences
)
645
end
646
insert
(
sequences
,
position
,
sequence
)
647
end
648 649
for
s
=
1
,
#
dataset
do
650
local
specification
=
dataset
[
s
]
651
local
valid
=
specification
.
valid
-- nowhere used
652
local
feature
=
specification
.
name
or
feature
653
if
not
feature
or
feature
=
=
"
"
then
654
report_otf
(
"
no valid name given for extra feature
"
)
655
elseif
not
valid
or
valid
(
data
,
specification
,
feature
)
then
-- anum uses this
656
local
initialize
=
specification
.
initialize
657
if
initialize
then
658
-- when false is returned we initialize only once
659
specification
.
initialize
=
initialize
(
specification
,
data
)
and
initialize
or
nil
660
end
661
local
askedfeatures
=
specification
.
features
or
everywhere
662
local
askedsteps
=
specification
.
steps
or
specification
.
subtables
or
{
specification
.
data
}
or
{
}
663
local
featuretype
=
specification
.
type
or
"
substitution
"
664
local
featureaction
=
false
665
local
featureflags
=
specification
.
flags
or
noflags
666
local
nocheck
=
specification
.
nocheck
667
local
mapping
=
specification
.
mapping
668
local
featureorder
=
specification
.
order
or
{
feature
}
669
local
featurechain
=
(
featuretype
=
=
"
chainsubstitution
"
or
featuretype
=
=
"
chainposition
"
)
and
1
or
0
670
local
nofsteps
=
0
671
local
steps
=
{
}
672
local
sublookups
=
specification
.
lookups
673
local
category
=
nil
674
local
steptype
=
nil
675
local
sequence
=
nil
676
--
677
if
fonts
.
handlers
.
otf
.
handlers
[
featuretype
]
then
678
featureaction
=
true
-- function based
679
else
680
featuretype
=
normalized
[
specification
.
type
or
"
substitution
"
]
or
"
substitution
"
681
end
682
--
683
checkflags
(
specification
,
resources
)
684
--
685
for
k
,
v
in
next
,
askedfeatures
do
686
if
v
[
1
]
then
687
askedfeatures
[
k
]
=
tohash
(
v
)
688
end
689
end
690
--
691
if
featureflags
[
1
]
then
featureflags
[
1
]
=
"
mark
"
end
692
if
featureflags
[
2
]
then
featureflags
[
2
]
=
"
ligature
"
end
693
if
featureflags
[
3
]
then
featureflags
[
3
]
=
"
base
"
end
694
--
695
if
featureaction
then
696 697
category
=
"
gsub
"
698
sequence
=
{
699
features
=
{
[
feature
]
=
askedfeatures
}
,
700
flags
=
featureflags
,
701
name
=
feature
,
-- redundant
702
order
=
featureorder
,
703
type
=
featuretype
,
704
-- steps = { },
705
nofsteps
=
0
,
-- just in case we test for that
706
}
707 708
else
709 710
if
sublookups
then
711
local
s
=
{
}
712
for
i
=
1
,
#
sublookups
do
713
local
specification
=
sublookups
[
i
]
714
local
askedsteps
=
specification
.
steps
or
specification
.
subtables
or
{
specification
.
data
}
or
{
}
715
local
featuretype
=
normalized
[
specification
.
type
or
"
substitution
"
]
or
"
substitution
"
716
local
featureflags
=
specification
.
flags
or
noflags
717
local
nofsteps
=
0
718
local
steps
=
{
}
719
for
i
=
1
,
#
askedsteps
do
720
local
list
=
askedsteps
[
i
]
721
local
coverage
=
nil
722
local
format
=
nil
723
if
featuretype
=
=
"
substitution
"
then
724
coverage
=
prepare_substitution
(
list
,
featuretype
,
nocheck
)
725
elseif
featuretype
=
=
"
ligature
"
then
726
coverage
=
prepare_ligature
(
list
,
featuretype
,
nocheck
)
727
elseif
featuretype
=
=
"
alternate
"
then
728
coverage
=
prepare_alternate
(
list
,
featuretype
,
nocheck
)
729
elseif
featuretype
=
=
"
multiple
"
then
730
coverage
=
prepare_multiple
(
list
,
featuretype
,
nocheck
)
731
elseif
featuretype
=
=
"
kern
"
or
featuretype
=
=
"
move
"
then
732
format
=
featuretype
733
coverage
=
prepare_kern
(
list
,
featuretype
)
734
elseif
featuretype
=
=
"
pair
"
then
735
format
=
"
pair
"
736
coverage
=
prepare_pair
(
list
,
featuretype
)
737
elseif
featuretype
=
=
"
single
"
then
738
format
=
"
single
"
739
coverage
=
prepare_single
(
list
,
featuretype
)
740
end
741
if
coverage
and
next
(
coverage
)
then
742
nofsteps
=
nofsteps
+
1
743
steps
[
nofsteps
]
=
register
(
coverage
,
featuretype
,
format
,
feature
,
nofsteps
,
descriptions
,
resources
)
744
end
745
end
746
--
747
checkmerge
(
specification
)
748
checksteps
(
specification
)
749
--
750
s
[
i
]
=
{
751
[
stepkey
]
=
steps
,
752
nofsteps
=
nofsteps
,
753
flags
=
featureflags
,
754
type
=
types
[
featuretype
]
,
755
}
756
end
757
sublookups
=
s
758
end
759 760
for
i
=
1
,
#
askedsteps
do
761
local
list
=
askedsteps
[
i
]
762
local
coverage
=
nil
763
local
format
=
nil
764
if
featuretype
=
=
"
substitution
"
then
765
-- see font-imp-tweaks: we directly pass a mapping so no checks done
766
category
=
"
gsub
"
767
coverage
=
(
mapping
and
list
)
or
prepare_substitution
(
list
,
featuretype
,
nocheck
)
768
elseif
featuretype
=
=
"
ligature
"
then
769
category
=
"
gsub
"
770
coverage
=
prepare_ligature
(
list
,
featuretype
,
nocheck
)
771
elseif
featuretype
=
=
"
alternate
"
then
772
category
=
"
gsub
"
773
coverage
=
prepare_alternate
(
list
,
featuretype
,
nocheck
)
774
elseif
featuretype
=
=
"
multiple
"
then
775
category
=
"
gsub
"
776
coverage
=
prepare_multiple
(
list
,
featuretype
,
nocheck
)
777
elseif
featuretype
=
=
"
kern
"
or
featuretype
=
=
"
move
"
then
778
category
=
"
gpos
"
779
format
=
featuretype
780
coverage
=
prepare_kern
(
list
,
featuretype
)
781
elseif
featuretype
=
=
"
pair
"
then
782
category
=
"
gpos
"
783
format
=
"
pair
"
784
coverage
=
prepare_pair
(
list
,
featuretype
)
785
elseif
featuretype
=
=
"
single
"
then
786
category
=
"
gpos
"
787
format
=
"
single
"
788
coverage
=
prepare_single
(
list
,
featuretype
)
789
elseif
featuretype
=
=
"
chainsubstitution
"
then
790
category
=
"
gsub
"
791
coverage
=
prepare_chain
(
list
,
featuretype
,
sublookups
,
nocheck
)
792
elseif
featuretype
=
=
"
chainposition
"
then
793
category
=
"
gpos
"
794
coverage
=
prepare_chain
(
list
,
featuretype
,
sublookups
,
nocheck
)
795
else
796
report_otf
(
"
not registering feature %a, unknown category
"
,
feature
)
797
return
798
end
799
if
coverage
and
next
(
coverage
)
then
800
nofsteps
=
nofsteps
+
1
801
steps
[
nofsteps
]
=
register
(
coverage
,
featuretype
,
format
,
feature
,
nofsteps
,
descriptions
,
resources
)
802
end
803
end
804 805
if
nofsteps
>
0
then
806
sequence
=
{
807
chain
=
featurechain
,
808
features
=
{
[
feature
]
=
askedfeatures
}
,
809
flags
=
featureflags
,
810
name
=
feature
,
-- redundant
811
order
=
featureorder
,
812
[
stepkey
]
=
steps
,
813
nofsteps
=
nofsteps
,
814
type
=
types
[
featuretype
]
,
815
}
816
end
817
end
818 819
if
sequence
then
820
-- script = { lang1, lang2, lang3 } or script = { lang1 = true, ... }
821
checkflags
(
sequence
,
resources
)
822
checkmerge
(
sequence
)
823
checksteps
(
sequence
)
824
-- position | prepend | append
825
local
first
,
last
=
getrange
(
sequences
,
category
)
826
inject
(
specification
,
sequences
,
sequence
,
first
,
last
,
category
,
feature
)
827
-- register in metadata (merge as there can be a few)
828
local
features
=
fontfeatures
[
category
]
829
if
not
features
then
830
features
=
{
}
831
fontfeatures
[
category
]
=
features
832
end
833
local
k
=
features
[
feature
]
834
if
not
k
then
835
k
=
{
}
836
features
[
feature
]
=
k
837
end
838
--
839
for
script
,
languages
in
next
,
askedfeatures
do
840
local
kk
=
k
[
script
]
841
if
not
kk
then
842
kk
=
{
}
843
k
[
script
]
=
kk
844
end
845
for
language
,
value
in
next
,
languages
do
846
kk
[
language
]
=
value
847
end
848
end
849
end
850 851
end
852
end
853
if
trace_loading
then
854
report_otf
(
"
registering feature %a, affected glyphs %a, skipped glyphs %a
"
,
feature
,
done
,
skip
)
855
end
856 857
end
858 859
otf
.
enhancers
.
addfeature
=
addfeature
860 861
local
extrafeatures
=
{
}
862
local
knownfeatures
=
{
}
863 864
function
otf
.
addfeature
(
name
,
specification
)
865
if
type
(
name
)
=
=
"
table
"
then
866
specification
=
name
867
end
868
if
type
(
specification
)
~
=
"
table
"
then
869
report_otf
(
"
invalid feature specification, no valid table
"
)
870
return
871
end
872
specification
,
name
=
validspecification
(
specification
,
name
)
873
if
name
and
specification
then
874
local
slot
=
knownfeatures
[
name
]
875
if
not
slot
then
876
-- we have a new one
877
slot
=
#
extrafeatures
+
1
878
knownfeatures
[
name
]
=
slot
879
elseif
specification
.
overload
=
=
false
then
880
-- we add an extre one
881
slot
=
#
extrafeatures
+
1
882
knownfeatures
[
name
]
=
slot
883
else
884
-- we overload a previous one
885
end
886
specification
.
name
=
name
-- to be sure
887
extrafeatures
[
slot
]
=
specification
888
-- report_otf("adding feature %a @ %i",name,slot)
889
end
890
end
891 892
-- for feature, specification in next, extrafeatures do
893
-- addfeature(data,feature,specification)
894
-- end
895 896
local
function
enhance
(
data
,
filename
,
raw
)
897
for
slot
=
1
,
#
extrafeatures
do
898
local
specification
=
extrafeatures
[
slot
]
899
addfeature
(
data
,
specification
.
name
,
specification
)
900
end
901
end
902 903
otf
.
enhancers
.
enhance
=
enhance
904 905
otf
.
enhancers
.
register
(
"
check extra features
"
,
enhance
)
906 907
-- fonts.handlers.otf.features.register {
908
-- name = 'hangulfix',
909
-- description = 'fixes for hangul',
910
-- }
911 912
-- fonts.handlers.otf.addfeature {
913
-- name = "stest",
914
-- type = "substitution",
915
-- data = {
916
-- a = "X",
917
-- b = "P",
918
-- }
919
-- }
920
-- fonts.handlers.otf.addfeature {
921
-- name = "atest",
922
-- type = "alternate",
923
-- data = {
924
-- a = { "X", "Y" },
925
-- b = { "P", "Q" },
926
-- }
927
-- }
928
-- fonts.handlers.otf.addfeature {
929
-- name = "mtest",
930
-- type = "multiple",
931
-- data = {
932
-- a = { "X", "Y" },
933
-- b = { "P", "Q" },
934
-- }
935
-- }
936
-- fonts.handlers.otf.addfeature {
937
-- name = "ltest",
938
-- type = "ligature",
939
-- data = {
940
-- X = { "a", "b" },
941
-- Y = { "d", "a" },
942
-- }
943
-- }
944
-- fonts.handlers.otf.addfeature {
945
-- name = "ktest",
946
-- type = "kern",
947
-- data = {
948
-- a = { b = -500 },
949
-- }
950
-- }
951