strc-pag.lua /size: 13 Kb    last modification: 2023-12-21 09:44
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 data = {
81            number       = userpage,
82            viewerprefix = viewerprefix ~= "" and viewerprefix or nil,
83            state        = state ~= "" and state or nil, -- maybe let "start" be default
84            block        = sections.currentblock(),
85            prefixdata   = prefixdata and helpers.simplify(prefixdata),
86            numberdata   = numberdata and helpers.simplify(numberdata),
87            marked       = pages.markedlist(realpage), -- not yet defined
88        }
89        tobesaved[realpage] = data
90        if not collected[realpage] then
91            collected[realpage] = data
92        end
93    elseif trace_pages then
94        report_pages("not saving page %s.%s",realpage,userpage)
95    end
96end
97
98-- We can set the pagenumber but as it only get incremented in the page
99-- builder we have to make sure it starts at least at 1.
100
101function counters.specials.userpage()
102    local r = texgetcount("realpageno")
103    if r > 0 then
104        local t = tobesaved[r]
105        if t then
106            t.number = texgetcount("userpageno")
107            if trace_pages then
108                report_pages("forcing pagenumber of realpage %s to %s",r,t.number)
109            end
110            return
111        end
112    end
113    local u = texgetcount("userpageno")
114    if u == 0 then
115        if trace_pages then
116            report_pages("forcing pagenumber of realpage %s to %s (probably a bug)",r,1)
117        end
118        counters.setvalue("userpage",1)
119        texsetcount("userpageno",1) -- not global ?
120    end
121end
122
123-- local f_convert = string.formatters["\\convertnumber{%s}{%s}"]
124--
125-- local function convertnumber(str,n)
126--     return f_convert(str or "numbers",n)
127-- end
128
129function pages.number(realdata,pagespec)
130    local userpage      = realdata.number
131    local block         = realdata.block or "" -- sections.currentblock()
132    local numberspec    = realdata.numberdata
133    local conversionset = (pagespec and pagespec.conversionset ~= "" and pagespec.conversionset) or (numberspec and numberspec.conversionset ~= "" and numberspec.conversionset) or ""
134    local conversion    = (pagespec and pagespec.conversion    ~= "" and pagespec.conversion   ) or (numberspec and numberspec.conversion    ~= "" and numberspec.conversion   ) or ""
135    local starter       = (pagespec and pagespec.starter       ~= "" and pagespec.starter      ) or (numberspec and numberspec.starter       ~= "" and numberspec.starter      ) or ""
136    local stopper       = (pagespec and pagespec.stopper       ~= "" and pagespec.stopper      ) or (numberspec and numberspec.stopper       ~= "" and numberspec.stopper      ) or ""
137    if starter ~= "" then
138        applyprocessor(starter)
139    end
140    if conversion ~= "" then
141        ctx_convertnumber(conversion,userpage)
142    else
143        if conversionset == "" then conversionset = "default" end
144        local theconversion = sets.get("structure:conversions",block,conversionset,1,"numbers") -- to be checked: 1
145        local data = startapplyprocessor(theconversion)
146        ctx_convertnumber(data or "number",userpage)
147        stopapplyprocessor()
148    end
149    if stopper ~= "" then
150        applyprocessors(stopper)
151    end
152end
153
154-- (pagespec.prefix == yes|unset) and (pages.prefix == yes) => prefix
155
156function pages.analyze(entry,pagespecification)
157    -- safeguard
158    if not entry then
159        return false, false, "no entry"
160    end
161    local references = entry.references
162    if not references then
163        return false, false, "no references"
164    end
165    local pagedata = references.pagedata -- sometimes resolved (external)
166    if not pagedata then
167        local realpage = references.realpage
168        if realpage then
169            pagedata = collected[realpage]
170        else
171            return false, false, "no realpage"
172        end
173    end
174    if not pagedata then
175        return false, false, "no pagedata"
176    end
177    local sectiondata = references.sectiondata -- sometimes resolved (external)
178    if not sectiondata then
179        local section = references.section
180        if section then
181            sectiondata = sections.collected[section]
182        else
183            return pagedata, false, "no section"
184        end
185    end
186    if not sectiondata then
187        return pagedata, false, "no sectiondata"
188    end
189    local v_no = variables.no
190    -- local preferences
191    if pagespecification and pagespecification.prefix == v_no then
192        return pagedata, false, "current spec blocks prefix"
193    end
194    -- stored preferences
195 -- if entry.prefix == v_no then
196 --     return pagedata, false, "entry blocks prefix"
197 -- end
198    -- stored page state
199    pagespecification = pagedata.prefixdata
200    if pagespecification and pagespecification.prefix == v_no then
201        return pagedata, false, "pagedata blocks prefix"
202    end
203    -- final verdict
204    return pagedata, sectiondata, "okay"
205end
206
207function helpers.page(data,pagespec)
208    if data then
209        local pagedata = pages.analyze(data,pagespec)
210        if pagedata then
211            pages.number(pagedata,pagespec)
212        end
213    end
214end
215
216function helpers.prefixpage(data,prefixspec,pagespec)
217    if data then
218        local pagedata, prefixdata, e = pages.analyze(data,pagespec)
219        if pagedata then
220            if prefixdata then
221                sections.typesetnumber(prefixdata,"prefix",prefixspec or false,prefixdata or false,pagedata.prefixdata or false)
222            end
223            pages.number(pagedata,pagespec)
224        end
225    end
226end
227
228function helpers.prefixlastpage(data,prefixspec,pagespec)
229    if data then
230        local r  = data.references
231        local ls = r.section
232        local lr = r.realpage
233        r.section  = r.lastsection or r.section
234        r.realpage = r.lastrealpage or r.realpage
235        helpers.prefixpage(data,prefixspec,pagespec)
236        r.section, r.realpage = ls, lr
237    end
238end
239
240--
241
242function helpers.analyze(entry,specification)
243    -- safeguard
244    if not entry then
245        return false, false, "no entry"
246    end
247    local yes = variables.yes
248    local no  = variables.no
249    -- section data
250    local references = entry.references
251    if not references then
252        return entry, false, "no references"
253    end
254    local section = references.section
255    if not section then
256        return entry, false, "no section"
257    end
258    local sectiondata = references.sectiondata or sections.collected[references.section] -- so we use an already resolved external one
259    if not sectiondata then
260        return entry, false, "no section data"
261    end
262    -- local preferences
263    if specification and specification.prefix == no then
264        return entry, false, "current spec blocks prefix"
265    end
266    -- stored preferences (not used)
267    local prefixdata = entry.prefixdata
268    if prefixdata and prefixdata.prefix == no then
269        return entry, false, "entry blocks prefix"
270    end
271    -- final verdict
272    return entry, sectiondata, "okay"
273end
274
275function helpers.prefix(data,prefixspec,nosuffix) -- not only page
276    if data then
277        local _, prefixdata, status = helpers.analyze(data,prefixspec)
278        if prefixdata then
279            if nosuffix and prefixspec then
280                local connector = prefixspec.connector
281                prefixspec.connector = nil
282                sections.typesetnumber(prefixdata,"prefix",prefixspec or false,data.prefixdata or false,prefixdata or false)
283                prefixspec.connector = connector
284            else
285                sections.typesetnumber(prefixdata,"prefix",prefixspec or false,data.prefixdata or false,prefixdata or false)
286            end
287        end
288    end
289end
290
291function helpers.pageofinternal(n,prefixspec,pagespec)
292    local data = structures.references.internals[n]
293    if not data then
294        -- error
295    elseif prefixspec then
296        helpers.prefixpage(data,prefixspec,pagespec)
297    else
298        helpers.prefix(data,pagespec)
299    end
300end
301
302function pages.is_odd(n)
303    n = n or texgetcount("realpageno")
304    if texgetcount("pagenoshift") % 2 == 0 then
305        return n % 2 ~= 0
306    else
307        return n % 2 == 0
308    end
309end
310
311function pages.on_right(n)
312    local pagemode = texgetcount("pageduplexmode")
313    if pagemode == 2 or pagemode == 1 then
314        n = n or texgetcount("realpageno")
315        if texgetcount("pagenoshift") % 2 == 0 then
316            return n % 2 ~= 0
317        else
318            return n % 2 == 0
319        end
320    else
321        return true
322    end
323end
324
325function pages.in_body(n)
326    return texgetcount("pagebodymode") > 0
327end
328
329function pages.fraction(n)
330    local lastpage = texgetcount("lastpageno") -- can be cached
331    return lastpage > 1 and (texgetcount("realpageno")-1)/(lastpage-1) or 1
332end
333
334-- move to strc-pag.lua
335
336function counters.analyze(name,counterspecification)
337    local cd = counterdata[name]
338    -- safeguard
339    if not cd then
340        return false, false, "no counter data"
341    end
342    -- section data
343    local sectiondata = sections.current()
344    if not sectiondata then
345        return cd, false, "not in section"
346    end
347    local references = sectiondata.references
348    if not references then
349        return cd, false, "no references"
350    end
351    local section = references.section
352    if not section then
353        return cd, false, "no section"
354    end
355    sectiondata = sections.collected[references.section]
356    if not sectiondata then
357        return cd, false, "no section data"
358    end
359    -- local preferences
360    local no = variables.no
361    if counterspecification and counterspecification.prefix == no then
362        return cd, false, "current spec blocks prefix"
363    end
364    -- stored preferences (not used)
365    if cd.prefix == no then
366        return cd, false, "entry blocks prefix"
367    end
368    -- sectioning
369    -- if sectiondata.prefix == no then
370    --     return false, false, "sectiondata blocks prefix"
371    -- end
372    -- final verdict
373    return cd, sectiondata, "okay"
374end
375
376function sections.prefixedconverted(name,prefixspec,numberspec)
377    local cd, prefixdata, result = counters.analyze(name,prefixspec)
378    if cd then
379        if prefixdata then
380            sections.typesetnumber(prefixdata,"prefix",prefixspec or false,cd or false)
381        end
382        counters.converted(name,numberspec)
383    end
384end
385
386--
387
388implement {
389    name      = "savepagedata",
390    actions   = pages.save,
391    arguments = {
392        {
393            { "prefix" },
394            { "separatorset" },
395            { "conversionset" },
396            { "conversion" },
397            { "set" },
398            { "segments" },
399            { "connector" },
400        },
401        {
402            { "conversionset" },
403            { "conversion" },
404            { "starter" },
405            { "stopper" },
406        },
407        {
408            { "viewerprefix" },
409            { "state" },
410        }
411    }
412}
413
414implement { -- weird place
415    name      = "prefixedconverted",
416    actions   = sections.prefixedconverted,
417    arguments = {
418        "string",
419        {
420            { "prefix" },
421            { "separatorset" },
422            { "conversionset" },
423            { "conversion" },
424            { "starter" },
425            { "stopper" },
426            { "set" },
427            { "segments" },
428            { "connector" },
429        },
430        {
431            { "order" },
432            { "separatorset" },
433            { "conversionset" },
434            { "conversion" },
435            { "starter" },
436            { "stopper" },
437            { "segments" },
438            { "type" },
439            { "criterium" },
440        }
441    }
442}
443
444interfaces.implement {
445    name      = "pageofinternal",
446    arguments = "integer",
447    actions   = helpers.pageofinternal,
448}
449