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 variables = interfaces.variables
23local settings_to_hash = utilities.parsers.settings_to_hash
24
25local nodecodes = nodes.nodecodes
26local glyph_code = nodecodes.glyph
27local kern_code = nodecodes.kern
28local glue_code = nodecodes.glue
29local disc_code = nodecodes.disc
30local math_code = nodecodes.math
31
32local end_math_code = nodes.mathcodes.endmath
33local font_kern_code = nodes.kerncodes.fontkern
34local italic_kern_code = nodes.kerncodes.italiccorrection
35local left_kern_code = nodes.kerncodes.leftcorrectionkern
36local right_kern_code = nodes.kerncodes.rightcorrectionkern
37local spaceskip_code = nodes.gluecodes.spaceskip
38local xspaceskip_code = nodes.gluecodes.xspaceskip
39
40local enableaction = nodes.tasks.enableaction
41
42local nuts = nodes.nuts
43local tonut = nodes.tonut
44local nodepool = nuts.pool
45
46local getprev = nuts.getprev
47local getnext = nuts.getnext
48local getid = nuts.getid
49local getsubtype = nuts.getsubtype
50local getchar = nuts.getchar
51local getdisc = nuts.getdisc
52local setattrlist = nuts.setattrlist
53local setdisc = nuts.setdisc
54local isglyph = nuts.isglyph
55local isnextglyph = nuts.isnextglyph
56local issimilarglyph = nuts.issimilarglyph
57local isitalicglyph = nuts.isitalicglyph
58local firstitalicglyph = nuts.firstitalicglyph
59local hasglyphoption = nuts.hasglyphoption
60local hasdiscoption = nuts.hasdiscoption
61local setkern = nuts.setkern
62local getkern = nuts.getkern
63local setglue = nuts.setglue
64local getglue = nuts.getglue
65local getheight = nuts.getheight
66local getoptions = nuts.getoptions
67local getslant = nuts.getslant
68local getcornerkerns = nuts.getcornerkerns
69local xscaled = nuts.xscaled
70local yscaled = nuts.yscaled
71
72local insertnodeafter = nuts.insertafter
73local insertnodebefore = nuts.insertbefore
74local endofmath = nuts.endofmath
75local findnode = nuts.findnode
76
77local traverseitalic = nuts.traverseitalic
78
79local new_correction_kern = nodepool.italickern
80
81
82local fonthashes = fonts.hashes
83local fontdata = fonthashes.identifiers
84
85local exheights = fonthashes.exheights
86local chardata = fonthashes.characters
87
88local ispunctuation = characters.is_punctuation
89
90local implement = interfaces.implement
91
92local no_correction_code = tex.glyphoptioncodes.noitaliccorrection
93local math_check_italic_code = tex.userglyphoptioncodes.mathcheckitalic
94local text_check_italic_code = tex.userglyphoptioncodes.textcheckitalic
95local disc_check_italic_code = tex.userdiscoptioncodes .textcheckitalic
96
97local mathokay = false
98local textokay = false
99local kernokay = false
100
101
102
103
104local exfactormath = 1.25
105local exfactortext = 0.50
106local spacefactor = 0
107
108directives.register("typesetters.italics.threshold", function(v)
109 exfactortext = v == true and 0.50 or tonumber(v)
110end)
111
112directives.register("typesetters.italics.threshold.math", function(v)
113 exfactormath = v == true and 1.25 or tonumber(v)
114end)
115
116
117
118
119
120
121
122
123
124
125local function getmultiplier(tfmdata,current)
126 local multiplier
127 local slant = getslant(current)
128 if slant and slant ~= 0 then
129 multiplier = slant/1000
130 else
131 local properties = tfmdata.properties
132 local italicangle = properties.useditalicangle or 0
133 if italicangle == 0 then
134 slant = properties.usedslant or 0
135 if slant and slant ~= 0 then
136 multiplier = slant/1000
137 else
138
139 italicangle = -12
140 multiplier = tand(12)
141 end
142 else
143 multiplier = tand(-italicangle)
144 end
145 end
146 return multiplier
147end
148
149local function getleftitalic(font,char,current)
150 local tfmdata = fontdata[font]
151 local character = tfmdata.characters[char]
152 if character then
153 local italic = character.italic
154 if not italic then
155 local multiplier = getmultiplier(tfmdata,current)
156 if multiplier ~= 0 then
157 local lsb = character._lsb_
158 if lsb == false then
159 return 0, 0
160 end
161 local description = tfmdata.descriptions[char]
162 if not lsb then
163 if description then
164 local boundingbox = description.boundingbox
165 if boundingbox then
166 lsb = boundingbox[1]
167 character._lsb_ = lsb
168 else
169 character._lsb_ = false
170 return 0, 0
171 end
172 else
173 character._lsb_ = false
174 return 0, 0
175 end
176 end
177 if lsb ~= 0 then
178 local factor = tfmdata.parameters.hfactor
179 local italic = xscaled(current,lsb * factor)
180 local extra = xscaled(current,description.height * multiplier * spacefactor * factor)
181 if trace_italics then
182 report_italics("setting left italic correction of %C of font %a to %p",char,font,italic)
183 end
184 return -italic, extra
185 end
186 end
187 end
188 end
189 return 0, 0
190end
191
192
193
194
195local function getrightitalic(font,char,current)
196 local tfmdata = fontdata[font]
197 local character = tfmdata.characters[char]
198 if character then
199 local italic = character.italic
200 if italic then
201 return italic, 0
202 else
203 local multiplier = getmultiplier(tfmdata,current)
204 if multiplier ~= 0 then
205 local rsb = character._rsb_
206 if rsb == false then
207 return 0, 0
208 end
209 local description = tfmdata.descriptions[char]
210 if not rsb then
211 if description then
212 local boundingbox = description.boundingbox
213 if boundingbox then
214 rsb = boundingbox[3] - description.width
215 character._rsb_ = rsb
216 else
217 character._rsb_ = false
218 return 0, 0
219 end
220 else
221 character._rsb_ = false
222 return 0, 0
223 end
224 end
225 if rsb > 0 then
226 local factor = tfmdata.parameters.hfactor
227 local italic = xscaled(current,rsb * factor)
228 local extra = xscaled(current,description.height * multiplier * spacefactor * factor)
229 if trace_italics then
230 report_italics("setting right italic correction of %C of font %a to %p",char,font,italic)
231 end
232 return italic, extra
233 else
234
235 end
236 end
237 return 0, 0
238 end
239 else
240 return 0, 0
241 end
242end
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
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 = findnode(current,math_code,begin_math_code)
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 current = fixrightitalic(current)
610 elseif subtype == left_kern_code then
611 current = fixleftitalic(current)
612 end
613 end
614 end
615 return head
616end
617
618function italics.handler(head)
619 if textokay then
620 return texthandler(head)
621 else
622
623 if mathokay then
624 return mathhandler(head)
625 end
626 return head
627 end
628end
629
630local enabletext = function()
631 if enable then
632 enable()
633 end
634 if trace_italics then
635 report_italics("enabling text/text italics")
636 end
637 enabletext = false
638 textokay = true
639end
640
641local enablemath = function()
642 if enable then
643 enable()
644 end
645 if trace_italics then
646 report_italics("enabling math/text italics")
647 end
648 enablemath = false
649 mathokay = true
650end
651
652function italics.enabletext()
653 if enabletext then
654 enabletext()
655 end
656end
657
658function italics.enablemath()
659 if enablemath then
660 enablemath()
661 end
662end
663
664
665
666local function setitaliccorrection(factor)
667 factor = tonumber(factor) or 0
668 if enabletext then
669 enabletext()
670 end
671 spacefactor = factor
672end
673
674implement {
675 name = "setitaliccorrection",
676 actions = setitaliccorrection,
677 arguments = "argument",
678}
679 |