if not modules then modules = { } end modules ['back-pdf'] = { version = 1.001, comment = "companion to lpdf-ini.tex", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } -- This code is very experimental ! local setmetatable, getmetatable, type, next, tostring, tonumber, rawset = setmetatable, getmetatable, type, next, tostring, tonumber, rawset local char, byte, format, gsub, concat = string.char, string.byte, string.format, string.gsub, table.concat local utfvalues = string.utfvalues local texwrite = tex.write local sind, cosd = math.sind, math.cosd local trace_finalizers = false trackers.register("backend.finalizers", function(v) trace_finalizers = 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 escaped:match(str) -- end -- -- no need for escaping .. just use unicode instead return "(" .. str .. ")" end lpdf.toeight = toeight local escapes = "-" local escaped = lpeg.Cs(lpeg.Cc("(") * (lpeg.S("\\/#<>[]()")/escapes + lpeg.P(1))^0 * lpeg.Cc(")")) local function cleaned(str) if not str or str == "" then return "()" else return escaped:match(str) end end lpdf.cleaned = cleaned 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) if mv and mv.__lpdftype 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) return concat(t[1],"") 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() return true end -- the call is experimental local function value_b() return 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 } function lpdf.stream(t) if t then for i=1,#t do t[i] = tostring(t[i]) end end return setmetatable(t or { },mt_x) end function lpdf.dictionary(t) return setmetatable(t or { },mt_d) end function lpdf.array(t) if type(t) == "string"then return setmetatable({ t },mt_a) else return setmetatable(t or { },mt_a) end end function lpdf.string(str,default) return setmetatable({ str or default or "" },mt_s) end function lpdf.unicode(str,default) return setmetatable({ str or default or "" },mt_u) end local cache = { } -- can be weak function lpdf.number(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] = lpdf.number(i) end local cache = { } -- can be weak function lpdf.constant(str,default) str = str or default or "" local c = cache[str] if not c then c = setmetatable({ "/" .. 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) function lpdf.null() return p_null end function lpdf.boolean(b,default) if ((type(b) == boolean) and b) or default then return p_true else return p_false end end function lpdf.reference(r) return setmetatable({ r or 0 },mt_r) end function lpdf.verbose(t) -- maybe check for type return setmetatable({ t or "" },mt_v) end --~ local d = lpdf.dictionary() --~ local e = lpdf.dictionary { ["e"] = "abc" } --~ local f = lpdf.dictionary { ["f"] = "ABC" } --~ local a = lpdf.array() --~ 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(d) --~ local d = lpdf.dictionary() --~ d["abcd"] = { 1, 2, 3, "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.boolean(false) --~ print(s()) -- fails somehow 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 -- there will be more of this local pdfreference = lpdf.reference local pdfdictionary = lpdf.dictionary local pdfreserveobj = pdf.reserveobj local pdfimmediateobj = pdf.immediateobj local texset, texsprint, ctxcatcodes = tex.set, tex.sprint, tex.ctxcatcodes local pdfobjcache = { } function lpdf.sharedobj(content) local r = pdfobjcache[content] if not r then r = pdfreference(pdfimmediateobj(content)) pdfobjcache[content] = r end return r end -- saves definitions later on backends = backends or { } backends.pdf = backends.pdf or { comment = "backend for directly generating pdf output", nodeinjections = { }, codeinjections = { }, registrations = { }, helpers = { }, } -- 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()) 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") end end local c_template = "\\normalpdfcatalog{/%s %s}" local i_template = "\\normalpdfinfo{/%s %s}" local n_template = "\\normalpdfnames{/%s %s}" function lpdf.addtocatalog(k,v) if not environment.initex then texsprint(ctxcatcodes,format(c_template,k,tostring(v))) end end function lpdf.addtoinfo (k,v) if not environment.initex then texsprint(ctxcatcodes,format(i_template,k,tostring(v))) end end function lpdf.addtonames (k,v) if not environment.initex then texsprint(ctxcatcodes,format(n_template,k,tostring(v))) end end 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 pdfimmediateobj(r_extgstates, tostring(d_extgstates )) end end local function flushcolorspaces() if next(d_colorspaces) then pdfimmediateobj(r_colorspaces,tostring(d_colorspaces)) end end local function flushpatterns () if next(d_patterns ) then pdfimmediateobj(r_patterns, tostring(d_patterns )) end end local function flushshades () if next(d_shades ) then 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.registerpagefinalizer(checkextgstates,3) lpdf.registerpagefinalizer(checkcolorspaces,3) lpdf.registerpagefinalizer(checkpatterns,3) lpdf.registerpagefinalizer(checkshades,3) -- 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