diff options
Diffstat (limited to 'tex/context/base/node-ini.lua')
-rw-r--r-- | tex/context/base/node-ini.lua | 905 |
1 files changed, 905 insertions, 0 deletions
diff --git a/tex/context/base/node-ini.lua b/tex/context/base/node-ini.lua new file mode 100644 index 000000000..77835e04b --- /dev/null +++ b/tex/context/base/node-ini.lua @@ -0,0 +1,905 @@ +if not modules then modules = { } end modules ['node-ini'] = { + version = 1.001, + comment = "companion to node-ini.tex", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +--[[ldx-- +<p>Access to nodes is what gives <l n='luatex'/> its power. Here we +implement a few helper functions.</p> +--ldx]]-- + +nodes = nodes or { } +nodes.trace = false + +-- handy helpers + +do + + local remove, free = node.remove, node.free + + function nodes.remove(head, current, free_too) + local t = current + head, current = remove(head,current) + if t then + if free_too then + free(t) + t = nil + else + t.next, t.prev = nil, nil + end + end + return head, current, t + end + + function nodes.delete(head,current) + return nodes.remove(head,current,true) + end + + nodes.before = node.insert_before + nodes.after = node.insert_after + + function nodes.show_list(head, message) + if message then + texio.write_nl(message) + end + for n in node.traverse(head) do + texio.write_nl(tostring(n)) + end + end + +end + +-- will move + +nodes.processors = { } +nodes.processors.char = { } +nodes.processors.char.proc = { } + +function nodes.report(t,done) + if nodes.trace then + if done then + if status.output_active then + texio.write(string.format("<++ %s>",nodes.count(t))) + else + texio.write(string.format("<+ %s>",nodes.count(t))) + end + else + if status.output_active then + texio.write(string.format("<-- %s>",nodes.count(t))) + else + texio.write(string.format("<- %s>",nodes.count(t))) + end + end + end +end + +--~ function nodes.count(stack) +--~ if stack then +--~ local n = 0 +--~ for _, node in pairs(stack) do +--~ if node then +--~ local kind = node[1] +--~ if kind == 'hlist' or kind == 'vlist' then +--~ local content = node[8] +--~ if type(content) == "table" then +--~ n = n + 1 + nodes.count(content) -- self counts too +--~ else +--~ n = n + 1 +--~ end +--~ elseif kind == 'inline' then +--~ n = n + nodes.count(node[4]) -- self does not count +--~ else +--~ n = n + 1 +--~ end +--~ end +--~ end +--~ return n +--~ else +--~ return 0 +--~ end +--~ end + +do + + local hlist, vlist = node.id('hlist'), node.id('vlist') + + function nodes.count(stack) + local n = 0 + while stack do + local id = stack.id + if id == hlist or id == vlist then + local list = stack.list + if list then + n = n + 1 + nodes.count(list) -- self counts too + else + n = n + 1 + end + else + n = n + 1 + end + stack = stack.next + end + return n + end + +end + +--[[ldx-- +<p>When manipulating node lists in <l n='context'/>, we will remove +nodes and insert new ones. While node access was implemented, we did +quite some experiments in order to find out if manipulating nodes +in <l n='lua'/> was feasible from the perspective of performance.</p> + +<p>First of all, we noticed that the bottleneck is more with excessive +callbacks (some gets called very often) and the conversion from and to +<l n='tex'/>'s datastructures. However, at the <l n='lua'/> end, we +found that inserting and deleting nodes in a table could become a +bottleneck.</p> + +<p>This resulted in two special situations in passing nodes back to +<l n='tex'/>: a table entry with value <type>false</type> is ignored, +and when instead of a table <type>true</type> is returned, the +original table is used.</p> + +<p>Insertion is handled (at least in <l n='context'/> as follows. When +we need to insert a node at a certain position, we change the node at +that position by a dummy node, tagged <type>inline</type> which itself +contains the original node and one or more new nodes. Before we pass +back the list we collapse the list. Of course collapsing could be built +into the <l n='tex'/> engine, but this is a not so natural extension.</p> + +<p>When we collapse (something that we only do when really needed), we +also ignore the empty nodes.</p> +--ldx]]-- + +--~ function nodes.inline(...) +--~ return { 'inline', 0, nil, { ... } } +--~ end + +--~ do + +--~ function collapse(stack,existing_t) +--~ if stack then +--~ local t = existing_t or { } +--~ for _, node in pairs(stack) do +--~ if node then +--~ -- if node[3] then node[3][1] = nil end -- remove status bit +--~ local kind = node[1] +--~ if kind == 'inline' then +--~ collapse(node[4],t) +--~ elseif kind == 'hlist' or kind == 'vlist' then +--~ local content = node[8] +--~ if type(content) == "table" then +--~ node[8] = collapse(content) +--~ end +--~ t[#t+1] = node +--~ else +--~ t[#t+1] = node +--~ end +--~ else +--~ -- deleted node +--~ end +--~ end +--~ return t +--~ else +--~ return stack +--~ end +--~ end + +--~ nodes.collapse = collapse + +--~ end + +--[[ldx-- +<p>The following function implements a generic node processor. A +generic processer is not that much needed, because we often need +to act differently for horizontal or vertical lists. For instance +counting nodes needs a different method (ok, we could add a second +handle for catching them but it would become messy then).</p> +--ldx]]-- + +--~ function nodes.each(stack,handle) +--~ if stack then +--~ local i = 1 +--~ while true do +--~ local node = stack[i] +--~ if node then +--~ local kind = node[1] +--~ if kind == 'hlist' or kind == 'vlist' then +--~ local content = node[8] +--~ if type(content) == "table" then +--~ nodes.each(content,handle) +--~ end +--~ elseif kind == 'inline' then +--~ nodes.each(node[4],handle) +--~ else +--~ stack[i] = handle(kind,node) +--~ end +--~ end +--~ i = i + 1 +--~ if i > #stack then +--~ break +--~ end +--~ end +--~ end +--~ end + +--~ function nodes.remove(stack,id,subid) -- "whatsit", 6 +--~ nodes.each(stack, function(kind,node) +--~ if kind == id and node[2] == subid then +--~ return false +--~ else +--~ return node +--~ end +--~ end) +--~ end + +--[[ldx-- +<p>Serializing nodes can be handy for tracing. Also, saving and +loading node lists can come in handy as soon we are going to +use external applications to process node lists.</p> +--ldx]]-- + +function nodes.show(stack) +--~ texio.write_nl(table.serialize(stack)) +end + +function nodes.save(stack,name) -- *.ltn : luatex node file +--~ if name then +--~ file.savedata(name,table.serialize(stack)) +--~ else +--~ texio.write_nl('log',table.serialize(stack)) +--~ end +end + +function nodes.load(name) +--~ return file.loaddata(name) +end + +-- node-cap.lua + +--~ nodes.capture = { } -- somehow fails + +--~ function nodes.capture.start(cbk) +--~ local head, tail = nil, nil +--~ callbacks.push(cbk, function(t) +--~ if tail then +--~ tail.next = t +--~ else +--~ head, tail = t, t +--~ end +--~ while tail.next do +--~ tail = tail.next +--~ end +--~ return false +--~ end) +--~ function nodes.capture.stop() +--~ function nodes.capture.stop() end +--~ function nodes.capture.get() +--~ function nodes.capture.get() end +--~ return head +--~ end +--~ callbacks.pop(cbk) +--~ end +--~ function nodes.capture.get() end -- error +--~ end + +--~ nodes.capture.stop = function() end +--~ nodes.capture.get = function() end + +-- node-gly.lua + +if not fonts then fonts = { } end +if not fonts.tfm then fonts.tfm = { } end +if not fonts.tfm.id then fonts.tfm.id = { } end + +--~ function nodes.do_process_glyphs(stack) +--~ if not stack or #stack == 0 then +--~ return false +--~ elseif #stack == 1 then +--~ local node = stack[1] +--~ if node then +--~ local kind = node[1] +--~ if kind == 'glyph' then +--~ local tfmdata = fonts.tfm.id[node[5]] -- we can use fonts.tfm.processor_id +--~ if tfmdata and tfmdata.shared and tfmdata.shared.processors then +--~ for _, func in pairs(tfmdata.shared.processors) do -- per font +--~ func(stack,1,node) +--~ end +--~ end +--~ elseif kind == 'hlist' or kind == "vlist" then +--~ local done = nodes.do_process_glyphs(node[8]) +--~ end +--~ return true +--~ else +--~ return false +--~ end +--~ else +--~ local font_ids = { } +--~ local done = false +--~ for _, v in pairs(stack) do +--~ if v then +--~ if v[1] == 'glyph' then +--~ local font_id = v[5] +--~ local tfmdata = fonts.tfm.id[font_id] -- we can use fonts.tfm.processor_id +--~ if tfmdata and tfmdata.shared and tfmdata.shared.processors then +--~ font_ids[font_id] = tfmdata.shared.processors +--~ end +--~ end +--~ end +--~ end +--~ if done then +--~ return false +--~ else +--~ -- todo: generic loop before +--~ for font_id, _ in pairs(font_ids) do +--~ for _, func in pairs(font_ids[font_id]) do -- per font +--~ local i = 1 +--~ while true do +--~ local node = stack[i] +--~ if node and node[1] == 'glyph' and node[5] == font_id then +--~ i = func(stack,i,node) +--~ end +--~ if i < #stack then +--~ i = i + 1 +--~ else +--~ break +--~ end +--~ end +--~ end +--~ end +--~ for i=1, #stack do +--~ local node = stack[i] +--~ if node then +--~ if node[1] == 'hlist' or node[1] == "vlist" then +--~ nodes.do_process_glyphs(node[8]) +--~ end +--~ end +--~ end +--~ return true +--~ end +--~ end +--~ end + +--~ function nodes.do_process_glyphs(stack) +--~ local function process_list(node) +--~ local done = false +--~ if node and node[1] == 'hlist' or node[1] == "vlist" then +--~ local attributes = node[3] +--~ if attributes then +--~ if not attributes[1] then +--~ nodes.do_process_glyphs(node[8]) +--~ attributes[1] = 1 +--~ done = true +--~ end +--~ else +--~ nodes.do_process_glyphs(node[8]) +--~ node[3] = { 1 } +--~ done = true +--~ end +--~ end +--~ return done +--~ end +--~ if not stack or #stack == 0 then +--~ return false +--~ elseif #stack == 1 then +--~ return process_list(stack[1]) +--~ else +--~ local font_ids, found = { }, false +--~ for _, node in ipairs(stack) do +--~ if node and node[1] == 'glyph' then +--~ local font_id = node[5] +--~ local tfmdata = fonts.tfm.id[font_id] -- we can use fonts.tfm.processor_id +--~ if tfmdata and tfmdata.shared and tfmdata.shared.processors then +--~ font_ids[font_id], found = tfmdata.shared.processors, true +--~ end +--~ end +--~ end +--~ if not found then +--~ return false +--~ else +--~ -- we need func to report a 'done' +--~ local done = false +--~ for font_id, font_func in pairs(font_ids) do +--~ for _, func in pairs(font_func) do -- per font +--~ local i = 1 +--~ while true do +--~ local node = stack[i] +--~ if node and node[1] == 'glyph' and node[5] == font_id then +--~ i = func(stack,i,node) +--~ done = true +--~ end +--~ if i < #stack then +--~ i = i + 1 +--~ else +--~ break +--~ end +--~ end +--~ end +--~ end +--~ for _, node in ipairs(stack) do +--~ if node then +--~ done = done or process_list(node) +--~ end +--~ end +--~ return done +--~ end +--~ end +--~ end + +--~ function nodes.process_glyphs(t,...) +--~ input.start_timing(nodes) +--~ local done = nodes.do_process_glyphs(t) +--~ if done then +--~ t = nodes.collapse(t) +--~ end +--~ input.stop_timing(nodes) +--~ nodes.report(t,done) +--~ if done then +--~ return t +--~ else +--~ return true +--~ end +--~ end + +--~ function nodes.do_process_glyphs(stack) +--~ local function process_list(node) +--~ local done = false +--~ if node and node[1] == 'hist' or node[1] == "vlist" then +--~ local attributes = node[3] +--~ if attributes then +--~ if attributes[1] then +--~ else +--~ local content = node[8] +--~ if type(content) == "table" then +--~ nodes.do_process_glyphs(content) +--~ end +--~ attributes[1] = 1 +--~ done = true +--~ end +--~ else +--~ nodes.do_process_glyphs(node[8]) +--~ node[3] = { 1 } +--~ done = true +--~ end +--~ end +--~ return done +--~ end +--~ if not stack or #stack == 0 then +--~ return false +--~ elseif #stack == 1 then +--~ return process_list(stack[1]) +--~ else +--~ local font_ids, found = { }, false +--~ for _, node in ipairs(stack) do +--~ if node and node[1] == 'glyph' then +--~ local font_id = node[5] +--~ local tfmdata = fonts.tfm.id[font_id] -- we can use fonts.tfm.processor_id +--~ if tfmdata and tfmdata.shared and tfmdata.shared.processors then +--~ font_ids[font_id], found = tfmdata.shared.processors, true +--~ end +--~ end +--~ end +--~ if not found then +--~ return false +--~ else +--~ -- we need func to report a 'done' +--~ local done = false +--~ for font_id, font_func in pairs(font_ids) do +--~ for _, func in pairs(font_func) do -- per font +--~ local i = 1 +--~ while true do +--~ local node = stack[i] +--~ if node and node[1] == 'glyph' and node[5] == font_id then +--~ i = func(stack,i,node) +--~ done = true +--~ end +--~ if i < #stack then +--~ i = i + 1 +--~ else +--~ break +--~ end +--~ end +--~ end +--~ end +--~ for _, node in ipairs(stack) do +--~ if node then +--~ done = done or process_list(node) +--~ end +--~ end +--~ return done +--~ end +--~ end +--~ end + +--~ function nodes.process_glyphs(t,...) +--~ if status.output_active then +--~ return true +--~ else +--~ input.start_timing(nodes) +--~ local done = nodes.do_process_glyphs(t) +--~ if done then +--~ t = nodes.collapse(t) +--~ end +--~ input.stop_timing(nodes) +--~ nodes.report(t,done) +--~ if done then +--~ return t +--~ else +--~ return true +--~ end +--~ end +--~ end + +--~ do + +--~ local function do_process_glyphs(stack) +--~ if not stack or #stack == 0 then +--~ return false +--~ elseif #stack == 1 and stack[1][1] ~= 'glyph' then +--~ return false +--~ else +--~ local font_ids, found = { }, false +--~ local fti = fonts.tfm.id +--~ for _, node in ipairs(stack) do +--~ if node and node[1] == 'glyph' then +--~ local font_id = node[5] +--~ local tfmdata = fti[font_id] -- we can use fonts.tfm.processor_id +--~ if tfmdata and tfmdata.shared and tfmdata.shared.processors then +--~ font_ids[font_id], found = tfmdata.shared.processors, true +--~ end +--~ end +--~ end +--~ if not found then +--~ return false +--~ else +--~ -- we need func to report a 'done' +--~ local done = false +--~ for font_id, font_func in pairs(font_ids) do +--~ for _, func in pairs(font_func) do -- per font +--~ local i = 1 +--~ while true do +--~ local node = stack[i] +--~ if node and node[1] == 'glyph' and node[5] == font_id then +--~ i = func(stack,i,node) +--~ done = true +--~ end +--~ if i < #stack then +--~ i = i + 1 +--~ else +--~ break +--~ end +--~ end +--~ end +--~ end +--~ for _, node in ipairs(stack) do +--~ if node then +--~ done = done or process_list(node) +--~ end +--~ end +--~ return done +--~ end +--~ end +--~ end + +--~ local function do_collapse_glyphs(stack,existing_t) +--~ if stack then +--~ local t = existing_t or { } +--~ for _, node in pairs(stack) do +--~ if node then +--~ if node[3] then node[3][1] = nil end -- remove status bit / 1 sec faster on 15 sec +--~ if node[1] == 'inline' then +--~ local nodes = node[4] +--~ if #nodes == 1 then +--~ t[#t+1] = nodes[1] +--~ else +--~ do_collapse_glyphs(nodes,t) +--~ end +--~ else +--~ t[#t+1] = node +--~ end +--~ else +--~ -- deleted node +--~ end +--~ end +--~ return t +--~ else +--~ return stack +--~ end +--~ end + +--~ function nodes.process_glyphs(t,...) +--~ --~ print(...) +--~ if status.output_active then -- not ok, we need a generic blocker, pagebody ! / attr tex.attibutes +--~ return true +--~ else +--~ input.start_timing(nodes) +--~ local done = do_process_glyphs(t) +--~ if done then +--~ t = do_collapse_glyphs(t) +--~ end +--~ input.stop_timing(nodes) +--~ nodes.report(t,done) +--~ if done then +--~ --~ texio.write_nl("RETURNING PROCESSED LIST") +--~ return t +--~ else +--~ --~ texio.write_nl("RETURNING SIGNAL") +--~ return true +--~ end +--~ end +--~ end + +--~ end + +do + + local glyph = node.id('glyph') + local pushmarks = false + + function do_process_glyphs(head) -- beware, we need to handle shifted heads -- todo + if not head then + return head + end + local usedfonts, found, fontdata = { }, false, fonts.tfm.id + for n in node.traverse_id(glyph,head) do + local font = n.font + if not usedfonts[font] then + local shared = fontdata[font].shared + if shared and shared.processors then + usedfonts[font], found = shared.processors, true + end + end + end + if not found then + return head, false + else + local tail, done = node.slide(head), false + for font, processors in pairs(usedfonts) do + if pushmarks then + local h, d = fonts.pushmarks(head,font) + head, done = head or h, done or d + end + for _, processor in ipairs(processors) do + local h, d = processor(head,font) + head, done = head or h, done or d + end + if pushmarks then + local h, d = fonts.popmarks(head,font) + head, done = head or h, done or d + end + end + return head, done + end + end + + function nodes.process_glyphs(head) + if status.output_active then -- not ok, we need a generic blocker, pagebody ! / attr tex.attibutes + return true + else + input.start_timing(nodes) + local head, done = do_process_glyphs(head) + input.stop_timing(nodes) + nodes.report(head,done) + if done then + return head -- something changed + elseif head then + return true -- nothing changed + else + return false -- delete list + end + end + end + +end + +-- vbox: grouptype: vbox vtop output split_off split_keep | box_type: exactly|aditional +-- hbox: grouptype: hbox adjusted_hbox(=hbox_in_vmode) | box_type: exactly|aditional + +callback.register('pre_linebreak_filter', nodes.process_glyphs) +callback.register('hpack_filter', nodes.process_glyphs) + +--~ callback.register('pre_linebreak_filter', function(t,...) +--~ print("pre_linebreak_filter",...) +--~ return nodes.process_glyphs(t) +--~ end ) +--~ callback.register('hpack_filter', function(t,...) +--~ print("hpack_filter",...) +--~ return nodes.process_glyphs(t) +--~ end ) + +function nodes.length(head) + if head then + local m = 0 + for n in node.traverse(head) do + m = m + 1 + end + return m + else + return 0 + end +end + +do + +--~ function nodes.totable(n) +--~ function totable(n) +--~ local f, tt = node.fields(n.id,n.subtype), { } +--~ for _,v in ipairs(f) do +--~ local nv = n[v] +--~ if nv then +--~ local tnv = type(nv) +--~ if tnv == "string" or tnv == "number" then +--~ tt[v] = nv +--~ else -- userdata +--~ tt[v] = nodes.totable(nv) +--~ end +--~ end +--~ end +--~ return tt +--~ end +--~ local t = { } +--~ while n do +--~ t[#t+1] = totable(n) +--~ n = n.next +--~ end +--~ return t +--~ end + + local expand = { + list = true, + pre = true, + post = true, + spec = true, + attr = true, + components = true, + } + + -- flat: don't use next, but indexes + -- verbose: also add type + + function nodes.totable(n,flat,verbose) + function totable(n,verbose) + local f = node.fields(n.id,n.subtype) + local tt = { } + for _,v in ipairs(f) do + if n[v] then + if v == "ref_count" then + -- skip + elseif expand[v] then -- or: type(n[v]) ~= "string" or type(n[v]) ~= "number" + tt[v] = nodes.totable(n[v],flat,verbose) + else + tt[v] = n[v] + end + end + end + if verbose then + tt.type = node.type(tt.id) + end + return tt + end + if flat then + local t = { } + while n do + t[#t+1] = totable(n,verbose) + n = n.next + end + return t + else + local t = totable(n,verbose) + if n.next then + t.next = nodes.totable(n.next,flat,verbose) + end + return t + end + end + + local function key(k) + if type(k) == "number" then + return "["..k.."]" + else + return k + end + end + + local function serialize(root,name,handle,depth,m) + handle = handle or print + if depth then + depth = depth .. " " + handle(("%s%s={"):format(depth,key(name))) + else + depth = "" + if type(name) == "string" then + if name == "return" then + handle("return {") + else + handle(name .. "={") + end + elseif type(name) == "number" then + handle("[" .. name .. "]={") + else + handle("t={") + end + end + if root then + local fld + if root.id then + fld = node.fields(root.id,root.subtype) + else + fld = table.sortedkeys(root) + end + if type(root) == 'table' and root['type'] then -- userdata or table + handle(("%s %s=%q,"):format(depth,'type',root['type'])) + end + for _,k in ipairs(fld) do + if k then + local v = root[k] + local t = type(v) + if t == "number" then + handle(("%s %s=%s,"):format(depth,key(k),v)) + elseif t == "string" then + handle(("%s %s=%q,"):format(depth,key(k),v)) + elseif v then -- userdata or table + serialize(v,k,handle,depth,n+1) + end + end + end + if root['next'] then -- userdata or table + serialize(root['next'],'next',handle,depth,n+1) + end + end + if m and m > 0 then + handle(("%s},"):format(depth)) + else + handle(("%s}"):format(depth)) + end + end + + function nodes.serialize(root,name) + local t = { } + local function flush(s) + t[#t+1] = s + end + serialize(root, name, flush, nil, nil) + return table.concat(t,"\n") + end + + function nodes.serializebox(n,flat,verbose) + return nodes.serialize(nodes.totable(tex.box[n],flat,verbose)) + -- return nodes.serialize(tex.box[n]) + end + + function nodes.visualizebox(...) + -- tex.sprint(tex.ctxcatcodes,"\\starttyping\n" .. nodes.serializebox(...) .. "\n\\stoptyping\n") + tex.print(tex.ctxcatcodes,"\\starttyping") + tex.print(nodes.serializebox(...)) + tex.print("\\stoptyping") + end + +end + +if not node.list_has_attribute then + + function node.list_has_attribute(list,attribute) + if list and attribute then + for n in node.traverse(list) do + local a = has_attribute(n,attribute) + if a then return a end + end + end + return false + end + +end + +function nodes.pack_list(head) + local t = { } + for n in node.traverse(head) do + t[#t+1] = tostring(n) + end + return t +end + |