if not modules then modules = { } end modules ['lpdf-ini'] = { 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" } -- beware of "too many locals" here local setmetatable, getmetatable, type, next, tostring, tonumber, rawset = setmetatable, getmetatable, type, next, tostring, tonumber, rawset local char, byte, format, gsub, concat, match, sub, gmatch = string.char, string.byte, string.format, string.gsub, table.concat, string.match, string.sub, string.gmatch local utfchar, utfbyte, utfvalues = utf.char, utf.byte, utf.values local sind, cosd, floor, max, min = math.sind, math.cosd, math.floor, math.max, math.min local lpegmatch, P, C, R, S, Cc, Cs = lpeg.match, lpeg.P, lpeg.C, lpeg.R, lpeg.S, lpeg.Cc, lpeg.Cs local formatters = string.formatters local isboolean = string.is_boolean local report_objects = logs.reporter("backend","objects") local report_finalizing = logs.reporter("backend","finalizing") local report_blocked = logs.reporter("backend","blocked") local implement = interfaces.implement local two_strings = interfaces.strings[2] -- In ConTeXt MkIV we use utf8 exclusively so all strings get mapped onto a hex -- encoded utf16 string type between <>. We could probably save some bytes by using -- strings between () but then we end up with escaped ()\ too. -- gethpos : used -- getpos : used -- getvpos : used -- -- getmatrix : used -- hasmatrix : used -- -- mapfile : used in font-ctx.lua -- mapline : used in font-ctx.lua -- -- maxobjnum : not used -- obj : used -- immediateobj : used -- objtype : not used -- pageref : used -- print : can be used -- refobj : used -- registerannot : not to be used -- reserveobj : used -- pdf.catalog : used -- pdf.info : used -- pdf.trailer : used -- pdf.names : not to be used -- pdf.setinfo : used -- pdf.setcatalog : used -- pdf.setnames : not to be used -- pdf.settrailer : used -- pdf.getinfo : used -- pdf.getcatalog : used -- pdf.getnames : not to be used -- pdf.gettrailer : used local pdf = pdf local factor = number.dimenfactors.bp if pdf.setinfo then -- table.setmetatablenewindex(pdf,function(t,k,v) -- report_blocked("'pdf.%s' is not supported",k) -- end) -- the getters are harmless end if not pdf.setinfo then function pdf.setinfo (s) pdf.info = s end function pdf.setcatalog(s) pdf.catalog = s end function pdf.setnames (s) pdf.names = s end function pdf.settrailer(s) pdf.trailer = s end end if not pdf.getpos then function pdf.getpos () return pdf.h, pdf.v end function pdf.gethpos () return pdf.h end function pdf.getvpos () return pdf.v end function pdf.hasmatrix() return false end function pdf.getmatrix() return 1, 0, 0, 1, 0, 0 end end if not pdf.setpageresources then function pdf.setpageresources (s) pdf.pageresources = s end function pdf.setpageattributes (s) pdf.pageattributes = s end function pdf.setpagesattributes(s) pdf.pagesattributes = s end end local pdfsetinfo = pdf.setinfo local pdfsetcatalog = pdf.setcatalog local pdfsetnames = pdf.setnames local pdfsettrailer = pdf.settrailer local pdfsetpageresources = pdf.setpageresources local pdfsetpageattributes = pdf.setpageattributes local pdfsetpagesattributes = pdf.setpagesattributes local pdfgetpos = pdf.getpos local pdfgethpos = pdf.gethpos local pdfgetvpos = pdf.getvpos local pdfgetmatrix = pdf.getmatrix local pdfhasmatrix = pdf.hasmatrix local pdfreserveobject = pdf.reserveobj local pdfimmediateobject = pdf.immediateobj local pdfdeferredobject = pdf.obj local pdfreferenceobject = pdf.refobj -- function pdf.setinfo () report_blocked("'pdf.%s' is not supported","setinfo") end -- use lpdf.addtoinfo etc -- function pdf.setcatalog () report_blocked("'pdf.%s' is not supported","setcatalog") end -- function pdf.setnames () report_blocked("'pdf.%s' is not supported","setnames") end -- function pdf.settrailer () report_blocked("'pdf.%s' is not supported","settrailer") end -- function pdf.setpageresources () report_blocked("'pdf.%s' is not supported","setpageresources") end -- function pdf.setpageattributes () report_blocked("'pdf.%s' is not supported","setpageattributes") end -- function pdf.setpagesattributes() report_blocked("'pdf.%s' is not supported","setpagesattributes") end -- function pdf.registerannot () report_blocked("'pdf.%s' is not supported","registerannot") end local function pdfdisablecommand(command) pdf[command] = function() report_blocked("'pdf.%s' is not supported",command) end end pdfdisablecommand("setinfo") pdfdisablecommand("setcatalog") pdfdisablecommand("setnames") pdfdisablecommand("settrailer") pdfdisablecommand("setpageresources") pdfdisablecommand("setpageattributes") pdfdisablecommand("setpagesattributes") pdfdisablecommand("registerannot") local trace_finalizers = false trackers.register("backend.finalizers", function(v) trace_finalizers = v end) local trace_resources = false trackers.register("backend.resources", function(v) trace_resources = v end) local trace_objects = false trackers.register("backend.objects", function(v) trace_objects = v end) local trace_detail = false trackers.register("backend.detail", function(v) trace_detail = v end) local backends = backends local pdfbackend = { comment = "backend for directly generating pdf output", nodeinjections = { }, codeinjections = { }, registrations = { }, tables = { }, } backends.pdf = pdfbackend lpdf = lpdf or { } local lpdf = lpdf local codeinjections = pdfbackend.codeinjections local nodeinjections = pdfbackend.nodeinjections codeinjections.getpos = pdfgetpos lpdf.getpos = pdfgetpos codeinjections.gethpos = pdfgethpos lpdf.gethpos = pdfgethpos codeinjections.getvpos = pdfgetvpos lpdf.getvpos = pdfgetvpos codeinjections.hasmatrix = pdfhasmatrix lpdf.hasmatrix = pdfhasmatrix codeinjections.getmatrix = pdfgetmatrix lpdf.getmatrix = pdfgetmatrix function lpdf.transform(llx,lly,urx,ury) if pdfhasmatrix() then local sx, rx, ry, sy = pdfgetmatrix() local w, h = urx - llx, ury - lly return llx, lly, llx + sy*w - ry*h, lly + sx*h - rx*w else return llx, lly, urx, ury end end -- function lpdf.rectangle(width,height,depth) -- local h, v = pdfgetpos() -- local llx, lly, urx, ury -- if pdfhasmatrix() then -- local sx, rx, ry, sy = pdfgetmatrix() -- llx = 0 -- lly = -depth -- -- llx = ry * depth -- -- lly = -sx * depth -- urx = sy * width - ry * height -- ury = sx * height - rx * width -- else -- llx = 0 -- lly = -depth -- urx = width -- ury = height -- return (h+llx)*factor, (v+lly)*factor, (h+urx)*factor, (v+ury)*factor -- end -- end function lpdf.rectangle(width,height,depth) local h, v = pdfgetpos() if pdfhasmatrix() then local sx, rx, ry, sy = pdfgetmatrix() -- return (h+ry*depth)*factor, (v-sx*depth)*factor, (h+sy*width-ry*height)*factor, (v+sx*height-rx*width)*factor return h *factor, (v- depth)*factor, (h+sy*width-ry*height)*factor, (v+sx*height-rx*width)*factor else return h *factor, (v- depth)*factor, (h+ width )*factor, (v+ height )*factor end end -- we could use a hash of predefined unicodes -- local function tosixteen(str) -- an lpeg might be faster (no table) -- if not str or str == "" then -- return "" -- not () as we want an indication that it's unicode -- else -- local r, n = { "")) local function tosixteen(str) -- an lpeg might be faster (no table) if not str or str == "" then return "" -- not () as we want an indication that it's unicode else return lpegmatch(unified,str) end end local more = 0 local pattern = C(4) / function(s) -- needs checking ! local now = tonumber(s,16) if more > 0 then now = (more-0xD800)*0x400 + (now-0xDC00) + 0x10000 -- the 0x10000 smells wrong more = 0 return utfchar(now) elseif now >= 0xD800 and now <= 0xDBFF then more = now return "" -- else the c's end up in the stream else return utfchar(now) end end local pattern = P(true) / function() more = 0 end * Cs(pattern^0) local function fromsixteen(str) if not str or str == "" then return "" else return lpegmatch(pattern,str) end end local toregime = regimes.toregime local fromregime = regimes.fromregime local function topdfdoc(str,default) if not str or str == "" then return "" else return lpegmatch(escaped,toregime("pdfdoc",str,default)) -- could be combined if needed end end local function frompdfdoc(str) if not str or str == "" then return "" else return fromregime("pdfdoc",str) end end if not toregime then topdfdoc = function(s) return s end end if not fromregime then frompdfdoc = function(s) return s end end local function toeight(str) if not str or str == "" then return "()" else return lpegmatch(escaped,str) end end lpdf.tosixteen = tosixteen lpdf.toeight = toeight lpdf.topdfdoc = topdfdoc lpdf.fromsixteen = fromsixteen lpdf.frompdfdoc = frompdfdoc local function merge_t(a,b) local t = { } for k,v in next, a do t[k] = v end for k,v in next, b do t[k] = v end return setmetatable(t,getmetatable(a)) end local f_key_null = formatters["/%s null"] local f_key_value = formatters["/%s %s"] local f_key_dictionary = formatters["/%s << % t >>"] local f_dictionary = formatters["<< % t >>"] local f_key_array = formatters["/%s [ % t ]"] local f_array = formatters["[ % t ]"] local f_key_number = formatters["/%s %F"] local f_tonumber = formatters["%F"] -- local f_key_value = formatters["/%s %s"] -- local f_key_dictionary = formatters["/%s <<% t>>"] -- local f_dictionary = formatters["<<% t>>"] -- local f_key_array = formatters["/%s [% t]"] -- local f_array = formatters["[% t]"] local tostring_a, tostring_d tostring_d = function(t,contentonly,key) if next(t) then local r, rn = { }, 0 for k, v in next, t do rn = rn + 1 local tv = type(v) if tv == "string" then r[rn] = f_key_value(k,toeight(v)) elseif tv == "number" then r[rn] = f_key_number(k,v) -- elseif tv == "unicode" then -- can't happen -- r[rn] = f_key_value(k,tosixteen(v)) elseif tv == "table" then local mv = getmetatable(v) if mv and mv.__lpdftype then -- if v == t then -- report_objects("ignoring circular reference in dirctionary") -- r[rn] = f_key_null(k) -- else r[rn] = f_key_value(k,tostring(v)) -- end elseif v[1] then r[rn] = f_key_value(k,tostring_a(v)) else r[rn] = f_key_value(k,tostring_d(v)) end else r[rn] = f_key_value(k,tostring(v)) end end if contentonly then return concat(r," ") elseif key then return f_key_dictionary(key,r) else return f_dictionary(r) end elseif contentonly then return "" else return "<< >>" end end tostring_a = function(t,contentonly,key) local tn = #t if tn ~= 0 then local r = { } for k=1,tn do local v = t[k] local tv = type(v) if tv == "string" then r[k] = toeight(v) elseif tv == "number" then r[k] = f_tonumber(v) -- elseif tv == "unicode" then -- r[k] = tosixteen(v) elseif tv == "table" then local mv = getmetatable(v) local mt = mv and mv.__lpdftype if mt then -- if v == t then -- report_objects("ignoring circular reference in array") -- r[k] = "null" -- else r[k] = tostring(v) -- end elseif v[1] then r[k] = tostring_a(v) else r[k] = tostring_d(v) end else r[k] = tostring(v) end end if contentonly then return concat(r, " ") elseif key then return f_key_array(key,r) else return f_array(r) end elseif contentonly then return "" else return "[ ]" end end local tostring_x = function(t) return concat(t," ") end local tostring_s = function(t) return toeight(t[1]) end local tostring_p = function(t) return topdfdoc(t[1],t[2]) end local tostring_u = function(t) return tosixteen(t[1]) end ----- tostring_n = function(t) return tostring(t[1]) end -- tostring not needed local tostring_n = function(t) return f_tonumber(t[1]) end -- tostring not needed local tostring_c = function(t) return t[1] end -- already prefixed (hashed) local tostring_z = function() return "null" end local tostring_t = function() return "true" end local tostring_f = function() return "false" end local tostring_r = function(t) local n = t[1] return n and n > 0 and (n .. " 0 R") or "null" end local tostring_v = function(t) local s = t[1] if type(s) == "table" then return concat(s) else return s end end local function value_x(t) return t end local function value_s(t) return t[1] end local function value_p(t) return t[1] end local function value_u(t) return t[1] end local function value_n(t) return t[1] end local function value_c(t) return sub(t[1],2) end local function value_d(t) return tostring_d(t,true) end local function value_a(t) return tostring_a(t,true) end local function value_z() return nil end local function value_t(t) return t.value or true end local function value_f(t) return t.value or false end local function value_r() return t[1] or 0 end -- null local function value_v() return t[1] end local function add_x(t,k,v) rawset(t,k,tostring(v)) end local mt_x = { __lpdftype = "stream", __tostring = tostring_x, __call = value_x, __newindex = add_x } local mt_d = { __lpdftype = "dictionary", __tostring = tostring_d, __call = value_d } local mt_a = { __lpdftype = "array", __tostring = tostring_a, __call = value_a } local mt_u = { __lpdftype = "unicode", __tostring = tostring_u, __call = value_u } local mt_s = { __lpdftype = "string", __tostring = tostring_s, __call = value_s } local mt_p = { __lpdftype = "docstring", __tostring = tostring_p, __call = value_p } local mt_n = { __lpdftype = "number", __tostring = tostring_n, __call = value_n } local mt_c = { __lpdftype = "constant", __tostring = tostring_c, __call = value_c } local mt_z = { __lpdftype = "null", __tostring = tostring_z, __call = value_z } local mt_t = { __lpdftype = "true", __tostring = tostring_t, __call = value_t } local mt_f = { __lpdftype = "false", __tostring = tostring_f, __call = value_f } local mt_r = { __lpdftype = "reference", __tostring = tostring_r, __call = value_r } local mt_v = { __lpdftype = "verbose", __tostring = tostring_v, __call = value_v } local function pdfstream(t) -- we need to add attributes if t then for i=1,#t do t[i] = tostring(t[i]) end end return setmetatable(t or { },mt_x) end local function pdfdictionary(t) return setmetatable(t or { },mt_d) end local function pdfarray(t) if type(t) == "string" then return setmetatable({ t },mt_a) else return setmetatable(t or { },mt_a) end end local function pdfstring(str,default) return setmetatable({ str or default or "" },mt_s) end local function pdfdocstring(str,default,defaultchar) return setmetatable({ str or default or "", defaultchar or " " },mt_p) end local function pdfunicode(str,default) return setmetatable({ str or default or "" },mt_u) -- could be a string end local cache = { } -- can be weak local function pdfnumber(n,default) -- 0-10 n = n or default local c = cache[n] if not c then c = setmetatable({ n },mt_n) -- cache[n] = c -- too many numbers end return c end for i=-1,9 do cache[i] = pdfnumber(i) end local cache = { } -- can be weak local forbidden, replacements = "\0\t\n\r\f ()[]{}/%%#\\", { } -- table faster than function for s in gmatch(forbidden,".") do replacements[s] = format("#%02x",byte(s)) end local escaped = Cs(Cc("/") * (S(forbidden)/replacements + P(1))^0) local function pdfconstant(str,default) str = str or default or "" local c = cache[str] if not c then -- c = setmetatable({ "/" .. str },mt_c) c = setmetatable({ lpegmatch(escaped,str) },mt_c) cache[str] = c end return c end local p_null = { } setmetatable(p_null, mt_z) local p_true = { } setmetatable(p_true, mt_t) local p_false = { } setmetatable(p_false,mt_f) local function pdfnull() return p_null end --~ print(pdfboolean(false),pdfboolean(false,false),pdfboolean(false,true)) --~ print(pdfboolean(true),pdfboolean(true,false),pdfboolean(true,true)) --~ print(pdfboolean(nil,true),pdfboolean(nil,false)) local function pdfboolean(b,default) if type(b) == "boolean" then return b and p_true or p_false else return default and p_true or p_false end end local r_zero = setmetatable({ 0 },mt_r) local function pdfreference(r) -- maybe make a weak table if r and r ~= 0 then return setmetatable({ r },mt_r) else return r_zero end end local v_zero = setmetatable({ 0 },mt_v) local v_empty = setmetatable({ "" },mt_v) local function pdfverbose(t) -- maybe check for type if t == 0 then return v_zero elseif t == "" then return v_empty else return setmetatable({ t },mt_v) end end lpdf.stream = pdfstream -- THIS WILL PROBABLY CHANGE lpdf.dictionary = pdfdictionary lpdf.array = pdfarray lpdf.docstring = pdfdocstring lpdf.string = pdfstring lpdf.unicode = pdfunicode lpdf.number = pdfnumber lpdf.constant = pdfconstant lpdf.null = pdfnull lpdf.boolean = pdfboolean lpdf.reference = pdfreference lpdf.verbose = pdfverbose local names, cache = { }, { } function lpdf.reserveobject(name) local r = pdfreserveobject() -- we don't support "annot" if name then names[name] = r if trace_objects then report_objects("reserving number %a under name %a",r,name) end elseif trace_objects then report_objects("reserving number %a",r) end return r end -- lpdf.immediateobject = pdfimmediateobject -- lpdf.deferredobject = pdfdeferredobject -- lpdf.object = pdfdeferredobject -- lpdf.referenceobject = pdfreferenceobject local pagereference = pdf.pageref -- tex.pdfpageref is obsolete local nofpages = 0 function lpdf.pagereference(n) if nofpages == 0 then nofpages = structures.pages.nofpages if nofpages == 0 then nofpages = 1 end end if n > nofpages then return pagereference(nofpages) -- or 1, could be configureable else return pagereference(n) end end function lpdf.delayedobject(data,n) if n then pdfdeferredobject(n,data) else n = pdfdeferredobject(data) end pdfreferenceobject(n) return n end function lpdf.flushobject(name,data) if data then local named = names[name] if named then if not trace_objects then elseif trace_detail then report_objects("flushing data to reserved object with name %a, data: %S",name,data) else report_objects("flushing data to reserved object with name %a",name) end return pdfimmediateobject(named,tostring(data)) else if not trace_objects then elseif trace_detail then report_objects("flushing data to reserved object with number %s, data: %S",name,data) else report_objects("flushing data to reserved object with number %s",name) end return pdfimmediateobject(name,tostring(data)) end else if trace_objects and trace_detail then report_objects("flushing data: %S",name) end return pdfimmediateobject(tostring(name)) end end function lpdf.flushstreamobject(data,dict,compressed) -- default compressed if trace_objects then report_objects("flushing stream object of %s bytes",#data) end local dtype = type(dict) return pdfdeferredobject { immediate = true, compresslevel = compressed == false and 0 or nil, type = "stream", string = data, attr = (dtype == "string" and dict) or (dtype == "table" and dict()) or nil, } end function lpdf.flushstreamfileobject(filename,dict,compressed) -- default compressed if trace_objects then report_objects("flushing stream file object %a",filename) end local dtype = type(dict) return pdfdeferredobject { immediate = true, compresslevel = compressed == false and 0 or nil, type = "stream", file = filename, attr = (dtype == "string" and dict) or (dtype == "table" and dict()) or nil, } end local shareobjectcache, shareobjectreferencecache = { }, { } function lpdf.shareobject(content) if content == nil then -- invalid object not created else content = tostring(content) local o = shareobjectcache[content] if not o then o = pdfimmediateobject(content) shareobjectcache[content] = o end return o end end function lpdf.shareobjectreference(content) if content == nil then -- invalid object not created else content = tostring(content) local r = shareobjectreferencecache[content] if not r then local o = shareobjectcache[content] if not o then o = pdfimmediateobject(content) shareobjectcache[content] = o end r = pdfreference(o) shareobjectreferencecache[content] = r end return r end end -- three priority levels, default=2 local pagefinalizers = { { }, { }, { } } local documentfinalizers = { { }, { }, { } } local pageresources, pageattributes, pagesattributes local function resetpageproperties() pageresources = pdfdictionary() pageattributes = pdfdictionary() pagesattributes = pdfdictionary() end resetpageproperties() local function setpageproperties() pdfsetpageresources (pageresources ()) pdfsetpageattributes (pageattributes ()) pdfsetpagesattributes(pagesattributes()) end local function addtopageresources (k,v) pageresources [k] = v end local function addtopageattributes (k,v) pageattributes [k] = v end local function addtopagesattributes(k,v) pagesattributes[k] = v end lpdf.addtopageresources = addtopageresources lpdf.addtopageattributes = addtopageattributes lpdf.addtopagesattributes = addtopagesattributes local function set(where,what,f,when,comment) if type(when) == "string" then when, comment = 2, when elseif not when then when = 2 end local w = where[when] w[#w+1] = { f, comment } if trace_finalizers then report_finalizing("%s set: [%s,%s]",what,when,#w) end end local function run(where,what) if trace_finalizers then report_finalizing("start backend, category %a, n %a",what,#where) end for i=1,#where do local w = where[i] for j=1,#w do local wj = w[j] if trace_finalizers then report_finalizing("%s finalizer: [%s,%s] %s",what,i,j,wj[2] or "") end wj[1]() end end if trace_finalizers then report_finalizing("stop finalizing") end end local function registerpagefinalizer(f,when,comment) set(pagefinalizers,"page",f,when,comment) end local function registerdocumentfinalizer(f,when,comment) set(documentfinalizers,"document",f,when,comment) end lpdf.registerpagefinalizer = registerpagefinalizer lpdf.registerdocumentfinalizer = registerdocumentfinalizer function lpdf.finalizepage(shipout) if shipout and not environment.initex then -- resetpageproperties() -- maybe better before run(pagefinalizers,"page") setpageproperties() resetpageproperties() -- maybe better before end end function lpdf.finalizedocument() if not environment.initex then run(documentfinalizers,"document") function lpdf.finalizedocument() report_finalizing("serious error: the document is finalized multiple times") function lpdf.finalizedocument() end end end end -- codeinjections.finalizepage = lpdf.finalizepage -- no longer triggered at the tex end if not callbacks.register("finish_pdfpage", lpdf.finalizepage) then local find_tail = nodes.tail local latelua_node = nodes.pool.latelua function nodeinjections.finalizepage(head) local t = find_tail(head.list) if t then local n = latelua_node("lpdf.finalizepage(true)") -- last in the shipout t.next = n n.prev = t end return head, true end nodes.tasks.appendaction("shipouts","normalizers","backends.pdf.nodeinjections.finalizepage") end callbacks.register("finish_pdffile", lpdf.finalizedocument) do -- some minimal tracing, handy for checking the order local function trace_set(what,key) if trace_resources then report_finalizing("setting key %a in %a",key,what) end end local function trace_flush(what) if trace_resources then report_finalizing("flushing %a",what) end end lpdf.protectresources = true local catalog = pdfdictionary { Type = pdfconstant("Catalog") } -- nicer, but when we assign we nil the Type local info = pdfdictionary { Type = pdfconstant("Info") } -- nicer, but when we assign we nil the Type ----- names = pdfdictionary { Type = pdfconstant("Names") } -- nicer, but when we assign we nil the Type local function flushcatalog() if not environment.initex then trace_flush("catalog") catalog.Type = nil pdfsetcatalog(catalog()) end end local function flushinfo() if not environment.initex then trace_flush("info") info.Type = nil pdfsetinfo(info()) end end -- local function flushnames() -- if not environment.initex then -- trace_flush("names") -- names.Type = nil -- pdfsetnames(names()) -- end -- end function lpdf.addtocatalog(k,v) if not (lpdf.protectresources and catalog[k]) then trace_set("catalog",k) catalog[k] = v end end function lpdf.addtoinfo(k,v) if not (lpdf.protectresources and info[k]) then trace_set("info",k) info[k] = v end end -- local function lpdf.addtonames(k,v) -- if not (lpdf.protectresources and names[k]) then -- trace_set("names",k) -- names[k] = v -- end -- end local names = pdfdictionary { -- Type = pdfconstant("Names") } local function flushnames() if next(names) and not environment.initex then names.Type = pdfconstant("Names") trace_flush("names") lpdf.addtocatalog("Names",pdfreference(pdfimmediateobject(tostring(names)))) end end function lpdf.addtonames(k,v) if not (lpdf.protectresources and names[k]) then trace_set("names", k) names [k] = v end end local r_extgstates, d_extgstates = pdfreserveobject(), pdfdictionary() local p_extgstates = pdfreference(r_extgstates) local r_colorspaces, d_colorspaces = pdfreserveobject(), pdfdictionary() local p_colorspaces = pdfreference(r_colorspaces) local r_patterns, d_patterns = pdfreserveobject(), pdfdictionary() local p_patterns = pdfreference(r_patterns) local r_shades, d_shades = pdfreserveobject(), pdfdictionary() local p_shades = pdfreference(r_shades) local function checkextgstates () if next(d_extgstates ) then addtopageresources("ExtGState", p_extgstates ) end end local function checkcolorspaces() if next(d_colorspaces) then addtopageresources("ColorSpace",p_colorspaces) end end local function checkpatterns () if next(d_patterns ) then addtopageresources("Pattern", p_patterns ) end end local function checkshades () if next(d_shades ) then addtopageresources("Shading", p_shades ) end end local function flushextgstates () if next(d_extgstates ) then trace_flush("extgstates") pdfimmediateobject(r_extgstates, tostring(d_extgstates )) end end local function flushcolorspaces() if next(d_colorspaces) then trace_flush("colorspaces") pdfimmediateobject(r_colorspaces,tostring(d_colorspaces)) end end local function flushpatterns () if next(d_patterns ) then trace_flush("patterns") pdfimmediateobject(r_patterns, tostring(d_patterns )) end end local function flushshades () if next(d_shades ) then trace_flush("shades") pdfimmediateobject(r_shades, tostring(d_shades )) end end function lpdf.collectedresources() local ExtGState = next(d_extgstates ) and p_extgstates local ColorSpace = next(d_colorspaces) and p_colorspaces local Pattern = next(d_patterns ) and p_patterns local Shading = next(d_shades ) and p_shades if ExtGState or ColorSpace or Pattern or Shading then local collected = pdfdictionary { ExtGState = ExtGState, ColorSpace = ColorSpace, Pattern = Pattern, Shading = Shading, -- ProcSet = pdfarray { pdfconstant("PDF") }, } return collected() else return "" end end function lpdf.adddocumentextgstate (k,v) d_extgstates [k] = v end function lpdf.adddocumentcolorspace(k,v) d_colorspaces[k] = v end function lpdf.adddocumentpattern (k,v) d_patterns [k] = v end function lpdf.adddocumentshade (k,v) d_shades [k] = v end registerdocumentfinalizer(flushextgstates,3,"extended graphic states") registerdocumentfinalizer(flushcolorspaces,3,"color spaces") registerdocumentfinalizer(flushpatterns,3,"patterns") registerdocumentfinalizer(flushshades,3,"shades") registerdocumentfinalizer(flushnames,3,"names") -- before catalog registerdocumentfinalizer(flushcatalog,3,"catalog") registerdocumentfinalizer(flushinfo,3,"info") registerpagefinalizer(checkextgstates,3,"extended graphic states") registerpagefinalizer(checkcolorspaces,3,"color spaces") registerpagefinalizer(checkpatterns,3,"patterns") registerpagefinalizer(checkshades,3,"shades") end -- in strc-bkm: lpdf.registerdocumentfinalizer(function() structures.bookmarks.place() end,1) function lpdf.rotationcm(a) local s, c = sind(a), cosd(a) return format("%0.6F %0.6F %0.6F %0.6F 0 0 cm",c,s,-s,c) end -- ! -> universaltime do local timestamp = os.date("%Y-%m-%dT%X") .. os.timezone(true) function lpdf.timestamp() return timestamp end function lpdf.pdftimestamp(str) local Y, M, D, h, m, s, Zs, Zh, Zm = match(str,"^(%d%d%d%d)%-(%d%d)%-(%d%d)T(%d%d):(%d%d):(%d%d)([%+%-])(%d%d):(%d%d)$") return Y and format("D:%s%s%s%s%s%s%s%s'%s'",Y,M,D,h,m,s,Zs,Zh,Zm) end function lpdf.id() return format("%s.%s",tex.jobname,timestamp) end end -- return nil is nicer in test prints function lpdf.checkedkey(t,key,variant) local pn = t and t[key] if pn ~= nil then local tn = type(pn) if tn == variant then if variant == "string" then if pn ~= "" then return pn end elseif variant == "table" then if next(pn) then return pn end else return pn end elseif tn == "string" then if variant == "number" then return tonumber(pn) elseif variant == "boolean" then return isboolean(pn,nil,true) end end end -- return nil end function lpdf.checkedvalue(value,variant) -- code not shared if value ~= nil then local tv = type(value) if tv == variant then if variant == "string" then if value ~= "" then return value end elseif variant == "table" then if next(value) then return value end else return value end elseif tv == "string" then if variant == "number" then return tonumber(value) elseif variant == "boolean" then return isboolean(value,nil,true) end end end -- return nil end function lpdf.limited(n,min,max,default) if not n then return default else n = tonumber(n) if not n then return default elseif n > max then return max elseif n < min then return min else return n end end end -- lpdf.addtoinfo("ConTeXt.Version", environment.version) -- lpdf.addtoinfo("ConTeXt.Time", os.date("%Y.%m.%d %H:%M")) -- :%S -- lpdf.addtoinfo("ConTeXt.Jobname", environment.jobname) -- lpdf.addtoinfo("ConTeXt.Url", "www.pragma-ade.com") -- lpdf.addtoinfo("ConTeXt.Support", "contextgarden.net") -- if not pdfreferenceobject then -- -- local delayed = { } -- -- local function flush() -- local n = 0 -- for k,v in next, delayed do -- pdfimmediateobject(k,v) -- n = n + 1 -- end -- if trace_objects then -- report_objects("%s objects flushed",n) -- end -- delayed = { } -- end -- -- lpdf.registerdocumentfinalizer(flush,3,"objects") -- so we need a final flush too -- lpdf.registerpagefinalizer (flush,3,"objects") -- somehow this lags behind .. I need to look into that some day -- -- function lpdf.delayedobject(data) -- local n = pdfreserveobject() -- delayed[n] = data -- return n -- end -- -- end -- setmetatable(pdf, { -- __index = function(t,k) -- if k == "info" then return pdf.getinfo() -- elseif k == "catalog" then return pdf.getcatalog() -- elseif k == "names" then return pdf.getnames() -- elseif k == "trailer" then return pdf.gettrailer() -- elseif k == "pageattribute" then return pdf.getpageattribute() -- elseif k == "pageattributes" then return pdf.getpageattributes() -- elseif k == "pageresources" then return pdf.getpageresources() -- elseif -- return nil -- end -- end, -- __newindex = function(t,k,v) -- if k == "info" then return pdf.setinfo(v) -- elseif k == "catalog" then return pdf.setcatalog(v) -- elseif k == "names" then return pdf.setnames(v) -- elseif k == "trailer" then return pdf.settrailer(v) -- elseif k == "pageattribute" then return pdf.setpageattribute(v) -- elseif k == "pageattributes" then return pdf.setpageattributes(v) -- elseif k == "pageresources" then return pdf.setpageresources(v) -- else -- rawset(t,k,v) -- end -- end, -- }) -- The next variant of ActualText is what Taco and I could come up with -- eventually. As of September 2013 Acrobat copies okay, Sumatra copies a -- question mark, pdftotext injects an extra space and Okular adds a -- newline plus space. -- return formatters["BT /Span << /ActualText (CONTEXT) >> BDC [] TJ % t EMC ET"](code) do local f_actual_text_one = formatters["BT /Span << /ActualText >> BDC [] TJ %s EMC ET"] local f_actual_text_two = formatters["BT /Span << /ActualText >> BDC [] TJ %s EMC ET"] local f_actual_text = formatters["/Span <> BDC"] local context = context local pdfdirect = nodes.pool.pdfdirect function codeinjections.unicodetoactualtext(unicode,pdfcode) if unicode < 0x10000 then return f_actual_text_one(unicode,pdfcode) else return f_actual_text_two(unicode/1024+0xD800,unicode%1024+0xDC00,pdfcode) end end implement { name = "startactualtext", arguments = "string", actions = function(str) context(pdfdirect(f_actual_text(tosixteen(str)))) end } implement { name = "stopactualtext", actions = function() context(pdfdirect("EMC")) end } end -- interface local lpdfverbose = lpdf.verbose implement { name = "lpdf_collectedresources", actions = { lpdf.collectedresources, context } } implement { name = "lpdf_addtocatalog", arguments = two_strings, actions = lpdf.addtocatalog } implement { name = "lpdf_addtoinfo", arguments = two_strings, actions = lpdf.addtoinfo } implement { name = "lpdf_addtonames", arguments = two_strings, actions = lpdf.addtonames } implement { name = "lpdf_addpageattributes", arguments = two_strings, actions = lpdf.addtopageattributes } implement { name = "lpdf_addpagesattributes", arguments = two_strings, actions = lpdf.addtopagesattributes } implement { name = "lpdf_addpageresources", arguments = two_strings, actions = lpdf.addtopageresources } implement { name = "lpdf_adddocumentextgstate", arguments = two_strings, actions = function(a,b) lpdf.adddocumentextgstate (a,lpdfverbose(b)) end } implement { name = "lpdf_adddocumentcolorspace", arguments = two_strings, actions = function(a,b) lpdf.adddocumentcolorspace(a,lpdfverbose(b)) end } implement { name = "lpdf_adddocumentpattern", arguments = two_strings, actions = function(a,b) lpdf.adddocumentpattern (a,lpdfverbose(b)) end } implement { name = "lpdf_adddocumentshade", arguments = two_strings, actions = function(a,b) lpdf.adddocumentshade (a,lpdfverbose(b)) end }