1if not modules then modules = { } end modules ['strc-not'] = {
2 version = 1.001,
3 comment = "companion to strc-not.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 format = string.format
10local next, tonumber = next, tonumber
11local sortedhash = table.sortedhash
12
13local trace_notes = false trackers.register("structures.notes", function(v) trace_notes = v end)
14local trace_references = false trackers.register("structures.notes.references", function(v) trace_references = v end)
15
16local report_notes = logs.reporter("structure","notes")
17
18local structures = structures
19local helpers = structures.helpers
20local lists = structures.lists
21local sections = structures.sections
22local counters = structures.counters
23local notes = structures.notes
24local references = structures.references
25local counterspecials = counters.specials
26
27local texgetcount = tex.getcount
28local texgetglue = tex.getglue
29local texgetbox = tex.getbox
30local getinsertmultiplier = tex.getinsertmultiplier
31local getmark = tex.getmark
32
33local nuts = nodes.nuts
34local tonut = nodes.tonut
35local tonode = nuts.tonode
36
37local setlink = nuts.setlink
38local gettail = nuts.tail
39local vpack = nuts.vpack
40
41local newgluespec = nuts.pool.gluespec
42
43
44
45notes.states = notes.states or { }
46lists.enhancers = lists.enhancers or { }
47notes.numbers = notes.numbers or { }
48
49storage.register("structures/notes/states", notes.states, "structures.notes.states")
50storage.register("structures/notes/numbers", notes.numbers, "structures.notes.numbers")
51
52local notestates = notes.states
53local notedata = table.setmetatableindex("table")
54
55local context = context
56local commands = commands
57
58local implement = interfaces.implement
59
60local v_page <const> = interfaces.variables.page
61
62
63
64local function store(tag,n)
65
66 if not counterspecials[tag] then
67 counterspecials[tag] = function(tag)
68 context.doresetlinenotecompression(tag)
69 end
70 end
71
72 local nd = notedata[tag]
73 local nnd = #nd + 1
74 nd[nnd] = n
75 local state = notestates[tag]
76 if not state then
77 report_notes("unknown state for %a",tag)
78 elseif state.kind ~= "insert" then
79 if trace_notes then
80 report_notes("storing %a with state %a as %a",tag,state.kind,nnd)
81 end
82 state.start = state.start or nnd
83 end
84 return nnd
85end
86
87notes.store = store
88
89implement {
90 name = "storenote",
91 actions = { store, context },
92 arguments = { "string", "integer" }
93}
94
95local function get(tag,n)
96 local nd = notedata[tag]
97 if not n then
98 n = #nd
99 end
100 nd = nd[n]
101 if nd then
102 if trace_notes then
103 report_notes("getting note %a of %a with listindex %a",n,tag,nd)
104 end
105
106 local newdata = lists.cached[nd]
107 return newdata
108 end
109end
110
111local function getn(tag)
112 return #notedata[tag]
113end
114
115notes.get = get
116notes.getn = getn
117
118
119
120local function listindex(tag,n)
121 local ndt = notedata[tag]
122 return ndt and ndt[n]
123end
124
125notes.listindex = listindex
126
127implement {
128 name = "notelistindex",
129 actions = { listindex, context },
130 arguments = { "string", "integer" }
131}
132
133local function setstate(tag,newkind)
134 local state = notestates[tag]
135 if trace_notes then
136 report_notes("setting state of %a from %s to %s",tag,(state and state.kind) or "unset",newkind)
137 end
138 if not state then
139 state = {
140 kind = newkind
141 }
142 notestates[tag] = state
143 elseif newkind == "insert" then
144 if not state.start then
145 state.kind = newkind
146 end
147 else
148
149
150 state.kind = newkind
151
152 end
153
154 return state
155end
156
157local function getstate(tag)
158 local state = notestates[tag]
159 return state and state.kind or "unknown"
160end
161
162notes.setstate = setstate
163notes.getstate = getstate
164
165implement {
166 name = "setnotestate",
167 actions = setstate,
168 arguments = "2 strings",
169}
170
171implement {
172 name = "getnotestate",
173 actions = { getstate, context },
174 arguments = "string"
175}
176
177function notes.define(tag,kind,number)
178 local state = setstate(tag,kind)
179 notes.numbers[number] = state
180 state.number = number
181end
182
183implement {
184 name = "definenote",
185 actions = notes.define,
186 arguments = { "string", "string", "integer" }
187}
188
189function notes.save(tag,newkind)
190 local state = notestates[tag]
191 if state and not state.saved then
192 if trace_notes then
193 report_notes("saving state of %a, old: %a, new %a",tag,state.kind,newkind or state.kind)
194 end
195 state.saveddata = notedata[tag]
196 state.savedkind = state.kind
197 state.kind = newkind or state.kind
198 state.saved = true
199 notedata[tag] = { }
200 end
201end
202
203function notes.restore(tag,forcedstate)
204 local state = notestates[tag]
205 if state and state.saved then
206 if trace_notes then
207 report_notes("restoring state of %a, old: %a, new: %a",tag,state.kind,state.savedkind)
208 end
209 notedata[tag] = state.saveddata
210 state.kind = forcedstate or state.savedkind
211 state.saveddata = nil
212 state.saved = false
213 end
214end
215
216implement { name = "savenote", actions = notes.save, arguments = "2 strings" }
217implement { name = "restorenote", actions = notes.restore, arguments = "2 strings" }
218
219local function hascontent(tag)
220 local ok = notestates[tag]
221 if ok then
222 if ok.kind == "insert" then
223 ok = texgetbox(ok.number)
224 if ok then
225 ok = tbs.list
226 ok = lst and lst.next
227 end
228 else
229 ok = ok.start
230 end
231 end
232 return ok and true or false
233end
234
235notes.hascontent = hascontent
236
237implement {
238 name = "doifnotecontent",
239 actions = { hascontent, commands.doif },
240 arguments = "string",
241}
242
243local function internal(tag,n)
244 local nd = get(tag,n)
245 if nd then
246 local r = nd.references
247 if r then
248 local i = r.internal
249 return i and references.internals[i]
250 end
251 end
252 return nil
253end
254
255local function ordered(kind,name,n)
256 local o = lists.ordered[kind]
257 o = o and o[name]
258 return o and o[n]
259end
260
261notes.internal = internal
262notes.ordered = ordered
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277local function onsamepageasprevious(tag)
278 local n = getn(tag,n)
279 local current = get(tag,n)
280 if not current then
281 return false
282 end
283 local cr = current.references
284 if not cr then
285 return false
286 end
287 local previous = get(tag,n-1)
288 if not previous then
289 return false
290 end
291 local pr = previous.references
292 if not pr then
293 return false
294 end
295 return cr.realpage == pr.realpage
296end
297
298notes.doifonsamepageasprevious = onsamepageasprevious
299
300implement {
301 name = "doifnoteonsamepageasprevious",
302 actions = { onsamepageasprevious, commands.doifelse },
303 arguments = "string",
304}
305
306function notes.checkpagechange(tag)
307 local nd = notedata[tag]
308 if nd then
309 local current = ordered("note",tag,#nd)
310 local nextone = ordered("note",tag,#nd+1)
311 if nextone then
312
313 if nextone.pagenumber.number > current.pagenumber.number then
314 counters.reset(tag)
315 end
316 elseif current then
317
318 if texgetcount("realpageno") > current.pagenumber.number then
319 counters.reset(tag)
320 end
321 end
322 end
323end
324
325function notes.postpone()
326 if trace_notes then
327 report_notes("postponing all insert notes")
328 end
329 for tag, state in next, notestates do
330 if state.kind ~= "store" then
331 setstate(tag,"postpone")
332 end
333 end
334end
335
336implement {
337 name = "postponenotes",
338 actions = notes.postpone
339}
340
341local function getinternal(tag,n)
342 local li = internal(tag,n)
343 if li then
344 local references = li.references
345 if references then
346 return references.internal or 0
347 end
348 end
349 return 0
350end
351
352local function getdeltapage(tag,n)
353
354 local li = internal(tag,n)
355 if li then
356 local references = li.references
357 if references then
358
359 local rymb = structures.references.collected[""]
360 local symb = rymb and rymb["*"..(references.internal or 0)]
361 local notepage = references.realpage or 0
362 local symbolpage = symb and symb.references.realpage or -1
363 if trace_references then
364 report_notes("note number %a of %a points from page %a to page %a",n,tag,symbolpage,notepage)
365 end
366 if notepage < symbolpage then
367 return 3
368 elseif notepage > symbolpage then
369 return 2
370 elseif notepage > 0 then
371 return 1
372 end
373 else
374
375
376 end
377 end
378 return 0
379end
380
381notes.getinternal = getinternal
382notes.getdeltapage = getdeltapage
383
384implement { name = "noteinternal", actions = { getinternal, context }, arguments = { "string", "integer" } }
385implement { name = "notedeltapage", actions = { getdeltapage, context }, arguments = { "string", "integer" } }
386
387local function flushnotes(tag,whatkind,how)
388 local state = notestates[tag]
389 local kind = state.kind
390 if kind == whatkind then
391 local nd = notedata[tag]
392 local ns = state.start
393 if kind == "postpone" then
394 if nd and ns then
395 if trace_notes then
396 report_notes("flushing state %a of %a from %a to %a",whatkind,tag,ns,#nd)
397 end
398 for i=ns,#nd do
399 context.handlenoteinsert(tag,i)
400 end
401 end
402 state.start = nil
403 state.kind = "insert"
404 elseif kind == "store" then
405 if nd and ns then
406 if trace_notes then
407 report_notes("flushing state %a of %a from %a to %a",whatkind,tag,ns,#nd)
408 end
409
410 for i=ns,#nd do
411
412 if how == v_page then
413 local rp = get(tag,i)
414 rp = rp and rp.references
415 rp = rp and rp.symbolpage or 0
416 if rp > texgetcount("realpageno") then
417 state.start = i
418 return
419 end
420 end
421 if i > ns then
422 context.betweennoteitself(tag)
423 end
424 context.handlenoteitself(tag,i)
425 end
426 end
427 state.start = nil
428 elseif kind == "reset" then
429 if nd and ns then
430 if trace_notes then
431 report_notes("flushing state %a of %a from %a to %a",whatkind,tag,ns,#nd)
432 end
433 end
434 state.start = nil
435 elseif trace_notes then
436 report_notes("not flushing state %a of %a",whatkind,tag)
437 end
438 elseif trace_notes then
439 report_notes("not flushing state %a of %a",whatkind,tag)
440 end
441end
442
443local function flushpostponednotes()
444 if trace_notes then
445 report_notes("flushing all postponed notes")
446 end
447 for tag, _ in next, notestates do
448 flushnotes(tag,"postpone")
449 end
450end
451
452implement {
453 name = "flushpostponednotes",
454 actions = flushpostponednotes
455}
456
457implement {
458 name = "flushnotes",
459 actions = flushnotes,
460 arguments = "3 strings",
461}
462
463function notes.resetpostponed()
464 if trace_notes then
465 report_notes("resetting all postponed notes")
466 end
467 for tag, state in next, notestates do
468 if state.kind == "postpone" then
469 state.start = nil
470 state.kind = "insert"
471 end
472 end
473end
474
475implement {
476 name = "notetitle",
477 actions = function(tag,n) lists.savedlisttitle(tag,notedata[tag][n]) end,
478 arguments = { "string", "integer" }
479}
480
481implement {
482 name = "noteprefixednumber",
483 actions = function(tag,n) lists.savedlistprefixednumber(tag,notedata[tag][n]) end,
484 arguments = { "string", "integer" }
485}
486
487function notes.internalid(tag,n)
488 local nd = get(tag,n)
489 if nd then
490 local r = nd.references
491 return r.internal
492 end
493end
494
495
496
497
498
499
500
501
502local report_insert = logs.reporter("pagebuilder","insert")
503local trace_insert = false trackers.register("pagebuilder.insert",function(v) trace_insert = v end)
504
505
506
507local function check_spacing(index,slot)
508
509 local gn, pn, mn = tex.getinsertdistance(index)
510 if not pn then pn = 0 end
511 if not mn then mn = 0 end
512 local gi, pi, mi = texgetglue(slot > 1 and "s_strc_notes_inbetween" or "s_strc_notes_before")
513 if not pi then pi = 0 end
514 if not mi then mi = 0 end
515 local gt = gn + gi
516 local pt = pn + pi
517 local mt = mn + mi
518 if trace_insert then
519 report_insert("%s %i: %p plus %p minus %p","always ",index,gn,pn,mn)
520 report_insert("%s %i: %p plus %p minus %p",slot > 1 and "inbetween" or "before ",index,gi,pi,mi)
521 report_insert("%s %i: %p plus %p minus %p","effective",index,gt,pt,mt)
522 end
523 return gt, pt, mt
524end
525
526notes.check_spacing = check_spacing
527
528
529
530local function action(index,slot)
531 local state = notes.numbers[index]
532 if state then
533 return tonode(newgluespec(check_spacing(index,slot)))
534 else
535 return tonode(newgluespec())
536 end
537end
538
539callbacks.register("insert_distance",action,"check spacing around inserts")
540
541
542
543local inserts = table.setmetatableindex("table")
544
545callback.register("balance_insert", function(n, cbk, index, data)
546 if cbk == 0 and getinsertmultiplier(index) == 0 then
547 inserts[index][data] = tonut(n.list)
548 n.list = nil
549 end
550end)
551
552function notes.flushrange(index,mark)
553 local first = tonumber(getmark("first", mark)) or 0
554 local last = tonumber(getmark("bottom",mark)) or 0
555 if first > 0 and last > 0 then
556
557 local byindex = inserts[index]
558 if byindex then
559 local head = nil
560 local tail = nil
561 for data, list in sortedhash(byindex) do
562 if data >= first and data <= last then
563 if head then
564 setlink(tail,list)
565 else
566 head = list
567 end
568 tail = gettail(list)
569 byindex[data] = nil
570 end
571 end
572 if head then
573 context(tonode(vpack(head)))
574 context.par()
575 end
576 end
577 end
578end
579
580implement {
581 name = "flushnoterange",
582
583 arguments = "2 integers",
584 actions = structures.notes.flushrange,
585}
586
587 |