Add some PK bitmap font support

This commit is contained in:
Marcel Krüger 2020-07-05 20:42:36 +02:00
parent ad44afdb0b
commit 06d1efeb55
3 changed files with 277 additions and 1 deletions

203
luametalatex-font-pk.lua Normal file
View File

@ -0,0 +1,203 @@
local function decode_char(buf)
local flag = buf:byte(1)
local form = flag & 0x07
local tfm, dx, dy, w, h, hoff, voff, off
if form < 4 then
tfm, dx, w, h, hoff, voff, off = string.unpack(">I3BBBbb", buf, 4)
dx, dy = dx * 2^16, 0
elseif form < 7 then
tfm, dx, w, h, hoff, voff, off = string.unpack(">I3HHHhh", buf, 5)
dx, dy = dx * 2^16, 0
else
tfm, dx, dy, w, h, hoff, voff, off = string.unpack(">I4I4I4I4I4i4i4", buf, 10)
end
local dyn_f, state = flag >> 4, flag & 8 == 8
local data
local stride = w+7>>3
if dyn_f == 14 then
print(state)
-- assert(not state)
data = lua.newtable(stride*h, 0)
local bit_offset, saved = 0
local delta_bit_offset = 8 - w%8
delta_bit_offset = delta_bit_offset == 8 and 0 or delta_bit_offset
for y=0,h-1 do
for x=1,stride do
if bit_offset == 0 then
saved = buf:byte(off)
data[y*stride+x] = saved
else
local saved_mask = (1<<bit_offset)-1
local current = (saved&saved_mask) << 8-bit_offset
saved = buf:byte(off)
data[y*stride+x] = current | (saved & ~saved_mask) >> bit_offset
end
off = off+1
end
if delta_bit_offset then
data[(y+1)*stride] = data[(y+1)*stride] & (0x100 - (1<<delta_bit_offset))
end
bit_offset = bit_offset + delta_bit_offset
if bit_offset >= 8 then
bit_offset = bit_offset-8
off = off-1
saved = buf:byte(off)
end
end
else
data = {string.rep('\0', stride*h):byte(1,-1)} -- FIXME: This is probably really slow
local nibble, repeat_row = nil, 0
local function get_nibble()
if nibble then
local cur = nibble
nibble = nil
off = off+1
return cur
else
local cur = buf:byte(off)
nibble = cur&0xF
return cur >> 4
end
end
local function get_packed()
local cur = get_nibble()
if cur == 0 then
local i = 0
repeat
cur = get_nibble()
i = i+1
until cur ~= 0
for _=1,i do
cur = (cur<<4) + get_nibble()
end
return cur - 0xF + (13-dyn_f << 4) + dyn_f
elseif cur <= dyn_f then
return cur
elseif cur < 14 then
return (cur-dyn_f-1 << 4) + get_nibble() + dyn_f + 1
else
repeat_row = cur == 14 and get_packed() or 1
return get_packed()
end
end
local cur_x, cur_y = 0, 0
while cur_y < h do
local count = get_packed()
repeat
local this_line = math.min(w - cur_x, count)
count = count - this_line
if state then
local cur_bit_offset = cur_x % 8
if cur_bit_offset ~= 0 then -- We are in the middle of a byte
cur_bit_offset = 8-cur_bit_offset -- The remaining bits in the byte
local off = cur_y*stride+(cur_x>>3)+1
if this_line > cur_bit_offset then -- Fill byte with ones
data[off] = data[off] + (1<<cur_bit_offset)-1
this_line, cur_x = this_line-cur_bit_offset, cur_x+cur_bit_offset
else
data[off] = data[off] + (1<<cur_bit_offset)-(1<<cur_bit_offset-this_line)
this_line, cur_x = 0, cur_x+this_line
end
end
while this_line >= 8 do
data[cur_y*stride+(cur_x>>3)+1] = 0xFF
this_line, cur_x = this_line-8, cur_x+8
end
if this_line ~= 0 then
data[cur_y*stride+(cur_x>>3)+1] = 0x100-(1<<8-this_line)
end
end
cur_x = cur_x + this_line
if cur_x == w then
for i = 1, repeat_row do
table.move(data, cur_y*stride+1, (cur_y+1)*stride, (cur_y+i)*stride+1) -- TODO
end
cur_y, cur_x, repeat_row = cur_y + 1 + repeat_row, 0, 0
end
until count == 0
state = not state
end
end
data = string.char(table.unpack(data))
return {
data = data,
tfm = tfm,
dx = dx,
dy = dy,
hoff = hoff,
voff = voff,
w = w,
h = h,
}
end
local commands = {
[240] = function(buf, off, t)
local xxx xxx, off = string.unpack(">xs1", buf, off)
return off
end,
[241] = function(buf, off, t)
local xxx xxx, off = string.unpack(">xs2", buf, off)
return off
end,
[242] = function(buf, off, t)
local xxx xxx, off = string.unpack(">xs3", buf, off)
return off
end,
[243] = function(buf, off, t)
local xxx xxx, off = string.unpack(">xs4", buf, off)
return off
end,
[244] = function(buf, off, t)
local yyy yyy, off = string.unpack(">xI4", buf, off)
return off
end,
[247] = function(buf, off, t)
local ident
ident, t.comment, t.designsize, t.checksum, t.hppp, t.vppp, off = string.unpack(">xBs1I4I4I4I4", buf, off)
if ident ~= 89 then
error[[Not a PK file]]
end
return off
end,
}
local function parse_commands(buf, off, t)
local cmd = buf:byte(off)
assert(cmd == 247)
repeat
if cmd < 240 then
local form = cmd & 0x07
local chr, newoff, length
if form < 4 then
length, chr, newoff = string.unpack(">xBB", buf, off)
length = length + ((form & 3)<<8)
elseif form < 7 then
length, chr, newoff = string.unpack(">xHB", buf, off)
length = length + ((form & 3)<<16)
else
length, chr, newoff = string.unpack(">xI4I4", buf, off)
end
newoff = newoff + length
t[chr] = decode_char(buf:sub(off, newoff))
off = newoff
else
local handler = commands[cmd]
if not handler then
print([[Unknown command ]] .. cmd)
return off-1
end
off = handler(buf, off, t)
end
cmd = buf:byte(off)
until cmd == 245
return off
end
return function(name)
local f = assert(io.open(kpse.find_file(name, 'pk', 600), 'rb')) -- TODO configurable dpi
local pk = f:read'a'
f:close()
local res = {}
off = parse_commands(pk, off, res)
-- assert(off == #pk+1) -- TODO: Check that only fillup bytes follow
return res
end

View File

@ -0,0 +1,28 @@
local read_pk = require'luametalatex-font-pk'
return function(pdf, fontdir, usedcids)
local pk = read_pk(fontdir.name)
local designsize = pk.designsize/1044654.326 -- 1044654.326=2^20*72/72.27 -- designsize in bp
local hscale = 65536/pk.hppp / designsize -- 65291.158=2^16*72/72.27
local vscale = 65536/pk.vppp / designsize -- 65291.158=2^16*72/72.27
local bbox = {0, 0, 0, 0}
local matrix = {hscale, 0, 0, vscale, 0, 0}
local widths = {}
local first_cid = usedcids[1][1]-1
local charprocs = {}
local prev = 0
for i=1,#usedcids do
local used = usedcids[i]
local glyph = pk[used[1]]
for j=prev+1,used[1]-first_cid-1 do
widths[j] = 0
end
prev = used[1]-first_cid
widths[prev] = glyph.dx/2^16
local lower, left, upper, right = glyph.voff - glyph.h, -glyph.hoff, glyph.voff, -glyph.hoff + glyph.w
bbox[1], bbox[2], bbox[3], bbox[4] = math.min(bbox[1], left), math.min(bbox[2], lower), math.max(bbox[3], right), math.max(bbox[4], upper)
charprocs[i] = string.format("/G%i %i 0 R", used[1], pdf:stream(nil, "", string.format("%i %i %i %i %i %i d1 %i 0 0 %i %i %i cm BI /W %i/H %i/IM true/BPC 1/D[1 0] ID %s EI",
glyph.dx/2^16, glyph.dy, left, lower, right, upper, glyph.w, glyph.h, left, lower, glyph.w, glyph.h, glyph.data
)))
end
return bbox, matrix, '[' .. table.concat(widths, ' ') .. ']', '<<' .. table.concat(charprocs) .. '>>'
end

View File

@ -58,6 +58,31 @@ local function fontdescriptor(pdf, basefont, fontdir, stream, kind)
fontdir.StemV or 100, -- FIXME: How to determine StemV?
kind, stream)
end
local function encodingtype3(pdf)
if not pdf.encodingtype3 then
pdf.encodingtype3 = string.format(" %i 0 R", pdf:indirect(nil, "\z
<</Differences[\z
0/G0/G1/G2/G3/G4/G5/G6/G7/G8/G9/G10/G11/G12/G13/G14/G15/G16\z
/G17/G18/G19/G20/G21/G22/G23/G24/G25/G26/G27/G28/G29/G30/G31\z
/G32/G33/G34/G35/G36/G37/G38/G39/G40/G41/G42/G43/G44/G45/G46\z
/G47/G48/G49/G50/G51/G52/G53/G54/G55/G56/G57/G58/G59/G60/G61\z
/G62/G63/G64/G65/G66/G67/G68/G69/G70/G71/G72/G73/G74/G75/G76\z
/G77/G78/G79/G80/G81/G82/G83/G84/G85/G86/G87/G88/G89/G90/G91\z
/G92/G93/G94/G95/G96/G97/G98/G99/G100/G101/G102/G103/G104/G105/G106\z
/G107/G108/G109/G110/G111/G112/G113/G114/G115/G116/G117/G118/G119/G120/G121\z
/G122/G123/G124/G125/G126/G127/G128/G129/G130/G131/G132/G133/G134/G135/G136\z
/G137/G138/G139/G140/G141/G142/G143/G144/G145/G146/G147/G148/G149/G150/G151\z
/G152/G153/G154/G155/G156/G157/G158/G159/G160/G161/G162/G163/G164/G165/G166\z
/G167/G168/G169/G170/G171/G172/G173/G174/G175/G176/G177/G178/G179/G180/G181\z
/G182/G183/G184/G185/G186/G187/G188/G189/G190/G191/G192/G193/G194/G195/G196\z
/G197/G198/G199/G200/G201/G202/G203/G204/G205/G206/G207/G208/G209/G210/G211\z
/G212/G213/G214/G215/G216/G217/G218/G219/G220/G221/G222/G223/G224/G225/G226\z
/G227/G228/G229/G230/G231/G232/G233/G234/G235/G236/G237/G238/G239/G240/G241\z
/G242/G243/G244/G245/G246/G247/G248/G249/G250/G251/G252/G253/G254/G255]>>"
))
end
return pdf.encodingtype3
end
local function cidmap1byte(pdf)
if not pdf.cidmap1byte then
pdf.cidmap1byte = string.format(" %i 0 R", pdf:stream(nil, [[/Type/CMap/CMapName/Identity-8-H/CIDSystemInfo<</Registry(Adobe)/Ordering(Identity)/Supplement 0>>]],
@ -222,9 +247,29 @@ local function buildfont0(pdf, fontdir, usedcids)
touni,
cidfont)
end
local buildfontpk = require'luametalatex-pdf-font-pk'
local function buildfont3(pdf, fontdir, usedcids)
usedcids = usedcids or allcids(fontdir)
table.sort(usedcids, function(a,b) return a[1]<b[1] end)
local enc = cidmap1byte(pdf)
local bbox, matrix, widths, charprocs = buildfontpk(pdf, fontdir, usedcids) -- TOOD
local touni = pdf:stream(nil, "", tounicode[1](fontdir, usedcids)) -- Done late to allow for defaults set from the font file
return string.format(
"<</Type/Font/Subtype/Type3/FontBBox[%f %f %f %f]/FontMatrix[%f %f %f %f %f %f]/CharProcs%s/Encoding%s/FirstChar %i/LastChar %i/Widths%s/ToUnicode %i 0 R>>",
-- "<</Type/Font/Subtype/Type3/FontBBox[%f %f %f %f]/FontMatrix[%f %f %f %f %f %f]/CharProcs%s/Encoding%s/FirstChar %i/LastChar %i/Widths[%s]/ToUnicode %i 0 R/FontDescriptor %i 0 R>>",
bbox[1], bbox[2], bbox[3], bbox[4],
matrix[1], matrix[2], matrix[3], matrix[4], matrix[5], matrix[6],
charprocs,
encodingtype3(pdf),
usedcids[1][1],
usedcids[#usedcids][1],
widths,
touni
) -- , descriptor) -- TODO
end
return function(pdf, fontdir, usedcids)
if fontdir.format == "type3" then
error[[Currently unsupported]] -- TODO
return buildfont3(pdf, fontdir, usedcids)
else
return buildfont0(pdf, fontdir, usedcids)
end