if not modules then modules = { } end modules ['node-fin'] = { version = 1.001, optimize = true, comment = "companion to node-fin.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files", } local next, type, format = next, type, string.format local setmetatableindex = table.setmetatableindex local attributes, nodes, node = attributes, nodes, node local nuts = nodes.nuts local tonut = nodes.tonut local getnext = nuts.getnext local getid = nuts.getid local getlist = nuts.getlist local getleader = nuts.getleader local getattr = nuts.getattr local getattrs = nuts.getattrs local getwidth = nuts.getwidth local getwhd = nuts.getwhd local hasgeometry = nuts.hasgeometry local hasdimensions = nuts.hasdimensions local getbox = nuts.getbox local setlist = nuts.setlist local setleader = nuts.setleader local copy_node = nuts.copy local insertnodebefore = nuts.insertbefore local insertnodeafter = nuts.insertafter local appendaftertail = nuts.appendaftertail local nextnode = nuts.traversers.node local nextcontent = nuts.traversers.content local nodecodes = nodes.nodecodes local rulecodes = nodes.rulecodes local boxrule_code = rulecodes.box local imagerule_code = rulecodes.image local emptyrule_code = rulecodes.empty local virtualrule_code = rulecodes.virtual local container_code = nodes.listcodes.container local glyph_code = nodecodes.glyph local disc_code = nodecodes.disc local glue_code = nodecodes.glue local rule_code = nodecodes.rule local hlist_code = nodecodes.hlist local vlist_code = nodecodes.vlist local texlists = tex.lists local texgetnest = tex.getnest local states = attributes.states local numbers = attributes.numbers local implement = interfaces.implement local starttiming = statistics.starttiming local stoptiming = statistics.stoptiming local loadstripped = utilities.lua.loadstripped local unsetvalue = attributes.unsetvalue -- these two will be like trackers function states.enabletriggering () triggering = true end function states.disabletriggering() triggering = false end implement { name = "enablestatetriggering", actions = states.enabletriggering } implement { name = "disablestatetriggering", actions = states.disabletriggering } nodes.plugindata = nil -- inheritance: -0x7FFFFFFF -- we can best use nil and skip ! local template = [[ local plugin = nodes.plugindata local starttiming = statistics.starttiming local stoptiming = statistics.stoptiming local namespace = plugin.namespace local attribute = namespace.attribute or attributes.numbers[plugin.name] local processor = plugin.processor local initializer = plugin.initializer local resolver = plugin.resolver local finalizer = plugin.finalizer local flusher = plugin.flusher if not processor then return function(head) return head end elseif initializer or finalizer or resolver then return function(head) starttiming(attributes) local used, inheritance if resolver then inheritance = resolver() end if initializer then initializer(namespace,attribute,head) end head = processor(namespace,attribute,head,inheritance) if finalizer then head, used = finalizer(namespace,attribute,head) if used and flusher then head = flusher(namespace,attribute,head,used) end end stoptiming(attributes) return head end else return function(head) starttiming(attributes) head = processor(namespace,attribute,head) stoptiming(attributes) return head end end nodes.plugindata = nil ]] function nodes.installattributehandler(plugin) nodes.plugindata = plugin return loadstripped(template)() end -- the injectors local nsdata, nsnone, nslistwise, nsforced, nsselector local current, current_selector = 0, 0 -- nb, stack has a local current ! local nsbegin, nsend, nsreset function states.initialize(namespace,attribute,head) nsdata = namespace.data nsnone = namespace.none nsforced = namespace.forced nsselector = namespace.selector nslistwise = namespace.listwise current = 0 current_selector = 0 nsstep = namespace.resolve_step if nsstep then nsreset = namespace.resolve_reset nsbegin = namespace.resolve_begin nsend = namespace.resolve_end nspush = namespace.push nspop = namespace.pop end end function states.finalize(namespace,attribute,head) -- is this one ok? if current > 0 and nsnone then local id = getid(head) if id == hlist_code or id == vlist_code then local content = getlist(head) if content then appendaftertail(content,copy_node(nsnone)) end else appendaftertail(head,copy_node(nsnone)) end return head, true end return head, false end -- we need to deal with literals too (reset as well as oval) local function process(attribute,head,inheritance,default) -- one attribute local check = false local leader = nil for stack, id, subtype, content in nextcontent, head do if id == glyph_code or id == disc_code then check = true elseif id == glue_code then check = true leader = content elseif id == hlist_code or id == vlist_code then -- tricky checking local outer if subtype == container_code or hasgeometry(stack) then outer = getattr(stack,attribute) if outer then if default and outer == inheritance then if current ~= default then head = insertnodebefore(head,stack,copy_node(nsdata[default])) current = default end elseif current ~= outer then head = insertnodebefore(head,stack,copy_node(nsdata[outer])) current = outer end elseif default and inheritance then if current ~= default then head = insertnodebefore(head,stack,copy_node(nsdata[default])) current = default end elseif current > 0 then head = insertnodebefore(head,stack,copy_node(nsnone)) current = 0 end end local list = process(attribute,content,inheritance,default) if content ~= list then setlist(stack,list) end elseif id == rule_code then check = subtype == virtualrule_code or hasdimensions(stack) end -- much faster this way than using a check() and nested() function if check then local c = getattr(stack,attribute) if c then if default and c == inheritance then if current ~= default then head = insertnodebefore(head,stack,copy_node(nsdata[default])) current = default end elseif current ~= c then head = insertnodebefore(head,stack,copy_node(nsdata[c])) current = c end if leader then local savedcurrent = current local ci = getid(leader) if ci == hlist_code or ci == vlist_code then -- else we reset inside a box unneeded, okay, the downside is -- that we trigger color in each repeated box, so there is room -- for improvement here current = 0 end local list = process(attribute,leader,inheritance,default) if leader ~= list then setleader(stack,list) end current = savedcurrent leader = false end elseif default and inheritance then if current ~= default then head = insertnodebefore(head,stack,copy_node(nsdata[default])) current = default end elseif current > 0 then head = insertnodebefore(head,stack,copy_node(nsnone)) current = 0 end check = false end end return head end states.process = function(namespace,attribute,head,default) return process(attribute,head,default) end local function simple(attribute,head) local check = false local leader = nil for stack, id, subtype, content in nextcontent, head do if id == glyph_code or id == disc_code then check = true elseif id == glue_code then check = true leader = content elseif id == hlist_code or id == vlist_code then if subtype == container_code or hasgeometry(stack) then local outer = getattr(stack,attribute) if outer then if current ~= outer then if current > 0 then head = insertnodebefore(head,stack,copy_node(nsnone)) end head = insertnodebefore(head,stack,copy_node(nsdata[c])) current = outer end elseif current > 0 then head = insertnodebefore(head,stack,copy_node(nsnone)) current = 0 end end local list = simple(attribute,content) if content ~= list then setlist(stack,list) end elseif id == rule_code then check = subtype == virtualrule_code or hasdimensions(stack) end if check then local c = getattr(stack,attribute) if c then if current ~= c then if current > 0 then head = insertnodebefore(head,stack,copy_node(nsnone)) end head = insertnodebefore(head,stack,copy_node(nsdata[c])) current = c end if leader then local savedcurrent = current local ci = getid(leader) if ci == hlist_code or ci == vlist_code then current = 0 end local list = simple(attribute,leader) if leader ~= list then setleader(stack,list) end current = savedcurrent leader = false end elseif current > 0 then head = insertnodebefore(head,stack,copy_node(nsnone)) current = 0 end check = false end end return head end states.simple = function(namespace,attribute,head,default) return simple(attribute,head,default) end -- We can force a selector, e.g. document wide color spaces, saves a little watch -- out, we need to check both the selector state (like colorspace) and the main -- state (like color), otherwise we get into troubles when a selector state changes -- while the main state stays the same (like two glyphs following each other with -- the same color but different color spaces e.g. \showcolor). The triggering -- mechanism has been removed because it was never really used, but the original can -- be seen in the mkiv (lua) code. local function selective(attribute,head,inheritance,default) -- two attributes local check = false local leader = nil for stack, id, subtype, content in nextcontent, head do if id == glyph_code or id == disc_code then check = true elseif id == glue_code then check = true leader = content elseif id == hlist_code or id == vlist_code then local outer, s if subtype == container_code or hasgeometry(stack) then outer, s = getattrs(stack,attribute,nsselector) if outer then if default and outer == inheritance then if current ~= default then local data = nsdata[default][nsforced or nsselector] if data then head = insertnodebefore(head,stack,copy_node(data)) end current = default end elseif current ~= outer or current_selector ~= s then local data = nsdata[outer][nsforced or s or nsselector] if data then head = insertnodebefore(head,stack,copy_node(data)) end current = outer current_selector = s end elseif default and inheritance then if current ~= default then local data = nsdata[default][nsforced or s or nsselector] if data then head = insertnodebefore(head,stack,copy_node(data)) end current = default end elseif current > 0 then head = insertnodebefore(head,stack,copy_node(nsnone)) current, current_selector = 0, 0 end end local list = selective(attribute,content,inheritance,default) if content ~= list then setlist(stack,list) end elseif id == rule_code then if subtype == virtualrule_code then check = true elseif subtype == boxrule_code or subtype == imagerule_code or subtype == emptyrule_code then -- so no redundant color stuff (only here, layers for instance should obey) check = false else check = hasdimensions(stack) end end if check then local c, s = getattrs(stack,attribute,nsselector) if c then if default and c == inheritance then if current ~= default then local data = nsdata[default][nsforced or s or nsselector] if data then head = insertnodebefore(head,stack,copy_node(data)) end current = default end elseif current ~= c or current_selector ~= s then local data = nsdata[c][nsforced or s or nsselector] if data then head = insertnodebefore(head,stack,copy_node(data)) end current = c current_selector = s end if leader then local list = selective(attribute,leader,inheritance,default) if leader ~= list then setleader(stack,list) end leader = false end elseif default and inheritance then if current ~= default then local data = nsdata[default][nsforced or s or nsselector] if data then head = insertnodebefore(head,stack,copy_node(data)) end current = default end elseif current > 0 then head = insertnodebefore(head,stack,copy_node(nsnone)) current, current_selector = 0, 0 end check = false end end return head end states.selective = function(namespace,attribute,head,default) return selective(attribute,head,default) end -- Ideally the next one should be merged with the previous but keeping it separate is -- safer. We deal with two situations: efficient boxwise (layoutareas) and mixed layers -- (as used in the stepper). In the stepper we cannot use the box branch as it involves -- paragraph lines and then gets mixed up. A messy business (esp since we want to be -- efficient). -- -- Todo: make a better stacker. Keep track (in attribute) about nesting level. Not -- entirely trivial and a generic solution is nicer (compares to the exporter). local function stacked(attribute,head,default) -- no inheritance, but list-wise local stack = head local current = default or 0 local depth = 0 local check = false local leader = false while stack do local id = getid(stack) if id == glyph_code then check = true elseif id == glue_code then leader = getleader(stack) if leader then check = true end elseif id == hlist_code or id == vlist_code then local content = getlist(stack) if content then -- the problem is that broken lines gets the attribute which can be a later one local list if subtype == container_code then check = true current = 0 end if nslistwise then local a = getattr(stack,attribute) if a and current ~= a and nslistwise[a] then -- viewerlayer / needs checking, see below local p = current current = a head = insertnodebefore(head,stack,copy_node(nsdata[a])) list = stacked(attribute,content,current) -- two return values head, stack = insertnodeafter(head,stack,copy_node(nsnone)) current = p else list = stacked(attribute,content,current) end else list = stacked(attribute,content,current) end if content ~= list then setlist(stack,list) -- only if ok end end elseif id == rule_code then check = subtype == virtualrule_code or hasdimensions(stack) end if check then local a = getattr(stack,attribute) if a then if current ~= a then head = insertnodebefore(head,stack,copy_node(nsdata[a])) depth = depth + 1 current = a end if leader then local content = getlist(leader) if content then local list = stacked(attribute,content,current) if leader ~= list then setleader(stack,list) -- only if ok end end leader = false end elseif default > 0 then -- elseif current > 0 then head = insertnodebefore(head,stack,copy_node(nsnone)) depth = depth - 1 current = 0 end check = false end stack = getnext(stack) end while depth > 0 do head = insertnodeafter(head,stack,copy_node(nsnone)) depth = depth - 1 end return head end states.stacked = function(namespace,attribute,head,default) return stacked(attribute,head,default) end local function stacker(attribute,head,default) -- no inheritance, but list-wise local stacked = false local current = head local previous = head local attrib = default or unsetvalue local check = false local leader = false for current, id, subtype, content in nextcontent, head do if id == glyph_code then check = true elseif id == glue_code then leader = content check = true elseif id == hlist_code or id == vlist_code then local list if subtype == container_code then check = true end if nslistwise then local a = getattr(current,attribute) if a and attrib ~= a and nslistwise[a] then -- viewerlayer head = insertnodebefore(head,current,copy_node(nsdata[a])) list = stacker(attribute,content,a) if list ~= content then setlist(current,list) end head, current = insertnodeafter(head,current,copy_node(nsnone)) else list = stacker(attribute,content,attrib) if list ~= content then setlist(current,list) end end else list = stacker(attribute,content,default) if list ~= content then setlist(current,list) end end elseif id == rule_code then if subtype == virtualrule_code then check = true elseif subtype == boxrule_code or subtype == imagerule_code or subtype == emptyrule_code then -- so no redundant color stuff (only here, layers for instance should obey) check = false else check = hasdimensions(current) end end if check then local a = getattr(current,attribute) or unsetvalue if a ~= attrib then if not stacked then stacked = true nsbegin() end local n = nsstep(a) if n then head = insertnodebefore(head,current,n) -- a end attrib = a if leader then -- tricky as a leader has to be a list so we cannot inject before local content = getlist(leader) if content then local list = stacker(attribute,leader,attrib) if leader ~= list then setleader(current,list) end end leader = false end end check = false end previous = current end if stacked then local n = nsend() while n do head = insertnodeafter(head,previous,n) n = nsend() end end return head end states.stacker = function(namespace,attribute,head,default) local head = stacker(attribute,head,default) nsreset() return head end -- -- -- statistics.register("attribute processing time", function() return statistics.elapsedseconds(attributes,"front- and backend") end)