summaryrefslogtreecommitdiff
path: root/tex/context/base/mkxl/attr-ini.lmt
blob: 32fc36cdd875ebaf8b1bba1da07dbd1a3b87d2eb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
if not modules then modules = { } end modules ['attr-ini'] = {
    version   = 1.001,
    comment   = "companion to attr-ini.mkiv",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"
}

local next, type = next, type
local osexit = os.exit
local sortedhash = table.sortedhash

-- We start with a registration system for atributes so that we can use the symbolic
-- names later on.

local nodes             = nodes
local context           = context
local storage           = storage
local commands          = commands

local implement         = interfaces.implement

attributes              = attributes or { }
local attributes        = attributes

local sharedstorage     = storage.shared

local texsetattribute   = tex.setattribute

attributes.names        = attributes.names    or { }
attributes.numbers      = attributes.numbers  or { }
attributes.list         = attributes.list     or { }
attributes.values       = attributes.values   or { }
attributes.counts       = attributes.counts   or { }
attributes.handlers     = attributes.handlers or { }
attributes.states       = attributes.states   or { }
attributes.unsetvalue   = -0x7FFFFFFF

local currentfont       = font.current
local currentattributes = nodes and                nodes.     currentattributes or node.currentattributes
local getusedattributes = nodes and nodes.nuts and nodes.nuts.getusedattributes or node.direct.getusedattributes

local names             = attributes.names
local numbers           = attributes.numbers
local list              = attributes.list
local values            = attributes.values
local counts            = attributes.counts

storage.register("attributes/names",   names,   "attributes.names")
storage.register("attributes/numbers", numbers, "attributes.numbers")
storage.register("attributes/list",    list,    "attributes.list")
storage.register("attributes/values",  values,  "attributes.values")
storage.register("attributes/counts",  counts,  "attributes.counts")

local report_attribute   = logs.reporter("attributes")
local report_value       = logs.reporter("attributes","values")

local trace_values       = false

local max_register_index = tex.magicconstants.max_attribute_register_index

trackers.register("attributes.values", function(v) trace_values = v end)

-- function attributes.define(name,number) -- at the tex end
--     if not numbers[name] then
--         numbers[name] = number
--         names[number] = name
--         list[number]  = { }
--     end
-- end

-- We reserve this one as we really want it to be always set (faster).

names[0], numbers["fontdynamic"] = "fontdynamic", 0

-- Private attributes are used by the system and public ones are for users. We use
-- dedicated ranges of numbers for them. Of course a the TeX end a private attribute
-- can be accessible too, so a private attribute can have a public appearance.

sharedstorage.attributes_last_private = sharedstorage.attributes_last_private or   15 -- very private
sharedstorage.attributes_last_public  = sharedstorage.attributes_last_public  or 1024 -- less private

function attributes.private(name) -- at the lua end (hidden from user)
    local number = numbers[name]
    if not number then
        local last = sharedstorage.attributes_last_private
        if last < 1023 then
            last = last + 1
            sharedstorage.attributes_last_private = last
        else
            report_attribute("no more room for private attributes")
            osexit()
        end
        number = last
        numbers[name], names[number], list[number] = number, name, { }
    end
    return number
end

function attributes.public(name) -- at the lua end (hidden from user)
    local number = numbers[name]
    if not number then
        local last = sharedstorage.attributes_last_public
        if last < max_register_index then
            last = last + 1
            sharedstorage.attributes_last_public = last
        else
            report_attribute("no more room for public attributes")
            osexit()
        end
        number = last
        numbers[name], names[number], list[number] = number, name, { }
    end
    return number
end

attributes.system = attributes.private

function attributes.define(name,category)
    return (attributes[category or "public"] or attributes["public"])(name)
end

-- tracers

local function showlist(what,list)
    if list then
        local a = list.next
        local i = 0
        while a do
            local number = a.index
            local value  = a.value
            i = i + 1
            report_attribute("%S %2i: attribute %3i, value %4i, name %a",what,i,number,value,names[number])
            a = a.next
        end
   end
end

function attributes.showcurrent()
    showlist("current",currentattributes())
end

function attributes.ofnode(n)
    showlist(n,n.attr)
end

-- rather special (can be optimized)

