typo-sus.lmt /size: 9571 b    last modification: 2021-10-28 13:51
1if not modules then modules = { } end modules ['typo-sus'] = {
2    version   = 1.001,
3    comment   = "companion to typo-sus.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 next = next
10
11local punctuation = {
12    po = true,
13}
14
15local openquote = {
16    ps = true,
17    pi = true,
18}
19
20local closequote = {
21    pe = true,
22    pf = true,
23}
24
25local weird = {
26    lm = true,
27    no = true,
28}
29
30local categories      = characters.categories
31
32local nodecodes       = nodes.nodecodes
33
34local glyph_code      = nodecodes.glyph
35local kern_code       = nodecodes.kern
36local penalty_code    = nodecodes.penalty
37local glue_code       = nodecodes.glue
38local math_code       = nodecodes.math
39local hlist_code      = nodecodes.hlist
40local vlist_code      = nodecodes.vlist
41
42local nuts            = nodes.nuts
43
44local nextnode        = nuts.traversers.node
45
46local getid           = nuts.getid
47local getprev         = nuts.getprev
48local getnext         = nuts.getnext
49local getattr         = nuts.getattr
50local getfont         = nuts.getfont
51local getlist         = nuts.getlist
52local getkern         = nuts.getkern
53local getpenalty      = nuts.getpenalty
54local getwidth        = nuts.getwidth
55local getwhd          = nuts.getwhd
56local isglyph         = nuts.isglyph
57
58local setattr         = nuts.setattr
59local setlist         = nuts.setlist
60
61local setcolor        = nodes.tracers.colors.set
62local insertbefore    = nuts.insertbefore
63local insertafter     = nuts.insertafter
64local endofmath       = nuts.endofmath
65
66local nodepool        = nuts.pool
67
68local new_rule        = nodepool.rule
69local new_kern        = nodepool.kern
70local new_hlist       = nodepool.hlist
71----- new_penalty     = nodepool.penalty
72
73local a_characters    = attributes.private("characters")
74local a_suspecting    = attributes.private('suspecting')
75local a_suspect       = attributes.private('suspect')
76local texsetattribute = tex.setattribute
77local unsetvalue      = attributes.unsetvalue
78local enabled         = false
79
80local enableaction    = nodes.tasks.enableaction
81
82local threshold       = 65536 / 4
83
84local function special(n)
85    if n then
86        local id = getid(n)
87        if id == kern_code then
88            return getkern(n) < threshold
89        elseif id == penalty_code then
90            return true
91        elseif id == glue_code then
92            return getwidth(n) < threshold
93        elseif id == hlist_code then
94            return getwidth(n) < threshold
95        end
96    else
97        return false
98    end
99end
100
101local function goback(current)
102    local prev = getprev(current)
103    while prev and special(prev) do
104        prev = getprev(prev)
105    end
106    if prev then
107        return prev, getid(prev)
108    end
109end
110
111local function goforward(current)
112    local next = getnext(current)
113    while next and special(next) do
114        next = getnext(next)
115    end
116    if next then
117        return next, getid(next)
118    end
119end
120
121-- We can cache rules if needed but there are not that many.
122
123local function mark(head,current,id,color)
124    if id == glue_code then
125        -- the glue can have stretch and/or shrink so the rule can overlap with the
126        -- following glyph .. no big deal as that one then sits on top of the rule
127        local width = getwidth(current)
128        local rule  = new_rule(width)
129        local kern  = new_kern(-width)
130        head = insertbefore(head,current,rule)
131        head = insertbefore(head,current,kern)
132        setcolor(rule,color)
133 -- elseif id == kern_code then
134 --     local width = getkern(current)
135 --     local rule  = new_rule(width)
136 --     local kern  = new_kern(-width)
137 --     head = insertbefore(head,current,rule)
138 --     head = insertbefore(head,current,kern)
139 --     setcolor(rule,color)
140    else
141        local width, height, depth = getwhd(current)
142        local extra = fonts.hashes.xheights[getfont(current)] / 2
143        local rule  = new_rule(width,height+extra,depth+extra)
144        local hlist = new_hlist(rule)
145        head = insertbefore(head,current,hlist)
146        setcolor(rule,color)
147        setcolor(current,"white")
148    end
149    return head, current
150end
151
152-- we can cache the font and skip ahead to next but it doesn't
153-- save enough time and it makes the code looks bad too ... after
154-- all, we seldom use this
155
156local colors = {
157    "darkred",
158    "darkgreen",
159    "darkblue",
160    "darkcyan",
161    "darkmagenta",
162    "darkyellow",
163    "darkgray",
164    "orange",
165}
166
167local found = 0
168
169function typesetters.marksuspects(head)
170    local current  = head
171    local lastdone = nil
172    while current do
173        if getattr(current,a_suspecting) then
174            local char, id = isglyph(current)
175            if char then
176                local code = categories[char]
177                local done = false
178                if punctuation[code] then
179                    local prev, pid = goback(current)
180                    if prev and pid == glue_code then
181                        done = 3 -- darkblue
182                    elseif prev and pid == math_code then
183                        done = 3 -- darkblue
184                    else
185                        local next, nid = goforward(current)
186                        if next and nid ~= glue_code then
187                            done = 3 -- darkblue
188                        end
189                    end
190                elseif openquote[code] then
191                    local next, nid = goforward(current)
192                    if next and nid == glue_code then
193                        done = 1 -- darkred
194                    end
195                elseif closequote[code] then
196                    local prev, pid = goback(current)
197                    if prev and pid == glue_code then
198                        done = 1 -- darkred
199                    end
200                elseif weird[code] then
201                    done = 2 -- darkgreen
202                else
203                    local prev, pid = goback(current)
204                    if prev then
205                        if pid == math_code then
206                            done = 7-- darkgray
207                        elseif pid == glyph_code and getfont(current) ~= getfont(prev) then
208                            if lastdone ~= prev then
209                                done = 2 -- darkgreen
210                            end
211                        end
212                    end
213                    if not done then
214                        local next, nid = goforward(current)
215                        if next then
216                            if nid == math_code then
217                                done = 7 -- darkgray
218                            elseif nid == glyph_code and getfont(current) ~= getfont(next) then
219                                if lastdone ~= prev then
220                                    done = 2 -- darkgreen
221                                end
222                            end
223                        end
224                    end
225                end
226                if done then
227                    setattr(current,a_suspect,done)
228                    lastdone = current
229                    found = found + 1
230                end
231                current = getnext(current)
232            elseif id == math_code then
233                current = getnext(endofmath(current))
234            elseif id == glue_code then
235                local a = getattr(current,a_characters)
236                if a then
237                    local prev = getprev(current)
238                    local prid = prev and getid(prev)
239                    local done = false
240                    if prid == penalty_code and getpenalty(prev) == 10000 then
241                        done = 8 -- orange
242                    else
243                        done = 5 -- darkmagenta
244                    end
245                    if done then
246                        setattr(current,a_suspect,done)
247                     -- lastdone = current
248                        found = found + 1
249                    end
250                end
251                current = getnext(current)
252            else
253                current = getnext(current)
254            end
255        else
256            current = getnext(current)
257        end
258    end
259    return head
260end
261
262local function showsuspects(head)
263    -- we inject before so we're okay with a loop
264    for current, id, subtype in nextnode, head do
265        if id == glyph_code then
266            local a = getattr(current,a_suspect)
267            if a then
268                head, current = mark(head,current,id,colors[a])
269            end
270        elseif id == glue_code then
271            local a = getattr(current,a_suspect)
272            if a then
273                head, current = mark(head,current,id,colors[a])
274            end
275        elseif id == math_code then
276            current = endofmath(current)
277        elseif id == hlist_code or id == vlist_code then
278            local list = getlist(current)
279            if list then
280                local l = showsuspects(list)
281                if l ~= list then
282                    setlist(current,l)
283                end
284            end
285        end
286    end
287    return head
288end
289
290function typesetters.showsuspects(head)
291    if found > 0 then
292        return showsuspects(head)
293    else
294        return head
295    end
296end
297
298-- or maybe a directive
299
300trackers.register("typesetters.suspects",function(v)
301    texsetattribute(a_suspecting,v and 1 or unsetvalue)
302    if v and not enabled then
303        enableaction("processors","typesetters.marksuspects")
304        enableaction("shipouts",  "typesetters.showsuspects")
305        enabled = true
306    end
307end)
308
309