From 81095dd1bf20eb5f7e126adbdc8047f940504180 Mon Sep 17 00:00:00 2001 From: Hans Hagen Date: Mon, 20 Jul 2020 11:09:33 +0200 Subject: 2020-07-20 10:42:00 --- tex/context/base/mkiv/lpdf-lmt.lmt | 2835 ++++++++++++++++++++++++++++++++++++ 1 file changed, 2835 insertions(+) create mode 100644 tex/context/base/mkiv/lpdf-lmt.lmt (limited to 'tex/context/base/mkiv/lpdf-lmt.lmt') diff --git a/tex/context/base/mkiv/lpdf-lmt.lmt b/tex/context/base/mkiv/lpdf-lmt.lmt new file mode 100644 index 000000000..61a194ca4 --- /dev/null +++ b/tex/context/base/mkiv/lpdf-lmt.lmt @@ -0,0 +1,2835 @@ +if not modules then modules = { } end modules ['lpdf-lmt'] = { + 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" +} + +-- The code below was originally in back-lpd.lua but it makes more sense in +-- this namespace. I will rename variables. +-- +-- There is no way that a lua based backend can compete with the original one +-- for relative simple text runs. And we're talking seconds here on say 500 +-- pages with paragraphs alternativng between three fonts and colors. But such +-- documents are rare so in practice we are quite okay, especially because in +-- ConTeXt we can gain quite a bit elsewhere. So, when we loose 30% on such +-- simple documents, we break even on for instance the manual, and gain 30% on +-- Thomas's turture test (also for other reasons). But .. who knows what magic +-- I can cook up in due time. + +-- If you consider this complex, watch: +-- +-- https://www.youtube.com/watch?v=6H-cAzfB2qo +-- +-- or in distractionmode: +-- +-- https://www.youtube.com/watch?v=TYuTE_1jvvE +-- https://www.youtube.com/watch?v=nnicGKX3lvM +-- +-- For the moment we have to support the built in backend as well as the alternative. So +-- the next interface is suboptimal and will change at some time. At that moment I will +-- also optimize and extend. + +local type, next, unpack, tonumber, rawget = type, next, unpack, tonumber, rawget +local char, rep, find = string.char, string.rep, string.find +local formatters, splitupstring = string.formatters, string.splitup +local band, extract = bit32.band, bit32.extract +local concat, sortedhash = table.concat, table.sortedhash +local setmetatableindex = table.setmetatableindex +local loaddata = io.loaddata + +local bpfactor = number.dimenfactors.bp + +local md5HEX = md5.HEX +local osuuid = os.uuid +local zlibcompress = (xzip or zlib).compress + +local nuts = nodes.nuts +local tonut = nodes.tonut + +local getdata = nuts.getdata +local getsubtype = nuts.getsubtype +local getwhd = nuts.getwhd +local flushlist = nuts.flush_list + +local pdfincludeimage = lpdf.includeimage +local pdfgetfontname = lpdf.getfontname +local pdfgetfontobjnumber = lpdf.getfontobjnumber + +local pdfreserveobject = lpdf.reserveobject +local pdfpagereference = lpdf.pagereference +local pdfflushobject = lpdf.flushobject +local pdfsharedobject = lpdf.shareobjectreference +local pdfreference = lpdf.reference +local pdfdictionary = lpdf.dictionary +local pdfarray = lpdf.array +local pdfconstant = lpdf.constant +local pdfflushstreamobject = lpdf.flushstreamobject +local pdfliteral = lpdf.literal -- not to be confused with a whatsit! + +local pdf_pages = pdfconstant("Pages") +local pdf_page = pdfconstant("Page") +local pdf_xobject = pdfconstant("XObject") +local pdf_form = pdfconstant("Form") + +local fonthashes = fonts.hashes +local characters = fonthashes.characters +local descriptions = fonthashes.descriptions +local parameters = fonthashes.parameters +local properties = fonthashes.properties + +local report = logs.reporter("backend") + +-- used variables + +local pdf_h, pdf_v +local need_tm, need_tf, cur_tmrx, cur_factor, cur_f, cur_e +local need_width, need_mode, done_width, done_mode +local mode +local f_pdf_cur, f_pdf, fs_cur, fs, f_cur +local tj_delta, cw +local usedfonts, usedxforms, usedximages, usedxgroups +local getxformname, getximagename +local boundingbox, shippingmode, objectnumber +local tmrx, tmry, tmsx, tmsy, tmtx, tmty +local cmrx, cmry, cmsx, cmsy, cmtx, cmty +local tmef + +local function usefont(t,k) -- a bit redundant hash + local v = pdfgetfontname(k) + t[k] = v + return v +end + +local function reset_variables(specification) + pdf_h, pdf_v = 0, 0 + cmrx, cmry = 1.0, 1.0 + cmsx, cmsy = 0.0, 0.0 + cmtx, cmty = 0.0, 0.0 + tmrx, tmry = 1.0, 1.0 + tmsx, tmsy = 0.0, 0.0 + tmtx, tmty = 0.0, 0.0 + tmef = 1.0 + need_tm = false + need_tf = false + need_width = 0 + need_mode = 0 + done_width = false + done_mode = false + mode = "page" + shippingmode = specification.shippingmode + objectnumber = specification.objectnumber + cur_tmrx = 0.0 + f_cur = 0 + f_pdf_cur = 0 -- nullfont + f_pdf = 0 -- nullfont + fs_cur = 0 + fs = 0 + cur_factor = 0 + cur_f = false + cur_e = false + tj_delta = 0.0 + cw = 0.0 + usedfonts = setmetatableindex(usefont) + usedxforms = { } + usedximages = { } + -- usedxgroups = { } + boundingbox = specification.boundingbox +end + +-- buffer + +local buffer = lua.newtable(1024,0) -- { } +local b = 0 + +local function reset_buffer() + b = 0 +end + +local function flush_buffer() + b = 0 +end + +-- fonts + +local fontcharacters +local fontdescriptions +local fontparameters +local fontproperties +local usedcharacters = setmetatableindex("table") +local pdfcharacters + +local horizontalmode = true +----- widefontmode = true +local scalefactor = 1 +local threshold = 655360 +local thresfactor = 100 +local tjfactor = 100 / 65536 + +lpdf.usedcharacters = usedcharacters + +local function updatefontstate(font) + fontcharacters = characters[font] + fontdescriptions = descriptions[font] + fontparameters = parameters[font] + fontproperties = properties[font] + local size = fontparameters.size -- or bad news + local designsize = fontparameters.designsize or size + pdfcharacters = usedcharacters[font] + horizontalmode = fontparameters.writingmode ~= "vertical" + -- widefontmode = fontproperties.encodingbytes == 2 + scalefactor = (designsize/size) * tjfactor + local fthreshold = fontproperties.threshold + threshold = (fthreshold and (size * fthreshold / 100)) or 655360 + -- when we bolden the threshold should be smaller .. a hack .. i need to redo all this + if (fontparameters.extendfactor or 1) == 1 then + -- we're probably okay + elseif fontparameters.hshift or fontparameters.vshift then + -- we could be okay + else + -- some vf magic going on + threshold = threshold / 5 + end +end + +-- helpers + +local f_cm = formatters["%.6N %.6N %.6N %.6N %.6N %.6N cm"] +local f_tm = formatters["%.6N %.6N %.6N %.6N %.6N %.6N Tm"] + +local saved_text_pos_v = 0 +local saved_text_pos_h = 0 + +local function begin_text() + saved_text_pos_h = pdf_h + saved_text_pos_v = pdf_v + b = b + 1 ; buffer[b] = "BT" + need_tf = true + need_width = 0 + need_mode = 0 + mode = "text" +end + +local function end_text() + if done_width then + b = b + 1 ; buffer[b] = "0 w" + done_width = false + end + if done_mode then + b = b + 1 ; buffer[b] = "0 Tr" + done_mode = false + end + b = b + 1 ; buffer[b] = "ET" + pdf_h = saved_text_pos_h + pdf_v = saved_text_pos_v + mode = "page" +end + +local saved_chararray_pos_h +local saved_chararray_pos_v + +local saved_b = 0 + +local function begin_chararray() + saved_chararray_pos_h = pdf_h + saved_chararray_pos_v = pdf_v + cw = horizontalmode and saved_chararray_pos_h or - saved_chararray_pos_v + tj_delta = 0 + saved_b = b + b = b + 1 ; buffer[b] = " [" + mode = "chararray" +end + +local function end_chararray() + b = b + 1 ; buffer[b] = "] TJ" + buffer[saved_b] = concat(buffer,"",saved_b,b) + b = saved_b + pdf_h = saved_chararray_pos_h + pdf_v = saved_chararray_pos_v + mode = "text" +end + +local function begin_charmode() + b = b + 1 ; buffer[b] = "<" + mode = "char" +end + +local function end_charmode() + b = b + 1 ; buffer[b] = ">" + mode = "chararray" +end + +local function calc_pdfpos(h,v) + -- mostly char + if mode == "page" then + cmtx = h - pdf_h + cmty = v - pdf_v + return h ~= pdf_h or v ~= pdf_v + elseif mode == "text" then + tmtx = h - saved_text_pos_h + tmty = v - saved_text_pos_v + return h ~= pdf_h or v ~= pdf_v + elseif horizontalmode then + tmty = v - saved_text_pos_v + tj_delta = cw - h + return tj_delta ~= 0 or v ~= pdf_v + else + tmtx = h - saved_text_pos_h + tj_delta = cw + v + return tj_delta ~= 0 or h ~= pdf_h + end +end + +local function pdf_set_pos(h,v) + local move = calc_pdfpos(h,v) + if move then + b = b + 1 ; buffer[b] = f_cm(cmrx, cmsx, cmsy, cmry, cmtx*bpfactor, cmty*bpfactor) + pdf_h = pdf_h + cmtx + pdf_v = pdf_v + cmty + end +end + +local function pdf_reset_pos() + if mode == "page" then + cmtx = - pdf_h + cmty = - pdf_v + if pdf_h == 0 and pdf_v == 0 then + return + end + elseif mode == "text" then + tmtx = - saved_text_pos_h + tmty = - saved_text_pos_v + if pdf_h == 0 and pdf_v == 0 then + return + end + elseif horizontalmode then + tmty = - saved_text_pos_v + tj_delta = cw + if tj_delta == 0 and pdf_v == 0 then + return + end + else + tmtx = - saved_text_pos_h + tj_delta = cw + if tj_delta == 0 and pdf_h == 0 then + return + end + end + b = b + 1 ; buffer[b] = f_cm(cmrx, cmsx, cmsy, cmry, cmtx*bpfactor, cmty*bpfactor) + pdf_h = pdf_h + cmtx + pdf_v = pdf_v + cmty +end + +local function pdf_set_pos_temp(h,v) + local move = calc_pdfpos(h,v) + if move then + b = b + 1 ; buffer[b] = f_cm(cmrx, cmsx, cmsy, cmry, cmtx*bpfactor, cmty*bpfactor) + end +end + +-- these dummy returns makes using them a bit faster + +local function pdf_end_string_nl() + if mode == "char" then + end_charmode() + return end_chararray() + elseif mode == "chararray" then + return end_chararray() + end +end + +local function pdf_goto_textmode() + if mode == "page" then + pdf_reset_pos() + return begin_text() + elseif mode ~= "text" then + if mode == "char" then + end_charmode() + return end_chararray() + else -- if mode == "chararray" then + return end_chararray() + end + end +end + +local function pdf_goto_pagemode() + if mode ~= "page" then + if mode == "char" then + end_charmode() + end_chararray() + return end_text() + elseif mode == "chararray" then + end_chararray() + return end_text() + elseif mode == "text" then + return end_text() + end + end +end + +local function pdf_goto_fontmode() + if mode == "char" then + end_charmode() + end_chararray() + end_text() + elseif mode == "chararray" then + end_chararray() + end_text() + elseif mode == "text" then + end_text() + end + pdf_reset_pos() + mode = "page" +end + +-- characters + +local flushcharacter do + + local round = math.round + + -- across pages ... todo: clean up because we don't need to pass the font + -- as fontparameters already has checked / set it we can also have a variable + -- for it so + + local naturalwidth = nil + local hshift = false + local vshift = false + + -- local naturalwidths = setmetatableindex(function(t,font) + -- local d = descriptions[font] + -- local c = characters[font] + -- local f = parameters[font].hfactor + -- local v = setmetatableindex(function(t,char) + -- local w + -- local e = d and d[char] + -- if e then + -- w = e.width + -- if w then + -- w = w * f + -- end + -- end + -- if not w then + -- e = c[char] + -- if e then + -- w = e.width or 0 + -- end + -- end + -- if not w then + -- w = 0 + -- end + -- t[char] = w + -- return w + -- end) + -- t[font] = v + -- return v + -- end) + + local naturalwidths = setmetatableindex(function(t,font) + local d = descriptions[font] + local c = characters[font] + local f = parameters[font].hfactor + local v = setmetatableindex(function(t,char) + local w + local e = c[char] + if e then + w = e.width or 0 + end + if not w then + e = d and d[char] + if e then + w = e.width + if w then + w = w * f + end + end + end + if not w then + w = 0 + end + t[char] = w + return w + end) + t[font] = v + return v + end) + + local function setup_fontparameters(font,factor,f,e) + local slant = fontparameters.slantfactor or 0 + local extend = fontparameters.extendfactor or 1 + local squeeze = fontparameters.squeezefactor or 1 + local expand = 1 + factor / 1000000 + local format = fontproperties.format + if e then + extend = extend * e + end + tmef = expand + tmrx = expand * extend + tmsy = slant + tmry = squeeze + need_width = fontparameters.width or 0 + need_mode = fontparameters.mode or 0 + f_cur = font + f_pdf = usedfonts[font] -- cache + cur_factor = factor + cur_f = f + cur_e = e + tj_delta = 0 + fs = fontparameters.size * bpfactor + if f then + fs = fs * f + end + -- kind of special: + if format == "opentype" or format == "type1" then + fs = fs * 1000 / fontparameters.units -- can we avoid this ? + end + -- + naturalwidth = naturalwidths[font] + -- + hshift = fontparameters.hshift + vshift = fontparameters.vshift + end + + local f_width = formatters["%.6N w"] + local f_mode = formatters["%i Tr"] -- can be hash + local f_font = formatters["/F%i %.6N Tf"] -- can be hash + + local s_width = "0 w" + local s_mode = "0 Tr" + + local width_factor = 72.27 / 72000.0 + + local function set_font() + -- if need_width and need_width ~= 0 then + if need_width ~= 0 then + b = b + 1 ; buffer[b] = f_width(width_factor*need_width) + done_width = true + elseif done_width then + b = b + 1 ; buffer[b] = s_width + done_width = false + end + -- if need_mode and need_mode ~= 0 then + if need_mode ~= 0 then + b = b + 1 ; buffer[b] = f_mode(need_mode) + done_mode = true + elseif done_mode then + b = b + 1 ; buffer[b] = s_mode + done_mode = false + end + b = b + 1 ; buffer[b] = f_font(f_pdf,fs) + f_pdf_cur = f_pdf + fs_cur = fs + need_tf = false + need_tm = true + end + + local function set_textmatrix(h,v) + local move = calc_pdfpos(h,v) + if need_tm or move then + b = b + 1 ; buffer[b] = f_tm(tmrx, tmsx, tmsy, tmry, tmtx*bpfactor, tmty*bpfactor) + pdf_h = saved_text_pos_h + tmtx + pdf_v = saved_text_pos_v + tmty + need_tm = false + end + cur_tmrx = tmrx + end + + local f_hex_4 = formatters["%04X"] + local f_hex_2 = formatters["%02X"] + + local h_hex_4 = setmetatableindex(function(t,k) -- we already have this somewhere + if k < 256 then -- maybe 512 + -- not sparse in this range + for i=0,255 do + t[i] = f_hex_4(i) + end + return t[k] + else + local v = f_hex_4(k) + t[k] = v + return v + end + end) + local h_hex_2 = setmetatableindex(function(t,k) -- we already have this somewhere + local v = k < 256 and f_hex_2(k) or "00" + t[k] = v + return v + end) + + -- local trace_threshold = false trackers.register("backends.pdf.threshold", function(v) trace_threshold = v end) + + -- local f_skip = formatters["%.2N"] + + -- I will redo this mess ... we no longer have the mkiv pdf generator that we used in + -- luatex (a precursor to lmtx and also for comparison) but only in lmtx now so ... + -- time to move on I guess. + + flushcharacter = function(current,pos_h,pos_v,pos_r,font,char,data,f,e,factor) -- ,naturalwidth,width) + if need_tf or font ~= f_cur or f_pdf ~= f_pdf_cur or fs ~= fs_cur or mode == "page" then + pdf_goto_textmode() + setup_fontparameters(font,factor,f,e) + set_font() + elseif cur_tmrx ~= tmrx or cur_factor ~= factor or cur_f ~= f or cur_e ~= e then + setup_fontparameters(font,factor,f,e) + need_tm = true + end + local move = calc_pdfpos(pos_h,pos_v) + + -- if trace_threshold then + -- report( + -- "font %i, char %C, factor %i, naturalwidth %p, move %l, tm %l, hpos %p, delta %p, threshold %p, cw %p", + -- font,char,factor,naturalwidth[char],move,need_tm,pos_h,tj_delta,threshold,cw + -- ) + -- end + + if move or need_tm then + if not need_tm then + if horizontalmode then + if (saved_text_pos_v + tmty) ~= pdf_v then + need_tm = true + elseif tj_delta >= threshold or tj_delta <= -threshold then + need_tm = true + end + else + if (saved_text_pos_h + tmtx) ~= pdf_h then + need_tm = true + elseif tj_delta >= threshold or tj_delta <= -threshold then + need_tm = true + end + end + end + + if hshift then pos_h = pos_h + hshift end + if vshift then pos_v = pos_v - vshift end + + if need_tm then + pdf_goto_textmode() + set_textmatrix(pos_h,pos_v) + begin_chararray() + move = calc_pdfpos(pos_h,pos_v) + end + if move then + local d = tj_delta * scalefactor + if d <= -0.5 or d >= 0.5 then + if mode == "char" then + end_charmode() + end + b = b + 1 ; buffer[b] = round(d) -- or f_skip(d) + end + cw = cw - tj_delta + end + end + + if mode == "chararray" then + begin_charmode() + end + + cw = cw + naturalwidth[char] * tmef + + local index = data.index or char + + b = b + 1 ; buffer[b] = font > 0 and h_hex_4[index] or h_hex_2[index] + + if not pdfcharacters[index] then + pdfcharacters[index] = true + end + + end + + flushfontchar = function(font,char,data) + local dummy = usedfonts[font] + local index = data.index or char + if not pdfcharacters[index] then + pdfcharacters[index] = true + end + return dummy + end + +end + +-- literals + +local flushliteral do + + local nodeproperties = nodes.properties.data + local literalvalues = nodes.literalvalues + + local originliteral_code = literalvalues.origin + local pageliteral_code = literalvalues.page + local alwaysliteral_code = literalvalues.always + local rawliteral_code = literalvalues.raw + local textliteral_code = literalvalues.text + local fontliteral_code = literalvalues.font + + flushliteral = function(current,pos_h,pos_v,mode,str) + if mode then + if not str then + mode, str = originliteral_code, mode + elseif mode == "mode" then + mode = literalvalues[str] + if mode == originliteral_code then + pdf_goto_pagemode() + pdf_set_pos(pos_h,pos_v) + elseif mode == pageliteral_code then + pdf_goto_pagemode() + elseif mode == textliteral_code then + pdf_goto_textmode() + elseif mode == fontliteral_code then + pdf_goto_fontmode() + elseif mode == alwaysliteral_code then + pdf_end_string_nl() + need_tm = true + elseif mode == rawliteral_code then + pdf_end_string_nl() + end + return + else + mode = literalvalues[mode] + end + else + local p = nodeproperties[current] + if p then + str = p.data + mode = p.mode + else + str, mode = getdata(current) + end + end + if str and str ~= "" then + if mode == originliteral_code then + pdf_goto_pagemode() + pdf_set_pos(pos_h,pos_v) + elseif mode == pageliteral_code then + pdf_goto_pagemode() + elseif mode == textliteral_code then + pdf_goto_textmode() + elseif mode == fontliteral_code then + pdf_goto_fontmode() + elseif mode == alwaysliteral_code then + pdf_end_string_nl() + need_tm = true + elseif mode == rawliteral_code then + pdf_end_string_nl() + else + report("check literal") + pdf_goto_pagemode() + pdf_set_pos(pos_h,pos_v) + end + b = b + 1 ; buffer[b] = str + end + end + + updaters.register("backend.update.pdf",function() + function pdf.print(mode,str) + if str then + mode = literalvalues[mode] + else + mode, str = originliteral_code, mode + end + if str and str ~= "" then + if mode == originliteral_code then + pdf_goto_pagemode() + -- pdf_set_pos(pdf_h,pdf_v) + elseif mode == pageliteral_code then + pdf_goto_pagemode() + elseif mode == textliteral_code then + pdf_goto_textmode() + elseif mode == fontliteral_code then + pdf_goto_fontmode() + elseif mode == alwaysliteral_code then + pdf_end_string_nl() + need_tm = true + elseif mode == rawliteral_code then + pdf_end_string_nl() + else + pdf_goto_pagemode() + -- pdf_set_pos(pdf_h,pdf_v) + end + b = b + 1 ; buffer[b] = str + flush_buffer() + end + end + end) + +end + +-- grouping & orientation + +local flushsave, flushrestore, flushsetmatrix do + + local matrices = { } + local positions = { } + local nofpositions = 0 + local nofmatrices = 0 + + local f_matrix = formatters["%s 0 0 cm"] + + flushsave = function(current,pos_h,pos_v) + nofpositions = nofpositions + 1 + positions[nofpositions] = { pos_h, pos_v, nofmatrices } + pdf_goto_pagemode() + pdf_set_pos(pos_h,pos_v) + b = b + 1 ; buffer[b] = "q" + end + + flushrestore = function(current,pos_h,pos_v) + if nofpositions < 1 then + return + end + local t = positions[nofpositions] + -- local h = pos_h - t[1] + -- local v = pos_v - t[2] + if shippingmode == "page" then + nofmatrices = t[3] + end + pdf_goto_pagemode() + pdf_set_pos(pos_h,pos_v) + b = b + 1 ; buffer[b] = "Q" + nofpositions = nofpositions - 1 + end + + local function pdf_set_matrix(str,pos_h,pos_v) + if shippingmode == "page" then + local rx, sx, sy, ry = splitupstring(str," ") + if rx and ry and sx and ry then + rx, sx, sy, ry = tonumber(rx), tonumber(sx), tonumber(sy), tonumber(ry) + local tx = pos_h * (1 - rx) - pos_v * sy + local ty = pos_v * (1 - ry) - pos_h * sx + if nofmatrices > 0 then + local t = matrices[nofmatrices] + local r_x, s_x, s_y, r_y, te, tf = t[1], t[2], t[3], t[4], t[5], t[6] + rx, sx = rx * r_x + sx * s_y, rx * s_x + sx * r_y + sy, ry = sy * r_x + ry * s_y, sy * s_x + ry * r_y + tx, ty = tx * r_x + ty * s_y, tx * s_x + ty * r_y + end + nofmatrices = nofmatrices + 1 + matrices[nofmatrices] = { rx, sx, sy, ry, tx, ty } + end + end + end + + local nodeproperties = nodes.properties.data + + flushsetmatrix = function(current,pos_h,pos_v) + local str + if type(current) == "string" then + str = current + else + local p = nodeproperties[current] + if p then + str = p.matrix + else + str = getdata(current) -- for the moment + end + end + if str and str ~= "" then + pdf_set_matrix(str,pos_h,pos_v) + pdf_goto_pagemode() + pdf_set_pos(pos_h,pos_v) + b = b + 1 ; buffer[b] = f_matrix(str) + end + end + + do + + local function hasmatrix() + return nofmatrices > 0 + end + + local function getmatrix() + if nofmatrices > 0 then + return unpack(matrices[nofmatrices]) + else + return 1, 0, 0, 1, 0, 0 + end + end + + updaters.register("backend.update.pdf",function() + pdf.hasmatrix = hasmatrix + pdf.getmatrix = getmatrix + end) + + end + + pushorientation = function(orientation,pos_h,pos_v,pos_r) + pdf_goto_pagemode() + pdf_set_pos(pos_h,pos_v) + b = b + 1 ; buffer[b] = "q" + if orientation == 1 then + b = b + 1 ; buffer[b] = "0 -1 1 0 0 0 cm" -- 90 + elseif orientation == 2 then + b = b + 1 ; buffer[b] = "-1 0 0 -1 0 0 cm" -- 180 + elseif orientation == 3 then + b = b + 1 ; buffer[b] = "0 1 -1 0 0 0 cm" -- 270 + end + end + + poporientation = function(orientation,pos_h,pos_v,pos_r) + pdf_goto_pagemode() + pdf_set_pos(pos_h,pos_v) + b = b + 1 ; buffer[b] = "Q" + end + + -- pushorientation = function(orientation,pos_h,pos_v,pos_r) + -- flushsave(false,pos_h,pos_v) + -- if orientation == 1 then + -- flushsetmatrix("0 -1 1 0",pos_h,pos_v) + -- elseif orientation == 2 then + -- flushsetmatrix("-1 0 0 -1",pos_h,pos_v) + -- elseif orientation == 3 then + -- flushsetmatrix("0 1 -1 0",pos_h,pos_v) + -- end + -- end + + -- poporientation = function(orientation,pos_h,pos_v,pos_r) + -- flushrestore(false,pos_h,pos_v) + -- end + +end + +-- rules + +local flushedxforms = { } -- actually box resources but can also be direct +local localconverter = nil -- will be set + +local flushrule, flushsimplerule, flushspecialrule, flushimage, flushgroup do + + local rulecodes = nodes.rulecodes + local newrule = nodes.pool.rule + + local setprop = nuts.setprop + local getprop = nuts.getprop + + local normalrule_code = rulecodes.normal + local boxrule_code = rulecodes.box + local imagerule_code = rulecodes.image + local emptyrule_code = rulecodes.empty + local userrule_code = rulecodes.user + local overrule_code = rulecodes.over + local underrule_code = rulecodes.under + local fractionrule_code = rulecodes.fraction + local radicalrule_code = rulecodes.radical + local outlinerule_code = rulecodes.outline + + local rule_callback = callbacks.functions.process_rule + + local f_fm = formatters["/Fm%d Do"] + local f_im = formatters["/Im%d Do"] + local f_gr = formatters["/Gp%d Do"] + + local s_b = "q" + local s_e = "Q" + + local f_v = formatters["[] 0 d 0 J %.6N w 0 0 m %.6N 0 l S"] + local f_h = formatters["[] 0 d 0 J %.6N w 0 0 m 0 %.6N l S"] + + local f_f = formatters["0 0 %.6N %.6N re f"] + local f_o = formatters["[] 0 d 0 J 0 0 %.6N %.6N re S"] + local f_w = formatters["[] 0 d 0 J %.6N w 0 0 %.6N %.6N re S"] + + local f_b = formatters["%.6N w 0 %.6N %.6N %.6N re f"] + local f_f = formatters["[] 0 d 0 J %.6N w %.6N %.6N %.6N %.6N re S"] + + -- Historically the index is an object which is kind of bad. + + local boxresources, n = { }, 0 + + getxformname = function(index) + local l = boxresources[index] + if l then + return l.name + else + report("no box resource %S",index) + end + end + + updaters.register("backend.update.pdf",function() + pdf.getxformname = getxformname + end) + + local function saveboxresource(box,attributes,resources,immediate,kind,margin) + n = n + 1 + local immediate = true + local margin = margin or 0 -- or dimension + local objnum = pdfreserveobject() + local list = tonut(type(box) == "number" and tex.takebox(box) or box) + -- + local width, height, depth = getwhd(list) + -- + local l = { + width = width, + height = height, + depth = depth, + margin = margin, + attributes = attributes, + resources = resources, + list = nil, + type = kind, + name = n, + index = objnum, + objnum = objnum, + } + boxresources[objnum] = l + if immediate then + localconverter(list,"xform",objnum,l) + flushedxforms[objnum] = { true , objnum } + flushlist(list) + else + l.list = list + end + return objnum + end + + local function useboxresource(index,wd,ht,dp) + local l = boxresources[index] + if l then + if wd or ht or dp then + wd, ht, dp = wd or 0, ht or 0, dp or 0 + else + wd, ht, dp = l.width, l.height, l.depth + end + local rule = newrule(wd,ht,dp) -- newboxrule + rule.subtype = boxrule_code + setprop(tonut(rule),"index",index) + return rule, wd, ht, dp + else + report("no box resource %S",index) + end + end + + local function getboxresourcedimensions(index) + local l = boxresources[index] + if l then + return l.width, l.height, l.depth, l.margin + else + report("no box resource %S",index) + end + end + + local function getboxresourcebox(index) + local l = boxresources[index] + if l then + return l.list + end + end + + updaters.register("backend.update.tex",function() + tex.saveboxresource = saveboxresource + tex.useboxresource = useboxresource + tex.getboxresourcedimensions = getboxresourcedimensions + tex.getboxresourcebox = getboxresourcebox + end) + + -- a bit of a mess: index is now objnum but that has to change to a proper index + -- ... an engine inheritance + + local function flushpdfxform(current,pos_h,pos_v,pos_r,size_h,size_v) + -- object properties + local objnum = getprop(current,"index") + local name = getxformname(objnum) + local info = flushedxforms[objnum] + local r = boxresources[objnum] + if not info then + info = { false , objnum } + flushedxforms[objnum] = info + end + local wd, ht, dp = getboxresourcedimensions(objnum) + -- or: wd, ht, dp = r.width, r.height, r.depth + -- sanity check + local htdp = ht + dp + if wd == 0 or size_h == 0 or htdp == 0 or size_v == 0 then + return + end + -- calculate scale + local rx, ry = 1, 1 + if wd ~= size_h or htdp ~= size_v then + rx = size_h / wd + ry = size_v / htdp + end + -- flush the reference + usedxforms[objnum] = true + pdf_goto_pagemode() + calc_pdfpos(pos_h,pos_v) + tx = cmtx * bpfactor + ty = cmty * bpfactor + b = b + 1 ; buffer[b] = s_b + b = b + 1 ; buffer[b] = f_cm(rx,0,0,ry,tx,ty) + b = b + 1 ; buffer[b] = f_fm(name) + b = b + 1 ; buffer[b] = s_e + end + + -- place image also used in vf but we can use a different one if we need it + + local imagetypes = images.types -- pdf png jpg jp2 jbig2 stream memstream + local img_none = imagetypes.none + local img_pdf = imagetypes.pdf + local img_stream = imagetypes.stream + local img_memstream = imagetypes.memstream + + local one_bp = 65536 * bpfactor + + local imageresources, n = { }, 0 + + getximagename = function(index) + local l = imageresources[index] + if l then + return l.name + else + report("no image resource %S",index) + end + end + + updaters.register("backend.update.pdf",function() + pdf.getximagename = getximagename + end) + + -- Groups are flushed immediately but we can decide to make them into a + -- specific whatsit ... but not now. We could hash them if needed when + -- we use lot sof them in mp ... but not now. + + usedxgroups = { } + local groups = 0 + local group = nil + + flushgroup = function(content,bbox) + if not group then + group = pdfdictionary { + Type = pdfconstant("Group"), + S = pdfconstant("Transparency"), + } + end + local wrapper = pdfdictionary { + Type = pdf_xobject, + Subtype = pdf_form, + FormType = 1, + Group = group, + BBox = pdfarray(bbox), + Resources = lpdf.collectedresources { serialize = false }, + } + local objnum = pdfflushstreamobject(content,wrapper,false) + groups = groups + 1 + usedxgroups[groups] = objnum + return f_gr(groups) + end + + lpdf.flushgroup = flushgroup -- todo: access via driver in mlib-pps + + -- end of experiment + + local function flushpdfximage(current,pos_h,pos_v,pos_r,size_h,size_v) + + local width, + height, + depth = getwhd(current) + local total = height + depth + local transform = getprop(current,"transform") or 0 -- we never set it ... so just use rotation then + local index = getprop(current,"index") or 0 + local kind, + xorigin, + yorigin, + xsize, + ysize, + rotation, -- transform / orientation / rotation : it's a mess (i need to redo this) + objnum, + groupref = pdfincludeimage(index) -- needs to be sorted out, bad name (no longer mixed anyway) + + if not kind then + report("invalid image %S",index) + return + end + + local rx, sx, sy, ry, tx, ty = 1, 0, 0, 1, 0, 0 + + -- tricky: xsize and ysize swapped + + if kind == img_pdf or kind == img_stream or kind == img_memstream then + rx, ry, tx, ty = 1/xsize, 1/ysize, xorigin/xsize, yorigin/ysize + else + -- if kind == img_png then + -- -- if groupref > 0 and img_page_group_val == 0 then + -- -- img_page_group_val = groupref + -- -- end + -- end + rx, ry = bpfactor, bpfactor + end + + if band(transform,7) > 3 then + -- mirror + rx, tx = -rx, -tx + end + local t = band(transform + rotation,3) + if t == 0 then + -- nothing + elseif t == 1 then + -- rotation over 90 degrees (counterclockwise) + rx, sx, sy, ry, tx, ty = 0, rx, -ry, 0, -ty, tx + elseif t == 2 then + -- rotation over 180 degrees (counterclockwise) + rx, ry, tx, ty = -rx, -ry, -tx, -ty + elseif t == 3 then + -- rotation over 270 degrees (counterclockwise) + rx, sx, sy, ry, tx, ty = 0, -rx, ry, 0, ty, -tx + end + + rx = rx * width + sx = sx * total + sy = sy * width + ry = ry * total + tx = pos_h - tx * width + ty = pos_v - ty * total + + local t = transform + rotation + + if band(transform,7) > 3 then + t = t + 1 + end + + t = band(t,3) + + if t == 0 then + -- no transform + elseif t == 1 then + -- rotation over 90 degrees (counterclockwise) + tx = tx + width + elseif t == 2 then + -- rotation over 180 degrees (counterclockwise) + tx = tx + width + ty = ty + total + elseif t == 3 then + -- rotation over 270 degrees (counterclockwise) + ty = ty + total + end + + -- a flaw in original, can go: + -- + -- if img_page_group_val == 0 then + -- img_page_group_val = group_ref + -- end + + usedximages[index] = objnum -- hm + + pdf_goto_pagemode() + + calc_pdfpos(tx,ty) + + tx = cmtx * bpfactor + ty = cmty * bpfactor + + b = b + 1 ; buffer[b] = s_b + b = b + 1 ; buffer[b] = f_cm(rx,sx,sy,ry,tx,ty) + b = b + 1 ; buffer[b] = f_im(index) + b = b + 1 ; buffer[b] = s_e + end + + flushimage = function(index,width,height,depth,pos_h,pos_v) + + -- used in vf characters + + local total = height + depth + local kind, + xorigin, yorigin, + xsize, ysize, + rotation, + objnum, + groupref = pdfincludeimage(index) + + local rx = width / xsize + local sx = 0 + local sy = 0 + local ry = total / ysize + local tx = pos_h + -- to be sorted out + -- local ty = pos_v - depth + local ty = pos_v -- we assume that depth is dealt with in the caller (for now) + + usedximages[index] = objnum + + pdf_goto_pagemode() + + calc_pdfpos(tx,ty) + + tx = cmtx * bpfactor + ty = cmty * bpfactor + + b = b + 1 ; buffer[b] = s_b + b = b + 1 ; buffer[b] = f_cm(rx,sx,sy,ry,tx,ty) + b = b + 1 ; buffer[b] = f_im(index) + b = b + 1 ; buffer[b] = s_e + end + + -- For the moment we need this hack because the engine checks the 'image' + -- command in virtual fonts (so we use lua instead). + -- + -- These will be replaced by a new more advanced one ... some day ... or + -- never because the next are like the other engines and compensate for + -- small sizes which is needed for inaccurate viewers. + + flushrule = function(current,pos_h,pos_v,pos_r,size_h,size_v,subtype) + + if subtype == emptyrule_code then + return + elseif subtype == boxrule_code then + return flushpdfxform(current,pos_h,pos_v,pos_r,size_h,size_v) + elseif subtype == imagerule_code then + return flushpdfximage(current,pos_h,pos_v,pos_r,size_h,size_v) + end + if subtype == userrule_code or subtype >= overrule_code and subtype <= radicalrule_code then + pdf_goto_pagemode() + b = b + 1 ; buffer[b] = s_b + pdf_set_pos_temp(pos_h,pos_v) + rule_callback(current,size_h,size_v,pos_r) -- so we pass direction + b = b + 1 ; buffer[b] = s_e + return + end + + pdf_goto_pagemode() + + -- local saved_b = b + + b = b + 1 ; buffer[b] = s_b + + local dim_h = size_h * bpfactor + local dim_v = size_v * bpfactor + local rule + + if dim_v <= one_bp then + pdf_set_pos_temp(pos_h,pos_v + 0.5 * size_v) + rule = f_v(dim_v,dim_h) + elseif dim_h <= one_bp then + pdf_set_pos_temp(pos_h + 0.5 * size_h,pos_v) + rule = f_h(dim_h,dim_v) + else + pdf_set_pos_temp(pos_h,pos_v) + if subtype == outlinerule_code then + local linewidth = getdata(current) + if linewidth > 0 then + rule = f_w(linewidth * bpfactor,dim_h,dim_v) + else + rule = f_o(dim_h,dim_v) + end + else + rule = f_f(dim_h,dim_v) + end + end + + b = b + 1 ; buffer[b] = rule + b = b + 1 ; buffer[b] = s_e + + -- buffer[saved_b] = concat(buffer," ",saved_b,b) + -- b = saved_b + + end + + flushsimplerule = function(pos_h,pos_v,pos_r,size_h,size_v) + pdf_goto_pagemode() + + b = b + 1 ; buffer[b] = s_b + + local dim_h = size_h * bpfactor + local dim_v = size_v * bpfactor + local rule + + if dim_v <= one_bp then + pdf_set_pos_temp(pos_h,pos_v + 0.5 * size_v) + rule = f_v(dim_v,dim_h) + elseif dim_h <= one_bp then + pdf_set_pos_temp(pos_h + 0.5 * size_h,pos_v) + rule = f_h(dim_h,dim_v) + else + pdf_set_pos_temp(pos_h,pos_v) + rule = f_f(dim_h,dim_v) + end + + b = b + 1 ; buffer[b] = rule + b = b + 1 ; buffer[b] = s_e + end + + flushspecialrule = function(pos_h,pos_v,pos_r,width,height,depth,line,outline) + pdf_goto_pagemode() + + b = b + 1 ; buffer[b] = s_b + + local width = bpfactor * width + local height = bpfactor * height + local depth = bpfactor * depth + local total = height + depth + local line = bpfactor * line + local half = line / 2 + local rule + + if outline then + rule = f_f(line,half,-depth+half,width-line,total-line) + else + rule = f_b(line,-depth,width,total) + end + pdf_set_pos_temp(pos_h,pos_v) + + b = b + 1 ; buffer[b] = rule + b = b + 1 ; buffer[b] = s_e + end + +end + +--- basics + +local wrapup, registerpage do + + local pages = { } + local maxkids = 10 + local nofpages = 0 + local pagetag = "unset" + + registerpage = function(object) + nofpages = nofpages + 1 + local objnum = pdfpagereference(nofpages) + pages[nofpages] = { + page = nofpages, -- original number, only for diagnostics + objnum = objnum, + object = object, + tag = pagetag, + } + end + + function lpdf.setpagetag(tag) + pagetag = tag or "unset" + end + + function lpdf.getnofpages() + return nofpages + end + + function lpdf.getpagetags() + local list = { } + for i=1,nofpages do + list[i] = pages[i].tag + end + return list + end + + function lpdf.setpageorder(mapping) + -- mapping can be a hash so: + local list = table.sortedkeys(mapping) + local n = #list + if n == nofpages then + local done = { } + local hash = { } + for i=1,n do + local order = mapping[list[i]] + if hash[order] then + report("invalid page order, duplicate entry %i",order) + return + elseif order < 1 or order > nofpages then + report("invalid page order, no page %i",order) + return + else + done[i] = pages[order] + hash[order] = true + end + end + pages = done + else + report("invalid page order, %i entries expected",nofpages) + end + end + + -- We can have this, but then via codeinjections etc. Later. + + -- function structures.pages.swapthem() + -- local n = lpdf.getnofpages() + -- local t = { } + -- for i=1,n do + -- t[i] = i + -- end + -- for i=2,math.odd(n) and n or (n-1),2 do + -- t[i] = i+1 + -- t[i+1] = i + -- end + -- lpdf.setpageorder(t) + -- end + + wrapup = function(driver) + + -- hook (to reshuffle pages) + local pagetree = { } + local parent = nil + local minimum = 0 + local maximum = 0 + local current = 0 + if #pages > 1.5 * maxkids then + repeat + local plist, pnode + if current == 0 then + plist, minimum = pages, 1 + elseif current == 1 then + plist, minimum = pagetree, 1 + else + plist, minimum = pagetree, maximum + 1 + end + maximum = #plist + if maximum > minimum then + local kids + for i=minimum,maximum do + local p = plist[i] + if not pnode or #kids == maxkids then + kids = pdfarray() + parent = pdfreserveobject() + pnode = pdfdictionary { + objnum = parent, + Type = pdf_pages, + Kids = kids, + Count = 0, + } + pagetree[#pagetree+1] = pnode + end + kids[#kids+1] = pdfreference(p.objnum) + pnode.Count = pnode.Count + (p.Count or 1) + p.Parent = pdfreference(parent) + end + end + current = current + 1 + until maximum == minimum + -- flush page tree + for i=1,#pagetree do + local entry = pagetree[i] + local objnum = entry.objnum + entry.objnum = nil + pdfflushobject(objnum,entry) + end + else + -- ugly + local kids = pdfarray() + local list = pdfdictionary { + Type = pdf_pages, + Kids = kids, + Count = nofpages, + } + parent = pdfreserveobject() + for i=1,nofpages do + local page = pages[i] + kids[#kids+1] = pdfreference(page.objnum) + page.Parent = pdfreference(parent) + end + pdfflushobject(parent,list) + end + for i=1,nofpages do + local page = pages[i] + local object = page.object + object.Parent = page.Parent + pdfflushobject(page.objnum,object) + end + lpdf.addtocatalog("Pages",pdfreference(parent)) + + end + +end + +pdf_h, pdf_v = 0, 0 + +local function initialize(driver,details) + reset_variables(details) + reset_buffer() +end + +-- This will all move and be merged and become less messy. + +-- todo: more clever resource management: a bit tricky as we can inject +-- stuff in the page stream + +local compact = false + +do + + -- This is more a convenience feature and it might even be not entirely robust. + -- It removes redundant color directives which makes the page stream look a bit + -- nicer (also when figuring out issues). I might add more here but there is + -- some additional overhead involved so runtime can be impacted. + + local P, R, S, Cs, lpegmatch = lpeg.P, lpeg.R, lpeg.S, lpeg.Cs, lpeg.match + + local p_ds = (R("09") + S(" ."))^1 + ----- p_nl = S("\n\r")^1 + local p_nl = S("\n")^1 + local p_eg = P("Q") + + local p_cl = p_ds * (P("rg") + P("g") + P("k")) * p_ds * (P("RG") + P("G") + P("K")) + ----- p_cl = (p_ds * (P("rg") + P("g") + P("k") + P("RG") + P("G") + P("K")))^1 + local p_tr = P("/Tr") * p_ds * P("gs") + + local p_no_cl = (p_cl * p_nl) / "" + local p_no_tr = (p_tr * p_nl) / "" + local p_no_nl = 1 - p_nl + + local p_do_cl = p_cl * p_nl + local p_do_tr = p_tr * p_nl + + local p_do_eg = p_eg * p_nl + + local pattern = Cs( ( + (p_no_cl + p_no_tr)^0 * p_do_eg -- transparencies and colors before Q + + p_no_tr * p_no_cl * p_do_tr * p_do_cl -- transparencies and colors before others + + p_no_cl * p_do_cl -- successive colors + + p_no_tr * p_do_tr -- successive transparencies + + p_no_nl^1 + + 1 + )^1 ) + + local oldsize = 0 + local newsize = 0 + + directives.register("pdf.compact", function(v) + compact = v and function(s) + oldsize = oldsize + #s + s = lpegmatch(pattern,s) or s + newsize = newsize + #s + return s + end + end) + + statistics.register("pdf pagestream",function() + if oldsize ~= newsize then + return string.format("old size: %i, new size %i",oldsize,newsize) + end + end) + + +end + +local flushdeferred -- defined later + +local level = 0 + +local finalize do + + local f_font = formatters["F%d"] + + local f_form = formatters["Fm%d"] + local f_group = formatters["Gp%d"] + local f_image = formatters["Im%d"] + + finalize = function(driver,details) + + level = level + 1 + + pdf_goto_pagemode() -- for now + + local objnum = details.objnum + local specification = details.specification + + local content = concat(buffer,"\n",1,b) + + if compact then + content = compact(content) + end + + local fonts = nil + local xforms = nil + + if next(usedfonts) then + fonts = pdfdictionary { } + for k, v in next, usedfonts do + fonts[f_font(v)] = pdfreference(pdfgetfontobjnumber(k)) -- we can overload for testing + end + end + + -- messy: use real indexes for both ... so we need to change some in the + -- full luatex part + + if next(usedxforms) or next(usedximages) or next(usedxgroups) then + xforms = pdfdictionary { } + for k in sortedhash(usedxforms) do + -- xforms[f_form(k)] = pdfreference(k) + xforms[f_form(getxformname(k))] = pdfreference(k) + end + for k, v in sortedhash(usedximages) do + xforms[f_image(k)] = pdfreference(v) + end + for k, v in sortedhash(usedxgroups) do + xforms[f_group(k)] = pdfreference(v) + end + end + + reset_buffer() + + -- finish_pdfpage_callback(shippingmode == "page") + + if shippingmode == "page" then + + local pageproperties = lpdf.getpageproperties() + + local pageresources = pageproperties.pageresources + local pageattributes = pageproperties.pageattributes + local pagesattributes = pageproperties.pagesattributes + + pageresources.Font = fonts + pageresources.XObject = xforms + pageresources.ProcSet = lpdf.procset() + + local xorigin, yorigin, relocated = backends.codeinjections.getpageorigin() -- for now here + + local bbox = pdfarray { + (boundingbox[1] + xorigin) * bpfactor, + (boundingbox[2] + yorigin) * bpfactor, + (boundingbox[3] + xorigin) * bpfactor, + (boundingbox[4] + yorigin) * bpfactor, + } + + if relocated then + content = formatters["1 0 0 1 %.6N %.6N cm\n%s"](bbox[1],bbox[2],content) + end + + local contentsobj = pdfflushstreamobject(content,false,false) + + pageattributes.Type = pdf_page + pageattributes.Contents = pdfreference(contentsobj) + pageattributes.Resources = pageresources + -- pageattributes.Resources = pdfreference(pdfflushobject(pageresources)) + -- pageattributes.MediaBox = bbox + pageattributes.MediaBox = pdfsharedobject(bbox) + pageattributes.Parent = nil -- precalculate + pageattributes.Group = nil -- todo + + -- resources can be indirect + + registerpage(pageattributes) + + lpdf.finalizepage(true) + + -- if relocated then + -- if pageattributes.TrimBox then pageattributes.TrimBox = box end + -- if pageattributes.CropBox then pageattributes.CropBox = box end + -- if pageattributes.BleedBox then pageattributes.BleedBox = box end + -- end + + if pageattributes.TrimBox then pageattributes.TrimBox = relocated and pdfsharedobject(box or pageattributes.TrimBox ) end + if pageattributes.CropBox then pageattributes.CropBox = relocated and pdfsharedobject(box or pageattributes.CropBox ) end + if pageattributes.BleedBox then pageattributes.BleedBox = relocated and pdfsharedobject(box or pageattributes.BleedBox) end + + else + + local xformtype = specification.type or 0 + local margin = specification.margin or 0 + local attributes = specification.attributes or "" + local resources = specification.resources or "" + + local wrapper = nil + + if xformtype == 0 then + wrapper = pdfdictionary { + Type = pdf_xobject, + Subtype = pdf_form, + FormType = 1, + BBox = nil, + Matrix = nil, + Resources = nil, + } + else + wrapper = pdfdictionary { + BBox = nil, + Matrix = nil, + Resources = nil, + } + end + if xformtype == 0 or xformtype == 1 or xformtype == 3 then + wrapper.BBox = pdfarray { + -margin * bpfactor, + -margin * bpfactor, + (boundingbox[3] + margin) * bpfactor, + (boundingbox[4] + margin) * bpfactor, + } + end + if xformtype == 0 or xformtype == 2 or xformtype == 3 then + -- can be shared too + wrapper.Matrix = pdfarray { 1, 0, 0, 1, 0, 0 } + end + + -- todo: additional = resources + + local boxresources = lpdf.collectedresources { serialize = false } + boxresources.Font = fonts + boxresources.XObject = xforms + + -- todo: maybe share them + -- wrapper.Resources = pdfreference(pdfflushobject(boxresources)) + + if resources ~= "" then + boxresources = boxresources + resources + end + if attributes ~= "" then + wrapper = wrapper + attributes + end + + wrapper.Resources = next(boxresources) and boxresources or nil + wrapper.ProcSet = lpdf.procset() + + -- pdfflushstreamobject(content,wrapper,false,objectnumber) + pdfflushstreamobject(content,wrapper,false,specification.objnum) + + end + + for objnum in sortedhash(usedxforms) do + local f = flushedxforms[objnum] + if f[1] == false then + f[1] = true + local objnum = f[2] -- specification.objnum + local specification = boxresources[objnum] + local list = specification.list + localconverter(list,"xform",f[2],specification) + end + end + + pdf_h, pdf_v = 0, 0 + + if level == 1 then + flushdeferred() + end + level = level - 1 + + end + +end + +updaters.register("backend.update.pdf",function() + job.positions.registerhandlers { + getpos = drivers.getpos, + getrpos = drivers.getrpos, + gethpos = drivers.gethpos, + getvpos = drivers.getvpos, + } +end) + +updaters.register("backend.update",function() + local saveboxresource = tex.boxresources.save + -- + -- also in lpdf-res .. brrr .. needs fixing + -- + backends.codeinjections.registerboxresource = function(n,offset) + local r = saveboxresource(n,nil,nil,false,0,offset or 0) + return r + end +end) + +-- now comes the pdf file handling + +local objects = { } +local streams = { } -- maybe just parallel to objects (no holes) +local nofobjects = 0 +local offset = 0 +local f = false +local flush = false +local threshold = 40 -- also #("/Filter /FlateDecode") (compression threshold) +local objectstream = true +local compress = true +local cache = false +local info = "" +local catalog = "" +local lastdeferred = false +local majorversion = 1 +local minorversion = 7 +local trailerid = true + +directives.register("backend.pdf.threshold",function(v) + if v then + threshold = tonumber(v) or 40 + else + threshold = -1000 + end +end) + +local f_object = formatters["%i 0 obj\010%s\010endobj\010"] +local f_stream_n_u = formatters["%i 0 obj\010<< /Length %i >>\010stream\010%s\010endstream\010endobj\010"] +local f_stream_n_c = formatters["%i 0 obj\010<< /Filter /FlateDecode /Length %i >>\010stream\010%s\010endstream\010endobj\010"] +local f_stream_d_u = formatters["%i 0 obj\010<< %s /Length %i >>\010stream\010%s\010endstream\010endobj\010"] +local f_stream_d_c = formatters["%i 0 obj\010<< %s /Filter /FlateDecode /Length %i >>\010stream\010%s\010endstream\010endobj\010"] +local f_stream_d_r = formatters["%i 0 obj\010<< %s >>\010stream\010%s\010endstream\010endobj\010"] + +----- f_object_b = formatters["%i 0 obj\010"] +local f_stream_b_n_u = formatters["%i 0 obj\010<< /Length %i >>\010stream\010"] +local f_stream_b_n_c = formatters["%i 0 obj\010<< /Filter /FlateDecode /Length %i >>\010stream\010"] +local f_stream_b_d_u = formatters["%i 0 obj\010<< %s /Length %i >>\010stream\010"] +local f_stream_b_d_c = formatters["%i 0 obj\010<< %s /Filter /FlateDecode /Length %i >>\010stream\010"] +local f_stream_b_d_r = formatters["%i 0 obj\010<< %s >>\010stream\010"] + +----- s_object_e = "\010endobj\010" +local s_stream_e = "\010endstream\010endobj\010" + +do + + local function setinfo() end -- we get it + local function setcatalog() end -- we get it + + local function settrailerid(v) + trailerid = v or false + end + + local function setmajorversion(v) majorversion = tonumber(v) or majorversion end + local function setminorversion(v) minorversion = tonumber(v) or minorversion end + + local function getmajorversion(v) return majorversion end + local function getminorversion(v) return minorversion end + + local function setcompresslevel (v) compress = v and v ~= 0 and true or false end + local function setobjcompresslevel(v) objectstream = v and v ~= 0 and true or false end + + local function getcompresslevel (v) return compress and 3 or 0 end + local function getobjcompresslevel(v) return objectstream and 1 or 0 end + + local function setpageresources () end -- needs to be sorted out + local function setpageattributes () end + local function setpagesattributes() end + + updaters.register("backend.update.pdf",function() + pdf.setinfo = setinfo + pdf.setcatalog = setcatalog + pdf.settrailerid = settrailerid + pdf.setmajorversion = setmajorversion + pdf.setminorversion = setminorversion + pdf.getmajorversion = getmajorversion + pdf.getminorversion = getminorversion + pdf.setcompresslevel = setcompresslevel + pdf.setobjcompresslevel = setobjcompresslevel + pdf.getcompresslevel = getcompresslevel + pdf.getobjcompresslevel = getobjcompresslevel + pdf.setpageresources = setpageresources + pdf.setpageattributes = setpageattributes + pdf.setpagesattributes = setpagesattributes + end) + +end + +local addtocache, flushcache, cache do + + local data, d = { }, 0 + local list, l = { }, 0 + local coffset = 0 + local indices = { } + + local maxsize = 32 * 1024 -- uncompressed + local maxcount = 0xFF + + addtocache = function(n,str) + local size = #str + if size == 0 then + -- todo: message + return + end + if coffset + size > maxsize or d == maxcount then + flushcache() + end + if d == 0 then + nofobjects = nofobjects + 1 + objects[nofobjects] = false + streams[nofobjects] = indices + cache = nofobjects + end + objects[n] = - cache + indices[n] = d + d = d + 1 + -- can have a comment n 0 obj as in luatex + data[d] = str + l = l + 1 ; list[l] = n + l = l + 1 ; list[l] = coffset + coffset = coffset + size + 1 + end + + local p_ObjStm = pdfconstant("ObjStm") + + flushcache = function() -- references cannot be stored + if l > 0 then + list = concat(list," ") + data[0] = list + data = concat(data,"\010",0,d) + local strobj = pdfdictionary { + Type = p_ObjStm, + N = d, + First = #list + 1, + } + objects[cache] = offset + local b = nil + local e = s_stream_e + if compress then + local comp = zlibcompress(data,3) + if comp and #comp < #data then + data = comp + b = f_stream_b_d_c(cache,strobj(),#data) + else + b = f_stream_b_d_u(cache,strobj(),#data) + end + else + b = f_stream_b_d_u(cache,strobj(),#data) + end + flush(f,b) + flush(f,data) + flush(f,e) + offset = offset + #b + #data + #e + data, d = { }, 0 + list, l = { }, 0 + coffset = 0 + indices = { } + end + end + +end + +local function pdfreserveobj() + nofobjects = nofobjects + 1 + objects[nofobjects] = false + return nofobjects +end + +local pages = table.setmetatableindex(function(t,k) + local v = pdfreserveobj() + t[k] = v + return v +end) + +local function getpageref(n) + return pages[n] +end + +local function refobj() + -- not needed, as we have auto-delay +end + +local function flushnormalobj(data,n) + if not n then + nofobjects = nofobjects + 1 + n = nofobjects + end + data = f_object(n,data) + if level == 0 then + objects[n] = offset + offset = offset + #data + flush(f,data) + else + if not lastdeferred then + lastdeferred = n + elseif n < lastdeferred then + lastdeferred = n + end + objects[n] = data + end + return n +end + +local function flushstreamobj(data,n,dict,comp,nolength) + if not data then + report("no data for %S",dict) + return + end + if not n then + nofobjects = nofobjects + 1 + n = nofobjects + end + local size = #data + if level == 0 then + local b = nil + local e = s_stream_e + if nolength then + b = f_stream_b_d_r(n,dict) + elseif comp ~= false and compress and size > threshold then + local compdata = zlibcompress(data,3) + if compdata then + local compsize = #compdata + if compsize > size - threshold then + b = dict and f_stream_b_d_u(n,dict,size) or f_stream_b_n_u(n,size) + else + data = compdata + b = dict and f_stream_b_d_c(n,dict,compsize) or f_stream_b_n_c(n,compsize) + end + else + b = dict and f_stream_b_d_u(n,dict,size) or f_stream_b_n_u(n,size) + end + else + b = dict and f_stream_b_d_u(n,dict,size) or f_stream_b_n_u(n,size) + end + flush(f,b) + flush(f,data) + flush(f,e) + objects[n] = offset + offset = offset + #b + #data + #e + else + if nolength then + data = f_stream_d_r(n,dict,data) + elseif comp ~= false and compress and size > threshold then + local compdata = zlibcompress(data,3) + if compdata then + local compsize = #compdata + if compsize > size - threshold then + data = dict and f_stream_d_u(n,dict,size,data) or f_stream_n_u(n,size,data) + else + data = dict and f_stream_d_c(n,dict,compsize,compdata) or f_stream_n_c(n,compsize,compdata) + end + else + data = dict and f_stream_d_u(n,dict,size,data) or f_stream_n_u(n,size,data) + end + else + data = dict and f_stream_d_u(n,dict,size,data) or f_stream_n_u(n,size,data) + end + if not lastdeferred then + lastdeferred = n + elseif n < lastdeferred then + lastdeferred = n + end + objects[n] = data + end + return n +end + +flushdeferred = function() -- was forward defined + if lastdeferred then + for n=lastdeferred,nofobjects do + local o = objects[n] + if type(o) == "string" then + objects[n] = offset + offset = offset + #o + flush(f,o) + end + end + lastdeferred = false + end +end + +-- n = pdf.obj([n,] objtext) +-- n = pdf.obj([n,] "file", filename) +-- n = pdf.obj([n,] "stream", streamtext [, attrtext]) +-- n = pdf.obj([n,] "streamfile", filename [, attrtext]) +-- +-- n = pdf.obj { +-- type = , -- raw|stream +-- immediate = , +-- objnum = , +-- attr = , +-- compresslevel = , +-- objcompression = , +-- file = , +-- string = , +-- nolength = , +-- } + +local function obj(a,b,c,d) + local kind --, immediate + local objnum, data, attr, filename + local compresslevel, objcompression, nolength + local argtype = type(a) + if argtype == "table" then + kind = a.type -- raw | stream + -- immediate = a.immediate + objnum = a.objnum + attr = a.attr + compresslevel = a.compresslevel + objcompression = a.objcompression + filename = a.file + data = a.string or a.stream or "" + nolength = a.nolength + if kind == "stream" then + if filename then + data = loaddata(filename) or "" + end + elseif kind == "raw"then + if filename then + data = loaddata(filename) or "" + end + elseif kind == "file"then + kind = "raw" + data = filename and loaddata(filename) or "" + elseif kind == "streamfile" then + kind = "stream" + data = filename and loaddata(filename) or "" + end + else + if argtype == "number" then + objnum = a + a, b, c = b, c, d + else + nofobjects = nofobjects + 1 + objnum = nofobjects + end + if b then + if a == "stream" then + kind = "stream" + data = b + elseif a == "file" then + -- kind = "raw" + data = loaddata(b) + elseif a == "streamfile" then + kind = "stream" + data = loaddata(b) + else + data = "" -- invalid object + end + attr = c + else + -- kind = "raw" + data = a + end + end + if not objnum then + nofobjects = nofobjects + 1 + objnum = nofobjects + end + -- todo: immediate + if kind == "stream" then + flushstreamobj(data,objnum,attr,compresslevel and compresslevel > 0 or nil,nolength) + elseif objectstream and objcompression ~= false then + addtocache(objnum,data) + else + flushnormalobj(data,objnum) + end + return objnum +end + +updaters.register("backend.update.pdf",function() + pdf.reserveobj = pdfreserveobj + pdf.getpageref = getpageref + pdf.refobj = refobj + pdf.flushstreamobj = flushstreamobj + pdf.flushnormalobj = flushnormalobj + pdf.obj = obj + pdf.immediateobj = obj +end) + +-- In lua 5.4 the methods are now moved one metalevel deeper so we need to get them +-- from mt.__index instead. (I did get that at first.) It makes for a slightly (imo) +-- nicer interface but no real gain in speed as we don't flush that often. + +local openfile, closefile do + + -- I used to do but then figured out that when I open and save a file in a mode + -- that removes trailing spaces, the xref becomes invalid. The problem was then that a + -- reconstruction of the file by a viewer gives weird effects probably because percent symbols + -- gets interpreted then. Thanks to Ross Moore for noticing this side effect! + + local f_used = formatters["%010i 00000 n\013\010"] + local f_link = formatters["%010i 00000 f\013\010"] + local f_first = formatters["%010i 65535 f\013\010"] + + local f_pdf = formatters["%%PDF-%i.%i\010"] + local f_xref = formatters["xref\0100 %i\010"] + local f_trailer_id = formatters["trailer\010<< %s /ID [ <%s> <%s> ] >>\010startxref\010%i\010%%%%EOF"] + local f_trailer_no = formatters["trailer\010<< %s >>\010startxref\010%i\010%%%%EOF"] + local f_startxref = formatters["startxref\010%i\010%%%%EOF"] + + local inmemory = false + local close = false + + -- local removefile = os.remove + + openfile = function(filename) + if inmemory then + local n = 0 + f = { } + flush = function(f,s) + n = n + 1 f[n] = s + end + close = function(f) + f = concat(f) + io.savedata(filename,f) + f = false + end + -- local n = 0 + -- f = { + -- write = function(self,s) + -- n = n + 1 f[n] = s + -- end, + -- close = function(self) + -- f = concat(f) + -- io.savedata(filename,f) + -- f = false + -- end, + -- } + else + f = io.open(filename,"wb") + if not f then + -- message + os.exit() + end + -- f:setvbuf("full",64*1024) + local m = getmetatable(f) + flush = m.write or m.__index.write + close = m.close or m.__index.close + end + -- + flush_buffer = function() + if b > 0 then + flush(f,concat(buffer,"",1,b)) + b = 0 + end + end + + local v = f_pdf(majorversion,minorversion) + -- local b = "%\xCC\xD5\xC1\xD4\xC5\xD8\xD0\xC4\xC6\010" -- LUATEXPDF (+128) + local b = "%\xC3\xCF\xCE\xD4\xC5\xD8\xD4\xD0\xC4\xC6\010" -- CONTEXTPDF (+128) + flush(f,v) + flush(f,b) + offset = #v + #b + end + + closefile = function(abort) + if abort then + f:close() + if not environment.arguments.nodummy then + f = io.open(abort,"wb") + if f then + local name = resolvers.findfile("context-lmtx-error.pdf") + if name then + local data = io.loaddata(name) + if data then + f:write(data) + f:close() + return + end + end + f:close() + end + end + os.remove(abort) + else + local xrefoffset = offset + local lastfree = 0 + local noffree = 0 + local catalog = lpdf.getcatalog() + local info = lpdf.getinfo() + if trailerid == true then + trailerid = md5HEX(osuuid()) + elseif trailerid and #trailerid > 32 then + trailerid = md5HEX(trailerid) + else + trailerid = false + end + if objectstream then + flushdeferred() + flushcache() + -- + xrefoffset = offset + -- + nofobjects = nofobjects + 1 + objects[nofobjects] = offset -- + 1 + -- + -- combine these three in one doesn't really give less code so + -- we go for the efficient ones + -- + local nofbytes = 4 + local c1, c2, c3, c4 + if offset <= 0xFFFF then + nofbytes = 2 + for i=1,nofobjects do + local o = objects[i] + if not o then + noffree = noffree + 1 + else + local strm = o < 0 + if strm then + o = -o + end + c1 = extract(o,8,8) + c2 = extract(o,0,8) + if strm then + objects[i] = char(2,c1,c2,streams[o][i]) + else + objects[i] = char(1,c1,c2,0) + end + end + end + if noffree > 0 then + for i=nofobjects,1,-1 do + local o = objects[i] + if not o then + local f1 = extract(lastfree,8,8) + local f2 = extract(lastfree,0,8) + objects[i] = char(0,f1,f2,0) + lastfree = i + end + end + end + elseif offset <= 0xFFFFFF then + nofbytes = 3 + for i=1,nofobjects do + local o = objects[i] + if not o then + noffree = noffree + 1 + else + local strm = o < 0 + if strm then + o = -o + end + c1 = extract(o,16,8) + c2 = extract(o, 8,8) + c3 = extract(o, 0,8) + if strm then + objects[i] = char(2,c1,c2,c3,streams[o][i]) + else + objects[i] = char(1,c1,c2,c3,0) + end + end + end + if noffree > 0 then + for i=nofobjects,1,-1 do + local o = objects[i] + if not o then + local f1 = extract(lastfree,16,8) + local f2 = extract(lastfree, 8,8) + local f3 = extract(lastfree, 0,8) + objects[i] = char(0,f1,f2,f3,0) + lastfree = i + end + end + end + else + nofbytes = 4 + for i=1,nofobjects do + local o = objects[i] + if not o then + noffree = noffree + 1 + else + local strm = o < 0 + if strm then + o = -o + end + c1 = extract(o,24,8) + c2 = extract(o,16,8) + c3 = extract(o, 8,8) + c4 = extract(o, 0,8) + if strm then + objects[i] = char(2,c1,c2,c3,c4,streams[o][i]) + else + objects[i] = char(1,c1,c2,c3,c4,0) + end + end + end + if noffree > 0 then + for i=nofobjects,1,-1 do + local o = objects[i] + if not o then + local f1 = extract(lastfree,24,8) + local f2 = extract(lastfree,16,8) + local f3 = extract(lastfree, 8,8) + local f4 = extract(lastfree, 0,8) + objects[i] = char(0,f1,f2,f3,f4,0) + lastfree = i + end + end + end + end + objects[0] = rep("\0",1+nofbytes+1) + local data = concat(objects,"",0,nofobjects) + local xref = pdfdictionary { + Type = pdfconstant("XRef"), + Size = nofobjects + 1, + W = pdfarray { 1, nofbytes, 1 }, + Root = catalog, + Info = info, + ID = trailerid and pdfarray { pdfliteral(trailerid,true), pdfliteral(trailerid,true) } or nil, + } + if compress then + local comp = zlibcompress(data,3) + if comp then + data = comp + flush(f,f_stream_b_d_c(nofobjects,xref(),#data)) + else + flush(f,f_stream_b_d_u(nofobjects,xref(),#data)) + end + else + flush(f,f_stream_b_d_u(nofobjects,xref(),#data)) + end + flush(f,data) + flush(f,s_stream_e) + flush(f,f_startxref(xrefoffset)) + else + flushdeferred() + xrefoffset = offset + flush(f,f_xref(nofobjects+1)) + local trailer = pdfdictionary { + Size = nofobjects+1, + Root = catalog, + Info = info, + } + for i=1,nofobjects do + local o = objects[i] + if o then + objects[i] = f_used(o) + end + end + for i=nofobjects,1,-1 do + local o = objects[i] + if not o then + objects[i] = f_link(lastfree) + lastfree = i + end + end + objects[0] = f_first(lastfree) + flush(f,concat(objects,"",0,nofobjects)) + trailer.Size = nofobjects + 1 + if trailerid then + flush(f,f_trailer_id(trailer(),trailerid,trailerid,xrefoffset)) + else + flush(f,f_trailer_no(trailer(),xrefoffset)) + end + end + f:close() + end + io.flush() + closefile = function() end + end + +end + +-- For the moment we overload it here, although back-fil.lua eventually will +-- be merged with back-pdf as it's pdf specific, or maybe back-imp-pdf or so. + +updaters.register("backend.update.pdf",function() + + -- We overload img but at some point it will even go away, so we just + -- reimplement what we need in context. This will change completely i.e. + -- we will drop the low level interface! + + local codeinjections = backends.pdf.codeinjections + + local imagetypes = images.types -- pdf png jpg jp2 jbig2 stream memstream + local img_none = imagetypes.none + + local rulecodes = nodes.rulecodes + + local setprop = nodes.nuts.setprop + + local report_images = logs.reporter("backend","images") + + local lastindex = 0 + local indices = { } + + local bpfactor = number.dimenfactors.bp + local imagerule_code = rulecodes.image + + function codeinjections.newimage(specification) + return specification + end + + function codeinjections.copyimage(original) + return setmetatableindex(original) + end + + function codeinjections.scanimgage(specification) + return specification + end + + local function embedimage(specification) + if specification then + lastindex = lastindex + 1 + index = lastindex + specification.index = index + local xobject = pdfdictionary { } + if not specification.notype then + xobject.Type = pdf_xobject + xobject.Subtype = pdf_form + xobject.FormType = 1 + end + local bbox = specification.bbox + if bbox and not specification.nobbox then + xobject.BBox = pdfarray { + bbox[1] * bpfactor, + bbox[2] * bpfactor, + bbox[3] * bpfactor, + bbox[4] * bpfactor, + } + end + xobject = xobject + specification.attr + if bbox and not specification.width then + specification.width = bbox[3] + end + if bbox and not specification.height then + specification.height = bbox[4] + end + local dict = xobject() + -- + nofobjects = nofobjects + 1 + local objnum = nofobjects + local nolength = specification.nolength + local stream = specification.stream or specification.string + -- + -- We cannot set type in native img so we need this hack or + -- otherwise we need to patch too much. Better that i write + -- a wrapper then. Anyway, it has to be done better: a key that + -- tells either or not to scale by xsize/ysize when flushing. + -- + if not specification.type then + local kind = specification.kind + if kind then + -- take that one + elseif attr and find(attr,"BBox") then + kind = img_stream + else + -- hack: a bitmap + kind = img_none + end + specification.type = kind + specification.kind = kind + end + local compress = compresslevel and compresslevel > 0 or nil + flushstreamobj(stream,objnum,dict,compress,nolength) + specification.objnum = objnum + specification.rotation = specification.rotation or 0 + specification.orientation = specification.orientation or 0 + specification.transform = specification.transform or 0 + specification.stream = nil + specification.attr = nil + specification.type = specification.kind or specification.type or img_none + indices[index] = specification -- better create a real specification + return specification + end + end + + codeinjections.embedimage = embedimage + + function codeinjections.wrapimage(specification) + -- + local index = specification.index + if not index then + embedimage(specification) + end + -- + local width = specification.width or 0 + local height = specification.height or 0 + local depth = specification.depth or 0 + -- newimagerule + local n = nodes.pool.rule(width,height,depth) + n.subtype = imagerule_code + setprop(tonut(n),"index",specification.index) + return n + end + + function pdf.includeimage(index) + local specification = indices[index] + if specification then + local bbox = specification.bbox + local xorigin = bbox[1] + local yorigin = bbox[2] + local xsize = bbox[3] - xorigin -- we need the original ones, not the 'rotated' ones + local ysize = bbox[4] - yorigin -- we need the original ones, not the 'rotated' ones + local transform = specification.transform or 0 + local objnum = specification.objnum or pdfreserveobj() + local groupref = nil + local kind = specification.kind or specification.type or img_none -- determines scaling type + return + kind, + xorigin, yorigin, + xsize, ysize, + transform, + objnum, + groupref + end + end + +end) + +updaters.register("backend.update.lpdf",function() + + -- todo: an md5 or sha2 hash can save space + -- todo: make a type 3 font instead + -- todo: move to lpdf namespace + + local pdfimage = lpdf.epdf.image + local newpdf = pdfimage.new + local openpdf = pdfimage.open + local closepdf = pdfimage.close + local copypage = pdfimage.copy + + local embedimage = images.embed + + local nofstreams = 0 + local topdf = { } + local toidx = { } + + local function storedata_s(pdf) + local idx = toidx[pdf] + if not idx then + nofstreams = nofstreams + 1 + idx = nofstreams + toidx[pdf] = nofstreams + topdf[idx] = pdf + end + return idx + end + + local function vfimage_s(id,wd,ht,dp,pos_h,pos_v) + local index = topdf[id] + if type(index) == "string" then + local pdfdoc = newpdf(index,#index) + local image = copypage(pdfdoc) + local bbox = image.bbox + image.width = bbox[3] - bbox[1] + image.height = bbox[4] - bbox[2] + embedimage(image) + index = image.index + topdf[id] = index + end + -- pdf.print or pdf.literal + flushimage(index,wd,ht,dp,pos_h,pos_v) + end + + local function storedata_n(name,page) + local idx = toidx[pdf] + if not idx then + nofstreams = nofstreams + 1 + idx = nofstreams + toidx[pdf] = nofstreams + topdf[idx] = pdf + end + return idx + end + + -- We need to have a way to close such a pdf ... esp for fonts. + + local pdfdocs = { } + + local function vfimage_n(name,page,wd,ht,dp,pos_h,pos_v) + local d = pdfdocs[name] + if not d then + d = { doc = openpdf(name), pages = { } } + pdfdocs[name] = d + end + local index = d.pages[page] + if not index then + local image = copypage(d.doc,page) + local bbox = image.bbox + image.width = bbox[3] - bbox[1] + image.height = bbox[4] - bbox[2] + embedimage(image) + index = image.index + d.pages[page] = index + end + flushimage(index,wd,ht,dp,pos_h,pos_v) + end + + local function pdfvfimage(wd,ht,dp,data,name) + if type(data) == "number" then + return { "lua", function(font,char,pos_h,pos_v) + vfimage_n(name,data,wd,ht,dp,pos_h,pos_v) + end } + else + return { "lua", function(font,char,pos_h,pos_v) + local id = storedata_s(data) + vfimage_s(id,wd,ht,dp,pos_h,pos_v) + end } + end + end + + lpdf.vfimage = pdfvfimage + +end) + +-- The driver. + +do + + -- local addsuffix = file.addsuffix + local texgetbox = tex.getbox + + local pdfname = nil + local converter = nil + local useddriver = nil -- a bit of a hack + + local function outputfilename(driver) + return pdfname + end + + -- todo: prevent twice + + local function prepare(driver) + if not environment.initex then + -- install new functions in pdf namespace + updaters.apply("backend.update.pdf") + -- install new functions in lpdf namespace + updaters.apply("backend.update.lpdf") + -- adapt existing shortcuts to lpdf namespace + updaters.apply("backend.update.tex") + -- adapt existing shortcuts to tex namespace + updaters.apply("backend.update") + -- + -- if rawget(pdf,"setforcefile") then + -- pdf.setforcefile(false) -- default anyway + -- end + -- + -- pdfname = file.addsuffix(tex.jobname,"pdf") + pdfname = tex.jobname .. ".pdf" + openfile(pdfname) + -- + luatex.registerstopactions(1,function() + if pdfname then + lpdf.finalizedocument() + closefile() + end + end) + -- + luatex.registerpageactions(1,function() + if pdfname then + lpdf.finalizepage(true) + end + end) + -- -- + lpdf.registerdocumentfinalizer(wrapup,nil,"wrapping up") + -- + end + -- + environment.lmtxmode = CONTEXTLMTXMODE + -- + converter = drivers.converters.lmtx + useddriver = driver + end + + local function wrapup(driver) + if pdfname then + closefile() + pdfname = nil + end + end + + local function cleanup(driver) + if pdfname then + closefile(pdfname) + pdfname = nil + end + end + + local function convert(driver,boxnumber) + converter(driver,texgetbox(boxnumber),"page") + end + + localconverter = function(...) + converter(useddriver,...) + end + + drivers.install { + name = "pdf", + flushers = { + character = flushcharacter, + fontchar = flushfontchar, + rule = flushrule, + simplerule = flushsimplerule, + specialrule = flushspecialrule, + pushorientation = pushorientation, + poporientation = poporientation, + -- + literal = flushliteral, + setmatrix = flushsetmatrix, + save = flushsave, + restore = flushrestore, + image = flushimage, + group = flushgroup, + -- + updatefontstate = updatefontstate, + }, + actions = { + prepare = prepare, + wrapup = wrapup, + cleanup = cleanup, + -- + initialize = initialize, + convert = convert, + finalize = finalize, + -- + outputfilename = outputfilename, + }, + } + +end -- cgit v1.2.3