local store = { }

function attributes.save(name)
    name = name or ""
    local n = currentattributes()
    n = n and n.next
    local t = { }
    while n do
        t[n.index] = n.value
        n = n.next
    end
    store[name] = {
        attr = t,
        font = currentfont(),
    }
end

function attributes.restore(name)
    name = name or ""
    local t = store[name]
    if t then
        local attr = t.attr
        local font = t.font
        if attr then
            for k, v in next, attr do
                texsetattribute(k,v)
            end
        end
        if font then
         -- tex.font = font
         -- context.getvalue(fonts.hashes.csnames[font])
            currentfont(font)
        end
    end
 -- store[name] = nil
end

-- value manager

local cleaners = { }

-- function attributes.registervalue(index,value)
--     local list = values[index]
--     local last
--     if list then
--         last = counts[index] + 1
--         list[last] = value
--     else
--         last = 1
--         values[index] = { value }
--     end
--     counts[index] = last
--     return last
-- end

function attributes.registervalue(index,value)
    local list = values[index]
    local last
    if list then
        local c = counts[index]
        if c and c[2] > 0 then
            -- this can be an option
            for i=c[1],c[2] do
                if list[i] == nil then
                    -- we avoid 0 because that can be a signal attribute value
                    local n = i == 0 and 1 or i
                    if trace_values then
                        report_value("reusing slot %i for attribute %i in range (%i,%i)",n,index,c[1],c[2])
                    end
                    c[1] = n
                    list[n] = value
                    return n
                end
            end
        else
            c = { 0, 0 }
        end
        last = c[2] + 1
        list[last] = value
        c[1] = last
        c[2] = last
        if trace_values then
            report_value("expanding to slot %i for attribute %i",last,index)
        end
    else
        last = 1
        values[index] = { value }
        counts[index] = { last, last }
        if trace_values then
            report_value("starting at slot %i for attribute %i",last,index)
        end
    end
    return last
end

function attributes.getvalue(index,value)
    local list = values[index]
    return list and list[value] or nil
end

function attributes.hasvalues(index)
    local list = values[index]
    return list and next(list) and true or false
end

function attributes.getvalues(index)
    local list = values[index]
    return list and next(list) and list or nil
end

function attributes.setcleaner(index,cleaner)
    cleaners[index] = cleaner
end

function attributes.checkvalues()
-- if true then
--     report_value("no checking done")
--     return
-- end
     if next(values) then
        local active = getusedattributes()
        if trace_values then
            -- sorted
            for index, list in sortedhash(values) do
                local b = active[index]
                if b then
                    local cleaner = cleaners[index]
                    for k in sortedhash(list) do
                        if b[k] then
                            report_value("keeping value %i for attribute %i",k,index)
                        else
                            report_value("wiping value %i for attribute %i",k,index)
                            if cleaner then
                                cleaner(list[k])
                            end
                            list[k] = nil
                        end
                    end
                    if next(list) then
                        counts[index][1] = 0
                        goto continue
                    end
                end
                report_value("no more values for attribute %i",index)
                values[index] = nil
                counts[index] = nil
                ::continue::
            end
        else
            for index, list in next, values do
                local b = active[index]
                if b then
                    local cleaner = cleaners[index]
                    for k in next, list do
                        if not b[k] then
                            if cleaner then
                                cleaner(list[k])
                            end
                            list[k] = nil
                        end
                    end
                    if next(list) then
                        counts[index][1] = 0
                        goto continue
                    end
                end
                values[index] = nil
                counts[index] = { 0, 0 }
                ::continue::
            end
        end
    elseif trace_values then
        report_value("no check needed")
    end
end

implement {
    name      = "cleanupattributes",
 -- public    = true, -- some day ... but then also \shipoutpage
    protected = true,
    actions   = attributes.checkvalues,
}

-- interface

implement {
    name      = "defineattribute",
    arguments = "2 strings",
    actions   = { attributes.define, context }
}

implement {
    name      = "showattributes",
    actions   = attributes.showcurrent
}

implement {
    name      = "savecurrentattributes",
    arguments = "string",
    actions   = attributes.save
}

implement {
    name      = "restorecurrentattributes",
    arguments = "string",
    actions   = attributes.restore
}