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
356
357
358
359
360
361
|
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
--[[ldx--
<p>We start with a registration system for atributes so that we can use the
symbolic names later on.</p>
--ldx]]--
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
--[[ldx--
<p>We reserve this one as we really want it to be always set (faster).</p>
--ldx]]--
names[0], numbers["fontdynamic"] = "fontdynamic", 0
--[[ldx--
<p>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 <l n='context'/> end a private attribute can be
accessible too, so a private attribute can have a public appearance.</p>
--ldx]]--
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
}
|