attr-ini.lmt /size: 10 Kb    last modification: 2024-01-16 10:22
1if not modules then modules = { } end modules ['attr-ini'] = {
2    version   = 1.001,
3    comment   = "companion to attr-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
9local next, type = next, type
10local osexit = os.exit
11local sortedhash = table.sortedhash
12
13-- We start with a registration system for atributes so that we can use the symbolic
14-- names later on.
15
16local nodes             = nodes
17local context           = context
18local storage           = storage
19local commands          = commands
20
21local implement         = interfaces.implement
22
23attributes              = attributes or { }
24local attributes        = attributes
25
26local sharedstorage     = storage.shared
27
28local texsetattribute   = tex.setattribute
29
30attributes.names        = attributes.names    or { }
31attributes.numbers      = attributes.numbers  or { }
32attributes.list         = attributes.list     or { }
33attributes.values       = attributes.values   or { }
34attributes.counts       = attributes.counts   or { }
35attributes.handlers     = attributes.handlers or { }
36attributes.states       = attributes.states   or { }
37attributes.unsetvalue   = -0x7FFFFFFF
38
39local currentfont       = font.current
40local currentattributes = nodes and                nodes.     currentattributes or node.currentattributes
41local getusedattributes = nodes and nodes.nuts and nodes.nuts.getusedattributes or node.direct.getusedattributes
42
43local names             = attributes.names
44local numbers           = attributes.numbers
45local list              = attributes.list
46local values            = attributes.values
47local counts            = attributes.counts
48
49storage.register("attributes/names",   names,   "attributes.names")
50storage.register("attributes/numbers", numbers, "attributes.numbers")
51storage.register("attributes/list",    list,    "attributes.list")
52storage.register("attributes/values",  values,  "attributes.values")
53storage.register("attributes/counts",  counts,  "attributes.counts")
54
55local report_attribute   = logs.reporter("attributes")
56local report_value       = logs.reporter("attributes","values")
57
58local trace_values       = false
59
60local max_register_index = tex.magicconstants.max_attribute_register_index
61
62trackers.register("attributes.values", function(v) trace_values = v end)
63
64-- function attributes.define(name,number) -- at the tex end
65--     if not numbers[name] then
66--         numbers[name] = number
67--         names[number] = name
68--         list[number]  = { }
69--     end
70-- end
71
72-- We reserve this one as we really want it to be always set (faster).
73
74names[0], numbers["fontdynamic"] = "fontdynamic", 0
75
76-- Private attributes are used by the system and public ones are for users. We use
77-- dedicated ranges of numbers for them. Of course a the TeX end a private attribute
78-- can be accessible too, so a private attribute can have a public appearance.
79
80sharedstorage.attributes_last_private = sharedstorage.attributes_last_private or   15 -- very private
81sharedstorage.attributes_last_public  = sharedstorage.attributes_last_public  or 1024 -- less private
82
83function attributes.private(name) -- at the lua end (hidden from user)
84    local number = numbers[name]
85    if not number then
86        local last = sharedstorage.attributes_last_private
87        if last < 1023 then
88            last = last + 1
89            sharedstorage.attributes_last_private = last
90        else
91            report_attribute("no more room for private attributes")
92            osexit()
93        end
94        number = last
95        numbers[name], names[number], list[number] = number, name, { }
96    end
97    return number
98end
99
100function attributes.public(name) -- at the lua end (hidden from user)
101    local number = numbers[name]
102    if not number then
103        local last = sharedstorage.attributes_last_public
104        if last < max_register_index then
105            last = last + 1
106            sharedstorage.attributes_last_public = last
107        else
108            report_attribute("no more room for public attributes")
109            osexit()
110        end
111        number = last
112        numbers[name], names[number], list[number] = number, name, { }
113    end
114    return number
115end
116
117attributes.system = attributes.private
118
119function attributes.define(name,category)
120    return (attributes[category or "public"] or attributes["public"])(name)
121end
122
123-- tracers
124
125local function showlist(what,list)
126    if list then
127        local a = list.next
128        local i = 0
129        while a do
130            local number = a.index
131            local value  = a.value
132            i = i + 1
133            report_attribute("%S %2i: attribute %3i, value %4i, name %a",what,i,number,value,names[number])
134            a = a.next
135        end
136   end
137end
138
139function attributes.showcurrent()
140    showlist("current",currentattributes())
141end
142
143function attributes.ofnode(n)
144    showlist(n,n.attr)
145end
146
147-- rather special (can be optimized)
148
149local store = { }
150
151function attributes.save(name)
152    name = name or ""
153    local n = currentattributes()
154    n = n and n.next
155    local t = { }
156    while n do
157        t[n.index] = n.value
158        n = n.next
159    end
160    store[name] = {
161        attr = t,
162        font = currentfont(),
163    }
164end
165
166function attributes.restore(name)
167    name = name or ""
168    local t = store[name]
169    if t then
170        local attr = t.attr
171        local font = t.font
172        if attr then
173            for k, v in next, attr do
174                texsetattribute(k,v)
175            end
176        end
177        if font then
178         -- tex.font = font
179         -- context.getvalue(fonts.hashes.csnames[font])
180            currentfont(font)
181        end
182    end
183 -- store[name] = nil
184end
185
186-- value manager
187
188local cleaners = { }
189
190-- function attributes.registervalue(index,value)
191--     local list = values[index]
192--     local last
193--     if list then
194--         last = counts[index] + 1
195--         list[last] = value
196--     else
197--         last = 1
198--         values[index] = { value }
199--     end
200--     counts[index] = last
201--     return last
202-- end
203
204function attributes.registervalue(index,value)
205    local list = values[index]
206    local last
207    if list then
208        local c = counts[index]
209        if c and c[2] > 0 then
210            -- this can be an option
211            for i=c[1],c[2] do
212                if list[i] == nil then
213                    -- we avoid 0 because that can be a signal attribute value
214                    local n = i == 0 and 1 or i
215                    if trace_values then
216                        report_value("reusing slot %i for attribute %i in range (%i,%i)",n,index,c[1],c[2])
217                    end
218                    c[1] = n
219                    list[n] = value
220                    return n
221                end
222            end
223        else
224            c = { 0, 0 }
225        end
226        last = c[2] + 1
227        list[last] = value
228        c[1] = last
229        c[2] = last
230        if trace_values then
231            report_value("expanding to slot %i for attribute %i",last,index)
232        end
233    else
234        last = 1
235        values[index] = { value }
236        counts[index] = { last, last }
237        if trace_values then
238            report_value("starting at slot %i for attribute %i",last,index)
239        end
240    end
241    return last
242end
243
244function attributes.getvalue(index,value)
245    local list = values[index]
246    return list and list[value] or nil
247end
248
249function attributes.hasvalues(index)
250    local list = values[index]
251    return list and next(list) and true or false
252end
253
254function attributes.getvalues(index)
255    local list = values[index]
256    return list and next(list) and list or nil
257end
258
259function attributes.setcleaner(index,cleaner)
260    cleaners[index] = cleaner
261end
262
263function attributes.checkvalues()
264-- if true then
265--     report_value("no checking done")
266--     return
267-- end
268     if next(values) then
269        local active = getusedattributes()
270        if trace_values then
271            -- sorted
272            for index, list in sortedhash(values) do
273                local b = active[index]
274                if b then
275                    local cleaner = cleaners[index]
276                    for k in sortedhash(list) do
277                        if b[k] then
278                            report_value("keeping value %i for attribute %i",k,index)
279                        else
280                            report_value("wiping value %i for attribute %i",k,index)
281                            if cleaner then
282                                cleaner(list[k])
283                            end
284                            list[k] = nil
285                        end
286                    end
287                    if next(list) then
288                        counts[index][1] = 0
289                        goto continue
290                    end
291                end
292                report_value("no more values for attribute %i",index)
293                values[index] = nil
294                counts[index] = nil
295                ::continue::
296            end
297        else
298            for index, list in next, values do
299                local b = active[index]
300                if b then
301                    local cleaner = cleaners[index]
302                    for k in next, list do
303                        if not b[k] then
304                            if cleaner then
305                                cleaner(list[k])
306                            end
307                            list[k] = nil
308                        end
309                    end
310                    if next(list) then
311                        counts[index][1] = 0
312                        goto continue
313                    end
314                end
315                values[index] = nil
316                counts[index] = { 0, 0 }
317                ::continue::
318            end
319        end
320    elseif trace_values then
321        report_value("no check needed")
322    end
323end
324
325implement {
326    name      = "cleanupattributes",
327 -- public    = true, -- some day ... but then also \shipoutpage
328    protected = true,
329    actions   = attributes.checkvalues,
330}
331
332-- interface
333
334implement {
335    name      = "defineattribute",
336    arguments = "2 strings",
337    actions   = { attributes.define, context }
338}
339
340implement {
341    name      = "showattributes",
342    actions   = attributes.showcurrent
343}
344
345implement {
346    name      = "savecurrentattributes",
347    arguments = "string",
348    actions   = attributes.save
349}
350
351implement {
352    name      = "restorecurrentattributes",
353    arguments = "string",
354    actions   = attributes.restore
355}
356