2019-07-17 21:14:34 +02:00
local function parse_charstring ( cs , subrs , result )
result = result or { { false } }
local lastresult = result [ # result ]
local i = 1
while i ~=# cs + 1 do
local cmd = cs : byte ( i )
if cmd == 255 then
lastresult [ # lastresult + 1 ] = string.unpack ( " >i4 " , cs : sub ( i + 1 , i + 4 ) )
i = i + 4
elseif cmd >= 251 then
lastresult [ # lastresult + 1 ] = - ( ( cmd - 251 ) * 256 ) - string.byte ( cs , i + 1 ) - 108
i = i + 1
elseif cmd >= 247 then
lastresult [ # lastresult + 1 ] = ( cmd - 247 ) * 256 + string.byte ( cs , i + 1 ) + 108
i = i + 1
elseif cmd >= 32 then
lastresult [ # lastresult + 1 ] = cmd - 139
elseif cmd == 9 then -- closepath, implicit in Type2
elseif cmd == 10 then
local subr = subrs [ lastresult [ # lastresult ] ]
lastresult [ # lastresult ] = nil
parse_charstring ( subr , subrs , result )
lastresult = result [ # result ]
elseif cmd == 11 then
break -- We do not keep subroutines, so drop returns and continue with the outer commands
elseif cmd == 12 then
i = i + 1
cmd = cs : byte ( i )
if cmd == 12 then -- div, we might have huge parameters, so execute directly
lastresult [ # lastresult - 1 ] = lastresult [ # lastresult - 1 ] / lastresult [ # lastresult ]
lastresult [ # lastresult ] = nil
elseif cmd == 16 then -- othersubr...
cmd = lastresult [ # lastresult ]
lastresult [ # lastresult ] = nil
local numargs = lastresult [ # lastresult ]
lastresult [ # lastresult ] = nil
if cmd == 3 then -- Hint replacement. This is easy, we support hint replacement, so we
-- keep the original subr number
assert ( numargs == 1 )
elseif cmd == 1 then -- Flex initialization
elseif cmd == 2 then -- Flex parameter
if result [ # result - 1 ] . flex then
2020-07-30 19:57:03 +02:00
result [ # result ] = nil -- TODO: Warn if there were additional values in lastresult.
2019-07-17 21:14:34 +02:00
lastresult = result [ # result ] -- We keep collecting arguments
end
lastresult.flex = true
elseif cmd == 0 then -- Flex
local flexinit = result [ # result - 1 ]
lastresult [ 2 ] = lastresult [ 2 ] + flexinit [ 2 ]
lastresult [ 3 ] = lastresult [ 3 ] + flexinit [ 3 ]
lastresult.flex = nil
result [ # result - 1 ] = lastresult
result [ # result ] = nil
lastresult [ # lastresult ] = nil
lastresult [ # lastresult ] = nil
lastresult [ 1 ] = - 36
lastresult = { false }
result [ # result + 1 ] = lastresult
lastresult [ # lastresult + 1 ] = " setcurrentpointmark "
elseif cmd == 12 or cmd == 13 then
local pending = { }
local results = # lastresult
for i = 1 , numargs do
pending [ i ] = lastresult [ results - numargs + i ]
lastresult [ results - numargs + i ] = nil
end
2020-07-30 19:57:03 +02:00
if lastresult.pendingargs then
for i = 1 , # lastresult.pendingargs do
pending [ numargs + i ] = lastresult.pendingargs [ i ]
end
2019-07-17 21:14:34 +02:00
end
if cmd == 12 then
lastresult.pendingargs = pending
else
lastresult.pendingargs = nil
2020-07-30 19:57:03 +02:00
local n = pending [ 1 ]
local i = 2
local groups = { }
for group = 1 , n do
local current = { 20 }
local last = 0
while pending [ i + 1 ] > 0 do
last = last + pending [ i ]
current [ # current + 1 ] = { 1 , last , pending [ i + 1 ] }
last = last + pending [ i + 1 ]
i = i + 2
end
last = last + pending [ i ]
current [ # current + 1 ] = { 1 , last + pending [ i + 1 ] , - pending [ i + 1 ] }
groups [ group ] = current
i = i + 2
end
n = pending [ i ]
i = i + 1
for group = 1 , n do
local current = groups [ group ] or { 20 }
local last = 0
while pending [ i + 1 ] > 0 do
last = last + pending [ i ]
current [ # current + 1 ] = { 3 , last , pending [ i + 1 ] }
last = last + pending [ i + 1 ]
i = i + 2
end
last = last + pending [ i ]
current [ # current + 1 ] = { 3 , last + pending [ i + 1 ] , - pending [ i + 1 ] }
groups [ group ] = current
i = i + 2
end
assert ( i == # pending + 1 )
table.move ( groups , 1 , # groups , # result , result ) -- This overwrites lastresult
result [ # result + 1 ] = lastresult -- And restore lastresult
2019-07-17 21:14:34 +02:00
end
else
error [[UNSUPPORTED Othersubr]]
end
elseif cmd == 17 then -- pop... Ignore them, they should already be handled by othersubr.
-- Compatibility with unknown othersubrs is futile, because
-- we can't interpret PostScript
elseif cmd == 33 then -- setcurrentpoint... If we expected this, it is already handled.
-- Otherwise fail, according to the spec it should
-- only be used with othersubrs.
assert ( lastresult [ # lastresult ] == " setcurrentpointmark " )
lastresult [ # lastresult ] = nil
else
lastresult [ 1 ] = - cmd - 1
lastresult = { false }
result [ # result + 1 ] = lastresult
end
else
lastresult [ 1 ] = cmd
lastresult = { false }
result [ # result + 1 ] = lastresult
end
i = i + 1
end
return result
end
local function adjust_charstring ( cs ) -- Here we get a not yet optimized but parsed Type1 charstring and
-- do some adjustments to make them more "Type2-like".
cs [ # cs ] = nil -- parse_charstring adds a `{false}` for internal reasons. Just drop it here. FIXME: Check that #cs[#cs]==1, otherwise there were values left on the charstring stack
if cs [ 1 ] [ 1 ] ~= 13 then
error [[Unsupported first Type1 operator]] -- probably cs[1][1] == sbw
-- If you find a font using this, I'm sorry for you.
end
local hoffset = cs [ 1 ] [ 2 ]
if hoffset ~= 0 then
-- non-zero sidebearings :-(
for i , cmd in ipairs ( cs ) do
if cmd [ 1 ] == 21 or cmd [ 1 ] == 22 then
cmd [ 2 ] = cmd [ 2 ] + cs [ 1 ] [ 2 ]
break
elseif cmd [ 1 ] == 4 then
cmd [ 3 ] = cmd [ 2 ]
cmd [ 2 ] = cs [ 1 ] [ 2 ]
cmd [ 1 ] = 21
break
end
-- Here I rely on the fact that the first relative command is always [hvr]moveto.
-- This is based on "Use rmoveto for the first point in the path." in the T1 spec
-- for hsbw. I am not entirely sure if this is a strict requirement or if there could
-- be weird charstrings where this fails (esp. since [hv]moveto are also used in the example),
-- but I decided to take the risk.
-- hints are affected too. They do not use relative coordinates in T1, so we store the offset
-- and handle hints later
end
end
cs [ 1 ] [ 2 ] = cs [ 1 ] [ 3 ]
cs [ 1 ] [ 3 ] = nil
cs [ 1 ] [ 1 ] = nil
-- That's it for the width, now we need some hinting stuff. This would be easy, if hint replacement
-- wouldn't require hint masks in Type2. And because we really enjoy this BS, we get counter
2020-07-30 19:57:03 +02:00
-- hinting as an additional treat... Oh, if you actually use counter hinting: Please test this
2019-07-17 21:14:34 +02:00
-- and report back if it works, because this is pretty much untested.
local stems = { }
2020-07-30 19:57:03 +02:00
local stem3 = { 20 }
local cntrs = { }
-- First iterate over the charstring, recording all hints and collecting them in stems/stem3/cntrs
2019-07-17 21:14:34 +02:00
for i , cmd in ipairs ( cs ) do
if cmd [ 1 ] == 1 or cmd [ 1 ] == 3 then
stems [ # stems + 1 ] = cmd
elseif cmd [ 1 ] == - 2 or cmd [ 1 ] == - 3 then
local c = cmd [ 1 ] == - 2 and 3 or 1
stems [ # stems + 1 ] = { c , cmd [ 2 ] , cmd [ 3 ] }
stems [ # stems + 1 ] = { c , cmd [ 4 ] , cmd [ 5 ] }
stems [ # stems + 1 ] = { c , cmd [ 6 ] , cmd [ 7 ] }
table.move ( stems , # stems - 2 , # stems , # stem3 + 1 , stem3 )
cs [ i ] = false
2020-07-30 19:57:03 +02:00
elseif cmd [ 1 ] == 20 then
cntrs [ # cntrs + 1 ] = cmd
table.move ( cmd , 2 , # cmd , # stems + 1 , stems )
2019-07-17 21:14:34 +02:00
end
end
table.sort ( stems , function ( first , second )
if first [ 1 ] ~= second [ 1 ] then return first [ 1 ] < second [ 1 ] end
if first [ 2 ] ~= second [ 2 ] then return first [ 2 ] < second [ 2 ] end
return first [ 3 ] < second [ 3 ]
end )
-- Now store the index of every stem in the idx member of the hint command
-- After that `j` stores the number of stems
local j , k = 1 , 1
if stems [ 1 ] then stems [ 1 ] . idx = 1 end
for i = 2 , # stems do
if stems [ i ] [ 2 ] == stems [ k ] [ 2 ] and stems [ i ] [ 3 ] == stems [ k ] [ 3 ] then
stems [ i ] . idx = j
stems [ i ] = false
else
j , k = j + 1 , i
stems [ i ] . idx = j
end
end
2020-07-30 19:57:03 +02:00
-- Now the indices are known, so the cntrmask can be written, if counters or stem3 occured.
2019-07-17 21:14:34 +02:00
-- This is done before writing the stem list to make the thable.insert parameters easier.
2020-07-30 19:57:03 +02:00
-- First translate stem3 into a counter group
if stem3 [ 2 ] then
cntrs [ # cntrs + 1 ] = stem3
table.insert ( cs , 2 , stem3 )
end
2019-07-17 21:14:34 +02:00
local bytes = { }
2020-07-30 19:57:03 +02:00
for i = 1 , # cntrs do
local cntr = cntrs [ i ]
for l = 1 , math.floor ( ( j + 7 ) / 8 ) do
bytes [ l ] = 0
end
for l = 2 , # cntr do
local idx = cntr [ l ] . idx - 1
2019-07-17 21:14:34 +02:00
bytes [ math.floor ( idx / 8 ) + 1 ] = bytes [ math.floor ( idx / 8 ) + 1 ] | ( 1 << ( 7 - idx % 8 ) )
2020-07-30 19:57:03 +02:00
cntr [ l ] = nil
2019-07-17 21:14:34 +02:00
end
2020-07-30 19:57:03 +02:00
cntr [ 2 ] = string.char ( table.unpack ( bytes ) )
2019-07-17 21:14:34 +02:00
end
local current = 1
-- Then list the collected stems at the beginning of the charstring
if stems [ current ] and stems [ current ] [ 1 ] == 1 then
local stem_tbl , last = { 18 } , 0
while stems [ current ] ~= nil and ( not stems [ current ] or stems [ current ] [ 1 ] == 1 ) do
if stems [ current ] then
stem_tbl [ # stem_tbl + 1 ] = stems [ current ] [ 2 ] - last
last = stems [ current ] [ 2 ] + stems [ current ] [ 3 ]
stem_tbl [ # stem_tbl + 1 ] = stems [ current ] [ 3 ]
end
current = current + 1
end
table.insert ( cs , 2 , stem_tbl )
end
if stems [ current ] and stems [ current ] [ 1 ] == 3 then
local stem_tbl , last = { false } , - hoffset
while stems [ current ] ~= nil and ( not stems [ current ] or stems [ current ] [ 1 ] == 3 ) do
if stems [ current ] then
stem_tbl [ # stem_tbl + 1 ] = stems [ current ] [ 2 ] - last
last = stems [ current ] [ 2 ] + stems [ current ] [ 3 ]
stem_tbl [ # stem_tbl + 1 ] = stems [ current ] [ 3 ]
end
current = current + 1
end
table.insert ( cs , stems [ 1 ] [ 1 ] == 1 and 3 or 2 , stem_tbl )
end
-- Finally, replace every run of hint commands, corresponding to a hint replacement, by a single hintmask
local i = 1
while cs [ i ] ~= nil do
if cs [ i ] and cs [ i ] . idx then
2021-11-07 15:46:56 +01:00
if stem3 [ 2 ] then
local s3 = stem3 [ 2 ]
for l = 1 , math.floor ( ( j + 7 ) / 8 ) do
bytes [ l ] = string.byte ( s3 , l )
end
else
for l = 1 , math.floor ( ( j + 7 ) / 8 ) do
bytes [ l ] = 0
end
2019-07-17 21:14:34 +02:00
end
while ( cs [ i ] or { } ) . idx do
local idx = cs [ i ] . idx - 1
bytes [ math.floor ( idx / 8 ) + 1 ] = bytes [ math.floor ( idx / 8 ) + 1 ] | ( 1 << ( 7 - idx % 8 ) )
cs [ i ] = false
i = i + 1
end
i = i - 1
cs [ i ] = { 19 , string.char ( table.unpack ( bytes ) ) }
end
i = i + 1
end
end
return function ( cs , subrs )
local parsed = parse_charstring ( cs , subrs )
adjust_charstring ( parsed )
return parsed
end