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