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