summaryrefslogtreecommitdiff
path: root/tex/context/base/node-ref.lua
diff options
context:
space:
mode:
authorMarius <mariausol@gmail.com>2010-07-04 15:32:09 +0300
committerMarius <mariausol@gmail.com>2010-07-04 15:32:09 +0300
commit85b7bc695629926641c7cb752fd478adfdf374f3 (patch)
tree80293f5aaa7b95a500a78392c39688d8ee7a32fc /tex/context/base/node-ref.lua
downloadcontext-85b7bc695629926641c7cb752fd478adfdf374f3.tar.gz
stable 2010-05-24 13:10
Diffstat (limited to 'tex/context/base/node-ref.lua')
-rw-r--r--tex/context/base/node-ref.lua533
1 files changed, 533 insertions, 0 deletions
diff --git a/tex/context/base/node-ref.lua b/tex/context/base/node-ref.lua
new file mode 100644
index 000000000..7128b1a6d
--- /dev/null
+++ b/tex/context/base/node-ref.lua
@@ -0,0 +1,533 @@
+if not modules then modules = { } end modules ['node-bck'] = {
+ version = 1.001,
+ comment = "companion to node-bck.mkiv",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+-- We supported pdf right from the start and in mkii this has resulted in
+-- extensive control over the links. Nowadays pdftex provides a lot more
+-- control over margins but as mkii supports multiple backends we stuck to
+-- our own mechanisms. In mkiv again we implement our own handling. Eventually
+-- we will even disable the pdf primitives.
+
+-- helper, will end up in luatex
+
+local cleanupreferences, cleanupdestinations = false, true
+
+local nodeinjections = backends.nodeinjections
+local codeinjections = backends.codeinjections
+
+local hpack_list = node.hpack
+local list_dimensions = node.dimensions
+
+-- current.glue_set current.glue_sign
+
+local trace_backend = false trackers.register("nodes.backend", function(v) trace_backend = v end)
+local trace_references = false trackers.register("nodes.references", function(v) trace_references = v end)
+local trace_destinations = false trackers.register("nodes.destinations", function(v) trace_destinations = v end)
+
+local hlist = node.id("hlist")
+local vlist = node.id("vlist")
+local glue = node.id("glue")
+local whatsit = node.id("whatsit")
+
+local new_kern = nodes.kern
+
+local has_attribute = node.has_attribute
+local traverse = node.traverse
+local find_node_tail = node.tail or node.slide
+local tosequence = nodes.tosequence
+
+local function dimensions(parent,start,stop)
+ stop = stop and stop.next
+ if parent then
+ if stop then
+ return list_dimensions(parent.glue_set,parent.glue_sign,parent.glue_order,start,stop)
+ else
+ return list_dimensions(parent.glue_set,parent.glue_sign,parent.glue_order,start)
+ end
+ else
+ if stop then
+ return list_dimensions(start,stop)
+ else
+ return list_dimensions(start)
+ end
+ end
+end
+
+--~ more compact
+
+local function dimensions(parent,start,stop)
+ if parent then
+ return list_dimensions(parent.glue_set,parent.glue_sign,parent.glue_order,start,stop and stop.next)
+ else
+ return list_dimensions(start,stop and stop.next)
+ end
+end
+
+-- is pardir important at all?
+
+local function inject_range(head,first,last,reference,make,stack,parent,pardir,txtdir)
+ local width, height, depth = dimensions(parent,first,last)
+ if pardir == "TRT" or txtdir == "+TRT" then
+ width = - width
+ end
+ local result, resolved = make(width,height,depth,reference)
+ if result and resolved then
+ if head == first then
+ if trace_backend then
+ logs.report("backend","head: %04i %s %s %s => w=%s, h=%s, d=%s, c=%s",reference,pardir or "---",txtdir or "----",tosequence(first,last,true),width,height,depth,resolved)
+ end
+ result.next = first
+ first.prev = result
+ return result, last
+ else
+ if trace_backend then
+ logs.report("backend","middle: %04i %s %s => w=%s, h=%s, d=%s, c=%s",reference,pardir or "---",txtdir or "----",tosequence(first,last,true),width,height,depth,resolved)
+ end
+ local prev = first.prev
+ if prev then
+ result.next = first
+ result.prev = prev
+ prev.next = result
+ first.prev = result
+ else
+ result.next = first
+ first.prev = result
+ end
+ if first == head.next then
+ head.next = result -- hm, weird
+ end
+ return head, last
+ end
+ else
+ return head, last
+ end
+end
+
+local function inject_list(id,current,reference,make,stack,pardir,txtdir)
+ local width, height, depth, correction = current.width, current.height, current.depth, 0
+ local moveright = false
+ local first = current.list
+ if id == hlist then
+ -- can be either an explicit hbox or a line and there is no way
+ -- to recognize this; anyway only if ht/dp (then inline)
+ --
+ -- to be tested: 0=unknown, 1=linebreak, 2=hbox
+--~ if id.subtype == 1 then
+ local sr = stack[reference]
+ if first then
+ if sr and sr[2] then
+ local last = find_node_tail(first)
+ if last.id == glue and last.subtype == 9 then
+ local prev = last.prev
+ moveright = first.id == glue and first.subtype == 8
+ if prev and prev.id == glue and prev.subtype == 15 then
+ width = dimensions(current,first,prev.prev) -- maybe not current as we already take care of it
+ else
+ if moveright and first.writable then
+ width = width - first.spec.stretch*current.glue_set * current.glue_sign
+ end
+ if last.writable then
+ width = width - last.spec.stretch*current.glue_set * current.glue_sign
+ end
+ end
+ end
+ else
+ -- also weird
+ end
+--~ else
+--~ print("!!!!!!!!!!!!!!!!!")
+ -- simple
+--~ end
+ else
+ -- ok
+ end
+ correction = width
+ else
+ correction = height + depth
+ height, depth = depth, height -- ugly hack, needed because pdftex backend does something funny
+ end
+ if pardir == "TRT" then
+ width = - width
+ end
+ local result, resolved = make(width,height,depth,reference)
+ if result and resolved then
+ if trace_backend then
+ logs.report("backend","box: %04i %s %s: w=%s, h=%s, d=%s, c=%s",reference,pardir or "---",txtdir or "----",width,height,depth,resolved)
+ end
+ if not first then
+ current.list = result
+ elseif moveright then -- brr no prevs done
+ -- result after first
+ local n = first.next
+ result.next = n
+ first.next = result
+ result.prev = first
+ if n then n.prev = result end
+ else
+ -- first after result
+ result.next = first
+ first.prev = result
+ current.list = result
+ end
+ end
+end
+
+-- skip is somewhat messy
+
+local function inject_areas(head,attribute,make,stack,done,skip,parent,pardir,txtdir) -- main
+ if head then
+ local current, first, last, firstdir, reference = head, nil, nil, nil, nil
+ pardir = pardir or "==="
+ txtdir = txtdir or "==="
+ while current do
+ local id = current.id
+ local r = has_attribute(current,attribute)
+ if id == whatsit then
+ local subtype = current.subtype
+ if subtype == 6 then
+ pardir = current.dir
+ elseif subtype == 7 then
+ txtdir = current.dir
+ end
+ elseif id == hlist or id == vlist then
+ if not reference and r and (not skip or r > skip) then
+ inject_list(id,current,r,make,stack,pardir,txtdir)
+ end
+ if r then
+ done[r] = (done[r] or 0) + 1
+ end
+ local list = current.list
+ if list then
+ local _
+ current.list, _, pardir, txtdir = inject_areas(list,attribute,make,stack,done,r or skip or 0,current,pardir,txtdir)
+ end
+ if r then
+ done[r] = done[r] - 1
+ end
+ elseif not r then
+ -- just go on, can be kerns
+ elseif not reference then
+ reference, first, last, firstdir = r, current, current, txtdir
+ elseif r == reference then
+ last = current
+ elseif (done[reference] or 0) == 0 then
+ if not skip or r > skip then
+ head, current = inject_range(head,first,last,reference,make,stack,parent,pardir,firstdir)
+ reference, first, last, firstdir = nil, nil, nil, nil
+ end
+ else
+ reference, first, last, firstdir = r, current, current, txtdir
+ end
+ current = current.next
+ end
+ if reference and (done[reference] or 0) == 0 then
+ head = inject_range(head,first,last,reference,make,stack,parent,pardir,firstdir)
+ end
+ end
+ return head, true, pardir, txtdir
+end
+
+local function inject_area(head,attribute,make,stack,done,parent,pardir,txtdir) -- singular !
+ if head then
+ pardir = pardir or "==="
+ txtdir = txtdir or "==="
+ local current = head
+ while current do
+ local id = current.id
+ local r = has_attribute(current,attribute)
+ if id == whatsit then
+ local subtype = current.subtype
+ if subtype == 6 then
+ pardir = current.dir
+ elseif subtype == 7 then
+ txtdir = current.dir
+ end
+ elseif id == hlist or id == vlist then
+ if r and not done[r] then
+ done[r] = true
+ inject_list(id,current,r,make,stack,pardir,txtdir)
+ end
+ current.list = inject_area(current.list,attribute,make,stack,done,current,pardir,txtdir)
+ elseif r and not done[r] then
+ done[r] = true
+ head, current = inject_range(head,current,current,r,make,stack,parent,pardir,txtdir)
+ end
+ current = current.next
+ end
+ end
+ return head, true
+end
+
+-- tracing
+
+local new_rule = nodes.rule
+local new_kern = nodes.kern
+local set_attribute = node.set_attribute
+local register_color = colors.register
+
+local a_colormodel = attributes.private('colormodel')
+local a_color = attributes.private('color')
+local a_transparency = attributes.private('transparency')
+local u_transparency = nil
+local u_colors = { }
+local force_gray = true
+
+local function colorize(width,height,depth,n)
+ if force_gray then n = 0 end
+ u_transparency = u_transparency or transparencies.register(nil,2,.65)
+ local ucolor = u_colors[n]
+ if not ucolor then
+ if n == 1 then
+ u_color = register_color(nil,'rgb',.75,0,0)
+ elseif n == 2 then
+ u_color = register_color(nil,'rgb',0,.75,0)
+ elseif n == 3 then
+ u_color = register_color(nil,'rgb',0,0,.75)
+ else
+ n = 0
+ u_color = register_color(nil,'gray',.5)
+ end
+ u_colors[n] = u_color
+ end
+ local rule = new_rule(width,height,depth)
+ set_attribute(rule,a_colormodel,1) -- gray color model
+ set_attribute(rule,a_color,u_color)
+ set_attribute(rule,a_transparency,u_transparency)
+ if width < 0 then
+ local kern = new_kern(width)
+ rule.width = -width
+ kern.next = rule
+ rule.prev = kern
+ return kern
+ else
+ return rule
+ end
+end
+
+local new_kern = nodes.kern
+local texattribute = tex.attribute
+local texcount = tex.count
+
+-- references:
+
+nodes.references = {
+ attribute = attributes.private('reference'),
+ stack = { },
+ done = { },
+}
+
+local stack, done, attribute = nodes.references.stack, nodes.references.done, nodes.references.attribute
+
+local nofreferences, topofstack = 0, 0
+
+local function setreference(n,h,d,r) -- n is just a number, can be used for tracing
+ topofstack = topofstack + 1
+ stack[topofstack] = { n, h, d, codeinjections.prerollreference(r) } -- the preroll permits us to determine samepage (but delayed also has some advantages)
+--~ texattribute[attribute] = topofstack -- todo -> at tex end
+ texcount.lastreferenceattribute = topofstack
+end
+
+nodes.setreference = setreference
+
+local function makereference(width,height,depth,reference)
+ local sr = stack[reference]
+ if sr then
+ local resolved, ht, dp, set = sr[1], sr[2], sr[3], sr[4]
+ if ht then
+ if height < ht then height = ht end
+ if depth < dp then depth = dp end
+ end
+ local annot = nodeinjections.reference(width,height,depth,set)
+ if annot then
+ nofreferences = nofreferences + 1
+ local result, current
+ if trace_references then
+ local step = 65536
+ result = hpack_list(colorize(width,height-step,depth-step,2)) -- step subtracted so that we can see seperate links
+ result.width = 0
+ current = result
+ end
+ if current then
+ current.next = annot
+ else
+ result = annot
+ end
+ result = hpack_list(result,0)
+ result.width, result.height, result.depth = 0, 0, 0
+ if cleanupreferences then stack[reference] = nil end
+ return result, resolved
+ else
+ logs.report("backends","unable to resolve reference annotation %s",reference)
+ end
+ else
+ logs.report("backends","unable to resolve reference attribute %s",reference)
+ end
+end
+
+function nodes.add_references(head)
+ if topofstack > 0 then
+ return inject_areas(head,attribute,makereference,stack,done)
+ else
+ return head, false
+ end
+end
+
+-- destinations (we can clean up once set!)
+
+nodes.destinations = {
+ attribute = attributes.private('destination'),
+ stack = { },
+ done = { },
+}
+
+local stack, done, attribute = nodes.destinations.stack, nodes.destinations.done, nodes.destinations.attribute
+
+local nofdestinations, topofstack = 0, 0
+
+local function setdestination(n,h,d,name,view) -- n = grouplevel, name == table
+ topofstack = topofstack + 1
+ stack[topofstack] = { n, h, d, name, view }
+ return topofstack
+end
+
+nodes.setdestination = setdestination
+
+local function makedestination(width,height,depth,reference)
+ local sr = stack[reference]
+ if sr then
+ local resolved, ht, dp, name, view = sr[1], sr[2], sr[3], sr[4], sr[5]
+ if ht then
+ if height < ht then height = ht end
+ if depth < dp then depth = dp end
+ end
+ local result, current
+ if trace_destinations then
+ local step = 0
+ if width == 0 then
+ step = 4*65536
+ width, height, depth = 5*step, 5*step, 0
+ end
+ for n=1,#name do
+ local rule = hpack_list(colorize(width,height,depth,3))
+ rule.width = 0
+ if not result then
+ result, current = rule, rule
+ else
+ current.next = rule
+ rule.prev = current
+ current = rule
+ end
+ width, height = width - step, height - step
+ end
+ end
+ nofdestinations = nofdestinations + 1
+ for n=1,#name do
+ local annot = nodeinjections.destination(width,height,depth,name[n],view)
+ if not result then
+ result, current = annot, annot
+ else
+ current.next = annot
+ annot.prev = current
+ current = annot
+ end
+ end
+ result = hpack_list(result,0)
+ result.width, result.height, result.depth = 0, 0, 0
+ if cleanupdestinations then stack[reference] = nil end
+ return result, resolved
+ else
+ logs.report("backends","unable to resolve destination attribute %s",reference)
+ end
+end
+
+function nodes.add_destinations(head)
+ if topofstack > 0 then
+ return inject_area(head,attribute,makedestination,stack,done) -- singular
+ else
+ return head, false
+ end
+end
+
+-- will move
+
+function jobreferences.mark(reference,h,d,view)
+ return setdestination(tex.currentgrouplevel,h,d,reference,view)
+end
+
+function jobreferences.inject(prefix,reference,h,d,highlight,newwindow,layer) -- todo: use currentreference is possible
+ local set, bug = jobreferences.identify(prefix,reference)
+ if bug or #set == 0 then
+ -- unknown ref, just don't set it and issue an error
+ else
+ -- check
+ set.highlight, set.newwindow,set.layer = highlight, newwindow, layer
+ setreference(tex.currentgrouplevel,h,d,set) -- sets attribute / todo: for set[*].error
+ end
+end
+
+function jobreferences.injectcurrentset(h,d) -- used inside doifelse
+ local currentset = jobreferences.currentset
+ if currentset then
+ setreference(tex.currentgrouplevel,h,d,currentset) -- sets attribute / todo: for set[*].error
+ end
+end
+
+--
+
+local function checkboth(open,close)
+ if open and open ~= "" then
+ local set, bug = jobreferences.identify("",open)
+ open = not bug and #set > 0 and set
+ end
+ if close and close ~= "" then
+ local set, bug = jobreferences.identify("",close)
+ close = not bug and #set > 0 and set
+ end
+ return open, close
+end
+
+-- expansion is temp hack
+
+local opendocument, closedocument, openpage, closepage
+
+local function check(what)
+ if what and what ~= "" then
+ local set, bug = jobreferences.identify("",what)
+ return not bug and #set > 0 and set
+ end
+end
+
+function jobreferences.checkopendocumentactions (open) opendocument = check(open) end
+function jobreferences.checkclosedocumentactions(close) closedocument = check(close) end
+function jobreferences.checkopenpageactions (open) openpage = check(open) end
+function jobreferences.checkclosepageactions (close) closepage = check(close) end
+
+function jobreferences.flushdocumentactions()
+ if opendocument or closedocument then
+ backends.codeinjections.flushdocumentactions(opendocument,closedocument) -- backend
+ end
+end
+function jobreferences.flushpageactions()
+ if openpage or closepage then
+ backends.codeinjections.flushpageactions(openpage,closepage) -- backend
+ end
+end
+
+-- end temp hack
+
+statistics.register("interactive elements", function()
+ if nofreferences > 0 or nofdestinations > 0 then
+ return string.format("%s references, %s destinations",nofreferences,nofdestinations)
+ else
+ return nil
+ end
+end)
+
+function jobreferences.enable_interaction()
+ tasks.enableaction("shipouts","nodes.add_references")
+ tasks.enableaction("shipouts","nodes.add_destinations")
+end