Hello,
conversion to UTF-16BE PDF strings used for example in bookmarks / PDF
outlines is not right.
Take the following example:
```
\starttext
\setupinteraction[state=start]
\placebookmarks[section][number=no]
\section[bookmark=đť•„]
\stoptext
```
Produces: <FEFF0075DD44> for đť•„ (U+1D544), instead of the correct
<FEFFD835DD44>.
The relevant function is `lpdf.tosixteen()` (from lpdf-ini.lmt), and its
`cache`. (Although the same function is also in lpdf-aux.lmt, and in
MkIV equivalents).
My proposal (also enclosed as a file attachment):
```
--- a/lpdf-ini.lmt
+++ b/lpdf-ini.lmt
@@ -178,7 +178,8 @@
if v < 0x10000 then
v = format("%04x",v)
else
- v = format("%04x%04x",rshift(v,10),v%1024+0xDC00)
+ v = v - 0x10000
+ v = format("%04x%04x",rshift(v,10)+0xD800,v%1024+0xDC00)
end
t[k] = v
return v
```
(Note the similiarity to existing function `big()` in l-unicode.lua.)
I found this by chance, but I am not really a ConTeXt user, so I hope
didn't miss anything.
Regards,
Michal Vlasák
if not modules then modules = { } end modules ['lpdf-ini'] = {
version = 1.001,
optimize = true,
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, max, min = math.sind, math.cosd, math.max, math.min
local sort, sortedhash = table.sort, table.sortedhash
local P, C, R, S, Cc, Cs, V = lpeg.P, lpeg.C, lpeg.R, lpeg.S, lpeg.Cc, lpeg.Cs,
lpeg.V
local lpegmatch, lpegpatterns = lpeg.match, lpeg.patterns
local formatters = string.formatters
local isboolean = string.is_boolean
local rshift = bit32.rshift
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 context = context
-- 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.
pdf = type(pdf) == "table" and pdf or { }
local factor = number.dimenfactors.bp
local codeinjections = { }
local nodeinjections = { }
local backends = backends
local pdfbackend = {
comment = "backend for directly generating pdf output",
nodeinjections = nodeinjections,
codeinjections = codeinjections,
registrations = { },
tables = { },
}
backends.pdf = pdfbackend
lpdf = lpdf or { }
local lpdf = lpdf
lpdf.flags = lpdf.flags or { } -- will be filled later
table.setmetatableindex(lpdf, function(t,k)
report_blocked("function %a is not accessible",k)
os.exit()
end)
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 pdfreserveobject
local pdfimmediateobject
updaters.register("backend.update.lpdf",function()
pdfreserveobject = lpdf.reserveobject
pdfimmediateobject = lpdf.immediateobject
end)
do
updaters.register("backend.update.lpdf",function()
job.positions.registerhandlers {
getpos = drivers.getpos,
getrpos = drivers.getrpos,
gethpos = drivers.gethpos,
getvpos = drivers.getvpos,
}
lpdf.getpos = drivers.getpos
end)
local pdfgetmatrix, pdfhasmatrix, pdfgetpos
updaters.register("backend.update.lpdf",function()
pdfgetmatrix = lpdf.getmatrix
pdfhasmatrix = lpdf.hasmatrix
pdfgetpos = lpdf.getpos
end)
-- 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
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",rshift(b,10),b%1024+0xDC00)
-- end
-- end
-- n = n + 1
-- r[n] = ">"
-- return concat(r)
-- end
-- end
local tosixteen, fromsixteen, topdfdoc, frompdfdoc, toeight, fromeight
do
local escaped = Cs(Cc("(") * (S("\\()\n\r\t\b\f")/"\\%0" + P(1))^0 *
Cc(")"))
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 = v - 0x10000
v = format("%04x%04x",rshift(v,10)+0xD800,v%1024+0xDC00)
end
t[k] = v
return v
end)
local unified = Cs(Cc("<feff") * (lpeg.patterns.utf8character/cache)^1 *
Cc(">"))
tosixteen = function(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)
fromsixteen = function(str)
if not str or str == "" then
return ""
else
return lpegmatch(pattern,str)
end
end
local toregime = regimes.toregime
local fromregime = regimes.fromregime
topdfdoc = function(str,default)
if not str or str == "" then
return ""
else
return lpegmatch(escaped,toregime("pdfdoc",str,default)) -- could
be combined if needed
end
end
frompdfdoc = function(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
toeight = function(str)
if not str or str == "" then
return "()"
else
return lpegmatch(escaped,str)
end
end
local b_pattern = Cs((P("\\")/"" * (
S("()")
+ S("nrtbf") / { n = "\n", r = "\r", t = "\t", b = "\b", f = "\f" }
+ lpegpatterns.octdigit^-3 / function(s) return char(tonumber(s,8)) end)
+ P(1))^0)
fromeight = function(str)
if not str or str == "" then
return ""
else
return lpegmatch(unescape,str)
end
end
local u_pattern = lpegpatterns.utfbom_16_be * lpegpatterns.utf16_to_utf8_be
-- official
+ lpegpatterns.utfbom_16_le * lpegpatterns.utf16_to_utf8_le
-- we've seen these
local h_pattern = lpegpatterns.hextobytes
local zero = S(" \n\r\t") + P("\\ ")
local one = C(4)
local two = P("d") * R("89","af") * C(2) * C(4)
local x_pattern = P { "start",
start = V("wrapped") + V("unwrapped") + V("original"),
original = Cs(P(1)^0),
wrapped = P("<") * V("unwrapped") * P(">") * P(-1),
unwrapped = P("feff")
* Cs( (
zero / ""
+ two / function(a,b)
a = (tonumber(a,16) - 0xD800) * 1024
b = (tonumber(b,16) - 0xDC00)
return utfchar(a+b)
end
+ one / function(a)
return utfchar(tonumber(a,16))
end
)^1 ) * P(-1)
}
function lpdf.frombytes(s,hex)
if not s or s == "" then
return ""
end
if hex then
local x = lpegmatch(x_pattern,s)
if x then
return x
end
local h = lpegmatch(h_pattern,s)
if h then
return h
end
else
local u = lpegmatch(u_pattern,s)
if u then
return u
end
end
return lpegmatch(b_pattern,s)
end
lpdf.tosixteen = tosixteen
lpdf.toeight = toeight
lpdf.topdfdoc = topdfdoc
lpdf.fromsixteen = fromsixteen
lpdf.fromeight = fromeight
lpdf.frompdfdoc = frompdfdoc
end
local tostring_a, tostring_d
do
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_dictionary = formatters["/%s << %s >>"]
local f_dictionary = formatters["<< %s >>"]
-- local f_key_array = formatters["/%s [ % t ]"]
-- local f_array = formatters["[ % t ]"]
local f_key_array = formatters["/%s [ %s ]"]
local f_array = formatters["[ %s ]"]
local f_key_number = formatters["/%s %N"] -- always with max 9 digits
and integer is possible
local f_tonumber = formatters["%N"] -- always with max 9 digits
and integer is possible
tostring_d = function(t,contentonly,key)
if next(t) then
local r = { }
local n = 0
local e
for k, v in next, t do
if k == "__extra__" then
e = v
elseif k == "__stream__" then
-- do nothing (yet)
else
n = n + 1
r[n] = k
end
end
if n > 1 then
sort(r)
end
for i=1,n do
local k = r[i]
local v = t[k]
local tv = type(v)
-- mostly tables
if tv == "table" then
-- local mv = getmetatable(v)
-- if mv and mv.__lpdftype then
if v.__lpdftype__ then
-- if v == t then
-- report_objects("ignoring circular reference in
dictionary")
-- 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
elseif tv == "string" then
r[i] = f_key_value(k,toeight(v))
elseif tv == "number" then
r[i] = f_key_number(k,v)
else
r[i] = f_key_value(k,tostring(v))
end
end
if e then
r[n+1] = e
end
r = concat(r," ")
if contentonly then
return 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)
-- mostly numbers and tables
if tv == "number" then
r[k] = f_tonumber(v)
elseif tv == "table" then
-- local mv = getmetatable(v)
-- if mv and mv.__lpdftype then
if v.__lpdftype__ 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
elseif tv == "string" then
r[k] = toeight(v)
else
r[k] = tostring(v)
end
end
local e = t.__extra__
if e then
r[tn+1] = e
end
r = concat(r," ")
if contentonly then
return r
elseif key then
return f_key_array(key,r)
else
return f_array(r)
end
elseif contentonly then
return ""
else
return "[ ]"
end
end
end
local f_tonumber = formatters["%N"]
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 tostring_l = function(t)
local s = t[1]
if not s or s == "" then
return "()"
elseif t[2] then
return "<" .. 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(t) return t[1] or 0 end -- null
local function value_v(t) return t[1] end
local function value_l(t) return t[1] end
local function add_to_d(t,v)
local k = type(v)
if k == "string" then
if t.__extra__ then
t.__extra__ = t.__extra__ .. " " .. v
else
t.__extra__ = v
end
elseif k == "table" then
for k, v in next, v do
t[k] = v
end
end
return t
end
local function add_to_a(t,v)
local k = type(v)
if k == "string" then
if t.__extra__ then
t.__extra__ = t.__extra__ .. " " .. v
else
t.__extra__ = v
end
elseif k == "table" then
local n = #t
for i=1,#v do
n = n + 1
t[n] = v[i]
end
end
return t
end
local function add_x(t,k,v) rawset(t,k,tostring(v)) end
local mt_x = { __index = { __lpdftype__ = "stream" }, __tostring =
tostring_x, __call = value_x, __newindex = add_x }
local mt_d = { __index = { __lpdftype__ = "dictionary" }, __tostring =
tostring_d, __call = value_d, __add = add_to_d }
local mt_a = { __index = { __lpdftype__ = "array" }, __tostring =
tostring_a, __call = value_a, __add = add_to_a }
local mt_u = { __index = { __lpdftype__ = "unicode" }, __tostring =
tostring_u, __call = value_u }
local mt_s = { __index = { __lpdftype__ = "string" }, __tostring =
tostring_s, __call = value_s }
local mt_p = { __index = { __lpdftype__ = "docstring" }, __tostring =
tostring_p, __call = value_p }
local mt_n = { __index = { __lpdftype__ = "number" }, __tostring =
tostring_n, __call = value_n }
local mt_c = { __index = { __lpdftype__ = "constant" }, __tostring =
tostring_c, __call = value_c }
local mt_z = { __index = { __lpdftype__ = "null" }, __tostring =
tostring_z, __call = value_z }
local mt_t = { __index = { __lpdftype__ = "true" }, __tostring =
tostring_t, __call = value_t }
local mt_f = { __index = { __lpdftype__ = "false" }, __tostring =
tostring_f, __call = value_f }
local mt_r = { __index = { __lpdftype__ = "reference" }, __tostring =
tostring_r, __call = value_r }
local mt_v = { __index = { __lpdftype__ = "verbose" }, __tostring =
tostring_v, __call = value_v }
local mt_l = { __index = { __lpdftype__ = "literal" }, __tostring =
tostring_l, __call = value_l }
local function pdfstream(t) -- we need to add attributes
if t then
local tt = type(t)
if tt == "table" then
for i=1,#t do
t[i] = tostring(t[i])
end
elseif tt == "string" then
t = { t }
else
t = { tostring(t) }
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 function pdfliteral(str,hex) -- can also produce a hex <> instead of ()
literal
return setmetatable({ str, hex },mt_l)
end
local pdfnumber, pdfconstant
do
local cache = { } -- can be weak
pdfnumber = function(n,default) -- 0-10
if not n then
n = default
end
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 replacer = S("\0\t\n\r\f ()[]{}/%%#\\") / {
["\00"]="#00",
["\09"]="#09",
["\10"]="#0a",
["\12"]="#0c",
["\13"]="#0d",
[ " " ]="#20",
[ "#" ]="#23",
[ "%" ]="#25",
[ "(" ]="#28",
[ ")" ]="#29",
[ "/" ]="#2f",
[ "[" ]="#5b",
[ "\\"]="#5c",
[ "]" ]="#5d",
[ "{" ]="#7b",
[ "}" ]="#7d",
} + P(1)
local escaped = Cs(Cc("/") * replacer^0)
local cache = table.setmetatableindex(function(t,k)
local v = setmetatable({ lpegmatch(escaped,k) }, mt_c)
t[k] = v
return v
end)
pdfconstant = function(str,default)
if not str then
str = default or "none"
end
return cache[str]
end
local escaped = Cs(replacer^0)
function lpdf.escaped(str)
return lpegmatch(escaped,str) or str
end
end
local pdfnull, pdfboolean, pdfreference, pdfverbose
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
lpdf.literal = pdfliteral
-- three priority levels, default=2
local pagefinalizers = { { }, { }, { } }
local documentfinalizers = { { }, { }, { } }
local pageresources, pageattributes, pagesattributes
local function resetpageproperties()
pageresources = pdfdictionary()
pageattributes = pdfdictionary()
pagesattributes = pdfdictionary()
end
function lpdf.getpageproperties()
return {
pageresources = pageresources,
pageattributes = pageattributes,
pagesattributes = pagesattributes,
}
end
resetpageproperties()
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")
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
callbacks.register("finish_pdfpage", lpdf.finalizepage)
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 checkcatalog()
if not environment.initex then
trace_flush("catalog")
return true
end
end
local function checkinfo()
if not environment.initex then
trace_flush("info")
if lpdf.majorversion() > 1 then
for k, v in next, info do
if k == "CreationDate" or k == "ModDate" then
-- mandate >= 2.0
else
info[k] = nil
end
end
end
return true
end
end
local function flushcatalog()
if checkcatalog() then
catalog.Type = nil
-- pdfsetcatalog(catalog())
end
end
local function flushinfo()
if checkinfo() then
info.Type = nil
end
end
function lpdf.getcatalog()
if checkcatalog() then
catalog.Type = pdfconstant("Catalog")
return pdfreference(pdfimmediateobject(tostring(catalog)))
end
end
function lpdf.getinfo()
if checkinfo() then
return pdfreference(pdfimmediateobject(tostring(info)))
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 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, r_colorspaces, r_patterns, r_shades
local d_extgstates, d_colorspaces, d_patterns, d_shades
local p_extgstates, p_colorspaces, p_patterns, p_shades
local function checkextgstates () if d_extgstates then
addtopageresources("ExtGState", p_extgstates ) end end
local function checkcolorspaces() if d_colorspaces then
addtopageresources("ColorSpace",p_colorspaces) end end
local function checkpatterns () if d_patterns then
addtopageresources("Pattern", p_patterns ) end end
local function checkshades () if d_shades then
addtopageresources("Shading", p_shades ) end end
local function flushextgstates () if d_extgstates then
trace_flush("extgstates") pdfimmediateobject(r_extgstates,
tostring(d_extgstates )) end end
local function flushcolorspaces() if d_colorspaces then
trace_flush("colorspaces")
pdfimmediateobject(r_colorspaces,tostring(d_colorspaces)) end end
local function flushpatterns () if d_patterns then
trace_flush("patterns") pdfimmediateobject(r_patterns, tostring(d_patterns
)) end end
local function flushshades () if d_shades then
trace_flush("shades") pdfimmediateobject(r_shades, tostring(d_shades
)) end end
-- patterns are special as they need resources to so we can get recursive
references and in that case
-- acrobat doesn't show anything (other viewers handle it well)
--
-- todo: share them
-- todo: force when not yet set
local pdfgetfontobjectnumber
updaters.register("backend.update.lpdf",function()
pdfgetfontobjectnumber = lpdf.getfontobjectnumber
end)
local f_font = formatters["%s%d"]
function lpdf.collectedresources(options)
local ExtGState = d_extgstates and next(d_extgstates ) and
p_extgstates
local ColorSpace = d_colorspaces and next(d_colorspaces) and
p_colorspaces
local Pattern = d_patterns and next(d_patterns ) and p_patterns
local Shading = d_shades and next(d_shades ) and p_shades
local Font
if options and options.patterns == false then
Pattern = nil
end
local fonts = options and options.fonts
if fonts and next(fonts) then
local prefix = options.fontprefix or "F"
Font = pdfdictionary { }
for k, v in sortedhash(fonts) do
Font[f_font(prefix,v)] = pdfreference(pdfgetfontobjectnumber(k))
end
end
if ExtGState or ColorSpace or Pattern or Shading or Font then
local collected = pdfdictionary {
ExtGState = ExtGState,
ColorSpace = ColorSpace,
Pattern = Pattern,
Shading = Shading,
Font = Font,
}
if options and options.serialize == false then
return collected
else
return collected()
end
elseif options and options.notempty then
return nil
elseif options and options.serialize == false then
return pdfdictionary { }
else
return ""
end
end
function lpdf.adddocumentextgstate (k,v)
if not d_extgstates then
r_extgstates = pdfreserveobject()
d_extgstates = pdfdictionary()
p_extgstates = pdfreference(r_extgstates)
end
d_extgstates[k] = v
end
function lpdf.adddocumentcolorspace(k,v)
if not d_colorspaces then
r_colorspaces = pdfreserveobject()
d_colorspaces = pdfdictionary()
p_colorspaces = pdfreference(r_colorspaces)
end
d_colorspaces[k] = v
end
function lpdf.adddocumentpattern(k,v)
if not d_patterns then
r_patterns = pdfreserveobject()
d_patterns = pdfdictionary()
p_patterns = pdfreference(r_patterns)
end
d_patterns[k] = v
end
function lpdf.adddocumentshade(k,v)
if not d_shades then
r_shades = pdfreserveobject()
d_shades = pdfdictionary()
p_shades = pdfreference(r_shades)
end
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 = sind(a)
local c = cosd(a)
return format("%.6F %.6F %.6F %.6F 0 0 cm",c,s,-s,c)
end
-- ! -> universaltime
do
-- It's a bit of a historical mess here.
local osdate, ostime, ostimezone = os.date, os.time, os.timezone
local metadata = nil
local timestamp = osdate("%Y-%m-%dT%X") .. ostimezone(true)
function lpdf.getmetadata()
if not metadata then
local contextversion = environment.version
local luatexversion = format("%1.2f",LUATEXVERSION)
local luatexfunctionality = tostring(LUATEXFUNCTIONALITY)
metadata = {
producer = format("LuaTeX-%s",luatexversion),
creator = format("LuaTeX %s %s + ConTeXt MkIV
%s",luatexversion,luatexfunctionality,contextversion),
luatexversion = luatexversion,
contextversion = contextversion,
luatexfunctionality = luatexfunctionality,
luaversion = tostring(LUAVERSION),
platform = os.platform,
time = timestamp,
}
end
return metadata
end
function lpdf.settime(n)
if n then
n = converters.totime(n)
if n then
converters.settime(n)
timestamp = osdate("%Y-%m-%dT%X") .. ostimezone(true) --
probably not ok
end
end
if metadata then
metadata.time = timestamp
end
return timestamp
end
lpdf.settime(tonumber(resolvers.variable("start_time")) or
tonumber(resolvers.variable("SOURCE_DATE_EPOCH"))) -- bah
function lpdf.pdftimestamp(str)
local t = type(str)
if t == "string" then
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)
else
return osdate("D:%Y%m%d%H%M%S",t == "number" and str or ostime())
-- maybe "!D..." : universal time
end
end
function lpdf.id(date)
local banner = environment.jobname or tex.jobname or "unknown"
if not date then
return banner
else
return format("%s | %s",banner,timestamp)
end
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
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
-- 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_p = formatters["BT /Span << /ActualText <feff%s> >>
BDC %s EMC ET"]
local f_actual_text_b = formatters["BT /Span << /ActualText <feff%s> >>
BDC"]
local s_actual_text_e = "EMC ET"
local f_actual_text_b_not = formatters["/Span << /ActualText <feff%s> >>
BDC"]
local s_actual_text_e_not = "EMC"
local f_actual_text = formatters["/Span <</ActualText %s >> BDC"]
local context = context
local pdfdirect = nodes.pool.directliteral -- we can use nuts.write deep
down
local tounicode = fonts.mappings.tounicode
function codeinjections.unicodetoactualtext(unicode,pdfcode)
return f_actual_text_p(type(unicode) == "string" and unicode or
tounicode(unicode),pdfcode)
end
function codeinjections.startunicodetoactualtext(unicode)
return f_actual_text_b(type(unicode) == "string" and unicode or
tounicode(unicode))
end
function codeinjections.stopunicodetoactualtext()
return s_actual_text_e
end
function codeinjections.startunicodetoactualtextdirect(unicode)
return f_actual_text_b_not(type(unicode) == "string" and unicode or
tounicode(unicode))
end
function codeinjections.stopunicodetoactualtextdirect()
return s_actual_text_e_not
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
}
local setstate = nodes.nuts.pool.setstate
function nodeinjections.startalternate(str)
return setstate(f_actual_text(tosixteen(str)))
end
function nodeinjections.stopalternate()
return setstate("EMC")
end
end
-- interface
implement { name = "lpdf_collectedresources",
actions = { lpdf.collectedresources, context } }
implement { name = "lpdf_addtocatalog", arguments = "2 strings",
actions = lpdf.addtocatalog }
implement { name = "lpdf_addtoinfo", arguments = "2 strings",
actions = function(a,b,c) lpdf.addtoinfo(a,b,c) end } -- gets adapted
implement { name = "lpdf_addtonames", arguments = "2 strings",
actions = lpdf.addtonames }
implement { name = "lpdf_addtopageattributes", arguments = "2 strings",
actions = lpdf.addtopageattributes }
implement { name = "lpdf_addtopagesattributes", arguments = "2 strings",
actions = lpdf.addtopagesattributes }
implement { name = "lpdf_addtopageresources", arguments = "2 strings",
actions = lpdf.addtopageresources }
implement { name = "lpdf_adddocumentextgstate", arguments = "2 strings",
actions = function(a,b) lpdf.adddocumentextgstate (a,pdfverbose(b)) end }
implement { name = "lpdf_adddocumentcolorspace", arguments = "2 strings",
actions = function(a,b) lpdf.adddocumentcolorspace(a,pdfverbose(b)) end }
implement { name = "lpdf_adddocumentpattern", arguments = "2 strings",
actions = function(a,b) lpdf.adddocumentpattern (a,pdfverbose(b)) end }
implement { name = "lpdf_adddocumentshade", arguments = "2 strings",
actions = function(a,b) lpdf.adddocumentshade (a,pdfverbose(b)) end }
-- more helpers: copy from lepd to lpdf
function lpdf.copyconstant(v)
if v ~= nil then
return pdfconstant(v)
end
end
function lpdf.copyboolean(v)
if v ~= nil then
return pdfboolean(v)
end
end
function lpdf.copyunicode(v)
if v then
return pdfunicode(v)
end
end
function lpdf.copyarray(a)
if a then
local t = pdfarray()
for i=1,#a do
t[i] = a(i)
end
return t
end
end
function lpdf.copydictionary(d)
if d then
local t = pdfdictionary()
for k, v in next, d do
t[k] = d(k)
end
return t
end
end
function lpdf.copynumber(v)
return v
end
function lpdf.copyinteger(v)
return v -- maybe checking or round ?
end
function lpdf.copyfloat(v)
return v
end
function lpdf.copystring(v)
if v then
return pdfstring(v)
end
end
do
-- This is obsolete but old viewers might still use it as directive
-- for what to send to a postscript printer.
local a_procset, d_procset
function lpdf.procset(dict)
if not a_procset then
a_procset = pdfarray {
pdfconstant("PDF"),
pdfconstant("Text"),
pdfconstant("ImageB"),
pdfconstant("ImageC"),
pdfconstant("ImageI"),
}
a_procset = pdfreference(pdfimmediateobject(tostring(a_procset)))
end
if dict then
if not d_procset then
d_procset = pdfdictionary {
ProcSet = a_procset
}
d_procset =
pdfreference(pdfimmediateobject(tostring(d_procset)))
end
return d_procset
else
return a_procset
end
end
end
_______________________________________________
dev-context mailing list
[email protected]
https://mailman.ntg.nl/mailman/listinfo/dev-context