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