diff options
author | Marius <mariausol@gmail.com> | 2013-05-20 03:20:28 +0300 |
---|---|---|
committer | Marius <mariausol@gmail.com> | 2013-05-20 03:20:28 +0300 |
commit | 5fc5cfb5014ddcc2942e13a559f4082fb66aa6e7 (patch) | |
tree | 53f81e99fac8c80ddd2fb70e233a7e5d5735722f /tex/context/base/lpdf-ano.lua | |
parent | 13ec4b540e0d46c97fd7b089e0b7413da81e0a9f (diff) | |
download | context-5fc5cfb5014ddcc2942e13a559f4082fb66aa6e7.tar.gz |
beta 2013.05.20 02:00
Diffstat (limited to 'tex/context/base/lpdf-ano.lua')
-rw-r--r-- | tex/context/base/lpdf-ano.lua | 1506 |
1 files changed, 753 insertions, 753 deletions
diff --git a/tex/context/base/lpdf-ano.lua b/tex/context/base/lpdf-ano.lua index ee9cb851b..adfea3812 100644 --- a/tex/context/base/lpdf-ano.lua +++ b/tex/context/base/lpdf-ano.lua @@ -1,753 +1,753 @@ -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"
-}
-
--- when using rotation: \disabledirectives[refences.sharelinks] (maybe flag links)
-
--- todo: /AA << WC << ... >> >> : WillClose actions etc
-
-local next, tostring = next, tostring
-local rep, format = string.rep, string.format
-local texcount = tex.count
-local lpegmatch = lpeg.match
-local formatters = string.formatters
-
-local backends, lpdf = backends, lpdf
-
-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 report_reference = logs.reporter("backend","references")
-local report_destination = logs.reporter("backend","destinations")
-local report_bookmark = logs.reporter("backend","bookmarks")
-
-local variables = interfaces.variables
-local constants = interfaces.constants
-
-local settings_to_array = utilities.parsers.settings_to_array
-
-local nodeinjections = backends.pdf.nodeinjections
-local codeinjections = backends.pdf.codeinjections
-local registrations = backends.pdf.registrations
-
-local javascriptcode = interactions.javascripts.code
-
-local references = structures.references
-local bookmarks = structures.bookmarks
-
-local runners = references.runners
-local specials = references.specials
-local handlers = references.handlers
-local executers = references.executers
-local getinnermethod = references.getinnermethod
-
-local nodepool = nodes.pool
-
-local pdfannotation_node = nodepool.pdfannotation
-local pdfdestination_node = nodepool.pdfdestination
-local latelua_node = nodepool.latelua
-
-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 pdfshareobjectreference = lpdf.shareobjectreference
-local pdfreserveobject = lpdf.reserveobject
-local pdfpagereference = lpdf.pagereference
-local pdfdelayedobject = lpdf.delayedobject
-local pdfregisterannotation = lpdf.registerannotation
-
--- todo: 3dview
-
-local pdf_annot = pdfconstant("Annot")
-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_fit = pdfconstant("Fit")
-local pdf_named = pdfconstant("Named")
-
-local pdf_border = pdfarray { 0, 0, 0 }
-
-local cache = { }
-
-local function pagedestination(n) -- only cache fit
- if n > 0 then
- local pd = cache[n]
- if not pd then
- local a = pdfarray {
- pdfreference(pdfpagereference(n)),
- pdf_fit,
- }
- pd = pdfshareobjectreference(a)
- cache[n] = pd
- end
- return pd
- end
-end
-
-lpdf.pagedestination = pagedestination
-
-local defaultdestination = pdfarray { 0, pdf_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
- -- no page ?
- if destination == "" then
- destination = nil
- end
- if not destination and page then
- destination = pdfarray { page - 1, pdf_fit }
- end
- return pdfdictionary {
- S = pdf_gotor, -- can also be pdf_launch
- F = filename,
- D = destination or defaultdestination, -- D is mandate
- NewWindow = (actions.newwindow and true) or nil,
- }
- elseif destination and destination ~= "" then
- return pdfdictionary { -- can be cached
- S = pdf_goto,
- D = destination,
- }
- else
- local p = tonumber(page)
- if p and p > 0 then
- return pdfdictionary { -- can be cached
- S = pdf_goto,
- D = pdfarray {
- pdfreference(pdfpagereference(p)),
- pdf_fit,
- }
- }
- elseif trace_references then
- report_reference("invalid page reference %a",page)
- 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 = javascriptcode(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
- 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, actions.n
- end
- end
-end
-
-lpdf.action = pdfaction
-
-function codeinjections.prerollreference(actions) -- share can become option
- if actions then
- local main, n = pdfaction(actions)
- if main then
- main = pdfdictionary {
- Subtype = pdf_link,
- Border = pdf_border,
- H = (not actions.highlight and pdf_n) or nil,
- A = pdfshareobjectreference(main),
- F = 4, -- print (mandate in pdf/a)
- }
- return main("A"), n
- end
- end
-end
-
-local function use_normal_annotations()
-
- local function reference(width,height,depth,prerolled) -- keep this one
- if prerolled then
- if trace_references then
- report_reference("width %p, height %p, depth %p, prerolled %a",width,height,depth,prerolled)
- end
- return pdfannotation_node(width,height,depth,prerolled)
- end
- end
-
- local function finishreference()
- end
-
- return reference, finishreference
-
-end
-
--- eventually we can do this for special refs only
-
-local hashed, nofunique, nofused = { }, 0, 0
-
-local f_annot = formatters["<< /Type /Annot %s /Rect [%0.3f %0.3f %0.3f %0.3f] >>"]
-local f_bpnf = formatters["_bpnf_(%s,%s,%s,'%s')"]
-
-local function use_shared_annotations()
-
- local factor = number.dimenfactors.bp
-
- local function finishreference(width,height,depth,prerolled) -- %0.2f looks okay enough (no scaling anyway)
- local h, v = pdf.h, pdf.v
- local llx, lly = h*factor, (v - depth)*factor
- local urx, ury = (h + width)*factor, (v + height)*factor
- local annot = f_annot(prerolled,llx,lly,urx,ury)
- local n = hashed[annot]
- if not n then
- n = pdfdelayedobject(annot)
- hashed[annot] = n
- nofunique = nofunique + 1
- end
- nofused = nofused + 1
- pdfregisterannotation(n)
- end
-
- _bpnf_ = finishreference
-
- local function reference(width,height,depth,prerolled)
- if prerolled then
- if trace_references then
- report_reference("width %p, height %p, depth %p, prerolled %a",width,height,depth,prerolled)
- end
- local luacode = f_bpnf(width,height,depth,prerolled)
- return latelua_node(luacode)
- end
- end
-
- statistics.register("pdf annotations", function()
- if nofused > 0 then
- return format("%s embedded, %s unique",nofused,nofunique)
- else
- return nil
- end
- end)
-
-
- return reference, finishreference
-
-end
-
-local lln = latelua_node() if node.has_field(lln,'string') then
-
- directives.register("refences.sharelinks", function(v)
- if v then
- nodeinjections.reference, codeinjections.finishreference = use_shared_annotations()
- else
- nodeinjections.reference, codeinjections.finishreference = use_normal_annotations()
- end
- end)
-
- nodeinjections.reference, codeinjections.finishreference = use_shared_annotations()
-
-else
-
- nodeinjections.reference, codeinjections.finishreference = use_normal_annotations()
-
-end node.free(lln)
-
--- -- -- --
--- -- -- --
-
-local done = { } -- prevent messages
-
-function nodeinjections.destination(width,height,depth,name,view)
- if not done[name] then
- done[name] = true
- if trace_destinations then
- report_destination("width %p, height %p, depth %p, name %a, view %a",width,height,depth,name,view)
- end
- return pdfdestination_node(width,height,depth,name,view) -- can be begin/end node
- end
-end
-
--- runners and specials
-
-runners["inner"] = function(var,actions)
- if getinnermethod() == "names" then
- local vi = var.i
- if vi then
- local vir = vi.references
- if vir then
- local internal = vir.internal
- if internal then
- var.inner = "aut:" .. internal
- end
- end
- end
- else
- var.inner = nil
- end
- local prefix = var.p
- local inner = var.inner
- if inner and prefix and prefix ~= "" then
- inner = prefix .. ":" .. inner -- might not always be ok
- end
- return link(nil,nil,inner,var.r,actions)
-end
-
-runners["inner with arguments"] = function(var,actions)
- report_reference("todo: inner with arguments")
- return false
-end
-
-runners["outer"] = function(var,actions)
- local file, url = references.checkedfileorurl(var.outer,var.outer)
- return link(url,file,var.arguments,nil,actions)
-end
-
-runners["outer with inner"] = function(var,actions)
- local file = references.checkedfile(var.outer) -- was var.f but fails ... why
- 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)
- report_reference("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)
- report_reference("todo: outer with inner with arguments")
- return false
-end
-
-runners["outer with special and operation and arguments"] = function(var,actions)
- report_reference("todo: outer with special and operation and arguments")
- return false
-end
-
-runners["outer with special"] = function(var,actions)
- report_reference("todo: outer with special")
- return false
-end
-
-runners["outer with special and operation"] = function(var,actions)
- report_reference("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 = i and references.internals[i]
- if not v then
- -- error
- report_reference("no internal reference %a",i)
- elseif getinnermethod() == "names" 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
-
--- realpage already resolved
-
-specials.i = specials.internal
-
-local pages = references.pages
-
-function specials.page(var,actions)
- local file = var.f
- if file then
- file = references.checkedfile(file)
- return link(nil,file,nil,var.operation,actions)
- else
- local p = var.r
- if not p then -- todo: call special from reference code
- p = pages[var.operation]
- if type(p) == "function" then -- double
- p = p()
- else
- p = references.realpageofpage(tonumber(p))
- end
- -- if p then
- -- var.r = p
- -- end
- end
- return link(nil,nil,nil,p or var.operation,actions)
- end
-end
-
-function specials.realpage(var,actions)
- local file = var.f
- if file then
- file = references.checkedfile(file)
- return link(nil,file,nil,var.operation,actions)
- else
- return link(nil,nil,nil,var.operation,actions)
- end
-end
-
-function specials.userpage(var,actions)
- local file = var.f
- if file then
- file = references.checkedfile(file)
- return link(nil,file,nil,var.operation,actions)
- else
- local p = var.r
- if not p then -- todo: call special from reference code
- p = var.operation
- if p then -- no function and special check here. only numbers
- p = references.realpageofpage(tonumber(p))
- end
- -- if p then
- -- var.r = p
- -- end
- end
- return link(nil,nil,nil,p or var.operation,actions)
- end
-end
-
-function specials.deltapage(var,actions)
- local p = tonumber(var.operation)
- if p then
- p = references.checkedrealpage(p + texcount.realpageno)
- return link(nil,nil,nil,p,actions)
- end
-end
-
--- sections
-
--- function specials.section(var,actions)
--- local sectionname = var.operation
--- local destination = var.arguments
--- local internal = structures.sections.internalreference(sectionname,destination)
--- if internal then
--- var.special = "internal"
--- var.operation = internal
--- var.arguments = nil
--- specials.internal(var,actions)
--- end
--- end
-
-specials.section = specials.internal -- specials.section just need to have a value as it's checked
-
--- todo, do this in references namespace ordered instead (this is an experiment)
-
-local splitter = lpeg.splitat(":")
-
-function specials.order(var,actions) -- references.specials !
- local operation = var.operation
- if operation then
- local kind, name, n = lpegmatch(splitter,operation)
- local order = structures.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 = references.checkedurl(var.operation)
- return link(url,nil,var.arguments,nil,actions)
-end
-
-function specials.file(var,actions)
- local file = references.checkedfile(var.operation)
- return link(nil,file,var.arguments,nil,actions)
-end
-
-function specials.fileorurl(var,actions)
- local file, url = references.checkedfileorurl(var.operation,var.operation)
- return link(url,file,var.arguments,nil,actions)
-end
-
-function specials.program(var,content)
- local program = references.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
-
-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 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 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("SubmitForm"),
- F = arguments[1],
- Field = fieldset(arguments[2]),
- Flags = flag,
- -- \PDFsubmitfiller
- }
-end
-
-local pdf_hide = pdfconstant("Hide")
-
-function executers.hide(arguments)
- return pdfdictionary {
- S = pdf_hide,
- H = true,
- T = arguments,
- }
-end
-
-function executers.show(arguments)
- return pdfdictionary {
- S = pdf_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 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 = pdf_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
- 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
- pdfflushobject(child,entry)
- return i, n, first, last
- elseif level == startlevel then
- if trace_bookmarks then
- report_bookmark("%3i %w%s %s",reference.realpage,(level-1)*2,(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 = pagedestination(reference.realpage)
- end
- if not first then first, last = child, child end
- prev = child
- last = prev
- n = n + 1
- i = i + 1
- 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)
- if #levels > 0 then
- structures.bookmarks.flatten(levels) -- dirty trick for lack of structure
- 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
-end
-
--- this could also be hooked into the frontend finalizer
-
-lpdf.registerdocumentfinalizer(function() bookmarks.place() end,1,"bookmarks")
+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" +} + +-- when using rotation: \disabledirectives[refences.sharelinks] (maybe flag links) + +-- todo: /AA << WC << ... >> >> : WillClose actions etc + +local next, tostring = next, tostring +local rep, format = string.rep, string.format +local texcount = tex.count +local lpegmatch = lpeg.match +local formatters = string.formatters + +local backends, lpdf = backends, lpdf + +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 report_reference = logs.reporter("backend","references") +local report_destination = logs.reporter("backend","destinations") +local report_bookmark = logs.reporter("backend","bookmarks") + +local variables = interfaces.variables +local constants = interfaces.constants + +local settings_to_array = utilities.parsers.settings_to_array + +local nodeinjections = backends.pdf.nodeinjections +local codeinjections = backends.pdf.codeinjections +local registrations = backends.pdf.registrations + +local javascriptcode = interactions.javascripts.code + +local references = structures.references +local bookmarks = structures.bookmarks + +local runners = references.runners +local specials = references.specials +local handlers = references.handlers +local executers = references.executers +local getinnermethod = references.getinnermethod + +local nodepool = nodes.pool + +local pdfannotation_node = nodepool.pdfannotation +local pdfdestination_node = nodepool.pdfdestination +local latelua_node = nodepool.latelua + +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 pdfshareobjectreference = lpdf.shareobjectreference +local pdfreserveobject = lpdf.reserveobject +local pdfpagereference = lpdf.pagereference +local pdfdelayedobject = lpdf.delayedobject +local pdfregisterannotation = lpdf.registerannotation + +-- todo: 3dview + +local pdf_annot = pdfconstant("Annot") +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_fit = pdfconstant("Fit") +local pdf_named = pdfconstant("Named") + +local pdf_border = pdfarray { 0, 0, 0 } + +local cache = { } + +local function pagedestination(n) -- only cache fit + if n > 0 then + local pd = cache[n] + if not pd then + local a = pdfarray { + pdfreference(pdfpagereference(n)), + pdf_fit, + } + pd = pdfshareobjectreference(a) + cache[n] = pd + end + return pd + end +end + +lpdf.pagedestination = pagedestination + +local defaultdestination = pdfarray { 0, pdf_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 + -- no page ? + if destination == "" then + destination = nil + end + if not destination and page then + destination = pdfarray { page - 1, pdf_fit } + end + return pdfdictionary { + S = pdf_gotor, -- can also be pdf_launch + F = filename, + D = destination or defaultdestination, -- D is mandate + NewWindow = (actions.newwindow and true) or nil, + } + elseif destination and destination ~= "" then + return pdfdictionary { -- can be cached + S = pdf_goto, + D = destination, + } + else + local p = tonumber(page) + if p and p > 0 then + return pdfdictionary { -- can be cached + S = pdf_goto, + D = pdfarray { + pdfreference(pdfpagereference(p)), + pdf_fit, + } + } + elseif trace_references then + report_reference("invalid page reference %a",page) + 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 = javascriptcode(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 + 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, actions.n + end + end +end + +lpdf.action = pdfaction + +function codeinjections.prerollreference(actions) -- share can become option + if actions then + local main, n = pdfaction(actions) + if main then + main = pdfdictionary { + Subtype = pdf_link, + Border = pdf_border, + H = (not actions.highlight and pdf_n) or nil, + A = pdfshareobjectreference(main), + F = 4, -- print (mandate in pdf/a) + } + return main("A"), n + end + end +end + +local function use_normal_annotations() + + local function reference(width,height,depth,prerolled) -- keep this one + if prerolled then + if trace_references then + report_reference("width %p, height %p, depth %p, prerolled %a",width,height,depth,prerolled) + end + return pdfannotation_node(width,height,depth,prerolled) + end + end + + local function finishreference() + end + + return reference, finishreference + +end + +-- eventually we can do this for special refs only + +local hashed, nofunique, nofused = { }, 0, 0 + +local f_annot = formatters["<< /Type /Annot %s /Rect [%0.3f %0.3f %0.3f %0.3f] >>"] +local f_bpnf = formatters["_bpnf_(%s,%s,%s,'%s')"] + +local function use_shared_annotations() + + local factor = number.dimenfactors.bp + + local function finishreference(width,height,depth,prerolled) -- %0.2f looks okay enough (no scaling anyway) + local h, v = pdf.h, pdf.v + local llx, lly = h*factor, (v - depth)*factor + local urx, ury = (h + width)*factor, (v + height)*factor + local annot = f_annot(prerolled,llx,lly,urx,ury) + local n = hashed[annot] + if not n then + n = pdfdelayedobject(annot) + hashed[annot] = n + nofunique = nofunique + 1 + end + nofused = nofused + 1 + pdfregisterannotation(n) + end + + _bpnf_ = finishreference + + local function reference(width,height,depth,prerolled) + if prerolled then + if trace_references then + report_reference("width %p, height %p, depth %p, prerolled %a",width,height,depth,prerolled) + end + local luacode = f_bpnf(width,height,depth,prerolled) + return latelua_node(luacode) + end + end + + statistics.register("pdf annotations", function() + if nofused > 0 then + return format("%s embedded, %s unique",nofused,nofunique) + else + return nil + end + end) + + + return reference, finishreference + +end + +local lln = latelua_node() if node.has_field(lln,'string') then + + directives.register("refences.sharelinks", function(v) + if v then + nodeinjections.reference, codeinjections.finishreference = use_shared_annotations() + else + nodeinjections.reference, codeinjections.finishreference = use_normal_annotations() + end + end) + + nodeinjections.reference, codeinjections.finishreference = use_shared_annotations() + +else + + nodeinjections.reference, codeinjections.finishreference = use_normal_annotations() + +end node.free(lln) + +-- -- -- -- +-- -- -- -- + +local done = { } -- prevent messages + +function nodeinjections.destination(width,height,depth,name,view) + if not done[name] then + done[name] = true + if trace_destinations then + report_destination("width %p, height %p, depth %p, name %a, view %a",width,height,depth,name,view) + end + return pdfdestination_node(width,height,depth,name,view) -- can be begin/end node + end +end + +-- runners and specials + +runners["inner"] = function(var,actions) + if getinnermethod() == "names" then + local vi = var.i + if vi then + local vir = vi.references + if vir then + local internal = vir.internal + if internal then + var.inner = "aut:" .. internal + end + end + end + else + var.inner = nil + end + local prefix = var.p + local inner = var.inner + if inner and prefix and prefix ~= "" then + inner = prefix .. ":" .. inner -- might not always be ok + end + return link(nil,nil,inner,var.r,actions) +end + +runners["inner with arguments"] = function(var,actions) + report_reference("todo: inner with arguments") + return false +end + +runners["outer"] = function(var,actions) + local file, url = references.checkedfileorurl(var.outer,var.outer) + return link(url,file,var.arguments,nil,actions) +end + +runners["outer with inner"] = function(var,actions) + local file = references.checkedfile(var.outer) -- was var.f but fails ... why + 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) + report_reference("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) + report_reference("todo: outer with inner with arguments") + return false +end + +runners["outer with special and operation and arguments"] = function(var,actions) + report_reference("todo: outer with special and operation and arguments") + return false +end + +runners["outer with special"] = function(var,actions) + report_reference("todo: outer with special") + return false +end + +runners["outer with special and operation"] = function(var,actions) + report_reference("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 = i and references.internals[i] + if not v then + -- error + report_reference("no internal reference %a",i) + elseif getinnermethod() == "names" 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 + +-- realpage already resolved + +specials.i = specials.internal + +local pages = references.pages + +function specials.page(var,actions) + local file = var.f + if file then + file = references.checkedfile(file) + return link(nil,file,nil,var.operation,actions) + else + local p = var.r + if not p then -- todo: call special from reference code + p = pages[var.operation] + if type(p) == "function" then -- double + p = p() + else + p = references.realpageofpage(tonumber(p)) + end + -- if p then + -- var.r = p + -- end + end + return link(nil,nil,nil,p or var.operation,actions) + end +end + +function specials.realpage(var,actions) + local file = var.f + if file then + file = references.checkedfile(file) + return link(nil,file,nil,var.operation,actions) + else + return link(nil,nil,nil,var.operation,actions) + end +end + +function specials.userpage(var,actions) + local file = var.f + if file then + file = references.checkedfile(file) + return link(nil,file,nil,var.operation,actions) + else + local p = var.r + if not p then -- todo: call special from reference code + p = var.operation + if p then -- no function and special check here. only numbers + p = references.realpageofpage(tonumber(p)) + end + -- if p then + -- var.r = p + -- end + end + return link(nil,nil,nil,p or var.operation,actions) + end +end + +function specials.deltapage(var,actions) + local p = tonumber(var.operation) + if p then + p = references.checkedrealpage(p + texcount.realpageno) + return link(nil,nil,nil,p,actions) + end +end + +-- sections + +-- function specials.section(var,actions) +-- local sectionname = var.operation +-- local destination = var.arguments +-- local internal = structures.sections.internalreference(sectionname,destination) +-- if internal then +-- var.special = "internal" +-- var.operation = internal +-- var.arguments = nil +-- specials.internal(var,actions) +-- end +-- end + +specials.section = specials.internal -- specials.section just need to have a value as it's checked + +-- todo, do this in references namespace ordered instead (this is an experiment) + +local splitter = lpeg.splitat(":") + +function specials.order(var,actions) -- references.specials ! + local operation = var.operation + if operation then + local kind, name, n = lpegmatch(splitter,operation) + local order = structures.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 = references.checkedurl(var.operation) + return link(url,nil,var.arguments,nil,actions) +end + +function specials.file(var,actions) + local file = references.checkedfile(var.operation) + return link(nil,file,var.arguments,nil,actions) +end + +function specials.fileorurl(var,actions) + local file, url = references.checkedfileorurl(var.operation,var.operation) + return link(url,file,var.arguments,nil,actions) +end + +function specials.program(var,content) + local program = references.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 + +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 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 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("SubmitForm"), + F = arguments[1], + Field = fieldset(arguments[2]), + Flags = flag, + -- \PDFsubmitfiller + } +end + +local pdf_hide = pdfconstant("Hide") + +function executers.hide(arguments) + return pdfdictionary { + S = pdf_hide, + H = true, + T = arguments, + } +end + +function executers.show(arguments) + return pdfdictionary { + S = pdf_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 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 = pdf_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 + 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 + pdfflushobject(child,entry) + return i, n, first, last + elseif level == startlevel then + if trace_bookmarks then + report_bookmark("%3i %w%s %s",reference.realpage,(level-1)*2,(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 = pagedestination(reference.realpage) + end + if not first then first, last = child, child end + prev = child + last = prev + n = n + 1 + i = i + 1 + 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) + if #levels > 0 then + structures.bookmarks.flatten(levels) -- dirty trick for lack of structure + 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 +end + +-- this could also be hooked into the frontend finalizer + +lpdf.registerdocumentfinalizer(function() bookmarks.place() end,1,"bookmarks") |