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