typo-chr.lmt /size: 8117 b    last modification: 2025-02-21 11:03
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-- A historic intermediate version can be found in the mkiv variant. The code
10-- can probably be improved but performance etc is not really an issue here.
11
12local insert, remove = table.insert, table.remove
13
14local context           = context
15local ctx_doifelse      = commands.doifelse
16
17local nodecodes         = nodes.nodecodes
18local boundarycodes     = nodes.boundarycodes
19local subtypes          = nodes.subtypes
20
21local glyph_code        <const> = nodecodes.glyph
22local par_code          <const> = nodecodes.par
23local boundary_code     <const> = nodecodes.boundary
24
25local wordboundary_code <const> = boundarycodes.word
26
27local texgetnest        = tex.getnest -- to be used
28local texsetcount       = tex.setcount
29
30local flushnode         = nodes.flushnode
31local flushlist         = nodes.flushlist
32
33local settexattribute   = tex.setattribute
34local ispunctuation     = characters.is_punctuation
35
36local variables         = interfaces.variables
37local v_all             <const> = variables.all
38local v_reset           <const> = variables.reset
39
40local stack             = { }
41
42local a_marked          <const> = attributes.numbers['marked']
43local lastmarked        = 0
44local marked            = {
45    [v_all]   = 1,
46    [""]      = 1,
47    [v_reset] = attributes.unsetvalue,
48}
49
50local function pickup()
51    local list = texgetnest()
52    if list then
53        local tail = list.tail
54        if tail and tail.id == glyph_code and ispunctuation[tail.char] then
55            local prev = tail.prev
56            list.tail = prev
57            if prev then
58                prev.next = nil
59            end
60            list.tail = prev
61            tail.prev = nil
62            return tail
63        end
64    end
65end
66
67local actions = {
68    remove = function(specification)
69        local n = pickup()
70        if n then
71            flushnode(n)
72        end
73    end,
74    push = function(specification)
75        local n = pickup()
76        if n then
77            insert(stack,n or false)
78        end
79    end,
80    pop = function(specification)
81        local n = remove(stack)
82        if n then
83            context(n)
84        end
85    end,
86}
87
88local function pickuppunctuation(specification)
89    local action = actions[specification.action or "remove"]
90    if action then
91        action(specification)
92    end
93end
94
95-- I played with nested marked content but it makes no sense and gives
96-- complex code. Also, it's never needed so why bother.
97
98-- nodelib_direct_findattributerange: also backtrack
99
100local function pickup(head,tail,mark,backtrack)
101    if tail.id == par_code then -- and startofpar(tail)
102        -- mandate
103    else
104        local last = tail
105        if backtrack then
106            local current = last
107            while current do
108                if current[a_marked] == mark then
109                    last    = current
110                    break
111                else
112                    current = current.prev
113                end
114            end
115        end
116        if last and last[a_marked] == mark then
117            local first = last
118            while true do
119                local prev = first.prev
120                if prev and prev[a_marked] == mark then
121                    if prev.id == par_code then -- and startofpar(prev)
122                        break
123                    else
124                        first = prev
125                    end
126                else
127                    break
128                end
129            end
130            return first, last
131        end
132    end
133end
134
135local function found(str)
136    local list = texgetnest()
137    if list then
138        local tail = list.tail
139        if tail then
140            local mark = marked[str]
141            return mark and mark == tail[a_marked]
142        end
143    end
144end
145
146local actions = {
147    remove = function(specification)
148        local mark = marked[specification.mark]
149        if mark then
150            local list = texgetnest()
151            if list then
152                local head = list.head
153                local tail = list.tail
154                local first, last = pickup(head,tail,mark,specification.backtrack)
155                if first then
156                    if first == head then
157                        list.head = nil
158                        list.tail = nil
159                    else
160                        local prev = first.prev
161                        list.tail  = prev
162                        prev.next  = nil
163                    end
164                    flushlist(first)
165                end
166            end
167        end
168    end,
169}
170
171local function pickupmarkedcontent(specification)
172    local action = actions[specification.action or "remove"]
173    if action then
174        action(specification)
175    end
176end
177
178local function markcontent(str)
179    local currentmarked = marked[str or v_all]
180    if not currentmarked then
181        lastmarked    = lastmarked + 1
182        currentmarked = lastmarked
183        marked[str]   = currentmarked
184    end
185    settexattribute(a_marked,currentmarked)
186end
187
188interfaces.implement {
189    name      = "pickuppunctuation",
190    actions   = pickuppunctuation,
191    arguments = {
192        {
193            { "action" }
194        }
195    }
196}
197
198interfaces.implement {
199    name      = "pickupmarkedcontent",
200    actions   = pickupmarkedcontent,
201    arguments = {
202        {
203            { "action" },
204            { "mark" },
205            { "backtrack" , "boolean" },
206        }
207    }
208}
209
210interfaces.implement {
211    name      = "markcontent",
212    actions   = markcontent,
213    arguments = "string",
214}
215
216interfaces.implement {
217    name      = "doifelsemarkedcontent",
218    actions   = function(str) ctx_doifelse(found(str)) end,
219    arguments = "string",
220}
221
222-- We just put these here.
223
224interfaces.implement {
225    name    = "lastnodeidstring",
226    public  = true,
227    actions = function()
228        local list = texgetnest() -- "top"
229        local okay = false
230        if list then
231            local tail = list.tail
232            if tail then
233                okay = nodecodes[tail.id]
234            end
235        end
236        context(okay or "")
237    end,
238}
239
240-- local t_lastnodeid = token.create("c_syst_last_node_id")
241--
242-- interfaces.implement {
243--     name    = "lastnodeid",
244--     public  = true,
245--     actions = function()
246--         ...
247--         tex.setcount("c_syst_last_node_id",okay)
248--         context.sprint(t_lastnodeid)
249--     end,
250-- }
251
252-- not needed in lmtx ...
253
254local c_syst_last_node_id <const> = tex.iscount("c_syst_last_node_id")
255
256interfaces.implement {
257    name    = "lastnodeid",
258    actions = function()
259        local list = texgetnest() -- "top"
260        local okay = -1
261        if list then
262            local tail = list.tail
263            if tail then
264                okay = tail.id
265            end
266        end
267        texsetcount(c_syst_last_node_id,okay)
268    end,
269}
270
271interfaces.implement {
272    name    = "lastnodesubtypestring",
273    public  = true,
274    actions = function()
275        local list = texgetnest() -- "top"
276        local okay = false
277        if list then
278            local tail = list.tail
279            if head then
280                okay = subtypes[tail.id][tail.subtype]
281            end
282        end
283        context(okay or "")
284    end,
285}
286
287local function lastnodeequals(id,subtype)
288    local list = texgetnest() -- "top"
289    local okay = false
290    if list then
291        local tail = list.tail
292        if tail then
293            local i = tail.id
294            okay = i == id or i == nodecodes[id]
295            if subtype then
296                local s = tail.subtype
297                okay = s == subtype or s == subtypes[i][subtype]
298            end
299        end
300    end
301    ctx_doifelse(okay)
302end
303
304interfaces.implement {
305    name      = "lastnodeequals",
306    arguments = "2 strings",
307    actions   = lastnodeequals,
308}
309
310interfaces.implement {
311    name    = "atwordboundary",
312    actions = function()
313        lastnodeequals(boundary_code,wordboundary_code)
314    end,
315}
316
317