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