luametalatex/luametalatex-font-tfm.lua

152 lines
4.9 KiB
Lua

local readfile = require'luametalatex-readfile'
local upper_mask = (1<<20)-1<<44
local shifted_sign = 1<<43
local function scale(factor1, factor2)
local result = factor1*factor2 >> 20
if result & shifted_sign == shifted_sign then
return result | upper_mask
else
return result
end
end
local function read_scaled(buf, i, count, factor)
local result = {}
for j = 1, count do
result[j] = scale(factor, string.unpack(">i4", buf, i + (j-1)*4))
end
return result, i + count * 4
end
local function parse_ligkern(buf, offset, r_boundary, kerns)
local kerning, ligatures, done = {}, {}, {}
repeat
local skip, next, op, rem
skip, next, op, rem, offset = string.unpack("BBBB", buf, offset)
if skip > 128 then break end
if next == r_boundary then next = "right_boundary" end
if not done[next] then
done[next] = true
if op >= 128 then
kerning[next] = kerns[(op - 128 << 8) + rem + 1]
else
ligatures[next] = {
type = op,
char = rem,
}
end
end
until skip == 128
return next(kerning) and kerning or nil, next(ligatures) and ligatures or nil
end
local function parse_tfm(buf, i, size)
local lf, lh, bc, ec, nw, nh, nd, ni, nl, nk, ne, np
lf, lh, bc, ec, nw, nh, nd, ni, nl, nk, ne, np, i =
string.unpack(">HHHHHHHHHHHH", buf, i)
assert(bc-1 <= ec and ec <= 255)
assert(lf == 6 + lh + (ec - bc + 1) + nw + nh + nd + ni + nl + nk + ne + np)
assert(lh >= 2)
local checksum, designsize
checksum, designsize = string.unpack(">I4i4", buf, i)
i = i + 4*lh
designsize = designsize>>4 -- Adjust TFM sizes to sp
if size < 0 then
size = math.floor(-size*designsize/1000+.5)
end
-- In contrast to TeX, we will assume that multiplication of two 32 bit
-- integers never overflows. This is safe if Lua integers have 64 bit,
-- which is the default.
local ligatureoffset, r_boundary
local widths, heights, depths, italics, kerns, parameters
local extensibles = {}
do
local i = i + (ec - bc + 1) * 4
widths, i = read_scaled(buf, i, nw, size)
heights, i = read_scaled(buf, i, nh, size)
depths, i = read_scaled(buf, i, nd, size)
italics, i = read_scaled(buf, i, ni, size)
for k,v in ipairs(italics) do if v == 0 then italics[k] = nil end end
ligatureoffset = i
if nl ~= 0 and string.byte(buf, i, i) == 255 then
r_boundary = string.byte(buf, i+1, i+1)
end
i = i + nl * 4
kerns, i = read_scaled(buf, i, nk, size)
for j = 1, ne do
local ext = {}
ext.top, ext.mid, ext.bot, ext.rep, i = string.unpack("BBBB", buf, i)
for k,v in pairs(ext) do
if v == 0 then ext[k] = nil end
end
extensibles[j] = ext
end
local slant = np ~= 0 and string.unpack(">i4", buf, i) >> 4 or nil
parameters = read_scaled(buf, i, np, size)
parameters[1] = slant
end
local characters = {}
for cc = bc,ec do
local charinfo
charinfo, i = string.unpack(">I4", buf, i)
if (charinfo >> 24) & 0xFF ~= 0 then
local char = {
width = widths[((charinfo >> 24) & 0xFF) + 1],
height = heights[((charinfo >> 20) & 0xF) + 1],
depth = depths[((charinfo >> 16) & 0xF) + 1],
italic = italics[((charinfo >> 10) & 0xF) + 1],
}
local tag = (charinfo >> 8) & 0x3
if tag == 0 then
elseif tag == 1 then
local offset = (charinfo & 0xFF) * 4 + ligatureoffset
if string.byte(buf, offset, offset) > 128 then
offset = string.unpack(">H", buf, offset + 2) * 4 + ligatureoffset
end
char.kerns, char.ligatures = parse_ligkern(buf, offset, r_boundary, kerns)
elseif tag == 2 then
char.next = charinfo & 0xFF
elseif tag == 3 then
char.extensible = extensibles[(charinfo & 0xFF) + 1]
end
characters[cc] = char
end
end
if nl ~= 0 and string.byte(buf, ligatureoffset + (nl-1) * 4) == 255 then
local char = {}
characters.left_boundary = char
local offset = string.unpack(">H", buf, ligatureoffset + nl * 4 - 2) * 4 + ligatureoffset
char.kerns, char.ligatures = parse_ligkern(buf, offset, r_boundary, kerns)
end
return {
checksum = checksum,
direction = 0,
embedding = "unknown",
-- encodingbytes = 0,
extend = 1000,
format = "unknown",
identity = "unknown",
mode = 0,
slant = 0,
squeeze = 1000,
oldmath = false,
streamprovider = 0,
tounicode = 0,
type = "unknown",
units_per_em = 0,
used = false,
width = 0,
writingmode = "unknown",
size = size,
designsize = designsize,
parameters = parameters,
characters = characters,
}
end
local basename = ((1-lpeg.S'\\/')^0*lpeg.S'\\/')^0*lpeg.C((1-lpeg.P'.tfm'*-1)^0)
return function(name, size)
local file <close> = readfile('tfm', name)
if not file then return end
local result = parse_tfm(file(), 1, size)
result.name = basename:match(name)
return result
end