luametalatex/luametalatex-pdf-image.lua

241 lines
6.8 KiB
Lua

local rawset = rawset
local reserve
local box_fallback = {
BleedBox = "CropBox",
TrimBox = "CropBox",
ArtBox = "CropBox",
CropBox = "MediaBox",
}
local boxmap = {
media = "MediaBox",
crop = "CropBox",
bleed = "BleedBox",
trim = "TrimBox",
art = "ArtBox",
}
-- FIXME:
local function to_sp(bp) return bp*65536//1 end
local function to_bp(sp) return sp/65536 end
local function get_box(page, box)
box = boxmap[box]
while box do
local found = pdfe.getbox(page, box)
if found then
return {to_sp(found[1]), to_sp(found[2]), to_sp(found[3]), to_sp(found[4])}
end
box = box_fallback[box]
end
end
local function open_pdfe(img)
local file = pdfe.open(img.filepath)
do
local userpassword = img.userpassword
local ownerpassword = img.ownerpassword
if userpassword or ownerpassword then
pdfe.unencrypt(file, userpassword, ownerpassword)
end
end
local status = pdfe.getstatus(file)
if status >= 0 then
return file
elseif status == -1 then
error[[PDF image is encrypted. Please provide the decryption key.]]
elseif status == -2 then
error[[PDF image could not be opened.]]
else
assert(false)
end
end
local function scan_pdf(img)
local file = open_pdfe(img)
img.imagetype = 'pdf'
img.pages = pdfe.getnofpages(file)
img.page = img.page or 1
if img.page > img.pages then
error[[Not enough pages in PDF image]]
end
local page = pdfe.getpage(file, img.page)
local bbox = img.bbox or get_box(page, img.pagebox or 'crop') or {0, 0, 0, 0}
img.bbox = bbox
img.rotation = (page.Rotation or 0) % 360
if img.rotation < 0 then img.rotation = img.rotation + 360 end
if img.rotation % 2 == 0 then
img.xsize = bbox[3] - bbox[1]
img.ysize = bbox[4] - bbox[2]
else
img.xsize = bbox[4] - bbox[2]
img.ysize = bbox[3] - bbox[1]
end
img.transform = img.transform or 0
end
local pdfe_deepcopy = require'luametalatex-pdfe-deepcopy'
local function write_pdf(img, pfile)
local file = open_pdfe(img)
local page = pdfe.getpage(file, img.page)
local bbox = img.bbox
local dict = string.format("/Subtype/Form/BBox[%f %f %f %f]/Resources %s", bbox[1], bbox[2], bbox[3], bbox[4], pdfe_deepcopy(file, img.filepath, pfile, pdfe.getfromdictionary(page, 'Resources')))
local content, raw = page.Contents
-- Three cases: Contents is a stream, so copy the stream (Remember to copy filter if necessary)
-- Contents is an array of streams, so append all the streams as a new stream
-- Contents is missing. Then create an empty stream.
local type = pdfe.type(content)
if type == 'pdfe.stream' then
raw = true
for i=1,#content do
local key, type, value, detail = pdfe.getfromstream(content, i)
dict = dict .. pdfe_deepcopy(file, img.filepath, pfile, 5, key) .. ' ' .. pdfe_deepcopy(file, img.filepath, pfile, type, value, detail)
end
content = content(false)
elseif type == 'pdfe.array' then
local array = content
content = ''
for i=1,#array do
content = content .. array[i](true)
end
else
content = ''
end
pfile:stream(img.objnum, dict, content, nil, raw)
end
local liberal_keys = {height = true, width = true, depth = true, transform = true}
local real_images = {}
local function relaxed_newindex(t, k, v)
if liberal_keys[k] then
return rawset(t, k, v)
else
real_images[t][k] = v
end
end
local function no_newindex(t, k, v)
if liberal_keys[k] then
return rawset(t, k, v)
else
error(string.format("You are not allowed to set %q in an already scanned image"))
end
end
local function get_underlying(t, k)
return assert(real_images[t])[k]
end
local meta = {__index = get_underlying, __newindex = relaxed_newindex}
local restricted_meta = {__index = get_underlying, __newindex = no_newindex}
-- transform isn't documented to be changable but it kind of fits
local function new(spec)
local img, real = {}, {}
real_images[img] = real
if spec then for k,v in next, spec do
(liberal_keys[k] and img or real)[k] = v
end end
img.depth = img.depth or 0
return setmetatable(img, meta)
end
local function scan(img)
local m = getmetatable(img)
local real
if m == restricted_meta then
real = real_images[img]
else
if m ~= meta then img = new(img) end
real = real_images[img]
if real.stream then error[[stream images are not yet supported]] end
assert(real.filename)
if not real.filename:match'%.pdf$' then error[[Currently only PDF images are supported]] end
real.filepath = assert(kpse.find_file(real.filename), "Image not found")
scan_pdf(real)
setmetatable(img, restricted_meta)
end
-- (Re)Set dimensions
if img.depth and img.height and img.width then
return img
end
local flipped = img.transform % 2 == 1
if not (img.depth or img.height) then img.depth = 0 end
if not img.width and not (img.height and img.depth) then
local total_y
if flipped then
img.width = real.ysize
total_y = real.xsize
else
img.width = real.xsize
total_y = real.ysize
end
if img.height then
img.depth = total_y - img.height
else
img.height = total_y - img.depth
end
else
local ratio = flipped and real.xsize / real.ysize or real.ysize / real.xsize
if img.width then
if img.depth then
img.height = (ratio * img.width - img.depth) // 1
else
img.depth = (ratio * img.width - img.height) // 1
end
else
img.width = ((img.height + img.depth) / ratio) // 1
end
end
return img
end
-- Noop if already reserved
function reserve(img, pfile)
local real = assert(real_images[img])
local obj = real.objnum or pfile:getobj()
real.objnum = obj
return img, obj
end
local function write_img(pfile, img)
local _, objnum = reserve(img, pfile)
local real = real_images[img]
if not real.written then
real.written = true
write_pdf(real, pfile)
end
end
local function do_img(prop, p, n, x, y, outer)
local img = prop.img
img.height, img.depth, img.width = prop.height, prop.depth, prop.width
scan(img)
write_img(p.file, img)
local real = real_images[img]
local total_height = img.height + img.depth
local bbox = real.bbox
x, y = to_bp(x - bbox[1]), to_bp(y - img.depth + bbox[2])
p.resources.XObject['Im' .. tostring(real.objnum)] = real.objnum
pdf.write('page', string.format('q 1 0 0 1 %f %f cm /Im%i Do Q', x, y, real.objnum), nil, nil, p)
end
local function node(img, pfile)
pfile = pfile or pdf.__get_pfile()
local n = _ENV.node.new('whatsit', 42) -- image
_ENV.node.setproperty(n, {
handle = do_img,
img = img,
})
return n
end
--[[
local function write(img, immediate, pfile)
pfile = pfile or pdf.__get_pfile()
local _, objnum = reserve(img, pfile)
local real = real_images[img]
end
]]
return {
new = new,
scan = scan,
write = write,
node = node,
}