From 463f240670b3bc241049b2eb9c3ac3c82ce696c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20Fabian=20Kr=C3=BCger?= Date: Mon, 6 Jul 2020 15:31:15 +0200 Subject: [PATCH] More compact float representation --- luametalatex-back-pdf.lua | 32 ++++++++++++++++++-------------- luametalatex-nodewriter.lua | 21 +++++++++++---------- luametalatex-pdf-image-pdf.lua | 10 ++++++---- luametalatex-pdf-image-png.lua | 8 +++++--- luametalatex-pdf-image.lua | 8 ++++---- luametalatex-pdf-savedbox.lua | 11 +++++++---- luametalatex-pdf-utils.lua | 23 +++++++++++++++++++++++ luametalatex-pdf.lua | 1 - luametalatex-pdfe-deepcopy.lua | 3 ++- 9 files changed, 76 insertions(+), 41 deletions(-) create mode 100644 luametalatex-pdf-utils.lua diff --git a/luametalatex-back-pdf.lua b/luametalatex-back-pdf.lua index 2899e40..2efb35c 100644 --- a/luametalatex-back-pdf.lua +++ b/luametalatex-back-pdf.lua @@ -1,9 +1,15 @@ local pdf = pdf local pdfvariable = pdf.variable + local writer = require'luametalatex-nodewriter' local newpdf = require'luametalatex-pdf' local nametree = require'luametalatex-pdf-nametree' local build_fontdir = require'luametalatex-pdf-font' + +local utils = require'luametalatex-pdf-utils' +local strip_floats = utils.strip_floats +local to_bp = utils.to_bp + local pdfname, pfile local fontdirs = setmetatable({}, {__index=function(t, k)t[k] = pfile:getobj() return t[k] end}) local usedglyphs = {} @@ -62,7 +68,7 @@ token.luacmd("shipout", function() local out, resources, annots = writer(pfile, list, fontdirs, usedglyphs, colorstacks) cur_page = nil local content = pfile:stream(nil, '', out) - pfile:indirect(page, string.format([[<>%s%s>>]], parent, content, -math.ceil(list.depth/65781.76), math.ceil(list.width/65781.76), math.ceil(list.height/65781.76), resources, pdfvariable.pageresources, annots, pdfvariable.pageattr)) + pfile:indirect(page, string.format([[<>%s%s>>]], parent, content, -math.ceil(to_bp(list.depth)), math.ceil(to_bp(list.width)), math.ceil(to_bp(list.height)), resources, pdfvariable.pageresources, annots, pdfvariable.pageattr)) node.flush_list(list) token.put_next(reset_deadcycles) token.scan_token() @@ -190,9 +196,6 @@ function pdf.newcolorstack(default, mode, page) } return idx end -local function sp2bp(sp) - return sp/65781.76 -end local function projected(m, x, y, w) w = w or 1 return x*m[1] + y*m[3] + w*m[5], x*m[2] + y*m[4] + w*m[6] @@ -253,14 +256,15 @@ local function write_link(p, link) local quadStr = {} for i=1,#quads,8 do local x1, y1, x4, y4, x2, y2, x3, y3 = table.unpack(quads, i, i+7) - x1, y1, x2, y2, x3, y3, x4, y4 = sp2bp(x1), sp2bp(y1), sp2bp(x2), sp2bp(y2), sp2bp(x3), sp2bp(y3), sp2bp(x4), sp2bp(y4) + x1, y1, x2, y2, x3, y3, x4, y4 = to_bp(x1), to_bp(y1), to_bp(x2), to_bp(y2), to_bp(x3), to_bp(y3), to_bp(x4), to_bp(y4) quadStr[i//8+1] = string.format("%f %f %f %f %f %f %f %f", x1, y1, x2, y2, x3, y3, x4, y4) minX = math.min(minX, x1, x2, x3, x4) minY = math.min(minY, y1, y2, y3, y4) maxX = math.max(maxX, x1, x2, x3, x4) maxY = math.max(maxY, y1, y2, y3, y4) end - pfile:indirect(link.objnum, string.format("<>", minX-.2, minY-.2, maxX+.2, maxY+.2, table.concat(quadStr, ' '), attr)) + local boxes = strip_floats(string.format("/Rect[%f %f %f %f]/QuadPoints[%s]", minX-.2, minY-.2, maxX+.2, maxY+.2, table.concat(quadStr, ' '))) + pfile:indirect(link.objnum, string.format("<>", boxes, attr)) for i=1,#quads do quads[i] = nil end link.objnum = nil end @@ -387,9 +391,9 @@ local dest_whatsit = declare_whatsit('pdf_dest', function(prop, p, n, x, y) local x, y = projected(p.matrix, x, y) local zoom = prop.xyz_zoom if zoom then - data = string.format("[%i 0 R/XYZ %.5f %.5f %.3f]", cur_page, sp2bp(x-off), sp2bp(y+off), prop.zoom/1000) + data = string.format("[%i 0 R/XYZ %.5f %.5f %.3f]", cur_page, to_bp(x-off), to_bp(y+off), prop.zoom/1000) else - data = string.format("[%i 0 R/XYZ %.5f %.5f null]", cur_page, sp2bp(x-off), sp2bp(y+off)) + data = string.format("[%i 0 R/XYZ %.5f %.5f null]", cur_page, to_bp(x-off), to_bp(y+off)) end elseif dest_type == "fitr" then local m = p.matrix @@ -399,28 +403,28 @@ local dest_whatsit = declare_whatsit('pdf_dest', function(prop, p, n, x, y) local urx, ury = projected(x+prop.width, x + prop.height) local left, lower, right, upper = math.min(llx, lrx, ulx, urx), math.min(lly, lry, uly, ury), math.max(llx, lrx, ulx, urx), math.max(lly, lry, uly, ury) - data = string.format("[%i 0 R/FitR %.5f %.5f %.5f %.5f]", cur_page, sp2bp(left-off), sp2bp(lower-off), sp2bp(right+off), sp2bp(upper+off)) + data = string.format("[%i 0 R/FitR %.5f %.5f %.5f %.5f]", cur_page, to_bp(left-off), to_bp(lower-off), to_bp(right+off), to_bp(upper+off)) elseif dest_type == "fit" then data = string.format("[%i 0 R/Fit]", cur_page) elseif dest_type == "fith" then local x, y = projected(p.matrix, x, y) - data = string.format("[%i 0 R/FitH %.5f]", cur_page, sp2bp(y+off)) + data = string.format("[%i 0 R/FitH %.5f]", cur_page, to_bp(y+off)) elseif dest_type == "fitv" then local x, y = projected(p.matrix, x, y) - data = string.format("[%i 0 R/FitV %.5f]", cur_page, sp2bp(x-off)) + data = string.format("[%i 0 R/FitV %.5f]", cur_page, to_bp(x-off)) elseif dest_type == "fitb" then data = string.format("[%i 0 R/FitB]", cur_page) elseif dest_type == "fitbh" then local x, y = projected(p.matrix, x, y) - data = string.format("[%i 0 R/FitBH %.5f]", cur_page, sp2bp(y+off)) + data = string.format("[%i 0 R/FitBH %.5f]", cur_page, to_bp(y+off)) elseif dest_type == "fitbv" then local x, y = projected(p.matrix, x, y) - data = string.format("[%i 0 R/FitBV %.5f]", cur_page, sp2bp(x-off)) + data = string.format("[%i 0 R/FitBV %.5f]", cur_page, to_bp(x-off)) end if pfile:written(dests[id]) then texio.write_nl(string.format("Duplicate destination %q", id)) else - dests[id] = pfile:indirect(dests[id], data) + dests[id] = pfile:indirect(dests[id], strip_floats(data)) end end) local refobj_whatsit = declare_whatsit('pdf_refobj', function(prop, p, n, x, y) diff --git a/luametalatex-nodewriter.lua b/luametalatex-nodewriter.lua index 92cdffd..a5f3222 100644 --- a/luametalatex-nodewriter.lua +++ b/luametalatex-nodewriter.lua @@ -31,6 +31,10 @@ local rangedimensions = direct.rangedimensions local traverse_id = direct.traverse_id local getdata = direct.getdata +local utils = require'luametalatex-pdf-utils' +local strip_floats = utils.strip_floats +local to_bp = utils.to_bp + local pdf_font_map = require'luametalatex-pdf-font-deduplicate' local get_whatsit_handler = require'luametalatex-whatsits'.handler @@ -72,14 +76,11 @@ local whatsithandler = (function() end) end)() local glyph, text, page, cm_pending = 1, 2, 3, 4 -local gsub = string.gsub + local function projected_point(m, x, y, w) w = w or 1 return x*m[1] + y*m[3] + w*m[5], x*m[2] + y*m[4] + w*m[6] end -local function sp2bp(sp) - return sp/65781.76 -end local fontnames = setmetatable({}, {__index = function(t, k) local res = format("F%i", k) t[k] = res return res end}) local topage local function totext(p, fid) @@ -98,7 +99,7 @@ local function totext(p, fid) local pdf_fid = pdf_font_map[fid] p.resources.Font[fontnames[pdf_fid]] = p.fontdirs[pdf_fid] - p.strings[#p.strings+1] = format("/F%i %f Tf 0 Tr", pdf_fid, sp2bp(f.size)) -- TODO: Setting the mode, width, etc. + p.strings[#p.strings+1] = strip_floats(format("/F%i %f Tf 0 Tr", pdf_fid, to_bp(f.size))) -- TODO: Setting the mode, width, etc. p.font.usedglyphs = p.usedglyphs[pdf_fid] p.font.fid = fid @@ -127,7 +128,7 @@ function topage(p) elseif last == cm_pending then local pending = p.pending_matrix if pending[1] ~= 1 or pending[2] ~= 0 or pending[3] ~= 0 or pending[4] ~= 1 or pending[5] ~= 0 or pending[6] ~= 0 then - p.strings[#p.strings+1] = format("%f %f %f %f %f %f cm", pending[1], pending[2], pending[3], pending[4], sp2bp(pending[5]), sp2bp(pending[6])) + p.strings[#p.strings+1] = strip_floats(format("%f %f %f %f %f %f cm", pending[1], pending[2], pending[3], pending[4], to_bp(pending[5]), to_bp(pending[6]))) end else error[[Unknown mode]] @@ -147,14 +148,14 @@ local function toglyph(p, fid, x, y, exfactor) end if totext(p, fid) or exfactor ~= p.font.exfactor then p.font.exfactor = exfactor - p.strings[#p.strings+1] = gsub(format("%f 0.0 %f %f %f %f Tm", p.font.extend * (1+exfactor/1000000), p.font.slant, p.font.squeeze, sp2bp(x), sp2bp(y)), '%.?0+ ', ' ') + p.strings[#p.strings+1] = strip_floats(format("%f 0.0 %f %f %f %f Tm", p.font.extend * (1+exfactor/1000000), p.font.slant, p.font.squeeze, to_bp(x), to_bp(y))) else -- To invert the text transformation matrix (extend 0 0;slant squeeze 0;0 0 1) -- we have to apply (extend^-1 0 0;-slant*extend^-1*squeeze^-1 squeeze^-1 0;0 0 1). (extend has to include expansion) -- We optimize slightly by separating some steps - local dx, dy = sp2bp((x - p.pos.lx)), sp2bp(y - p.pos.ly) / p.font.squeeze + local dx, dy = to_bp((x - p.pos.lx)), to_bp(y - p.pos.ly) / p.font.squeeze dx = (dx-p.font.slant*dy) / (p.font.extend * (1+exfactor/1000000)) - p.strings[#p.strings+1] = gsub(format("%f %f Td", dx, dy), '%.?0+ ', ' ') + p.strings[#p.strings+1] = strip_floats(format("%f %f Td", dx, dy)) end p.pos.lx, p.pos.ly, p.pos.x, p.pos.y = x, y, x, y p.mode = glyph @@ -287,7 +288,7 @@ function nodehandler.rule(p, n, x, y, outer) error[[We can't handle outline rules yet]] else topage(p) - p.strings[#p.strings+1] = gsub(format("%f %f %f %f re f", sp2bp(x), sp2bp(y - getdepth(n)), sp2bp(getwidth(n)), sp2bp(getdepth(n) + getheight(n))), '%.?0+ ', ' ') + p.strings[#p.strings+1] = strip_floats(format("%f %f %f %f re f", to_bp(x), to_bp(y - getdepth(n)), to_bp(getwidth(n)), to_bp(getdepth(n) + getheight(n)))) end end end diff --git a/luametalatex-pdf-image-pdf.lua b/luametalatex-pdf-image-pdf.lua index 2de3d67..b6753da 100644 --- a/luametalatex-pdf-image-pdf.lua +++ b/luametalatex-pdf-image-pdf.lua @@ -13,9 +13,10 @@ local boxmap = { art = "ArtBox", } --- FIXME: -local function to_sp(bp) return bp*65781.76//1 end -local function to_bp(sp) return sp/65781.76 end +local utils = require'luametalatex-pdf-utils' +local strip_floats = utils.strip_floats +local to_sp = utils.to_sp +local to_bp = utils.to_bp local function get_box(page, box) box = boxmap[box] @@ -74,7 +75,8 @@ function pdf_functions.write(pfile, img) 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", to_bp(bbox[1]), to_bp(bbox[2]), to_bp(bbox[3]), to_bp(bbox[4]), pdfe_deepcopy(file, img.filepath, pfile, pdfe.getfromdictionary(page, 'Resources'))) + local dict = strip_floats(string.format("/Subtype/Form/BBox[%f %f %f %f]/Resources ", to_bp(bbox[1]), to_bp(bbox[2]), to_bp(bbox[3]), to_bp(bbox[4]))) + dict = dict .. 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 diff --git a/luametalatex-pdf-image-png.lua b/luametalatex-pdf-image-png.lua index 90b3730..0a54e7b 100644 --- a/luametalatex-pdf-image-png.lua +++ b/luametalatex-pdf-image-png.lua @@ -1,3 +1,5 @@ +local strip_floats = require'luametalatex-pdf-utils'.strip_floats + local function ignore() end local parse = setmetatable({ -- IHDR = below, @@ -131,11 +133,11 @@ function parse.cHRM(buf, i, after, ctxt) local X_C, Z_C = Y_C*x_B/y_B, Y_C*((1-x_B)/y_B-1) local X_W, Y_W, Z_W = X_A+X_B+X_C, Y_A+Y_B+Y_C, Z_A+Z_B+Z_C - ctxt.cHRM = string.format("/WhitePoint[%f %f %f]/Matrix[%f %f %f %f %f %f %f %f %f]", + ctxt.cHRM = strip_floats(string.format("/WhitePoint[%f %f %f]/Matrix[%f %f %f %f %f %f %f %f %f]", X_W, Y_W, Z_W, X_A, Y_A, Z_A, X_B, Y_B, Z_B, - X_C, Y_C, Z_C) + X_C, Y_C, Z_C)) end function parse.IDAT(buf, i, after, ctxt) ctxt.IDAT = ctxt.IDAT or {} @@ -277,7 +279,7 @@ function png_functions.write(pfile, img) elseif colortype & 2 == 2 then -- RGB if t.cHRM then local gamma = t.gAMA or 2.2 - gamma = gamma and string.format("/Gamma[%f %f %f]", gamma, gamma, gamma) or '' + gamma = gamma and strip_floats(string.format("/Gamma[%f %f %f]", gamma, gamma, gamma)) or '' colorspace = string.format("[/CalRGB<<%s%s>>]", t.cHRM, gamma) else if t.gAMA then diff --git a/luametalatex-pdf-image.lua b/luametalatex-pdf-image.lua index f5971ac..19a456c 100644 --- a/luametalatex-pdf-image.lua +++ b/luametalatex-pdf-image.lua @@ -17,9 +17,9 @@ local imagetypes = setmetatable({}, {__index = function(t, k) return module end}) --- FIXME: -local function to_sp(bp) return bp*65781.76//1 end -local function to_bp(sp) return sp/65781.76 end +local utils = require'luametalatex-pdf-utils' +local strip_floats = utils.strip_floats +local to_bp = utils.to_bp local liberal_keys = {height = true, width = true, depth = true, transform = true} local real_images = {} @@ -171,7 +171,7 @@ local function do_img(data, p, n, x, y) b, d, f = b*yscale, d*yscale, f*yscale e, f = to_bp(x + e), to_bp(y - depth + f) p.resources.XObject['Im' .. tostring(img.objnum)] = img.objnum - pdf.write('page', string.format('q %f %f %f %f %f %f cm /Im%i Do Q', a, b, c, d, e, f, img.objnum), nil, nil, p) + pdf.write('page', strip_floats(string.format('q %f %f %f %f %f %f cm /Im%i Do Q', a, b, c, d, e, f, img.objnum)), nil, nil, p) end local ruleid = node.id'rule' local ruletypes = node.subtypes'rule' diff --git a/luametalatex-pdf-savedbox.lua b/luametalatex-pdf-savedbox.lua index 4dad21c..15d3ecd 100644 --- a/luametalatex-pdf-savedbox.lua +++ b/luametalatex-pdf-savedbox.lua @@ -4,7 +4,9 @@ local pdfvariable = pdf.variable -- XForms currently have the form {width, height, depth, objnum, attributes, list, margin} local xforms = {} -local function to_bp(sp) return sp/65781.76 end +local utils = require'luametalatex-pdf-utils' +local strip_floats = utils.strip_floats +local to_bp = utils.to_bp local function shipout(pfile, xform, fontdirs, usedglyphs) local list, margin = xform.list, xform.margin @@ -16,7 +18,8 @@ local function shipout(pfile, xform, fontdirs, usedglyphs) if pdfvariable.xformattr ~= '' or pdfvariable.xformresources ~= '' then texio.write_nl('term and log', 'WARNING (savedboxresource shipout): Ignoring unsupported PDF variables xformattr and xformresources. Specify resources and attributes for specific XForms instead.') end - local dict = string.format('/Subtype/Form/BBox[%f %f %f %f]/Resources<<%s%s>>%s', -to_bp(margin), -to_bp(list.depth+margin), to_bp(list.width+margin), to_bp(list.height+margin), resources, xform.resources or '', xform.attributes or '') + local bbox = strip_floats(string.format('/BBox[%f %f %f %f]', -to_bp(margin), -to_bp(list.depth+margin), to_bp(list.width+margin), to_bp(list.height+margin))) + local dict = string.format('/Subtype/Form%s/Resources<<%s%s>>%s', bbox, resources, xform.resources or '', xform.attributes or '') node.flush_list(list) xform.list = nil local objnum = pfile:stream(xform.objnum, dict, out) @@ -89,10 +92,10 @@ local function do_box(data, p, n, x, y) local width, height, depth = node.direct.getwhd(n) local xscale, yscale = width / xform.width, (height+depth) / (xform.height+xform.depth) p.resources.XObject['Fm' .. tostring(data)] = objnum - pdf.write('page', string.format('q %f 0 0 %f %f %f cm /Fm%i Do Q', + pdf.write('page', strip_floats(string.format('q %f 0 0 %f %f %f cm /Fm%i Do Q', xscale, yscale, to_bp(x), to_bp(y-depth+yscale*xform.depth), - data), nil, nil, p) + data)), nil, nil, p) end return { diff --git a/luametalatex-pdf-utils.lua b/luametalatex-pdf-utils.lua new file mode 100644 index 0000000..f72eb42 --- /dev/null +++ b/luametalatex-pdf-utils.lua @@ -0,0 +1,23 @@ +local l = lpeg or require'lpeg' +local trailing_zeros = l.P'0'^0 * -l.R'09' +local strip_floats_patt = l.Cs((1-l.R'09' + + (l.R'09')^1 * (l.P'.' * trailing_zeros / '' + l.P'.' * (l.R'09'-trailing_zeros)^1 * (trailing_zeros/''))^-1)^0) +local match = l.match + +local function strip_floats(s) + return match(strip_floats_patt, s) +end + +local function to_bp(sp) + return sp/65781.76 +end + +local function to_sp(bp) + return (bp*65781.76+.5)//1 +end + +return { + strip_floats = strip_floats, + to_bp = to_bp, + to_sp = to_sp, +} diff --git a/luametalatex-pdf.lua b/luametalatex-pdf.lua index d9b2b2b..2ff859c 100644 --- a/luametalatex-pdf.lua +++ b/luametalatex-pdf.lua @@ -1,5 +1,4 @@ local format = string.format -local gsub = string.gsub local byte = string.byte local pack = string.pack local error = error diff --git a/luametalatex-pdfe-deepcopy.lua b/luametalatex-pdfe-deepcopy.lua index 9e32ee7..e3b461e 100644 --- a/luametalatex-pdfe-deepcopy.lua +++ b/luametalatex-pdfe-deepcopy.lua @@ -1,4 +1,5 @@ local format = string.format +local strip_floats = require'luametalatex-pdf-utils'.strip_floats local pdfe = pdfe local l = lpeg local regularchar = 1-l.S'\0\t\n\r\f ()<>[]{}/%#' @@ -17,7 +18,7 @@ local deepcopy_lookup deepcopy_lookup = { return format("%d", i) end, function(_, pdf, f) -- 4: number - return format("%f", f) + return strip_floats(format("%f", f), "%.?0+[ %]]", "") end, function(_, pdf, name) -- 5: name return nameescape:match(name)