1 if not modules then modules = { } end modules ['typo-krn'] = {
2 version = 1.001,
3 comment = "companion to typo-krn.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
10
11
12
13
14local next, type, tonumber = next, type, tonumber
15
16local nodes = nodes
17local fonts = fonts
18
19local enableaction = nodes.tasks.enableaction
20
21local nuts = nodes.nuts
22local nodepool = nuts.pool
23
24
25
26local find_node_tail = nuts.tail
27local insertnodebefore = nuts.insertbefore
28local insertnodeafter = nuts.insertafter
29local endofmath = nuts.endofmath
30local copy_node = nuts.copy
31
32local getnext = nuts.getnext
33local getprev = nuts.getprev
34local getid = nuts.getid
35local getfont = nuts.getfont
36local getsubtype = nuts.getsubtype
37local getchar = nuts.getchar
38local getdisc = nuts.getdisc
39local getglue = nuts.getglue
40local getkern = nuts.getkern
41local getglyphdata = nuts.getglyphdata
42
43local isglyph = nuts.isglyph
44
45local setfield = nuts.setfield
46local getattr = nuts.getattr
47local takeattr = nuts.takeattr
48local setattr = nuts.setattr
49local setlink = nuts.setlink
50local setdisc = nuts.setdisc
51local setglue = nuts.setglue
52local setkern = nuts.setkern
53local setchar = nuts.setchar
54local setglue = nuts.setglue
55
56local texsetattribute = tex.setattribute
57local unsetvalue = attributes.unsetvalue
58
59local new_kern = nodepool.kern
60local new_glue = nodepool.glue
61
62local nodecodes = nodes.nodecodes
63local kerncodes = nodes.kerncodes
64local gluecodes = nodes.gluecodes
65local disccodes = nodes.disccodes
66local listcodes = nodes.listcodes
67
68local glyph_code = nodecodes.glyph
69local kern_code = nodecodes.kern
70local disc_code = nodecodes.disc
71local glue_code = nodecodes.glue
72local hlist_code = nodecodes.hlist
73local vlist_code = nodecodes.vlist
74local math_code = nodecodes.math
75
76local boxlist_code = listcodes.box
77local unknownlist_code = listcodes.unknown
78
79local discretionarydisc_code = disccodes.discretionary
80local automaticdisc_code = disccodes.automatic
81
82local fontkern_code = kerncodes.fontkern
83local userkern_code = kerncodes.userkern
84
85local userskip_code = gluecodes.userskip
86local spaceskip_code = gluecodes.spaceskip
87local xspaceskip_code = gluecodes.xspaceskip
88
89local fonthashes = fonts.hashes
90local chardata = fonthashes.characters
91local quaddata = fonthashes.quads
92local markdata = fonthashes.marks
93local fontproperties = fonthashes.properties
94local fontdescriptions = fonthashes.descriptions
95local fontfeatures = fonthashes.features
96
97local tracers = nodes.tracers
98local setcolor = tracers.colors.set
99local resetcolor = tracers.colors.reset
100
101local v_max = interfaces.variables.max
102local v_auto = interfaces.variables.auto
103
104typesetters = typesetters or { }
105local typesetters = typesetters
106
107local kerns = typesetters.kerns or { }
108typesetters.kerns = kerns
109
110local report = logs.reporter("kerns")
111local trace_ligatures = false trackers.register("typesetters.kerns.ligatures", function(v) trace_ligatures = v end)
112local trace_ligatures_d = false trackers.register("typesetters.kerns.ligatures.details", function(v) trace_ligatures_d = v end)
113
114kerns.mapping = kerns.mapping or { }
115kerns.factors = kerns.factors or { }
116local a_kerns = attributes.private("kern")
117
118local contextsetups = fonts.specifiers.contextsetups
119
120storage.register("typesetters/kerns/mapping", kerns.mapping, "typesetters.kerns.mapping")
121storage.register("typesetters/kerns/factors", kerns.factors, "typesetters.kerns.factors")
122
123local mapping = kerns.mapping
124local factors = kerns.factors
125
126
127
128
129
130
131
132
133local gluefactor = 4
134
135
136
137
138
139function kerns.keepligature(n)
140 local f = getfont(n)
141 local a = getglyphdata(n) or 0
142 if trace_ligatures then
143 local c = getchar(n)
144 local d = fontdescriptions[f][c].name
145 if a > 0 and contextsetups[a].keepligatures == v_auto then
146 if trace_ligatures_d then
147 report("font %!font:name!, glyph %a, slot %X -> ligature %s, by %s feature %a",f,d,c,"kept","dynamic","keepligatures")
148 end
149 setcolor(n,"darkred")
150 return true
151 end
152 local k = fontfeatures[f].keepligatures
153 if k == v_auto then
154 if trace_ligatures_d then
155 report("font %!font:name!, glyph %a, slot %X -> ligature %s, by %s feature %a",f,d,c,"kept","static","keepligatures")
156 end
157 setcolor(n,"darkgreen")
158 return true
159 end
160 if not k then
161 if trace_ligatures_d then
162 report("font %!font:name!, glyph %a, slot %X -> ligature %s, by %s feature %a",f,d,c,"split","static","keepligatures")
163 end
164 resetcolor(n)
165 return false
166 end
167 local k = fontproperties[f].keptligatures
168 if not k then
169 report("font %!font:name!, glyph %a, slot %X -> ligature %s, %s goodie specification",f,d,c,"split","no")
170 resetcolor(n)
171 return false
172 end
173 if k and k[c] then
174 report("font %!font:name!, glyph %a, slot %X -> ligature %s, %s goodie specification",f,d,c,"kept","by")
175 setcolor(n,"darkblue")
176 return true
177 else
178 report("font %!font:name!, glyph %a, slot %X -> ligature %s, %s goodie specification",f,d,c,"split","by")
179 resetcolor(n)
180 return false
181 end
182 else
183 if a > 0 and contextsetups[a].keepligatures == v_auto then
184 return true
185 end
186 local k = fontfeatures[f].keepligatures
187 if k == v_auto then
188 return true
189 end
190 if not k then
191 return false
192 end
193 local k = fontproperties[f].keptligatures
194 if not k then
195 return false
196 end
197 if k and k[c] then
198 return true
199 end
200 end
201end
202
203
204
205local function kern_injector(fillup,kern)
206 if fillup then
207 local g = new_glue(kern)
208 setfield(g,"stretch",kern)
209 setfield(g,"stretch_order",1)
210 return g
211 else
212 return new_kern(kern)
213 end
214end
215
216
217
218
219
220local function inject_begin(boundary,prev,keeptogether,krn,ok)
221 local char, id = isglyph(boundary)
222 if id == kern_code then
223 if getsubtype(boundary) == fontkern_code then
224 local inject = true
225 if keeptogether then
226 local next = getnext(boundary)
227 if not next or (getid(next) == glyph_code and keeptogether(prev,next)) then
228 inject = false
229 end
230 end
231 if inject then
232
233 setkern(boundary,getkern(boundary) + quaddata[getfont(prev)]*krn,userkern_code)
234 return boundary, true
235 end
236 end
237 elseif char then
238 if keeptogether and keeptogether(boundary,prev) then
239
240 else
241 local prevchar = isglyph(prev)
242 if prevchar and prevchar > 0 then
243 local font = getfont(boundary)
244 local data = chardata[font][prevchar]
245 local kerns = data and data.kerns
246 local kern = new_kern((kerns and kerns[char] or 0) + quaddata[font]*krn)
247 setlink(kern,boundary)
248 return kern, true
249 end
250 end
251 end
252 return boundary, ok
253end
254
255local function inject_end(boundary,next,keeptogether,krn,ok)
256 local tail = find_node_tail(boundary)
257 local char, id = isglyph(tail)
258 if id == kern_code then
259 if getsubtype(tail) == fontkern_code then
260 local inject = true
261 if keeptogether then
262 local prev = getprev(tail)
263 if getid(prev) == glyph_code and keeptogether(prev,two) then
264 inject = false
265 end
266 end
267 if inject then
268
269 setkern(tail,getkern(tail) + quaddata[getfont(next)]*krn,userkern_code)
270 return boundary, true
271 end
272 end
273 elseif char then
274 if keeptogether and keeptogether(tail,two) then
275
276 else
277 local nextchar = isglyph(tail)
278 if nextchar and nextchar > 0 then
279 local font = getfont(tail)
280 local data = chardata[font][nextchar]
281 local kerns = data and data.kerns
282 local kern = (kerns and kerns[char] or 0) + quaddata[font]*krn
283 setlink(tail,new_kern(kern))
284 return boundary, true
285 end
286 end
287 end
288 return boundary, ok
289end
290
291local function process_list(head,keeptogether,krn,font,okay)
292 local start = head
293 local prev = nil
294 local pid = nil
295 local kern = 0
296 local mark = font and markdata[font]
297 while start do
298 local char, id = isglyph(start)
299 if char then
300 if not font then
301 font = id
302 mark = markdata[font]
303 kern = quaddata[font]*krn
304 end
305 if prev then
306 if mark[char] then
307
308 elseif pid == kern_code then
309 if getsubtype(prev) == fontkern_code then
310 local inject = true
311 if keeptogether then
312 local prevprev = getprev(prev)
313 if getid(prevprev) == glyph_code and keeptogether(prevprev,start) then
314 inject = false
315 end
316 end
317 if inject then
318
319 setkern(prev,getkern(prev) + kern,userkern_code)
320 okay = true
321 end
322 end
323 elseif pid == glyph_code then
324 if keeptogether and keeptogether(prev,start) then
325
326 else
327 local prevchar = getchar(prev)
328 local data = chardata[font][prevchar]
329 local kerns = data and data.kerns
330
331
332
333 insertnodebefore(head,start,new_kern((kerns and kerns[char] or 0) + kern))
334 okay = true
335 end
336 end
337 end
338 end
339 if start then
340 prev = start
341 pid = id
342 start = getnext(start)
343 end
344 end
345 return head, okay, prev
346end
347
348local function closest_bound(b,get)
349 b = get(b)
350 if b and getid(b) ~= glue_code then
351 while b do
352 if not getattr(b,a_kerns) then
353 break
354 else
355 local c, f = isglyph(b)
356 if c then
357 return b, f
358 else
359 b = get(b)
360 end
361 end
362 end
363 end
364end
365
366function kerns.handler(head)
367 local start = head
368 local lastfont = nil
369 local keepligature = kerns.keepligature
370 local keeptogether = kerns.keeptogether
371 local fillup = false
372 local bound = false
373 local prev = nil
374 local previd = nil
375 local prevchar = nil
376 local prevfont = nil
377 local prevmark = nil
378 while start do
379
380
381 local attr = takeattr(start,a_kerns)
382 if attr and attr > 0 then
383 local char, id = isglyph(start)
384 local krn = mapping[attr]
385 if krn == v_max then
386 krn = .25
387 fillup = true
388 else
389 fillup = false
390 end
391 if not krn or krn == 0 then
392 bound = false
393 elseif char then
394 local font = id
395 local mark = markdata[font]
396 if keepligature and keepligature(start) then
397
398 else
399
400
401 local data = chardata[font][char]
402 if data then
403 local unicode = data.unicode
404 if type(unicode) == "table" then
405 char = unicode[1]
406 local s = start
407 setchar(s,char)
408 for i=2,#unicode do
409 local n = copy_node(s)
410 if i == 2 then
411 setattr(n,a_kerns,attr)
412 end
413 setchar(n,unicode[i])
414 insertnodeafter(head,s,n)
415 s = n
416 end
417 end
418 end
419 end
420 if not bound then
421
422 elseif mark[char] then
423
424 elseif previd == kern_code then
425 if getsubtype(prev) == fontkern_code then
426 local inject = true
427 if keeptogether then
428 if previd == glyph_code and keeptogether(prev,start) then
429 inject = false
430 end
431 end
432 if inject then
433
434 setkern(prev,getkern(prev) + quaddata[font]*krn,userkern_code)
435 end
436 end
437 elseif previd == glyph_code then
438 if prevfont == font then
439 if keeptogether and keeptogether(prev,start) then
440
441 else
442
443 local data = chardata[font][prevchar]
444 local kerns = data and data.kerns
445 local kern = (kerns and kerns[char] or 0) + quaddata[font]*krn
446 insertnodebefore(head,start,kern_injector(fillup,kern))
447 end
448 else
449 insertnodebefore(head,start,kern_injector(fillup,quaddata[font]*krn))
450 end
451 end
452 prev = start
453 prevchar = char
454 prevfont = font
455 prevmark = mark
456 previd = glyph_code
457 bound = true
458 elseif id == disc_code then
459 local prev, next, pglyph, nglyph
460 local subtype = getsubtype(start)
461
462
463
464
465
466
467
468
469
470
471
472
473 local pre, post, replace = getdisc(start)
474 local indeed = false
475 if pre then
476 local okay = false
477 if not prev then
478 prev = getprev(start)
479 pglyph = prev and getid(prev) == glyph_code
480 end
481 if pglyph then
482 pre, okay = inject_begin(pre,prev,keeptogether,krn,okay)
483 end
484 pre, okay = process_list(pre,keeptogether,krn,false,okay)
485 if okay then
486 indeed = true
487 end
488 end
489 if post then
490 local okay = false
491 if not next then
492 next = getnext(start)
493 nglyph = next and getid(next) == glyph_code
494 end
495 if nglyph then
496 post, okay = inject_end(post,next,keeptogether,krn,okay)
497 end
498 post, okay = process_list(post,keeptogether,krn,false,okay)
499 if okay then
500 indeed = true
501 end
502 end
503 if replace then
504 local okay = false
505 if not prev then
506 prev = getprev(start)
507 pglyph = prev and getid(prev) == glyph_code
508 end
509 if pglyph then
510 replace, okay = inject_begin(replace,prev,keeptogether,krn,okay)
511 end
512 if not next then
513 next = getnext(start)
514 nglyph = next and getid(next) == glyph_code
515 end
516 if nglyph then
517 replace, okay = inject_end(replace,next,keeptogether,krn,okay)
518 end
519 replace, okay = process_list(replace,keeptogether,krn,false,okay)
520 if okay then
521 indeed = true
522 end
523 elseif prevfont then
524 replace = new_kern(quaddata[prevfont]*krn)
525 indeed = true
526 end
527 if indeed then
528 setdisc(start,pre,post,replace)
529 end
530 bound = false
531 elseif id == kern_code then
532 bound = getsubtype(start) == fontkern_code
533 prev = start
534 previd = id
535 elseif id == glue_code then
536 local subtype = getsubtype(start)
537 if subtype == userskip_code or subtype == xspaceskip_code or subtype == spaceskip_code then
538 local width, stretch, shrink, stretch_order, shrink_order = getglue(start)
539 if width > 0 then
540 local w = width + gluefactor * width * krn
541 stretch = stretch * w / width
542 shrink = shrink * w / width
543 if fillup then
544 stretch = 2 * stretch
545 shrink = 2 * shrink
546 stretch_order = 1
547
548 end
549 setglue(start,w,stretch,shrink,stretch_order,shrink_order)
550 end
551 end
552 bound = false
553 elseif id == hlist_code or id == vlist_code then
554 local subtype = getsubtype(start)
555 if subtype == unknownlist_code or subtype == boxlist_code then
556
557 local b, f = closest_bound(start,getprev)
558 if b then
559 insertnodebefore(head,start,kern_injector(fillup,quaddata[f]*krn))
560 end
561 local b, f = closest_bound(start,getnext)
562 if b then
563 insertnodeafter(head,start,kern_injector(fillup,quaddata[f]*krn))
564 end
565 end
566 bound = false
567 elseif id == math_code then
568 start = endofmath(start)
569 bound = false
570 end
571 if start then
572 start = getnext(start)
573 end
574 else
575 local id = getid(start)
576 if id == kern_code then
577 bound = getsubtype(start) == fontkern_code
578 prev = start
579 previd = id
580 start = getnext(start)
581 else
582 bound = false
583 start = getnext(start)
584 end
585 end
586 end
587 return head
588end
589
590local enabled = false
591
592function kerns.set(factor)
593 if factor ~= v_max then
594 factor = tonumber(factor) or 0
595 end
596 if factor == v_max or factor ~= 0 then
597 if not enabled then
598 enableaction("processors","typesetters.kerns.handler")
599 enabled = true
600 end
601 local a = factors[factor]
602 if not a then
603 a = #mapping + 1
604 factors[factors], mapping[a] = a, factor
605 end
606 factor = a
607 else
608 factor = unsetvalue
609 end
610 texsetattribute(a_kerns,factor)
611 return factor
612end
613
614
615
616interfaces.implement {
617 name = "setcharacterkerning",
618 actions = kerns.set,
619 arguments = "string"
620}
621
622 |