font-sol.lua /size: 32 Kb    last modification: 2020-07-01 14:35
1
if
not
modules
then
modules
=
{
}
end
modules
[
'
font-sol
'
]
=
{
-- this was: node-spl
2
version
=
1
.
001
,
3
comment
=
"
companion to font-sol.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
-- We can speed this up.
10 11
-- This module is dedicated to the oriental tex project and for
12
-- the moment is too experimental to be publicly supported.
13
--
14
-- We could cache solutions: say that we store the featureset and
15
-- all 'words' -> replacement ... so we create a large solution
16
-- database (per font)
17
--
18
-- This module can be optimized by using a dedicated dynamics handler
19
-- but I'll only do that when the rest of the code is stable.
20
--
21
-- Todo: bind setups to paragraph.
22 23
local
gmatch
,
concat
,
format
,
remove
=
string
.
gmatch
,
table
.
concat
,
string
.
format
,
table
.
remove
24
local
next
,
tostring
,
tonumber
=
next
,
tostring
,
tonumber
25
local
insert
,
remove
=
table
.
insert
,
table
.
remove
26
local
getrandom
=
utilities
.
randomizer
.
get
27 28
local
utilities
,
logs
,
statistics
,
fonts
,
trackers
=
utilities
,
logs
,
statistics
,
fonts
,
trackers
29
local
interfaces
,
commands
,
attributes
=
interfaces
,
commands
,
attributes
30
local
nodes
,
node
,
tex
=
nodes
,
node
,
tex
31 32
local
trace_split
=
false
trackers
.
register
(
"
builders.paragraphs.solutions.splitters.splitter
"
,
function
(
v
)
trace_split
=
v
end
)
33
local
trace_optimize
=
false
trackers
.
register
(
"
builders.paragraphs.solutions.splitters.optimizer
"
,
function
(
v
)
trace_optimize
=
v
end
)
34
local
trace_colors
=
false
trackers
.
register
(
"
builders.paragraphs.solutions.splitters.colors
"
,
function
(
v
)
trace_colors
=
v
end
)
35
local
trace_goodies
=
false
trackers
.
register
(
"
fonts.goodies
"
,
function
(
v
)
trace_goodies
=
v
end
)
36 37
local
report_solutions
=
logs
.
reporter
(
"
fonts
"
,
"
solutions
"
)
38
local
report_splitters
=
logs
.
reporter
(
"
fonts
"
,
"
splitters
"
)
39
local
report_optimizers
=
logs
.
reporter
(
"
fonts
"
,
"
optimizers
"
)
40 41
local
variables
=
interfaces
.
variables
42 43
local
v_normal
=
variables
.
normal
44
local
v_reverse
=
variables
.
reverse
45
local
v_preroll
=
variables
.
preroll
46
local
v_random
=
variables
.
random
47
local
v_split
=
variables
.
split
48 49
local
implement
=
interfaces
.
implement
50 51
local
settings_to_array
=
utilities
.
parsers
.
settings_to_array
52
local
settings_to_hash
=
utilities
.
parsers
.
settings_to_hash
53 54
local
tasks
=
nodes
.
tasks
55 56
local
nuts
=
nodes
.
nuts
57 58
local
getfield
=
nuts
.
getfield
59
local
getnext
=
nuts
.
getnext
60
local
getprev
=
nuts
.
getprev
61
local
getid
=
nuts
.
getid
62
local
getattr
=
nuts
.
getattr
63
local
getfont
=
nuts
.
getfont
64
local
getsubtype
=
nuts
.
getsubtype
65
local
getlist
=
nuts
.
getlist
66
local
getdirection
=
nuts
.
getdirection
67
local
getwidth
=
nuts
.
getwidth
68
local
getdata
=
nuts
.
getdata
69 70
local
getboxglue
=
nuts
.
getboxglue
71 72
local
setattr
=
nuts
.
setattr
73
local
setlink
=
nuts
.
setlink
74
local
setnext
=
nuts
.
setnext
75
local
setlist
=
nuts
.
setlist
76 77
local
find_node_tail
=
nuts
.
tail
78
local
flush_node
=
nuts
.
flush_node
79
local
flush_node_list
=
nuts
.
flush_list
80
local
copy_node_list
=
nuts
.
copy_list
81
local
hpack_nodes
=
nuts
.
hpack
82
local
insert_node_before
=
nuts
.
insert_before
83
local
insert_node_after
=
nuts
.
insert_after
84
local
protect_glyphs
=
nuts
.
protect_glyphs
85
local
start_of_par
=
nuts
.
start_of_par
86 87
local
nextnode
=
nuts
.
traversers
.
next
88
local
nexthlist
=
nuts
.
traversers
.
hlist
89
local
nextwhatsit
=
nuts
.
traversers
.
whatsit
90 91
local
repack_hlist
=
nuts
.
repackhlist
92 93
local
nodes_to_utf
=
nodes
.
listtoutf
94 95
----- protect_glyphs = nodes.handlers.protectglyphs
96 97
local
setnodecolor
=
nodes
.
tracers
.
colors
.
set
98 99
local
nodecodes
=
nodes
.
nodecodes
100
local
whatsitcodes
=
nodes
.
whatsitcodes
101
local
kerncodes
=
nodes
.
kerncodes
102 103
local
glyph_code
=
nodecodes
.
glyph
104
local
disc_code
=
nodecodes
.
disc
105
local
kern_code
=
nodecodes
.
kern
106
local
hlist_code
=
nodecodes
.
hlist
107
local
dir_code
=
nodecodes
.
dir
108
local
localpar_code
=
nodecodes
.
localpar
109 110
local
whatsit_code
=
nodecodes
.
whatsit
111 112
local
fontkern_code
=
kerncodes
.
fontkern
113 114
local
userdefinedwhatsit_code
=
whatsitcodes
.
userdefined
115 116
local
nodeproperties
=
nodes
.
properties
.
data
117 118
local
nodepool
=
nuts
.
pool
119
local
usernodeids
=
nodepool
.
userids
120 121
local
new_direction
=
nodepool
.
direction
122
local
new_usernode
=
nodepool
.
usernode
123
local
new_glue
=
nodepool
.
glue
124
local
new_leftskip
=
nodepool
.
leftskip
125 126
local
starttiming
=
statistics
.
starttiming
127
local
stoptiming
=
statistics
.
stoptiming
128
----- process_characters = nodes.handlers.characters
129
local
inject_kerns
=
nodes
.
injections
.
handler
130 131
local
fonthashes
=
fonts
.
hashes
132
local
setfontdynamics
=
fonthashes
.
setdynamics
133 134
local
texsetattribute
=
tex
.
setattribute
135
local
unsetvalue
=
attributes
.
unsetvalue
136 137
local
parbuilders
=
builders
.
paragraphs
138
parbuilders
.
solutions
=
parbuilders
.
solutions
or
{
}
139
local
parsolutions
=
parbuilders
.
solutions
140
parsolutions
.
splitters
=
parsolutions
.
splitters
or
{
}
141
local
splitters
=
parsolutions
.
splitters
142 143
local
solutions
=
{
}
-- attribute sets
144
local
registered
=
{
}
-- backmapping
145
splitters
.
registered
=
registered
146 147
local
a_split
=
attributes
.
private
(
'
splitter
'
)
148 149
local
preroll
=
true
150
local
criterium
=
0
151
local
randomseed
=
nil
152
local
optimize
=
nil
-- set later
153
local
variant
=
v_normal
154
local
splitwords
=
true
155 156
local
cache
=
{
}
157
local
variants
=
{
}
158
local
max_less
=
0
159
local
max_more
=
0
160 161
local
stack
=
{
}
162 163
local
dummy
=
{
164
attribute
=
unsetvalue
,
165
randomseed
=
0
,
166
criterium
=
0
,
167
preroll
=
false
,
168
optimize
=
nil
,
169
splitwords
=
false
,
170
variant
=
v_normal
,
171
}
172 173
local
function
checksettings
(
r
,
settings
)
174
local
s
=
r
.
settings
175
local
method
=
settings_to_array
(
settings
.
method
or
"
"
)
176
local
optimize
,
preroll
,
splitwords
177
for
i
=
1
,
#
method
do
178
local
k
=
method
[
i
]
179
if
k
=
=
v_preroll
then
180
preroll
=
true
181
elseif
k
=
=
v_split
then
182
splitwords
=
true
183
elseif
variants
[
k
]
then
184
variant
=
k
185
optimize
=
variants
[
k
]
-- last one wins
186
end
187
end
188
r
.
randomseed
=
tonumber
(
settings
.
randomseed
)
or
s
.
randomseed
or
r
.
randomseed
or
0
189
r
.
criterium
=
tonumber
(
settings
.
criterium
)
or
s
.
criterium
or
r
.
criterium
or
0
190
r
.
preroll
=
preroll
or
false
191
r
.
splitwords
=
splitwords
or
false
192
r
.
optimize
=
optimize
or
s
.
optimize
or
r
.
optimize
or
variants
[
v_normal
]
193
end
194 195
local
function
pushsplitter
(
name
,
settings
)
196
local
r
=
name
and
registered
[
name
]
197
if
r
then
198
if
settings
then
199
checksettings
(
r
,
settings
)
200
end
201
else
202
r
=
dummy
203
end
204
insert
(
stack
,
r
)
205
-- brr
206
randomseed
=
r
.
randomseed
or
0
207
criterium
=
r
.
criterium
or
0
208
preroll
=
r
.
preroll
or
false
209
optimize
=
r
.
optimize
or
nil
210
splitwords
=
r
.
splitwords
or
nil
211
--
212
texsetattribute
(
a_split
,
r
.
attribute
)
213
return
#
stack
214
end
215 216
local
function
popsplitter
(
)
217
remove
(
stack
)
218
local
n
=
#
stack
219
local
r
=
stack
[
n
]
or
dummy
220
--
221
randomseed
=
r
.
randomseed
or
0
222
criterium
=
r
.
criterium
or
0
223
preroll
=
r
.
preroll
or
false
224
optimize
=
r
.
optimize
or
nil
225
--
226
texsetattribute
(
a_split
,
r
.
attribute
)
227
return
n
228
end
229 230
local
contextsetups
=
fonts
.
specifiers
.
contextsetups
231 232
local
function
convert
(
featuresets
,
name
,
list
)
233
if
list
then
234
local
numbers
=
{
}
235
local
nofnumbers
=
0
236
for
i
=
1
,
#
list
do
237
local
feature
=
list
[
i
]
238
local
fs
=
featuresets
[
feature
]
239
local
fn
=
fs
and
fs
.
number
240
if
not
fn
then
241
-- fall back on global features
242
fs
=
contextsetups
[
feature
]
243
fn
=
fs
and
fs
.
number
244
end
245
if
fn
then
246
nofnumbers
=
nofnumbers
+
1
247
numbers
[
nofnumbers
]
=
fn
248
if
trace_goodies
or
trace_optimize
then
249
report_solutions
(
"
solution %a of %a uses feature %a with number %s
"
,
i
,
name
,
feature
,
fn
)
250
end
251
else
252
report_solutions
(
"
solution %a of %a has an invalid feature reference %a
"
,
i
,
name
,
feature
)
253
end
254
end
255
return
nofnumbers
>
0
and
numbers
256
end
257
end
258 259
local
function
initialize
(
goodies
)
260
local
solutions
=
goodies
.
solutions
261
if
solutions
then
262
local
featuresets
=
goodies
.
featuresets
263
local
goodiesname
=
goodies
.
name
264
if
trace_goodies
or
trace_optimize
then
265
report_solutions
(
"
checking solutions in %a
"
,
goodiesname
)
266
end
267
for
name
,
set
in
next
,
solutions
do
268
set
.
less
=
convert
(
featuresets
,
name
,
set
.
less
)
269
set
.
more
=
convert
(
featuresets
,
name
,
set
.
more
)
270
end
271
end
272
end
273 274
fonts
.
goodies
.
register
(
"
solutions
"
,
initialize
)
275 276
function
splitters
.
define
(
name
,
settings
)
277
local
goodies
=
settings
.
goodies
278
local
solution
=
settings
.
solution
279
local
less
=
settings
.
less
280
local
more
=
settings
.
more
281
local
less_set
,
more_set
282
local
l
=
less
and
settings_to_array
(
less
)
283
local
m
=
more
and
settings_to_array
(
more
)
284
if
goodies
then
285
goodies
=
fonts
.
goodies
.
load
(
goodies
)
-- also in tfmdata
286
if
goodies
then
287
local
featuresets
=
goodies
.
featuresets
288
local
solution
=
solution
and
goodies
.
solutions
[
solution
]
289
if
l
and
#
l
>
0
then
290
less_set
=
convert
(
featuresets
,
name
,
less
)
-- take from settings
291
else
292
less_set
=
solution
and
solution
.
less
-- take from goodies
293
end
294
if
m
and
#
m
>
0
then
295
more_set
=
convert
(
featuresets
,
name
,
more
)
-- take from settings
296
else
297
more_set
=
solution
and
solution
.
more
-- take from goodies
298
end
299
end
300
else
301
if
l
then
302
local
n
=
#
less_set
303
for
i
=
1
,
#
l
do
304
local
ss
=
contextsetups
[
l
[
i
]
]
305
if
ss
then
306
n
=
n
+
1
307
less_set
[
n
]
=
ss
.
number
308
end
309
end
310
end
311
if
m
then
312
local
n
=
#
more_set
313
for
i
=
1
,
#
m
do
314
local
ss
=
contextsetups
[
m
[
i
]
]
315
if
ss
then
316
n
=
n
+
1
317
more_set
[
n
]
=
ss
.
number
318
end
319
end
320
end
321
end
322
if
trace_optimize
then
323
report_solutions
(
"
defining solutions %a, less %a, more %a
"
,
name
,
concat
(
less_set
or
{
}
,
"
"
)
,
concat
(
more_set
or
{
}
,
"
"
)
)
324
end
325
local
nofsolutions
=
#
solutions
+
1
326
local
t
=
{
327
solution
=
solution
,
328
less
=
less_set
or
{
}
,
329
more
=
more_set
or
{
}
,
330
settings
=
settings
,
-- for tracing
331
attribute
=
nofsolutions
,
332
}
333
solutions
[
nofsolutions
]
=
t
334
registered
[
name
]
=
t
335
return
nofsolutions
336
end
337 338
local
nofwords
,
noftries
,
nofadapted
,
nofkept
,
nofparagraphs
=
0
,
0
,
0
,
0
,
0
339 340
local
splitter_one
=
usernodeids
[
"
splitters.one
"
]
341
local
splitter_two
=
usernodeids
[
"
splitters.two
"
]
342 343
local
a_word
=
attributes
.
private
(
'
word
'
)
344 345
local
encapsulate
=
false
346 347
directives
.
register
(
"
builders.paragraphs.solutions.splitters.encapsulate
"
,
function
(
v
)
348
encapsulate
=
v
349
end
)
350 351
function
splitters
.
split
(
head
)
-- best also pass the direction
352
local
current
=
head
353
local
r2l
=
false
354
local
start
=
nil
355
local
stop
=
nil
356
local
attribute
=
0
357
cache
=
{
}
358
max_less
=
0
359
max_more
=
0
360
local
function
flush
(
)
-- we can move this
361
local
font
=
getfont
(
start
)
362
local
last
=
getnext
(
stop
)
363
local
list
=
last
and
copy_node_list
(
start
,
last
)
or
copy_node_list
(
start
)
364
local
n
=
#
cache
+
1
365
if
encapsulate
then
366
local
user_one
=
new_usernode
(
splitter_one
,
n
)
367
local
user_two
=
new_usernode
(
splitter_two
,
n
)
368
head
,
start
=
insert_node_before
(
head
,
start
,
user_one
)
369
insert_node_after
(
head
,
stop
,
user_two
)
370
else
371
local
current
=
start
372
while
true
do
373
setattr
(
current
,
a_word
,
n
)
374
if
current
=
=
stop
then
375
break
376
else
377
current
=
getnext
(
current
)
378
end
379
end
380
end
381
if
r2l
then
382
local
dirnode
=
new_direction
(
righttoleft
)
-- brrr, we don't pop ... to be done (when used at all)
383
setlink
(
dirnode
,
list
)
384
list
=
dirnode
385
end
386
local
c
=
{
387
original
=
list
,
388
attribute
=
attribute
,
389
-- direction = rlmode,
390
font
=
font
391
}
392
if
trace_split
then
393
report_splitters
(
"
cached %4i: font %a, attribute %a, direction %a, word %a
"
,
394
n
,
font
,
attribute
,
nodes_to_utf
(
list
,
true
)
,
r2l
and
"
r2l
"
or
"
l2r
"
)
395
end
396
cache
[
n
]
=
c
397
local
solution
=
solutions
[
attribute
]
398
local
l
=
#
solution
.
less
399
local
m
=
#
solution
.
more
400
if
l
>
max_less
then
max_less
=
l
end
401
if
m
>
max_more
then
max_more
=
m
end
402
start
,
stop
=
nil
,
nil
403
end
404
while
current
do
-- also ischar
405
local
next
=
getnext
(
current
)
406
local
id
=
getid
(
current
)
407
if
id
=
=
glyph_code
then
408
if
getsubtype
(
current
)
<
256
then
409
local
a
=
getattr
(
current
,
a_split
)
410
if
not
a
then
411
start
,
stop
=
nil
,
nil
412
elseif
not
start
then
413
start
,
stop
,
attribute
=
current
,
current
,
a
414
elseif
a
~
=
attribute
then
415
start
,
stop
=
nil
,
nil
416
else
417
stop
=
current
418
end
419
end
420
elseif
id
=
=
disc_code
then
421
if
splitwords
then
422
if
start
then
423
flush
(
)
424
end
425
elseif
start
and
next
and
getid
(
next
)
=
=
glyph_code
and
getsubtype
(
next
)
<
256
then
426
-- beware: we can cross future lines
427
stop
=
next
428
else
429
start
,
stop
=
nil
,
nil
430
end
431
elseif
id
=
=
dir_code
then
432
-- not tested (to be done by idris when font is ready)
433
if
start
then
434
flush
(
)
435
end
436
local
direction
,
pop
=
getdirection
(
current
)
437
r2l
=
not
pop
and
direction
=
=
righttoleft
438
elseif
id
=
=
localpar_code
and
start_of_par
(
current
)
then
439
if
start
then
440
flush
(
)
-- very unlikely as this starts a paragraph
441
end
442
local
direction
=
getdirection
(
current
)
443
r2l
=
direction
=
=
righttoleft
or
direction
=
=
"
TRT
"
-- for old times sake
444
else
445
if
start
then
446
flush
(
)
447
end
448
end
449
current
=
next
450
end
451
if
start
then
452
flush
(
)
453
end
454
nofparagraphs
=
nofparagraphs
+
1
455
nofwords
=
nofwords
+
#
cache
456
return
head
457
end
458 459
local
function
collect_words
(
list
)
-- can be made faster for attributes
460
local
words
=
{
}
461
local
w
=
0
462
local
word
=
nil
463
if
encapsulate
then
464
for
current
,
subtype
in
nextwhatsit
,
list
do
465
if
subtype
=
=
userdefinedwhatsit_code
then
-- hm
466
local
p
=
nodeproperties
[
current
]
467
if
p
then
468
local
user_id
=
p
.
id
469
if
user_id
=
=
splitter_one
then
470
word
=
{
p
.
data
,
current
,
current
}
471
w
=
w
+
1
472
words
[
w
]
=
word
473
elseif
user_id
=
=
splitter_two
then
474
if
word
then
475
word
[
3
]
=
current
476
else
477
-- something is wrong
478
end
479
end
480
end
481
end
482
end
483
else
484
local
first
,
last
,
index
485
local
current
=
list
486
while
current
do
487
-- todo: disc and kern
488
local
id
=
getid
(
current
)
489
if
id
=
=
glyph_code
or
id
=
=
disc_code
then
490
local
a
=
getattr
(
current
,
a_word
)
491
if
a
then
492
if
a
=
=
index
then
493
-- same word
494
last
=
current
495
elseif
index
then
496
w
=
w
+
1
497
words
[
w
]
=
{
index
,
first
,
last
}
498
first
=
current
499
last
=
current
500
index
=
a
501
elseif
first
then
502
last
=
current
503
index
=
a
504
else
505
first
=
current
506
last
=
current
507
index
=
a
508
end
509
elseif
index
then
510
if
first
then
511
w
=
w
+
1
512
words
[
w
]
=
{
index
,
first
,
last
}
513
end
514
index
=
nil
515
first
=
nil
516
elseif
trace_split
then
517
if
id
=
=
disc_code
then
518
report_splitters
(
"
skipped: disc node
"
)
519
else
520
report_splitters
(
"
skipped: %C
"
,
current
.
char
)
521
end
522
end
523
elseif
id
=
=
kern_code
and
getsubtype
(
current
)
=
=
fontkern_code
then
524
if
first
then
525
last
=
current
526
else
527
first
=
current
528
last
=
current
529
end
530
elseif
index
then
531
w
=
w
+
1
532
words
[
w
]
=
{
index
,
first
,
last
}
533
index
=
nil
534
first
=
nil
535
if
id
=
=
disc_code
then
536
if
trace_split
then
537
report_splitters
(
"
skipped: disc node
"
)
538
end
539
end
540
end
541
current
=
getnext
(
current
)
542
end
543
if
index
then
544
w
=
w
+
1
545
words
[
w
]
=
{
index
,
first
,
last
}
546
end
547
if
trace_split
then
548
for
i
=
1
,
#
words
do
549
local
w
=
words
[
i
]
550
local
n
=
w
[
1
]
551
local
f
=
w
[
2
]
552
local
l
=
w
[
3
]
553
local
c
=
cache
[
n
]
554
if
c
then
555
report_splitters
(
"
found %4i: word %a, cached %a
"
,
n
,
nodes_to_utf
(
f
,
true
,
true
,
l
)
,
nodes_to_utf
(
c
.
original
,
true
)
)
556
else
557
report_splitters
(
"
found %4i: word %a, not in cache
"
,
n
,
nodes_to_utf
(
f
,
true
,
true
,
l
)
)
558
end
559
end
560
end
561
end
562
return
words
,
list
-- check for empty (elsewhere)
563
end
564 565
-- we could avoid a hpack but hpack is not that slow
566 567
local
function
doit
(
word
,
list
,
best
,
width
,
badness
,
line
,
set
,
listdir
)
568
local
changed
=
0
569
local
n
=
word
[
1
]
570
local
found
=
cache
[
n
]
571
if
found
then
572
local
h
,
t
573
if
encapsulate
then
574
h
=
getnext
(
word
[
2
]
)
-- head of current word
575
t
=
getprev
(
word
[
3
]
)
-- tail of current word
576
else
577
h
=
word
[
2
]
578
t
=
word
[
3
]
579
end
580
if
splitwords
then
581
-- there are no lines crossed in a word
582
else
583
local
ok
=
false
584
local
c
=
h
585
while
c
do
586
if
c
=
=
t
then
587
ok
=
true
588
break
589
else
590
c
=
getnext
(
c
)
591
end
592
end
593
if
not
ok
then
594
report_solutions
(
"
skipping hyphenated word (for now)
"
)
595
-- todo: mark in words as skipped, saves a bit runtime
596
return
false
,
changed
597
end
598
end
599
local
original
=
found
.
original
600
local
attribute
=
found
.
attribute
601
local
solution
=
solutions
[
attribute
]
602
local
features
=
solution
and
solution
[
set
]
603
if
features
then
604
local
featurenumber
=
features
[
best
]
-- not ok probably
605
if
featurenumber
then
606
noftries
=
noftries
+
1
607
local
first
=
copy_node_list
(
original
)
608
if
not
trace_colors
then
609
for
n
in
nextnode
,
first
do
-- maybe fast force so no attr needed
610
setattr
(
n
,
0
,
featurenumber
)
-- this forces dynamics
611
end
612
elseif
set
=
=
"
less
"
then
613
for
n
in
nextnode
,
first
do
614
setnodecolor
(
n
,
"
font:isol
"
)
-- yellow
615
setattr
(
n
,
0
,
featurenumber
)
616
end
617
else
618
for
n
in
nextnode
,
first
do
619
setnodecolor
(
n
,
"
font:medi
"
)
-- green
620
setattr
(
n
,
0
,
featurenumber
)
621
end
622
end
623
local
font
=
found
.
font
624
local
setdynamics
=
setfontdynamics
[
font
]
625
if
setdynamics
then
626
local
processes
=
setdynamics
[
featurenumber
]
627
for
i
=
1
,
#
processes
do
-- often more than 1
628
first
=
processes
[
i
]
(
first
,
font
,
featurenumber
)
629
end
630
else
631
report_solutions
(
"
fatal error, no dynamics for font %a
"
,
font
)
632
end
633
first
=
inject_kerns
(
first
)
634
if
getid
(
first
)
=
=
whatsit_code
then
635
local
temp
=
first
636
first
=
getnext
(
first
)
637
flush_node
(
temp
)
638
end
639
local
last
=
find_node_tail
(
first
)
640
-- replace [u]h->t by [u]first->last
641
local
prev
=
getprev
(
h
)
642
local
next
=
getnext
(
t
)
643
setlink
(
prev
,
first
)
644
if
next
then
645
setlink
(
last
,
next
)
646
end
647
-- check new pack
648
local
temp
,
b
=
repack_hlist
(
list
,
width
,
'
exactly
'
,
listdir
)
649
if
b
>
badness
then
650
if
trace_optimize
then
651
report_optimizers
(
"
line %a, set %a, badness before %a, after %a, criterium %a, verdict %a
"
,
line
,
set
or
"
?
"
,
badness
,
b
,
criterium
,
"
quit
"
)
652
end
653
-- remove last insert
654
setlink
(
prev
,
h
)
655
if
next
then
656
setlink
(
t
,
next
)
657
else
658
setnext
(
t
)
659
end
660
setnext
(
last
)
661
flush_node_list
(
first
)
662
else
663
if
trace_optimize
then
664
report_optimizers
(
"
line %a, set %a, badness before: %a, after %a, criterium %a, verdict %a
"
,
line
,
set
or
"
?
"
,
badness
,
b
,
criterium
,
"
continue
"
)
665
end
666
-- free old h->t
667
setnext
(
t
)
668
flush_node_list
(
h
)
-- somehow fails
669
if
not
encapsulate
then
670
word
[
2
]
=
first
671
word
[
3
]
=
last
672
end
673
changed
,
badness
=
changed
+
1
,
b
674
end
675
if
b
<
=
criterium
then
676
return
true
,
changed
677
end
678
end
679
end
680
end
681
return
false
,
changed
682
end
683 684
-- We repeat some code but adding yet another layer of indirectness is not
685
-- making things better.
686 687
variants
[
v_normal
]
=
function
(
words
,
list
,
best
,
width
,
badness
,
line
,
set
,
listdir
)
688
local
changed
=
0
689
for
i
=
1
,
#
words
do
690
local
done
,
c
=
doit
(
words
[
i
]
,
list
,
best
,
width
,
badness
,
line
,
set
,
listdir
)
691
changed
=
changed
+
c
692
if
done
then
693
break
694
end
695
end
696
if
changed
>
0
then
697
nofadapted
=
nofadapted
+
1
698
-- todo: get rid of pack when ok because we already have packed and we only need the last b
699
local
list
,
b
=
repack_hlist
(
list
,
width
,
'
exactly
'
,
listdir
)
700
return
list
,
true
,
changed
,
b
-- badness
701
else
702
nofkept
=
nofkept
+
1
703
return
list
,
false
,
0
,
badness
704
end
705
end
706 707
variants
[
v_reverse
]
=
function
(
words
,
list
,
best
,
width
,
badness
,
line
,
set
,
listdir
)
708
local
changed
=
0
709
for
i
=
#
words
,
1
,
-1
do
710
local
done
,
c
=
doit
(
words
[
i
]
,
list
,
best
,
width
,
badness
,
line
,
set
,
listdir
)
711
changed
=
changed
+
c
712
if
done
then
713
break
714
end
715
end
716
if
changed
>
0
then
717
nofadapted
=
nofadapted
+
1
718
-- todo: get rid of pack when ok because we already have packed and we only need the last b
719
local
list
,
b
=
repack_hlist
(
list
,
width
,
'
exactly
'
,
listdir
)
720
return
list
,
true
,
changed
,
b
-- badness
721
else
722
nofkept
=
nofkept
+
1
723
return
list
,
false
,
0
,
badness
724
end
725
end
726 727
variants
[
v_random
]
=
function
(
words
,
list
,
best
,
width
,
badness
,
line
,
set
,
listdir
)
728
local
changed
=
0
729
while
#
words
>
0
do
730
local
done
,
c
=
doit
(
remove
(
words
,
getrandom
(
"
solution
"
,
1
,
#
words
)
)
,
list
,
best
,
width
,
badness
,
line
,
set
,
listdir
)
731
changed
=
changed
+
c
732
if
done
then
733
break
734
end
735
end
736
if
changed
>
0
then
737
nofadapted
=
nofadapted
+
1
738
-- todo: get rid of pack when ok because we already have packed and we only need the last b
739
local
list
,
b
=
repack_hlist
(
list
,
width
,
'
exactly
'
,
listdir
)
740
return
list
,
true
,
changed
,
b
-- badness
741
else
742
nofkept
=
nofkept
+
1
743
return
list
,
false
,
0
,
badness
744
end
745
end
746 747
local
function
show_quality
(
current
,
what
,
line
)
748
local
set
,
order
,
sign
=
getboxglue
(
current
)
749
local
amount
=
set
*
(
(
sign
=
=
2
and
-1
)
or
1
)
750
report_optimizers
(
"
line %a, category %a, amount %a, set %a, sign %a, how %a, order %a
"
,
line
,
what
,
amount
,
set
,
sign
,
how
,
order
)
751
end
752 753
function
splitters
.
optimize
(
head
)
754
if
not
optimize
then
755
report_optimizers
(
"
no optimizer set
"
)
756
return
757
end
758
local
nc
=
#
cache
759
if
nc
=
=
0
then
760
return
761
end
762
starttiming
(
splitters
)
763
local
listdir
=
nil
-- todo ! ! !
764
if
randomseed
then
765
math
.
setrandomseedi
(
randomseed
)
766
randomseed
=
nil
767
end
768
local
line
=
0
769
local
tex_hbadness
=
tex
.
hbadness
770
local
tex_hfuzz
=
tex
.
hfuzz
771
tex
.
hbadness
=
10000
772
tex
.
hfuzz
=
number
.
maxdimen
773
if
trace_optimize
then
774
report_optimizers
(
"
preroll %a, variant %a, criterium %a, cache size %a
"
,
preroll
,
variant
,
criterium
,
nc
)
775
end
776
for
current
in
nexthlist
,
head
do
777
line
=
line
+
1
778
local
sign
=
getfield
(
current
,
"
glue_sign
"
)
779
local
direction
=
getdirection
(
current
)
780
local
width
=
getwidth
(
current
)
781
local
list
=
getlist
(
current
)
782
if
not
encapsulate
and
getid
(
list
)
=
=
glyph_code
then
783
-- nasty .. we always assume a prev being there .. future luatex will always have a leftskip set
784
-- is this assignment ok ? .. needs checking
785
list
=
insert_node_before
(
list
,
list
,
new_leftskip
(
0
)
)
-- new_glue(0)
786
setlist
(
current
,
list
)
787
end
788
local
temp
,
badness
=
repack_hlist
(
list
,
width
,
"
exactly
"
,
direction
)
-- it would be nice if the badness was stored in the node
789
if
badness
>
0
then
790
if
sign
=
=
0
then
791
if
trace_optimize
then
792
report_optimizers
(
"
line %a, badness %a, outcome %a, verdict %a
"
,
line
,
badness
,
"
okay
"
,
"
okay
"
)
793
end
794
else
795
local
set
,
max
796
if
sign
=
=
1
then
797
if
trace_optimize
then
798
report_optimizers
(
"
line %a, badness %a, outcome %a, verdict %a
"
,
line
,
badness
,
"
underfull
"
,
"
trying more
"
)
799
end
800
set
,
max
=
"
more
"
,
max_more
801
else
802
if
trace_optimize
then
803
report_optimizers
(
"
line %a, badness %a, outcome %a, verdict %a
"
,
line
,
badness
,
"
overfull
"
,
"
trying less
"
)
804
end
805
set
,
max
=
"
less
"
,
max_less
806
end
807
-- we can keep the best variants
808
local
lastbest
=
nil
809
local
lastbadness
=
badness
810
if
preroll
then
811
local
bb
,
base
812
for
i
=
1
,
max
do
813
if
base
then
814
flush_node_list
(
base
)
815
end
816
base
=
copy_node_list
(
list
)
817
local
words
=
collect_words
(
base
)
-- beware: words is adapted
818
for
j
=
i
,
max
do
819
local
temp
,
done
,
changes
,
b
=
optimize
(
words
,
base
,
j
,
width
,
badness
,
line
,
set
,
dir
)
820
base
=
temp
821
if
trace_optimize
then
822
report_optimizers
(
"
line %a, alternative %a.%a, changes %a, badness %a
"
,
line
,
i
,
j
,
changes
,
b
)
823
end
824
bb
=
b
825
if
b
<
=
criterium
then
826
break
827
end
828
-- if done then
829
-- break
830
-- end
831
end
832
if
bb
and
bb
>
criterium
then
-- needs checking
833
if
not
lastbest
then
834
lastbest
,
lastbadness
=
i
,
bb
835
elseif
bb
>
lastbadness
then
836
lastbest
,
lastbadness
=
i
,
bb
837
end
838
else
839
break
840
end
841
end
842
flush_node_list
(
base
)
843
end
844
local
words
=
collect_words
(
list
)
845
for
best
=
lastbest
or
1
,
max
do
846
local
temp
,
done
,
changes
,
b
=
optimize
(
words
,
list
,
best
,
width
,
badness
,
line
,
set
,
dir
)
847
setlist
(
current
,
temp
)
848
if
trace_optimize
then
849
report_optimizers
(
"
line %a, alternative %a, changes %a, badness %a
"
,
line
,
best
,
changes
,
b
)
850
end
851
if
done
then
852
if
b
<
=
criterium
then
-- was == 0
853
protect_glyphs
(
list
)
854
break
855
end
856
end
857
end
858
end
859
else
860
if
trace_optimize
then
861
report_optimizers
(
"
line %a, verdict %a
"
,
line
,
"
not bad enough
"
)
862
end
863
end
864
-- we pack inside the outer hpack and that way keep the original wd/ht/dp as bonus
865
local
list
=
hpack_nodes
(
getlist
(
current
)
,
width
,
'
exactly
'
,
listdir
)
866
setlist
(
current
,
list
)
867
end
868
for
i
=
1
,
nc
do
869
local
ci
=
cache
[
i
]
870
flush_node_list
(
ci
.
original
)
871
end
872
cache
=
{
}
873
tex
.
hbadness
=
tex_hbadness
874
tex
.
hfuzz
=
tex_hfuzz
875
stoptiming
(
splitters
)
876
end
877 878
statistics
.
register
(
"
optimizer statistics
"
,
function
(
)
879
if
nofwords
>
0
then
880
local
elapsed
=
statistics
.
elapsedtime
(
splitters
)
881
local
average
=
noftries
/
elapsed
882
return
format
(
"
%s words identified in %s paragraphs, %s words retried, %s lines tried, %s seconds used, %s adapted, %0.1f lines per second
"
,
883
nofwords
,
nofparagraphs
,
noftries
,
nofadapted
+
nofkept
,
elapsed
,
nofadapted
,
average
)
884
end
885
end
)
886 887
-- we could use a stack
888 889
local
enableaction
=
tasks
.
enableaction
890
local
disableaction
=
tasks
.
disableaction
891 892
local
function
enable
(
)
893
enableaction
(
"
processors
"
,
"
builders.paragraphs.solutions.splitters.split
"
)
894
enableaction
(
"
finalizers
"
,
"
builders.paragraphs.solutions.splitters.optimize
"
)
895
end
896 897
local
function
disable
(
)
898
disableaction
(
"
processors
"
,
"
builders.paragraphs.solutions.splitters.split
"
)
899
disableaction
(
"
finalizers
"
,
"
builders.paragraphs.solutions.splitters.optimize
"
)
900
end
901 902
function
splitters
.
start
(
name
,
settings
)
903
if
pushsplitter
(
name
,
settings
)
=
=
1
then
904
enable
(
)
905
end
906
end
907 908
function
splitters
.
stop
(
)
909
if
popsplitter
(
)
=
=
0
then
910
disable
(
)
911
end
912
end
913 914
function
splitters
.
set
(
name
,
settings
)
915
if
#
stack
>
0
then
916
stack
=
{
}
917
else
918
enable
(
)
919
end
920
pushsplitter
(
name
,
settings
)
-- sets attribute etc
921
end
922 923
function
splitters
.
reset
(
)
924
if
#
stack
>
0
then
925
stack
=
{
}
926
popsplitter
(
)
-- resets attribute etc
927
disable
(
)
928
end
929
end
930 931
-- interface
932 933
implement
{
934
name
=
"
definefontsolution
"
,
935
actions
=
splitters
.
define
,
936
arguments
=
{
937
"
string
"
,
938
{
939
{
"
goodies
"
}
,
940
{
"
solution
"
}
,
941
{
"
less
"
}
,
942
{
"
more
"
}
,
943
}
944
}
945
}
946 947
implement
{
948
name
=
"
startfontsolution
"
,
949
actions
=
splitters
.
start
,
950
arguments
=
{
951
"
string
"
,
952
{
953
{
"
method
"
}
,
954
{
"
criterium
"
}
,
955
{
"
randomseed
"
}
,
956
}
957
}
958
}
959 960
implement
{
961
name
=
"
stopfontsolution
"
,
962
actions
=
splitters
.
stop
963
}
964 965
implement
{
966
name
=
"
setfontsolution
"
,
967
actions
=
splitters
.
set
,
968
arguments
=
{
969
"
string
"
,
970
{
971
{
"
method
"
}
,
972
{
"
criterium
"
}
,
973
{
"
randomseed
"
}
,
974
}
975
}
976
}
977 978
implement
{
979
name
=
"
resetfontsolution
"
,
980
actions
=
splitters
.
reset
981
}
982