diff options
Diffstat (limited to 'tex/context/base/mkxl/font-ttf.lmt')
-rw-r--r-- | tex/context/base/mkxl/font-ttf.lmt | 1496 |
1 files changed, 1496 insertions, 0 deletions
diff --git a/tex/context/base/mkxl/font-ttf.lmt b/tex/context/base/mkxl/font-ttf.lmt new file mode 100644 index 000000000..fa0e3c494 --- /dev/null +++ b/tex/context/base/mkxl/font-ttf.lmt @@ -0,0 +1,1496 @@ +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 +----- band, rshift = bit32.band, bit32.rshift +local sqrt, round, abs, min, max = math.sqrt, math.round, math.abs, math.min, math.max +local char, rep = string.char, string.rep +local concat = table.concat +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] = { + -- unifractur : u n + -- seguiemj : 0x270E 0x2710 + xscale * x + xrotate * y + xoffset, + yscale * y + yrotate * x + yoffset, +-- xscale * x + yrotate * y + xoffset, +-- xrotate * x + yscale * y + 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. + +-- dowidth is kind of hack ... fonts are not always ok wrt these extra points + +local xv = { } -- we share this cache +local yv = { } -- we share this cache + +local function applyaxis(glyph,shape,deltas,dowidth) + local points = shape.points + if points then + local nofpoints = #points + local dw = 0 + local dl = 0 + for i=1,#deltas do + local deltaset = deltas[i] + local xvalues = deltaset.xvalues + local yvalues = deltaset.yvalues + if xvalues and yvalues then + local dpoints = deltaset.points + local factor = deltaset.factor + if dpoints then + local cnt = #dpoints + if dowidth then + cnt = cnt - 4 + end + if cnt > 0 then + -- Not the most efficient solution but we seldom do this. We + -- actually need to avoid the extra points here but I'll deal + -- with that when needed. + local contours = shape.contours + local nofcontours = #contours + local first = 1 + local firstindex = 1 + for contour=1,nofcontours do + local last = contours[contour] + if last >= first then + local lastindex = cnt + if firstindex < cnt then + for currentindex=firstindex,cnt do + local found = dpoints[currentindex] + if found <= first then + firstindex = currentindex + end + if found == last then + lastindex = currentindex + break + elseif found > last then + -- \definefontfeature[book][default][axis={weight=800}] + -- \definefont[testfont][file:Commissioner-vf-test.ttf*book] + -- \testfont EΘÄΞ + while lastindex > 1 and dpoints[lastindex] > last do + lastindex = lastindex - 1 + end + -- + break + end + end + end + -- print("unicode: ",glyph.unicode or "?") + -- print("contour: ",first,contour,last) + -- print("index : ",firstindex,lastindex,cnt) + -- print("points : ",dpoints[firstindex],dpoints[lastindex]) + local function find(i) + local prv = lastindex + for j=firstindex,lastindex do + local nxt = dpoints[j] -- we could save this lookup when we return it + if nxt == i then + return false, j, false + elseif nxt > i then + return prv, false, j + end + prv = j + end + return prv, false, firstindex + end + -- We need the first and last points untouched so we first + -- collect data. + for point=first,last do + local d1, d2, d3 = find(point) + local p2 = points[point] + if d2 then + xv[point] = xvalues[d2] + yv[point] = yvalues[d2] + else + local n1 = dpoints[d1] + local n3 = dpoints[d3] + -- Some day I need to figure out these extra points but + -- I'll wait till the standard is more clear and fonts + -- become better (ntg-context: fraunces.ttf > abcdef). + if n1 > nofpoints then + n1 = nofpoints + end + if n3 > nofpoints then + n3 = nofpoints + end + -- + local p1 = points[n1] + local p3 = points[n3] + local p1x = p1[1] + local p2x = p2[1] + local p3x = p3[1] + local p1y = p1[2] + local p2y = p2[2] + local p3y = p3[2] + local x1 = xvalues[d1] + local y1 = yvalues[d1] + local x3 = xvalues[d3] + local y3 = yvalues[d3] + -- + local fx + local fy + -- + if p1x == p3x then + if x1 == x3 then + fx = x1 + else + fx = 0 + end + elseif p2x <= min(p1x,p3x) then + if p1x < p3x then + fx = x1 + else + fx = x3 + end + elseif p2x >= max(p1x,p3x) then + if p1x > p3x then + fx = x1 + else + fx = x3 + end + else + fx = (p2x - p1x)/(p3x - p1x) +-- fx = round(fx) + fx = (1 - fx) * x1 + fx * x3 + end + -- + if p1y == p3y then + if y1 == y3 then + fy = y1 + else + fy = 0 + end + elseif p2y <= min(p1y,p3y) then + if p1y < p3y then + fy = y1 + else + fy = y3 + end + elseif p2y >= max(p1y,p3y) then + if p1y > p3y then + fy = y1 + else + fy = y3 + end + else + fy = (p2y - p1y)/(p3y - p1y) +-- fy = round(fy) + fy = (1 - fy) * y1 + fy * y3 + end + -- -- maybe: + -- if p1y ~= p3y then + -- fy = (p2y - p1y)/(p3y - p1y) + -- fy = (1 - fy) * y1 + fy * y3 + -- elseif abs(p1y-p2y) < abs(p3y-p2y) then + -- fy = y1 + -- else + -- fy = y3 + -- end + -- + xv[point] = fx + yv[point] = fy + end + end + if lastindex < cnt then + firstindex = lastindex + 1 + end + end + first = last + 1 + end + + for i=1,nofpoints do + local pi = points[i] + local fx = xv[i] + local fy = yv[i] + if fx ~= 0 then + pi[1] = pi[1] + factor * fx + end + if fy ~= 0 then + pi[2] = pi[2] + factor * fy + end + end + else + report("bad deltapoint data, maybe a missing hvar table") + end + else + for i=1,nofpoints do + local p = points[i] + local x = xvalues[i] + if x then + local y = yvalues[i] + if x ~= 0 then + p[1] = p[1] + factor * x + end + if y ~= 0 then + p[2] = p[2] + factor * y + end + else + break + end + end + end + if dowidth then + local h = nofpoints + 2 -- weird, the example font seems to have left first + local l = nofpoints + 1 + ----- v = nofpoints + 3 + ----- t = nofpoints + 4 + 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 + -- See readers.hvar where we set the delta lsb as well as the adapted + -- width. At this point we do know the boundingbox's llx. The xmax is + -- not that relevant. It needs more testing! + -- + xmin = glyph.boundingbox[1] + -- + local dlsb = glyph.dlsb + if dlsb then + xmin = xmin + dlsb + glyph.dlsb = nil -- save space + 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] +-- t[k] = char((k >> 8) & 0xFF,k & 0xFF) return t[k] +-- end) + +local function toushort(n) + -- return char(band(rshift(n,8),0xFF),band(n,0xFF)) + return char((n >> 8) & 0xFF,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 char((n >> 8) & 0xFF,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 = ((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 (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 = (flag & 0x02) ~= 0 + -- local same = (flag & 0x10) ~= 0 + if (flag & 0x02) ~= 0 then + if (flag & 0x10) ~= 0 then + x = x + readbyte(f) + else + x = x - readbyte(f) + end + elseif (flag & 0x10) ~= 0 then + -- copy + else + x = x + readshort(f) + end + points[i] = { x, 0, (flag & 0x01) ~= 0 } + end + local y = 0 + for i=1,nofpoints do + local flag = flags[i] + -- local short = (flag & 0x04) ~= 0 + -- local same = (flag & 0x20) ~= 0 + if (flag & 0x04) ~= 0 then + if (flag & 0x20) ~= 0 then + y = y + readbyte(f) + else + y = y - readbyte(f) + end + elseif (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 = (flags & 0x0001) ~= 0 + local f_xyarg = (flags & 0x0002) ~= 0 + ----- f_round = (flags & 0x0006) ~= 0 -- 2 + 4 + ----- f_scale = (flags & 0x0008) ~= 0 + ----- f_reserved = (flags & 0x0010) ~= 0 + ----- f_more = (flags & 0x0020) ~= 0 + ----- f_xyscale = (flags & 0x0040) ~= 0 + ----- f_matrix = (flags & 0x0080) ~= 0 + ----- f_instruct = (flags & 0x0100) ~= 0 + ----- f_usemine = (flags & 0x0200) ~= 0 + ----- f_overlap = (flags & 0x0400) ~= 0 + local f_offset = (flags & 0x0800) ~= 0 + ----- f_uoffset = (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 (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 (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 (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 (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 (flags & 0x0080) ~= 0 then -- f_matrix + xscale = read2dot14(f) -- xxpart + xrotate = read2dot14(f) -- yxpart + yrotate = read2dot14(f) -- xypart + yscale = read2dot14(f) -- yypart + if f_xyarg and f_offset then + xoffset = xoffset * sqrt(xscale ^2 + yrotate^2) -- was xrotate + yoffset = yoffset * sqrt(xrotate^2 + yscale ^2) -- was yrotate + end + end + nofcomponents = nofcomponents + 1 + components[nofcomponents] = { + index = index, + usemine = (flags & 0x0200) ~= 0, -- f_usemine + round = (flags & 0x0006) ~= 0, -- f_round, + base = base, + reference = reference, + matrix = { xscale, xrotate, yrotate, yscale, xoffset, yoffset }, + } + if (flags & 0x0100) ~= 0 then + instructions = true + end + if (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 = (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 = (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 (count & 0x80) ~= 0 then + count = (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 = (control & 0x80) ~= 0 and readushort or readbyte + local runlength = (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 + while nofpoints > 0 do + local control = readbyte(f) + if control then + local allzero = (control & 0x80) ~= 0 + local runlength = (control & 0x3F) + 1 + if allzero then + for i=1,runlength do + p = p + 1 + deltas[p] = 0 + end + else + local runreader = (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 (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 = (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 (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 = (flags & 0x0FFF) + local haspeak = (flags & 0x8000) ~= 0 + local intermediate = (flags & 0x4000) ~= 0 + local private = (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) -- or 1 ? +-- local stop = stop and stop [i] or (peak > 0 and peak or 1) -- or 1 ? + -- 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) + 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 |