Current File : //usr/share/texlive/texmf-dist/tex/luatex/luaotfload/fontloader-font-ttf.lua |
if not modules then modules = { } end modules ['font-ttf'] = {
version = 1.001,
optimize = true,
comment = "companion to font-ini.mkiv",
author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright = "PRAGMA ADE / ConTeXt Development Team",
license = "see context related readme files"
}
-- This version is different from previous in the sense that we no longer store
-- contours but keep points and contours (endpoints) separate for a while
-- because later on we need to apply deltas and that is easier on a list of
-- points.
-- The code is a bit messy. I looked at the ff code but it's messy too. It has
-- to do with the fact that we need to look at points on the curve and control
-- points in between. This also means that we start at point 2 and have to look
-- at point 1 when we're at the end. We still use a ps like storage with the
-- operator last in an entry. It's typical code that evolves stepwise till a
-- point of no comprehension.
-- For deltas we need a rather complex loop over points that can have holes and
-- be less than nofpoints and even can have duplicates and also the x and y value
-- lists can be shorter than etc. I need fonts in order to complete this simply
-- because I need to visualize in order to understand (what the standard tries
-- to explain).
-- 0 point then none applied
-- 1 points then applied to all
-- otherwise inferred deltas using nearest
-- if no lower point then use highest referenced point
-- if no higher point then use lowest referenced point
-- factor = (target-left)/(right-left)
-- delta = (1-factor)*left + factor * right
local next, type, unpack = next, type, unpack
local band, rshift = bit32.band, bit32.rshift
local sqrt, round = math.sqrt, math.round
local char, rep = string.char, string.rep
local concat = table.concat
local idiv = number.idiv
local setmetatableindex = table.setmetatableindex
local report = logs.reporter("otf reader","ttf")
local trace_deltas = false
local readers = fonts.handlers.otf.readers
local streamreader = readers.streamreader
local setposition = streamreader.setposition
local getposition = streamreader.getposition
local skipbytes = streamreader.skip
local readbyte = streamreader.readcardinal1 -- 8-bit unsigned integer
local readushort = streamreader.readcardinal2 -- 16-bit unsigned integer
local readulong = streamreader.readcardinal4 -- 24-bit unsigned integer
local readchar = streamreader.readinteger1 -- 8-bit signed integer
local readshort = streamreader.readinteger2 -- 16-bit signed integer
local read2dot14 = streamreader.read2dot14 -- 16-bit signed fixed number with the low 14 bits of fraction (2.14) (F2DOT14)
local readinteger = streamreader.readinteger1
local readcardinaltable = streamreader.readcardinaltable
local readintegertable = streamreader.readintegertable
directives.register("fonts.streamreader",function()
streamreader = utilities.streams
setposition = streamreader.setposition
getposition = streamreader.getposition
skipbytes = streamreader.skip
readbyte = streamreader.readcardinal1
readushort = streamreader.readcardinal2
readulong = streamreader.readcardinal4
readchar = streamreader.readinteger1
readshort = streamreader.readinteger2
read2dot14 = streamreader.read2dot14
readinteger = streamreader.readinteger1
readcardinaltable = streamreader.readcardinaltable
readintegertable = streamreader.readintegertable
end)
local short = 2
local ushort = 2
local ulong = 4
local helpers = readers.helpers
local gotodatatable = helpers.gotodatatable
local function mergecomposites(glyphs,shapes)
-- todo : deltas
local function merge(index,shape,components)
local contours = { }
local points = { }
local nofcontours = 0
local nofpoints = 0
local offset = 0
local deltas = shape.deltas
for i=1,#components do
local component = components[i]
local subindex = component.index
local subshape = shapes[subindex]
local subcontours = subshape.contours
local subpoints = subshape.points
if not subcontours then
local subcomponents = subshape.components
if subcomponents then
subcontours, subpoints = merge(subindex,subshape,subcomponents)
end
end
if subpoints then
local matrix = component.matrix
local xscale = matrix[1]
local xrotate = matrix[2]
local yrotate = matrix[3]
local yscale = matrix[4]
local xoffset = matrix[5]
local yoffset = matrix[6]
local count = #subpoints
if xscale == 1 and yscale == 1 and xrotate == 0 and yrotate == 0 then
for i=1,count do
local p = subpoints[i]
nofpoints = nofpoints + 1
points[nofpoints] = {
p[1] + xoffset,
p[2] + yoffset,
p[3]
}
end
else
for i=1,count do
local p = subpoints[i]
local x = p[1]
local y = p[2]
nofpoints = nofpoints + 1
points[nofpoints] = {
xscale * x + xrotate * y + xoffset,
yscale * y + yrotate * x + yoffset,
p[3]
}
end
end
local subcount = #subcontours
if subcount == 1 then
nofcontours = nofcontours + 1
contours[nofcontours] = offset + subcontours[1]
else
for i=1,#subcontours do
nofcontours = nofcontours + 1
contours[nofcontours] = offset + subcontours[i]
end
end
offset = offset + count
else
report("missing contours composite %s, component %s of %s, glyph %s",index,i,#components,subindex)
end
end
shape.points = points -- todo : phantom points
shape.contours = contours
shape.components = nil
return contours, points
end
for index=0,#glyphs do
local shape = shapes[index]
if shape then
local components = shape.components
if components then
merge(index,shape,components)
end
end
end
end
local function readnothing(f)
return {
type = "nothing",
}
end
-- begin of converter
local function curveto(m_x,m_y,l_x,l_y,r_x,r_y) -- todo: inline this
return
l_x + 2/3 *(m_x-l_x), l_y + 2/3 *(m_y-l_y),
r_x + 2/3 *(m_x-r_x), r_y + 2/3 *(m_y-r_y),
r_x, r_y, "c"
end
-- We could omit the operator which saves some 10%:
--
-- #2=lineto #4=quadratic #6=cubic #3=moveto (with "m")
--
-- This is tricky ... something to do with phantom points .. however, the hvar
-- and vvar tables should take care of the width .. the test font doesn't have
-- those so here we go then (we need a flag for hvar).
--
-- h-advance left-side-bearing v-advance top-side-bearing
--
-- We had two loops (going backward) but can do it in one loop .. but maybe we
-- should only accept fonts with proper hvar tables.
local function applyaxis(glyph,shape,deltas,dowidth)
local points = shape.points
if points then
local nofpoints = #points
local h = nofpoints + 2 -- weird, the example font seems to have left first
local l = nofpoints + 1
----- v = nofpoints + 3
----- t = nofpoints + 4
local dw = 0
local dl = 0
for i=1,#deltas do
local deltaset = deltas[i]
local xvalues = deltaset.xvalues
local yvalues = deltaset.yvalues
local dpoints = deltaset.points
local factor = deltaset.factor
if dpoints then
-- todo: interpolate
local nofdpoints = #dpoints
for i=1,nofdpoints do
local d = dpoints[i]
local p = points[d]
if p then
if xvalues then
local x = xvalues[i]
if x and x ~= 0 then
p[1] = p[1] + factor * x
end
end
if yvalues then
local y = yvalues[i]
if y and y ~= 0 then
p[2] = p[2] + factor * y
end
end
elseif dowidth then
-- we've now ran into phantom points which is a bit fuzzy because:
-- are there gaps in there?
--
-- todo: move this outside the loop (when we can be sure of all 4 being there)
if d == h then
-- we have a phantom point hadvance
local x = xvalues[i]
if x then
dw = dw + factor * x
end
elseif d == l then
local x = xvalues[i]
if x then
dl = dl + factor * x
end
end
end
end
else
for i=1,nofpoints do
local p = points[i]
if xvalues then
local x = xvalues[i]
if x and x ~= 0 then
p[1] = p[1] + factor * x
end
end
if yvalues then
local y = yvalues[i]
if y and y ~= 0 then
p[2] = p[2] + factor * y
end
end
end
if dowidth then
local x = xvalues[h]
if x then
dw = dw + factor * x
end
local x = xvalues[l]
if x then
dl = dl + factor * x
end
end
end
end
-- for i=1,nofpoints do
-- local p = points[i]
-- p[1] = round(p[1])
-- p[2] = round(p[2])
-- end
if dowidth then
local width = glyph.width or 0
-- local lsb = glyph.lsb or 0
glyph.width = width + dw - dl
end
else
report("no points for glyph %a",glyph.name)
end
end
-- round or not ?
-- local quadratic = true -- both methods work, todo: install a directive
local quadratic = false
local function contours2outlines_normal(glyphs,shapes) -- maybe accept the bbox overhead
-- for index=1,#glyphs do
for index=0,#glyphs-1 do
local shape = shapes[index]
if shape then
local glyph = glyphs[index]
local contours = shape.contours
local points = shape.points
if contours then
local nofcontours = #contours
local segments = { }
local nofsegments = 0
glyph.segments = segments
if nofcontours > 0 then
local px = 0
local py = 0
local first = 1
for i=1,nofcontours do
local last = contours[i]
if last >= first then
local first_pt = points[first]
local first_on = first_pt[3]
-- todo no new tables but reuse lineto and quadratic
if first == last then
first_pt[3] = "m" -- "moveto"
nofsegments = nofsegments + 1
segments[nofsegments] = first_pt
else -- maybe also treat n == 2 special
local first_on = first_pt[3]
local last_pt = points[last]
local last_on = last_pt[3]
local start = 1
local control_pt = false
if first_on then
start = 2
else
if last_on then
first_pt = last_pt
else
first_pt = { (first_pt[1]+last_pt[1])/2, (first_pt[2]+last_pt[2])/2, false }
end
control_pt = first_pt
end
local x = first_pt[1]
local y = first_pt[2]
if not done then
xmin = x
ymin = y
xmax = x
ymax = y
done = true
end
nofsegments = nofsegments + 1
segments[nofsegments] = { x, y, "m" } -- "moveto"
if not quadratic then
px = x
py = y
end
local previous_pt = first_pt
for i=first,last do
local current_pt = points[i]
local current_on = current_pt[3]
local previous_on = previous_pt[3]
if previous_on then
if current_on then
-- both normal points
local x, y = current_pt[1], current_pt[2]
nofsegments = nofsegments + 1
segments[nofsegments] = { x, y, "l" } -- "lineto"
if not quadratic then
px, py = x, y
end
else
control_pt = current_pt
end
elseif current_on then
local x1 = control_pt[1]
local y1 = control_pt[2]
local x2 = current_pt[1]
local y2 = current_pt[2]
nofsegments = nofsegments + 1
if quadratic then
segments[nofsegments] = { x1, y1, x2, y2, "q" } -- "quadraticto"
else
x1, y1, x2, y2, px, py = curveto(x1, y1, px, py, x2, y2)
segments[nofsegments] = { x1, y1, x2, y2, px, py, "c" } -- "curveto"
end
control_pt = false
else
local x2 = (previous_pt[1]+current_pt[1])/2
local y2 = (previous_pt[2]+current_pt[2])/2
local x1 = control_pt[1]
local y1 = control_pt[2]
nofsegments = nofsegments + 1
if quadratic then
segments[nofsegments] = { x1, y1, x2, y2, "q" } -- "quadraticto"
else
x1, y1, x2, y2, px, py = curveto(x1, y1, px, py, x2, y2)
segments[nofsegments] = { x1, y1, x2, y2, px, py, "c" } -- "curveto"
end
control_pt = current_pt
end
previous_pt = current_pt
end
if first_pt == last_pt then
-- we're already done, probably a simple curve
else
nofsegments = nofsegments + 1
local x2 = first_pt[1]
local y2 = first_pt[2]
if not control_pt then
segments[nofsegments] = { x2, y2, "l" } -- "lineto"
elseif quadratic then
local x1 = control_pt[1]
local y1 = control_pt[2]
segments[nofsegments] = { x1, y1, x2, y2, "q" } -- "quadraticto"
else
local x1 = control_pt[1]
local y1 = control_pt[2]
x1, y1, x2, y2, px, py = curveto(x1, y1, px, py, x2, y2)
segments[nofsegments] = { x1, y1, x2, y2, px, py, "c" } -- "curveto"
-- px, py = x2, y2
end
end
end
end
first = last + 1
end
end
end
end
end
end
local function contours2outlines_shaped(glyphs,shapes,keepcurve)
-- for index=1,#glyphs do
for index=0,#glyphs-1 do
local shape = shapes[index]
if shape then
local glyph = glyphs[index]
local contours = shape.contours
local points = shape.points
if contours then
local nofcontours = #contours
local segments = keepcurve and { } or nil
local nofsegments = 0
if keepcurve then
glyph.segments = segments
end
if nofcontours > 0 then
local xmin, ymin, xmax, ymax, done = 0, 0, 0, 0, false
local px, py = 0, 0 -- we could use these in calculations which saves a copy
local first = 1
for i=1,nofcontours do
local last = contours[i]
if last >= first then
local first_pt = points[first]
local first_on = first_pt[3]
-- todo no new tables but reuse lineto and quadratic
if first == last then
-- this can influence the boundingbox
if keepcurve then
first_pt[3] = "m" -- "moveto"
nofsegments = nofsegments + 1
segments[nofsegments] = first_pt
end
else -- maybe also treat n == 2 special
local first_on = first_pt[3]
local last_pt = points[last]
local last_on = last_pt[3]
local start = 1
local control_pt = false
if first_on then
start = 2
else
if last_on then
first_pt = last_pt
else
first_pt = { (first_pt[1]+last_pt[1])/2, (first_pt[2]+last_pt[2])/2, false }
end
control_pt = first_pt
end
local x = first_pt[1]
local y = first_pt[2]
if not done then
xmin, ymin, xmax, ymax = x, y, x, y
done = true
else
if x < xmin then xmin = x elseif x > xmax then xmax = x end
if y < ymin then ymin = y elseif y > ymax then ymax = y end
end
if keepcurve then
nofsegments = nofsegments + 1
segments[nofsegments] = { x, y, "m" } -- "moveto"
end
if not quadratic then
px = x
py = y
end
local previous_pt = first_pt
for i=first,last do
local current_pt = points[i]
local current_on = current_pt[3]
local previous_on = previous_pt[3]
if previous_on then
if current_on then
-- both normal points
local x = current_pt[1]
local y = current_pt[2]
if x < xmin then xmin = x elseif x > xmax then xmax = x end
if y < ymin then ymin = y elseif y > ymax then ymax = y end
if keepcurve then
nofsegments = nofsegments + 1
segments[nofsegments] = { x, y, "l" } -- "lineto"
end
if not quadratic then
px = x
py = y
end
else
control_pt = current_pt
end
elseif current_on then
local x1 = control_pt[1]
local y1 = control_pt[2]
local x2 = current_pt[1]
local y2 = current_pt[2]
if quadratic then
if x1 < xmin then xmin = x1 elseif x1 > xmax then xmax = x1 end
if y1 < ymin then ymin = y1 elseif y1 > ymax then ymax = y1 end
if keepcurve then
nofsegments = nofsegments + 1
segments[nofsegments] = { x1, y1, x2, y2, "q" } -- "quadraticto"
end
else
x1, y1, x2, y2, px, py = curveto(x1, y1, px, py, x2, y2)
if x1 < xmin then xmin = x1 elseif x1 > xmax then xmax = x1 end
if y1 < ymin then ymin = y1 elseif y1 > ymax then ymax = y1 end
if x2 < xmin then xmin = x2 elseif x2 > xmax then xmax = x2 end
if y2 < ymin then ymin = y2 elseif y2 > ymax then ymax = y2 end
if px < xmin then xmin = px elseif px > xmax then xmax = px end
if py < ymin then ymin = py elseif py > ymax then ymax = py end
if keepcurve then
nofsegments = nofsegments + 1
segments[nofsegments] = { x1, y1, x2, y2, px, py, "c" } -- "curveto"
end
end
control_pt = false
else
local x2 = (previous_pt[1]+current_pt[1])/2
local y2 = (previous_pt[2]+current_pt[2])/2
local x1 = control_pt[1]
local y1 = control_pt[2]
if quadratic then
if x1 < xmin then xmin = x1 elseif x1 > xmax then xmax = x1 end
if y1 < ymin then ymin = y1 elseif y1 > ymax then ymax = y1 end
if keepcurve then
nofsegments = nofsegments + 1
segments[nofsegments] = { x1, y1, x2, y2, "q" } -- "quadraticto"
end
else
x1, y1, x2, y2, px, py = curveto(x1, y1, px, py, x2, y2)
if x1 < xmin then xmin = x1 elseif x1 > xmax then xmax = x1 end
if y1 < ymin then ymin = y1 elseif y1 > ymax then ymax = y1 end
if x2 < xmin then xmin = x2 elseif x2 > xmax then xmax = x2 end
if y2 < ymin then ymin = y2 elseif y2 > ymax then ymax = y2 end
if px < xmin then xmin = px elseif px > xmax then xmax = px end
if py < ymin then ymin = py elseif py > ymax then ymax = py end
if keepcurve then
nofsegments = nofsegments + 1
segments[nofsegments] = { x1, y1, x2, y2, px, py, "c" } -- "curveto"
end
end
control_pt = current_pt
end
previous_pt = current_pt
end
if first_pt == last_pt then
-- we're already done, probably a simple curve
elseif not control_pt then
if keepcurve then
nofsegments = nofsegments + 1
segments[nofsegments] = { first_pt[1], first_pt[2], "l" } -- "lineto"
end
else
local x1 = control_pt[1]
local y1 = control_pt[2]
local x2 = first_pt[1]
local y2 = first_pt[2]
if x1 < xmin then xmin = x1 elseif x1 > xmax then xmax = x1 end
if y1 < ymin then ymin = y1 elseif y1 > ymax then ymax = y1 end
if quadratic then
if keepcurve then
nofsegments = nofsegments + 1
segments[nofsegments] = { x1, y1, x2, y2, "q" } -- "quadraticto"
end
else
x1, y1, x2, y2, px, py = curveto(x1, y1, px, py, x2, y2)
if x2 < xmin then xmin = x2 elseif x2 > xmax then xmax = x2 end
if y2 < ymin then ymin = y2 elseif y2 > ymax then ymax = y2 end
if px < xmin then xmin = px elseif px > xmax then xmax = px end
if py < ymin then ymin = py elseif py > ymax then ymax = py end
if keepcurve then
nofsegments = nofsegments + 1
segments[nofsegments] = { x1, y1, x2, y2, px, py, "c" } -- "curveto"
end
-- px, py = x2, y2
end
end
end
end
first = last + 1
end
glyph.boundingbox = { round(xmin), round(ymin), round(xmax), round(ymax) }
end
end
end
end
end
-- optimize for zero
local c_zero = char(0)
local s_zero = char(0,0)
-- local shorthash = setmetatableindex(function(t,k)
-- t[k] = char(band(rshift(k,8),0xFF),band(k,0xFF)) return t[k]
-- end)
local function toushort(n)
return char(band(rshift(n,8),0xFF),band(n,0xFF))
-- return shorthash[n]
end
local function toshort(n)
if n < 0 then
n = n + 0x10000
end
return char(band(rshift(n,8),0xFF),band(n,0xFF))
-- return shorthash[n]
end
-- todo: we can reuse result, xpoints and ypoints
local chars = setmetatableindex(function(t,k)
for i=0,255 do local v = char(i) t[i] = v end return t[k]
end)
local function repackpoints(glyphs,shapes)
local noboundingbox = { 0, 0, 0, 0 }
local result = { } -- reused
local xpoints = { } -- reused
local ypoints = { } -- reused
for index=0,#glyphs do
local shape = shapes[index]
if shape then
local r = 0
local glyph = glyphs[index]
local contours = shape.contours
local nofcontours = contours and #contours or 0
local boundingbox = glyph.boundingbox or noboundingbox
r = r + 1 result[r] = toshort(nofcontours)
r = r + 1 result[r] = toshort(boundingbox[1]) -- xmin
r = r + 1 result[r] = toshort(boundingbox[2]) -- ymin
r = r + 1 result[r] = toshort(boundingbox[3]) -- xmax
r = r + 1 result[r] = toshort(boundingbox[4]) -- ymax
if nofcontours > 0 then
for i=1,nofcontours do
r = r + 1 result[r] = toshort(contours[i]-1)
end
r = r + 1 result[r] = s_zero -- no instructions
local points = shape.points
local currentx = 0
local currenty = 0
-- local xpoints = { }
-- local ypoints = { }
local x = 0
local y = 0
local lastflag = nil
local nofflags = 0
for i=1,#points do
local pt = points[i]
local px = pt[1]
local py = pt[2]
local fl = pt[3] and 0x01 or 0x00
if px == currentx then
fl = fl + 0x10
else
local dx = round(px - currentx)
x = x + 1
if dx < -255 or dx > 255 then
xpoints[x] = toshort(dx)
elseif dx < 0 then
fl = fl + 0x02
-- xpoints[x] = char(-dx)
xpoints[x] = chars[-dx]
elseif dx > 0 then
fl = fl + 0x12
-- xpoints[x] = char(dx)
xpoints[x] = chars[dx]
else
fl = fl + 0x02
xpoints[x] = c_zero
end
end
if py == currenty then
fl = fl + 0x20
else
local dy = round(py - currenty)
y = y + 1
if dy < -255 or dy > 255 then
ypoints[y] = toshort(dy)
elseif dy < 0 then
fl = fl + 0x04
-- ypoints[y] = char(-dy)
ypoints[y] = chars[-dy]
elseif dy > 0 then
fl = fl + 0x24
-- ypoints[y] = char(dy)
ypoints[y] = chars[dy]
else
fl = fl + 0x04
ypoints[y] = c_zero
end
end
currentx = px
currenty = py
if lastflag == fl then
if nofflags == 255 then
-- This happens in koeieletters!
lastflag = lastflag + 0x08
r = r + 1 result[r] = char(lastflag,nofflags-1)
nofflags = 1
lastflag = fl
else
nofflags = nofflags + 1
end
else -- if > 255
if nofflags == 1 then
-- r = r + 1 result[r] = char(lastflag)
r = r + 1 result[r] = chars[lastflag]
elseif nofflags == 2 then
r = r + 1 result[r] = char(lastflag,lastflag)
elseif nofflags > 2 then
lastflag = lastflag + 0x08
r = r + 1 result[r] = char(lastflag,nofflags-1)
end
nofflags = 1
lastflag = fl
end
end
if nofflags == 1 then
-- r = r + 1 result[r] = char(lastflag)
r = r + 1 result[r] = chars[lastflag]
elseif nofflags == 2 then
r = r + 1 result[r] = char(lastflag,lastflag)
elseif nofflags > 2 then
lastflag = lastflag + 0x08
r = r + 1 result[r] = char(lastflag,nofflags-1)
end
-- r = r + 1 result[r] = concat(xpoints)
-- r = r + 1 result[r] = concat(ypoints)
r = r + 1 result[r] = concat(xpoints,"",1,x)
r = r + 1 result[r] = concat(ypoints,"",1,y)
end
-- can be helper or delegated to user
local stream = concat(result,"",1,r)
local length = #stream
local padding = idiv(length+3,4) * 4 - length
if padding > 0 then
-- stream = stream .. rep("\0",padding) -- can be a repeater
if padding == 1 then
padding = "\0"
elseif padding == 2 then
padding = "\0\0"
else
padding = "\0\0\0"
end
padding = stream .. padding
end
glyph.stream = stream
end
end
end
-- end of converter
local flags = { }
local function readglyph(f,nofcontours) -- read deltas here, saves space
local points = { }
-- local instructions = { }
local contours = { } -- readintegertable(f,nofcontours,short)
for i=1,nofcontours do
contours[i] = readshort(f) + 1
end
local nofpoints = contours[nofcontours]
local nofinstructions = readushort(f)
skipbytes(f,nofinstructions)
-- because flags can repeat we don't know the amount ... in fact this is
-- not that efficient (small files but more mem)
local i = 1
while i <= nofpoints do
local flag = readbyte(f)
flags[i] = flag
if band(flag,0x08) ~= 0 then
local n = readbyte(f)
if n == 1 then
i = i + 1
flags[i] = flag
else
for j=1,n do
i = i + 1
flags[i] = flag
end
end
end
i = i + 1
end
-- first come the x coordinates, and next the y coordinates and they
-- can be repeated
local x = 0
for i=1,nofpoints do
local flag = flags[i]
-- local short = band(flag,0x04) ~= 0
-- local same = band(flag,0x20) ~= 0
if band(flag,0x02) ~= 0 then
if band(flag,0x10) ~= 0 then
x = x + readbyte(f)
else
x = x - readbyte(f)
end
elseif band(flag,0x10) ~= 0 then
-- copy
else
x = x + readshort(f)
end
points[i] = { x, 0, band(flag,0x01) ~= 0 }
end
local y = 0
for i=1,nofpoints do
local flag = flags[i]
-- local short = band(flag,0x04) ~= 0
-- local same = band(flag,0x20) ~= 0
if band(flag,0x04) ~= 0 then
if band(flag,0x20) ~= 0 then
y = y + readbyte(f)
else
y = y - readbyte(f)
end
elseif band(flag,0x20) ~= 0 then
-- copy
else
y = y + readshort(f)
end
points[i][2] = y
end
return {
type = "glyph",
points = points,
contours = contours,
nofpoints = nofpoints,
}
end
local function readcomposite(f)
local components = { }
local nofcomponents = 0
local instructions = false
while true do
local flags = readushort(f)
local index = readushort(f)
----- f_words = band(flags,0x0001) ~= 0
local f_xyarg = band(flags,0x0002) ~= 0
----- f_round = band(flags,0x0006) ~= 0 -- 2 + 4
----- f_scale = band(flags,0x0008) ~= 0
----- f_reserved = band(flags,0x0010) ~= 0
----- f_more = band(flags,0x0020) ~= 0
----- f_xyscale = band(flags,0x0040) ~= 0
----- f_matrix = band(flags,0x0080) ~= 0
----- f_instruct = band(flags,0x0100) ~= 0
----- f_usemine = band(flags,0x0200) ~= 0
----- f_overlap = band(flags,0x0400) ~= 0
local f_offset = band(flags,0x0800) ~= 0
----- f_uoffset = band(flags,0x1000) ~= 0
local xscale = 1
local xrotate = 0
local yrotate = 0
local yscale = 1
local xoffset = 0
local yoffset = 0
local base = false
local reference = false
if f_xyarg then
if band(flags,0x0001) ~= 0 then -- f_words
xoffset = readshort(f)
yoffset = readshort(f)
else
xoffset = readchar(f) -- signed byte, stupid name
yoffset = readchar(f) -- signed byte, stupid name
end
else
if band(flags,0x0001) ~= 0 then -- f_words
base = readshort(f)
reference = readshort(f)
else
base = readchar(f) -- signed byte, stupid name
reference = readchar(f) -- signed byte, stupid name
end
end
if band(flags,0x0008) ~= 0 then -- f_scale
xscale = read2dot14(f)
yscale = xscale
if f_xyarg and f_offset then
xoffset = xoffset * xscale
yoffset = yoffset * yscale
end
elseif band(flags,0x0040) ~= 0 then -- f_xyscale
xscale = read2dot14(f)
yscale = read2dot14(f)
if f_xyarg and f_offset then
xoffset = xoffset * xscale
yoffset = yoffset * yscale
end
elseif band(flags,0x0080) ~= 0 then -- f_matrix
xscale = read2dot14(f)
xrotate = read2dot14(f)
yrotate = read2dot14(f)
yscale = read2dot14(f)
if f_xyarg and f_offset then
xoffset = xoffset * sqrt(xscale ^2 + xrotate^2)
yoffset = yoffset * sqrt(yrotate^2 + yscale ^2)
end
end
nofcomponents = nofcomponents + 1
components[nofcomponents] = {
index = index,
usemine = band(flags,0x0200) ~= 0, -- f_usemine
round = band(flags,0x0006) ~= 0, -- f_round,
base = base,
reference = reference,
matrix = { xscale, xrotate, yrotate, yscale, xoffset, yoffset },
}
if band(flags,0x0100) ~= 0 then
instructions = true
end
if band(flags,0x0020) == 0 then -- f_more
break
end
end
return {
type = "composite",
components = components,
}
end
-- function readers.cff(f,offset,glyphs,doshapes) -- false == no shapes (nil or true otherwise)
-- The glyf table depends on the loca table. We have one entry to much
-- in the locations table (the last one is a dummy) because we need to
-- calculate the size of a glyph blob from the delta, although we not
-- need it in our usage (yet). We can remove the locations table when
-- we're done (todo: cleanup finalizer).
function readers.loca(f,fontdata,specification)
if specification.glyphs then
local datatable = fontdata.tables.loca
if datatable then
-- locations are relative to the glypdata table (glyf)
local offset = fontdata.tables.glyf.offset
local format = fontdata.fontheader.indextolocformat
local profile = fontdata.maximumprofile
local nofglyphs = profile and profile.nofglyphs
local locations = { }
setposition(f,datatable.offset)
if format == 1 then
if not nofglyphs then
nofglyphs = idiv(datatable.length,4) - 1
end
for i=0,nofglyphs do
locations[i] = offset + readulong(f)
end
fontdata.nofglyphs = nofglyphs
else
if not nofglyphs then
nofglyphs = idiv(datatable.length,2) - 1
end
for i=0,nofglyphs do
locations[i] = offset + readushort(f) * 2
end
end
fontdata.nofglyphs = nofglyphs
fontdata.locations = locations
end
end
end
function readers.glyf(f,fontdata,specification) -- part goes to cff module
local tableoffset = gotodatatable(f,fontdata,"glyf",specification.glyphs)
if tableoffset then
local locations = fontdata.locations
if locations then
local glyphs = fontdata.glyphs
local nofglyphs = fontdata.nofglyphs
local filesize = fontdata.filesize
local nothing = { 0, 0, 0, 0 }
local shapes = { }
local loadshapes = specification.shapes or specification.instance or specification.streams
for index=0,nofglyphs-1 do
local location = locations[index]
local length = locations[index+1] - location
if location >= filesize then
report("discarding %s glyphs due to glyph location bug",nofglyphs-index+1)
fontdata.nofglyphs = index - 1
fontdata.badfont = true
break
elseif length > 0 then
setposition(f,location)
local nofcontours = readshort(f)
glyphs[index].boundingbox = {
readshort(f), -- xmin
readshort(f), -- ymin
readshort(f), -- xmax
readshort(f), -- ymax
}
if not loadshapes then
-- save space
elseif nofcontours == 0 then
shapes[index] = readnothing(f)
elseif nofcontours > 0 then
shapes[index] = readglyph(f,nofcontours)
else
shapes[index] = readcomposite(f,nofcontours)
end
else
if loadshapes then
shapes[index] = readnothing(f)
end
glyphs[index].boundingbox = nothing
end
end
if loadshapes then
if readers.gvar then
readers.gvar(f,fontdata,specification,glyphs,shapes)
end
mergecomposites(glyphs,shapes)
if specification.instance then
if specification.streams then
repackpoints(glyphs,shapes)
else
contours2outlines_shaped(glyphs,shapes,specification.shapes)
end
elseif specification.shapes then
if specification.streams then
repackpoints(glyphs,shapes)
else
contours2outlines_normal(glyphs,shapes)
end
elseif specification.streams then
repackpoints(glyphs,shapes)
end
end
end
end
end
-- gvar is a bit crazy format and one can really wonder if the bit-jugling obscurity
-- is still needed in these days .. cff is much nicer with these blends while the ttf
-- coding variant looks quite horrible
local function readtuplerecord(f,nofaxis)
local record = { }
for i=1,nofaxis do
record[i] = read2dot14(f)
end
return record
end
-- (1) the first is a real point the rest deltas
-- (2) points can be present more than once (multiple deltas then)
local function readpoints(f)
local count = readbyte(f)
if count == 0 then
-- second byte not used, deltas for all point numbers
return nil, 0 -- todo
else
if count < 128 then
-- no second byte, use count
elseif band(count,0x80) ~= 0 then
count = band(count,0x7F) * 256 + readbyte(f)
else
-- bad news
end
local points = { }
local p = 0
local n = 1 -- indices
while p < count do
local control = readbyte(f)
local runreader = band(control,0x80) ~= 0 and readushort or readbyte
local runlength = band(control,0x7F)
for i=1,runlength+1 do
n = n + runreader(f)
p = p + 1
points[p] = n
end
end
return points, p
end
end
local function readdeltas(f,nofpoints)
local deltas = { }
local p = 0
local z = 0
while nofpoints > 0 do
local control = readbyte(f)
if not control then
break
end
local allzero = band(control,0x80) ~= 0
local runlength = band(control,0x3F) + 1
if allzero then
z = z + runlength
else
local runreader = band(control,0x40) ~= 0 and readshort or readinteger
if z > 0 then
for i=1,z do
p = p + 1
deltas[p] = 0
end
z = 0
end
for i=1,runlength do
p = p + 1
deltas[p] = runreader(f)
end
end
nofpoints = nofpoints - runlength
end
-- saves space
-- if z > 0 then
-- for i=1,z do
-- p = p + 1
-- deltas[p] = 0
-- end
-- end
if p > 0 then
-- forget about trailing zeros
return deltas
else
-- forget about all zeros
end
end
local function readdeltas(f,nofpoints)
local deltas = { }
local p = 0
while nofpoints > 0 do
local control = readbyte(f)
if control then
local allzero = band(control,0x80) ~= 0
local runlength = band(control,0x3F) + 1
if allzero then
for i=1,runlength do
p = p + 1
deltas[p] = 0
end
else
local runreader = band(control,0x40) ~= 0 and readshort or readinteger
for i=1,runlength do
p = p + 1
deltas[p] = runreader(f)
end
end
nofpoints = nofpoints - runlength
else
-- it happens
break
end
end
-- saves space
if p > 0 then
return deltas
else
-- forget about all zeros
end
end
function readers.gvar(f,fontdata,specification,glyphdata,shapedata)
-- this is one of the messiest tables
local instance = specification.instance
if not instance then
return
end
local factors = specification.factors
if not factors then
return
end
local tableoffset = gotodatatable(f,fontdata,"gvar",specification.variable or specification.shapes)
if tableoffset then
local version = readulong(f) -- 1.0
local nofaxis = readushort(f)
local noftuples = readushort(f)
local tupleoffset = tableoffset + readulong(f)
local nofglyphs = readushort(f)
local flags = readushort(f)
local dataoffset = tableoffset + readulong(f)
local data = { }
local tuples = { }
local glyphdata = fontdata.glyphs
local dowidth = not fontdata.variabledata.hvarwidths
-- there is one more offset (so that one can calculate the size i suppose)
-- so we could test for overflows but we simply assume sane font files
if band(flags,0x0001) ~= 0 then
for i=1,nofglyphs+1 do
data[i] = dataoffset + readulong(f)
end
else
for i=1,nofglyphs+1 do
data[i] = dataoffset + 2*readushort(f)
end
end
--
if noftuples > 0 then
setposition(f,tupleoffset)
for i=1,noftuples do
tuples[i] = readtuplerecord(f,nofaxis)
end
end
local nextoffset = false
local startoffset = data[1]
for i=1,nofglyphs do -- hm one more cf spec
nextoffset = data[i+1]
local glyph = glyphdata[i-1]
local name = trace_deltas and glyph.name
if startoffset == nextoffset then
if name then
report("no deltas for glyph %a",name)
end
else
local shape = shapedata[i-1] -- todo 0
if not shape then
if name then
report("no shape for glyph %a",name)
end
else
lastoffset = startoffset
setposition(f,startoffset)
local flags = readushort(f)
local count = band(flags,0x0FFF)
local offset = startoffset + readushort(f) -- to serialized
local deltas = { }
local allpoints = (shape.nofpoints or 0) -- + 1
local shared = false
local nofshared = 0
if band(flags,0x8000) ~= 0 then -- has shared points
-- go to the packed stream (get them once)
local current = getposition(f)
setposition(f,offset)
shared, nofshared = readpoints(f)
offset = getposition(f)
setposition(f,current)
-- and back to the table
end
for j=1,count do
local size = readushort(f) -- check
local flags = readushort(f)
local index = band(flags,0x0FFF)
local haspeak = band(flags,0x8000) ~= 0
local intermediate = band(flags,0x4000) ~= 0
local private = band(flags,0x2000) ~= 0
local peak = nil
local start = nil
local stop = nil
local xvalues = nil
local yvalues = nil
local points = shared -- we default to shared
local nofpoints = nofshared -- we default to shared
-- local advance = 4
if haspeak then
peak = readtuplerecord(f,nofaxis)
-- advance = advance + 2*nofaxis
else
if index+1 > #tuples then
report("error, bad tuple index",index)
end
peak = tuples[index+1] -- hm, needs checking, only peak?
end
if intermediate then
start = readtuplerecord(f,nofaxis)
stop = readtuplerecord(f,nofaxis)
-- advance = advance + 4*nofaxis
end
-- get the deltas
if size > 0 then
local current = getposition(f)
-- goto the packed stream
setposition(f,offset)
if private then
points, nofpoints = readpoints(f)
end -- else
if nofpoints == 0 then
nofpoints = allpoints + 4
end
if nofpoints > 0 then
-- a nice test is to do only one
xvalues = readdeltas(f,nofpoints)
yvalues = readdeltas(f,nofpoints)
end
-- resync offset
offset = offset + size
-- back to the table
setposition(f,current)
end
if not xvalues and not yvalues then
points = nil
end
local s = 1
for i=1,nofaxis do
local f = factors[i]
local peak = peak and peak [i] or 0
-- local start = start and start[i] or 0
-- local stop = stop and stop [i] or 0
local start = start and start[i] or (peak < 0 and peak or 0)
local stop = stop and stop [i] or (peak > 0 and peak or 0)
-- do we really need these tests ... can't we assume sane values
if start > peak or peak > stop then
-- * 1
elseif start < 0 and stop > 0 and peak ~= 0 then
-- * 1
elseif peak == 0 then
-- * 1
elseif f < start or f > stop then
-- * 0
s = 0
break
elseif f < peak then
-- s = - s * (f - start) / (peak - start)
s = s * (f - start) / (peak - start)
elseif f > peak then
s = s * (stop - f) / (stop - peak)
else
-- * 1
end
end
if s == 0 then
if name then
report("no deltas applied for glyph %a",name)
end
else
deltas[#deltas+1] = {
factor = s,
points = points,
xvalues = xvalues,
yvalues = yvalues,
}
end
end
if shape.type == "glyph" then
applyaxis(glyph,shape,deltas,dowidth)
else
-- todo: args_are_xy_values mess .. i have to be really bored
-- and motivated to deal with it
shape.deltas = deltas
end
end
end
startoffset = nextoffset
end
end
end