page-ini.lmt /size: 10 Kb    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['page-ini'] = {
2    version   = 1.001,
3    comment   = "companion to page-ini.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-- Some day I need to make this more efficient.
10
11local tonumber, rawget, rawset, type, next = tonumber, rawget, rawset, type, next
12local match = string.match
13local sort, tohash, insert, remove, sortedkeys = table.sort, table.tohash, table.insert, table.remove, table.sortedkeys
14local settings_to_array, settings_to_hash = utilities.parsers.settings_to_array, utilities.parsers.settings_to_hash
15
16local texgetcount  = tex.getcount
17
18local tonut        = nodes.tonut
19local nextlist     = nodes.nuts.traversers.list
20local texlists     = tex.lists
21
22local context      = context
23local ctx_doif     = commands.doif
24local ctx_doifelse = commands.doifelse
25
26local implement    = interfaces.implement
27
28local data         = table.setmetatableindex("table")
29local last         = 0
30local pages        = structures.pages
31local autolist     = { }
32local report       = logs.reporter("pages","mark")
33local active       = false
34
35local trace        = false  trackers.register("pages.mark",function(v) trace = v end)
36
37local c_realpageno = tex.iscount("realpageno")
38
39function pages.mark(name,list,settings)
40    active = true
41    --
42    local realpage = texgetcount(c_realpageno)
43    if type(settings) == "string" then
44        settings = settings_to_hash(settings)
45    end
46    if not list or list == "" then
47        if trace then
48            report("marking current page %i as %a",realpage,name)
49        end
50        data[realpage][name] = settings or true
51        return
52    end
53    if type(list) == "string" then
54        list = settings_to_array(list)
55    end
56    if type(list) == "table" then
57        for i=1,#list do
58            local page = list[i]
59            local sign = false
60            if type(page) == "string" then
61                local f, t = match(page,"(%d+)[:%-](%d+)")
62                if f and t then
63                    f, t = tonumber(f), tonumber(t)
64                    if f and t and f <= t then
65                        if trace then
66                            report("marking page %i upto %i as %a",f,t,name)
67                        end
68                        for page=f,t do
69                            data[page][name] = settings or true
70                        end
71                    end
72                    page = false
73                else
74                    local s, p = match(page,"([%+%-])(%d+)")
75                    if s then
76                        sign, page = s, p
77                    end
78                end
79            end
80            if page then
81                page = tonumber(page)
82                if page then
83                    if sign == "+" then
84                        page = realpage + page
85                    end
86                    if sign == "-" then
87                        report("negative page numbers are not supported")
88                    else
89                        if trace then
90                            report("marking page %i as %a",page,name)
91                        end
92                        data[page][name] = settings or true
93                    end
94                end
95            end
96        end
97    else
98        if trace then
99            report("marking current page %i as %a",realpage,name)
100        end
101        data[realpage][name] = settings or true
102    end
103end
104
105local tobemarked = { }
106
107function pages.markedlist(realpage)
108    if active and realpage then
109        local m = rawget(tobemarked,realpage) or rawget(data,realpage)
110        return m and next(m) and sortedkeys(m)
111    end
112end
113
114local function marked(name)
115    if active then
116        local realpage = texgetcount(c_realpageno)
117        if last ~= 0 then
118            for i=last,realpage-1 do
119    --     print(last)
120                local di = data[i]
121                if di then
122                    tobemarked[i] = di
123                    rawset(data,i,nil)
124                end
125            end
126            last = 0 -- needs checking
127        end
128        local pagedata = rawget(data,realpage)
129        return pagedata and pagedata[name] and true or false
130    else
131        return false
132    end
133end
134
135local function markedparameter(name,key)
136    if active then
137        local pagedata = rawget(data,texgetcount(c_realpageno))
138        if pagedata then
139            pagedata = pagedata[name]
140            if pagedata then
141                pagedata = pagedata[key]
142            end
143        end
144        return pagedata
145    end
146end
147
148local function toranges(marked)
149    local list = { }
150    local size = #marked
151    if size > 0 then
152        local first = marked[1]
153        local last  = first
154        for i=2,size do
155            local page = marked[i]
156            if page > last + 1 then
157                list[#list+1] = { first, last }
158                first = page
159            end
160            last = page
161        end
162        list[#list+1] = { first, last }
163        --
164        active = true
165    end
166    return list
167end
168
169local function allmarked(list)
170    if active and list then
171        local collected = pages.collected
172        if collected then
173            if type(list) == "string" then
174                list = settings_to_hash(list)
175            elseif type(list) == "table" and #list > 0 then
176                list = tohash(list)
177            end
178            if type(list) == "table" then
179                local found = { }
180                for name in next, list do
181                    for page, list in next, data do
182                        if list[name] and collected[page] then
183                            found[#found+1] = page
184                        end
185                    end
186                end
187                if #found > 0 then
188                    sort(found)
189                    if trace then
190                        local ranges = toranges(found)
191                        for i=1,#ranges do
192                            local range = ranges[i]
193                            local first = range[1]
194                            local last  = range[2]
195                            if first == last then
196                                report("marked page : %i",first)
197                            else
198                                report("marked range: %i upto %i",first,last)
199                            end
200                        end
201                    end
202                    return found
203                end
204            end
205        end
206    end
207end
208
209pages.marked    = marked
210pages.toranges  = toranges
211pages.allmarked = allmarked
212
213-- An alternative is to use an attribute and identify the state by parsing the node
214-- list but that's a bit overkill for a hardly used feature like this.
215
216-- Page actions are bound to a real page. When we set one, we need to bind to the
217-- current page unless we just flushed. So we also need to check the current page.
218
219-- \page ... \start : sync realpage
220-- \page     \start : sync realpage
221-- \page     \stop  : reset synced because no content yet
222-- \page ... \stop  : keep sync
223
224local function autopageaction()
225    if active then
226        local nofauto = #autolist
227        if nofauto > 0 then
228            local realpage = texgetcount(c_realpageno)
229            for i=1,nofauto do
230                local entry    = autolist[i]
231                local names    = entry[1]
232                local settings = entry[2]
233                for j=1,#names do
234                    local name = names[j]
235                    local list = data[realpage]
236                    if not list[name] then
237                        if trace then
238                            report("automatically marking page %i as %a",realpage,name)
239                        end
240                        list[name] = settings or true
241                    end
242                end
243            end
244        end
245    end
246end
247
248local function startmarked(name,settings)
249    active = true
250    --
251    insert(autolist, { settings_to_array(name), settings_to_hash(settings) })
252    autopageaction(true)
253end
254
255local function stopmarked()
256    local nofauto = #autolist
257    if nofauto > 0 then
258        if not texlists.pagehead then
259            local realpage = texgetcount(c_realpageno)
260            for i=1,nofauto do
261                local entry = autolist[i]
262                local names = entry[1]
263                for j=1,#names do
264                    local name = names[j]
265                    local list = data[realpage]
266                    if list[name] then
267                        if trace then
268                            report("automatically unmarking page %i as %a",realpage,name)
269                        end
270                        list[name] = nil
271                    end
272                end
273            end
274        end
275        remove(autolist)
276    end
277end
278
279implement {
280    name      = "checkmarkedpages",
281    protected = true,
282    public    = true,
283    actions   = autopageaction,
284}
285
286implement {
287    name      = "markpage",
288    arguments = "3 strings",
289    actions   = pages.mark
290}
291
292implement {
293    name      = "doifelsemarkedpage",
294    arguments = "argument",
295    protected = true,
296    public    = true,
297    actions   = { marked, ctx_doifelse }
298}
299
300implement {
301    name      = "doifmarkedpage",
302    arguments = "argument",
303    protected = true,
304    public    = true,
305    actions   = { marked, ctx_doif }
306}
307
308implement {
309    name      = "markedpageparameter",
310    arguments = "2 arguments",
311    public    = true,
312    actions   = function(name,key)
313        local value = markedparameter(name,key)
314        if value then
315            context(value)
316        end
317    end
318}
319
320implement {
321    name      = "markedpages",
322    arguments = "string",
323    actions   = function(name)
324        local t = allmarked(name)
325        if t then
326            context("%,t",t)
327        end
328    end
329}
330
331implement {
332    name      = "startmarkpages",
333    arguments = "2 strings",
334    actions   = startmarked,
335}
336
337implement {
338    name      = "stopmarkpages",
339    protected = true,
340    public    = true,
341    actions   = stopmarked,
342}
343
344implement {
345    name      = "doifelsependingpagecontent",
346    actions   = function()
347        local h = texlists.contrib_head
348     -- local t = texlists.contrib_tail
349        local p = false
350        if h then
351            for n in nextlist, tonut(h) do
352                p = true
353                break
354            end
355        end
356        ctx_doifelse(p)
357    end,
358}
359
360-- implement {
361--     name    = "pagestate",
362--     public  = true,
363--     usage   = "value",
364--     actions = function()
365--         local result = 0
366--         if nodes.nuts.getspeciallist("contributehead") then
367--             result = result | 1
368--         end
369--         if nodes.nuts.getspeciallist("pagehead") then
370--             result = result | 2
371--         end
372--         return tokens.values.integer, result
373--     end
374-- }
375