summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/context/base/mkiv/lpdf-ini.lua
diff options
context:
space:
mode:
Diffstat (limited to 'Master/texmf-dist/tex/context/base/mkiv/lpdf-ini.lua')
-rw-r--r--Master/texmf-dist/tex/context/base/mkiv/lpdf-ini.lua1271
1 files changed, 1271 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/context/base/mkiv/lpdf-ini.lua b/Master/texmf-dist/tex/context/base/mkiv/lpdf-ini.lua
new file mode 100644
index 00000000000..2149f4729a4
--- /dev/null
+++ b/Master/texmf-dist/tex/context/base/mkiv/lpdf-ini.lua
@@ -0,0 +1,1271 @@
+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 sort = table.sort
+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
+
+do
+
+ local texget = tex.get
+ local texset = tex.set
+
+ function pdf.setmajorversion (n) texset("global","pdfmajorversion", n) end
+ function pdf.getmajorversion ( ) return texget("pdfmajorversion") 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
+
+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
+
+-- local function transform(llx,lly,urx,ury,rx,sx,sy,ry)
+-- local x1 = llx * rx + lly * sy
+-- local y1 = llx * sx + lly * ry
+-- local x2 = llx * rx + ury * sy
+-- local y2 = llx * sx + ury * ry
+-- local x3 = urx * rx + lly * sy
+-- local y3 = urx * sx + lly * ry
+-- local x4 = urx * rx + ury * sy
+-- local y4 = urx * sx + ury * ry
+-- llx = min(x1,x2,x3,x4);
+-- lly = min(y1,y2,y3,y4);
+-- urx = max(x1,x2,x3,x4);
+-- ury = max(y1,y2,y3,y4);
+-- return llx, lly, urx, ury
+-- end
+
+function lpdf.transform(llx,lly,urx,ury) -- not yet used so unchecked
+ 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
+ -- return transform(llx,lly,urx,ury,sx,rx,ry,sy)
+ else
+ return llx, lly, urx, ury
+ end
+end
+
+-- funny values for tx and ty
+
+function lpdf.rectangle(width,height,depth,offset)
+ local tx, ty = pdfgetpos()
+ if offset then
+ tx = tx - offset
+ ty = ty + offset
+ width = width + 2*offset
+ height = height + offset
+ depth = depth + offset
+ end
+ if pdfhasmatrix() then
+ local rx, sx, sy, ry = pdfgetmatrix()
+ return
+ factor * tx,
+ factor * (ty - ry*depth + sx*width),
+ factor * (tx + rx*width - sy*height),
+ factor * (ty + ry*height - sx*width)
+ else
+ return
+ factor * tx,
+ factor * (ty - depth),
+ factor * (tx + width),
+ factor * (ty + height)
+ 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 "<feff>" -- not () as we want an indication that it's unicode
+-- else
+-- local r, n = { "<feff" }, 1
+-- for b in utfvalues(str) do
+-- n = n + 1
+-- if b < 0x10000 then
+-- r[n] = format("%04x",b)
+-- else
+-- -- r[n] = format("%04x%04x",b/1024+0xD800,b%1024+0xDC00)
+-- r[n] = format("%04x%04x",floor(b/1024),b%1024+0xDC00) --bit32.rshift(b,10)
+-- end
+-- end
+-- n = n + 1
+-- r[n] = ">"
+-- return concat(r)
+-- end
+-- end
+
+local cache = table.setmetatableindex(function(t,k) -- can be made weak
+ local v = utfbyte(k)
+ if v < 0x10000 then
+ v = format("%04x",v)
+ else
+ -- v = format("%04x%04x",v/1024+0xD800,v%1024+0xDC00)
+ v = format("%04x%04x",floor(v/1024),v%1024+0xDC00)
+ end
+ t[k] = v
+ return v
+end)
+
+local escaped = Cs(Cc("(") * (S("\\()")/"\\%0" + P(1))^0 * Cc(")"))
+local unified = Cs(Cc("<feff") * (lpeg.patterns.utf8character/cache)^1 * Cc(">"))
+
+local function tosixteen(str) -- an lpeg might be faster (no table)
+ if not str or str == "" then
+ return "<feff>" -- 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
+-- -- for k, v in sortedhash(t) do -- can be an option
+-- 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_d = function(t,contentonly,key)
+ if next(t) then
+ local r, n = { }, 0
+ for k in next, t do
+ n = n + 1
+ r[n] = k
+ end
+ sort(r)
+ for i=1,n do
+ local k = r[i]
+ local v = t[k]
+ local tv = type(v)
+ if tv == "string" then
+ r[i] = f_key_value(k,toeight(v))
+ elseif tv == "number" then
+ r[i] = f_key_number(k,v)
+ -- elseif tv == "unicode" then -- can't happen
+ -- r[i] = 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[i] = f_key_null(k)
+ -- else
+ r[i] = f_key_value(k,tostring(v))
+ -- end
+ elseif v[1] then
+ r[i] = f_key_value(k,tostring_a(v))
+ else
+ r[i] = f_key_value(k,tostring_d(v))
+ end
+ else
+ r[i] = 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 escaped = Cs((S(forbidden)/replacements + P(1))^0)
+----- escaped = Cs((1-forbidden)^0 * S(forbidden)/replacements * ((S(forbidden)/replacements + P(1))^0)
+
+function lpdf.escaped(str)
+ return lpegmatch(escaped,str) or str
+end
+
+do
+
+ local p_null = { } setmetatable(p_null, mt_z)
+ local p_true = { } setmetatable(p_true, mt_t)
+ local p_false = { } setmetatable(p_false,mt_f)
+
+ pdfnull = function()
+ return p_null
+ end
+
+ pdfboolean = function(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
+
+ -- 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 r_zero = setmetatable({ 0 },mt_r)
+
+ pdfreference = function(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)
+
+ pdfverbose = function(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
+
+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
+
+-- 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 [<feff>] TJ % t EMC ET"](code)
+
+do
+
+ local f_actual_text_one = formatters["BT /Span << /ActualText <feff%04x> >> BDC [<feff>] TJ %s EMC ET"]
+ local f_actual_text_one_b = formatters["BT /Span << /ActualText <feff%04x> >> BDC [<feff>] TJ "]
+ local f_actual_text_two = formatters["BT /Span << /ActualText <feff%04x%04x> >> BDC [<feff>] TJ %s EMC ET"]
+ local f_actual_text_two_b = formatters["BT /Span << /ActualText <feff%04x%04x> >> BDC [<feff>] TJ "]
+ local s_actual_text_e = " EMC ET"
+ local f_actual_text = formatters["/Span <</ActualText %s >> BDC"]
+
+ local context = context
+ local pdfdirect = nodes.pool.pdfdirect
+
+ -- todo: use tounicode from the font mapper
+
+ 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
+
+ function codeinjections.startunicodetoactualtext(unicode)
+ if unicode < 0x10000 then
+ return f_actual_text_one_b(unicode)
+ else
+ return f_actual_text_two_b(unicode/1024+0xD800,unicode%1024+0xDC00)
+ end
+ end
+
+ function codeinjections.stopunicodetoactualtext()
+ return s_actual_text_e
+ 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
+
+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 = function(a,b,c) lpdf.addtoinfo(a,b,c) end } -- gets adapted
+implement { name = "lpdf_addtonames", arguments = two_strings, actions = lpdf.addtonames }
+implement { name = "lpdf_addtopageattributes", arguments = two_strings, actions = lpdf.addtopageattributes }
+implement { name = "lpdf_addtopagesattributes", arguments = two_strings, actions = lpdf.addtopagesattributes }
+implement { name = "lpdf_addtopageresources", arguments = two_strings, actions = lpdf.addtopageresources }
+implement { name = "lpdf_adddocumentextgstate", arguments = two_strings, actions = function(a,b) lpdf.adddocumentextgstate (a,pdfverbose(b)) end }
+implement { name = "lpdf_adddocumentcolorspace", arguments = two_strings, actions = function(a,b) lpdf.adddocumentcolorspace(a,pdfverbose(b)) end }
+implement { name = "lpdf_adddocumentpattern", arguments = two_strings, actions = function(a,b) lpdf.adddocumentpattern (a,pdfverbose(b)) end }
+implement { name = "lpdf_adddocumentshade", arguments = two_strings, actions = function(a,b) lpdf.adddocumentshade (a,pdfverbose(b)) end }
+