summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/context/base/node-ini.lua
diff options
context:
space:
mode:
authorTaco Hoekwater <taco@elvenkind.com>2008-06-12 10:42:53 +0000
committerTaco Hoekwater <taco@elvenkind.com>2008-06-12 10:42:53 +0000
commit0d01365d53c456d246da0ca1f0b3cd9868f02b35 (patch)
tree01a655c8028e17cfb371456b299c1848fe08c05b /Master/texmf-dist/tex/context/base/node-ini.lua
parent44f3714442da07fdfc36a7f2a8dcd5d4294c5d26 (diff)
ConTeXt release 2008.05.21
git-svn-id: svn://tug.org/texlive/trunk@8691 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/tex/context/base/node-ini.lua')
-rw-r--r--Master/texmf-dist/tex/context/base/node-ini.lua1042
1 files changed, 1042 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/context/base/node-ini.lua b/Master/texmf-dist/tex/context/base/node-ini.lua
new file mode 100644
index 00000000000..5923a49e40b
--- /dev/null
+++ b/Master/texmf-dist/tex/context/base/node-ini.lua
@@ -0,0 +1,1042 @@
+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. These functions are rather optimized.</p>
+--ldx]]--
+
+nodes = nodes or { }
+nodes.trace = false
+nodes.ignore = nodes.ignore or false
+
+-- handy helpers
+
+if node.protect_glyphs then
+
+ nodes.protect_glyphs = node.protect_glyphs
+ nodes.unprotect_glyphs = node.unprotect_glyphs
+
+else do
+
+ -- initial value subtype : X000 0001 = 1 = 0x01 = char
+ --
+ -- expected before linebreak : X000 0000 = 0 = 0x00 = glyph
+ -- X000 0010 = 2 = 0x02 = ligature
+ -- X000 0100 = 4 = 0x04 = ghost
+ -- X000 1010 = 10 = 0x0A = leftboundary lig
+ -- X001 0010 = 18 = 0x12 = rightboundary lig
+ -- X001 1010 = 26 = 0x1A = both boundaries lig
+ -- X000 1100 = 12 = 0x1C = leftghost
+ -- X001 0100 = 20 = 0x14 = rightghost
+
+
+ local glyph = node.id('glyph')
+ local traverse_id = node.traverse_id
+
+ function nodes.protect_glyphs(head)
+ local done = false
+ for g in traverse_id(glyph,head) do
+ local s = g.subtype
+ if s == 1 then
+ done, g.subtype = true, 256
+ elseif s <= 256 then
+ done, g.subtype = true, 256 + s
+ end
+ end
+ return done
+ end
+
+ function nodes.unprotect_glyphs(head)
+ local done = false
+ for g in traverse_id(glyph,head) do
+ local s = g.subtype
+ if s > 256 then
+ done, g.subtype = true, s - 256
+ end
+ end
+ return done
+ end
+
+end end
+
+do
+
+ local remove, free = node.remove, node.free
+
+ --~ function nodes.remove(head, current, free_too)
+ --~ if head == current then
+ --~ local cn = current.next
+ --~ if cn then
+ --~ cn.prev = nil
+ --~ if free_too then
+ --~ node.free(current)
+ --~ return cn, cn, nil
+ --~ else
+ --~ current.prev = nil
+ --~ current.next = nil
+ --~ return cn, cn, current
+ --~ end
+ --~ else
+ --~ if free_too then
+ --~ node.free(current)
+ --~ return nil, nil, nil
+ --~ else
+ --~ return head,current,current
+ --~ end
+ --~ end
+ --~ else
+ --~ local cp = current.prev
+ --~ local cn = current.next
+ --~ if not cp and head.next == current then
+ --~ cp = head
+ --~ end
+ --~ if cn then
+ --~ cn.prev = cp
+ --~ if cp then
+ --~ cp.next = cn
+ --~ end
+ --~ elseif cp then
+ --~ cp.next = nil
+ --~ end
+ --~ if free_too then
+ --~ node.free(current)
+ --~ return head, cn, nil
+ --~ else
+ --~ current.prev = nil
+ --~ current.next = nil
+ --~ return head, cn, current
+ --~ end
+ --~ end
+ --~ end
+
+ 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 -- broken
+ nodes.after = node.insert_after
+
+ function nodes.before(h,c,n)
+ if c then
+ if c == h then
+ n.next = h
+ n.prev = nil
+ h.prev = n
+ else
+ local cp = c.prev
+ n.next = c
+ n.prev = cp
+ if cp then
+ cp.next = n
+ end
+ c.prev = n
+ return h, n
+ end
+ end
+ return n, n
+ end
+
+ function nodes.after(h,c,n)
+ if c then
+ local cn = c.next
+ if cn then
+ n.next = cn
+ cn.prev = n
+ else
+ n.next = nil
+ end
+ c.next = n
+ n.prev = c
+ if c ~= h then
+ return h, n
+ end
+ end
+ return n, n
+ end
+
+ 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 -- best also test this before calling
+ 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
+
+do
+
+ local hlist, vlist = node.id('hlist'), node.id('vlist')
+
+ local function count(stack,flat)
+ local n = 0
+ while stack do
+ local id = stack.id
+ if not flat and id == hlist or id == vlist then
+ local list = stack.list
+ if list then
+ n = n + 1 + count(list) -- self counts too
+ else
+ n = n + 1
+ end
+ else
+ n = n + 1
+ end
+ stack = stack.next
+ end
+ return n
+ end
+
+ nodes.count = count
+
+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
+has_attribute 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. [This is obsolete!]</p>
+--ldx]]--
+
+
+--[[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.otf then fonts.otf = { } end
+if not fonts.tfm then fonts.tfm = { } end
+if not fonts.tfm.id then fonts.tfm.id = { } end
+
+do
+
+ local glyph = node.id('glyph')
+ local has_attribute = node.has_attribute
+ local traverse_id = node.traverse_id
+
+ local pairs = pairs
+
+ local starttiming, stoptiming = input.starttiming, input.stoptiming
+
+ function nodes.process_characters(head)
+ -- not ok yet; we need a generic blocker
+ -- if status.output_active then
+ if false then -- status.output_active then
+ return head, false -- true
+ else
+ -- either next or not, but definitely no already processed list
+ starttiming(nodes)
+ local usedfonts, attrfonts, done = { }, { }, false
+ -- todo: should be independent of otf
+ local set_dynamics, font_ids = fonts.otf.set_dynamics, fonts.tfm.id -- todo: font-var.lua so that we can global this one
+ local a, u, prevfont, prevattr = 0, 0, nil, 0
+ for n in traverse_id(glyph,head) do
+ local font, attr = n.font, has_attribute(n,0) -- zero attribute is reserved for fonts, preset to 0 is faster (first match)
+ if attr and attr > 0 then
+ if font ~= prevfont or attr ~= prevattr then
+ local used = attrfonts[font]
+ if not used then
+ used = { }
+ attrfonts[font] = used
+ end
+ if not used[attr] then
+ local d = set_dynamics(font_ids[font],attr) -- todo, script, language -> n.language also axis
+ if d then
+ used[attr] = d
+ a = a + 1
+ end
+ end
+ prevfont, prevattr = font, attr
+ end
+ elseif font ~= prevfont then
+ prevfont, prevattr = font, 0
+ local used = usedfonts[font]
+ if not used then
+ local data = font_ids[font]
+ if data then
+ local shared = data.shared -- we need to check shared, only when same features
+ if shared then
+ local processors = shared.processors
+ if processors and #processors > 0 then
+ usedfonts[font] = processors
+ u = u + 1
+ end
+ end
+ else
+ -- probably nullfont
+ end
+ end
+ else
+ prevattr = attr
+ end
+ end
+ -- we could combine these and just make the attribute nil
+ if u > 0 then
+ for font, processors in pairs(usedfonts) do
+ local n = #processors
+ if n == 1 then
+ local h, d = processors[1](head,font,false)
+ head, done = h or head, done or d
+ else
+ for i=1,#processors do
+ local h, d = processors[i](head,font,false)
+ head, done = h or head, done or d
+ end
+ end
+ end
+ end
+ if a > 0 then -- we need to get rid of a loop here
+ for font, dynamics in pairs(attrfonts) do
+ for attribute, processors in pairs(dynamics) do -- attr can switch in between
+ local n = #processors
+ if n == 1 then
+ local h, d = processors[1](head,font,attribute)
+ head, done = h or head, done or d
+ else
+ for i=1,n do
+ local h, d = processors[i](head,font,attribute)
+ head, done = h or head, done or d
+ end
+ end
+ end
+ end
+ end
+ stoptiming(nodes)
+ if nodes.trace then
+ nodes.report(head,done)
+ end
+ return head, true
+ 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
+
+do
+
+ local has_attribute, set, attribute = node.has_attribute, node.set_attribute, tex.attribute
+
+ function nodes.inherit_attributes(n) -- still ok ?
+ if n then
+ local i = 1
+ while true do
+ local a = attribute[i]
+ if a < 0 then
+ break
+ else
+ local ai = has_attribute(n,i)
+ if not ai then
+ set(n,i,a)
+ end
+ i = i + 1
+ end
+ end
+ end
+ end
+
+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
+
+--~ nodes.processors.actions = nodes.processors.actions or { }
+
+--~ function nodes.processors.action(head)
+--~ if head then
+--~ node.slide(head)
+--~ local done = false
+--~ local actions = nodes.processors.actions
+--~ for i=1,#actions do
+--~ local h, ok = actions[i](head)
+--~ if ok then
+--~ head, done = h, true
+--~ end
+--~ end
+--~ if done then
+--~ return head
+--~ else
+--~ return true
+--~ end
+--~ else
+--~ return head
+--~ end
+--~ end
+
+lists = lists or { }
+lists.plugins = lists.plugins or { }
+
+chars = chars or { }
+chars.plugins = chars.plugins or { }
+
+--~ words = words or { }
+--~ words.plugins = words.plugins or { }
+
+callbacks.trace = false
+
+do
+
+ kernel = kernel or { }
+
+ local starttiming, stoptiming = input.starttiming, input.stoptiming
+ local hyphenate, ligaturing, kerning = lang.hyphenate, node.ligaturing, node.kerning
+
+ function kernel.hyphenation(head,tail) -- lang.hyphenate returns done
+ starttiming(kernel)
+ local done = hyphenate(head,tail)
+ stoptiming(kernel)
+ return head, tail, done
+ end
+ function kernel.ligaturing(head,tail) -- node.ligaturing returns head,tail,done
+ starttiming(kernel)
+ local head, tail, done = ligaturing(head,tail)
+ stoptiming(kernel)
+ return head, tail, done
+ end
+ function kernel.kerning(head,tail) -- node.kerning returns head,tail,done
+ starttiming(kernel)
+ local head, tail, done = kerning(head,tail)
+ stoptiming(kernel)
+ return head, tail, done
+ end
+
+end
+
+callback.register('hyphenate' , function(head,tail) return tail end)
+callback.register('ligaturing', function(head,tail) return tail end)
+callback.register('kerning' , function(head,tail) return tail end)
+
+-- used to be loop, this is faster, called often; todo: shift up tail or even better,
+-- handle tail everywhere; for the moment we're safe
+
+do
+
+ local charplugins, listplugins = chars.plugins, lists.plugins
+
+ nodes.processors.actions = function(head,tail) -- removed: if head ... end
+ local ok, done = false, false
+ head, ok = nodes.process_attributes(head,charplugins) ; done = done or ok -- attribute driven
+ head, tail, ok = kernel.hyphenation (head,tail) ; done = done or ok -- language driven
+ head, ok = languages.words.check (head,tail) ; done = done or ok -- language driven
+ head, ok = nodes.process_characters(head) ; done = done or ok -- font driven
+ ok = nodes.protect_glyphs (head) ; done = done or ok -- turn chars into glyphs
+ head, tail, ok = kernel.ligaturing (head,tail) ; done = done or ok -- normal ligaturing routine / needed for base mode
+ head, tail, ok = kernel.kerning (head,tail) ; done = done or ok -- normal kerning routine / needed for base mode
+ head, ok = nodes.process_attributes(head,listplugins) ; done = done or ok -- attribute driven
+ return head, done
+ end
+
+end
+
+do
+
+ local actions = nodes.processors.actions
+ local first_character = node.first_character
+ local slide = node.slide
+
+ local function tracer(what,state,head,groupcode,glyphcount)
+ texio.write_nl(string.format("%s %s: group: %s, nodes: %s",
+ (state and "Y") or "N", what, groupcode or "?", nodes.count(head,true)))
+ end
+
+ function nodes.processors.pre_linebreak_filter(head,groupcode) -- todo: tail
+ local first, found = first_character(head)
+ if found then
+ if callbacks.trace then tracer("pre_linebreak",true,head,groupcode) end
+ local head, done = actions(head,slide(head))
+ return (done and head) or true
+ else
+ if callbacks.trace then tracer("pre_linebreak",false,head,groupcode) end
+ return true
+ end
+ end
+
+ function nodes.processors.hpack_filter(head,groupcode) -- todo: tail
+ local first, found = first_character(head)
+ if found then
+ if callbacks.trace then tracer("hpack",true,head,groupcode) end
+ local head, done = actions(head,slide(head))
+ return (done and head) or true
+ end
+ if callbacks.trace then tracer("hpack",false,head,groupcode) end
+ return true
+ end
+
+end
+
+callback.register('pre_linebreak_filter', nodes.processors.pre_linebreak_filter)
+callback.register('hpack_filter' , nodes.processors.hpack_filter)
+
+do
+
+ -- beware, some field names will change in a next release of luatex
+
+ local expand = table.tohash {
+ "list", -- list_ptr & ins_ptr & adjust_ptr
+ "pre", --
+ "post", --
+ "spec", -- glue_ptr
+ "top_skip", --
+ "attr", --
+ "replace", -- nobreak
+ "components", -- lig_ptr
+ "box_left", --
+ "box_right", --
+ "glyph", -- margin_char
+ "leader", -- leader_ptr
+ "action", -- action_ptr
+ "value", -- user_defined nodes with subtype 'a' en 'n'
+ }
+
+ -- page_insert: "height", "last_ins_ptr", "best_ins_ptr"
+ -- split_insert: "height", "last_ins_ptr", "best_ins_ptr", "broken_ptr", "broken_ins"
+
+ local ignore = table.tohash {
+ "page_insert",
+ "split_insert",
+ "ref_count",
+ }
+
+ local dimension = table.tohash {
+ "width", "height", "depth", "shift",
+ "stretch", "shrink",
+ "xoffset", "yoffset",
+ "surround",
+ "kern",
+ "box_left_width", "box_right_width"
+ }
+
+ -- flat: don't use next, but indexes
+ -- verbose: also add type
+ -- can be sped up
+
+ nodes.dimensionfields = dimension
+ nodes.listablefields = expand
+ nodes.ignorablefields = ignore
+
+ -- not ok yet:
+
+ function nodes.astable(n,sparse) -- not yet ok
+ local f, t = node.fields(n.id,n.subtype), { }
+ for i=1,#f do
+ local v = f[i]
+ local d = n[v]
+ if d then
+ if ignore[v] or v == "id" then
+ -- skip
+ elseif expand[v] then -- or: type(n[v]) ~= "string" or type(n[v]) ~= "number" or type(n[v]) ~= "table"
+ t[v] = "pointer to list"
+ elseif sparse then
+ if (type(d) == "number" and d ~= 0) or (type(d) == "string" and d ~= "") then
+ t[v] = d
+ end
+ else
+ t[v] = d
+ end
+ end
+ end
+ t.type = node.type(n.id)
+ return t
+ end
+
+ local nodefields = node.fields
+ local nodetype = node.type
+
+ -- under construction:
+
+ local function totable(n,flat,verbose)
+ local function to_table(n)
+ local f = nodefields(n.id,n.subtype)
+ local tt = { }
+ for k=1,#f do
+ local v = f[k]
+ local nv = n[v]
+ if nv then
+ if ignore[v] then
+ -- skip
+ elseif expand[v] then
+ if type(nv) == "number" or type(nv) == "string" then
+ tt[v] = nv
+ else
+ tt[v] = totable(nv,flat,verbose)
+ end
+ elseif type(nv) == "table" then
+ tt[v] = nv -- totable(nv,flat,verbose) -- data
+ else
+ tt[v] = nv
+ end
+ end
+ end
+ if verbose then
+ tt.type = nodetype(tt.id)
+ end
+ return tt
+ end
+ if n then
+ if flat then
+ local t = { }
+ while n do
+ t[#t+1] = to_table(n)
+ n = n.next
+ end
+ return t
+ else
+ local t = to_table(n)
+ if n.next then
+ t.next = totable(n.next,flat,verbose)
+ end
+ return t
+ end
+ else
+ return { }
+ end
+ end
+
+ nodes.totable = totable
+
+ local function key(k)
+ return ((type(k) == "number") and "["..k.."]") or k
+ end
+
+ -- not ok yet:
+
+ 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 = ""
+ local tname = type(name)
+ if tname == "string" then
+ if name == "return" then
+ handle("return {")
+ else
+ handle(name .. "={")
+ end
+ elseif tname == "number"then
+ handle("[" .. name .. "]={")
+ else
+ handle("t={")
+ end
+ end
+ if root then
+ local fld
+ if root.id then
+ fld = nodefields(root.id,root.subtype) -- we can cache these (todo)
+ 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 == "ref_count" then
+ -- skip
+ elseif k then
+ local v = root[k]
+ local t = type(v)
+ if t == "number" then
+ if v == 0 then
+ -- skip
+ else
+ handle(("%s %s=%s,"):format(depth,key(k),v))
+ end
+ elseif t == "string" then
+ if v == "" then
+ -- skip
+ else
+ handle(("%s %s=%q,"):format(depth,key(k),v))
+ end
+ elseif v then -- userdata or table
+ serialize(v,k,handle,depth,m+1)
+ end
+ end
+ end
+ if root['next'] then -- userdata or table
+ serialize(root['next'],'next',handle,depth,m+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, 0)
+ 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
+
+ function nodes.check_for_leaks(sparse)
+ local l = { }
+ local q = node.usedlist()
+ for p in node.traverse(q) do
+ local s = table.serialize(nodes.astable(p,sparse),node.type(p.id))
+ l[s] = (l[s] or 0) + 1
+ end
+ node.flush_list(q)
+ for k, v in pairs(l) do
+ texio.write_nl(string.format("%s * %s", v, k))
+ end
+ end
+
+end
+
+if not node.list_has_attribute then -- no longer needed
+
+ 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
+
+do
+
+ local glue, whatsit, hlist = node.id("glue"), node.id("whatsit"), node.id("hlist")
+
+ function nodes.leftskip(n)
+ while n do
+ local id = n.id
+ if id == glue then
+ if n.subtype == 8 then -- 7 in c/web source
+ return (n.spec and n.spec.width) or 0
+ else
+ return 0
+ end
+ elseif id == whatsit then
+ n = n.next
+ elseif id == hlist then
+ return n.width
+ else
+ break
+ end
+ end
+ return 0
+ end
+ function nodes.rightskip(n)
+ if n then
+ n = node.slide(n)
+ while n do
+ local id = n.id
+ if id == glue then
+ if n.subtype == 9 then -- 8 in the c/web source
+ return (n.spec and n.spec.width) or 0
+ else
+ return 0
+ end
+ elseif id == whatsit then
+ n = n.prev
+ else
+ break
+ end
+ end
+ end
+ return false
+ end
+
+end
+
+-- goodie
+--
+-- if node.valid(tex.box[0]) then print("valid node") end
+
+--~ do
+--~ local n = node.new(0,0)
+--~ local m = getmetatable(n)
+--~ m.__metatable = 'node'
+--~ node.free(n)
+
+--~ function node.valid(n)
+--~ return n and getmetatable(n) == 'node'
+--~ end
+--~ end
+
+-- for the moment we put this here:
+
+do
+
+ nodes.tracers = { }
+ nodes.tracers.characters = { }
+
+ local glyph, disc = node.id('glyph'), node.id('disc')
+
+ local fontdata = fonts.tfm.id
+
+ local function collect(head,list,tag,n)
+ n = n or 0
+ local ok, fn = false, nil
+ while head do
+ local id = head.id
+ if id == glyph then
+ local f = head.font
+ if f ~= fn then
+ ok, fn = false, f
+ end
+ local c = head.char
+ local d = fontdata[f].characters[c]
+ local i = (d and d.description.index) or -1
+ if not ok then
+ ok = true
+ n = n + 1
+ list[n] = list[n] or { }
+ list[n][tag] = { }
+ end
+ local l = list[n][tag]
+ l[#l+1] = { c, f, i }
+ elseif id == disc then
+ -- skip
+ else
+ ok = false
+ end
+ head = head.next
+ end
+ end
+
+ function nodes.tracers.characters.equal(ta, tb)
+ if #ta ~= #tb then
+ return false
+ else
+ for i=1,#ta do
+ local a, b = ta[i], tb[i]
+ if a[1] ~= b[1] or a[2] ~= b[2] or a[3] ~= b[3] then
+ return false
+ end
+ end
+ end
+ return true
+ end
+ function nodes.tracers.characters.string(t)
+ local tt = { }
+ for i=1,#t do
+ tt[i] = utf.char(t[i][1])
+ end
+ return table.concat(tt,"")
+ end
+ function nodes.tracers.characters.unicodes(t,decimal)
+ local tt = { }
+ for i=1,#t do
+ if decimal then
+ tt[i] = t[i][1]
+ else
+ tt[i] = string.format("%04X",t[i][1])
+ end
+ end
+ return table.concat(tt," ")
+ end
+ function nodes.tracers.characters.indices(t,decimal)
+ local tt = { }
+ for i=1,#t do
+ if decimal then
+ tt[i] = t[i][3]
+ else
+ tt[i] = string.format("%04X",t[i][3])
+ end
+ end
+ return table.concat(tt," ")
+ end
+ function nodes.tracers.characters.fonts(t)
+ local f = t[1] and t[1][2]
+ return (f and file.basename(fontdata[f].filename or "unknown")) or "unknown"
+ end
+
+ function nodes.tracers.characters.start()
+ local npc = nodes.process_characters
+ local list = { }
+ function nodes.process_characters(head)
+ local n = #list
+ collect(head,list,'before',n)
+ local h, d = npc(head)
+ collect(head,list,'after',n)
+ if #list > n then
+ list[#list+1] = { }
+ end
+ return h, d
+ end
+ function nodes.tracers.characters.stop()
+ tracers.list['characters'] = list
+ lmx.set('title', 'ConTeXt Character Processing Information')
+ lmx.set('color-background-one', lmx.get('color-background-yellow'))
+ lmx.set('color-background-two', lmx.get('color-background-purple'))
+ lmx.show('context-characters.lmx')
+ lmx.restore()
+ nodes.process_characters = npc
+ end
+ end
+
+ local stack = { }
+
+ function nodes.tracers.start(tag)
+ stack[#stack+1] = tag
+ local tracer = nodes.tracers[tag]
+ if tracer and tracer.start then
+ tracer.start()
+ end
+ end
+ function nodes.tracers.stop()
+ local tracer = stack[#stack]
+ if tracer and tracer.stop then
+ tracer.stop()
+ end
+ stack[#stack] = nil
+ end
+
+end