typo-itc.lmt /size: 22 Kb    last modification: 2024-01-16 09:03
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----- new_correction_glue = nodepool.glue
81
82local fonthashes          = fonts.hashes
83local fontdata            = fonthashes.identifiers
84----- italicsdata         = fonthashes.italics
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-- We don't drive this by attributes because most likely it is a document
102-- property.
103
104local exfactormath = 1.25 -- these need checking because not we go unscaled
105local exfactortext = 0.50 -- these need checking because not we go unscaled
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-- function typesetters.italics.forcevariant(variant)
117-- end
118
119-- We use the same key as the tex font handler. So, if a value has already be set, we
120-- use that one. Contrary to \MKIV\ we don't cache the kern value because we share the
121-- character data in compact mode as well as in slant mode. We do however cache the
122-- intermediate (partial) italic value. Instead of attributes we now set bits in the
123-- glyph and discretionary options which is more efficient and we had these anyway.
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                -- We take the median of all valid italic/slanted on my disk.
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-- bb3 > width : shape sticks out : we we need to add
193-- bb3 < width : we could have a negative correction
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                    -- < 0 indicates no overshoot or a very small auto italic
235                end
236            end
237            return 0, 0
238        end
239    else
240        return 0, 0
241    end
242end
243
244-- The left and right correction kerns will be done in a second pass because we have
245-- to deal with the left side anyway due to lack of a next glyph at injection time.
246
247-- callback.register("italic_correction", function(node,kern,subtype)
248--     if node and subtype == italic_kern_code then
249--         node = tonut(node)
250--         local char, id = isglyph(node)
251--         if char then
252--             kern = getrightitalic(id,char,node)
253--         elseif id == disc_code then
254--             -- for now we forget about it
255--         end
256--     end
257--     return kern
258-- end)
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            -- hm, check previous .. probably not ok yet
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) -- chardata[id][char].height or 0
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-- The code below now assumes the non-italic math model to be used so instead of
325-- nilling a kern (italic correction) we now compensate for the right corner kern.
326--
327-- [math: <glyph><kern>]<glyph> is now just [math: <glyph>]<glyph>
328
329----- function domath(head,last)
330local function domath(head,current)
331    if exfactormath and exfactormath > 0 then
332     -- local current = endofmath(last)
333        local next    = getnext(current)
334        if not hasglyphoption(next,no_correction_code) then -- check node and id too
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 -- check node and id too
339                    local ll, lr = getcornerkerns(glyph)
340                    if lr ~= 0 then
341                        -- unscaled
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        -- we could have a .01 pt threshold but correction doesn't happen often
374        local kern = correction_kern(italic,glyph)
375        insertnodeafter(head,glyph,kern)
376        if extra > 0 then
377            -- todo helper: isspace
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    -- yes : slantedglyph end
416    -- nop : slantedglyph kern
417    -- nop : slantedglyph      similarglyph
418    -- nop : slantedglyph      samefont
419    -- nop : slantedglyph glue similarglyph
420    -- nop : slantedglyph glue samefont
421    -- yes : slantedglyph glue
422
423 -- local current = head
424
425 -- local current = firstitalicglyph(head,true) -- no discretionaries (yet) as we assume a leading glyph
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--     if true then
479        local p = getprev(current)
480        if not p or getid(p) ~= glue_code then
481            local char, id = isglyph(current) -- isitalicglyph(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                -- nothing to do
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                -- nothing to do
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             -- local data = fine and (italicsdata[id] or getslant(current))
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             -- if not hasglyphoption(current,no_correction_code) then -- checked in the glyph itself
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             -- end
563            end
564        elseif id == kern_code then
565            local subtype = getsubtype(current)
566--             if subtype == italic_kern_code or subtype == right_kern_code then
567--                 fixrightitalic(current)
568--                 prevglyph    = nil
569--                 replaceglyph = nil
570--                 postglyph    = nil
571--             elseif subtype == left_kern_code then
572--                 fixleftitalic(current)
573--                 prevglyph    = nil
574--                 replaceglyph = nil
575--                 postglyph    = nil
576--             end
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            -- selective correction
584        elseif id == math_code then
585            flush()
586            current = endofmath(current)
587            if mathokay then
588                current = domath(head,current)
589         -- else
590         --     current = endofmath(current)
591            end
592            prevglyph    = nil
593            replaceglyph = nil
594            postglyph    = nil
595        else
596            flush()
597        end
598        current = nxt
599    end
600 -- if lastattr and lastattr > 1 then -- more control is needed here
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        -- fast loop for math
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() -- math and text
653    if enabletext then
654        enabletext()
655    end
656end
657
658function italics.enablemath() -- only math
659    if enablemath then
660        enablemath()
661    end
662end
663
664-- maybe a table and also thresholds
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