summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/context/base/mkxl/lpdf-lmt.lmt
diff options
context:
space:
mode:
Diffstat (limited to 'Master/texmf-dist/tex/context/base/mkxl/lpdf-lmt.lmt')
-rw-r--r--Master/texmf-dist/tex/context/base/mkxl/lpdf-lmt.lmt3019
1 files changed, 3019 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/context/base/mkxl/lpdf-lmt.lmt b/Master/texmf-dist/tex/context/base/mkxl/lpdf-lmt.lmt
new file mode 100644
index 00000000000..fbc002a9327
--- /dev/null
+++ b/Master/texmf-dist/tex/context/base/mkxl/lpdf-lmt.lmt
@@ -0,0 +1,3019 @@
+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 pdfreference = lpdf.reference
+local pdfdictionary = lpdf.dictionary
+local pdfarray = lpdf.array
+local pdfconstant = lpdf.constant
+local pdfliteral = lpdf.literal -- not to be confused with a whatsit!
+
+local pdfreserveobject
+local pdfpagereference
+local pdfflushobject
+local pdfsharedobject
+local pdfflushstreamobject
+local pdfdeferredobject
+local pdfimmediateobject
+
+local pdfgetfontname
+local pdfgetfontobjectnumber
+
+local pdfgetpagereference
+
+updaters.register("backend.update.lpdf",function()
+ pdfreserveobject = lpdf.reserveobject
+ pdfpagereference = lpdf.pagereference
+ pdfflushobject = lpdf.flushobject
+ pdfsharedobject = lpdf.shareobjectreference
+ pdfflushstreamobject = lpdf.flushstreamobject
+ pdfdeferredobject = lpdf.deferredobject
+ pdfimmediateobject = lpdf.immediateobject
+ --
+ pdfgetfontname = lpdf.getfontname
+ pdfgetfontobjectnumber = lpdf.getfontobjectnumber
+ --
+ pdfgetpagereference = lpdf.getpagereference
+end)
+
+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")
+local report_objects = logs.reporter("backend","objects")
+
+local trace_objects = false trackers.register("backend.objects", function(v) trace_objects = v end)
+local trace_details = false trackers.register("backend.details", function(v) trace_details = v end)
+
+-- we collect them:
+
+local flushers = { }
+
+-- 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, f_x_scale, f_y_scale
+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
+ f_x_scale = 1.0
+ f_y_scale = 1.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
+
+-- fonts
+
+local fontcharacters
+local fontdescriptions
+local fontparameters
+local fontproperties
+local usedcharacters = setmetatableindex("table")
+local pdfcharacters
+
+local horizontalmode = true
+local scalefactor = 1
+local threshold = 655360
+local thresfactor = 100
+local tjfactor = 100 / 65536
+
+lpdf.usedcharacters = usedcharacters
+
+function flushers.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"
+ 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
+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 or parameters[font].factor
+ -- 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 and 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)
+
+ -- when changing this, check math: compact-001.tex (rule width)
+
+ local naturalwidths = setmetatableindex(function(t,font)
+ local d = descriptions[font]
+ local c = characters[font]
+ local f = parameters[font].hfactor or parameters[font].factor
+ local v = setmetatableindex(function(t,char)
+ local w
+ local e = c and 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,sx,sy)
+ 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
+ f_x_scale = 1.0
+ f_y_scale = 1.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
+ --
+ f_x_scale = sx
+ if f_x_scale ~= 1.0 then
+ tmrx = tmrx * f_x_scale
+ end
+ f_y_scale = sy
+ if f_y_scale ~= 1.0 then
+ tmry = tmry * f_y_scale
+ 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.
+
+ flushers.character = function(current,pos_h,pos_v,pos_r,font,char,data,f,e,factor,sx,sy) -- ,naturalwidth,width)
+ if sx ~= f_x_scale or sy ~= f_y_scale or 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,sx,sy)
+ 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,sx,sy)
+ 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 / f_x_scale
+ 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 * f_x_scale
+
+ 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
+
+ flushers.fontchar = 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)
+ local p = nodeproperties[current]
+ if p then
+ local str = p.data
+ if str and str ~= "" then
+ local mode = p.mode
+ 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 -- aka direct
+ pdf_end_string_nl()
+ need_tm = true
+ elseif mode == rawliteral_code then
+ pdf_end_string_nl()
+ else
+ report("invalid literal mode %a when flushing %a",mode,str)
+ return
+ end
+ b = b + 1 ; buffer[b] = str
+ end
+ end
+ end
+
+ flushers.literal = flushliteral
+
+ function lpdf.print(mode,str)
+ -- This only works inside objects, don't change this to flush
+ -- in between. It's different from luatex but okay.
+ 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
+ report("invalid literal mode %a when flushing %a",mode,str)
+ return
+ end
+ b = b + 1 ; buffer[b] = str
+ end
+ end
+
+end
+
+-- grouping & orientation
+
+do
+
+ local matrices = { }
+ local positions = { }
+ local nofpositions = 0
+ local nofmatrices = 0
+
+ local 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
+
+ local 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 nodeproperties = nodes.properties.data
+
+ local s_matrix_0 = "1 0 0 1 0 0 cm"
+ local f_matrix_2 = formatters["%.6N 0 0 %.6N 0 0 cm"]
+ local f_matrix_4 = formatters["%.6N %.6N %.6N %.6N 0 0 cm"]
+
+ local flushsetmatrix = function(current,pos_h,pos_v)
+ local p = nodeproperties[current]
+ if p then
+ local m = p.matrix
+ if m then
+ local rx, sx, sy, ry = unpack(m)
+ local s
+ if not rx then
+ rx = 1
+ elseif rx == 0 then
+ rx = 0.0001
+ end
+ if not ry then
+ ry = 1
+ elseif ry == 0 then
+ ry = 0.0001
+ end
+ if not sx then
+ sx = 0
+ end
+ if not sy then
+ sy = 0
+ end
+ --
+ if sx == 0 and sy == 0 then
+ if rx == 1 and ry == 1 then
+ s = s_matrix_0
+ else
+ s = f_matrix_2(rx,ry)
+ end
+ else
+ s = f_matrix_4(rx,sx,sy,ry)
+ end
+ --
+ if shippingmode == "page" then
+ 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
+ --
+ pdf_goto_pagemode()
+ pdf_set_pos(pos_h,pos_v)
+ --
+ b = b + 1
+ buffer[b] = s
+ end
+ end
+ end
+
+ flushers.setmatrix = flushsetmatrix
+ flushers.save = flushsave
+ flushers.restore = flushrestore
+
+ function lpdf.hasmatrix()
+ return nofmatrices > 0
+ end
+
+ function lpdf.getmatrix()
+ if nofmatrices > 0 then
+ return unpack(matrices[nofmatrices])
+ else
+ return 1, 0, 0, 1, 0, 0
+ end
+ end
+
+ flushers.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
+
+ flushers.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
+
+ --
+
+ flushers.startmatrix = function(current,pos_h,pos_v)
+ flushsave(current,pos_h,pos_v)
+ flushsetmatrix(current,pos_h,pos_v)
+ end
+
+ flushers.stopmatrix = function(current,pos_h,pos_v)
+ flushrestore(current,pos_h,pos_v)
+ end
+
+ flushers.startscaling = function(current,pos_h,pos_v)
+ flushsave(current,pos_h,pos_v)
+ flushsetmatrix(current,pos_h,pos_v)
+ end
+
+ flushers.stopscaling = function(current,pos_h,pos_v)
+ flushrestore(current,pos_h,pos_v)
+ end
+
+ flushers.startrotation = function(current,pos_h,pos_v)
+ flushsave(current,pos_h,pos_v)
+ flushsetmatrix(current,pos_h,pos_v)
+ end
+
+ flushers.stoprotation = function(current,pos_h,pos_v)
+ flushrestore(current,pos_h,pos_v)
+ end
+
+ flushers.startmirroring = function(current,pos_h,pos_v)
+ flushsave(current,pos_h,pos_v)
+ flushsetmatrix(current,pos_h,pos_v)
+ end
+
+ flushers.stopmirroring = function(current,pos_h,pos_v)
+ flushrestore(current,pos_h,pos_v)
+ end
+
+ flushers.startclipping = function(current,pos_h,pos_v)
+ flushsave(current,pos_h,pos_v)
+ -- lpdf.print("origin",formatters["0 w %s W n"](nodeproperties[current].path))
+ pdf_goto_pagemode()
+ b = b + 1 ; buffer[b] = formatters["0 w %s W n"](nodeproperties[current].path)
+ end
+
+ flushers.stopclipping = function(current,pos_h,pos_v)
+ flushrestore(current,pos_h,pos_v)
+ end
+
+end
+
+do
+
+ local nodeproperties = nodes.properties.data
+
+ flushers.setstate = function(current,pos_h,pos_v)
+ local p = nodeproperties[current]
+ if p then
+ local d = p.data
+ if d and d ~= "" then
+ pdf_goto_pagemode()
+ b = b + 1 ; buffer[b] = d
+ end
+ end
+ end
+
+end
+
+-- rules
+
+local flushedxforms = { } -- actually box resources but can also be direct
+local localconverter = nil -- will be set
+
+local flushimage do
+
+ local rulecodes = nodes.rulecodes
+ local newrule = nodes.pool.rule
+
+ local setprop = nuts.setprop
+ local getprop = nuts.getprop
+
+ local getwhd = nuts.getwhd
+ local flushlist = nuts.flush_list
+ local getdata = nuts.getdata
+
+ 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 processrule = nodes.rules.process
+
+ 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_x = formatters["[] 0 d 0 J %.6N w %.6N %.6N %.6N %.6N re S"]
+ local f_y = formatters["[] 0 d 0 J %.6N w %.6N %.6N %.6N %.6N re S %.6N 0 m %.6N 0 l 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
+
+ lpdf.getxformname = getxformname
+
+ 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()
+ updaters.register("backend.update.lpdf",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
+ local img_none = imagetypes.none
+ local img_pdf = imagetypes.pdf
+ local img_stream = imagetypes.stream
+
+ local one_bp = 65536 * bpfactor
+
+ local imageresources, n = { }, 0
+
+ getximagename = function(index) -- not used
+ local l = imageresources[index]
+ if l then
+ return l.name
+ else
+ report("no image resource %S",index)
+ end
+ 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
+
+ local 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
+
+ flushers.group = flushgroup
+ lpdf.flushgroup = flushgroup -- todo: access via driver in mlib-pps
+
+ -- end of experiment
+
+ local pdfincludeimage
+
+ updaters.register("backend.update.lpdf",function()
+ pdfincludeimage = lpdf.includeimage
+ end)
+
+ 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 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
+
+ flushers.image = flushimage
+
+ -- 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.
+
+ flushers.rule = 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)
+ processrule(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
+
+ flushers.simplerule = 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
+
+ flushers.specialrule = function(pos_h,pos_v,pos_r,width,height,depth,line,outline,baseline)
+ 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
+ local d = -depth + half
+ local w = width - line
+ local t = total - line
+ if baseline then
+ rule = f_y(line,half,d,w,t,half,w)
+ else
+ rule = f_x(line,half,d,w,t)
+ end
+ 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(pdfgetfontobjectnumber(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(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 bbox = pdfarray {
+ boundingbox[1] * bpfactor,
+ boundingbox[2] * bpfactor,
+ boundingbox[3] * bpfactor,
+ boundingbox[4] * bpfactor,
+ }
+
+ 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)
+
+ local TrimBox = pageattributes.TrimBox
+ local CropBox = pageattributes.CropBox
+ local BleedBox = pageattributes.BleedBox
+
+ -- Indirect objects don't work in all viewers.
+
+ if TrimBox then pageattributes.TrimBox = pdfsharedobject(TrimBox ) end
+ if CropBox then pageattributes.CropBox = pdfsharedobject(CropBox ) end
+ if BleedBox then pageattributes.BleedBox = pdfsharedobject(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,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",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
+
+ -- Versions can be set but normally are managed by the official standards. When possible
+ -- reading and writing should look at these values.
+
+ function lpdf.setversion(major,minor)
+ majorversion = tonumber(major) or majorversion
+ minorversion = tonumber(minor) or minorversion
+ end
+
+ function lpdf.getversion(major,minor)
+ return majorversion, minorversion
+ end
+
+ function lpdf.majorversion() return majorversion end
+ function lpdf.minorversion() return minorversion end
+
+ -- It makes no sense to support levels so we only enable and disable and stick to level 3
+ -- which is both fast and efficient.
+
+ local frozen = false
+ local clevel = 3
+ local olevel = 1
+
+ function lpdf.setcompression(level,objectlevel,freeze)
+ if not frozen then
+ compress = level and level ~= 0 and true or false
+ objectstream = objectlevel and objectlevel ~= 0 and true or false
+ frozen = freeze
+ end
+ end
+
+ function lpdf.getcompression()
+ return compress and olevel or 0, objectstream and clevel or 0
+ end
+
+ function lpdf.compresslevel()
+ return compress and olevel or 0
+ end
+
+ function lpdf.objectcompresslevel()
+ return objectstream and clevel or 0
+ end
+
+ if environment.arguments.nocompression then
+ lpdf.setcompression(0,0,true)
+ 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
+
+do
+
+ local names = { }
+ local cache = { }
+ local nofpages = 0
+
+ local texgetcount = tex.getcount
+
+ function lpdf.reserveobject(name)
+ nofobjects = nofobjects + 1
+ objects[nofobjects] = false
+ if name then
+ names[name] = nofobjects
+ if trace_objects then
+ report_objects("reserving number %a under name %a",nofobjects,name)
+ end
+ elseif trace_objects then
+ report_objects("reserving number %a",nofobjects)
+ end
+ return nofobjects
+ end
+
+ function lpdf.pagereference(n,complete) -- true | false | nil | n [true,false]
+ if nofpages == 0 then
+ nofpages = structures.pages.nofpages
+ if nofpages == 0 then
+ nofpages = 1
+ end
+ end
+ if n == true or not n then
+ complete = n
+ n = texgetcount("realpageno")
+ end
+ local r = n > nofpages and pdfgetpagereference(nofpages) or pdfgetpagereference(n)
+ return complete and pdfreference(r) or r
+ end
+
+ function lpdf.nofpages()
+ return structures.pages.nofpages
+ end
+
+ function lpdf.object(...)
+ pdfdeferredobject(...)
+ end
+
+ function lpdf.delayedobject(data,n)
+ if n then
+ pdfdeferredobject(n,data)
+ else
+ n = pdfdeferredobject(data)
+ end
+-- pdfreferenceobject(n)
+ return n
+ end
+
+ function lpdf.flushobject(name,data)
+ if data then
+ local named = names[name]
+ if named then
+ if not trace_objects then
+ elseif trace_details then
+ report_objects("flushing data to reserved object with name %a, data: %S",name,data)
+ else
+ report_objects("flushing data to reserved object with name %a",name)
+ end
+ return pdfimmediateobject(named,tostring(data))
+ else
+ if not trace_objects then
+ elseif trace_details then
+ report_objects("flushing data to reserved object with number %s, data: %S",name,data)
+ else
+ report_objects("flushing data to reserved object with number %s",name)
+ end
+ return pdfimmediateobject(name,tostring(data))
+ end
+ else
+ if trace_objects and trace_details then
+ report_objects("flushing data: %S",name)
+ end
+ return pdfimmediateobject(tostring(name))
+ end
+ end
+
+ function lpdf.flushstreamobject(data,dict,compressed,objnum) -- default compressed
+ if trace_objects then
+ report_objects("flushing stream object of %s bytes",#data)
+ end
+ local dtype = type(dict)
+ local kind = compressed == "raw" and "raw" or "stream"
+ local nolength = nil
+ if compressed == "raw" then
+ compressed = nil
+ nolength = true
+ -- data = string.formatters["<< %s >>stream\n%s\nendstream"](attr,data)
+ end
+ return pdfdeferredobject {
+ objnum = objnum,
+ immediate = true,
+ nolength = nolength,
+ compresslevel = compressed == false and 0 or nil,
+ type = "stream",
+ string = data,
+ attr = (dtype == "string" and dict) or (dtype == "table" and dict()) or nil,
+ }
+ end
+
+ function lpdf.flushstreamfileobject(filename,dict,compressed,objnum) -- default compressed
+ if trace_objects then
+ report_objects("flushing stream file object %a",filename)
+ end
+ local dtype = type(dict)
+ return pdfdeferredobject {
+ objnum = objnum,
+ immediate = true,
+ compresslevel = compressed == false and 0 or nil,
+ type = "stream",
+ file = filename,
+ attr = (dtype == "string" and dict) or (dtype == "table" and dict()) or nil,
+ }
+ end
+
+ local shareobjectcache, shareobjectreferencecache = { }, { }
+
+ function lpdf.shareobject(content)
+ if content == nil then
+ -- invalid object not created
+ else
+ content = tostring(content)
+ local o = shareobjectcache[content]
+ if not o then
+ o = pdfimmediateobject(content)
+ shareobjectcache[content] = o
+ end
+ return o
+ end
+ end
+
+ function lpdf.shareobjectreference(content)
+ if content == nil then
+ -- invalid object not created
+ else
+ content = tostring(content)
+ local r = shareobjectreferencecache[content]
+ if not r then
+ local o = shareobjectcache[content]
+ if not o then
+ o = pdfimmediateobject(content)
+ shareobjectcache[content] = o
+ end
+ r = pdfreference(o)
+ shareobjectreferencecache[content] = r
+ end
+ return r
+ end
+ end
+
+end
+
+local pages = table.setmetatableindex(function(t,k)
+ local v = pdfreserveobject()
+ t[k] = v
+ return v
+end)
+
+function lpdf.getpagereference(n)
+ return pages[n]
+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
+
+function lpdf.immediateobject(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
+
+lpdf.deferredobject = lpdf.immediateobject
+
+-- 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 <space><lf> 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_tag = 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 update = 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
+ -- offset = offset + #s
+ end
+ close = function(f)
+ f = concat(f)
+ io.savedata(filename,f)
+ f = false
+ end
+ update = function(f,s)
+ f[1] = s
+ 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
+ update = function(f,s)
+ f:seek("set",0)
+ f:write(s)
+ end
+ end
+ local v = f_pdf_tag(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 = offset + #v + #b
+ end
+
+ closefile = function(abort)
+ if abort then
+ close(f)
+ 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 = md5.HEX(osuuid())
+ elseif trailerid and #trailerid > 32 then
+ trailerid = md5.HEX(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
+ update(f,f_pdf_tag(majorversion,minorversion))
+ close(f)
+ 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.
+
+do -- 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
+ 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 lpdf.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 pdfreserveobject()
+ 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 -- )
+
+do -- 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
+ local newpdf
+ local openpdf
+ local closepdf
+ local copypage
+
+
+ updaters.register("backend.update.lpdf",function()
+ pdfimage = lpdf.epdf.image
+ newpdf = pdfimage.new
+ openpdf = pdfimage.open
+ closepdf = pdfimage.close
+ copypage = pdfimage.copy
+ end)
+
+ 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
+ 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
+
+ function lpdf.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
+
+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")
+ --
+ 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")
+ --
+ statistics.register("result saved in file", function()
+ local outputfilename = environment.outputfilename or environment.jobname or tex.jobname or "<unset>"
+ return string.format("%s.%s, compresslevel %s, objectcompresslevel %s",outputfilename,"pdf",lpdf.getcompression())
+ end)
+ --
+ end
+ 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 = flushers,
+ actions = {
+ prepare = prepare,
+ wrapup = wrapup,
+ cleanup = cleanup,
+ --
+ initialize = initialize,
+ convert = convert,
+ finalize = finalize,
+ --
+ outputfilename = outputfilename,
+ },
+ }
+
+end