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 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 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, null = function() return nil 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) pagedata.number = pagenumber pages[pagenumber] = pagedata xrefs[pagedata] = pagereference cache[pagereference] = pagedata 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 -- for k, v in next, expand(t) do function lpdf.epdf.expand(t) if type(t) == "table" then local dummy = t.dummy end return t end -- helpers -- function lpdf.epdf.getdestinationpage(document,name) -- local destination = document.data:findDest(name) -- return destination and destination.number -- end