typo-itc.lua /size: 25 Kb    last modification: 2021-10-28 13:50
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
10
11local trace_italics       = false  trackers.register("typesetters.italics", function(v) trace_italics = v end)
12
13local report_italics      = logs.reporter("nodes","italics")
14
15local threshold           = 0.5   trackers.register("typesetters.threshold", function(v) threshold = v == true and 0.5 or tonumber(v) end)
16
17typesetters.italics       = typesetters.italics or { }
18local italics             = typesetters.italics
19
20local nodecodes           = nodes.nodecodes
21local glyph_code          = nodecodes.glyph
22local kern_code           = nodecodes.kern
23local glue_code           = nodecodes.glue
24local disc_code           = nodecodes.disc
25local math_code           = nodecodes.math
26
27local enableaction        = nodes.tasks.enableaction
28
29local nuts                = nodes.nuts
30local nodepool            = nuts.pool
31
32local getprev             = nuts.getprev
33local getnext             = nuts.getnext
34local getid               = nuts.getid
35local getchar             = nuts.getchar
36local getdisc             = nuts.getdisc
37local getattr             = nuts.getattr
38local setattr             = nuts.setattr
39local getattrlist         = nuts.getattrlist
40local setattrlist         = nuts.setattrlist
41local setdisc             = nuts.setdisc
42local isglyph             = nuts.isglyph
43local setkern             = nuts.setkern
44local getkern             = nuts.getkern
45local getheight           = nuts.getheight
46
47local insertnodeafter     = nuts.insertafter
48local remove_node         = nuts.remove
49local endofmath           = nuts.endofmath
50
51local texgetattribute     = tex.getattribute
52local texsetattribute     = tex.setattribute
53local a_italics           = attributes.private("italics")
54local a_mathitalics       = attributes.private("mathitalics")
55
56local unsetvalue          = attributes.unsetvalue
57
58local new_correction_kern = nodepool.italickern
59local new_correction_glue = nodepool.glue
60
61local fonthashes          = fonts.hashes
62local fontdata            = fonthashes.identifiers
63local italicsdata         = fonthashes.italics
64local exheights           = fonthashes.exheights
65local chardata            = fonthashes.characters
66
67local is_punctuation      = characters.is_punctuation
68
69local implement           = interfaces.implement
70
71local forcedvariant       = false
72
73function typesetters.italics.forcevariant(variant)
74    forcedvariant = variant
75end
76
77-- We use the same key as the tex font handler. So, if a valua has already be set, we
78-- use that one.
79
80local function setitalicinfont(font,char)
81    local tfmdata = fontdata[font]
82    local character = tfmdata.characters[char]
83    if character then
84        local italic = character.italic
85        if not italic then
86            local autoitalicamount = tfmdata.properties.autoitalicamount or 0
87            if autoitalicamount ~= 0 then
88                local description = tfmdata.descriptions[char]
89                if description then
90                    italic = description.italic
91                    if not italic then
92                        local boundingbox = description.boundingbox
93                        italic = boundingbox[3] - description.width + autoitalicamount
94                        if italic < 0 then -- < 0 indicates no overshoot or a very small auto italic
95                            italic = 0
96                        end
97                    end
98                    if italic ~= 0 then
99                        italic = italic * tfmdata.parameters.hfactor
100                    end
101                end
102            end
103            if trace_italics then
104                report_italics("setting italic correction of %C of font %a to %p",char,font,italic)
105            end
106            if not italic then
107                italic = 0
108            end
109            character.italic = italic
110        end
111        return italic
112    else
113        return 0
114    end
115end
116
117-- todo: clear attribute
118
119local function okay(data,current,font,prevchar,previtalic,char,what)
120    if data then
121        if trace_italics then
122            report_italics("ignoring %p between %s italic %C and italic %C",previtalic,what,prevchar,char)
123        end
124        return false
125    end
126    if threshold then
127     -- if getid(current) == glyph_code then
128        while current and getid(current) ~= glyph_code do
129            current = getprev(current)
130        end
131        if current then
132            local ht = getheight(current)
133            local ex = exheights[font]
134            local th = threshold * ex
135            if ht <= th then
136                if trace_italics then
137                    report_italics("ignoring correction between %s italic %C and regular %C, height %p less than threshold %p",prevchar,what,char,ht,th)
138                end
139                return false
140            end
141        else
142            -- maybe backtrack to glyph
143        end
144    end
145    if trace_italics then
146        report_italics("inserting %p between %s italic %C and regular %C",previtalic,what,prevchar,char)
147    end
148    return true
149end
150
151-- maybe: with_attributes(current,n) :
152--
153-- local function correction_kern(kern,n)
154--     return with_attributes(new_correction_kern(kern),n)
155-- end
156
157local function correction_kern(kern,n)
158    local k = new_correction_kern(kern)
159    if n then
160        local a = getattrlist(n)
161        if a then -- maybe not
162            setattrlist(k,a) -- can be a marked content (border case)
163        end
164    end
165    return k
166end
167
168local function correction_glue(glue,n)
169    local g = new_correction_glue(glue)
170    if n then
171        local a = getattrlist(n)
172        if a then -- maybe not
173            setattrlist(g,a) -- can be a marked content (border case)
174        end
175    end
176    return g
177end
178
179local mathokay   = false
180local textokay   = false
181local enablemath = false
182local enabletext = false
183
184local function domath(head,current)
185    current    = endofmath(current)
186    local next = getnext(current)
187    if next then
188        local char, id = isglyph(next)
189        if char then
190            -- we can have an old font where italic correction has been applied
191            -- or a new one where it hasn't been done
192            local kern = getprev(current)
193            if kern and getid(kern) == kern_code then
194                local glyph = getprev(kern)
195                if glyph and getid(glyph) == glyph_code then
196                    -- [math: <glyph><kern>]<glyph> : we remove the correction when we have
197                    -- punctuation
198                    if is_punctuation[char] then
199                        local a = getattr(glyph,a_mathitalics)
200                        if a and (a < 100 or a > 100) then
201                            if a > 100 then
202                                a = a - 100
203                            else
204                                a = a + 100
205                            end
206                            local i = getkern(kern)
207                            local c, f = isglyph(glyph)
208                            if getheight(next) < 1.25*exheights[f] then
209                                if i == 0 then
210                                    if trace_italics then
211                                        report_italics("%s italic %p between math %C and punctuation %C","ignoring",i,c,char)
212                                    end
213                                else
214                                    if trace_italics then
215                                        report_italics("%s italic between math %C and punctuation %C","removing",i,c,char)
216                                    end
217                                    setkern(kern,0) -- or maybe a small value or half the ic
218                                end
219                            elseif i == 0 then
220                                local d = chardata[f][c]
221                                local i = d.italic
222                                if i == 0 then
223                                    if trace_italics then
224                                        report_italics("%s italic %p between math %C and punctuation %C","ignoring",i,c,char)
225                                    end
226                                else
227                                    setkern(kern,i)
228                                    if trace_italics then
229                                        report_italics("%s italic %p between math %C and punctuation %C","setting",i,c,char)
230                                    end
231                                end
232                            elseif trace_italics then
233                                report_italics("%s italic %p between math %C and punctuation %C","keeping",k,c,char)
234                            end
235                        end
236                    end
237                end
238            else
239                local glyph = kern
240                if glyph and getid(glyph) == glyph_code then
241                    -- [math: <glyph>]<glyph> : we add the correction when we have
242                    -- no punctuation
243                    if not is_punctuation[char] then
244                        local a = getattr(glyph,a_mathitalics)
245                        if a and (a < 100 or a > 100) then
246                            if a > 100 then
247                                a = a - 100
248                            else
249                                a = a + 100
250                            end
251                            if trace_italics then
252                                report_italics("%s italic %p between math %C and non punctuation %C","adding",a,getchar(glyph),char)
253                            end
254                            insertnodeafter(head,glyph,correction_kern(a,glyph))
255                        end
256                    end
257                end
258            end
259        end
260    end
261    return current
262end
263
264local function mathhandler(head)
265    local current = head
266    while current do
267        if getid(current) == math_code then
268            current = domath(head,current)
269        end
270        current = getnext(current)
271    end
272    return head
273end
274
275local function texthandler(head)
276
277    local prev            = nil
278    local prevchar        = nil
279    local prevhead        = head
280    local previtalic      = 0
281    local previnserted    = nil
282
283    local pre             = nil
284    local pretail         = nil
285
286    local post            = nil
287    local posttail        = nil
288    local postchar        = nil
289    local posthead        = nil
290    local postitalic      = 0
291    local postinserted    = nil
292
293    local replace         = nil
294    local replacetail     = nil
295    local replacechar     = nil
296    local replacehead     = nil
297    local replaceitalic   = 0
298    local replaceinserted = nil
299
300    local current         = prevhead
301    local lastfont        = nil
302    local lastattr        = nil
303
304    while current do
305        local char, id = isglyph(current)
306        if char then
307            local font = id
308            local data = italicsdata[font]
309            if font ~= lastfont then
310                if previtalic ~= 0 then
311                    if okay(data,current,font,prevchar,previtalic,char,"glyph") then
312                        insertnodeafter(prevhead,prev,correction_kern(previtalic,current))
313                    end
314                elseif previnserted and data then
315                    if trace_italics then
316                        report_italics("deleting last correction before %s %C",char,"glyph")
317                    end
318                    remove_node(prevhead,previnserted,true)
319                else
320                    --
321                    if replaceitalic ~= 0 then
322                        if okay(data,replace,font,replacechar,replaceitalic,char,"replace") then
323                            insertnodeafter(replacehead,replace,correction_kern(replaceitalic,current))
324                        end
325                        replaceitalic = 0
326                    elseif replaceinserted and data then
327                        if trace_italics then
328                            report_italics("deleting last correction before %s %C","replace",char)
329                        end
330                        remove_node(replacehead,replaceinserted,true)
331                    end
332                    --
333                    if postitalic ~= 0 then
334                        if okay(data,post,font,postchar,postitalic,char,"post") then
335                            insertnodeafter(posthead,post,correction_kern(postitalic,current))
336                        end
337                        postitalic = 0
338                    elseif postinserted and data then
339                        if trace_italics then
340                            report_italics("deleting last correction before %s %C","post",char)
341                        end
342                        remove_node(posthead,postinserted,true)
343                    end
344                end
345                --
346                lastfont = font
347            end
348            if data then
349                local attr = forcedvariant or getattr(current,a_italics)
350                if attr and attr > 0 then
351                    local cd = data[char]
352                    if not cd then
353                        -- this really can happen
354                        previtalic = 0
355                    else
356                        previtalic = cd.italic
357                        if not previtalic then
358                            previtalic = setitalicinfont(font,char) -- calculated once
359                         -- previtalic = 0
360                        end
361                        if previtalic ~= 0 then
362                            lastfont = font
363                            lastattr = attr
364                            prev     = current
365                         -- prevhead = head
366                            prevchar = char
367                        end
368                    end
369                else
370                    previtalic = 0
371                end
372            else
373                previtalic = 0
374            end
375            previnserted    = nil
376            replaceinserted = nil
377            postinserted    = nil
378        elseif id == disc_code then
379            previnserted    = nil
380            previtalic      = 0
381            replaceinserted = nil
382            replaceitalic   = 0
383            postinserted    = nil
384            postitalic      = 0
385            updated         = false
386            replacefont     = nil
387            postfont        = nil
388            pre, post, replace, pretail, posttail, replacetail = getdisc(current,true)
389            if replace then
390                local current = replacetail
391                while current do
392                    local char, id = isglyph(current)
393                    if char then
394                        local font = id
395                        if font ~= lastfont then
396                            local data = italicsdata[font]
397                            if data then
398                                local attr = forcedvariant or getattr(current,a_italics)
399                                if attr and attr > 0 then
400                                    local cd = data[char]
401                                    if not cd then
402                                        -- this really can happen
403                                        replaceitalic = 0
404                                    else
405                                        replaceitalic = cd.italic
406                                        if not replaceitalic then
407                                            replaceitalic = setitalicinfont(font,char) -- calculated once
408                                         -- replaceitalic = 0
409                                        end
410                                        if replaceitalic ~= 0 then
411                                            lastfont    = font
412                                            lastattr    = attr
413                                            replacechar = char
414                                            replacehead = replace
415                                            updated     = true
416                                        end
417                                    end
418                                end
419                            end
420                            replacefont = font
421                        end
422                        break
423                    else
424                        current = getprev(current)
425                    end
426                end
427            end
428            if post then
429                local current = posttail
430                while current do
431                    local char, id = isglyph(current)
432                    if char then
433                        local font = id
434                        if font ~= lastfont then
435                            local data = italicsdata[font]
436                            if data then
437                                local attr = forcedvariant or getattr(current,a_italics)
438                                if attr and attr > 0 then
439                                    local cd = data[char]
440                                    if not cd then
441                                        -- this really can happen
442                                        -- postitalic = 0
443                                    else
444                                        postitalic = cd.italic
445                                        if not postitalic then
446                                            postitalic = setitalicinfont(font,char) -- calculated once
447                                         -- postitalic = 0
448                                        end
449                                        if postitalic ~= 0 then
450                                            lastfont = font
451                                            lastattr = attr
452                                            postchar = char
453                                            posthead = post
454                                            updated  = true
455                                        end
456                                    end
457                                end
458                            end
459                            postfont = font
460                        end
461                        break
462                    else
463                        current = getprev(current)
464                    end
465                end
466            end
467            if replacefont or postfont then
468                lastfont = replacefont or postfont
469            end
470            if updated then
471                setdisc(current,pre,post,replace)
472            end
473        elseif id == kern_code then -- how about fontkern ?
474            previnserted    = nil
475            previtalic      = 0
476            replaceinserted = nil
477            replaceitalic   = 0
478            postinserted    = nil
479            postitalic      = 0
480        elseif id == glue_code then
481            if previtalic ~= 0 then
482                if trace_italics then
483                    report_italics("inserting %p between %s italic %C and glue",previtalic,"glyph",prevchar)
484                end
485                previnserted = correction_glue(previtalic,current) -- maybe just add ? else problem with penalties
486                previtalic   = 0
487                insertnodeafter(prevhead,prev,previnserted)
488            else
489                if replaceitalic ~= 0 then
490                    if trace_italics then
491                        report_italics("inserting %p between %s italic %C and glue",replaceitalic,"replace",replacechar)
492                    end
493                    replaceinserted = correction_kern(replaceitalic,current) -- needs to be a kern
494                    replaceitalic   = 0
495                    insertnodeafter(replacehead,replace,replaceinserted)
496                end
497                if postitalic ~= 0 then
498                    if trace_italics then
499                        report_italics("inserting %p between %s italic %C and glue",postitalic,"post",postchar)
500                    end
501                    postinserted = correction_kern(postitalic,current) -- needs to be a kern
502                    postitalic   = 0
503                    insertnodeafter(posthead,post,postinserted)
504                end
505            end
506        elseif id == math_code then
507            -- is this still needed ... the current engine implementation has been redone
508            previnserted    = nil
509            previtalic      = 0
510            replaceinserted = nil
511            replaceitalic   = 0
512            postinserted    = nil
513            postitalic      = 0
514            if mathokay then
515                current = domath(head,current)
516            else
517                current = endofmath(current)
518            end
519        else
520            if previtalic ~= 0 then
521                if trace_italics then
522                    report_italics("inserting %p between %s italic %C and whatever",previtalic,"glyph",prevchar)
523                end
524                insertnodeafter(prevhead,prev,correction_kern(previtalic,current))
525                previnserted    = nil
526                previtalic      = 0
527                replaceinserted = nil
528                replaceitalic   = 0
529                postinserted    = nil
530                postitalic      = 0
531            else
532                if replaceitalic ~= 0 then
533                    if trace_italics then
534                        report_italics("inserting %p between %s italic %C and whatever",replaceitalic,"replace",replacechar)
535                    end
536                    insertnodeafter(replacehead,replace,correction_kern(replaceitalic,current))
537                    previnserted    = nil
538                    previtalic      = 0
539                    replaceinserted = nil
540                    replaceitalic   = 0
541                    postinserted    = nil
542                    postitalic      = 0
543                end
544                if postitalic ~= 0 then
545                    if trace_italics then
546                        report_italics("inserting %p between %s italic %C and whatever",postitalic,"post",postchar)
547                    end
548                    insertnodeafter(posthead,post,correction_kern(postitalic,current))
549                    previnserted    = nil
550                    previtalic      = 0
551                    replaceinserted = nil
552                    replaceitalic   = 0
553                    postinserted    = nil
554                    postitalic      = 0
555                end
556            end
557        end
558        current = getnext(current)
559    end
560    if lastattr and lastattr > 1 then -- more control is needed here
561        if previtalic ~= 0 then
562            if trace_italics then
563                report_italics("inserting %p between %s italic %C and end of list",previtalic,"glyph",prevchar)
564            end
565            insertnodeafter(prevhead,prev,correction_kern(previtalic,current))
566        else
567            if replaceitalic ~= 0 then
568                if trace_italics then
569                    report_italics("inserting %p between %s italic %C and end of list",replaceitalic,"replace",replacechar)
570                end
571                insertnodeafter(replacehead,replace,correction_kern(replaceitalic,current))
572            end
573            if postitalic ~= 0 then
574                if trace_italics then
575                    report_italics("inserting %p between %s italic %C and end of list",postitalic,"post",postchar)
576                end
577                insertnodeafter(posthead,post,correction_kern(postitalic,current))
578            end
579        end
580    end
581    return head
582end
583
584function italics.handler(head)
585    if textokay then
586        return texthandler(head)
587    elseif mathokay then
588        return mathhandler(head)
589    else
590        return head, false
591    end
592end
593
594enabletext = function()
595    enableaction("processors","typesetters.italics.handler")
596    if trace_italics then
597        report_italics("enabling text/text italics")
598    end
599    enabletext = false
600    textokay   = true
601end
602
603enablemath = function()
604    enableaction("processors","typesetters.italics.handler")
605    if trace_italics then
606        report_italics("enabling math/text italics")
607    end
608    enablemath = false
609    mathokay   = true
610end
611
612function italics.enabletext()
613    if enabletext then
614        enabletext()
615    end
616end
617
618function italics.enablemath()
619    if enablemath then
620        enablemath()
621    end
622end
623
624function italics.set(n)
625    if enabletext then
626        enabletext()
627    end
628    if n == variables.reset then
629        texsetattribute(a_italics,unsetvalue)
630    else
631        texsetattribute(a_italics,tonumber(n) or unsetvalue)
632    end
633end
634
635function italics.reset()
636    texsetattribute(a_italics,unsetvalue)
637end
638
639implement {
640    name      = "setitaliccorrection",
641    actions   = italics.set,
642    arguments = "string"
643}
644
645implement {
646    name      = "resetitaliccorrection",
647    actions   = italics.reset,
648}
649
650local variables        = interfaces.variables
651local settings_to_hash = utilities.parsers.settings_to_hash
652
653local function setupitaliccorrection(option) -- no grouping !
654    if enabletext then
655        enabletext()
656    end
657    local options = settings_to_hash(option)
658    local variant = unsetvalue
659    if options[variables.text] then
660        variant = 1
661    elseif options[variables.always] then
662        variant = 2
663    end
664    -- maybe also keywords for threshold
665    if options[variables.global] then
666        forcedvariant = variant
667        texsetattribute(a_italics,unsetvalue)
668    else
669        forcedvariant = false
670        texsetattribute(a_italics,variant)
671    end
672    if trace_italics then
673        report_italics("forcing %a, variant %a",forcedvariant or "-",variant ~= unsetvalue and variant)
674    end
675end
676
677implement {
678    name      = "setupitaliccorrection",
679    actions   = setupitaliccorrection,
680    arguments = "string"
681}
682
683-- for manuals:
684
685local stack = { }
686
687implement {
688    name    = "pushitaliccorrection",
689    actions = function()
690        table.insert(stack,{forcedvariant, texgetattribute(a_italics) })
691    end
692}
693
694implement {
695    name    = "popitaliccorrection",
696    actions = function()
697        local top = table.remove(stack)
698        forcedvariant = top[1]
699        texsetattribute(a_italics,top[2])
700    end
701}
702