1if not modules then modules = { } end modules ['typo-itc'] = {
2 version = 1.001,
3 comment = "companion to typo-itc.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
9local tonumber = tonumber
10local tand = math.tand
11
12local trace_italics = false trackers.register("typesetters.italics", function(v) trace_italics = v end)
13
14local report_italics = logs.reporter("nodes","italics")
15
16typesetters.italics = typesetters.italics or { }
17local italics = typesetters.italics
18
19typesetters.corrections = typesetters.corrections or { }
20local corrections = typesetters.corrections
21
22local settings_to_hash = utilities.parsers.settings_to_hash
23
24local nodecodes = nodes.nodecodes
25local glyph_code <const> = nodecodes.glyph
26local kern_code <const> = nodecodes.kern
27local glue_code <const> = nodecodes.glue
28local disc_code <const> = nodecodes.disc
29local math_code <const> = nodecodes.math
30
31local font_kern_code <const> = nodes.kerncodes.fontkern
32local italic_kern_code <const> = nodes.kerncodes.italiccorrection
33local left_kern_code <const> = nodes.kerncodes.leftcorrectionkern
34local right_kern_code <const> = nodes.kerncodes.rightcorrectionkern
35local spaceskip_code <const> = nodes.gluecodes.spaceskip
36local xspaceskip_code <const> = nodes.gluecodes.xspaceskip
37
38local enableaction = nodes.tasks.enableaction
39
40local nuts = nodes.nuts
41local tonut = nodes.tonut
42local nodepool = nuts.pool
43
44local getprev = nuts.getprev
45local getnext = nuts.getnext
46local getid = nuts.getid
47local getsubtype = nuts.getsubtype
48local getchar = nuts.getchar
49local getdisc = nuts.getdisc
50local setattrlist = nuts.setattrlist
51local setdisc = nuts.setdisc
52local isglyph = nuts.isglyph
53local isnextglyph = nuts.isnextglyph
54local issimilarglyph = nuts.issimilarglyph
55local isitalicglyph = nuts.isitalicglyph
56local firstitalicglyph = nuts.firstitalicglyph
57local hasglyphoption = nuts.hasglyphoption
58local hasdiscoption = nuts.hasdiscoption
59local setkern = nuts.setkern
60local getkern = nuts.getkern
61local setglue = nuts.setglue
62local getglue = nuts.getglue
63local getheight = nuts.getheight
64local getoptions = nuts.getoptions
65local getslant = nuts.getslant
66local getcornerkerns = nuts.getcornerkerns
67local xscaled = nuts.xscaled
68local yscaled = nuts.yscaled
69
70local insertnodeafter = nuts.insertafter
71local insertnodebefore = nuts.insertbefore
72local beginofmath = nuts.beginofmath
73local endofmath = nuts.endofmath
74local findnode = nuts.findnode
75
76local traverseitalic = nuts.traverseitalic
77
78local new_correction_kern = nodepool.italiccorrection
79
80local fonthashes = fonts.hashes
81local fontdata = fonthashes.identifiers
82local exheights = fonthashes.exheights
83local chardata = fonthashes.characters
84
85local ispunctuation = characters.is_punctuation
86
87local implement = interfaces.implement
88
89local no_correction_code <const> = tex.glyphoptioncodes.noitaliccorrection
90local math_check_italic_code <const> = tex.userglyphoptioncodes.mathcheckitalic
91local text_check_italic_code <const> = tex.userglyphoptioncodes.textcheckitalic
92local disc_check_italic_code <const> = tex.userdiscoptioncodes .textcheckitalic
93
94local mathokay = false
95local textokay = false
96local kernokay = false
97
98
99
100local exfactormath = 1.25
101local exfactortext = 0.50
102local spacefactor = 0
103
104directives.register("typesetters.italics.threshold", function(v)
105 exfactortext = v == true and 0.50 or tonumber(v)
106end)
107
108directives.register("typesetters.italics.threshold.math", function(v)
109 exfactormath = v == true and 1.25 or tonumber(v)
110end)
111
112
113
114
115
116
117
118
119
120
121local function getmultiplier(tfmdata,current)
122 local multiplier
123 local slant = getslant(current)
124 if slant and slant ~= 0 then
125 multiplier = slant/1000
126 else
127 local properties = tfmdata.properties
128 local italicangle = properties.useditalicangle or 0
129 if italicangle == 0 then
130 slant = properties.usedslant or 0
131 if slant and slant ~= 0 then
132 multiplier = slant/1000
133 else
134
135 italicangle = -12
136 multiplier = tand(12)
137 end
138 else
139 multiplier = tand(-italicangle)
140 end
141 end
142 return multiplier
143end
144
145local function getleftitalic(font,char,current)
146 local tfmdata = fontdata[font]
147 local character = tfmdata.characters[char]
148 if character then
149 local italic = character.italic
150 if not italic then
151 local multiplier = getmultiplier(tfmdata,current)
152 if multiplier ~= 0 then
153 local lsb = character._lsb_
154 if lsb == false then
155 return 0, 0
156 end
157 local description = tfmdata.descriptions[char]
158 if not lsb then
159 if description then
160 local boundingbox = description.boundingbox
161 if boundingbox then
162 lsb = boundingbox[1]
163 character._lsb_ = lsb
164 else
165 character._lsb_ = false
166 return 0, 0
167 end
168 else
169 character._lsb_ = false
170 return 0, 0
171 end
172 end
173 if lsb ~= 0 then
174 local factor = tfmdata.parameters.hfactor
175 local italic = xscaled(current,lsb * factor)
176 local extra = xscaled(current,description.height * multiplier * spacefactor * factor)
177 if trace_italics then
178 report_italics("setting left italic correction of %C of font %a to %p",char,font,italic)
179 end
180 return -italic, extra
181 end
182 end
183 end
184 end
185 return 0, 0
186end
187
188
189
190
191local function getrightitalic(font,char,current)
192 local tfmdata = fontdata[font]
193 local character = tfmdata.characters[char]
194 if character then
195 local italic = character.italic
196 if italic then
197 return italic, 0
198 else
199 local multiplier = getmultiplier(tfmdata,current)
200 if multiplier ~= 0 then
201 local rsb = character._rsb_
202 if rsb == false then
203 return 0, 0
204 end
205 local description = tfmdata.descriptions[char]
206 if not rsb then
207 if description then
208 local boundingbox = description.boundingbox
209 if boundingbox then
210 rsb = boundingbox[3] - description.width
211 character._rsb_ = rsb
212 else
213 character._rsb_ = false
214 return 0, 0
215 end
216 else
217 character._rsb_ = false
218 return 0, 0
219 end
220 end
221 if rsb > 0 then
222 local factor = tfmdata.parameters.hfactor
223 local italic = xscaled(current,rsb * factor)
224 local extra = xscaled(current,description.height * multiplier * spacefactor * factor)
225 if trace_italics then
226 report_italics("setting right italic correction of %C of font %a to %p",char,font,italic)
227 end
228 return italic, extra
229 else
230
231 end
232 end
233 return 0, 0
234 end
235 else
236 return 0, 0
237 end
238end
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260callback.register("italic_correction", function(node,kern,subtype)
261 if not kernokay then
262 enableaction("processors","typesetters.corrections.handler")
263 kernokay = true
264 if trace_italics then
265 report_italics("enabling kern italics")
266 end
267 end
268 return kern
269end)
270
271local function report_ignored(previous,current,why)
272 report_italics("%s correction between %C and %C, %s","ignoring",getchar(previous),getchar(current),why)
273end
274
275local function report_obeyed(previous,current,why)
276 report_italics("%s correction between %C and %C, %s","inserting",getchar(previous),getchar(current),why)
277end
278
279local function report_math(kern,mathchar,char,why)
280 report_italics("%s italic %p between math %C and punctuation %C, %s",kern,mathchar,char,why)
281end
282
283local function injection_okay(previous,current)
284 if hasglyphoption(current,no_correction_code) then
285 if trace_italics then
286 report_ignored(previous,current,"disabled")
287 end
288 return false
289 else
290 if exfactortext and exfactortext > 0 then
291
292 while current and getid(current) ~= glyph_code do
293 current = getprev(current)
294 end
295 if current then
296 local char, id = isglyph(current)
297 if char then
298 local height = getheight(current)
299 local exheight = yscaled(current, exheights[id])
300 if height <= exfactortext*exheight then
301 if trace_italics then
302 report_ignored(previous,current,"threshold")
303 end
304 return false
305 end
306 end
307 end
308 end
309 if trace_italics then
310 report_obeyed(previous,current,"enabled")
311 end
312 return true
313 end
314end
315
316local function correction_kern(kern,n)
317 local k = new_correction_kern(kern)
318 if n then
319 setattrlist(k,n)
320 end
321 return k
322end
323
324
325
326
327
328
329
330local function domath(head,current)
331 if exfactormath and exfactormath > 0 then
332 local current = endofmath(current)
333 local next = getnext(current)
334 if not hasglyphoption(next,no_correction_code) then
335 local char, id = isglyph(next)
336 if char and ispunctuation[char] then
337 local glyph = getprev(current)
338 if hasglyphoption(glyph,math_check_italic_code) then
339 local ll, lr = getcornerkerns(glyph)
340 if lr ~= 0 then
341
342 local height = chardata[id][char].height or 0
343 local exheight = yscaled(current, exheights[id])
344 if height < exfactormath*exheight then
345 insertnodeafter(head,current,correction_kern(lr,glyph))
346 if trace_italics then
347 report_math(lr,c,char,"injecting")
348 end
349 end
350 end
351 end
352 end
353 end
354 end
355 return current
356end
357
358local function mathhandler(head)
359 local current = head
360 while current do
361 current = beginofmath(current)
362 if current then
363 current = domath(head,current)
364 current = getnext(current)
365 end
366 end
367 return head
368end
369
370local function injectafter(head,glyph,font,char,where)
371 local italic, extra = getrightitalic(font,char,glyph)
372 if italic ~= 0 then
373
374 local kern = correction_kern(italic,glyph)
375 insertnodeafter(head,glyph,kern)
376 if extra > 0 then
377
378 local next = getnext(kern)
379 if next and getid(next) == glue_code then
380 local subtype = getsubtype(next)
381 if subtype == spaceskip_code or subtype == xspaceskip_code then
382 local width, stretch, shrink = getglue(next)
383 setglue(next,width+extra,stretch,shrink)
384 end
385 end
386 end
387 if trace_italics then
388 report_italics("inserting %p between %s italic %C ",italic,where,char)
389 end
390 end
391end
392
393local function fixleftitalic(current)
394 local prev = getnext(current)
395 local char, id = isglyph(prev)
396 if char then
397 local italic, extra = getleftitalic(id,char,prev)
398 setkern(current,italic)
399 end
400 return current
401end
402
403local function fixrightitalic(current)
404 local prev = getprev(current)
405 local char, id = isglyph(prev)
406 if char then
407 local italic, extra = getrightitalic(id,char,prev)
408 setkern(current,italic)
409 end
410 return current
411end
412
413local function texthandler(head)
414
415
416
417
418
419
420
421
422
423
424
425
426 local current = firstitalicglyph(head)
427
428 if not current then
429 return head
430 end
431
432 local currentdisc = nil
433
434 local prevglyph = nil
435 local prevchar = nil
436 local prevfont = nil
437
438 local prehead = nil
439 local pretail = nil
440
441 local postglyph = nil
442 local posthead = nil
443 local posttail = nil
444 local postfont = nil
445 local postchar = nil
446
447 local replaceglyph = nil
448 local replacehead = nil
449 local replacetail = nil
450 local replacefont = nil
451 local replacechar = nil
452
453 local function flush()
454 if prevglyph then
455 injectafter(head,prevglyph,prevfont,prevchar,"glyph")
456 prevglyph = nil
457 replaceglyph = nil
458 postglyph = nil
459 else
460 local updatedisc = false
461 if replaceglyph then
462 injectafter(replacehead,replaceglyph,replacefont,replacechar,"replace")
463 replaceglyph = nil
464 updatedisc = true
465 end
466 if postglyph then
467 injectafter(posthead,postglyph,postfont,postchar,"post")
468 postglyph = nil
469 updatedisc = true
470 end
471 if updatedisc then
472 setdisc(currentdisc,prehead,posthead,replacehead)
473 end
474 end
475 end
476
477 if false then
478
479 local p = getprev(current)
480 if not p or getid(p) ~= glue_code then
481 local char, id = isglyph(current)
482 if char then
483 local italic = getleftitalic(id,char,current)
484 if italic ~= 0 and current == head then
485 head = insertnodebefore(head,current,correction_kern(italic,current))
486 end
487 end
488 end
489 end
490
491 while current do
492 local nxt, char, id = isnextglyph(current)
493 if char then
494 if not hasglyphoption(current,text_check_italic_code) then
495 prevglyph = nil
496 replaceglyph = nil
497 postglyph = nil
498 elseif not prevglyph then
499
500 local data = isitalicglyph(current)
501 if data then
502 prevglyph = current
503 prevchar = char
504 prevfont = id
505 replaceglyph = nil
506 postglyph = nil
507 end
508 elseif issimilarglyph(current,prevglyph) then
509
510 prevglyph = current
511 prevchar = char
512 prevfont = id
513 replaceglyph = nil
514 postglyph = nil
515 else
516 local fine = injection_okay(prevglyph,current)
517 if fine then
518 flush()
519 else
520 prevglyph = nil
521 replaceglyph = nil
522 postglyph = nil
523 end
524
525 local data = isitalicglyph(current)
526 if data then
527 prevglyph = current
528 prevchar = char
529 prevfont = id
530 end
531 end
532 elseif id == disc_code then
533 prevglyph = nil
534 replaceglyph = nil
535 postglyph = nil
536 if hasdiscoption(current,disc_check_italic_code) then
537
538 prehead, posthead, replacehead, pretail, posttail, replacetail = getdisc(current,true)
539 if replacetail then
540 local char, id = isglyph(replacetail)
541 if char then
542 local data = isitalicglyph(replacetail)
543 if data then
544 replaceglyph = replacetail
545 replacechar = char
546 replacefont = id
547 end
548 end
549 end
550 if posttail then
551 local char, id = isglyph(posttail)
552 if char then
553 local data = isitalicglyph(replacetail)
554 if data then
555 postglyph = posttail
556 postchar = char
557 postfont = id
558 end
559 end
560 end
561 currentdisc = current
562
563 end
564 elseif id == kern_code then
565 local subtype = getsubtype(current)
566
567
568
569
570
571
572
573
574
575
576
577 if subtype == italic_kern_code or subtype == right_kern_code or subtype == left_kern_code then
578 prevglyph = nil
579 replaceglyph = nil
580 postglyph = nil
581 end
582 elseif id == glue_code then
583
584 elseif id == math_code then
585 flush()
586 current = endofmath(current)
587 if mathokay then
588 current = domath(head,current)
589
590
591 end
592 prevglyph = nil
593 replaceglyph = nil
594 postglyph = nil
595 else
596 flush()
597 end
598 current = nxt
599 end
600
601 flush()
602 return head
603end
604
605function corrections.handler(head)
606 if kernokay then
607 for current, subtype in traverseitalic(head) do
608 if subtype == italic_kern_code or subtype == right_kern_code then
609 fixrightitalic(current)
610 elseif subtype == left_kern_code then
611 fixleftitalic(current)
612 end
613 end
614 end
615 return head
616end
617
618local function enable()
619 enableaction("processors","typesetters.italics.handler")
620 enable = false
621end
622
623function italics.handler(head)
624 if textokay then
625 return texthandler(head)
626 else
627
628 if mathokay then
629 return mathhandler(head)
630 end
631 return head
632 end
633end
634
635local enabletext = function()
636 if enable then
637 enable()
638 end
639 if trace_italics then
640 report_italics("enabling text/text italics")
641 end
642 enabletext = false
643 textokay = true
644end
645
646local enablemath = function()
647 if enable then
648 enable()
649 end
650 if trace_italics then
651 report_italics("enabling math/text italics")
652 end
653 enablemath = false
654 mathokay = true
655end
656
657function italics.enabletext()
658 if enabletext then
659 enabletext()
660 end
661end
662
663function italics.enablemath()
664 if enablemath then
665 enablemath()
666 end
667end
668
669
670
671local function setitaliccorrection(factor)
672 factor = tonumber(factor) or 0
673 if enabletext then
674 enabletext()
675 end
676 spacefactor = factor
677end
678
679implement {
680 name = "setitaliccorrection",
681 actions = setitaliccorrection,
682 arguments = "argument",
683}
684 |