page-lin.lua /size: 14 Kb    last modification: 2021-10-28 13:50
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
9-- experimental -> will become builders
10
11-- if there is demand for it, we can support multiple numbering streams
12-- and use more than one attibute
13
14local next, tonumber = next, tonumber
15
16local trace_numbers      = false  trackers.register("lines.numbers",  function(v) trace_numbers = v end)
17
18local report_lines       = logs.reporter("lines")
19
20local attributes         = attributes
21local nodes              = nodes
22local context            = context
23
24local implement          = interfaces.implement
25
26nodes.lines              = nodes.lines or { }
27local lines              = nodes.lines
28
29lines.data               = lines.data or { } -- start step tag
30local data               = lines.data
31local last               = #data
32
33lines.scratchbox         = lines.scratchbox or 0
34
35storage.register("lines/data", data, "nodes.lines.data")
36
37local variables          = interfaces.variables
38
39local v_next             = variables.next
40local v_page             = variables.page
41local v_no               = variables.no
42
43local properties         = nodes.properties.data
44
45local nodecodes          = nodes.nodecodes
46local listcodes          = nodes.listcodes
47
48local hlist_code         = nodecodes.hlist
49local vlist_code         = nodecodes.vlist
50local whatsit_code       = nodecodes.whatsit
51local glyph_code         = nodecodes.glyph
52
53local linelist_code      = listcodes.line
54
55local a_displaymath      = attributes.private('displaymath')
56local a_linenumber       = attributes.private('linenumber')
57local a_linereference    = attributes.private('linereference')
58----- a_verbatimline     = attributes.private('verbatimline')
59
60local current_list       = { }
61local cross_references   = { }
62local chunksize          = 250 -- not used in boxed
63
64local nuts               = nodes.nuts
65
66local getid              = nuts.getid
67local getsubtype         = nuts.getsubtype
68local getnext            = nuts.getnext
69local getattr            = nuts.getattr
70local setattr            = nuts.setattr
71local getlist            = nuts.getlist
72local getbox             = nuts.getbox
73----- getdirection       = nuts.getdirection
74----- getwidth           = nuts.getwidth
75local getheight          = nuts.getheight
76local getdepth           = nuts.getdepth
77
78local setprop            = nuts.setprop
79local getprop            = nuts.getprop
80
81local nexthlist          = nuts.traversers.hlist
82local nextvlist          = nuts.traversers.vlist
83
84local copy_node          = nuts.copy
85----- hpack_nodes        = nuts.hpack
86local is_display_math    = nuts.is_display_math
87
88----- nodepool           = nuts.pool
89----- new_kern           = nodepool.kern
90
91local ctx_convertnumber  = context.convertnumber
92local ctx_makelinenumber = context.makelinenumber
93
94local paragraphs         = typesetters.paragraphs
95local addtoline          = paragraphs.addtoline
96local checkline          = paragraphs.checkline
97local moveinline         = paragraphs.moveinline
98
99-- cross referencing
100
101function lines.number(n)
102    n = tonumber(n)
103    local cr = cross_references[n] or 0
104    cross_references[n] = nil
105    return cr
106end
107
108local function resolve(n,m) -- we can now check the 'line' flag (todo)
109    while n do
110        local id = getid(n)
111        if id == whatsit_code then -- why whatsit
112            local a = getattr(n,a_linereference)
113            if a then
114                cross_references[a] = m
115            end
116        elseif id == hlist_code or id == vlist_code then
117            resolve(getlist(n),m)
118        end
119        n = getnext(n)
120    end
121end
122
123function lines.finalize(t)
124    local getnumber = lines.number
125    for _,p in next, t do
126        for _,r in next, p do
127            local m = r.metadata
128            if m and m.kind == "line" then
129                local e = r.entries
130                local u = r.userdata
131                e.linenumber = getnumber(e.text or 0) -- we can nil e.text
132                e.conversion = u and u.conversion
133                r.userdata = nil -- hack
134            end
135        end
136    end
137end
138
139local filters = structures.references.filters
140local helpers = structures.helpers
141
142structures.references.registerfinalizer(lines.finalize)
143
144filters.line = filters.line or { }
145
146function filters.line.default(data)
147--  helpers.title(data.entries.linenumber or "?",data.metadata)
148    ctx_convertnumber(data.entries.conversion or "numbers",data.entries.linenumber or "0")
149end
150
151function filters.line.page(data,prefixspec,pagespec) -- redundant
152    helpers.prefixpage(data,prefixspec,pagespec)
153end
154
155function filters.line.linenumber(data) -- raw
156    context(data.entries.linenumber or "0")
157end
158
159-- boxed variant, todo: use number mechanism
160
161lines.boxed = { }
162local boxed = lines.boxed
163
164-- todo: cache setups, and free id no longer used
165-- use interfaces.cachesetup(t)
166
167function boxed.register(configuration)
168    last = last + 1
169    data[last] = configuration
170    if trace_numbers then
171        report_lines("registering setup %a",last)
172    end
173    return last
174end
175
176implement {
177    name      = "registerlinenumbering",
178    actions   = { boxed.register, context },
179    arguments = {
180        {
181            { "continue" },
182            { "start", "integer" },
183            { "step", "integer" },
184            { "method" },
185            { "tag" },
186        }
187    }
188}
189
190function boxed.setup(n,configuration)
191    local d = data[n]
192    if d then
193        if trace_numbers then
194            report_lines("updating setup %a",n)
195        end
196        for k,v in next, configuration do
197            d[k] = v
198        end
199    else
200        if trace_numbers then
201            report_lines("registering setup %a (br)",n)
202        end
203        data[n] = configuration
204    end
205    return n
206end
207
208implement {
209    name      = "setuplinenumbering",
210    actions   = boxed.setup,
211    arguments = {
212        "integer",
213        {
214            { "continue" },
215            { "start", "integer" },
216            { "step", "integer" },
217            { "method" },
218            { "tag" },
219        }
220    }
221}
222
223local function check_number(n,a,skip,sameline)
224    local d = data[a]
225    if d then
226        local tag      = d.tag or ""
227        local skipflag = 0
228        local s        = d.start or 1
229        current_list[#current_list+1] = { n, s }
230        if sameline then
231            skipflag = 0
232            if trace_numbers then
233                report_lines("skipping broken line number %s for setup %a: %s (%s)",#current_list,a,s,d.continue or v_no)
234            end
235        elseif not skip and s % d.step == 0 then
236            skipflag, d.start = 1, s + 1 -- (d.step or 1)
237            if trace_numbers then
238                report_lines("making number %s for setup %a: %s (%s)",#current_list,a,s,d.continue or v_no)
239            end
240        else
241            skipflag, d.start = 0, s + 1 -- (d.step or 1)
242            if trace_numbers then
243                report_lines("skipping line number %s for setup %a: %s (%s)",#current_list,a,s,d.continue or v_no)
244            end
245        end
246        local p = checkline(n)
247        if p then
248            ctx_makelinenumber(tag,skipflag,s,p.hsize,p.reverse and 1 or 0)
249        else
250            report_lines("needs checking")
251        end
252    end
253end
254
255-- print(nodes.idstostring(list))
256
257-- hlists of type line will only have an attribute when the line number attribute
258-- still set at par building time which is not always the case unless we explicitly
259-- do a par before we end the line
260
261-- todo: check for a: when <= 0 then false
262
263local function lineisnumbered(n)
264    local n = getlist(n)
265    while n do
266        local id = getid(n)
267        if id == hlist_code or id == vlist_code then
268            -- this can hit fast as we inherit anchor attributes from parent
269            local a = getattr(n,a_linenumber)
270            if a and a > 0 then
271                return a
272            end
273        elseif id == glyph_code then
274            local a = getattr(n,a_linenumber)
275            if a and a > 0 then
276                return a
277            else
278                return false
279            end
280        end
281        n = getnext(n)
282    end
283end
284
285local function listisnumbered(list)
286    if list then
287        for n, subtype in nexthlist, list do
288            if subtype == linelist_code then
289                local a = getattr(n,a_linenumber)
290                if a then
291                    -- a quick test for lines (only valid when \par before \stoplinenumbering)
292                    return a > 0 and list or false
293                else
294                    -- a bit slower one, assuming that we have normalized and anchored
295                    if lineisnumbered(n) then
296                        return list
297                    end
298                end
299            end
300        end
301    end
302end
303
304local function findnumberedlist(list)
305    -- we assume wrapped boxes, only one with numbers
306    local n = list
307    while n do
308        local id = getid(n)
309        if id == hlist_code then
310            if getsubtype(n) == linelist_code then
311                local a = getattr(n,a_linenumber)
312                if a then
313                    return a > 0 and list
314                end
315                return
316            else
317                local list = getlist(n)
318                if lineisnumbered(list) then
319                    return n
320                end
321                local okay = findnumberedlist(list)
322                if okay then
323                    return okay
324                end
325            end
326        elseif id == vlist_code then
327            local list = getlist(n)
328            if listisnumbered(list) then
329                return list
330            end
331            local okay = findnumberedlist(list)
332            if okay then
333                return okay
334            end
335        elseif id == glyph_code then
336            return
337        end
338        n = getnext(n)
339    end
340end
341
342-- reset ranges per page
343-- store first and last per page
344-- maybe just set marks directly
345
346local function findcolumngap(list)
347    -- we assume wrapped boxes, only one with numbers
348    local n = list
349    while n do
350        local id = getid(n)
351        if id == hlist_code or id == vlist_code then
352            local p = properties[n]
353            if p and p.columngap then
354                if trace_numbers then
355                    report_lines("first column gap %a",p.columngap)
356                end
357                return n
358            else
359                local list = getlist(n)
360                if list then
361                    local okay = findcolumngap(list)
362                    if okay then
363                        return okay
364                    end
365                end
366            end
367        end
368        n = getnext(n)
369    end
370end
371
372function boxed.stage_one(n,nested)
373    current_list = { }
374    local box = getbox(n)
375    if not box then
376        return
377    end
378    local list = getlist(box)
379    if not list then
380        return
381    end
382    local last_a = nil
383    local last_v = -1
384    local skip   = false
385
386    local function check()
387        for n, subtype in nexthlist, list do
388            if subtype ~= linelist_code then
389                -- go on
390            elseif getheight(n) == 0 and getdepth(n) == 0 then
391                -- skip funny hlists -- todo: check line subtype
392            else
393                local a = lineisnumbered(n)
394                if a then
395                    if last_a ~= a then
396                        local da = data[a]
397                        local ma = da.method
398                        if ma == v_next then
399                            skip = true
400                        elseif ma == v_page then
401                            da.start = 1 -- eventually we will have a normal counter
402                        end
403                        last_a = a
404                        if trace_numbers then
405                            report_lines("starting line number range %s: start %s, continue %s",a,da.start,da.continue or v_no)
406                        end
407                    end
408                    if getattr(n,a_displaymath) then
409                        -- this probably needs to be adapted !
410                        if is_display_math(n) then
411                            check_number(n,a,skip)
412                        end
413                    else
414                     -- -- we now prevent nesting anyway .. maybe later we need to check again
415                     -- local v = getattr(list,a_verbatimline)
416                     -- if not v or v ~= last_v then
417                     --     last_v = v
418                            check_number(n,a,skip)
419                     -- else
420                     --     check_number(n,a,skip,true)
421                     -- end
422                    end
423                    skip = false
424                end
425            end
426        end
427    end
428
429    if nested == 0 then
430        if list then
431            check()
432        end
433    elseif nested == 1 then
434        local id = getid(box)
435        if id == vlist_code then
436            if listisnumbered(list) then
437                -- ok
438            else
439                list = findnumberedlist(list)
440            end
441        else -- hlist
442            list = findnumberedlist(list)
443        end
444        if list then
445            check()
446        end
447    elseif nested == 2 then
448        list = findcolumngap(list)
449        -- we assume we have a vlist
450        if not list then
451            return
452        end
453        for n in nextvlist, list do
454            local p = properties[n]
455            if p and p.columngap then
456                if trace_numbers then
457                    report_lines("found column gap %a",p.columngap)
458                end
459                list = getlist(n)
460                if list then
461                    check()
462                end
463            end
464        end
465    else
466        -- bad call
467    end
468end
469
470-- column attribute
471
472function boxed.stage_two(n,m)
473    if #current_list > 0 then
474        m = m or lines.scratchbox
475        local t  = { }
476        local tn = 0
477        for l in nexthlist, getlist(getbox(m)) do
478            tn = tn + 1
479            t[tn] = copy_node(l) -- use take_box instead
480        end
481        for i=1,#current_list do
482            local li = current_list[i]
483            local n  = li[1]
484            local m  = li[2]
485            local ti = t[i]
486            if ti then
487                addtoline(n,ti)
488                resolve(n,m)
489            else
490                report_lines("error in linenumbering (1)")
491                return
492            end
493       end
494    end
495end
496
497-- function boxed.stage_zero(n) -- not used
498--     return identify(getlist(getbox(n)))
499-- end
500
501implement {
502    name      = "linenumbersstageone",
503    actions   = boxed.stage_one,
504    arguments = { "integer", "integer" }
505}
506
507implement {
508    name      = "linenumbersstagetwo",
509    actions   = boxed.stage_two,
510    arguments = { "integer", "integer" }
511}
512