if not modules then modules = { } end modules ['font-ttf'] = { version = 1.001, 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=1,#glyphs do for index=0,#glyphs-1 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-1 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 nofflags = nofflags + 1 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