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