page-lin.lmt /size: 11 Kb    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['page-lin'] = {
2    version   = 1.001,
3    comment   = "companion to page-lin.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, tonumber = next, tonumber
10
11local trace_numbers      = false  trackers.register("lines.numbers",  function(v) trace_numbers = v end)
12
13local report_lines       = logs.reporter("lines")
14
15local attributes         = attributes
16local nodes              = nodes
17local context            = context
18
19local implement          = interfaces.implement
20
21nodes.lines              = nodes.lines or { }
22local lines              = nodes.lines
23
24lines.data               = lines.data or { } -- start step tag
25local data               = lines.data
26local last               = #data
27
28storage.register("lines/data", data, "nodes.lines.data")
29
30local variables          = interfaces.variables
31
32local v_next             = variables.next
33local v_page             = variables.page
34local v_no               = variables.no
35
36local properties         = nodes.properties.data
37
38local nodecodes          = nodes.nodecodes
39local hlist_code         = nodecodes.hlist
40local vlist_code         = nodecodes.vlist
41local whatsit_code       = nodecodes.whatsit
42local glyph_code         = nodecodes.glyph
43
44local linelist_code      = nodes.listcodes.line
45
46local latelua_code       = nodes.whatsitcodes.latelua
47
48local localtexrun        = tex.runlocal
49
50----- a_displaymath      = attributes.private('displaymath')
51local a_linenumber       = attributes.private('linenumber')
52local a_linereference    = attributes.private('linereference')
53
54local cross_references   = { }
55
56local nuts               = nodes.nuts
57
58local getid              = nuts.getid
59local getattr            = nuts.getattr
60local getlist            = nuts.getlist
61local getbox             = nuts.getbox
62local gettotal           = nuts.gettotal
63
64local takebox            = nuts.takebox
65
66local nexthlist          = nuts.traversers.hlist
67local nextvlist          = nuts.traversers.vlist
68local nextcontent        = nuts.traversers.content
69local nextlist           = nuts.traversers.list
70local nextnode           = nuts.traversers.node
71local nextwhatsit        = nuts.traversers.whatsit
72
73local is_display_math    = nuts.is_display_math
74
75local ctx_convertnumber  = context.convertnumber
76local ctx_makelinenumber = context.makelinenumber
77
78local paragraphs         = typesetters.paragraphs
79local addtoline          = paragraphs.addtoline
80local checkline          = paragraphs.checkline
81
82-- cross referencing
83
84function lines.number(n)
85    n = tonumber(n)
86    local cr = cross_references[n] or 0
87    cross_references[n] = nil
88    return cr
89end
90
91function lines.finalize(t)
92    local getnumber = lines.number
93    for _,p in next, t do
94        for _,r in next, p do
95            local m = r.metadata
96            if m and m.kind == "line" then
97                local e = r.entries
98                local u = r.userdata
99                e.linenumber = getnumber(e.text or 0) -- we can nil e.text
100                e.conversion = u and u.conversion
101                r.userdata = nil -- hack
102            end
103        end
104    end
105end
106
107local filters = structures.references.filters
108local helpers = structures.helpers
109
110structures.references.registerfinalizer(lines.finalize)
111
112filters.line = filters.line or { }
113
114function filters.line.default(data)
115    ctx_convertnumber(data.entries.conversion or "numbers",data.entries.linenumber or "0")
116end
117
118function filters.line.page(data,prefixspec,pagespec) -- redundant
119    helpers.prefixpage(data,prefixspec,pagespec)
120end
121
122function filters.line.linenumber(data) -- raw
123    context(data.entries.linenumber or "0")
124end
125
126-- boxed variant, todo: use number mechanism
127
128local boxed = lines.boxed or { }
129lines.boxed = boxed
130
131-- todo: cache setups, and free id no longer used
132-- use interfaces.cachesetup(t)
133
134function boxed.register(configuration)
135    last = last + 1
136    data[last] = configuration
137    if trace_numbers then
138        report_lines("registering setup %a",last)
139    end
140    return last
141end
142
143implement {
144    name      = "registerlinenumbering",
145    actions   = { boxed.register, context },
146    arguments = {
147        {
148            { "continue" },
149            { "start", "integer" },
150            { "step", "integer" },
151            { "method" },
152            { "tag" },
153        }
154    }
155}
156
157function boxed.setup(n,configuration)
158    local d = data[n]
159    if d then
160        if trace_numbers then
161            report_lines("updating setup %a",n)
162        end
163        for k,v in next, configuration do
164            d[k] = v
165        end
166    else
167        if trace_numbers then
168            report_lines("registering setup %a (br)",n)
169        end
170        data[n] = configuration
171    end
172    return n
173end
174
175implement {
176    name      = "setuplinenumbering",
177    actions   = boxed.setup,
178    arguments = {
179        "integer",
180        {
181            { "continue" },
182            { "start", "integer" },
183            { "step", "integer" },
184            { "method" },
185            { "tag" },
186        }
187    }
188}
189
190local function resolve(n,m) -- we can now check the 'line' flag (todo)
191    for current, id, subtype, content in nextlist, n do
192        if content then
193            if id == hlist_code then
194                for current, subtype in nextwhatsit, content do
195                    if subtype == latelua_code then            -- this has to change!
196                        local a = getattr(current,a_linereference)
197                        if a then
198                            cross_references[a] = m
199                        end
200                    end
201                end
202            end
203            resolve(content,m)
204        end
205    end
206end
207
208local function check_number(n,b,a,skip)
209    local d = data[a]
210    if d then
211        local tag  = d.tag or ""
212        local s    = d.start or 1
213        local okay = not skip and s % d.step == 0
214        if trace_numbers then
215            report_lines("%s number for setup %a, tag %a, line %s, continued %a",okay and "making" or "skipping",a,tag,s,d.continue or v_no)
216        end
217        if okay then
218            local p = checkline(n)
219            if p then
220                localtexrun(function() ctx_makelinenumber(tag,s,p.hsize,p.reverse and 1 or 0) end)
221                local l = takebox(b)
222                if l then
223                    addtoline(n,l)
224                else
225                    -- very unlikely
226                end
227            end
228        end
229        resolve(n,s)
230        d.start  = s + 1
231    end
232end
233
234-- print(nodes.idstostring(list))
235
236-- hlists of type line will only have an attribute when the line number attribute
237-- still set at par building time which is not always the case unless we explicitly
238-- do a par before we end the line
239
240local function lineisnumbered(n) -- content node
241    for n, id, subtype, content in nextcontent, getlist(n) do
242        local a = getattr(n,a_linenumber)
243        if a and a > 0 then
244            return a
245        end
246    end
247end
248
249local function listisnumbered(list)
250    if list then
251        for n, subtype in nexthlist, list do
252            if subtype == linelist_code then
253                local a = getattr(n,a_linenumber)
254                if a then
255                    -- a quick test for lines (only valid when \par before \stoplinenumbering)
256                    return a > 0 and list or false
257                else
258                    -- a bit slower one, assuming that we have normalized and anchored
259                    if lineisnumbered(n) then
260                        return list
261                    end
262                end
263            end
264        end
265    end
266end
267
268local function findnumberedlist(list)
269    -- we assume wrapped boxes, only one with numbers
270    for n, id, subtype, content in nextcontent, list do
271        if id == hlist_code then
272            if subtype == linelist_code then
273                local a = getattr(n,a_linenumber)
274                if a then
275                    return a > 0 and list
276                end
277                return
278            else
279                if lineisnumbered(content) then
280                    return n
281                end
282                local okay = findnumberedlist(content)
283                if okay then
284                    return okay
285                end
286            end
287        elseif id == vlist_code then
288            if listisnumbered(content) then
289                return content
290            end
291            local okay = findnumberedlist(content)
292            if okay then
293                return okay
294            end
295        elseif id == glyph_code then
296            return
297        end
298    end
299end
300
301-- reset ranges per page
302-- store first and last per page
303-- maybe just set marks directly
304
305local function findcolumngap(list)
306    -- we assume wrapped boxes, only one with numbers
307    for n, id, subtype, content in nextlist, list do
308        if id == hlist_code or id == vlist_code then
309            local p = properties[n]
310            if p and p.columngap then
311                if trace_numbers then
312                    report_lines("first column gap %a",p.columngap)
313                end
314                return n
315            elseif content then
316                local okay = findcolumngap(content)
317                if okay then
318                    return okay
319                end
320            end
321        end
322    end
323end
324
325function boxed.addlinenumbers(n,b,nested)
326    local box = getbox(n)
327    if not box then
328        return
329    end
330    local list = getlist(box)
331    if not list then
332        return
333    end
334    local last_a = nil
335    local last_v = -1
336    local skip   = false
337
338    local function check()
339        for n, subtype in nexthlist, list do
340            if subtype ~= linelist_code then
341                -- go on
342            elseif gettotal(n) == 0 then
343                -- skip funny hlists -- todo: check line subtype
344            else
345                local a = lineisnumbered(n)
346                if a then
347                    if last_a ~= a then
348                        local da = data[a]
349                        local ma = da.method
350                        if ma == v_next then
351                            skip = true
352                        elseif ma == v_page then
353                            da.start = 1 -- eventually we will have a normal counter
354                        end
355                        last_a = a
356                        if trace_numbers then
357                            report_lines("starting line number range %s: start %s, continue %s",a,da.start,da.continue or v_no)
358                        end
359                    end
360                 -- if getattr(n,a_displaymath) then
361                 --     check_number(n,b,a,skip)
362                 -- else
363                       check_number(n,b,a,skip)
364                 -- end
365                    skip = false
366                end
367            end
368        end
369    end
370
371    if nested == 0 then
372        if list then
373            check()
374        end
375    elseif nested == 1 then
376        local id = getid(box)
377        if id == vlist_code then
378            if listisnumbered(list) then
379                -- ok
380            else
381                list = findnumberedlist(list)
382            end
383        else -- hlist
384            list = findnumberedlist(list)
385        end
386        if list then
387            check()
388        end
389    elseif nested == 2 then
390        list = findcolumngap(list)
391        -- we assume we have a vlist
392        if not list then
393            return
394        end
395        for n in nextvlist, list do
396            local p = properties[n]
397            if p and p.columngap then
398                if trace_numbers then
399                    report_lines("found column gap %a",p.columngap)
400                end
401                list = getlist(n)
402                if list then
403                    check()
404                end
405            end
406        end
407    else
408        -- bad call
409    end
410end
411
412-- column attribute
413
414implement {
415    name      = "addlinenumbers",
416    actions   = boxed.addlinenumbers,
417    arguments = "3 integers",
418}
419