diff options
Diffstat (limited to 'tex/context/base/mkxl/lpdf-epa.lmt')
-rw-r--r-- | tex/context/base/mkxl/lpdf-epa.lmt | 1105 |
1 files changed, 1105 insertions, 0 deletions
diff --git a/tex/context/base/mkxl/lpdf-epa.lmt b/tex/context/base/mkxl/lpdf-epa.lmt new file mode 100644 index 000000000..00d9f3c4b --- /dev/null +++ b/tex/context/base/mkxl/lpdf-epa.lmt @@ -0,0 +1,1105 @@ +if not modules then modules = { } end modules ['lpdf-epa'] = { + version = 1.001, + comment = "companion to lpdf-epa.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +-- Links can also have quadpoint + +-- embedded files ... not bound to a page + +local type, tonumber, next = type, tonumber, next +local format, gsub, lower, find = string.format, string.gsub, string.lower, string.find +local formatters = string.formatters +local concat, merged = table.concat, table.merged +local abs = math.abs +local expandname = file.expandname +local allocate = utilities.storage.allocate +local bor, band = bit32.bor, bit32.band +local isfile = lfs.isfile + +local trace_links = false trackers.register("figures.links", function(v) trace_links = v end) +local trace_comments = false trackers.register("figures.comments", function(v) trace_comments = v end) +local trace_fields = false trackers.register("figures.fields", function(v) trace_fields = v end) +local trace_outlines = false trackers.register("figures.outlines", function(v) trace_outlines = v end) + +local report_link = logs.reporter("backend","link") +local report_comment = logs.reporter("backend","comment") +local report_field = logs.reporter("backend","field") +local report_outline = logs.reporter("backend","outline") + +local lpdf = lpdf +local backends = backends +local context = context + +local nodeinjections = backends.pdf.nodeinjections + +local pdfarray = lpdf.array +local pdfdictionary = lpdf.dictionary +local pdfconstant = lpdf.constant +local pdfreference = lpdf.reference + +local pdfreserveobject +local pdfgetpos + +updaters.register("backend.update.lpdf",function() + pdfreserveobject = lpdf.reserveobject + pdfgetpos = lpdf.getpos +end) + +local pdfcopyboolean = lpdf.copyboolean +local pdfcopyunicode = lpdf.copyunicode +local pdfcopyarray = lpdf.copyarray +local pdfcopydictionary = lpdf.copydictionary +local pdfcopynumber = lpdf.copynumber +local pdfcopyinteger = lpdf.copyinteger +local pdfcopystring = lpdf.copystring +local pdfcopyconstant = lpdf.copyconstant + +local createimage = images.create +local embedimage = images.embed + +local hpack_node = nodes.hpack + +local loadpdffile = lpdf.epdf.load + +local nameonly = file.nameonly + +local variables = interfaces.variables +local codeinjections = backends.pdf.codeinjections +----- urlescaper = lpegpatterns.urlescaper +----- utftohigh = lpegpatterns.utftohigh +local escapetex = characters.filters.utf.private.escape + +local bookmarks = structures.bookmarks + +local maxdimen = 0x3FFFFFFF -- 2^30-1 + +local bpfactor = number.dimenfactors.bp + +local layerspec = { + "epdfcontent" +} + +local getpos = function() getpos = backends.codeinjections.getpos return getpos() end + +local collected = allocate() +local tobesaved = allocate() + +local jobembedded = { + collected = collected, + tobesaved = tobesaved, +} + +job.embedded = jobembedded + +local function initializer() + tobesaved = jobembedded.tobesaved + collected = jobembedded.collected +end + +job.register('job.embedded.collected',tobesaved,initializer) + +local function validdocument(specification) + if figures and not specification then + specification = figures and figures.current() + specification = specification and specification.status + end + if specification then + local fullname = specification.fullname + local expanded = lower(expandname(fullname)) + -- we could add a check for duplicate page insertion + tobesaved[expanded] = true + --- but that is messy anyway so we forget about it + return specification, fullname, loadpdffile(fullname) -- costs time + end +end + +local function getmediasize(specification,pagedata) + local xscale = specification.xscale or 1 + local yscale = specification.yscale or 1 + ----- size = specification.size or "crop" -- todo + local mediabox = pagedata.MediaBox + local llx = mediabox[1] + local lly = mediabox[2] + local urx = mediabox[3] + local ury = mediabox[4] + local width = xscale * (urx - llx) -- \\overlaywidth, \\overlayheight + local height = yscale * (ury - lly) -- \\overlaywidth, \\overlayheight + return llx, lly, urx, ury, width, height, xscale, yscale +end + +local function getdimensions(annotation,llx,lly,xscale,yscale,width,height,report) + local rectangle = annotation.Rect + local a_llx = rectangle[1] + local a_lly = rectangle[2] + local a_urx = rectangle[3] + local a_ury = rectangle[4] + local x = xscale * (a_llx - llx) + local y = yscale * (a_lly - lly) + local w = xscale * (a_urx - a_llx) + local h = yscale * (a_ury - a_lly) + if w > width or h > height or w < 0 or h < 0 or abs(x) > (maxdimen/2) or abs(y) > (maxdimen/2) then + report("broken rectangle [%.6F %.6F %.6F %.6F] (max: %.6F)",a_llx,a_lly,a_urx,a_ury,maxdimen/2) + return + end + return x, y, w, h, a_llx, a_lly, a_urx, a_ury +end + +local layerused = false + +-- local function initializelayer(height,width) +-- if not layerused then +-- context.definelayer(layerspec, { height = height .. "bp", width = width .. "bp" }) +-- layerused = true +-- end +-- end + +local function initializelayer(height,width) +-- if not layerused then + context.setuplayer(layerspec, { height = height .. "bp", width = width .. "bp" }) + layerused = true +-- end +end + +function codeinjections.flushmergelayer() + if layerused then + context.flushlayer(layerspec) + layerused = false + end +end + +local f_namespace = formatters["lpdf-epa-%s-"] + +local function makenamespace(filename) + filename = gsub(lower(nameonly(filename)),"[^%a%d]+","-") + return f_namespace(filename) +end + +local function add_link(x,y,w,h,destination,what) + x = x .. "bp" + y = y .. "bp" + w = w .. "bp" + h = h .. "bp" + if trace_links then + report_link("destination %a, type %a, dx %s, dy %s, wd %s, ht %s",destination,what,x,y,w,h) + end + local locationspec = { -- predefining saves time + x = x, + y = y, + preset = "leftbottom", + } + local buttonspec = { + width = w, + height = h, + offset = variables.overlay, + frame = trace_links and variables.on or variables.off, + } + context.setlayer ( + layerspec, + locationspec, + function() context.button ( buttonspec, "", { destination } ) end + -- context.nested.button(buttonspec, "", { destination }) -- time this + ) +end + +local function link_goto(x,y,w,h,document,annotation,pagedata,namespace) + local a = annotation.A + if a then + local destination = a.D -- [ 18 0 R /Fit ] + local what = "page" + if type(destination) == "string" then + local destinations = document.destinations + local wanted = destinations[destination] + destination = wanted and wanted.D -- is this ok? isn't it destination already a string? + if destination then what = "named" end + end + local pagedata = destination and destination[1] + if pagedata then + local destinationpage = pagedata.number + if destinationpage then + add_link(x,y,w,h,namespace .. destinationpage,what) + end + end + end +end + +local function link_uri(x,y,w,h,document,annotation) + local url = annotation.A.URI + if url then + -- url = lpegmatch(urlescaper,url) + -- url = lpegmatch(utftohigh,url) + url = escapetex(url) + add_link(x,y,w,h,formatters["url(%s)"](url),"url") + end +end + +-- The rules in PDF on what a 'file specification' is, is in fact quite elaborate +-- (see section 3.10 in the 1.7 reference) so we need to test for string as well +-- as a table. TH/20140916 + +-- When embedded is set then files need to have page references which is seldom the +-- case but you can generate them with context: +-- +-- \setupinteraction[state=start,page={page,page}] +-- +-- see tests/mkiv/interaction/cross[1|2|3].tex for an example + +local embedded = false directives.register("figures.embedded", function(v) embedded = v end) +local reported = { } + +local function link_file(x,y,w,h,document,annotation) + local a = annotation.A + if a then + local filename = a.F + if type(filename) == "table" then + filename = filename.F + end + if filename then + filename = escapetex(filename) + local destination = a.D + if not destination then + add_link(x,y,w,h,formatters["file(%s)"](filename),"file") + elseif type(destination) == "string" then + add_link(x,y,w,h,formatters["%s::%s"](filename,destination),"file (named)") + else + -- hm, zero offset so maybe: destination + 1 + destination = tonumber(destination[1]) -- array + if destination then + destination = destination + 1 + local loaded = collected[lower(expandname(filename))] + if embedded and loaded then + add_link(x,y,w,h,makenamespace(filename) .. destination,what) + else + if loaded and not reported[filename] then + report_link("reference to an also loaded file %a, consider using directive: figures.embedded",filename) + reported[filename] = true + end + add_link(x,y,w,h,formatters["%s::page(%s)"](filename,destination),"file (page)") + end + else + add_link(x,y,w,h,formatters["file(%s)"](filename),"file") + end + end + end + end +end + +-- maybe handler per subtype and then one loop but then what about order ... + +function codeinjections.mergereferences(specification) + local specification, fullname, document = validdocument(specification) + if not document then + return "" + end + local pagenumber = specification.page or 1 + local pagedata = document.pages[pagenumber] + local annotations = pagedata and pagedata.Annots + local namespace = makenamespace(fullname) + local reference = namespace .. pagenumber + if annotations and #annotations > 0 then + local llx, lly, urx, ury, width, height, xscale, yscale = getmediasize(specification,pagedata,xscale,yscale) + initializelayer(height,width) + for i=1,#annotations do + local annotation = annotations[i] + if annotation then + if annotation.Subtype == "Link" then + local a = annotation.A + if not a then + local d = annotation.Dest + if d then + annotation.A = { S = "GoTo", D = d } -- no need for a dict + end + end + if not a then + report_link("missing link annotation") + else + local x, y, w, h = getdimensions(annotation,llx,lly,xscale,yscale,width,height,report_link) + if x then + local linktype = a.S + if linktype == "GoTo" then + link_goto(x,y,w,h,document,annotation,pagedata,namespace) + elseif linktype == "GoToR" then + link_file(x,y,w,h,document,annotation) + elseif linktype == "URI" then + link_uri(x,y,w,h,document,annotation) + elseif trace_links then + report_link("unsupported link annotation %a",linktype) + end + end + end + end + elseif trace_links then + report_link("broken annotation, index %a",i) + end + end + end + -- moved outside previous test + context.setgvalue("figurereference",reference) -- global, todo: setmacro + if trace_links then + report_link("setting figure reference to %a",reference) + end + specification.reference = reference + return namespace +end + +function codeinjections.mergeviewerlayers(specification) + -- todo: parse included page for layers .. or only for whole document inclusion + if true then + return + end + local specification, fullname, document = validdocument(specification) + if not document then + return "" + end + local namespace = makenamespace(fullname) + local layers = document.layers + if layers then + for i=1,#layers do + local layer = layers[i] + if layer then + local tag = namespace .. gsub(layer," ",":") + local title = tag + if trace_links then + report_link("using layer %a",tag) + end + attributes.viewerlayers.define { -- also does some cleaning + tag = tag, -- todo: #3A or so + title = title, + visible = variables.start, + editable = variables.yes, + printable = variables.yes, + } + codeinjections.useviewerlayer(tag) + elseif trace_links then + report_link("broken layer, index %a",i) + end + end + end +end + +-- It took a bit of puzzling and playing around to come to the following +-- implementation. In the end it looks simple but as usual it takes a while +-- to see what the specification (and implementation) boils down to. Lots of +-- shared properties and such. The scaling took some trial and error as +-- viewers differ. I had to extend some low level helpers to make it more +-- comfortable. Hm, the specification is somewhat incomplete as some fields +-- are permitted even if not mentioned so in the end we can share more code. +-- +-- If all works ok, we can get rid of some copies which saves time and space. + +local commentlike = { + Text = "text", + FreeText = "freetext", + Line = "line", + Square = "shape", + Circle = "shape", + Polygon = "poly", + PolyLine = "poly", + Highlight = "markup", + Underline = "markup", + Squiggly = "markup", + StrikeOut = "markup", + Caret = "text", + Stamp = "stamp", + Ink = "ink", + Popup = "popup", +} + +local function copyBS(v) -- dict can be shared + if v then + -- return pdfdictionary { + -- Type = copypdfconstant(V.Type), + -- W = copypdfnumber (V.W), + -- S = copypdfstring (V.S), + -- D = copypdfarray (V.D), + -- } + return copypdfdictionary(v) + end +end + +local function copyBE(v) -- dict can be shared + if v then + -- return pdfdictionary { + -- S = copypdfstring(V.S), + -- I = copypdfnumber(V.I), + -- } + return copypdfdictionary(v) + end +end + +local function copyBorder(v) -- dict can be shared + if v then + -- todo + return copypdfarray(v) + end +end + +local function copyPopup(v,references) + if v then + local p = references[v] + if p then + return pdfreference(p) + end + end +end + +local function copyParent(v,references) + if v then + local p = references[v] + if p then + return pdfreference(p) + end + end +end + +local function copyIRT(v,references) + if v then + local p = references[v] + if p then + return pdfreference(p) + end + end +end + +local function copyC(v) + if v then + -- todo: check color space + return pdfcopyarray(v) + end +end + +local function finalizer(d,xscale,yscale,a_llx,a_ury) + local q = d.QuadPoints or d.Vertices or d.CL + if q then + return function() + local h, v = pdfgetpos() -- already scaled + for i=1,#q,2 do + q[i] = xscale * q[i] + (h*bpfactor - xscale * a_llx) + q[i+1] = yscale * q[i+1] + (v*bpfactor - yscale * a_ury) + end + return d() + end + end + q = d.InkList or d.Path + if q then + return function() + local h, v = pdfgetpos() -- already scaled + for i=1,#q do + local q = q[i] + for i=1,#q,2 do + q[i] = xscale * q[i] + (h*bpfactor - xscale * a_llx) + q[i+1] = yscale * q[i+1] + (v*bpfactor - yscale * a_ury) + end + end + return d() + end + end + return d() +end + +local validstamps = { + Approved = true, + Experimental = true, + NotApproved = true, + AsIs = true, + Expired = true, + NotForPublicRelease = true, + Confidential = true, + Final = true, + Sold = true, + Departmental = true, + ForComment = true, + TopSecret = true, + Draft = true, + ForPublicRelease = true, +} + +-- todo: we can use runlocal instead of steps + +local function validStamp(v) + local name = "Stamped" -- fallback + if v then + local ok = validstamps[v] + if ok then + name = ok + else + for k in next, validstamps do + if find(v,k.."$") then + name = k + validstamps[v] = k + break + end + end + end + end + -- we temporary return to \TEX: + context.predefinesymbol { name } + context.step() + -- beware, an error is not reported + return pdfconstant(name), codeinjections.analyzenormalsymbol(name) +end + +local annotationflags = lpdf.flags.annotations + +local function copyF(v,lock) -- todo: bxor 24 + if lock then + v = bor(v or 0,annotationflags.ReadOnly + annotationflags.Locked + annotationflags.LockedContents) + end + if v then + return pdfcopyinteger(v) + end +end + +-- Speed is not really an issue so we don't optimize this code too much. In the end (after +-- testing we end up with less code that we started with. + +function codeinjections.mergecomments(specification) + local specification, fullname, document = validdocument(specification) + if not document then + return "" + end + local pagenumber = specification.page or 1 + local pagedata = document.pages[pagenumber] + local annotations = pagedata and pagedata.Annots + if annotations and #annotations > 0 then + local llx, lly, urx, ury, width, height, xscale, yscale = getmediasize(specification,pagedata,xscale,yscale) + initializelayer(height,width) + -- + local lockflags = specification.lock -- todo: proper parameter + local references = { } + local usedpopups = { } + for i=1,#annotations do + local annotation = annotations[i] + if annotation then + local subtype = annotation.Subtype + if commentlike[subtype] then + references[annotation] = pdfreserveobject() + local p = annotation.Popup + if p then + usedpopups[p] = true + end + end + end + end + -- + for i=1,#annotations do + -- we keep the order + local annotation = annotations[i] + if annotation then + local reference = references[annotation] + if reference then + local subtype = annotation.Subtype + local kind = commentlike[subtype] + if kind ~= "popup" or usedpopups[annotation] then + local x, y, w, h, a_llx, a_lly, a_urx, a_ury = getdimensions(annotation,llx,lly,xscale,yscale,width,height,report_comment) + if x then + local voffset = h + local dictionary = pdfdictionary { + Subtype = pdfconstant (subtype), + -- common (skipped: P AP AS OC AF BM StructParent) + Contents = pdfcopyunicode(annotation.Contents), + NM = pdfcopystring (annotation.NM), + M = pdfcopystring (annotation.M), + F = copyF (annotation.F,lockflags), + C = copyC (annotation.C), + ca = pdfcopynumber (annotation.ca), + CA = pdfcopynumber (annotation.CA), + Lang = pdfcopystring (annotation.Lang), + -- also common + CreationDate = pdfcopystring (annotation.CreationDate), + T = pdfcopyunicode(annotation.T), + Subj = pdfcopyunicode(annotation.Subj), + -- border + Border = pdfcopyarray (annotation.Border), + BS = copyBS (annotation.BS), + BE = copyBE (annotation.BE), + -- sort of common + Popup = copyPopup (annotation.Popup,references), + RC = pdfcopyunicode(annotation.RC) -- string or stream + } + if kind == "markup" then + dictionary.IRT = copyIRT (annotation.IRT,references) + dictionary.RT = pdfconstant (annotation.RT) + dictionary.IT = pdfcopyconstant (annotation.IT) + dictionary.QuadPoints = pdfcopyarray (annotation.QuadPoints) + -- dictionary.RD = pdfcopyarray (annotation.RD) + elseif kind == "text" then + -- somehow F fails to view : /F 24 : bit4=nozoom bit5=norotate + dictionary.F = nil + dictionary.Open = pdfcopyboolean (annotation.Open) + dictionary.Name = pdfcopyunicode (annotation.Name) + dictionary.State = pdfcopystring (annotation.State) + dictionary.StateModel = pdfcopystring (annotation.StateModel) + dictionary.IT = pdfcopyconstant (annotation.IT) + dictionary.QuadPoints = pdfcopyarray (annotation.QuadPoints) + dictionary.RD = pdfcopyarray (annotation.RD) -- caret + dictionary.Sy = pdfcopyconstant (annotation.Sy) -- caret + voffset = 0 + elseif kind == "freetext" then + dictionary.DA = pdfcopystring (annotation.DA) + dictionary.Q = pdfcopyinteger (annotation.Q) + dictionary.DS = pdfcopystring (annotation.DS) + dictionary.CL = pdfcopyarray (annotation.CL) + dictionary.IT = pdfcopyconstant (annotation.IT) + dictionary.LE = pdfcopyconstant (annotation.LE) + -- dictionary.RC = pdfcopystring (annotation.RC) + elseif kind == "line" then + dictionary.LE = pdfcopyarray (annotation.LE) + dictionary.IC = pdfcopyarray (annotation.IC) + dictionary.LL = pdfcopynumber (annotation.LL) + dictionary.LLE = pdfcopynumber (annotation.LLE) + dictionary.Cap = pdfcopyboolean (annotation.Cap) + dictionary.IT = pdfcopyconstant (annotation.IT) + dictionary.LLO = pdfcopynumber (annotation.LLO) + dictionary.CP = pdfcopyconstant (annotation.CP) + dictionary.Measure = pdfcopydictionary(annotation.Measure) -- names + dictionary.CO = pdfcopyarray (annotation.CO) + voffset = 0 + elseif kind == "shape" then + dictionary.IC = pdfcopyarray (annotation.IC) + -- dictionary.RD = pdfcopyarray (annotation.RD) + voffset = 0 + elseif kind == "stamp" then + local name, appearance = validStamp(annotation.Name) + dictionary.Name = name + dictionary.AP = appearance + voffset = 0 + elseif kind == "ink" then + dictionary.InkList = pdfcopyarray (annotation.InkList) + elseif kind == "poly" then + dictionary.Vertices = pdfcopyarray (annotation.Vertices) + -- dictionary.LE = pdfcopyarray (annotation.LE) -- todo: names in array + dictionary.IC = pdfcopyarray (annotation.IC) + dictionary.IT = pdfcopyconstant (annotation.IT) + dictionary.Measure = pdfcopydictionary(annotation.Measure) + dictionary.Path = pdfcopyarray (annotation.Path) + -- dictionary.RD = pdfcopyarray (annotation.RD) + elseif kind == "popup" then + dictionary.Open = pdfcopyboolean (annotation.Open) + dictionary.Parent = copyParent (annotation.Parent,references) + voffset = 0 + end + if dictionary then + local locationspec = { + x = x .. "bp", + y = y .. "bp", + voffset = voffset .. "bp", + preset = "leftbottom", + } + local finalize = finalizer(dictionary,xscale,yscale,a_llx,a_ury) + context.setlayer(layerspec,locationspec,function() + context(hpack_node(nodeinjections.annotation(w/bpfactor,h/bpfactor,0,finalize,reference))) + end) + end + end + else + -- report_comment("skipping annotation, index %a",i) + end + end + elseif trace_comments then + report_comment("broken annotation, index %a",i) + end + end + end + return namespace +end + +local widgetflags = lpdf.flags.widgets + +local function flagstoset(flag,flags) + local t = { } + if flags then + for k, v in next, flags do + if band(flag,v) ~= 0 then + t[k] = true + end + end + end + return t +end + +-- BS : border style dict +-- R : rotation 0 90 180 270 +-- BG : background array +-- CA : caption string +-- RC : roll over caption +-- AC : down caption +-- I/RI/IX : icon streams +-- IF : fit dictionary +-- TP : text position number + +-- Opt : array of texts +-- TI : top index + +-- V : value +-- DV : default value +-- DS : default string +-- RV : rich +-- Q : quadding (0=left 1=middle 2=right) + +function codeinjections.mergefields(specification) + local specification, fullname, document = validdocument(specification) + if not document then + return "" + end + local pagenumber = specification.page or 1 + local pagedata = document.pages[pagenumber] + local annotations = pagedata and pagedata.Annots + if annotations and #annotations > 0 then + local llx, lly, urx, ury, width, height, xscale, yscale = getmediasize(specification,pagedata,xscale,yscale) + initializelayer(height,width) + -- + for i=1,#annotations do + -- we keep the order + local annotation = annotations[i] + if annotation then + local subtype = annotation.Subtype + if subtype == "Widget" then + local parent = annotation.Parent or { } + local name = annotation.T or parent.T + local what = annotation.FT or parent.FT + if name and what then + local x, y, w, h, a_llx, a_lly, a_urx, a_ury = getdimensions(annotation,llx,lly,xscale,yscale,width,height,report_field) + if x then + x = x .. "bp" + y = y .. "bp" + local W, H = w, h + w = w .. "bp" + h = h .. "bp" + if trace_fields then + report_field("field %a, type %a, dx %s, dy %s, wd %s, ht %s",name,what,x,y,w,h) + end + local locationspec = { + x = x, + y = y, + preset = "leftbottom", + } + -- + local aflags = flagstoset(annotation.F or parent.F, annotationflags) + local wflags = flagstoset(annotation.Ff or parent.Ff, widgetflags) + if what == "Tx" then + -- DA DV F FT MaxLen MK Q T V | AA OC + if wflags.MultiLine then + wflags.MultiLine = nil + what = "text" + else + what = "line" + end + -- via context + local fieldspec = { + width = w, + height = h, + offset = variables.overlay, + frame = trace_links and variables.on or variables.off, + n = annotation.MaxLen or (parent and parent.MaxLen), + type = what, + option = concat(merged(aflags,wflags),","), + } + context.setlayer (layerspec,locationspec,function() + context.definefieldbody ( { name } , fieldspec ) + context.fieldbody ( { name } ) + end) + -- + elseif what == "Btn" then + if wflags.Radio or wflags.RadiosInUnison then + -- AP AS DA F Ff FT H MK T V | AA OC + wflags.Radio = nil + wflags.RadiosInUnison = nil + what = "radio" + elseif wflags.PushButton then + -- AP DA F Ff FT H MK T | AA OC + -- + -- Push buttons only have an appearance and some associated + -- actions so they are not worth copying. + -- + wflags.PushButton = nil + what = "push" + else + -- AP AS DA F Ff FT H MK T V | OC AA + what = "check" + -- direct + local AP = annotation.AP or (parent and parent.AP) + if AP then + local a = document.__xrefs__[AP] + if a and pdfe.copyappearance then + local o = pdfe.copyappearance(document,a) + if o then + AP = pdfreference(o) + end + end + end + local dictionary = pdfdictionary { + Subtype = pdfconstant("Widget"), + FT = pdfconstant("Btn"), + T = pdfcopyunicode(annotation.T or parent.T), + F = pdfcopyinteger(annotation.F or parent.F), + Ff = pdfcopyinteger(annotation.Ff or parent.Ff), + AS = pdfcopyconstant(annotation.AS or (parent and parent.AS)), + AP = AP and pdfreference(AP), + } + local finalize = dictionary() + context.setlayer(layerspec,locationspec,function() + context(hpack_node(nodeinjections.annotation(W/bpfactor,H/bpfactor,0,finalize))) + end) + -- + end + elseif what == "Ch" then + -- F Ff FT Opt T | AA OC (rest follows) + if wflags.PopUp then + wflags.PopUp = nil + if wflags.Edit then + wflags.Edit = nil + what = "combo" + else + what = "popup" + end + else + what = "choice" + end + elseif what == "Sig" then + what = "signature" + else + what = nil + end + end + end + end + end + end + end +end + +-- Beware, bookmarks can be in pdfdoc encoding or in unicode. However, in mkiv we +-- write out the strings in unicode (hex). When we read them in, we check for a bom +-- and convert to utf. + +function codeinjections.getbookmarks(filename) + + -- The first version built a nested tree and flattened that afterwards ... but I decided + -- to keep it simple and flat. + + local list = bookmarks.extras.get(filename) + + if list then + return list + else + list = { } + end + + local document = nil + + if isfile(filename) then + document = loadpdffile(filename) + else + report_outline("unknown file %a",filename) + bookmarks.extras.register(filename,list) + return list + end + + local outlines = document.Catalog.Outlines + local pages = document.pages + local nofpages = document.nofpages + local destinations = document.destinations + + -- I need to check this destination analyzer with the one in annotations .. best share + -- code (and not it's inconsistent). On the todo list ... + + local function setdestination(current,entry) + local destination = nil + local action = current.A + if action then + local subtype = action.S + if subtype == "GoTo" then + destination = action.D + local kind = type(destination) + if kind == "string" then + entry.destination = destination + destination = destinations[destination] + local pagedata = destination and destination[1] + if pagedata then + entry.realpage = pagedata.number + end + elseif kind == "table" then + local pageref = #destination + if pageref then + local pagedata = pages[pageref] + if pagedata then + entry.realpage = pagedata.number + end + end + end + -- elseif subtype then + -- report("unsupported bookmark action %a",subtype) + end + else + local destination = current.Dest + if destination then + if type(destination) == "string" then + local wanted = destinations[destination] + destination = wanted and wanted.D + if destination then + entry.destination = destination + end + else + local pagedata = destination and destination[1] + if pagedata and pagedata.Type == "Page" then + entry.realpage = pagedata.number + -- else + -- report("unsupported bookmark destination (no page)") + end + end + end + end + end + + local function traverse(current,depth) + while current do + -- local title = current.Title + local title = current("Title") -- can be pdfdoc or unicode + if title then + local entry = { + level = depth, + title = title, + } + list[#list+1] = entry + setdestination(current,entry) + if trace_outlines then + report_outline("%w%s",2*depth,title) + end + end + local first = current.First + if first then + local current = first + while current do + local title = current.Title + if title and trace_outlines then + report_outline("%w%s",2*depth,title) + end + local entry = { + level = depth, + title = title, + } + setdestination(current,entry) + list[#list+1] = entry + traverse(current.First,depth+1) + current = current.Next + end + end + current = current.Next + end + end + + if outlines then + if trace_outlines then + report_outline("outline of %a:",document.filename) + report_outline() + end + traverse(outlines,0) + if trace_outlines then + report_outline() + end + elseif trace_outlines then + report_outline("no outline in %a",document.filename) + end + + bookmarks.extras.register(filename,list) + + return list + +end + +function codeinjections.mergebookmarks(specification) + -- codeinjections.getbookmarks(document) + if not specification then + specification = figures and figures.current() + specification = specification and specification.status + end + if specification then + local fullname = specification.fullname + local bookmarks = backends.codeinjections.getbookmarks(fullname) + local realpage = tonumber(specification.page) or 1 + for i=1,#bookmarks do + local b = bookmarks[i] + if not b.usedpage then + if b.realpage == realpage then + if trace_options then + report_outline("using %a at page %a of file %a",b.title,realpage,fullname) + end + b.usedpage = true + b.section = structures.sections.currentsectionindex() + b.pageindex = specification.pageindex + end + end + end + end +end + +-- A bit more than a placeholder but in the same perspective as +-- inclusion of comments and fields: +-- +-- getinfo{ filename = "tt.pdf", metadata = true } +-- getinfo{ filename = "tt.pdf", page = 1, metadata = "xml" } +-- getinfo("tt.pdf") + +function codeinjections.getinfo(specification) + if type(specification) == "string" then + specification = { filename = specification } + end + local filename = specification.filename + if type(filename) == "string" and isfile(filename) then + local pdffile = loadpdffile(filename) + if pdffile then + local pagenumber = specification.page or 1 + local metadata = specification.metadata + local catalog = pdffile.Catalog + local info = pdffile.Info + local pages = pdffile.pages + local nofpages = pdffile.nofpages + if metadata then + local m = catalog.Metadata + if m then + m = m() + if metadata == "xml" then + metadata = xml.convert(m) + else + metadata = m + end + else + metadata = nil + end + else + metadata = nil + end + if pagenumber > nofpages then + pagenumber = nofpages + end + local nobox = { 0, 0, 0, 0 } + local crop = nobox + local media = nobox + local page = pages[pagenumber] + if page then + crop = page.CropBox or nobox + media = page.MediaBox or crop or nobox + end + local bbox = crop or media or nobox + return { + filename = filename, + pdfversion = tonumber(catalog.Version), + nofpages = nofpages, + title = info.Title, + creator = info.Creator, + producer = info.Producer, + creationdate = info.CreationDate, + modification = info.ModDate, + metadata = metadata, + width = bbox[4] - bbox[2], + height = bbox[3] - bbox[1], + cropbox = { crop[1], crop[2], crop[3], crop[4] }, -- we need access + mediabox = { media[1], media[2], media[3], media[4] } , -- we need access + } + end + end +end |