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