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" } 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 utfvalues = string.utfvalues local texwrite, texset, texsprint, ctxcatcodes = tex.write, tex.set, tex.sprint, tex.ctxcatcodes local sind, cosd = math.sind, math.cosd local lpegmatch = lpeg.match local pdfreserveobj = pdf and pdf.reserveobj or function() return 1 end -- for testing local pdfimmediateobj = pdf and pdf.immediateobj or function() return 2 end -- for testing 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) lpdf = lpdf or { } local function tosixteen(str) if not str or str == "" then return "()" else local r = { ""] = "\\>", -- ["["] = "\\[", ["]"] = "\\]", -- ["("] = "\\(", [")"] = "\\)", -- } -- -- local escaped = lpeg.Cs(lpeg.Cc("(") * (lpeg.S("\\/#<>[]()")/escapes + lpeg.P(1))^0 * lpeg.Cc(")")) -- -- local function toeight(str) -- if not str or str == "" then -- return "()" -- else -- return lpegmatch(escaped,str) -- end -- end -- -- -- no need for escaping .. just use unicode instead -- \0 \t \n \r \f ( ) [ ] { } / % local function toeight(str) return "(" .. str .. ")" end lpdf.toeight = toeight --~ local escaped = lpeg.Cs((lpeg.S("\0\t\n\r\f ()[]{}/%")/"#" + lpeg.P(1))^0) --~ --~ local function cleaned(str) --~ return (str and str ~= "" and lpegmatch(escaped,str)) or "" --~ end --~ --~ lpdf.cleaned = cleaned -- not public yet 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 tostring_a, tostring_d tostring_d = function(t,contentonly,key) if not next(t) then if contentonly then return "" else return "<< >>" end else local r = { } for k, v in next, t do local tv = type(v) if tv == "string" then r[#r+1] = format("/%s %s",k,toeight(v)) elseif tv == "unicode" then r[#r+1] = format("/%s %s",k,tosixteen(v)) elseif tv == "table" then local mv = getmetatable(v) if mv and mv.__lpdftype then r[#r+1] = format("/%s %s",k,tostring(v)) elseif v[1] then r[#r+1] = format("/%s %s",k,tostring_a(v)) else r[#r+1] = format("/%s %s",k,tostring_d(v)) end else r[#r+1] = format("/%s %s",k,tostring(v)) end end if contentonly then return concat(r, " ") elseif key then return format("/%s << %s >>", key, concat(r, " ")) else return format("<< %s >>", concat(r, " ")) end end end tostring_a = function(t,contentonly,key) if #t == 0 then if contentonly then return "" else return "[ ]" end else local r = { } for k, v in next, t do local tv = type(v) if tv == "string" then r[#r+1] = toeight(v) elseif tv == "unicode" then r[#r+1] = tosixteen(v) elseif tv == "table" then local mv = getmetatable(v) local mt = mv and mv.__lpdftype if mt then r[#r+1] = tostring(v) elseif v[1] then r[#r+1] = tostring_a(v) else r[#r+1] = tostring_d(v) end else r[#r+1] = tostring(v) end end if contentonly then return concat(r, " ") elseif key then return format("/%s [ %s ]", key, concat(r, " ")) else return format("[ %s ]", concat(r, " ")) end end end local tostring_x = function(t) return concat(t, " ") end local tostring_s = function(t) return toeight(t[1]) end local tostring_u = function(t) return tosixteen(t[1]) end local tostring_n = function(t) return tostring(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) return t[1] .. " 0 R" 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 -- the call is experimental local function value_s(t,key) return t[1] end -- the call is experimental local function value_u(t,key) return t[1] end -- the call is experimental local function value_n(t,key) return t[1] end -- the call is experimental local function value_c(t) return sub(t[1],2) end -- the call is experimental local function value_d(t) return tostring_d(t,true,key) end -- the call is experimental local function value_a(t) return tostring_a(t,true,key) end -- the call is experimental local function value_z() return nil end -- the call is experimental local function value_t(t) return t.value or true end -- the call is experimental local function value_f(t) return t.value or false end -- the call is experimental local function value_r() return t[1] end -- the call is experimental local function value_v() return t[1] end -- the call is experimental 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_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 attrbutes 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 pdfunicode(str,default) return setmetatable({ str or default or "" },mt_u) 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 = lpeg.Cs(lpeg.Cc("/") * (lpeg.S(forbidden)/replacements + lpeg.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 function pdfreference(r) return setmetatable({ r or 0 },mt_r) end local function pdfverbose(t) -- maybe check for type return setmetatable({ t or "" },mt_v) end lpdf.stream = pdfstream lpdf.dictionary = pdfdictionary lpdf.array = pdfarray lpdf.string = pdfstring lpdf.unicode = pdfunicode lpdf.number = pdfnumber lpdf.constant = pdfconstant lpdf.null = pdfnull lpdf.boolean = pdfboolean lpdf.reference = pdfreference lpdf.verbose = pdfverbose -- n = pdf.obj(n, str) -- n = pdf.obj(n, "file", filename) -- n = pdf.obj(n, "stream", streamtext, attrtext) -- n = pdf.obj(n, "streamfile", filename, attrtext) -- we only use immediate objects -- todo: tracing local names, cache = { }, { } function lpdf.reserveobject(name) local r = pdfreserveobj() if name then names[name] = r if trace_objects then logs.report("backends", "reserving object number %s under name '%s'",r,name) end elseif trace_objects then logs.report("backends", "reserving object number %s",r) end return r end --~ local pdfreserveobject = lpdf.reserveobject function lpdf.flushobject(name,data) if data then name = names[name] or name if name then if trace_objects then if trace_detail then logs.report("backends", "flushing object data to reserved object with name '%s' -> %s",name,tostring(data)) else logs.report("backends", "flushing object data to reserved object with name '%s'",name) end end return pdfimmediateobj(name,tostring(data)) else if trace_objects then if trace_detail then logs.report("backends", "flushing object data to reserved object with number %s -> %s",name,tostring(data)) else logs.report("backends", "flushing object data to reserved object with number %s",name) end end return pdfimmediateobj(tostring(data)) end else if trace_objects and trace_detail then logs.report("backends", "flushing object data -> %s",tostring(name)) end return pdfimmediateobj(tostring(name)) end end function lpdf.sharedobj(content) local r = cache[content] if not r then r = pdfreference(pdfimmediateobj(content)) cache[content] = r end return r end --~ local d = lpdf.dictionary() --~ local e = lpdf.dictionary { ["e"] = "abc", x = lpdf.dictionary { ["f"] = "ABC" } } --~ local f = lpdf.dictionary { ["f"] = "ABC" } --~ local a = lpdf.array { lpdf.array { lpdf.string("xxx") } } --~ print(a) --~ os.exit() --~ d["test"] = lpdf.string ("test") --~ d["more"] = "more" --~ d["bool"] = true --~ d["numb"] = 1234 --~ d["oeps"] = lpdf.dictionary { ["hans"] = "ton" } --~ d["whow"] = lpdf.array { lpdf.string("ton") } --~ a[#a+1] = lpdf.string("xxx") --~ a[#a+1] = lpdf.string("yyy") --~ d.what = a --~ print(e) --~ local d = lpdf.dictionary() --~ d["abcd"] = { 1, 2, 3, "test" } --~ print(d) --~ print(d()) --~ local d = lpdf.array() --~ d[#d+1] = 1 --~ d[#d+1] = 2 --~ d[#d+1] = 3 --~ d[#d+1] = "test" --~ print(d) --~ local d = lpdf.array() --~ d[#d+1] = { 1, 2, 3, "test" } --~ print(d) --~ local d = lpdf.array() --~ d[#d+1] = { a=1, b=2, c=3, d="test" } --~ print(d) --~ local s = lpdf.constant("xx") --~ print(s) -- fails somehow --~ print(s()) -- fails somehow --~ local s = lpdf.boolean(false) --~ s.value = true --~ print(s) --~ print(s()) -- three priority levels, default=2 local pagefinalizers, documentfinalizers = { { }, { }, { } }, { { }, { }, { } } local pageresources, pageattributes, pagesattributes local function resetpageproperties() pageresources = pdfdictionary() pageattributes = pdfdictionary() pagesattributes = pdfdictionary() end local function setpageproperties() --~ texset("global", "pdfpageresources", pageresources ()) --~ texset("global", "pdfpageattr", pageattributes ()) --~ texset("global", "pdfpagesattr", pagesattributes()) pdf.pageresources = pageresources () pdf.pageattributes = pageattributes () pdf.pagesattributes = pagesattributes() end function lpdf.addtopageresources (k,v) pageresources [k] = v end function lpdf.addtopageattributes (k,v) pageattributes [k] = v end function lpdf.addtopagesattributes(k,v) pagesattributes[k] = v end local function set(where,f,when,what) when = when or 2 local w = where[when] w[#w+1] = f if trace_finalizers then logs.report("backend","%s set: [%s,%s]",what,when,#w) end end local function run(where,what) for i=1,#where do local w = where[i] for j=1,#w do if trace_finalizers then logs.report("backend","%s finalizer: [%s,%s]",what,i,j) end w[j]() end end end function lpdf.registerpagefinalizer(f,when) set(pagefinalizers,f,when,"page") end function lpdf.registerdocumentfinalizer(f,when) set(documentfinalizers,f,when,"document") end function lpdf.finalizepage() if not environment.initex then resetpageproperties() run(pagefinalizers,"page") setpageproperties() end end function lpdf.finalizedocument() if not environment.initex then run(documentfinalizers,"document") function lpdf.finalizedocument() logs.report("backend","serious error: the document is finalized multiple times") function lpdf.finalizedocument() end end end end -- some minimal tracing, handy for checking the order local function trace_set(what,key) if trace_resources then logs.report("backend", "setting key '%s' in '%s'",key,what) end end local function trace_flush(what) if trace_resources then logs.report("backend", "flushing '%s'",what) end end lpdf.protectresources = true local catalog, info, names = pdfdictionary(), pdfdictionary(), pdfdictionary() local function flushcatalog() if not environment.initex then trace_flush("catalog") pdf.catalog = catalog() end end local function flushinfo () if not environment.initex then trace_flush("info") pdf.info = info () end end local function flushnames () if not environment.initex then trace_flush("names") pdf.names = 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 function lpdf.addtonames (k,v) if not (lpdf.protectresources and names [k]) then trace_set("names", k) names [k] = v end end local dummy = pdfreserveobj() -- else bug in hvmd due so some internal luatex conflict local r_extgstates, d_extgstates = pdfreserveobj(), pdfdictionary() local p_extgstates = pdfreference(r_extgstates) local r_colorspaces, d_colorspaces = pdfreserveobj(), pdfdictionary() local p_colorspaces = pdfreference(r_colorspaces) local r_patterns, d_patterns = pdfreserveobj(), pdfdictionary() local p_patterns = pdfreference(r_patterns) local r_shades, d_shades = pdfreserveobj(), pdfdictionary() local p_shades = pdfreference(r_shades) local function checkextgstates () if next(d_extgstates ) then lpdf.addtopageresources("ExtGState", p_extgstates ) end end local function checkcolorspaces() if next(d_colorspaces) then lpdf.addtopageresources("ColorSpace",p_colorspaces) end end local function checkpatterns () if next(d_patterns ) then lpdf.addtopageresources("Pattern", p_patterns ) end end local function checkshades () if next(d_shades ) then lpdf.addtopageresources("Shading", p_shades ) end end local function flushextgstates () if next(d_extgstates ) then trace_flush("extgstates") pdfimmediateobj(r_extgstates, tostring(d_extgstates )) end end local function flushcolorspaces() if next(d_colorspaces) then trace_flush("colorspaces") pdfimmediateobj(r_colorspaces,tostring(d_colorspaces)) end end local function flushpatterns () if next(d_patterns ) then trace_flush("patterns") pdfimmediateobj(r_patterns, tostring(d_patterns )) end end local function flushshades () if next(d_shades ) then trace_flush("shades") pdfimmediateobj(r_shades, tostring(d_shades )) end end local collected = pdfdictionary { ExtGState = p_extgstates, ColorSpace = p_colorspaces, Pattern = p_patterns, Shading = p_shades, } ; collected = collected() function lpdf.collectedresources() tex.sprint(tex.ctxcatcodes,collected) 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 lpdf.registerdocumentfinalizer(flushextgstates,3) lpdf.registerdocumentfinalizer(flushcolorspaces,3) lpdf.registerdocumentfinalizer(flushpatterns,3) lpdf.registerdocumentfinalizer(flushshades,3) lpdf.registerdocumentfinalizer(flushcatalog,3) lpdf.registerdocumentfinalizer(flushinfo,3) lpdf.registerdocumentfinalizer(flushnames,3) lpdf.registerpagefinalizer(checkextgstates,3) lpdf.registerpagefinalizer(checkcolorspaces,3) lpdf.registerpagefinalizer(checkpatterns,3) lpdf.registerpagefinalizer(checkshades,3) -- in strc-bkm: lpdf.registerdocumentfinalizer(function() structure.bookmarks.place() end,1) function lpdf.rotationcm(a) local s, c = sind(a), cosd(a) texwrite(format("%s %s %s %s 0 0 cm",c,s,-s,c)) end -- ! -> universaltime 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 function lpdf.checkedkey(t,key,kind) local pn = t[key] if pn then local tn = type(pn) if tn == kind then if kind == "string" then return pn ~= "" and pn elseif kind == "table" then return next(pn) and pn else return pn end elseif tn == "string" and kind == "number" then return tonumber(pn) end end end function lpdf.checkedvalue(value,kind) -- code not shared if value then local tv = type(value) if tv == kind then if kind == "string" then return value ~= "" and value elseif kind == "table" then return next(value) and value else return value end elseif tv == "string" and kind == "number" then return tonumber(value) end end 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", tex.contextversiontoks) -- lpdf.addtoinfo("ConTeXt.Time", os.date("%Y.%m.%d %H:%M")) -- :%S -- lpdf.addtoinfo("ConTeXt.Jobname", tex.jobname) -- lpdf.addtoinfo("ConTeXt.Url", "www.pragma-ade.com") -- saves definitions later on backends = backends or { } backends.pdf = backends.pdf or { comment = "backend for directly generating pdf output", nodeinjections = { }, codeinjections = { }, registrations = { }, helpers = { }, }