typo-chr.lua /size: 9185 b    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['typo-chr'] = {
2    version   = 1.001,
3    comment   = "companion to typo-bld.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
9-- This module can be optimized.
10
11-- local nodecodes        = nodes.nodecodes
12-- local whatsitcodes     = nodes.whatsitcodes
13--
14-- local glyph_code       = nodecodes.glyph
15-- local whatsit_code     = nodecodes.whatsit
16--
17-- local userwhatsit_code = whatsitcodes.userdefined
18--
19-- local stringusernode   = nodes.pool.userstring
20--
21-- local nuts             = nodes.nuts
22-- local pool             = nuts.pool
23--
24-- local getid            = nuts.getid
25-- local getprev          = nuts.getprev
26-- local getchar          = nuts.getchar
27-- local getdata          = nuts.getdata
28-- local getfield         = nuts.getfield
29--
30-- local remove_node      = nuts.remove
31-- local nextwhatsit      = nuts.traversers.whatsit
32--
33-- local signal           = pool.userids.signal
34--
35-- local is_punctuation   = characters.is_punctuation
36--
37-- local actions = {
38--     removepunctuation = function(head,n)
39--         local prev = getprev(n)
40--         if prev then
41--             if getid(prev) == glyph_code and is_punctuation[getchar(prev)] then
42--                 head = remove_node(head,prev,true)
43--             end
44--         end
45--         return head
46--     end
47-- }
48--
49-- -- we can also use properties .. todo (saves pass)
50--
51-- typesetters.signals = { }
52--
53-- function typesetters.signals.handler(head)
54--     local done = false
55--     for n, subtype in nextwhatsit, head do
56--         if subtype == userwhatsit_code and getfield(n,"user_id") == signal and getfield(n,"type") == 115 then
57--             local action = actions[getdata(n)]
58--             if action then
59--                 head = action(h,n)
60--             end
61--             head = remove_node(head,n,true)
62--             done = true
63--         end
64--     end
65--     return head, done
66-- end
67--
68-- local enabled = false
69--
70-- local function signal(what)
71--     if not enabled then
72--         nodes.tasks.prependaction("processors","normalizers", "typesetters.signals.handler")
73--         enabled = true
74--     end
75--     context(stringusernode(signal,what))
76-- end
77--
78-- interfaces.implement {
79--     name      = "signal",
80--     actions   = signal,
81--     arguments = "string",
82-- }
83
84local insert, remove = table.insert, table.remove
85
86local context           = context
87local ctx_doifelse      = commands.doifelse
88
89local nodecodes         = nodes.nodecodes
90local boundarycodes     = nodes.boundarycodes
91local subtypes          = nodes.subtypes
92
93local glyph_code        = nodecodes.glyph
94local par_code          = nodecodes.par
95local boundary_code     = nodecodes.boundary
96
97local wordboundary_code = boundarycodes.word
98
99local texgetnest        = tex.getnest -- to be used
100local texsetcount       = tex.setcount
101
102local flushnode         = nodes.flushnode
103local flushlist         = nodes.flushlist
104
105local settexattribute   = tex.setattribute
106local punctuation       = characters.is_punctuation
107
108local variables         = interfaces.variables
109local v_all             = variables.all
110local v_reset           = variables.reset
111
112local stack             = { }
113
114local a_marked          = attributes.numbers['marked']
115local lastmarked        = 0
116local marked            = {
117    [v_all]   = 1,
118    [""]      = 1,
119    [v_reset] = attributes.unsetvalue,
120}
121
122local function pickup()
123    local list = texgetnest()
124    if list then
125        local tail = list.tail
126        if tail and tail.id == glyph_code and punctuation[tail.char] then
127            local prev = tail.prev
128            list.tail = prev
129            if prev then
130                prev.next = nil
131            end
132            list.tail = prev
133            tail.prev = nil
134            return tail
135        end
136    end
137end
138
139local actions = {
140    remove = function(specification)
141        local n = pickup()
142        if n then
143            flushnode(n)
144        end
145    end,
146    push = function(specification)
147        local n = pickup()
148        if n then
149            insert(stack,n or false)
150        end
151    end,
152    pop = function(specification)
153        local n = remove(stack)
154        if n then
155            context(n)
156        end
157    end,
158}
159
160local function pickuppunctuation(specification)
161    local action = actions[specification.action or "remove"]
162    if action then
163        action(specification)
164    end
165end
166
167-- I played with nested marked content but it makes no sense and gives
168-- complex code. Also, it's never needed so why bother.
169
170local function pickup(head,tail,str)
171    local attr = marked[str]
172    local last = tail
173    if last[a_marked] == attr then
174        local first = last
175        while true do
176            local prev = first.prev
177            if prev and prev[a_marked] == attr then
178                if prev.id == par_code then -- and startofpar(prev)
179                    break
180                else
181                    first = prev
182                end
183            else
184                break
185            end
186        end
187        return first, last
188    end
189end
190
191local function found(str)
192    local list = texgetnest()
193    if list then
194        local tail = list.tail
195        return tail and tail[a_marked] == marked[str]
196    end
197end
198
199local actions = {
200    remove = function(specification)
201        local list = texgetnest()
202        if list then
203            local head = list.head
204            local tail = list.tail
205            local first, last = pickup(head,tail,specification.mark)
206            if first then
207                if first == head then
208                    list.head = nil
209                    list.tail = nil
210                else
211                    local prev = first.prev
212                    list.tail  = prev
213                    prev.next  = nil
214                end
215                flushlist(first)
216            end
217        end
218    end,
219}
220
221local function pickupmarkedcontent(specification)
222    local action = actions[specification.action or "remove"]
223    if action then
224        action(specification)
225    end
226end
227
228local function markcontent(str)
229    local currentmarked = marked[str or v_all]
230    if not currentmarked then
231        lastmarked    = lastmarked + 1
232        currentmarked = lastmarked
233        marked[str]   = currentmarked
234    end
235    settexattribute(a_marked,currentmarked)
236end
237
238interfaces.implement {
239    name      = "pickuppunctuation",
240    actions   = pickuppunctuation,
241    arguments = {
242        {
243            { "action" }
244        }
245    }
246}
247
248interfaces.implement {
249    name      = "pickupmarkedcontent",
250    actions   = pickupmarkedcontent,
251    arguments = {
252        {
253            { "action" },
254            { "mark" }
255        }
256    }
257}
258
259interfaces.implement {
260    name      = "markcontent",
261    actions   = markcontent,
262    arguments = "string",
263}
264
265interfaces.implement {
266    name      = "doifelsemarkedcontent",
267    actions   = function(str) ctx_doifelse(found(str)) end,
268    arguments = "string",
269}
270
271-- We just put these here.
272
273interfaces.implement {
274    name    = "lastnodeidstring",
275    public  = true,
276    actions = function()
277        local list = texgetnest() -- "top"
278        local okay = false
279        if list then
280            local tail = list.tail
281            if tail then
282                okay = nodecodes[tail.id]
283            end
284        end
285        context(okay or "")
286    end,
287}
288
289-- local t_lastnodeid = token.create("c_syst_last_node_id")
290--
291-- interfaces.implement {
292--     name    = "lastnodeid",
293--     public  = true,
294--     actions = function()
295--         ...
296--         tex.setcount("c_syst_last_node_id",okay)
297--         context.sprint(t_lastnodeid)
298--     end,
299-- }
300
301-- not needed in lmtx ...
302
303interfaces.implement {
304    name    = "lastnodeid",
305    actions = function()
306        local list = texgetnest() -- "top"
307        local okay = -1
308        if list then
309            local tail = list.tail
310            if tail then
311                okay = tail.id
312            end
313        end
314        texsetcount("c_syst_last_node_id",okay)
315    end,
316}
317
318interfaces.implement {
319    name    = "lastnodesubtypestring",
320    public  = true,
321    actions = function()
322        local list = texgetnest() -- "top"
323        local okay = false
324        if list then
325            local tail = list.tail
326            if head then
327                okay = subtypes[tail.id][tail.subtype]
328            end
329        end
330        context(okay or "")
331    end,
332}
333
334local function lastnodeequals(id,subtype)
335    local list = texgetnest() -- "top"
336    local okay = false
337    if list then
338        local tail = list.tail
339        if tail then
340            local i = tail.id
341            okay = i == id or i == nodecodes[id]
342            if subtype then
343                local s = tail.subtype
344                okay = s == subtype or s == subtypes[i][subtype]
345            end
346        end
347    end
348    ctx_doifelse(okay)
349end
350
351interfaces.implement {
352    name      = "lastnodeequals",
353    arguments = "2 strings",
354    actions   = lastnodeequals,
355}
356
357interfaces.implement {
358    name    = "atwordboundary",
359    actions = function()
360        lastnodeequals(boundary_code,wordboundary_code)
361    end,
362}
363
364