typo-chr.lmt /size: 7266 b    last modification: 2023-12-21 09:44
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        = nodecodes.glyph
22local par_code          = nodecodes.par
23local boundary_code     = nodecodes.boundary
24
25local wordboundary_code = 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             = variables.all
38local v_reset           = variables.reset
39
40local stack             = { }
41
42local a_marked          = 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
98local function pickup(head,tail,str)
99    local attr = marked[str]
100    local last = tail
101    if last[a_marked] == attr then
102        local first = last
103        while true do
104            local prev = first.prev
105            if prev and prev[a_marked] == attr then
106                if prev.id == par_code then -- and startofpar(prev)
107                    break
108                else
109                    first = prev
110                end
111            else
112                break
113            end
114        end
115        return first, last
116    end
117end
118
119local function found(str)
120    local list = texgetnest()
121    if list then
122        local tail = list.tail
123        return tail and tail[a_marked] == marked[str]
124    end
125end
126
127local actions = {
128    remove = function(specification)
129        local list = texgetnest()
130        if list then
131            local head = list.head
132            local tail = list.tail
133            local first, last = pickup(head,tail,specification.mark)
134            if first then
135                if first == head then
136                    list.head = nil
137                    list.tail = nil
138                else
139                    local prev = first.prev
140                    list.tail  = prev
141                    prev.next  = nil
142                end
143                flushlist(first)
144            end
145        end
146    end,
147}
148
149local function pickupmarkedcontent(specification)
150    local action = actions[specification.action or "remove"]
151    if action then
152        action(specification)
153    end
154end
155
156local function markcontent(str)
157    local currentmarked = marked[str or v_all]
158    if not currentmarked then
159        lastmarked    = lastmarked + 1
160        currentmarked = lastmarked
161        marked[str]   = currentmarked
162    end
163    settexattribute(a_marked,currentmarked)
164end
165
166interfaces.implement {
167    name      = "pickuppunctuation",
168    actions   = pickuppunctuation,
169    arguments = {
170        {
171            { "action" }
172        }
173    }
174}
175
176interfaces.implement {
177    name      = "pickupmarkedcontent",
178    actions   = pickupmarkedcontent,
179    arguments = {
180        {
181            { "action" },
182            { "mark" }
183        }
184    }
185}
186
187interfaces.implement {
188    name      = "markcontent",
189    actions   = markcontent,
190    arguments = "string",
191}
192
193interfaces.implement {
194    name      = "doifelsemarkedcontent",
195    actions   = function(str) ctx_doifelse(found(str)) end,
196    arguments = "string",
197}
198
199-- We just put these here.
200
201interfaces.implement {
202    name    = "lastnodeidstring",
203    public  = true,
204    actions = function()
205        local list = texgetnest() -- "top"
206        local okay = false
207        if list then
208            local tail = list.tail
209            if tail then
210                okay = nodecodes[tail.id]
211            end
212        end
213        context(okay or "")
214    end,
215}
216
217-- local t_lastnodeid = token.create("c_syst_last_node_id")
218--
219-- interfaces.implement {
220--     name    = "lastnodeid",
221--     public  = true,
222--     actions = function()
223--         ...
224--         tex.setcount("c_syst_last_node_id",okay)
225--         context.sprint(t_lastnodeid)
226--     end,
227-- }
228
229-- not needed in lmtx ...
230
231local c_syst_last_node_id = tex.iscount("c_syst_last_node_id")
232
233interfaces.implement {
234    name    = "lastnodeid",
235    actions = function()
236        local list = texgetnest() -- "top"
237        local okay = -1
238        if list then
239            local tail = list.tail
240            if tail then
241                okay = tail.id
242            end
243        end
244        texsetcount(c_syst_last_node_id,okay)
245    end,
246}
247
248interfaces.implement {
249    name    = "lastnodesubtypestring",
250    public  = true,
251    actions = function()
252        local list = texgetnest() -- "top"
253        local okay = false
254        if list then
255            local tail = list.tail
256            if head then
257                okay = subtypes[tail.id][tail.subtype]
258            end
259        end
260        context(okay or "")
261    end,
262}
263
264local function lastnodeequals(id,subtype)
265    local list = texgetnest() -- "top"
266    local okay = false
267    if list then
268        local tail = list.tail
269        if tail then
270            local i = tail.id
271            okay = i == id or i == nodecodes[id]
272            if subtype then
273                local s = tail.subtype
274                okay = s == subtype or s == subtypes[i][subtype]
275            end
276        end
277    end
278    ctx_doifelse(okay)
279end
280
281interfaces.implement {
282    name      = "lastnodeequals",
283    arguments = "2 strings",
284    actions   = lastnodeequals,
285}
286
287interfaces.implement {
288    name    = "atwordboundary",
289    actions = function()
290        lastnodeequals(boundary_code,wordboundary_code)
291    end,
292}
293
294