2019-07-17 21:14:34 +02:00
local format = string.format
local concat = table.concat
local write = texio.write_nl
2020-01-02 04:14:39 +01:00
local direct = node.direct
2020-05-31 09:30:49 +02:00
local properties = direct.properties
2020-01-02 04:14:39 +01:00
local tonode = direct.tonode
local todirect = direct.todirect
local getid = direct.getid
local traverse = direct.traverse
local getsubtype = direct.getsubtype
2020-05-31 09:30:49 +02:00
local getdirection = direct.getdirection
2020-01-02 04:14:39 +01:00
local setsubtype = direct.setsubtype
local getdepth = direct.getdepth
local getheight = direct.getheight
local getwidth = direct.getwidth
local setdepth = direct.setdepth
local setheight = direct.setheight
local setwidth = direct.setwidth
local getshift = direct.getshift
local getlist = direct.getlist
local getkern = direct.getkern
local getreplace = direct.getreplace
local getleader = direct.getleader
local setfont = direct.setfont
local getfont = direct.getfont
local getoffsets = direct.getoffsets
local getnext = direct.getnext
local getexpansion = direct.getexpansion
local getchar = direct.getchar
local rangedimensions = direct.rangedimensions
2020-05-31 09:30:49 +02:00
local traverse_id = direct.traverse_id
2020-06-11 01:16:42 +02:00
local getdata = direct.getdata
2020-07-27 15:27:42 +02:00
local getorientation = direct.getorientation
2020-05-31 09:30:49 +02:00
2020-07-06 15:31:15 +02:00
local utils = require ' luametalatex-pdf-utils '
local strip_floats = utils.strip_floats
local to_bp = utils.to_bp
2020-07-08 22:20:08 +02:00
local make_resources = require ' luametalatex-pdf-resources '
2020-07-06 13:30:03 +02:00
local pdf_font_map = require ' luametalatex-pdf-font-deduplicate '
2020-07-01 19:47:25 +02:00
local get_whatsit_handler = require ' luametalatex-whatsits ' . handler
2020-07-27 15:27:42 +02:00
local write_matrix -- Defined later
2020-05-31 09:30:49 +02:00
local dir_id = node.id ' dir '
2021-11-06 11:38:39 +01:00
local glue_id = node.id ' glue '
2020-05-31 09:30:49 +02:00
2019-07-17 21:14:34 +02:00
local function doublekeyed ( t , id2name , name2id , index )
return setmetatable ( t , {
__index = index ,
__newindex = function ( t , k , v )
rawset ( t , k , v )
if type ( k ) == ' string ' then
rawset ( t , name2id ( k ) , v )
else
rawset ( t , id2name ( k ) , v )
end
end ,
} )
end
local nodehandler = ( function ( )
local function unknown_handler ( _ , n , x , y )
2020-05-31 09:30:49 +02:00
write ( format ( " Sorry, but the PDF backend does not support %q (id = %i) nodes right now. The supplied node will be dropped at coordinates (%i, %i). " , node.type ( getid ( n ) ) , getid ( n ) , x // 1 , y // 1 ) )
2019-07-17 21:14:34 +02:00
end
return doublekeyed ( { } , node.type , node.id , function ( )
return unknown_handler
end )
end ) ( )
local whatsithandler = ( function ( )
local whatsits = node.whatsits ( )
local function unknown_handler ( p , n , x , y , ... )
2020-01-02 04:14:39 +01:00
local prop = properties [ n ] -- or node.getproperty(n)
2019-07-17 21:14:34 +02:00
if prop and prop.handle then
prop : handle ( p , n , x , y , ... )
else
2020-05-31 09:30:49 +02:00
write ( format ( " Sorry, but the PDF backend does not support %q (id = %i) whatsits right now. The supplied node will be dropped at coordinates (%i, %i). " , whatsits [ getsubtype ( n ) ] , getsubtype ( n ) , x // 1 , y // 1 ) )
2019-07-17 21:14:34 +02:00
end
end
return doublekeyed ( { } , function ( n ) return whatsits [ n ] end , function ( n ) return whatsits [ n ] end , function ( )
return unknown_handler
end )
end ) ( )
local glyph , text , page , cm_pending = 1 , 2 , 3 , 4
2020-07-06 15:31:15 +02:00
2019-07-17 21:14:34 +02:00
local function projected_point ( m , x , y , w )
w = w or 1
return x * m [ 1 ] + y * m [ 3 ] + w * m [ 5 ] , x * m [ 2 ] + y * m [ 4 ] + w * m [ 6 ]
end
2020-06-29 19:04:09 +02:00
local fontnames = setmetatable ( { } , { __index = function ( t , k ) local res = format ( " F%i " , k ) t [ k ] = res return res end } )
2019-07-17 21:14:34 +02:00
local topage
local function totext ( p , fid )
local last = p.mode
if last == glyph then
p.pending [ # p.pending + 1 ] = " )]TJ "
p.strings [ # p.strings + 1 ] = concat ( p.pending )
for i = 1 , # p.pending do p.pending [ i ] = nil end
last = text
end
if last == cm_pending then topage ( p ) end
p.mode = text
if last == text and p.font . fid == fid then return end
local f = font.getfont ( fid ) or font.fonts [ fid ]
2020-06-19 18:58:01 +02:00
if last ~= text then p.strings [ # p.strings + 1 ] = " BT " p.pos . lx , p.pos . ly , p.pos . x , p.pos . y , p.font . exfactor , p.font . extend , p.font . squeeze , p.font . slant = 0 , 0 , 0 , 0 , 0 , 1 , 1 , 0 end
2020-06-29 19:04:09 +02:00
2020-07-06 13:30:03 +02:00
local pdf_fid = pdf_font_map [ fid ]
p.resources . Font [ fontnames [ pdf_fid ] ] = p.fontdirs [ pdf_fid ]
2020-07-06 15:31:15 +02:00
p.strings [ # p.strings + 1 ] = strip_floats ( format ( " /F%i %f Tf 0 Tr " , pdf_fid , to_bp ( f.size ) ) ) -- TODO: Setting the mode, width, etc.
2020-07-06 13:30:03 +02:00
p.font . usedglyphs = p.usedglyphs [ pdf_fid ]
2020-06-29 19:04:09 +02:00
2019-07-17 21:14:34 +02:00
p.font . fid = fid
p.font . font = f
2020-06-19 18:58:01 +02:00
local need_tm = false
if p.font . extend ~= ( f.extend or 1000 ) / 1000 then
p.font . extend = ( f.extend or 1000 ) / 1000
need_tm = true
end
if p.font . squeeze ~= ( f.squeeze or 1000 ) / 1000 then
p.font . squeeze = ( f.squeeze or 1000 ) / 1000
need_tm = true
end
if p.font . slant ~= ( f.slant or 0 ) / 1000 then
p.font . slant = ( f.slant or 0 ) / 1000
need_tm = true
end
return need_tm
2019-07-17 21:14:34 +02:00
end
2019-07-20 14:53:24 +02:00
function topage ( p )
2019-07-17 21:14:34 +02:00
local last = p.mode
if last == page then return end
if last <= text then
totext ( p , p.font . fid ) -- First make sure we are really in text mode
p.strings [ # p.strings + 1 ] = " ET "
elseif last == cm_pending then
local pending = p.pending_matrix
if pending [ 1 ] ~= 1 or pending [ 2 ] ~= 0 or pending [ 3 ] ~= 0 or pending [ 4 ] ~= 1 or pending [ 5 ] ~= 0 or pending [ 6 ] ~= 0 then
2020-07-06 15:31:15 +02:00
p.strings [ # p.strings + 1 ] = strip_floats ( format ( " %f %f %f %f %f %f cm " , pending [ 1 ] , pending [ 2 ] , pending [ 3 ] , pending [ 4 ] , to_bp ( pending [ 5 ] ) , to_bp ( pending [ 6 ] ) ) )
2019-07-17 21:14:34 +02:00
end
else
error [[Unknown mode]]
end
p.mode = page
end
local function toglyph ( p , fid , x , y , exfactor )
local last = p.mode
if last == glyph and p.font . fid == fid and p.pos . y == y and p.font . exfactor == exfactor then
if x == p.pos . x then return end
2020-06-19 18:58:01 +02:00
local xoffset = ( x - p.pos . x ) / p.font . font.size * 1000 / p.font . extend / ( 1 + exfactor / 1000000 )
2019-07-17 21:14:34 +02:00
if math.abs ( xoffset ) < 1000000 then -- 1000000 is arbitrary
p.pending [ # p.pending + 1 ] = format ( " )%i( " , math.floor ( - xoffset ) )
p.pos . x = x
return
end
end
if totext ( p , fid ) or exfactor ~= p.font . exfactor then
p.font . exfactor = exfactor
2020-07-06 15:31:15 +02:00
p.strings [ # p.strings + 1 ] = strip_floats ( format ( " %f 0.0 %f %f %f %f Tm " , p.font . extend * ( 1 + exfactor / 1000000 ) , p.font . slant , p.font . squeeze , to_bp ( x ) , to_bp ( y ) ) )
2019-07-17 21:14:34 +02:00
else
2020-06-19 18:58:01 +02:00
-- To invert the text transformation matrix (extend 0 0;slant squeeze 0;0 0 1)
-- we have to apply (extend^-1 0 0;-slant*extend^-1*squeeze^-1 squeeze^-1 0;0 0 1). (extend has to include expansion)
-- We optimize slightly by separating some steps
2020-07-06 15:31:15 +02:00
local dx , dy = to_bp ( ( x - p.pos . lx ) ) , to_bp ( y - p.pos . ly ) / p.font . squeeze
2020-06-19 18:58:01 +02:00
dx = ( dx - p.font . slant * dy ) / ( p.font . extend * ( 1 + exfactor / 1000000 ) )
2020-07-06 15:31:15 +02:00
p.strings [ # p.strings + 1 ] = strip_floats ( format ( " %f %f Td " , dx , dy ) )
2019-07-17 21:14:34 +02:00
end
p.pos . lx , p.pos . ly , p.pos . x , p.pos . y = x , y , x , y
p.mode = glyph
p.pending [ 1 ] = " [( "
end
2020-07-27 15:27:42 +02:00
local function boxrotation ( p , list , x , y )
local orientation , xoff , yoff , woff , hoff , doff = getorientation ( list )
2020-07-27 15:54:19 +02:00
if not orientation then return x , y , direct.getwhd ( list ) end
2020-07-27 15:27:42 +02:00
x , y = x + xoff , y + yoff
local baseorientation = orientation & 0xF
local v_anchor = ( orientation & 0xF0 ) >> 4
local h_anchor = ( orientation & 0xF00 ) >> 8
-- assert(baseorientation & 8 == 0)
if baseorientation & 4 == 4 then
-- assert(baseorientation < 6)
baseorientation , v_anchor = 0 , baseorientation - 3
end
if baseorientation & 1 == 0 then -- horizontal
if h_anchor == 0 then
elseif h_anchor == 1 then
x = x - woff
elseif h_anchor == 2 then
x = x + woff
elseif h_anchor == 3 then
x = x - woff // 2
elseif h_anchor == 4 then
x = x + woff // 2
-- else assert(false)
end
local flipped = baseorientation ~= 0
if v_anchor ~= 0 then
local h , d = hoff , doff
if flipped then h , d = d , h end
if v_anchor == 1 then
y = y + d
elseif v_anchor == 2 then
y = y - h
elseif v_anchor == 3 then
y = y + ( d - h ) // 2
-- else assert(false)
end
end
if flipped then
write_matrix ( - 1 , 0 , 0 , - 1 , 2 * x , 2 * y , p )
2020-07-27 15:54:19 +02:00
x , y = x - woff , y
2020-07-27 15:27:42 +02:00
end
else -- vertical
if v_anchor == 0 then
elseif v_anchor == 1 then
y = y + woff
elseif v_anchor == 2 then
y = y - woff
elseif v_anchor == 3 then
y = y - woff // 2
-- else assert(false)
end
local flipped = baseorientation ~= 1
if h_anchor ~= 0 then
local h , d = hoff , doff
if flipped then h , d = d , h end
if h_anchor == 1 then
x = x - h - d
elseif h_anchor == 2 then
x = x + h + d
elseif h_anchor == 3 then
x = x - ( d + h ) // 2
elseif h_anchor == 4 then
x = x + ( d + h ) // 2
elseif h_anchor == 5 then
x = x - d
elseif h_anchor == 6 then
x = x + h
-- else assert(false)
end
end
if flipped then
write_matrix ( 0 , 1 , - 1 , 0 , x + y , y - x , p )
2020-07-27 15:54:19 +02:00
x , y = x , y - hoff
2020-07-27 15:27:42 +02:00
else
write_matrix ( 0 , - 1 , 1 , 0 , x - y , x + y , p )
2020-07-27 15:54:19 +02:00
x , y = x - woff , y + doff
2020-07-27 15:27:42 +02:00
end
end
2020-07-27 15:54:19 +02:00
return x , y , woff , hoff , doff
2020-07-27 15:27:42 +02:00
end
local function endboxrotation ( p , list , x , y )
local orientation , xoff , yoff , woff , hoff , doff = getorientation ( list )
if not orientation then return end
local orientation = orientation & 0xF
-- assert(orientation & 8 == 0)
if orientation & 4 == 4 or orientation == 0 then
-- write_matrix(1, 0, 0, 1, 0, 0, p)
elseif orientation == 1 then
x , y = x + woff , y - doff
write_matrix ( 0 , 1 , - 1 , 0 , x + y , y - x , p )
elseif orientation == 2 then
x = x + woff
write_matrix ( - 1 , 0 , 0 , - 1 , 2 * x , 2 * y , p )
elseif orientation == 3 then
y = y + hoff
write_matrix ( 0 , - 1 , 1 , 0 , x - y , x + y , p )
end
end
2020-06-17 18:16:46 +02:00
-- Let's start with "handlers" for nodes which do not need any special handling:
local function ignore_node ( ) end
-- The following are already handled by the list handler because they only correspond to blank space:
nodehandler.math = ignore_node
nodehandler.kern = ignore_node
-- The following are only for frontend use:
nodehandler.boundary = ignore_node
2021-04-16 10:15:58 +02:00
nodehandler.par = ignore_node
2020-06-17 18:16:46 +02:00
nodehandler.penalty = ignore_node
nodehandler.mark = ignore_node
-- Now we come to more interesting nodes:
2019-07-17 21:14:34 +02:00
function nodehandler . hlist ( p , list , x0 , y , outerlist , origin , level )
if outerlist then
2020-01-02 04:14:39 +01:00
if getid ( outerlist ) == 0 then
y = y - getshift ( list )
2019-07-17 21:14:34 +02:00
else
2020-01-02 04:14:39 +01:00
x0 = x0 + getshift ( list )
2019-07-17 21:14:34 +02:00
end
end
2020-07-27 15:27:42 +02:00
local width
x0 , y , width = boxrotation ( p , list , x0 , y )
local x = x0
2020-05-31 09:30:49 +02:00
local direction = getdirection ( list )
if direction == 1 then
2020-07-27 15:27:42 +02:00
x = x + width
2020-05-31 09:30:49 +02:00
end
local dirstack = { }
local dirnodes = { }
for n , sub in traverse_id ( dir_id , getlist ( list ) ) do
if sub == 0 then
dirstack [ # dirstack + 1 ] = n
else
local m = dirstack [ # dirstack ]
dirnodes [ m ] = n
dirstack [ # dirstack ] = nil
end
end
for i = 1 , # dirstack do
dirnodes [ dirstack [ i ] ] = rangedimensions ( list , dirstack [ i ] )
end
2020-06-05 04:12:29 +02:00
local linkcontext = p.linkcontext
if linkcontext then
linkcontext : set ( p , x , y , list , level + 1 , ' start ' )
end
2020-05-31 09:30:49 +02:00
for n , id , sub in traverse ( getlist ( list ) ) do
if id == dir_id then
if sub == 0 then
local newdir = getdirection ( n )
if newdir ~= direction then
local close = dirnodes [ n ]
local dim = rangedimensions ( list , n , close )
if close then dirnodes [ close ] = dim end
x = x + ( 2 * newdir - 1 ) * dim
direction = newdir
end
else
local dim = dirnodes [ n ]
if dim then
2020-06-02 01:22:59 +02:00
x = x + ( 2 * direction - 1 ) * dim
2020-05-31 09:30:49 +02:00
direction = 1 - direction
end
end
else
local next = getnext ( n )
local w = next and rangedimensions ( list , n , next ) or rangedimensions ( list , n )
if direction == 1 then x = x - w end
2020-07-13 23:19:11 +02:00
nodehandler [ id ] ( p , n , x , y , list , x0 , level + 1 , direction )
2020-05-31 09:30:49 +02:00
if direction == 0 then x = w + x end
end
2019-07-17 21:14:34 +02:00
end
2020-06-05 04:12:29 +02:00
linkcontext = p.linkcontext
if linkcontext then
linkcontext : set ( p , x , y , list , level + 1 , ' end ' )
end
2020-07-27 15:27:42 +02:00
endboxrotation ( p , list , x0 , y )
2019-07-17 21:14:34 +02:00
end
function nodehandler . vlist ( p , list , x , y0 , outerlist , origin , level )
if outerlist then
2020-01-02 04:14:39 +01:00
if getid ( outerlist ) == 0 then
y0 = y0 - getshift ( list )
2019-07-17 21:14:34 +02:00
else
2020-01-02 04:14:39 +01:00
x = x + getshift ( list )
2019-07-17 21:14:34 +02:00
end
end
2020-07-27 15:54:19 +02:00
local width , height
x , y0 , width , height = boxrotation ( p , list , x , y0 )
2019-07-17 21:14:34 +02:00
local y = y0
2020-07-27 15:54:19 +02:00
y = y + height
2020-01-02 04:14:39 +01:00
for n in traverse ( getlist ( list ) ) do
2021-11-06 11:38:39 +01:00
local d , h , _ = 0 , getid ( n ) == glue_id and direct.effective_glue ( n , list ) or math.tointeger ( getkern ( n ) )
2019-07-17 21:14:34 +02:00
if not h then
2020-01-02 04:14:39 +01:00
_ , h , d = direct.getwhd ( n )
2019-07-17 21:14:34 +02:00
end
y = y - ( h or 0 )
2020-07-09 17:32:42 +02:00
nodehandler [ getid ( n ) ] ( p , n , x , ( y + .5 ) // 1 , list , y0 , level + 1 )
2019-07-17 21:14:34 +02:00
y = y - ( d or 0 )
end
2020-07-27 15:54:19 +02:00
endboxrotation ( p , list , x , y0 )
2019-07-17 21:14:34 +02:00
end
2020-06-11 01:16:42 +02:00
do
local rulesubtypes = { }
for i , n in next , node.subtypes ' rule ' do
rulesubtypes [ n ] = i
end
local box_rule = rulesubtypes.box
local image_rule = rulesubtypes.image
local user_rule = rulesubtypes.user
local empty_rule = rulesubtypes.empty
local outline_rule = rulesubtypes.outline
local ship_img = require ' luametalatex-pdf-image ' . ship
2020-06-29 19:04:09 +02:00
local ship_box = require ' luametalatex-pdf-savedbox ' . ship
2020-06-23 18:08:14 +02:00
-- print(require'inspect'(node.subtypes('glue')))
-- print(require'inspect'(node.fields('glue')))
-- print(require'inspect'(node.fields('rule')))
-- print(require'inspect'(node.fields('whatsit')))
2019-07-17 21:14:34 +02:00
function nodehandler . rule ( p , n , x , y , outer )
2020-01-02 04:14:39 +01:00
if getwidth ( n ) == - 1073741824 then setwidth ( n , getwidth ( outer ) ) end
if getheight ( n ) == - 1073741824 then setheight ( n , getheight ( outer ) ) end
if getdepth ( n ) == - 1073741824 then setdepth ( n , getdepth ( outer ) ) end
local sub = getsubtype ( n )
2020-06-29 19:04:09 +02:00
if getwidth ( n ) <= 0 or getdepth ( n ) + getheight ( n ) <= 0 then return end
2020-06-11 01:16:42 +02:00
if sub == box_rule then
2020-06-29 19:04:09 +02:00
ship_box ( getdata ( n ) , p , n , x , y )
2020-06-11 01:16:42 +02:00
elseif sub == image_rule then
ship_img ( getdata ( n ) , p , n , x , y )
elseif sub == empty_rule then
elseif sub == user_rule then
2019-07-17 21:14:34 +02:00
error [[We can't handle user rules yet]]
2020-06-11 01:16:42 +02:00
elseif sub == outline_rule then
2019-07-17 21:14:34 +02:00
error [[We can't handle outline rules yet]]
else
topage ( p )
2020-07-06 15:31:15 +02:00
p.strings [ # p.strings + 1 ] = strip_floats ( format ( " %f %f %f %f re f " , to_bp ( x ) , to_bp ( y - getdepth ( n ) ) , to_bp ( getwidth ( n ) ) , to_bp ( getdepth ( n ) + getheight ( n ) ) ) )
2019-07-17 21:14:34 +02:00
end
end
2020-06-11 01:16:42 +02:00
end
2020-07-31 12:04:02 +02:00
-- If we encounter disc here, we can just use .replace. We could make TeX drop these using node.flatten_discretionaries,
-- but for now we just accept them. This approach might be a bit faster, but it leads to a few issue due to directions etc.
-- so it might change soon(ish) TODO: Review
function nodehandler . disc ( p , n , x , y , list , ... )
2020-01-02 04:14:39 +01:00
for n in traverse ( getreplace ( n ) ) do
local next = getnext ( n )
local w = next and rangedimensions ( list , n , next ) or rangedimensions ( list , n )
nodehandler [ getid ( n ) ] ( p , n , x , y , list , ... )
2019-07-17 21:14:34 +02:00
x = w + x
end
end
2020-08-19 09:25:07 +02:00
local gluesubtypes = { }
for i , n in next , node.subtypes ' glue ' do
gluesubtypes [ n ] = i
end
local leaders_glue = gluesubtypes.leaders
local cleaders_glue = gluesubtypes.cleaders
local xleaders_glue = gluesubtypes.xleaders
local gleaders_glue = gluesubtypes.gleaders
2019-07-17 21:14:34 +02:00
function nodehandler . glue ( p , n , x , y , outer , origin , level ) -- Naturally this is an interesting one.
2020-01-02 04:14:39 +01:00
local subtype = getsubtype ( n )
2020-08-19 09:25:07 +02:00
if subtype < leaders_glue then return end -- We only really care about leaders
2020-01-02 04:14:39 +01:00
local leader = getleader ( n )
local w = direct.effective_glue ( n , outer )
if getid ( leader ) == 2 then -- We got a rule, this should be easy
if getid ( outer ) == 0 then
setwidth ( leader , w )
2019-07-17 21:14:34 +02:00
else
2020-01-02 04:14:39 +01:00
setheight ( leader , w )
setdepth ( leader , 0 )
2019-07-17 21:14:34 +02:00
end
return nodehandler.rule ( p , leader , x , y , outer )
end
2020-01-02 04:14:39 +01:00
local lwidth = getid ( outer ) == 0 and getwidth ( leader ) or getheight ( leader ) + getdepth ( leader )
if getid ( outer ) ~= 0 then
2019-07-17 21:14:34 +02:00
y = y + w
end
2020-08-19 09:25:07 +02:00
if subtype == leaders_glue then
2020-01-02 04:14:39 +01:00
if getid ( outer ) == 0 then
2019-07-17 21:14:34 +02:00
local newx = ( ( x - origin - 1 ) // lwidth + 1 ) * lwidth + origin
-- local newx = -(origin-x)//lwidth * lwidth + origin
w = w + x - newx
x = newx
else
-- local newy = -(origin-y)//lwidth * lwidth + origin
local newy = ( y - origin ) // lwidth * lwidth + origin
w = w + newy - y
y = newy
end
2020-08-19 09:25:07 +02:00
elseif subtype == cleaders_glue then
2019-07-17 21:14:34 +02:00
local inner = w - ( w // lwidth ) * lwidth
2020-01-02 04:14:39 +01:00
if getid ( outer ) == 0 then
2019-07-17 21:14:34 +02:00
x = x + inner / 2
else
y = y - inner / 2
end
2020-08-19 09:25:07 +02:00
elseif subtype == xleaders_glue then
2019-07-17 21:14:34 +02:00
local count = w // lwidth
local skip = ( w - count * lwidth ) / ( count + 1 )
2020-01-02 04:14:39 +01:00
if getid ( outer ) == 0 then
2019-07-17 21:14:34 +02:00
x = x + skip
else
y = y - skip
end
lwidth = lwidth + skip
2020-08-19 09:25:07 +02:00
elseif subtype == gleaders_glue then
2020-01-02 04:14:39 +01:00
if getid ( outer ) == 0 then
2019-07-17 21:14:34 +02:00
local newx = ( ( x - 1 ) // lwidth + 1 ) * lwidth
w = w + x - newx
x = newx
else
local newy = y // lwidth * lwidth
w = w + newy - y
y = newy
end
end
2020-01-02 04:14:39 +01:00
local handler = nodehandler [ getid ( leader ) ]
if getid ( outer ) == 0 then
2019-07-17 21:14:34 +02:00
while w >= lwidth do
handler ( p , leader , x , y , outer , origin , level + 1 )
w = w - lwidth
x = x + lwidth
end
else
2020-01-02 04:14:39 +01:00
y = y - getheight ( leader )
2019-07-17 21:14:34 +02:00
while w >= lwidth do
handler ( p , leader , x , y , outer , origin , level + 1 )
w = w - lwidth
y = y - lwidth
end
end
end
2020-06-07 22:42:53 +02:00
2020-07-19 18:27:52 +02:00
local vf_state
2020-06-07 22:42:53 +02:00
local pdf_escape = require ' luametalatex-pdf-escape ' . escape_raw
2019-07-17 21:14:34 +02:00
local match = lpeg.match
2020-07-19 18:27:52 +02:00
local function do_commands ( p , c , f , cid , fid , x , y , outer , x0 , level , direction )
2019-07-17 21:14:34 +02:00
local fonts = f.fonts
2020-07-19 18:27:52 +02:00
local stack , current_font = { } , fonts and fonts [ 1 ] and fonts [ 1 ] . id or fid
2019-07-17 21:14:34 +02:00
for _ , cmd in ipairs ( c.commands ) do
if cmd [ 1 ] == " node " then
local cmd = cmd [ 2 ]
2020-07-08 22:20:08 +02:00
assert ( node.type ( cmd ) )
cmd = todirect ( cmd )
2020-07-19 18:27:52 +02:00
nodehandler [ getid ( cmd ) ] ( p , cmd , x , y , outer , x0 , level , direction )
2020-01-02 04:14:39 +01:00
x = x + getwidth ( cmd )
2019-07-17 21:14:34 +02:00
elseif cmd [ 1 ] == " font " then
2020-07-19 18:27:52 +02:00
current_font = assert ( fonts [ cmd [ 2 ] ] , " invalid font requested " ) . id
2019-07-17 21:14:34 +02:00
elseif cmd [ 1 ] == " char " then
2020-01-02 04:14:39 +01:00
local n = direct.new ' glyph '
setsubtype ( n , 256 )
2020-07-19 18:27:52 +02:00
setfont ( n , current_font , cmd [ 2 ] )
nodehandler.glyph ( p , n , x , y , outer , x0 , level , direction )
2020-01-02 04:14:39 +01:00
x = x + getwidth ( n )
2020-06-18 01:38:20 +02:00
direct.free ( n )
2019-07-17 21:14:34 +02:00
elseif cmd [ 1 ] == " slot " then
2020-07-19 18:27:52 +02:00
current_font = assert ( fonts [ cmd [ 2 ] ] , " invalid font requested " ) . id
2020-01-02 04:14:39 +01:00
local n = direct.new ' glyph '
setsubtype ( n , 256 )
2020-07-19 18:27:52 +02:00
setfont ( n , current_font , cmd [ 3 ] )
nodehandler.glyph ( p , n , x , y , outer , x0 , level , direction )
2020-01-02 04:14:39 +01:00
x = x + getwidth ( n )
2020-07-08 22:20:08 +02:00
direct.free ( n )
2019-07-17 21:14:34 +02:00
elseif cmd [ 1 ] == " rule " then
2020-01-02 04:14:39 +01:00
local n = direct.new ' rule '
2020-07-19 18:27:52 +02:00
setheight ( n , cmd [ 3 ] )
setwidth ( n , cmd [ 2 ] )
nodehandler.rule ( p , n , x , y , outer , x0 , level , direction )
2020-01-02 04:14:39 +01:00
x = x + getwidth ( n )
2020-07-08 22:20:08 +02:00
direct.free ( n )
2020-07-19 18:27:52 +02:00
elseif cmd [ 1 ] == " right " then
2019-07-17 21:14:34 +02:00
x = x + cmd [ 2 ]
elseif cmd [ 1 ] == " down " then
2020-07-31 12:04:02 +02:00
y = y - cmd [ 2 ]
2019-07-17 21:14:34 +02:00
elseif cmd [ 1 ] == " push " then
stack [ # stack + 1 ] = { x , y }
elseif cmd [ 1 ] == " pop " then
local top = stack [ # stack ]
stack [ # stack ] = nil
x , y = top [ 1 ] , top [ 2 ]
elseif cmd [ 1 ] == " special " then
2019-07-20 14:53:24 +02:00
error [[specials aren't supported yet]] -- TODO
2019-07-17 21:14:34 +02:00
elseif cmd [ 1 ] == " pdf " then
2020-07-19 18:27:52 +02:00
local mode , literal = cmd [ 2 ] , cmd [ 3 ]
if not literal then mode , literal = " origin " , mode end
pdf.write ( mode , literal , x , y , p )
2019-07-17 21:14:34 +02:00
elseif cmd [ 1 ] == " lua " then
cmd = cmd [ 2 ]
if type ( cmd ) == " string " then cmd = load ( cmd ) end
assert ( type ( cmd ) == " function " )
2020-07-19 18:27:52 +02:00
local old_vf_state = vf_state -- This can be triggered recursivly in odd cases
vf_state = { p , stack , current_font , x , y , outer , x0 , level , direction }
pdf._latelua ( p , x , y , cmd , fid , cid )
current_font , x , y = vf_state [ 3 ] , vf_state [ 4 ] , vf_state [ 5 ]
vf_state = old_vf_state
2019-07-17 21:14:34 +02:00
elseif cmd [ 1 ] == " image " then
2019-07-20 14:53:24 +02:00
error [[images aren't supported yet]] -- TODO
2019-07-17 21:14:34 +02:00
-- ???
-- else
-- NOP, comment and invalid commands ignored
end
end
end
2020-07-19 18:27:52 +02:00
-- Now we basically duplicate all of that for the `vf` library...
vf = {
char = function ( cid )
local n = direct.new ' glyph '
setsubtype ( n , 256 )
setfont ( n , vf_state [ 3 ] , cid )
local x = vf_state [ 4 ]
nodehandler.glyph ( vf_state [ 1 ] , n , x , vf_state [ 5 ] , vf_state [ 6 ] , vf_state [ 7 ] , vf_state [ 8 ] , vf_state [ 9 ] )
vf_state [ 4 ] = x + getwidth ( n )
direct.free ( n )
end ,
down = function ( dy )
2020-07-31 12:04:02 +02:00
vf_state [ 5 ] = vf_state [ 5 ] - dy
2020-07-19 18:27:52 +02:00
end ,
fontid = function ( fid )
vf_state [ 3 ] = fid
end ,
-- image = function(img) -- TODO
node = function ( n )
assert ( node.type ( n ) )
cmd = todirect ( n )
local x = vf_state [ 4 ]
nodehandler [ getid ( n ) ] ( vf_state [ 1 ] , n , x , vf_state [ 5 ] , vf_state [ 6 ] , vf_state [ 7 ] , vf_state [ 8 ] , vf_state [ 9 ] )
vf_state [ 4 ] = x + getwidth ( n )
end ,
nop = function ( ) end ,
pop = function ( )
local stack = vf_state [ 2 ]
local top = stack [ # stack ]
stack [ # stack ] = nil
vf_state [ 4 ] , vf_state [ 5 ] = top [ 1 ] , top [ 2 ]
end ,
push = function ( )
local stack = vf_state [ 2 ]
stack [ # stack + 1 ] = vf_state [ 4 ] , vf_state [ 5 ]
end ,
right = function ( dx )
vf_state [ 4 ] = vf_state [ 4 ] + dx
end ,
rule = function ( width , height )
local n = direct.new ' rule '
setheight ( n , height )
setwidth ( n , width )
local x = vf_state [ 4 ]
nodehandler.rule ( vf_state [ 1 ] , n , x , vf_state [ 5 ] , vf_state [ 6 ] , vf_state [ 7 ] , vf_state [ 8 ] , vf_state [ 9 ] )
vf_state [ 4 ] = x + getwidth ( n )
direct.free ( n )
end ,
special = function ( )
error [[specials aren't supported yet]] -- TODO
end ,
pdf = function ( mode , literal )
if not literal then mode , literal = " origin " , mode end
pdf.write ( mode , literal , vf_state [ 4 ] , vf_state [ 5 ] , vf_state [ 1 ] )
end ,
}
2020-07-13 23:19:11 +02:00
function nodehandler . glyph ( p , n , x , y , outer , x0 , level , direction )
2020-01-02 04:14:39 +01:00
if getfont ( n ) ~= p.vfont . fid then
p.vfont . fid = getfont ( n )
p.vfont . font = font.getfont ( getfont ( n ) ) or font.fonts [ getfont ( n ) ]
2019-07-17 21:14:34 +02:00
end
local f , fid = p.vfont . font , p.vfont . fid
2020-07-19 18:27:52 +02:00
local cid = getchar ( n )
local c = f.characters [ cid ]
2019-07-17 21:14:34 +02:00
if not c then
2019-07-18 20:02:58 +02:00
texio.write_nl ( " Missing character " )
return
2019-07-17 21:14:34 +02:00
end
2020-07-19 18:27:52 +02:00
if c.commands then return do_commands ( p , c , f , cid , fid , x , y , outer , x0 , level , direction ) end
2020-01-02 04:14:39 +01:00
local xoffset , yoffset = getoffsets ( n )
2020-07-13 23:19:11 +02:00
toglyph ( p , getfont ( n ) , x + ( direction == 1 and - xoffset or xoffset ) , y + yoffset , getexpansion ( n ) )
2019-07-17 21:14:34 +02:00
local index = c.index
if index then
-- if f.encodingbytes == -3 then
2020-01-02 04:14:39 +01:00
if false then
2019-07-17 21:14:34 +02:00
if index < 0x80 then
2020-06-07 22:42:53 +02:00
p.pending [ # p.pending + 1 ] = pdf_escape ( string.pack ( ' >B ' , index ) )
2019-07-17 21:14:34 +02:00
elseif index < 0x7F80 then
2020-06-07 22:42:53 +02:00
p.pending [ # p.pending + 1 ] = pdf_escape ( string.pack ( ' >H ' , index + 0x7F80 ) )
2019-07-17 21:14:34 +02:00
else
2020-06-07 22:42:53 +02:00
p.pending [ # p.pending + 1 ] = pdf_escape ( string.pack ( ' >BH ' , 0xFF , index - 0x7F80 ) )
2019-07-17 21:14:34 +02:00
end
else
2020-06-07 22:42:53 +02:00
p.pending [ # p.pending + 1 ] = pdf_escape ( string.pack ( ' >H ' , index ) )
2019-07-17 21:14:34 +02:00
end
2020-06-29 19:04:09 +02:00
if not p.font . usedglyphs [ index ] then
p.font . usedglyphs [ index ] = { index , math.floor ( c.width * 1000 / f.size / p.font . extend + .5 ) , c.tounicode }
2019-07-17 21:14:34 +02:00
end
else
2020-06-07 22:42:53 +02:00
p.pending [ # p.pending + 1 ] = pdf_escape ( string.char ( getchar ( n ) ) )
2020-06-29 19:04:09 +02:00
if not p.font . usedglyphs [ getchar ( n ) ] then
p.font . usedglyphs [ getchar ( n ) ] = { getchar ( n ) , math.floor ( c.width * 1000 / f.size / p.font . extend + .5 ) , c.tounicode }
2019-07-17 21:14:34 +02:00
end
end
2020-01-02 04:14:39 +01:00
p.pos . x = p.pos . x + math.floor ( getwidth ( n ) * ( 1 + getexpansion ( n ) / 1000000 ) + .5 )
2019-07-17 21:14:34 +02:00
end
function nodehandler . whatsit ( p , n , ... ) -- Whatsit?
2020-07-01 19:47:25 +02:00
local handler , prop = get_whatsit_handler ( n )
if handler then
handler ( prop , p , n , ... )
2020-06-06 00:36:05 +02:00
else
write ( " Invalid whatsit found (missing handler). " )
end
2019-07-17 21:14:34 +02:00
end
2019-07-18 20:02:58 +02:00
local global_p , global_x , global_y
function pdf . _latelua ( p , x , y , func , ... )
global_p , global_x , global_y = p , x , y
return func ( ... )
end
2020-07-27 15:27:42 +02:00
function write_matrix ( a , b , c , d , e , f , p )
2020-06-04 23:30:46 +02:00
e , f , p = e or 0 , f or 0 , p or global_p
local pending = p.pending_matrix
if p.mode ~= cm_pending then
topage ( p )
p.mode = cm_pending
else
a , b = projected_point ( pending , a , b , 0 )
c , d = projected_point ( pending , c , d , 0 )
e , f = projected_point ( pending , e , f , 1 )
end
pending [ 1 ] , pending [ 2 ] , pending [ 3 ] , pending [ 4 ] , pending [ 5 ] , pending [ 6 ] = a , b , c , d , e , f
end
2020-07-27 15:27:42 +02:00
pdf.write_matrix = write_matrix
2020-07-01 22:50:50 +02:00
local literal_type_names = { [ 0 ] =
' origin ' , ' page ' , ' direct ' , ' raw ' , ' text '
}
2019-07-18 20:02:58 +02:00
function pdf . write ( mode , text , x , y , p )
x , y , p = x or global_x , y or global_y , p or global_p
2020-07-01 22:50:50 +02:00
mode = literal_type_names [ mode ] or mode
2019-07-20 14:53:24 +02:00
if mode == " page " then
topage ( p )
p.strings [ # p.strings + 1 ] = text
elseif mode == " text " then
2019-07-18 20:02:58 +02:00
topage ( p )
p.strings [ # p.strings + 1 ] = text
2019-07-20 14:53:24 +02:00
elseif mode == " direct " then
if p.mode ~= page then
totext ( p , p.font . fid )
end
p.strings [ # p.strings + 1 ] = text
2019-07-18 20:02:58 +02:00
elseif mode == " origin " then
2020-06-04 23:30:46 +02:00
write_matrix ( 1 , 0 , 0 , 1 , x , y , p )
2019-07-18 20:02:58 +02:00
topage ( p )
p.strings [ # p.strings + 1 ] = text
2020-06-04 23:30:46 +02:00
write_matrix ( 1 , 0 , 0 , 1 , - x , - y , p )
2019-07-18 20:02:58 +02:00
else
write ( format ( ' Literal type %s unsupported ' , mode ) )
end
end
2019-07-17 21:14:34 +02:00
local ondemandmeta = {
__index = function ( t , k )
t [ k ] = { }
return t [ k ]
end
}
2020-07-08 22:20:08 +02:00
local function nodewriter ( file , n , fontdirs , usedglyphs , colorstacks , resources )
2020-01-02 04:14:39 +01:00
n = todirect ( n )
2020-07-08 22:20:08 +02:00
resources = resources or make_resources ( )
2019-07-17 21:14:34 +02:00
local p = {
2019-07-18 20:02:58 +02:00
is_page = not not colorstacks ,
2019-07-17 21:14:34 +02:00
file = file ,
mode = 3 ,
strings = { } ,
pending = { } ,
pos = { } ,
font = { } ,
vfont = { } ,
matrix = { 1 , 0 , 0 , 1 , 0 , 0 } ,
pending_matrix = { } ,
2020-07-08 22:20:08 +02:00
resources = resources ,
2019-07-17 21:14:34 +02:00
annots = { } ,
linkcontext = file.linkcontext ,
2020-06-29 19:04:09 +02:00
fontdirs = fontdirs ,
usedglyphs = usedglyphs ,
2019-07-17 21:14:34 +02:00
}
2019-07-18 20:02:58 +02:00
if colorstacks then
for i = 1 , # colorstacks do
local colorstack = colorstacks [ i ]
if colorstack.page then
local stack = colorstack.page_stack
if colorstack.default ~= stack [ # stack ] then
pdf.write ( colorstack.mode , stack [ # stack ] , 0 , 0 , p )
end
end
end
end
2020-01-02 04:14:39 +01:00
nodehandler [ getid ( n ) ] ( p , n , 0 , 0 , n , nil , 0 )
-- nodehandler[getid(n)](p, n, 0, getdepth(n), n)
2019-07-17 21:14:34 +02:00
topage ( p )
2020-07-08 22:20:08 +02:00
return concat ( p.strings , ' \n ' ) , resources , ( p.annots [ 1 ] and string.format ( " /Annots[%s] " , table.concat ( p.annots , ' ' ) ) or " " )
2019-07-17 21:14:34 +02:00
end
2020-06-29 19:04:09 +02:00
require ' luametalatex-pdf-savedbox ' : init_nodewriter ( nodewriter )
return nodewriter