strc-pag.lua /size: 14 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['strc-pag'] = {
2    version   = 1.001,
3    comment   = "companion to strc-pag.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 allocate, mark = utilities.storage.allocate, utilities.storage.mark
10
11local trace_pages         = false  trackers.register("structures.pages", function(v) trace_pages = v end)
12
13local report_pages        = logs.reporter("structure","pages")
14
15local structures          = structures
16
17local helpers             = structures.helpers
18local sections            = structures.sections
19local pages               = structures.pages
20local sets                = structures.sets
21local counters            = structures.counters
22
23local counterdata         = counters.data
24
25local variables           = interfaces.variables
26local context             = context
27local commands            = commands
28local implement           = interfaces.implement
29
30local processors          = typesetters.processors
31local applyprocessor      = processors.apply
32local startapplyprocessor = processors.startapply
33local stopapplyprocessor  = processors.stopapply
34
35local texsetcount         = tex.setcount
36local texgetcount         = tex.getcount
37
38local texconditionals     = tex.conditionals
39
40local ctx_convertnumber   = context.convertnumber
41
42-- storage
43
44local collected, tobesaved = allocate(), allocate()
45
46pages.collected = collected
47pages.tobesaved = tobesaved
48pages.nofpages  = 0
49
50-- utilitydata.structures.counters.collected.realpage[1]
51
52local function initializer()
53    collected = pages.collected
54    tobesaved = pages.tobesaved
55    -- tricky, with pageinjection we can have holes
56 -- pages.nofpages = #collected
57 -- pages.nofpages = table.count(collected) -- could be a helper
58    local n = 0
59    for k in next, collected do
60        if k > n then
61            n = k
62        end
63    end
64    pages.nofpages = n
65end
66
67job.register('structures.pages.collected', tobesaved, initializer)
68
69local specification = { } -- to be checked
70
71function pages.save(prefixdata,numberdata,extradata)
72    local realpage = texgetcount("realpageno")
73    local userpage = texgetcount("userpageno")
74    if realpage > 0 then
75        if trace_pages then
76            report_pages("saving page %s.%s",realpage,userpage)
77        end
78        local viewerprefix = extradata.viewerprefix
79        local state        = extradata.state
80        local label        = extradata.label
81        local data = {
82            number       = userpage,
83            viewerprefix = viewerprefix ~= "" and viewerprefix or nil,
84            state        = state ~= "" and state or nil, -- maybe let "start" be default
85            block        = sections.currentblock(),
86            prefixdata   = prefixdata and helpers.simplify(prefixdata),
87            numberdata   = numberdata and helpers.simplify(numberdata),
88            marked       = pages.markedlist(realpage), -- not yet defined
89            label        = label and label ~= "" and label,
90        }
91        tobesaved[realpage] = data
92        if not collected[realpage] then
93            collected[realpage] = data
94        end
95    elseif trace_pages then
96        report_pages("not saving page %s.%s",realpage,userpage)
97    end
98end
99
100-- We can set the pagenumber but as it only get incremented in the page
101-- builder we have to make sure it starts at least at 1.
102
103function counters.specials.userpage()
104    local r = texgetcount("realpageno")
105    if r > 0 then
106        local t = tobesaved[r]
107        if t then
108            t.number = texgetcount("userpageno")
109            if trace_pages then
110                report_pages("forcing pagenumber of realpage %s to %s",r,t.number)
111            end
112            return
113        end
114    end
115    local u = texgetcount("userpageno")
116    if u == 0 then
117        if trace_pages then
118            report_pages("forcing pagenumber of realpage %s to %s (probably a bug)",r,1)
119        end
120        counters.setvalue("userpage",1)
121        texsetcount("userpageno",1) -- not global ?
122    end
123end
124
125-- local f_convert = string.formatters["\\convertnumber{%s}{%s}"]
126--
127-- local function convertnumber(str,n)
128--     return f_convert(str or "numbers",n)
129-- end
130
131function pages.number(realdata,pagespec)
132    local userpage      = realdata.number
133    local block         = realdata.block or "" -- sections.currentblock()
134    local numberspec    = realdata.numberdata
135    local conversionset = (pagespec and pagespec.conversionset ~= "" and pagespec.conversionset) or (numberspec and numberspec.conversionset ~= "" and numberspec.conversionset) or ""
136    local conversion    = (pagespec and pagespec.conversion    ~= "" and pagespec.conversion   ) or (numberspec and numberspec.conversion    ~= "" and numberspec.conversion   ) or ""
137    local starter       = (pagespec and pagespec.starter       ~= "" and pagespec.starter      ) or (numberspec and numberspec.starter       ~= "" and numberspec.starter      ) or ""
138    local stopper       = (pagespec and pagespec.stopper       ~= "" and pagespec.stopper      ) or (numberspec and numberspec.stopper       ~= "" and numberspec.stopper      ) or ""
139    if starter ~= "" then
140        applyprocessor(starter)
141    end
142    if conversion ~= "" then
143        ctx_convertnumber(conversion,userpage)
144    else
145        if conversionset == "" then conversionset = "default" end
146        local theconversion = sets.get("structure:conversions",block,conversionset,1,"numbers") -- to be checked: 1
147        local data = startapplyprocessor(theconversion)
148        ctx_convertnumber(data or "number",userpage)
149        stopapplyprocessor()
150    end
151    if stopper ~= "" then
152        applyprocessor(stopper)
153    end
154end
155
156-- (pagespec.prefix == yes|unset) and (pages.prefix == yes) => prefix
157
158function pages.analyze(entry,pagespecification)
159    -- safeguard
160    if not entry then
161        return false, false, "no entry"
162    end
163    local references = entry.references
164    if not references then
165        return false, false, "no references"
166    end
167    local pagedata = references.pagedata -- sometimes resolved (external)
168    if not pagedata then
169        local realpage = references.realpage
170        if realpage then
171            pagedata = collected[realpage]
172        else
173            return false, false, "no realpage"
174        end
175    end
176    if not pagedata then
177        return false, false, "no pagedata"
178    end
179    local sectiondata = references.sectiondata -- sometimes resolved (external)
180    if not sectiondata then
181        local section = references.section
182        if section then
183            sectiondata = sections.collected[section]
184        else
185            return pagedata, false, "no section"
186        end
187    end
188    if not sectiondata then
189        return pagedata, false, "no sectiondata"
190    end
191    local v_no = variables.no
192    -- local preferences
193    if pagespecification and pagespecification.prefix == v_no then
194        return pagedata, false, "current spec blocks prefix"
195    end
196    -- stored preferences
197 -- if entry.prefix == v_no then
198 --     return pagedata, false, "entry blocks prefix"
199 -- end
200    -- stored page state
201    pagespecification = pagedata.prefixdata
202    if pagespecification and pagespecification.prefix == v_no then
203        return pagedata, false, "pagedata blocks prefix"
204    end
205    -- final verdict
206    return pagedata, sectiondata, "okay"
207end
208
209function helpers.page(data,pagespec)
210    if data then
211        local pagedata = pages.analyze(data,pagespec)
212        if pagedata then
213            pages.number(pagedata,pagespec)
214        end
215    end
216end
217
218function helpers.prefixpage(data,prefixspec,pagespec)
219    if data then
220        local pagedata, prefixdata, e = pages.analyze(data,pagespec)
221        if pagedata then
222            if prefixdata then
223                sections.typesetnumber(prefixdata,"prefix",prefixspec or false,prefixdata or false,pagedata.prefixdata or false)
224            end
225            pages.number(pagedata,pagespec)
226        end
227    end
228end
229
230function helpers.prefixlastpage(data,prefixspec,pagespec)
231    if data then
232        local r  = data.references
233        local ls = r.section
234        local lr = r.realpage
235        r.section  = r.lastsection or r.section
236        r.realpage = r.lastrealpage or r.realpage
237        helpers.prefixpage(data,prefixspec,pagespec)
238        r.section, r.realpage = ls, lr
239    end
240end
241
242--
243
244function helpers.analyze(entry,specification)
245    -- safeguard
246    if not entry then
247        return false, false, "no entry"
248    end
249    local yes = variables.yes
250    local no  = variables.no
251    -- section data
252    local references = entry.references
253    if not references then
254        return entry, false, "no references"
255    end
256    local section = references.section
257    if not section then
258        return entry, false, "no section"
259    end
260    local sectiondata = references.sectiondata or sections.collected[references.section] -- so we use an already resolved external one
261    if not sectiondata then
262        return entry, false, "no section data"
263    end
264    -- local preferences
265    if specification and specification.prefix == no then
266        return entry, false, "current spec blocks prefix"
267    end
268    -- stored preferences (not used)
269    local prefixdata = entry.prefixdata
270    if prefixdata and prefixdata.prefix == no then
271        return entry, false, "entry blocks prefix"
272    end
273    -- final verdict
274    return entry, sectiondata, "okay"
275end
276
277function helpers.prefix(data,prefixspec,nosuffix) -- not only page
278    if data then
279        local _, prefixdata, status = helpers.analyze(data,prefixspec)
280        if prefixdata then
281            if nosuffix and prefixspec then
282                local connector = prefixspec.connector
283                prefixspec.connector = nil
284                sections.typesetnumber(prefixdata,"prefix",prefixspec or false,data.prefixdata or false,prefixdata or false)
285                prefixspec.connector = connector
286            else
287                sections.typesetnumber(prefixdata,"prefix",prefixspec or false,data.prefixdata or false,prefixdata or false)
288            end
289        end
290    end
291end
292
293function helpers.pageofinternal(n,prefixspec,pagespec)
294    local data = structures.references.internals[n]
295    if not data then
296        -- error
297    elseif prefixspec then
298        helpers.prefixpage(data,prefixspec,pagespec)
299    else
300        helpers.prefix(data,pagespec)
301    end
302end
303
304function pages.is_odd(n)
305    n = n or texgetcount("realpageno")
306    if texgetcount("pagenoshift") % 2 == 0 then
307        return n % 2 ~= 0
308    else
309        return n % 2 == 0
310    end
311end
312
313function pages.on_right(n)
314    local pagemode = texgetcount("pageduplexmode")
315    if pagemode == 2 or pagemode == 1 then
316        n = n or texgetcount("realpageno")
317        if texgetcount("pagenoshift") % 2 == 0 then
318            return n % 2 ~= 0
319        else
320            return n % 2 == 0
321        end
322    else
323        return true
324    end
325end
326
327function pages.in_body(n)
328    return texgetcount("pagebodymode") > 0
329end
330
331function pages.fraction(n)
332    local lastpage = texgetcount("lastpageno") -- can be cached
333    return lastpage > 1 and (texgetcount("realpageno")-1)/(lastpage-1) or 1
334end
335
336-- move to strc-pag.lua
337
338function counters.analyze(name,counterspecification)
339    local cd = counterdata[name]
340    -- safeguard
341    if not cd then
342        return false, false, "no counter data"
343    end
344    -- section data
345    local sectiondata = sections.current()
346    if not sectiondata then
347        return cd, false, "not in section"
348    end
349    local references = sectiondata.references
350    if not references then
351        return cd, false, "no references"
352    end
353    local section = references.section
354    if not section then
355        return cd, false, "no section"
356    end
357    sectiondata = sections.collected[references.section]
358    if not sectiondata then
359        return cd, false, "no section data"
360    end
361    -- local preferences
362    local no = variables.no
363    if counterspecification and counterspecification.prefix == no then
364        return cd, false, "current spec blocks prefix"
365    end
366    -- stored preferences (not used)
367    if cd.prefix == no then
368        return cd, false, "entry blocks prefix"
369    end
370    -- sectioning
371    -- if sectiondata.prefix == no then
372    --     return false, false, "sectiondata blocks prefix"
373    -- end
374    -- final verdict
375    return cd, sectiondata, "okay"
376end
377
378function sections.prefixedconverted(name,prefixspec,numberspec)
379    local cd, prefixdata, result = counters.analyze(name,prefixspec)
380    if cd then
381        if prefixdata then
382            sections.typesetnumber(prefixdata,"prefix",prefixspec or false,cd or false)
383        end
384        counters.converted(name,numberspec)
385    end
386end
387
388function pages.getlabels()
389    local pages  = structures.pages.tobesaved
390    local labels = false
391    for i=1,#pages do
392        local p = pages[i]
393        if p then
394            local label = p.label
395            if label and label ~= "" then
396                if not labels then
397                    labels = { }
398                end
399                local l = labels[label]
400                local t = type(l)
401                if t == "number" then
402                    l = { l, i }
403                elseif t == "table" then
404                    l[#l+1] = i
405                else
406                    l = i
407                end
408                labels[label] = l
409            end
410        end
411    end
412    return labels
413end
414
415--
416
417implement {
418    name      = "savepagedata",
419    actions   = pages.save,
420    arguments = {
421        {
422            { "prefix" },
423            { "separatorset" },
424            { "conversionset" },
425            { "conversion" },
426            { "set" },
427            { "segments" },
428            { "connector" },
429        },
430        {
431            { "conversionset" },
432            { "conversion" },
433            { "starter" },
434            { "stopper" },
435        },
436        {
437            { "viewerprefix" },
438            { "state" },
439            { "label" },
440        }
441    }
442}
443
444implement { -- weird place
445    name      = "prefixedconverted",
446    actions   = sections.prefixedconverted,
447    arguments = {
448        "string",
449        {
450            { "prefix" },
451            { "separatorset" },
452            { "conversionset" },
453            { "conversion" },
454            { "starter" },
455            { "stopper" },
456            { "set" },
457            { "segments" },
458            { "connector" },
459        },
460        {
461            { "order" },
462            { "separatorset" },
463            { "conversionset" },
464            { "conversion" },
465            { "starter" },
466            { "stopper" },
467            { "segments" },
468            { "type" },
469            { "criterium" },
470        }
471    }
472}
473
474interfaces.implement {
475    name      = "pageofinternal",
476    arguments = "integer",
477    actions   = helpers.pageofinternal,
478}
479