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 }