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
10
11
12
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 { }
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
59
60local current_list = { }
61local cross_references = { }
62local chunksize = 250
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
74
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
86local is_display_math = nuts.is_display_math
87
88
89
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
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)
109 while n do
110 local id = getid(n)
111 if id == whatsit_code then
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)
132 e.conversion = u and u.conversion
133 r.userdata = nil
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
148 ctx_convertnumber(data.entries.conversion or "numbers",data.entries.linenumber or "0")
149end
150
151function filters.line.page(data,prefixspec,pagespec)
152 helpers.prefixpage(data,prefixspec,pagespec)
153end
154
155function filters.line.linenumber(data)
156 context(data.entries.linenumber or "0")
157end
158
159
160
161lines.boxed = { }
162local boxed = lines.boxed
163
164
165
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
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
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
256
257
258
259
260
261
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
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
292 return a > 0 and list or false
293 else
294
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
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
343
344
345
346local function findcolumngap(list)
347
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
390 elseif getheight(n) == 0 and getdepth(n) == 0 then
391
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
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
410 if is_display_math(n) then
411 check_number(n,a,skip)
412 end
413 else
414
415
416
417
418 check_number(n,a,skip)
419
420
421
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
438 else
439 list = findnumberedlist(list)
440 end
441 else
442 list = findnumberedlist(list)
443 end
444 if list then
445 check()
446 end
447 elseif nested == 2 then
448 list = findcolumngap(list)
449
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
467 end
468end
469
470
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)
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
498
499
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 |