Hi Hans,

You know we at Docwolves do lots of stuff with

  \externalfigure[extern.pdf][interaction=yes]

right? That sometimes produces errors. Some of those are hard to send
back to you because the PDFs tend to be confidential, but there are two
cases where valid but unexpected external references wreak havoc that
are easy to explain and report:

The first is:

   mailto:"some...@gmail.com";

(with embedded " symbols). Resulting in

     ) expected near 'someone'

error from lua, and it required us to patch context like so:

\def\doifreferencefoundelse#1#2#3%
  {\ctxcommand {doifelsereference("\referenceprefix ",[[#1]], % "#1"
                   \luaconditional \highlighthyperlinks ,
                   \luaconditional \gotonewwindow )}
                   {\expandtexincurrentreference #2}{#3}}


The second was a variation of:

  C:\TEMP\FILE.PDF

(I forgot the real example). This of course results in

        Undefined control sequence: \\TEMP

from luatex, and it required an extra line in link_uri in
lpdf-epa.lua:

local function link_uri(x,y,w,h,document,annotation)
    local url = annotation.A.URI
    url = string.gsub(url,"\\","/") -- TH Don't want backslashes here
    if url then
        add_link(x,y,w,h,format("url(%s)",url),"url")
    end
end


For the other bugs (which are ones where I often doubt whether the
external pdf was even correct in the first place) I am attaching my
versions of lpdf-epa.lua and lpdf-epd.lua for you to compare with your
version. Unfortunately, we are using "2012.06.20 20:43", so that may
be too old to help much. But you can grep for TH in the source and
perhaps get some inspiration ;)

Best wishes,
Taco




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"
}

-- This is a rather experimental feature and the code will probably
-- change.

local type, tonumber = type, tonumber
local format, gsub = string.format, string.gsub

local trace_links = false  trackers.register("figures.links", function(v) trace_links = v end)

local report_link = logs.reporter("backend","merging")

local backends, lpdf = backends, lpdf

local variables      = interfaces.variables
local codeinjections = backends.pdf.codeinjections

local layerspec = { -- predefining saves time
    "epdflinks"
}

local function makenamespace(filename)
    return format("lpdf-epa-%s-",file.removesuffix(file.basename(filename)))
end

local function add_link(x,y,w,h,destination,what)
    if trace_links then
        report_link("dx: % 4i, dy: % 4i, wd: % 4i, ht: % 4i, destination: %s, type: %s",x,y,w,h,destination,what)
    end
    local locationspec = { -- predefining saves time
        x      = x .. "bp",
        y      = y .. "bp",
        preset = "leftbottom",
    }
    local buttonspec = {
        width  = w .. "bp",
        height = h .. "bp",
        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 destination = annotation.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
        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

local function link_uri(x,y,w,h,document,annotation)
    local url = annotation.A.URI
    url = string.gsub(url,"\\","/") -- TH Don't want backslashes here
    if url then
        add_link(x,y,w,h,format("url(%s)",url),"url")
    end
end

local function link_file(x,y,w,h,document,annotation)
    local filename = annotation.A.F
    if filename then
        local destination = annotation.A.D
        if not destination then
            add_link(x,y,w,h,format("file(%s)",filename),"file")
        elseif type(destination) == "string" then
            add_link(x,y,w,h,format("%s::%s",filename,destination),"file (named)")
        else
            destination = destination[1] -- array
            if tonumber(destination) then
                add_link(x,y,w,h,format("%s::page(%s)",filename,destination),"file (page)")
            else
                add_link(x,y,w,h,format("file(%s)",filename),"file")
            end
        end
    end
end

function codeinjections.mergereferences(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 document = lpdf.epdf.load(fullname)
        if document then
            local pagenumber  = specification.page    or 1
            local xscale      = specification.yscale  or 1
            local yscale      = specification.yscale  or 1
            local size        = specification.size    or "crop" -- todo
            local pagedata    = document.pages[pagenumber]
            local annotations = pagedata and pagedata.Annots or nil
            if annotations and annotations.n > 0 then
                local namespace   = format("lpdf-epa-%s-",file.removesuffix(file.basename(fullname)))
                local reference   = namespace .. pagenumber
                local mediabox = pagedata.MediaBox
                local llx, lly, urx, ury = mediabox[1], mediabox[2], mediabox[3], mediabox[4]
                local width, height = xscale * (urx - llx), yscale * (ury - lly) -- \\overlaywidth, \\overlayheight
                context.definelayer( { "epdflinks" }, { height = height.."bp" , width = width.."bp" })
                for i=1,annotations.n do
                    local annotation = annotations[i]
                    if annotation then -- TH hack for broken annots, hack line 1 (this test is just paranoia)
                    local subtype = annotation.Subtype
                    local rectangle = annotation.Rect
                    local a_llx, a_lly, a_urx, a_ury = rectangle[1], rectangle[2], rectangle[3], rectangle[4]
                    local x, y = xscale * (a_llx -   llx), yscale * (a_lly -   lly)
                    local w, h = xscale * (a_urx - a_llx), yscale * (a_ury - a_lly)
                     -- TH extra test op annotation.A added on line below, because of ".A.S", hack line 2
                    if (subtype  == "Link") and annotation.A then
                        local linktype = annotation.A and annotation.A.S or ""
                        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 '%s'",linktype)
                        end
                    elseif trace_links then
		        if not annotation.A then table.print(annotation) end -- TH debug info moved here, hack line 3
                        report_link("unsupported annotation '%s'",subtype)
                    end
                    end -- TH hack for broken annotations, hack line 4
                end
                context.flushlayer { "epdflinks" }
             -- context("\\gdef\\figurereference{%s}",reference) -- global
                context.setgvalue("figurereference",reference) -- global
                if trace_links then
                    report_link("setting figure reference to '%s'",reference)
                end
                specification.reference = reference
                return namespace
            end
        end
    end
    return ""-- no namespace, empty, not nil
end

function codeinjections.mergeviewerlayers(specification)
    -- todo: parse included page for layers
    if true then
        return
    end
    if not specification then
        specification = figures and figures.current()
        specification = specification and specification.status
    end
    if specification then
        local fullname = specification.fullname
        local document = lpdf.epdf.load(fullname)
        if document then
            local namespace = format("lpdf:epa:%s:",file.removesuffix(file.basename(fullname)))
            local layers = document.layers
            if layers then
                for i=1,layers.n do
                    local tag = namespace .. gsub(layers[i]," ",":")
                    local title = tag
                    if trace_links then
                        report_link("using layer '%s'",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)
                end
            end
        end
    end
end

if not modules then modules = { } end modules ['lpdf-epd'] = {
    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"
}

-- This is an experimental layer around the epdf library. The reason for
-- this layer is that I want to be independent of the library (which
-- implements a selection of what a file provides) and also because I
-- want an interface closer to Lua's table model while the API stays
-- close to the original xpdf library. Of course, after prototyping a
-- solution, we can optimize it using the low level epdf accessors.

-- It will be handy when we have a __length and __next that can trigger
-- the resolve till then we will provide .n as #.

-- As there can be references to the parent we cannot expand a tree. I
-- played with some expansion variants but it does to pay off.

-- Maybe we need a close().
-- We cannot access all destinations in one run.

local setmetatable, rawset, rawget, tostring, tonumber = setmetatable, rawset, rawget, tostring, tonumber
local lower, match, char, find, sub = string.lower, string.match, string.char, string.find, string.sub
local concat = table.concat
local toutf = string.toutf

-- a bit of protection

local limited = false

directives.register("system.inputmode", function(v)
    if not limited then
        local i_limiter = io.i_limiter(v)
        if i_limiter then
            epdf.open = i_limiter.protect(epdf.open)
            limited = true
        end
    end
end)

--

function epdf.type(o)
    local t = lower(match(tostring(o),"[^ :]+"))
    return t or "?"
end

lpdf = lpdf or { }
local lpdf = lpdf

lpdf.epdf  = { }

local checked_access

local function prepare(document,d,t,n,k)
    for i=1,n do
        local v = d:getVal(i)
        local r = d:getValNF(i)
        if r:getTypeName() ~= "ref" then
            t[d:getKey(i)] = checked_access[v:getTypeName()](v,document)
        else
            r = r:getRef().num
            local c = document.cache[r]
            if c then
                --
            else
                c = checked_access[v:getTypeName()](v,document,r)
                if c then
                    document.cache[r] = c
                    document.xrefs[c] = r
                end
            end
            t[d:getKey(i)] = c
        end
    end
    getmetatable(t).__index = nil
    return t[k]
end

local function some_dictionary(d,document,r)
    local n = d and d:getLength() or 0
    if n > 0 then
        local t = { }
        setmetatable(t, { __index = function(t,k) return prepare(document,d,t,n,k) end } )
        return t
    end
end

local done = { }

local function prepare(document,a,t,n,k)
    for i=1,n do
        local v = a:get(i)
        local r = a:getNF(i)
        if v:getTypeName() ~= "null" then -- TH weird, but appears possible
        if r:getTypeName() ~= "ref" then
            t[i] = checked_access[v:getTypeName()](v,document)
        else
            r = r:getRef().num
            local c = document.cache[r]
            if c then
                --
            else
                c = checked_access[v:getTypeName()](v,document,r)
                document.cache[r] = c
                document.xrefs[c] = r
            end
            t[i] = c
        end
        end
    end
    getmetatable(t).__index = nil
    return t[k]
end

local function some_array(a,document,r)
    local n = a and a:getLength() or 0
    if n > 0 then
        local t = { n = n }
        setmetatable(t, { __index = function(t,k) return prepare(document,a,t,n,k) end } )
        return t
    end
end

local function streamaccess(s,_,what)
    if not what or what == "all" or what == "*all" then
        local t, n = { }, 0
        s:streamReset()
        while true do
            local c = s:streamGetChar()
            if c < 0 then
                break
            else
                n = n + 1
                t[n] = char(c)
            end
        end
        return concat(t)
    end
end

local function some_stream(d,document,r)
    if d then
        d:streamReset()
        local s = some_dictionary(d:streamGetDict(),document,r)
        getmetatable(s).__call = function(...) return streamaccess(d,...) end
        return s
    end
end

-- we need epdf.getBool

checked_access = {
    dictionary = function(d,document,r)
        return some_dictionary(d:getDict(),document,r)
    end,
    array = function(a,document,r)
        return some_array(a:getArray(),document,r)
    end,
    stream = function(v,document,r)
        return some_stream(v,document,r)
    end,
    real = function(v)
        return v:getReal()
    end,
    integer = function(v)
        return v:getNum()
    end,
    string = function(v)
        return toutf(v:getString())
    end,
    boolean = function(v)
        return v:getBool()
    end,
    name = function(v)
        return v:getName()
    end,
    ref = function(v)
        return v:getRef()
    end,
}

--~ checked_access.real    = epdf.real
--~ checked_access.integer = epdf.integer
--~ checked_access.string  = epdf.string
--~ checked_access.boolean = epdf.boolean
--~ checked_access.name    = epdf.name
--~ checked_access.ref     = epdf.ref

local function getnames(document,n,target) -- direct
    if n then
        local Names = n.Names
        if Names then
            if not target then
                target = { }
            end
            for i=1,Names.n,2 do
                target[Names[i]] = Names[i+1]
            end
        else
            local Kids = n.Kids
            if Kids then
                for i=1,Kids.n do
                    target = getnames(document,Kids[i],target)
                end
            end
        end
        return target
    end
end

local function getkids(document,n,target) -- direct
    if n then
        local Kids = n.Kids
        if Kids then
            for i=1,Kids.n do
                target = getkids(document,Kids[i],target)
            end
        elseif target then
            target[#target+1] = n
        else
            target = { n }
        end
        return target
    end
end

-- /OCProperties <<
--     /OCGs [ 15 0 R 17 0 R 19 0 R 21 0 R 23 0 R 25 0 R 27 0 R ]
--     /D <<
--         /Order [ 15 0 R 17 0 R 19 0 R 21 0 R 23 0 R 25 0 R 27 0 R ]
--         /ON    [ 15 0 R 17 0 R 19 0 R 21 0 R 23 0 R 25 0 R 27 0 R ]
--         /OFF   [ ]
--     >>
-- >>

local function getlayers(document)
    local properties = document.Catalog.OCProperties
    if properties then
        local layers = properties.OCGs
        if layers then
            local t = { }
            local n = layers.n
            for i=1,n do
                local layer = layers[i]
--~ print(document.xrefs[layer])
                t[i] = layer.Name
            end
            t.n = n
            return t
        end
    end
end

local function getpages(document)
    local data  = document.data
    local xrefs = document.xrefs
    local cache = document.cache
    local cata  = data:getCatalog()
    local xref  = data:getXRef()
    local pages = { }
    local nofpages = cata:getNumPages()
    for pagenumber=1,nofpages do
        local pagereference = cata:getPageRef(pagenumber).num
        local pagedata = some_dictionary(xref:fetch(pagereference,0):getDict(),document,pagereference)
        if pagedata then 
        pagedata.number = pagenumber
        pages[pagenumber] = pagedata
        xrefs[pagedata] = pagereference
        cache[pagereference] = pagedata
        end
    end
    pages.n = nofpages
    return pages
end

-- loader

local function delayed(document,tag,f)
    local t = { }
    setmetatable(t, { __index = function(t,k)
        local result = f()
        if result then
            document[tag] = result
            return result[k]
        end
    end } )
    return t
end

local loaded = { }

function lpdf.epdf.load(filename)
    local document = loaded[filename]
    if not document then
        statistics.starttiming(lpdf.epdf)
        local data = epdf.open(filename) -- maybe resolvers.find_file
        if data then
            document = {
                filename = filename,
                cache    = { },
                xrefs    = { },
                data     = data,
            }
            local Catalog    = some_dictionary(data:getXRef():getCatalog():getDict(),document)
            local Info       = some_dictionary(data:getXRef():getDocInfo():getDict(),document)
            document.Catalog = Catalog
            document.Info    = Info
         -- document.catalog = Catalog
            -- a few handy helper tables
            document.pages         = delayed(document,"pages",        function() return getpages(document) end)
            document.destinations  = delayed(document,"destinations", function() return getnames(document,Catalog.Names and Catalog.Names.Dests) end)
            document.javascripts   = delayed(document,"javascripts",  function() return getnames(document,Catalog.Names and Catalog.Names.JS) end)
            document.widgets       = delayed(document,"widgets",      function() return getnames(document,Catalog.Names and Catalog.Names.AcroForm) end)
            document.embeddedfiles = delayed(document,"embeddedfiles",function() return getnames(document,Catalog.Names and Catalog.Names.EmbeddedFiles) end)
            document.layers        = delayed(document,"layers",       function() return getlayers(document) end)
        else
            document = false
        end
        loaded[filename] = document
        statistics.stoptiming(lpdf.epdf)
     -- print(statistics.elapsedtime(lpdf.epdf))
    end
    return document
end

-- helpers

-- function lpdf.epdf.getdestinationpage(document,name)
--     local destination = document.data:findDest(name)
--     return destination and destination.number
-- end
_______________________________________________
dev-context mailing list
dev-context@ntg.nl
http://www.ntg.nl/mailman/listinfo/dev-context

Reply via email to