if not modules then modules = { } end modules ['lpdf-ano'] = { version = 1.001, comment = "companion to lpdf-ini.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } local tostring, format, rep = tostring, string.rep, string.format local texcount = tex.count local trace_references = false trackers.register("references.references", function(v) trace_references = v end) local trace_destinations = false trackers.register("references.destinations", function(v) trace_destinations = v end) local trace_bookmarks = false trackers.register("references.bookmarks", function(v) trace_bookmarks = v end) local variables = interfaces.variables local constants = interfaces.constants local nodeinjections = backends.pdf.nodeinjections local codeinjections = backends.pdf.codeinjections local registrations = backends.pdf.registrations jobreferences = jobreferences or { } jobreferences.runners = jobreferences.runners or { } jobreferences.specials = jobreferences.specials or { } jobreferences.handlers = jobreferences.handlers or { } jobreferences.executers = jobreferences.executers or { } local runners = jobreferences.runners local specials = jobreferences.specials local handlers = jobreferences.handlers local executers = jobreferences.executers local pdfdictionary = lpdf.dictionary local pdfarray = lpdf.array local pdfreference = lpdf.reference local pdfunicode = lpdf.unicode local pdfconstant = lpdf.constant local pdfflushobject = lpdf.flushobject local pdfreserveobject = lpdf.reserveobject local pdfannotation = nodes.pdfannotation local pdfdestination = nodes.pdfdestination local pdfpagereference = tex.pdfpageref local pdf_uri = pdfconstant("URI") local pdf_gotor = pdfconstant("GoToR") local pdf_goto = pdfconstant("GoTo") local pdf_launch = pdfconstant("Launch") local pdf_javascript = pdfconstant("JavaScript") local pdf_link = pdfconstant("Link") local pdf_n = pdfconstant("N") local pdf_t = pdfconstant("T") local pdf_border = pdfarray { 0, 0, 0 } local cache = { } local function pagedest(n) local pd = cache[n] if not pd then local a = pdfarray { pdfreference(pdfpagereference(n)), pdfconstant("Fit") } pd = pdfreference(pdfflushobject(a)) cache[n] = pd end return pd end lpdf.pagedest = pagedest local defaultdestination = pdfarray { 0, pdfconstant("Fit") } local function link(url,filename,destination,page,actions) if filename and filename ~= "" then if file.basename(filename) == tex.jobname then return false else filename = file.addsuffix(filename,"pdf") end end if url and url ~= "" then if filename and filename ~= "" then if destination and destination ~= "" then url = file.join(url,filename).."#"..destination else url = file.join(url,filename) end end return pdfdictionary { S = pdf_uri, URI = url, } elseif filename and filename ~= "" then return pdfdictionary { S = pdf_gotor, -- can also be pdf_launch F = filename, D = (destination and destination ~= "" and destination), -- or defaultdestination, NewWindow = (actions.newwindow and true) or nil, } elseif destination and destination ~= "" then local realpage, p = texcount.realpageno, tonumber(page) if not p then -- sorry elseif p > realpage then texcount.referencepagestate = 3 elseif p < realpage then texcount.referencepagestate = 2 else texcount.referencepagestate = 1 end return pdfdictionary { S = pdf_goto, D = destination, } elseif page and page ~= "" then local realpage, p = texcount.realpageno, tonumber(page) if p then if p > realpage then texcount.referencepagestate = 3 elseif p < realpage then texcount.referencepagestate = 2 else texcount.referencepagestate = 1 end return pdfdictionary { S = pdf_goto, D = pagedest(p), } else commands.writestatus("references","invalid page reference: %s",page or "?") end end return false end lpdf.link = link function lpdf.launch(program,parameters) if program and program ~= "" then local d = pdfdictionary { S = pdf_launch, F = program, D = ".", } if parameters and parameters ~= "" then d.P = parameters end return d end end function lpdf.javascript(name,arguments) local script = javascripts.code(name,arguments) -- make into object (hash) if script then return pdfdictionary { S = pdf_javascript, JS = script, } end end local function pdfaction(actions) local nofactions = #actions texcount.referencepagestate = 0 -- goodie, as we do all in the backend, we need to set it here too if nofactions > 0 then local a = actions[1] local action = runners[a.kind] if action then action = action(a,actions) end if action then local first = action for i=2,nofactions do local a = actions[i] local what = runners[a.kind] if what then what = what(a,actions) end if what then action.next = what action = what else -- error return nil end end return first end end end lpdf.pdfaction = pdfaction function codeinjections.prerollreference(actions) local main = actions and pdfaction(actions) if main then main = pdfdictionary { Subtype = pdf_link, Border = pdf_border, H = (not actions.highlight and pdf_n) or nil, A = main, -- does not work at all in spite of specification -- OC = (actions.layer and lpdf.layerreferences[actions.layer]) or nil, -- OC = backends.pdf.layerreference(actions.layer), } return main("A") -- todo: cache this, maybe weak end end -- local cache = { } -- no real gain in thsi -- -- function codeinjections.prerollreference(actions) -- local main = actions and pdfaction(actions) -- if main then -- main = pdfdictionary { -- Subtype = pdf_link, -- Border = pdf_border, -- H = (not actions.highlight and pdf_n) or nil, -- A = main, -- } -- local cm = cache[main] -- if not cm then -- cm = "/A ".. tostring(pdfreference(pdfflushobject(main)) -- cache[main] = cm -- end -- return cm -- end -- end function nodeinjections.reference(width,height,depth,prerolled) if prerolled then if swapdir then width = - width end if trace_references then logs.report("references","w=%s, h=%s, d=%s, a=%s",width,height,depth,prerolled) end return pdfannotation(width,height,depth,prerolled) end end function nodeinjections.destination(width,height,depth,name,view) if swapdir then width = - width end if trace_destinations then logs.report("destinations","w=%s, h=%s, d=%s, n=%s, v=%s",width,height,depth,name,view or "no view") end return pdfdestination(width,height,depth,name,view) end -- runners and specials local method = "internal" runners["inner"] = function(var,actions) if method == "internal" then local vir = var.i.references local internal = vir and vir.internal if internal then var.inner = "aut:"..internal end end return link(nil,nil,var.inner,var.r,actions) end runners["inner with arguments"] = function(var,actions) logs.report("references","todo: inner with arguments") return false end runners["outer"] = function(var,actions) local file, url = jobreferences.checkedfileorurl(var.outer,var.outer) return link(url,file,var.arguments,nil,actions) end runners["outer with inner"] = function(var,actions) local file = jobreferences.checkedfile(var.f) return link(nil,file,var.inner,var.r,actions) end runners["special outer with operation"] = function(var,actions) local handler = specials[var.special] return handler and handler(var,actions) end runners["special outer"] = function(var,actions) logs.report("references","todo: special outer") return false end runners["special"] = function(var,actions) local handler = specials[var.special] return handler and handler(var,actions) end runners["outer with inner with arguments"] = function(var,actions) logs.report("references","todo: outer with inner with arguments") return false end runners["outer with special and operation and arguments"] = function(var,actions) logs.report("references","todo: outer with special and operation and arguments") return false end runners["outer with special"] = function(var,actions) logs.report("references","todo: outer with special") return false end runners["outer with special and operation"] = function(var,actions) logs.report("references","todo: outer with special and operation") return false end runners["special operation"] = runners["special"] runners["special operation with arguments"] = runners["special"] function specials.internal(var,actions) -- better resolve in strc-ref local i = tonumber(var.operation) local v = jobreferences.internals[i] if not v then -- error elseif method == "internal" then -- named return link(nil,nil,"aut:"..i,v.references.realpage,actions) else -- page return link(nil,nil,nil,v.references.realpage,actions) end end specials.i = specials.internal function specials.page(var,actions) -- better resolve in strc-ref local file = var.f if file then file = jobreferences.checkedfile(file) return link(nil,file,nil,p or var.operation,actions) else local p = jobreferences.pages[var.operation] if type(p) == "function" then p = p() end return link(nil,nil,nil,p or var.operation,actions) end end -- todo, do this in references namespace ordered instead (this is an experiment) local splitter = lpeg.splitat(":") function specials.order(var,actions) -- jobreferences.specials ! local operation = var.operation if operation then local kind, name, n = lpegmatch(splitter,operation) local order = lists.ordered[kind] order = order and order[name] local v = order[tonumber(n)] local r = v and v.references.realpage if r then var.operation = r -- brrr, but test anyway return specials.page(var,actions) end end end function specials.url(var,actions) local url = jobreferences.checkedurl(var.operation) return link(url,nil,var.arguments,nil,actions) end function specials.file(var,actions) local file = jobreferences.checkedfile(var.operation) return link(nil,file,var.arguments,nil,actions) end function specials.fileorurl(var,actions) local file, url = jobreferences.checkedfileorurl(var.operation,var.operation) return link(url,file,var.arguments,nil,actions) end function specials.program(var,content) local program = jobreferences.checkedprogram(var.operation) return lpdf.launch(program,var.arguments) end function specials.javascript(var) return lpdf.javascript(var.operation,var.arguments) end specials.JS = specials.javascript local pdf_named = pdfconstant("Named") executers.importform = pdfdictionary { S = pdf_named, N = pdfconstant("AcroForm:ImportFDF") } executers.exportform = pdfdictionary { S = pdf_named, N = pdfconstant("AcroForm:ExportFDF") } executers.first = pdfdictionary { S = pdf_named, N = pdfconstant("FirstPage") } executers.previous = pdfdictionary { S = pdf_named, N = pdfconstant("PrevPage") } executers.next = pdfdictionary { S = pdf_named, N = pdfconstant("NextPage") } executers.last = pdfdictionary { S = pdf_named, N = pdfconstant("LastPage") } executers.backward = pdfdictionary { S = pdf_named, N = pdfconstant("GoBack") } executers.forward = pdfdictionary { S = pdf_named, N = pdfconstant("GoForward") } executers.print = pdfdictionary { S = pdf_named, N = pdfconstant("Print") } executers.exit = pdfdictionary { S = pdf_named, N = pdfconstant("Quit") } executers.close = pdfdictionary { S = pdf_named, N = pdfconstant("Close") } executers.save = pdfdictionary { S = pdf_named, N = pdfconstant("Save") } executers.savenamed = pdfdictionary { S = pdf_named, N = pdfconstant("SaveAs") } executers.opennamed = pdfdictionary { S = pdf_named, N = pdfconstant("Open") } executers.help = pdfdictionary { S = pdf_named, N = pdfconstant("HelpUserGuide") } executers.toggle = pdfdictionary { S = pdf_named, N = pdfconstant("FullScreen") } executers.search = pdfdictionary { S = pdf_named, N = pdfconstant("Find") } executers.searchagain = pdfdictionary { S = pdf_named, N = pdfconstant("FindAgain") } executers.gotopage = pdfdictionary { S = pdf_named, N = pdfconstant("GoToPage") } executers.query = pdfdictionary { S = pdf_named, N = pdfconstant("AcroSrch:Query") } executers.queryagain = pdfdictionary { S = pdf_named, N = pdfconstant("AcroSrch:NextHit") } executers.fitwidth = pdfdictionary { S = pdf_named, N = pdfconstant("FitWidth") } executers.fitheight = pdfdictionary { S = pdf_named, N = pdfconstant("FitHeight") } local function fieldset(arguments) -- [\dogetfieldset{#1}] return nil end function executers.resetform(arguments) arguments = (type(arguments) == "table" and arguments) or aux.settings_to_array(arguments) return pdfdictionary { S = pdfconstant("ResetForm"), Field = fieldset(arguments[1]) } end local formmethod = "post" -- "get" "post" local formformat = "xml" -- "xml" "html" "fdf" -- bit 3 = html bit 6 = xml bit 4 = get local flags = { get = { html = 12, fdf = 8, xml = 40, }, post = { html = 4, fdf = 0, xml = 32, } } function executers.submitform(arguments) arguments = (type(arguments) == "table" and arguments) or aux.settings_to_array(arguments) local flag = flags[formmethod] or flags.post flag = (flag and (flag[formformat] or flag.xml)) or 32 -- default: post, xml return pdfdictionary { S = pdfconstant("ResetForm"), F = fieldset(arguments[1]), Field = fieldset(arguments[2]), Flags = flag, -- \PDFsubmitfiller } end function executers.hide(arguments) return pdfdictionary { S = pdfconstant("Hide"), H = true, T = arguments, } end function executers.show(arguments) return pdfdictionary { S = pdfconstant("Hide"), H = false, T = arguments, } end local pdf_movie = pdfconstant("Movie") local pdf_start = pdfconstant("Start") local pdf_stop = pdfconstant("Stop") local pdf_resume = pdfconstant("Resume") local pdf_pause = pdfconstant("Pause") local function movie_or_sound(operation,arguments) arguments = (type(arguments) == "table" and arguments) or aux.settings_to_array(arguments) return pdfdictionary { S = pdf_movie, T = format("movie %s",arguments[1] or "noname"), Operation = operation, } end function executers.startmovie (arguments) return movie_or_sound(pdf_start ,arguments) end function executers.stopmovie (arguments) return movie_or_sound(pdf_stop ,arguments) end function executers.resumemovie(arguments) return movie_or_sound(pdf_resume,arguments) end function executers.pausemovie (arguments) return movie_or_sound(pdf_pause ,arguments) end function executers.startsound (arguments) return movie_or_sound(pdf_start ,arguments) end function executers.stopsound (arguments) return movie_or_sound(pdf_stop ,arguments) end function executers.resumesound(arguments) return movie_or_sound(pdf_resume,arguments) end function executers.pausesound (arguments) return movie_or_sound(pdf_pause ,arguments) end function specials.action(var) local operation = var.operation if var.operation and operation ~= "" then local e = executers[operation] if type(e) == "table" then return e elseif type(e) == "function" then return e(var.arguments) end end end --~ entry.A = pdfdictionary { --~ S = pdfconstant("GoTo"), --~ D = .... --~ } local function build(levels,start,parent,method) local startlevel = levels[start][1] local i, n = start, 0 local child, entry, m, prev, first, last, f, l -- to be tested: i can be nil while i and i <= #levels do local li = levels[i] local level, title, reference, open = li[1], li[2], li[3], li[4] if level == startlevel then if trace_bookmarks then logs.report("bookmark","%3i %s%s %s",reference.realpage,rep(" ",level-1),(open and "+") or "-",title) end local prev = child child = pdfreserveobject() if entry then entry.Next = child and pdfreference(child) pdfflushobject(prev,entry) end entry = pdfdictionary { Title = pdfunicode(title), Parent = parent, Prev = prev and pdfreference(prev), } if method == "internal" then entry.Dest = "aut:" .. reference.internal else -- if method == "page" then entry.Dest = pagedest(reference.realpage) end if not first then first, last = child, child end prev = child last = prev n = n + 1 i = i + 1 elseif level < startlevel then pdfflushobject(child,entry) return i, n, first, last elseif i < #levels and level > startlevel then i, m, f, l = build(levels,i,pdfreference(child),method) entry.Count = (open and m) or -m if m > 0 then entry.First, entry.Last = pdfreference(f), pdfreference(l) end else -- missing intermediate level but ok i, m, f, l = build(levels,i,pdfreference(child),method) entry.Count = (open and m) or -m if m > 0 then entry.First, entry.Last = pdfreference(f), pdfreference(l) end pdfflushobject(child,entry) return i, n, first, last end end pdfflushobject(child,entry) return nil, n, first, last end function codeinjections.addbookmarks(levels,method) local parent = pdfreserveobject() local _, m, first, last = build(levels,1,pdfreference(parent),method or "internal") local dict = pdfdictionary { Type = pdfconstant("Outlines"), First = pdfreference(first), Last = pdfreference(last), Count = m, } pdfflushobject(parent,dict) lpdf.addtocatalog("Outlines",lpdf.reference(parent)) end