From 8e0089484acf80066e7393b1245d59dda211be09 Mon Sep 17 00:00:00 2001 From: Hans Hagen Date: Mon, 20 Mar 2017 18:51:53 +0100 Subject: 2017-03-20 17:38:00 --- tex/context/base/mkii/cont-new.mkii | 2 +- tex/context/base/mkii/context.mkii | 2 +- tex/context/base/mkiv/cldf-int.lua | 4 +- tex/context/base/mkiv/cont-new.mkiv | 2 +- tex/context/base/mkiv/context.mkiv | 2 +- tex/context/base/mkiv/font-cff.lua | 894 +++-- tex/context/base/mkiv/font-con.lua | 38 +- tex/context/base/mkiv/font-ctx.lua | 269 +- tex/context/base/mkiv/font-def.lua | 22 +- tex/context/base/mkiv/font-dsp.lua | 1591 ++++++-- tex/context/base/mkiv/font-fil.mkvi | 4 + tex/context/base/mkiv/font-lib.mkvi | 8 +- tex/context/base/mkiv/font-mps.lua | 8 +- tex/context/base/mkiv/font-onr.lua | 2 +- tex/context/base/mkiv/font-otc.lua | 27 + tex/context/base/mkiv/font-oti.lua | 100 + tex/context/base/mkiv/font-otl.lua | 80 +- tex/context/base/mkiv/font-otr.lua | 747 ++-- tex/context/base/mkiv/font-ott.lua | 7 + tex/context/base/mkiv/font-oup.lua | 132 +- tex/context/base/mkiv/font-shp.lua | 232 +- tex/context/base/mkiv/font-syn.lua | 42 +- tex/context/base/mkiv/font-ttf.lua | 1060 +++++- tex/context/base/mkiv/l-lpeg.lua | 8 +- tex/context/base/mkiv/l-md5.lua | 3 + tex/context/base/mkiv/l-string.lua | 5 + tex/context/base/mkiv/lpdf-ini.lua | 28 +- tex/context/base/mkiv/luat-cbk.lua | 8 +- tex/context/base/mkiv/lxml-tab.lua | 7 +- tex/context/base/mkiv/m-fonts-plugins.mkiv | 406 +++ tex/context/base/mkiv/mlib-pdf.lua | 2 +- tex/context/base/mkiv/node-tra.lua | 10 +- tex/context/base/mkiv/status-files.pdf | Bin 25672 -> 25633 bytes tex/context/base/mkiv/status-lua.pdf | Bin 420238 -> 422419 bytes tex/context/base/mkiv/util-fil.lua | 50 +- tex/context/interface/mkiv/i-context.pdf | Bin 804052 -> 804058 bytes tex/context/interface/mkiv/i-readme.pdf | Bin 60772 -> 60772 bytes tex/context/modules/mkiv/m-ipsum.mkiv | 1 + tex/context/modules/mkiv/s-fonts-shapes.lua | 2 +- tex/context/modules/mkiv/s-fonts-shapes.mkiv | 2 +- tex/context/modules/mkiv/s-fonts-variable.lua | 264 ++ tex/context/modules/mkiv/s-fonts-variable.mkiv | 46 + tex/generic/context/luatex/luatex-core.lua | 91 +- tex/generic/context/luatex/luatex-fonts-merged.lua | 3791 +++++++++++++++----- 44 files changed, 7573 insertions(+), 2426 deletions(-) create mode 100644 tex/context/base/mkiv/m-fonts-plugins.mkiv create mode 100644 tex/context/modules/mkiv/s-fonts-variable.lua create mode 100644 tex/context/modules/mkiv/s-fonts-variable.mkiv (limited to 'tex') diff --git a/tex/context/base/mkii/cont-new.mkii b/tex/context/base/mkii/cont-new.mkii index eed45203f..547fdc99d 100644 --- a/tex/context/base/mkii/cont-new.mkii +++ b/tex/context/base/mkii/cont-new.mkii @@ -11,7 +11,7 @@ %C therefore copyrighted by \PRAGMA. See mreadme.pdf for %C details. -\newcontextversion{2017.03.02 22:23} +\newcontextversion{2017.03.20 17:33} %D This file is loaded at runtime, thereby providing an %D excellent place for hacks, patches, extensions and new diff --git a/tex/context/base/mkii/context.mkii b/tex/context/base/mkii/context.mkii index 6a5f25120..334f20b95 100644 --- a/tex/context/base/mkii/context.mkii +++ b/tex/context/base/mkii/context.mkii @@ -20,7 +20,7 @@ %D your styles an modules. \edef\contextformat {\jobname} -\edef\contextversion{2017.03.02 22:23} +\edef\contextversion{2017.03.20 17:33} %D For those who want to use this: diff --git a/tex/context/base/mkiv/cldf-int.lua b/tex/context/base/mkiv/cldf-int.lua index cd4db2e90..a97eadf35 100644 --- a/tex/context/base/mkiv/cldf-int.lua +++ b/tex/context/base/mkiv/cldf-int.lua @@ -26,7 +26,7 @@ local trace_define = false trackers.register("context.define", function(v) tr interfaces = interfaces or { } -_clmh_ = utilities.parsers.settings_to_array +_clmh_ = utilities.parsers.settings_to_hash _clma_ = utilities.parsers.settings_to_array local starters, stoppers, macros, stack = { }, { }, { }, { } @@ -66,6 +66,8 @@ _clmn_ = tonumber local estart = interfaces.elements.start local estop = interfaces.elements.stop +-- this is a bit old definition ... needs to be modernized + function interfaces.definecommand(name,specification) -- name is optional if type(name) == "table" then specification = name diff --git a/tex/context/base/mkiv/cont-new.mkiv b/tex/context/base/mkiv/cont-new.mkiv index c603af8ba..ec63ac14b 100644 --- a/tex/context/base/mkiv/cont-new.mkiv +++ b/tex/context/base/mkiv/cont-new.mkiv @@ -11,7 +11,7 @@ %C therefore copyrighted by \PRAGMA. See mreadme.pdf for %C details. -\newcontextversion{2017.03.02 22:23} +\newcontextversion{2017.03.20 17:33} %D This file is loaded at runtime, thereby providing an excellent place for %D hacks, patches, extensions and new features. diff --git a/tex/context/base/mkiv/context.mkiv b/tex/context/base/mkiv/context.mkiv index d12ba09be..0a70af437 100644 --- a/tex/context/base/mkiv/context.mkiv +++ b/tex/context/base/mkiv/context.mkiv @@ -39,7 +39,7 @@ %D up and the dependencies are more consistent. \edef\contextformat {\jobname} -\edef\contextversion{2017.03.02 22:23} +\edef\contextversion{2017.03.20 17:33} \edef\contextkind {beta} %D For those who want to use this: diff --git a/tex/context/base/mkiv/font-cff.lua b/tex/context/base/mkiv/font-cff.lua index 37436fbcf..eb0a2c1be 100644 --- a/tex/context/base/mkiv/font-cff.lua +++ b/tex/context/base/mkiv/font-cff.lua @@ -13,15 +13,21 @@ if not modules then modules = { } end modules ['font-cff'] = { -- This is a heavy one as it is a rather packed format. We don't need al the information -- now but we might need it later (who know what magic we can do with metapost). So at -- some point this might become a module. We just follow Adobe Technical Notes #5176 and --- #5177. In case of doubt I looked in the fontforge code that comes with LuaTeX. +-- #5177. In case of doubt I looked in the fontforge code that comes with LuaTeX but +-- it's not the easiest source to read (and doesn't cover cff2). -- For now we save the segments in a list of segments with the operator last in an entry -- because that reflects the original. But it might make more sense to use a single array -- per segment. For pdf a simple concat works ok, but for other purposes a operator first -- flush is nicer. +-- +-- In retrospect I could have looked into the backend code of LuaTeX but it never +-- occurred to me that parsing charstrings was needed there (which has to to +-- with merging subroutines and flattening, not so much with calculations.) On +-- the other hand, we can now feed back cff2 stuff. local next, type, tonumber = next, type, tonumber -local byte, gmatch = string.byte, string.gmatch +local byte, char, gmatch = string.byte, string.char, string.gmatch local concat, remove = table.concat, table.remove local floor, abs, round, ceil, min, max = math.floor, math.abs, math.round, math.ceil, math.min, math.max local P, C, R, S, C, Cs, Ct = lpeg.P, lpeg.C, lpeg.R, lpeg.S, lpeg.C, lpeg.Cs, lpeg.Ct @@ -51,6 +57,8 @@ local parsecharstring local parsecharstrings local resetcharstrings local parseprivates +local startparsing +local stopparsing local defaultstrings = { [0] = -- taken from ff ".notdef", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", @@ -131,13 +139,21 @@ local cffreaders = { local function readheader(f) local offset = getposition(f) + local major = readbyte(f) local header = { offset = offset, - major = readbyte(f), + major = major, minor = readbyte(f), size = readbyte(f), -- headersize - osize = readbyte(f), -- for offsets to start } + if major == 1 then + header.dsize = readbyte(f) -- list of dict offsets + elseif major == 2 then + header.dsize = readushort(f) -- topdict size + else + -- I'm probably no longer around by then and we use AI's to + -- handle this kind of stuff, if we typeset documents at all. + end setposition(f,offset+header.size) return header end @@ -145,8 +161,8 @@ end -- The indexes all look the same, so we share a loader. We could pass a handler -- and run over the array but why bother, we only have a few uses. -local function readlengths(f) - local count = readushort(f) +local function readlengths(f,longcount) + local count = longcount and readulong(f) or readushort(f) if count == 0 then return { } end @@ -160,7 +176,12 @@ local function readlengths(f) local previous = read(f) for i=1,count do local offset = read(f) - lengths[i] = offset - previous + local length = offset - previous + if length < 0 then + report("bad offset: %i",length) + length = 0 + end + lengths[i] = length previous = offset end return lengths @@ -218,7 +239,8 @@ end do - -- We use a closure so that we don't need to pass too much around. + -- We use a closure so that we don't need to pass too much around. For cff2 we can + -- at some point use a simple version as there is less. local stack = { } local top = 0 @@ -272,7 +294,7 @@ do result.encoding = stack[top] top = 0 end - + P("\17") / function() + + P("\17") / function() -- valid cff2 result.charstrings = stack[top] top = 0 end @@ -285,19 +307,32 @@ do end + P("\19") / function() result.subroutines = stack[top] + top = 0 -- new, forgotten ? end + P("\20") / function() result.defaultwidthx = stack[top] + top = 0 -- new, forgotten ? end + P("\21") / function() result.nominalwidthx = stack[top] + top = 0 -- new, forgotten ? + end + -- + P("\22") / function() -- reserved + -- end + -- + P("\23") / function() -- reserved + -- end + + P("\24") / function() -- new in cff2 + result.vstore = stack[top] + top = 0 + end + + P("\25") / function() -- new in cff2 + result.maxstack = stack[top] + top = 0 end - -- + P("\22") / function() end -- reserved - -- + P("\23") / function() end -- reserved - -- + P("\24") / function() end -- reserved - -- + P("\25") / function() end -- reserved - -- + P("\26") / function() end -- reserved - -- + P("\27") / function() end -- reserved + -- + P("\26") / function() -- reserved + -- end + -- + P("\27") / function() -- reserved + -- end local p_double = P("\12") * ( P("\00") / function() @@ -328,7 +363,7 @@ do result.charstringtype = stack[top] top = 0 end - + P("\07") / function() + + P("\07") / function() -- valid cff2 result.fontmatrix = { unpack(stack,1,6) } top = 0 end @@ -378,11 +413,11 @@ do result.cid.uidbase = stack[top] top = 0 end - + P("\36") / function() + + P("\36") / function() -- valid cff2 result.cid.fdarray = stack[top] top = 0 end - + P("\37") / function() + + P("\37") / function() -- valid cff2 result.cid.fdselect = stack[top] top = 0 end @@ -503,12 +538,12 @@ do + p_unsupported )^1 - parsedictionaries = function(data,dictionaries) + parsedictionaries = function(data,dictionaries,what) stack = { } strings = data.strings for i=1,#dictionaries do top = 0 - result = { + result = what == "cff" and { monospaced = false, italicangle = 0, underlineposition = -100, @@ -526,6 +561,13 @@ do fonttype = 0, count = 8720, } + } or { + charstringtype = 2, + charset = 0, + vstore = 0, + cid = { + -- nothing yet + }, } lpegmatch(p_dictionary,dictionaries[i]) dictionaries[i] = result @@ -580,23 +622,32 @@ do -- because there quite some variants are done in one helper with a lot of -- testing for states. - local x = 0 - local y = 0 - local width = false - local r = 0 - local stems = 0 - local globalbias = 0 - local localbias = 0 - local globals = false - local locals = false - local depth = 1 - local xmin = 0 - local xmax = 0 - local ymin = 0 - local ymax = 0 - local checked = false - local keepcurve = false - local version = 2 + local x = 0 + local y = 0 + local width = false + local r = 0 + local stems = 0 + local globalbias = 0 + local localbias = 0 + local nominalwidth = 0 + local defaultwidth = 0 + local charset = false + local globals = false + local locals = false + local depth = 1 + local xmin = 0 + local xmax = 0 + local ymin = 0 + local ymax = 0 + local checked = false + local keepcurve = false + local version = 2 + local regions = false + local nofregions = 0 + local region = false + local factors = false + local axis = false + local vsindex = 0 local function showstate(where) report("%w%-10s : [%s] n=%i",depth*2,where,concat(stack," ",1,top),top) @@ -615,7 +666,7 @@ do -- some back. I inlined some of then and a bit speed can be gained by more -- inlining but not that much. - local function moveto() + local function xymoveto() if keepcurve then r = r + 1 result[r] = { x, y, "m" } @@ -668,7 +719,15 @@ do end end - local function lineto() -- we could inline + local function moveto() + if trace_charstrings then + showstate("moveto") + end + top = 0 -- forgotten + xymoveto() + end + + local function xylineto() -- we could inline, no blend if keepcurve then r = r + 1 result[r] = { x, y, "l" } @@ -721,7 +780,10 @@ do end end - local function curveto(x1,y1,x2,y2,x3,y3) + local function xycurveto(x1,y1,x2,y2,x3,y3) -- called local so no blend here + if trace_charstrings then + showstate("curveto") + end if keepcurve then r = r + 1 result[r] = { x1, y1, x2, y2, x3, y3, "c" } @@ -747,7 +809,7 @@ do if top > 2 then width = stack[1] if trace_charstrings then - showvalue("width",width) + showvalue("backtrack width",width) end else width = true @@ -759,7 +821,7 @@ do x = x + stack[top-1] -- dx1 y = y + stack[top] -- dy1 top = 0 - moveto() + xymoveto() end local function hmoveto() @@ -767,7 +829,7 @@ do if top > 1 then width = stack[1] if trace_charstrings then - showvalue("width",width) + showvalue("backtrack width",width) end else width = true @@ -786,7 +848,7 @@ do if top > 1 then width = stack[1] if trace_charstrings then - showvalue("width",width) + showvalue("backtrack width",width) end else width = true @@ -807,7 +869,7 @@ do for i=1,top,2 do x = x + stack[i] -- dxa y = y + stack[i+1] -- dya - lineto() + xylineto() end top = 0 end @@ -871,7 +933,7 @@ do local by = ay + stack[i+3] -- dyb x = bx + stack[i+4] -- dxc y = by + stack[i+5] -- dyc - curveto(ax,ay,bx,by,x,y) + xycurveto(ax,ay,bx,by,x,y) end top = 0 end @@ -892,7 +954,7 @@ do local by = ay + stack[i+2] -- dyb x = bx + stack[i+3] -- dxc y = by - curveto(ax,ay,bx,by,x,y) + xycurveto(ax,ay,bx,by,x,y) end top = 0 end @@ -914,7 +976,7 @@ do local by = ay + stack[i+2] -- dyb x = bx y = by + stack[i+3] -- dyc - curveto(ax,ay,bx,by,x,y) + xycurveto(ax,ay,bx,by,x,y) d = 0 end top = 0 @@ -952,7 +1014,7 @@ do end swap = true end - curveto(ax,ay,bx,by,x,y) + xycurveto(ax,ay,bx,by,x,y) end top = 0 end @@ -982,11 +1044,11 @@ do local by = ay + stack[i+3] -- dyb x = bx + stack[i+4] -- dxc y = by + stack[i+5] -- dyc - curveto(ax,ay,bx,by,x,y) + xycurveto(ax,ay,bx,by,x,y) end x = x + stack[top-1] -- dxc y = y + stack[top] -- dyc - lineto() + xylineto() top = 0 end @@ -998,7 +1060,7 @@ do for i=1,top-6,2 do x = x + stack[i] y = y + stack[i+1] - lineto() + xylineto() end end local ax = x + stack[top-5] @@ -1007,7 +1069,7 @@ do local by = ay + stack[top-2] x = bx + stack[top-1] y = by + stack[top] - curveto(ax,ay,bx,by,x,y) + xycurveto(ax,ay,bx,by,x,y) top = 0 end @@ -1023,14 +1085,14 @@ do local by = ay + stack[4] -- dy2 local cx = bx + stack[5] -- dx3 local cy = by + stack[6] -- dy3 - curveto(ax,ay,bx,by,cx,cy) + xycurveto(ax,ay,bx,by,cx,cy) local dx = cx + stack[7] -- dx4 local dy = cy + stack[8] -- dy4 local ex = dx + stack[9] -- dx5 local ey = dy + stack[10] -- dy5 x = ex + stack[11] -- dx6 y = ey + stack[12] -- dy6 - curveto(dx,dy,ex,ey,x,y) + xycurveto(dx,dy,ex,ey,x,y) top = 0 end @@ -1044,13 +1106,13 @@ do local by = ay + stack[3] -- dy2 local cx = bx + stack[4] -- dx3 local cy = by - curveto(ax,ay,bx,by,cx,cy) + xycurveto(ax,ay,bx,by,cx,cy) local dx = cx + stack[5] -- dx4 local dy = by local ex = dx + stack[6] -- dx5 local ey = y x = ex + stack[7] -- dx6 - curveto(dx,dy,ex,ey,x,y) + xycurveto(dx,dy,ex,ey,x,y) top = 0 end @@ -1064,13 +1126,13 @@ do local by = ay + stack[4] -- dy2 local cx = bx + stack[5] -- dx3 local cy = by - curveto(ax,ay,bx,by,cx,cy) + xycurveto(ax,ay,bx,by,cx,cy) local dx = cx + stack[6] -- dx4 local dy = by local ex = dx + stack[7] -- dx5 local ey = dy + stack[8] -- dy5 x = ex + stack[9] -- dx6 - curveto(dx,dy,ex,ey,x,y) + xycurveto(dx,dy,ex,ey,x,y) top = 0 end @@ -1084,7 +1146,7 @@ do local by = ay + stack[4] --dy2 local cx = bx + stack[5] --dx3 local cy = by + stack[6] --dy3 - curveto(ax,ay,bx,by,cx,cy) + xycurveto(ax,ay,bx,by,cx,cy) local dx = cx + stack[7] --dx4 local dy = cy + stack[8] --dy4 local ex = dx + stack[9] --dx5 @@ -1094,7 +1156,7 @@ do else y = ey + stack[11] end - curveto(dx,dy,ex,ey,x,y) + xycurveto(dx,dy,ex,ey,x,y) top = 0 end @@ -1253,7 +1315,109 @@ do top = 0 end - -- so far for unsupported postscript + -- So far for unsupported postscript. Now some cff2 magic. As I still need + -- to wrap my head around the rather complex variable font specification + -- with regions and axis, the following approach kind of works but is more + -- some trial and error trick. It's still not clear how much of the complex + -- truetype description applies to cff. + + local reginit = false + + local function updateregions(n) -- n + 1 + if regions then + local current = regions[n] or regions[1] + nofregions = #current + if axis and n ~= reginit then + factors = { } + for i=1,nofregions do + local region = current[i] + local s = 1 + for j=1,#axis do + local f = axis[j] + local r = region[j] + local start = r.start + local peak = r.peak + local stop = r.stop + 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 + factors[i] = s + end + end + end + reginit = n + end + + local function setvsindex() + local vsindex = stack[top] + if trace_charstrings then + showstate(formatters["vsindex %i"](vsindex)) + end + updateregions(vsindex) + top = top - 1 + end + + local function blend() + local n = stack[top] + top = top - 1 + if axis then + -- x (r1x,r2x,r3x) + -- (x,y) (r1x,r2x,r3x) (r1y,r2y,r3y) + if trace_charstrings then + local t = top - nofregions * n + local m = t - n + for i=1,n do + local k = m + i + local d = m + n + (i-1)*nofregions + local old = stack[k] + local new = old + for r=1,nofregions do + new = new + stack[d+r] * factors[r] + end + stack[k] = new + showstate(formatters["blend %i of %i: %s -> %s"](i,n,old,new)) + end + top = t + elseif n == 1 then + top = top - nofregions + local v = stack[top] + for r=1,nofregions do + v = v + stack[top+r] * factors[r] + end + stack[top] = v + else + top = top - nofregions * n + local d = top + local k = top - n + for i=1,n do + k = k + 1 + local v = stack[k] + for r=1,nofregions do + v = v + stack[d+r] * factors[r] + end + stack[k] = v + d = d + nofregions + end + end + else + -- error + end + end -- Bah, we cannot use a fast lpeg because a hint has an unknown size and a -- runtime capture cannot handle that well. @@ -1274,8 +1438,8 @@ do unsupported, -- 12 -- elsewhere hsbw, -- 13 -- hsbw (type 1 cff) unsupported, -- 14 -- endchar, - unsupported, -- 15 - unsupported, -- 16 + setvsindex, -- 15 -- cff2 + blend, -- 16 -- cff2 unsupported, -- 17 getstem, -- 18 -- hstemhm getmask, -- 19 -- hintmask @@ -1311,6 +1475,92 @@ do [037] = flex1, } + local c_endchar = char(14) + + local passon do + + -- todo: round in blend + -- todo: delay this hash + + local rshift = bit32.rshift + local band = bit32.band + local round = math.round + + local encode = table.setmetatableindex(function(t,i) + for i=-2048,-1130 do + t[i] = char(28,band(rshift(i,8),0xFF),band(i,0xFF)) + end + for i=-1131,-108 do + local v = 0xFB00 - i - 108 + t[i] = char(band(rshift(v,8),0xFF),band(v,0xFF)) + end + for i=-107,107 do + t[i] = char(i + 139) + end + for i=108,1131 do + local v = 0xF700 + i - 108 + t[i] = char(band(rshift(v,8),0xFF),band(v,0xFF)) + end + for i=1132,2048 do + t[i] = char(28,band(rshift(i,8),0xFF),band(i,0xFF)) + end + return t[i] + end) + + local function setvsindex() + local vsindex = stack[top] + updateregions(vsindex) + top = top - 1 + end + + local function blend() + local n = stack[top] + top = top - 1 + if not axis then + -- fatal error + elseif n == 1 then + top = top - nofregions + local v = stack[top] + for r=1,nofregions do + v = v + stack[top+r] * factors[r] + end + stack[top] = round(v) + else + top = top - nofregions * n + local d = top + local k = top - n + for i=1,n do + k = k + 1 + local v = stack[k] + for r=1,nofregions do + v = v + stack[d+r] * factors[r] + end + stack[k] = round(v) + d = d + nofregions + end + end + end + + passon = function(operation) + if operation == 15 then + setvsindex() + elseif operation == 16 then + blend() + else + for i=1,top do + r = r + 1 + result[r] = encode[stack[i]] + end + r = r + 1 + result[r] = char(operation) -- maybe use a hash + top = 0 + end + end + + end + + -- end of experiment + local process local function call(scope,list,bias) -- ,process) @@ -1337,6 +1587,8 @@ do -- precompiling and reuse is much slower than redoing the calls + local justpass = false + process = function(tab) local i = 1 local n = #tab @@ -1384,7 +1636,7 @@ do stack[top] = n end i = i + 3 - elseif t == 11 then + elseif t == 11 then -- not in cff2 if trace_charstrings then showstate("return") end @@ -1392,7 +1644,7 @@ do elseif t == 10 then call("local",locals,localbias) -- ,process) i = i + 1 - elseif t == 14 then -- endchar + elseif t == 14 then -- not in cff2 if width then -- okay elseif top > 0 then @@ -1423,6 +1675,9 @@ do top = 0 end i = i + 1 + elseif justpass then + passon(t) + i = i + 1 else local a = actions[t] if a then @@ -1492,143 +1747,58 @@ do end end - parsecharstrings = function(data,glyphs,doshapes,tversion) - -- for all charstrings - local dictionary = data.dictionaries[1] - local charstrings = dictionary.charstrings - local charset = dictionary.charset - local private = dictionary.private or { data = { } } - - keepcurve = doshapes - version = tversion - stack = { } - glyphs = glyphs or { } - strings = data.strings - globals = data.routines or { } - locals = dictionary.subroutines or { } - - globalbias, localbias = setbias(globals,locals) - - local nominalwidth = private.data.nominalwidthx or 0 - local defaultwidth = private.data.defaultwidthx or 0 + local function processshape(tab,index) - for i=1,#charstrings do - local tab = bytetable(charstrings[i]) - local index = i - 1 - x = 0 - y = 0 - width = false - r = 0 - top = 0 - stems = 0 - result = { } -- we could reuse it when only boundingbox calculations are needed - -- - xmin = 0 - xmax = 0 - ymin = 0 - ymax = 0 - checked = false - -- - if trace_charstrings then - report("glyph: %i",index) - report("data: % t",tab) - end - -- - process(tab) - -- - local boundingbox = { round(xmin), round(ymin), round(xmax), round(ymax) } - -- - if width == true or width == false then - width = defaultwidth - else - width = nominalwidth + width - end - -- - -- trace_charstrings = index == 3078 -- todo: make tracker - local glyph = glyphs[index] -- can be autodefined in otr - if glyph then - glyph.segments = doshapes ~= false and result or nil - glyph.boundingbox = boundingbox - if not glyph.width then - glyph.width = width - end - if charset and not glyph.name then - glyph.name = charset[index] - end - -- glyph.sidebearing = 0 -- todo - elseif doshapes then - glyphs[index] = { - segments = result, - boundingbox = boundingbox, - width = width, - name = charset[index], - -- sidebearing = 0, - } - else - glyphs[index] = { - boundingbox = boundingbox, - width = width, - name = charset[index], - } - end - if trace_charstrings then - report("width: %s",tostring(width)) - report("boundingbox: % t",boundingbox) - end - charstrings[i] = nil -- free memory (what if used more often?) - end - return glyphs - end + tab = bytetable(tab) - parsecharstring = function(data,dictionary,tab,glyphs,index,doshapes,tversion) - local private = dictionary.private - keepcurve = doshapes - version = tversion - strings = data.strings -- or in dict? - locals = dictionary.subroutines or { } - globals = data.routines or { } + x = 0 + y = 0 + width = false + r = 0 + top = 0 + stems = 0 + result = { } -- we could reuse it when only boundingbox calculations are needed - globalbias, localbias = setbias(globals,locals) + xmin = 0 + xmax = 0 + ymin = 0 + ymax = 0 + checked = false - local nominalwidth = private and private.data.nominalwidthx or 0 - local defaultwidth = private and private.data.defaultwidthx or 0 - -- - tab = bytetable(tab) - -- - x = 0 - y = 0 - width = false - r = 0 - top = 0 - stems = 0 - result = { } - -- - xmin = 0 - xmax = 0 - ymin = 0 - ymax = 0 - checked = false - -- if trace_charstrings then report("glyph: %i",index) - report("data: % t",tab) + report("data : % t",tab) end - -- + + updateregions(vsindex) + process(tab) - -- - local boundingbox = { xmin, ymin, xmax, ymax } - -- + + local boundingbox = { + round(xmin), + round(ymin), + round(xmax), + round(ymax), + } + if width == true or width == false then width = defaultwidth else width = nominalwidth + width end - -- - index = index - 1 - -- + local glyph = glyphs[index] -- can be autodefined in otr - if glyph then - glyph.segments = doshapes ~= false and result or nil + if justpass then + r = r + 1 + result[r] = c_endchar + local stream = concat(result) + if glyph then + glyph.stream = stream + else + glyphs[index] = { stream = stream } + end + elseif glyph then + glyph.segments = keepcurve ~= false and result or nil glyph.boundingbox = boundingbox if not glyph.width then glyph.width = width @@ -1637,32 +1807,107 @@ do glyph.name = charset[index] end -- glyph.sidebearing = 0 -- todo - elseif doshapes then + elseif keepcurve then glyphs[index] = { segments = result, boundingbox = boundingbox, width = width, - name = charset[index], + name = charset and charset[index] or nil, -- sidebearing = 0, } else glyphs[index] = { boundingbox = boundingbox, width = width, - name = charset[index], + name = charset and charset[index] or nil, } end - -- + if trace_charstrings then - report("width: %s",tostring(width)) + report("width : %s",tostring(width)) report("boundingbox: % t",boundingbox) end + end - resetcharstrings = function() - result = { } - top = 0 - stack = { } + startparsing = function(fontdata,data,streams) + reginit = false + axis = false + regions = data.regions + justpass = streams == true + if regions then + regions = { regions } -- needs checking + axis = data.factors or false + end + end + + stopparsing = function(fontdata,data) + stack = { } + glyphs = false + result = { } + top = 0 + locals = false + globals = false + strings = false + end + + local function setwidths(private) + if not private then + return 0, 0 + end + local privatedata = private.data + if not privatedata then + return 0, 0 + end + return privatedata.nominalwidthx or 0, privatedata.defaultwidthx or 0 + end + + parsecharstrings = function(fontdata,data,glphs,doshapes,tversion,streams) + + local dictionary = data.dictionaries[1] + local charstrings = dictionary.charstrings + + keepcurve = doshapes + version = tversion + strings = data.strings + globals = data.routines or { } + locals = dictionary.subroutines or { } + charset = dictionary.charset + vsindex = dictionary.vsindex or 0 + glyphs = glphs or { } + + globalbias, localbias = setbias(globals,locals) + nominalwidth, defaultwidth = setwidths(dictionary.private) + + startparsing(fontdata,data,streams) + + for index=1,#charstrings do + processshape(charstrings[index],index-1) + charstrings[index] = nil -- free memory (what if used more often?) + end + + stopparsing(fontdata,data) + + return glyphs + end + + parsecharstring = function(fontdata,data,dictionary,tab,glphs,index,doshapes,tversion) + + keepcurve = doshapes + version = tversion + strings = data.strings + globals = data.routines or { } + locals = dictionary.subroutines or { } + charset = false + vsindex = dictionary.vsindex or 0 + glyphs = glphs or { } + + globalbias, localbias = setbias(globals,locals) + nominalwidth, defaultwidth = setwidths(dictionary.private) + + processshape(tab,index-1) + + -- return glyphs[index] end end @@ -1684,8 +1929,7 @@ local function readcharsets(f,data,dictionary) local strings = data.strings local nofglyphs = data.nofglyphs local charsetoffset = dictionary.charset - - if charsetoffset ~= 0 then + if charsetoffset and charsetoffset ~= 0 then setposition(f,header.offset+charsetoffset) local format = readbyte(f) local charset = { [0] = ".notdef" } @@ -1711,6 +1955,9 @@ local function readcharsets(f,data,dictionary) else report("cff parser: unsupported charset format %a",format) end + else + dictionary.nocharset = true + dictionary.charset = nil end end @@ -1748,16 +1995,18 @@ end -- These charstrings are little programs and described in: Technical Note #5177. A truetype -- font has only one dictionary. -local function readcharstrings(f,data) +local function readcharstrings(f,data,what) local header = data.header local dictionaries = data.dictionaries local dictionary = dictionaries[1] - local type = dictionary.charstringtype + local stringtype = dictionary.charstringtype local offset = dictionary.charstrings - if type == 2 then + if type(offset) ~= "number" then + -- weird + elseif stringtype == 2 then setposition(f,header.offset+offset) -- could be a metatable .. delayed loading - local charstrings = readlengths(f) + local charstrings = readlengths(f,what=="cff2") local nofglyphs = #charstrings for i=1,nofglyphs do charstrings[i] = readstring(f,charstrings[i]) @@ -1765,7 +2014,7 @@ local function readcharstrings(f,data) data.nofglyphs = nofglyphs dictionary.charstrings = charstrings else - report("unsupported charstr type %i",type) + report("unsupported charstr type %i",stringtype) data.nofglyphs = 0 dictionary.charstrings = { } end @@ -1787,31 +2036,36 @@ local function readcidprivates(f,data) parseprivates(data,dictionaries) end -local function readnoselect(f,data,glyphs,doshapes,version) +readers.parsecharstrings = parsecharstrings -- used in font-onr.lua (type 1) + +local function readnoselect(f,fontdata,data,glyphs,doshapes,version,streams) local dictionaries = data.dictionaries local dictionary = dictionaries[1] readglobals(f,data) - readcharstrings(f,data) - readencodings(f,data) - readcharsets(f,data,dictionary) + readcharstrings(f,data,version) + if version ~= "cff2" then + readencodings(f,data) + readcharsets(f,data,dictionary) + end readprivates(f,data) parseprivates(data,data.dictionaries) readlocals(f,data,dictionary) - parsecharstrings(data,glyphs,doshapes,version) - resetcharstrings() + startparsing(fontdata,data,streams) + parsecharstrings(fontdata,data,glyphs,doshapes,version,streams) + stopparsing(fontdata,data) end -readers.parsecharstrings = parsecharstrings - -local function readfdselect(f,data,glyphs,doshapes,version) +local function readfdselect(f,fontdata,data,glyphs,doshapes,version,streams) local header = data.header local dictionaries = data.dictionaries local dictionary = dictionaries[1] local cid = dictionary.cid local cidselect = cid and cid.fdselect readglobals(f,data) - readcharstrings(f,data) - readencodings(f,data) + readcharstrings(f,data,version) + if version ~= "cff2" then + readencodings(f,data) + end local charstrings = dictionary.charstrings local fdindex = { } local nofglyphs = data.nofglyphs @@ -1848,6 +2102,7 @@ local function readfdselect(f,data,glyphs,doshapes,version) else -- unsupported format end + -- hm, always if maxindex >= 0 then local cidarray = cid.fdarray setposition(f,header.offset+cidarray) @@ -1861,86 +2116,151 @@ local function readfdselect(f,data,glyphs,doshapes,version) for i=1,#dictionaries do readlocals(f,data,dictionaries[i]) end + startparsing(fontdata,data,streams) for i=1,#charstrings do - parsecharstring(data,dictionaries[fdindex[i]+1],charstrings[i],glyphs,i,doshapes,version) + parsecharstring(fontdata,data,dictionaries[fdindex[i]+1],charstrings[i],glyphs,i,doshapes,version) charstrings[i] = nil end - resetcharstrings() + stopparsing(fontdata,data) end end +local gotodatatable = readers.helpers.gotodatatable + +local function cleanup(data,dictionaries) + -- for i=1,#dictionaries do + -- local d = dictionaries[i] + -- d.subroutines = nil + -- end + -- data.strings = nil + -- if data then + -- data.charstrings = nil + -- data.routines = nil + -- end +end + function readers.cff(f,fontdata,specification) - -- if specification.glyphs then - if specification.details then - local datatable = fontdata.tables.cff - if datatable then - local offset = datatable.offset - local glyphs = fontdata.glyphs - if not f then - report("invalid filehandle") - return - end - if offset then - setposition(f,offset) - end - local header = readheader(f) - if header.major > 1 then - report("version mismatch") - return - end - local names = readfontnames(f) - local dictionaries = readtopdictionaries(f) - local strings = readstrings(f) - local data = { - header = header, - names = names, - dictionaries = dictionaries, - strings = strings, - nofglyphs = fontdata.nofglyphs, - } - -- - parsedictionaries(data,data.dictionaries) - -- - local d = dictionaries[1] - local c = d.cid - fontdata.cffinfo = { - familynamename = d.familyname, - fullname = d.fullname, - boundingbox = d.boundingbox, - weight = d.weight, - italicangle = d.italicangle, - underlineposition = d.underlineposition, - underlinethickness = d.underlinethickness, - monospaced = d.monospaced, - } - fontdata.cidinfo = c and { - registry = c.registry, - ordering = c.ordering, - supplement = c.supplement, - } - -- - if not specification.glyphs then - -- we only want some metadata + local tableoffset = gotodatatable(f,fontdata,"cff",specification.details) + if tableoffset then + local header = readheader(f) + if header.major ~= 1 then + report("only version %s is supported for table %a",1,"cff") + return + end + local glyphs = fontdata.glyphs + local names = readfontnames(f) + local dictionaries = readtopdictionaries(f) + local strings = readstrings(f) + local data = { + header = header, + names = names, + dictionaries = dictionaries, + strings = strings, + nofglyphs = fontdata.nofglyphs, + } + -- + parsedictionaries(data,dictionaries,"cff") + -- + local dic = dictionaries[1] + local cid = dic.cid + fontdata.cffinfo = { + familynamename = dic.familyname, + fullname = dic.fullname, + boundingbox = dic.boundingbox, + weight = dic.weight, + italicangle = dic.italicangle, + underlineposition = dic.underlineposition, + underlinethickness = dic.underlinethickness, + monospaced = dic.monospaced, + } + fontdata.cidinfo = cid and { + registry = cid.registry, + ordering = cid.ordering, + supplement = cid.supplement, + } + -- + if specification.glyphs then + local all = specification.shapes or false + if cid and cid.fdselect then + readfdselect(f,fontdata,data,glyphs,all,"cff") else - local cid = d.cid - if cid and cid.fdselect then - readfdselect(f,data,glyphs,specification.shapes or false) - else - readnoselect(f,data,glyphs,specification.shapes or false) - end + readnoselect(f,fontdata,data,glyphs,all,"cff") end - -- - -- cleanup (probably more can go) - -- - -- for i=1,#dictionaries do - -- local d = dictionaries[i] - -- d.subroutines = nil - -- end - -- data.strings = nil - -- if data then - -- data.charstrings = nil - -- data.routines = nil - -- end end + cleanup(data,dictionaries) + end +end + +function readers.cff2(f,fontdata,specification) + local tableoffset = gotodatatable(f,fontdata,"cff2",specification.glyphs) + if tableoffset then + local header = readheader(f) + if header.major ~= 2 then + report("only version %s is supported for table %a",2,"cff2") + return + end + local glyphs = fontdata.glyphs + local dictionaries = { readstring(f,header.dsize) } + local data = { + header = header, + dictionaries = dictionaries, + nofglyphs = fontdata.nofglyphs, + } + -- + parsedictionaries(data,dictionaries,"cff2") + -- + local storeoffset = dictionaries[1].vstore + data.header.offset + 2 -- cff has a preceding size field + local regions, deltas = readers.helpers.readvariationdata(f,storeoffset,factors) + -- + data.regions = regions + data.deltas = deltas + data.factors = specification.factors + -- + local cid = data.dictionaries[1].cid + local all = specification.shapes or false + if cid and cid.fdselect then + readfdselect(f,fontdata,data,glyphs,all,"cff2",specification.streams) + else + readnoselect(f,fontdata,data,glyphs,all,"cff2",specification.streams) + end + cleanup(data,dictionaries) + end +end + +-- temporary helper needed for checking backend patches + +function readers.cffcheck(filename) + local f = io.open(filename,"rb") + if f then + local fontdata = { + glyphs = { }, + } + local header = readheader(f) + if header.major ~= 1 then + report("only version %s is supported for table %a",1,"cff") + return + end + local names = readfontnames(f) + local dictionaries = readtopdictionaries(f) + local strings = readstrings(f) + local glyphs = { } + local data = { + header = header, + names = names, + dictionaries = dictionaries, + strings = strings, + glyphs = glyphs, + nofglyphs = 4, + } + -- + parsedictionaries(data,dictionaries,"cff") + -- + local cid = data.dictionaries[1].cid + if cid and cid.fdselect then + readfdselect(f,fontdata,data,glyphs,false) + else + readnoselect(f,fontdata,data,glyphs,false) + end + return data end end diff --git a/tex/context/base/mkiv/font-con.lua b/tex/context/base/mkiv/font-con.lua index 18f221710..85ac33a10 100644 --- a/tex/context/base/mkiv/font-con.lua +++ b/tex/context/base/mkiv/font-con.lua @@ -10,7 +10,8 @@ if not modules then modules = { } end modules ['font-con'] = { local next, tostring, rawget = next, tostring, rawget local format, match, lower, gsub, find = string.format, string.match, string.lower, string.gsub, string.find -local sort, insert, concat, sortedkeys, serialize, fastcopy = table.sort, table.insert, table.concat, table.sortedkeys, table.serialize, table.fastcopy +local sort, insert, concat = table.sort, table.insert, table.concat +local sortedkeys, sortedhash, serialize, fastcopy = table.sortedkeys, table.sortedhash, table.serialize, table.fastcopy local derivetable = table.derive local ioflush = io.flush @@ -963,49 +964,28 @@ constructors.hashmethods = hashmethods function constructors.hashfeatures(specification) -- will be overloaded local features = specification.features if features then - local t, tn = { }, 0 - for category, list in next, features do + local t, n = { }, 0 +-- inspect(features) +-- for category, list in next, features do + for category, list in sortedhash(features) do if next(list) then local hasher = hashmethods[category] if hasher then local hash = hasher(list) if hash then - tn = tn + 1 - t[tn] = category .. ":" .. hash + n = n + 1 + t[n] = category .. ":" .. hash end end end end - if tn > 0 then + if n > 0 then return concat(t," & ") end end return "unknown" end --- hashmethods.normal = function(list) --- local s = { } --- local n = 0 --- for k, v in next, list do --- if not k then --- -- no need to add to hash --- elseif k == "number" or k == "features" then --- -- no need to add to hash (maybe we need a skip list) --- else --- n = n + 1 --- s[n] = k --- end --- end --- if n > 0 then --- sort(s) --- for i=1,n do --- local k = s[i] --- s[i] = k .. '=' .. tostring(list[k]) --- end --- return concat(s,"+") --- end --- end - hashmethods.normal = function(list) local s = { } local n = 0 diff --git a/tex/context/base/mkiv/font-ctx.lua b/tex/context/base/mkiv/font-ctx.lua index f53a4e643..0e64511e2 100644 --- a/tex/context/base/mkiv/font-ctx.lua +++ b/tex/context/base/mkiv/font-ctx.lua @@ -64,6 +64,8 @@ local hashes = fonts.hashes local currentfont = font.current local definefont = font.define +local cleanname = names.cleanname + local encodings = fonts.encodings ----- aglunicodes = encodings.agl.unicodes local aglunicodes = nil -- delayed loading @@ -181,13 +183,52 @@ do local shares = { } local hashes = { } +local nofinstances = 0 +local instances = table.setmetatableindex(function(t,k) + nofinstances = nofinstances + 1 + t[k] = nofinstances + return nofinstances +end) + function constructors.trytosharefont(target,tfmdata) constructors.noffontsloaded = constructors.noffontsloaded + 1 if constructors.sharefonts then - local fonthash = target.specification.hash + local fonthash = target.specification.hash if fonthash then local properties = target.properties local fullname = target.fullname + local fontname = target.fontname + local psname = target.psname + -- for the moment here: + local instance = properties.instance + if instance then + local format = tfmdata.properties.format + if format == "opentype" then + target.streamprovider = 1 + elseif format == "truetype" then + target.streamprovider = 2 + else + target.streamprovider = 0 + end + if target.streamprovider > 0 then + if fullname then + fullname = fullname .. ":" .. instances[instance] + target.fullname = fullname + end + if fontname then + fontname = fontname .. ":" .. instances[instance] + target.fontname = fontname + end + if psname then + -- this one is used for the funny prefix in font names in pdf + -- so it has ot be kind of unique in order to avoid subset prefix + -- clashes being reported + psname = psname .. ":" .. instances[instance] + target.psname = psname + end + end + end + -- local sharedname = hashes[fonthash] if sharedname then -- this is ok for context as we know that only features can mess with font definitions @@ -201,10 +242,18 @@ do constructors.nofsharedhashes = constructors.nofsharedhashes + 1 else -- the one takes more time (in the worst case of many cjk fonts) but it also saves - -- embedding time + -- embedding time .. haha, this is interesting: when i got a clash on subset tag + -- collision i saw in the source that these tags are also using a hash like below + -- so maybe we should have an option to pass it from lua local characters = target.characters local n = 1 local t = { target.psname } + -- for the moment here: + if instance then + n = n + 1 + t[n] = instance + end + -- local u = sortedkeys(characters) for i=1,#u do local k = u[i] @@ -1441,14 +1490,19 @@ end local designsizes = constructors.designsizes +-- called quite often when in mp labels +-- otf.normalizedaxis + function constructors.hashinstance(specification,force) - local hash, size, fallbacks = specification.hash, specification.size, specification.fallbacks + local hash = specification.hash + local size = specification.size + local fallbacks = specification.fallbacks if force or not hash then hash = constructors.hashfeatures(specification) specification.hash = hash end if size < 1000 and designsizes[hash] then - size = math.round(constructors.scaled(size,designsizes[hash])) + size = round(constructors.scaled(size,designsizes[hash])) specification.size = size end if fallbacks then @@ -1512,9 +1566,6 @@ function definers.resolve(specification) -- overload function in font-con.lua return specification end - - - -- soon to be obsolete: local mappings = fonts.mappings @@ -2302,100 +2353,106 @@ end -- make a closure (200 limit): -local trace_analyzing = false trackers.register("otf.analyzing", function(v) trace_analyzing = v end) +do -local analyzers = fonts.analyzers -local methods = analyzers.methods + local trace_analyzing = false trackers.register("otf.analyzing", function(v) trace_analyzing = v end) -local unsetvalue = attributes.unsetvalue + local analyzers = fonts.analyzers + local methods = analyzers.methods -local traverse_id = nuts.traverse_id + local unsetvalue = attributes.unsetvalue -local a_color = attributes.private('color') -local a_colormodel = attributes.private('colormodel') -local a_state = attributes.private('state') -local m_color = attributes.list[a_color] or { } + local traverse_id = nuts.traverse_id -local glyph_code = nodes.nodecodes.glyph + local a_color = attributes.private('color') + local a_colormodel = attributes.private('colormodel') + local a_state = attributes.private('state') + local m_color = attributes.list[a_color] or { } -local states = analyzers.states + local glyph_code = nodes.nodecodes.glyph -local colornames = { - [states.init] = "font:1", - [states.medi] = "font:2", - [states.fina] = "font:3", - [states.isol] = "font:4", - [states.mark] = "font:5", - [states.rest] = "font:6", - [states.rphf] = "font:1", - [states.half] = "font:2", - [states.pref] = "font:3", - [states.blwf] = "font:4", - [states.pstf] = "font:5", -} + local states = analyzers.states + + local colornames = { + [states.init] = "font:1", + [states.medi] = "font:2", + [states.fina] = "font:3", + [states.isol] = "font:4", + [states.mark] = "font:5", + [states.rest] = "font:6", + [states.rphf] = "font:1", + [states.half] = "font:2", + [states.pref] = "font:3", + [states.blwf] = "font:4", + [states.pstf] = "font:5", + } -local function markstates(head) - if head then - head = tonut(head) - local model = getattr(head,a_colormodel) or 1 - for glyph in traverse_id(glyph_code,head) do - local a = getprop(glyph,a_state) - if a then - local name = colornames[a] - if name then - local color = m_color[name] - if color then - setattr(glyph,a_colormodel,model) - setattr(glyph,a_color,color) + local function markstates(head) + if head then + head = tonut(head) + local model = getattr(head,a_colormodel) or 1 + for glyph in traverse_id(glyph_code,head) do + local a = getprop(glyph,a_state) + if a then + local name = colornames[a] + if name then + local color = m_color[name] + if color then + setattr(glyph,a_colormodel,model) + setattr(glyph,a_color,color) + end end end end end end -end -local function analyzeprocessor(head,font,attr) - local tfmdata = fontdata[font] - local script, language = otf.scriptandlanguage(tfmdata,attr) - local action = methods[script] - if not action then - return head, false - end - if type(action) == "function" then - local head, done = action(head,font,attr) - if done and trace_analyzing then - markstates(head) + local function analyzeprocessor(head,font,attr) + local tfmdata = fontdata[font] + local script, language = otf.scriptandlanguage(tfmdata,attr) + local action = methods[script] + if not action then + return head, false end - return head, done - end - action = action[language] - if action then - local head, done = action(head,font,attr) - if done and trace_analyzing then - markstates(head) + if type(action) == "function" then + local head, done = action(head,font,attr) + if done and trace_analyzing then + markstates(head) + end + return head, done + end + action = action[language] + if action then + local head, done = action(head,font,attr) + if done and trace_analyzing then + markstates(head) + end + return head, done + else + return head, false end - return head, done - else - return head, false end -end -registerotffeature { -- adapts - name = "analyze", - processors = { - node = analyzeprocessor, + registerotffeature { -- adapts + name = "analyze", + processors = { + node = analyzeprocessor, + } } -} -function methods.nocolor(head,font,attr) - for n in traverse_id(glyph_code,head) do - if not font or getfont(n) == font then - setattr(n,a_color,unsetvalue) + + function methods.nocolor(head,font,attr) + for n in traverse_id(glyph_code,head) do + if not font or getfont(n) == font then + setattr(n,a_color,unsetvalue) + end end + return head, true end - return head, true + end + local function purefontname(name) if type(name) == "number" then name = getfontname(name) @@ -2412,6 +2469,7 @@ implement { } local list = storage.shared.bodyfontsizes or { } + storage.shared.bodyfontsizes = list implement { @@ -2448,7 +2506,7 @@ implement { implement { name = "cleanfontname", - actions = { names.cleanname, context }, + actions = { cleanname, context }, arguments = "string" } @@ -2654,3 +2712,58 @@ do } end + +do + + local function getinstancespec(id) + local data = fontdata[id or true] + local shared = data.shared + local resources = shared and shared.rawdata.resources + if resources then + local instancespec = data.properties.instance + if instancespec then + local variabledata = resources.variabledata + if variabledata then + local instances = variabledata.instances + if instances then + for i=1,#instances do + local instance = instances[i] + if cleanname(instance.subfamily)== instancespec then + local values = table.copy(instance.values) + local axis = variabledata.axis + for i=1,#values do + for j=1,#axis do + if values[i].axis == axis[j].tag then + values[i].name = axis[j].name + break + end + end + end + return values + end + end + end + end + end + end + end + + helpers.getinstancespec = getinstancespec + + implement { + name = "currentfontinstancespec", + actions = function() + local t = getinstancespec() -- current font + if t then + for i=1,#t do + if i > 1 then + context.space() + end + local ti = t[i] + context("%s=%s",ti.name,ti.value) + end + end + end + } + +end diff --git a/tex/context/base/mkiv/font-def.lua b/tex/context/base/mkiv/font-def.lua index 88d614566..6765be9d3 100644 --- a/tex/context/base/mkiv/font-def.lua +++ b/tex/context/base/mkiv/font-def.lua @@ -184,11 +184,30 @@ end function resolvers.name(specification) local resolve = fonts.names.resolve if resolve then - local resolved, sub, subindex = resolve(specification.name,specification.sub,specification) -- we pass specification for overloaded versions + local resolved, sub, subindex, instance = resolve(specification.name,specification.sub,specification) -- we pass specification for overloaded versions if resolved then specification.resolved = resolved specification.sub = sub specification.subindex = subindex + -- new, needed for experiments + if instance then + specification.instance = instance + local features = specification.features + if not features then + features = { } + specification.features = features + end + local normal = features.normal + if not normal then + normal = { } + features.normal = normal + end + normal.instance = instance +if not callbacks.supported.glyph_stream_provider then + normal.variableshapes = true -- for the moment +end + end + -- local suffix = lower(suffixonly(resolved)) if fonts.formats[suffix] then specification.forced = suffix @@ -296,6 +315,7 @@ end function definers.loadfont(specification) local hash = constructors.hashinstance(specification) + -- todo: also hash by instance / factors local tfmdata = loadedfonts[hash] -- hashes by size ! if not tfmdata then local forced = specification.forced or "" diff --git a/tex/context/base/mkiv/font-dsp.lua b/tex/context/base/mkiv/font-dsp.lua index 09c6aea3d..fc56df3f2 100644 --- a/tex/context/base/mkiv/font-dsp.lua +++ b/tex/context/base/mkiv/font-dsp.lua @@ -48,19 +48,28 @@ if not modules then modules = { } end modules ['font-dsp'] = { -- of node lists is not noticeable faster for latin texts, but for arabic we gain some 10% -- (and could probably gain a bit more). +-- All this packing in the otf format is somewhat obsessive as nowadays 4K resolution +-- multi-gig videos pass through our networks and storage and memory is abundant. + local next, type = next, type local bittest = bit32.btest local band = bit32.band +local extract = bit32.extract local bor = bit32.bor local lshift = bit32.lshift local rshift = bit32.rshift -local concat = table.concat +local gsub = string.gsub local lower = string.lower -local copy = table.copy local sub = string.sub local strip = string.strip local tohash = table.tohash +local concat = table.concat +local copy = table.copy local reversed = table.reversed +local sort = table.sort +local insert = table.insert +local round = math.round +local lpegmatch = lpeg.match local setmetatableindex = table.setmetatableindex local formatters = string.formatters @@ -92,6 +101,13 @@ local readbyte = streamreader.readbyte local gsubhandlers = { } local gposhandlers = { } +readers.gsubhandlers = gsubhandlers +readers.gposhandlers = gposhandlers + +local helpers = readers.helpers +local gotodatatable = helpers.gotodatatable +local setvariabledata = helpers.setvariabledata + local lookupidoffset = -1 -- will become 1 when we migrate (only -1 for comparign with old) local classes = { @@ -130,6 +146,90 @@ local chaindirections = { reversechainedcontextsingle = -1, } +local function setmetrics(data,where,tag,d) + local w = data[where] + if w then + local v = w[tag] + if v then + -- it looks like some fonts set the value and not the delta + -- report("adding %s to %s.%s value %s",d,where,tag,v) + w[tag] = v + d + end + end +end + +local variabletags = { + hasc = function(data,d) setmetrics(data,"windowsmetrics","typoascender",d) end, + hdsc = function(data,d) setmetrics(data,"windowsmetrics","typodescender",d) end, + hlgp = function(data,d) setmetrics(data,"windowsmetrics","typolinegap",d) end, + hcla = function(data,d) setmetrics(data,"windowsmetrics","winascent",d) end, + hcld = function(data,d) setmetrics(data,"windowsmetrics","windescent",d) end, + vasc = function(data,d) setmetrics(data,"vhea not done","ascent",d) end, + vdsc = function(data,d) setmetrics(data,"vhea not done","descent",d) end, + vlgp = function(data,d) setmetrics(data,"vhea not done","linegap",d) end, + xhgt = function(data,d) setmetrics(data,"windowsmetrics","xheight",d) end, + cpht = function(data,d) setmetrics(data,"windowsmetrics","capheight",d) end, + sbxs = function(data,d) setmetrics(data,"windowsmetrics","subscriptxsize",d) end, + sbys = function(data,d) setmetrics(data,"windowsmetrics","subscriptysize",d) end, + sbxo = function(data,d) setmetrics(data,"windowsmetrics","subscriptxoffset",d) end, + sbyo = function(data,d) setmetrics(data,"windowsmetrics","subscriptyoffset",d) end, + spxs = function(data,d) setmetrics(data,"windowsmetrics","superscriptxsize",d) end, + spys = function(data,d) setmetrics(data,"windowsmetrics","superscriptysize",d) end, + spxo = function(data,d) setmetrics(data,"windowsmetrics","superscriptxoffset",d) end, + spyo = function(data,d) setmetrics(data,"windowsmetrics","superscriptyoffset",d) end, + strs = function(data,d) setmetrics(data,"windowsmetrics","strikeoutsize",d) end, + stro = function(data,d) setmetrics(data,"windowsmetrics","strikeoutpos",d) end, + unds = function(data,d) setmetrics(data,"postscript","underlineposition",d) end, + undo = function(data,d) setmetrics(data,"postscript","underlinethickness",d) end, +} + +local read_cardinal = { + streamreader.readcardinal1, + streamreader.readcardinal2, + streamreader.readcardinal3, + streamreader.readcardinal4, +} + +local read_integer = { + streamreader.readinteger1, + streamreader.readinteger2, + streamreader.readinteger3, + streamreader.readinteger4, +} + +-- using helpers doesn't make much sense, subtle differences +-- +-- local function readushortarray(f,n) +-- local t = { } +-- for i=1,n do +-- t[i] = readushort(f) +-- end +-- return t +-- end +-- +-- local function readulongarray(f,n) +-- local t = { } +-- for i=1,n do +-- t[i] = readulong(f) +-- end +-- return t +-- end +-- +-- local function readushortarray(f,target,first,size) +-- if not size then +-- for i=1,size do +-- target[i] = readushort(f) +-- end +-- else +-- for i=1,size do +-- target[first+i] = readushort(f) +-- end +-- end +-- return target +-- end +-- +-- so we get some half helper - half non helper mix then + -- Traditionally we use these unique names (so that we can flatten the lookup list -- (we create subsets runtime) but I will adapt the old code to newer names. @@ -192,6 +292,264 @@ local lookupflags = setmetatableindex(function(t,k) return v end) +-- Variation stores: it's not entirely clear if the regions are a shared +-- resource (it looks like they are). Anyway, we play safe and use a +-- share. + +-- values can be anything the min/max permits so we can either think of +-- real values of a fraction along the axis (probably easier) + +-- wght:400,wdth:100,ital:1 + +-- local names = table.setmetatableindex ( { +-- weight = "wght", +-- width = "wdth", +-- italic = "ital", +-- }, "self") + +local pattern = lpeg.Cf ( + lpeg.Ct("") * + lpeg.Cg ( + --(lpeg.R("az")^1/names) * lpeg.S(" :") * + lpeg.C(lpeg.R("az")^1) * lpeg.S(" :=") * + (lpeg.patterns.number/tonumber) * lpeg.S(" ,")^0 + )^1, rawset +) + +local hash = table.setmetatableindex(function(t,k) + local v = lpegmatch(pattern,k) + local t = { } + for k, v in sortedhash(v) do + t[#t+1] = k .. "=" .. v + end + v = concat(t,",") + t[k] = v + return v +end) + +helpers.normalizedaxishash = hash + +local cleanname = fonts.names and fonts.names.cleanname or function(name) + return name and (gsub(lower(name),"[^%a%d]","")) or nil +end + +helpers.cleanname = cleanname + +function helpers.normalizedaxis(str) + return hash[str] or str +end + +local function axistofactors(str) + return lpegmatch(pattern,str) +end + +-- contradicting spec ... (signs) so i'll check it and fix it once we have +-- proper fonts + +local function getaxisscale(segments,minimum,default,maximum,user) + -- + -- returns the right values cf example in standard + -- + if not minimum or not default or not maximum then + return false + end + if user < minimum then + user = minimum + elseif user > maximum then + user = maximum + end + if user < default then + default = - (default - user) / (default - minimum) + elseif user > default then + default = (user - default) / (maximum - default) + else + default = 0 + end + if not segments then + return default + end + local e + for i=1,#segments do + local s = segments[i] + if s[1] >= default then + if s[2] == default then + return default + else + e = i + break + end + end + end + if e then + local b = segments[e-1] + local e = segments[e] + return b[2] + (e[2] - b[2]) * (default - b[1]) / (e[1] - b[1]) + else + return false + end +end + +local function getfactors(data,instancespec) + if instancespec == true then + -- take default + elseif type(instancespec) ~= "string" or instancespec == "" then + return + end + local variabledata = data.variabledata + if not variabledata then + return + end + local instances = variabledata.instances + local axis = variabledata.axis + local segments = variabledata.segments + if instances and axis then + local values + if instancespec == true then + -- first instance: + -- values = instances[1].values + -- axis defaults: + values = { } + for i=1,#axis do + values[i] = { + -- axis = axis[i].tag, + value = axis[i].default, + } + end + + else + for i=1,#instances do + local instance = instances[i] + if cleanname(instance.subfamily) == instancespec then + values = instance.values + break + end + end + end + if values then + local factors = { } + for i=1,#axis do + local a = axis[i] + factors[i] = getaxisscale(segments,a.minimum,a.default,a.maximum,values[i].value) + end + return factors + end + local values = axistofactors(hash[instancespec] or instancespec) + if values then + local factors = { } + for i=1,#axis do + local a = axis[i] + local d = a.default + factors[i] = getaxisscale(segments,a.minimum,d,a.maximum,values[a.name or a.tag] or d) + end + return factors + end + end +end + +local function getscales(regions,factors) + local scales = { } + for i=1,#regions do + local region = regions[i] + local s = 1 + for j=1,#region do + local axis = region[j] + local f = factors[j] + local start = axis.start + local peak = axis.peak + local stop = axis.stop + -- get rid of these tests, false flag + 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 + scales[i] = s + end + return scales +end + +helpers.getaxisscale = getaxisscale +helpers.getfactors = getfactors +helpers.getscales = getscales +helpers.axistofactors = axistofactors + +local function readvariationdata(f,storeoffset,factors) -- store + local position = getposition(f) + setposition(f,storeoffset) + -- header + local format = readushort(f) + local regionoffset = storeoffset + readulong(f) + local nofdeltadata = readushort(f) + local deltadata = { } + for i=1,nofdeltadata do + deltadata[i] = readulong(f) + end + -- regions + setposition(f,regionoffset) + local nofaxis = readushort(f) + local nofregions = readushort(f) + local regions = { } + for i=1,nofregions do -- 0 + local t = { } + for i=1,nofaxis do + t[i] = { -- maybe no keys, just 1..3 + start = read2dot14(f), + peak = read2dot14(f), + stop = read2dot14(f), + } + end + regions[i] = t + end + -- deltas + if factors then + for i=1,nofdeltadata do + setposition(f,storeoffset+deltadata[i]) + local nofdeltasets = readushort(f) + local nofshorts = readushort(f) + local nofregions = readushort(f) + local usedregions = { } + local deltas = { } + for i=1,nofregions do + usedregions[i] = regions[readushort(f)+1] + end + -- we could test before and save a for + for i=1,nofdeltasets do + local t = { } -- newtable + for i=1,nofshorts do + t[i] = readshort(f) + end + for i=nofshorts+1,nofregions do + t[i] = readinteger(f) + end + deltas[i] = t + end + deltadata[i] = { + regions = usedregions, + deltas = deltas, + scales = factors and getscales(usedregions,factors) or nil, + } + end + end + setposition(f,position) + return regions, deltadata +end + +helpers.readvariationdata = readvariationdata + -- Beware: only use the simple variant if we don't set keys/values (otherwise too many entries). We -- could also have a variant that applies a function but there is no real benefit in this. @@ -292,36 +650,168 @@ end -- extra readers -local function readposition(f,format) +local skips = { [0] = + 0, -- ---- + 1, -- ---x + 1, -- --y- + 2, -- --yx + 1, -- -h-- + 2, -- -h-x + 2, -- -hy- + 3, -- -hyx + 2, -- v--x + 2, -- v-y- + 3, -- v-yx + 2, -- vh-- + 3, -- vh-x + 3, -- vhy- + 4, -- vhyx +} + +-- We can assume that 0 is nothing and in fact we can start at 1 as +-- usual in Lua to make sure of that. + +local function readvariation(f,offset) + local p = getposition(f) + setposition(f,offset) + local outer = readushort(f) + local inner = readushort(f) + local format = readushort(f) + setposition(f,p) + if format == 0x8000 then + return outer, inner + end +end + +local function readposition(f,format,mainoffset,getdelta) if format == 0 then - return nil + return end - -- maybe fast test on 0x0001 + 0x0002 + 0x0004 + 0x0008 (profile first) - local x = bittest(format,0x0001) and readshort(f) or 0 -- placement - local y = bittest(format,0x0002) and readshort(f) or 0 -- placement - local h = bittest(format,0x0004) and readshort(f) or 0 -- advance - local v = bittest(format,0x0008) and readshort(f) or 0 -- advance - if x == 0 and y == 0 and h == 0 and v == 0 then - return nil + -- a few happen often + if format == 0x04 then + local h = readshort(f) + if h == 0 then + return + else + return { 0, 0, h, 0 } + end + end + if format == 0x05 then + local x = readshort(f) + local h = readshort(f) + if x == 0 and h == 0 then + return + else + return { x, 0, h, 0 } + end + end + if format == 0x44 then + local h = readshort(f) + if getdelta then + local d = readshort(f) -- short or ushort + if d > 0 then + local outer, inner = readvariation(f,mainoffset+d) + if outer then + h = h + getdelta(outer,inner) + end + end + else + skipshort(f,1) + end + if h == 0 then + return + else + return { 0, 0, h, 0 } + end + end + -- + -- todo: + -- + -- if format == 0x55 then + -- local x = readshort(f) + -- local h = readshort(f) + -- .... + -- end + -- + local x = bittest(format,0x01) and readshort(f) or 0 -- x placement + local y = bittest(format,0x02) and readshort(f) or 0 -- y placement + local h = bittest(format,0x04) and readshort(f) or 0 -- h advance + local v = bittest(format,0x08) and readshort(f) or 0 -- v advance + if format >= 0x10 then + local X = bittest(format,0x10) and skipshort(f) or 0 + local Y = bittest(format,0x20) and skipshort(f) or 0 + local H = bittest(format,0x40) and skipshort(f) or 0 + local V = bittest(format,0x80) and skipshort(f) or 0 + local s = skips[extract(format,4,4)] + if s > 0 then + skipshort(f,s) + end + if getdelta then + if X > 0 then + local outer, inner = readvariation(f,mainoffset+X) + if outer then + x = x + getdelta(outer,inner) + end + end + if Y > 0 then + local outer, inner = readvariation(f,mainoffset+Y) + if outer then + y = y + getdelta(outer,inner) + end + end + if H > 0 then + local outer, inner = readvariation(f,mainoffset+H) + if outer then + h = h + getdelta(outer,inner) + end + end + if V > 0 then + local outer, inner = readvariation(f,mainoffset+V) + if outer then + v = v + getdelta(outer,inner) + end + end + end + return { x, y, h, v } + elseif x == 0 and y == 0 and h == 0 and v == 0 then + return else return { x, y, h, v } end end -local function readanchor(f,offset) +local function readanchor(f,offset,getdelta) -- maybe also ignore 0's as in pos if not offset or offset == 0 then return nil -- false end setposition(f,offset) - local format = readshort(f) - if format == 0 then - report("invalid anchor format %i @ position %i",format,offset) - return false - elseif format > 3 then - report("unsupported anchor format %i @ position %i",format,offset) - return false + -- no need to skip as we position each + local format = readshort(f) -- 1: x y 2: x y index 3 x y X Y + local x = readshort(f) + local y = readshort(f) + if format == 3 then + if getdelta then + local X = readshort(f) + local Y = readshort(f) + if X > 0 then + local outer, inner = readvariation(f,offset+X) + if outer then + x = x + getdelta(outer,inner) + end + end + if Y > 0 then + local outer, inner = readvariation(f,offset+Y) + if outer then + y = y + getdelta(outer,inner) + end + end + else + skipshort(f,2) + end + return { x, y } -- , { xindex, yindex } + else + return { x, y } end - return { readshort(f), readshort(f) } end -- common handlers: inlining can be faster but we cache anyway @@ -907,20 +1397,21 @@ end -- gpos handlers -local function readpairsets(f,tableoffset,sets,format1,format2) +local function readpairsets(f,tableoffset,sets,format1,format2,mainoffset,getdelta) local done = { } for i=1,#sets do local offset = sets[i] local reused = done[offset] if not reused then - setposition(f,tableoffset + offset) + offset = tableoffset + offset + setposition(f,offset) local n = readushort(f) reused = { } for i=1,n do reused[i] = { readushort(f), -- second glyph id - readposition(f,format1), - readposition(f,format2) + readposition(f,format1,offset,getdelta), + readposition(f,format2,offset,getdelta), } end done[offset] = reused @@ -930,14 +1421,14 @@ local function readpairsets(f,tableoffset,sets,format1,format2) return sets end -local function readpairclasssets(f,nofclasses1,nofclasses2,format1,format2) +local function readpairclasssets(f,nofclasses1,nofclasses2,format1,format2,mainoffset,getdelta) local classlist1 = { } for i=1,nofclasses1 do local classlist2 = { } classlist1[i] = classlist2 for j=1,nofclasses2 do - local one = readposition(f,format1) - local two = readposition(f,format2) + local one = readposition(f,format1,mainoffset,getdelta) + local two = readposition(f,format2,mainoffset,getdelta) if one or two then classlist2[j] = { one, two } else @@ -953,26 +1444,27 @@ end function gposhandlers.single(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs) local tableoffset = lookupoffset + offset setposition(f,tableoffset) - local subtype = readushort(f) + local subtype = readushort(f) + local getdelta = fontdata.temporary.getdelta if subtype == 1 then local coverage = readushort(f) local format = readushort(f) - local value = readposition(f,format) + local value = readposition(f,format,tableoffset,getdelta) local coverage = readcoverage(f,tableoffset+coverage) for index, newindex in next, coverage do coverage[index] = value end return { format = "pair", - coverage = coverage + coverage = coverage, } elseif subtype == 2 then local coverage = readushort(f) local format = readushort(f) - local values = { } local nofvalues = readushort(f) + local values = { } for i=1,nofvalues do - values[i] = readposition(f,format) + values[i] = readposition(f,format,tableoffset,getdelta) end local coverage = readcoverage(f,tableoffset+coverage) for index, newindex in next, coverage do @@ -980,7 +1472,7 @@ function gposhandlers.single(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofg end return { format = "pair", - coverage = coverage + coverage = coverage, } else report("unsupported subtype %a in %a positioning",subtype,"single") @@ -997,13 +1489,14 @@ end function gposhandlers.pair(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs) local tableoffset = lookupoffset + offset setposition(f,tableoffset) - local subtype = readushort(f) + local subtype = readushort(f) + local getdelta = fontdata.temporary.getdelta if subtype == 1 then local coverage = readushort(f) local format1 = readushort(f) local format2 = readushort(f) local sets = readarray(f) - sets = readpairsets(f,tableoffset,sets,format1,format2) + sets = readpairsets(f,tableoffset,sets,format1,format2,mainoffset,getdelta) coverage = readcoverage(f,tableoffset + coverage) for index, newindex in next, coverage do local set = sets[newindex+1] @@ -1025,7 +1518,7 @@ function gposhandlers.pair(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofgly end return { format = "pair", - coverage = coverage + coverage = coverage, } elseif subtype == 2 then local coverage = readushort(f) @@ -1035,7 +1528,7 @@ function gposhandlers.pair(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofgly local classdef2 = readushort(f) local nofclasses1 = readushort(f) -- incl class 0 local nofclasses2 = readushort(f) -- incl class 0 - local classlist = readpairclasssets(f,nofclasses1,nofclasses2,format1,format2) + local classlist = readpairclasssets(f,nofclasses1,nofclasses2,format1,format2,tableoffset,getdelta) coverage = readcoverage(f,tableoffset+coverage) classdef1 = readclassdef(f,tableoffset+classdef1,coverage) classdef2 = readclassdef(f,tableoffset+classdef2,nofglyphs) @@ -1063,7 +1556,7 @@ function gposhandlers.pair(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofgly end return { format = "pair", - coverage = usedcoverage + coverage = usedcoverage, } elseif subtype == 3 then report("yet unsupported subtype %a in %a positioning",subtype,"pair") @@ -1075,7 +1568,8 @@ end function gposhandlers.cursive(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs) local tableoffset = lookupoffset + offset setposition(f,tableoffset) - local subtype = readushort(f) + local subtype = readushort(f) + local getdelta = fontdata.temporary.getdelta if subtype == 1 then local coverage = tableoffset + readushort(f) local nofrecords = readushort(f) @@ -1091,17 +1585,18 @@ function gposhandlers.cursive(f,fontdata,lookupid,lookupoffset,offset,glyphs,nof coverage = readcoverage(f,coverage) for i=1,nofrecords do local r = records[i] + -- slot 1 will become hash after loading (must be unique per lookup when packed) records[i] = { - 1, -- will become hash after loading (must be unique per lookup when packed) - readanchor(f,r.entry) or nil, - readanchor(f,r.exit ) or nil, + 1, + readanchor(f,r.entry,getdelta) or nil, + readanchor(f,r.exit, getdelta) or nil, } end for index, newindex in next, coverage do coverage[index] = records[newindex+1] end return { - coverage = coverage + coverage = coverage, } else report("unsupported subtype %a in %a positioning",subtype,"cursive") @@ -1111,7 +1606,8 @@ end local function handlemark(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,ligature) local tableoffset = lookupoffset + offset setposition(f,tableoffset) - local subtype = readushort(f) + local subtype = readushort(f) + local getdelta = fontdata.temporary.getdelta if subtype == 1 then -- we are one based, not zero local markcoverage = tableoffset + readushort(f) @@ -1130,17 +1626,12 @@ local function handlemark(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyp local lastanchor = fontdata.lastanchor or 0 local usedanchors = { } -- --- local placeholder = (fontdata.markcount or 0) + 1 --- fontdata.markcount = placeholder --- placeholder = "m" .. placeholder - -- for i=1,nofmarkclasses do local class = readushort(f) + 1 local offset = readushort(f) if offset == 0 then markclasses[i] = false else --- markclasses[i] = { placeholder, class, markoffset + offset } markclasses[i] = { class, markoffset + offset } end usedanchors[class] = true @@ -1148,8 +1639,7 @@ local function handlemark(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyp for i=1,nofmarkclasses do local mc = markclasses[i] if mc then --- mc[3] = readanchor(f,mc[3]) - mc[2] = readanchor(f,mc[2]) + mc[2] = readanchor(f,mc[2],getdelta) end end -- @@ -1203,7 +1693,7 @@ local function handlemark(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyp local classes = components[c] if classes then for i=1,nofclasses do - local anchor = readanchor(f,classes[i]) + local anchor = readanchor(f,classes[i],getdelta) local bclass = baseclasses[i] local bentry = bclass[b] if bentry then @@ -1213,7 +1703,6 @@ local function handlemark(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyp end end end --- components[i] = classes end end end @@ -1246,7 +1735,7 @@ local function handlemark(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyp local r = baserecords[i] local b = basecoverage[i] for j=1,nofclasses do - baseclasses[j][b] = readanchor(f,r[j]) + baseclasses[j][b] = readanchor(f,r[j],getdelta) end end for index, newindex in next, markcoverage do @@ -1304,10 +1793,10 @@ do setposition(f,offset) local designsize = readushort(f) if designsize > 0 then -- we could also have a threshold - local fontstyle = readushort(f) - local guimenuid = readushort(f) - local minsize = readushort(f) - local maxsize = readushort(f) + local fontstyleid = readushort(f) + local guimenuid = readushort(f) + local minsize = readushort(f) + local maxsize = readushort(f) if minsize == 0 and maxsize == 0 and fontstyleid == 0 and guimenuid == 0 then minsize = designsize maxsize = designsize @@ -1337,6 +1826,10 @@ do end end + -- function plugins.rvrn(f,fontdata,tableoffset,feature) + -- -- todo, at least a message + -- end + -- feature order needs checking ... as we loop over a hash ... however, in the file -- they are sorted so order is not that relevant @@ -1495,8 +1988,8 @@ do lookups[i] = readushort(f) end for lookupid=1,noflookups do - local index = lookups[lookupid] - setposition(f,lookupoffset+index) + local offset = lookups[lookupid] + setposition(f,lookupoffset+offset) local subtables = { } local typebits = readushort(f) local flagbits = readushort(f) @@ -1504,8 +1997,7 @@ do local lookupflags = lookupflags[flagbits] local nofsubtables = readushort(f) for j=1,nofsubtables do - local offset = readushort(f) - subtables[j] = offset + index -- we can probably put lookupoffset here + subtables[j] = offset + readushort(f) -- we can probably put lookupoffset here end -- which one wins? local markclass = bittest(flagbits,0x0010) -- usemarkfilteringset @@ -1530,23 +2022,9 @@ do return lookups end - local function readscriptoffsets(f,fontdata,tableoffset) - if not tableoffset then - return - end - setposition(f,tableoffset) - local version = readulong(f) - if version ~= 0x00010000 then - report("table version %a of %a is not supported (yet), maybe font %s is bad",version,what,fontdata.filename) - return - end - -- - return tableoffset + readushort(f), tableoffset + readushort(f), tableoffset + readushort(f) - end - local f_lookupname = formatters["%s_%s_%s"] - local function resolvelookups(f,lookupoffset,fontdata,lookups,lookuptypes,lookuphandlers,what) + local function resolvelookups(f,lookupoffset,fontdata,lookups,lookuptypes,lookuphandlers,what,tableoffset) local sequences = fontdata.sequences or { } local sublookuplist = fontdata.sublookups or { } @@ -1767,45 +2245,124 @@ do if n == 0 and t ~= "extension" then local d = l.done report("%s lookup %s of type %a is not used",what,d and d.name or l.name,t) - -- inspect(l) end end end - local function readscripts(f,fontdata,what,lookuptypes,lookuphandlers,lookupstoo) - local datatable = fontdata.tables[what] - if not datatable then - return - end - local tableoffset = datatable.offset - if not tableoffset then - return + local function loadvariations(f,fontdata,variationsoffset,lookuptypes,featurehash,featureorder) + setposition(f,variationsoffset) + local version = readulong(f) + local nofrecords = readulong(f) + local records = { } + for i=1,nofrecords do + records[i] = { + conditions = readulong(f), + substitutions = readulong(f), + } end - local scriptoffset, featureoffset, lookupoffset = readscriptoffsets(f,fontdata,tableoffset) - if not scriptoffset then - return + for i=1,nofrecords do + local record = records[i] + local offset = record.conditions + if offset == 0 then + record.condition = nil + record.matchtype = "always" + else + setposition(f,variationsoffset+offset) + local nofconditions = readushort(f) + local conditions = { } + for i=1,nofconditions do + conditions[i] = variationsoffset+offset+readulong(f) + end + record.conditions = conditions + record.matchtype = "condition" + end end - -- - local scripts = readscriplan(f,fontdata,scriptoffset) - local features = readfeatures(f,fontdata,featureoffset) - -- - local scriptlangs, featurehash, featureorder = reorderfeatures(fontdata,scripts,features) - -- - if fontdata.features then - fontdata.features[what] = scriptlangs - else - fontdata.features = { [what] = scriptlangs } + for i=1,nofrecords do + local record = records[i] + if record.matchtype == "condition" then + local conditions = record.conditions + for i=1,#conditions do + setposition(f,conditions[i]) + conditions[i] = { + format = readushort(f), + axis = readushort(f), + minvalue = read2dot14(f), + maxvalue = read2dot14(f), + } + end + end end - -- - if not lookupstoo then - return + + for i=1,nofrecords do + local record = records[i] + local offset = record.substitutions + if offset == 0 then + record.substitutions = { } + else + setposition(f,variationsoffset + offset) + local version = readulong(f) + local nofsubstitutions = readushort(f) + local substitutions = { } + for i=1,nofsubstitutions do + substitutions[readushort(f)] = readulong(f) + end + for index, alternates in sortedhash(substitutions) do + if index == 0 then + record.substitutions = false + else + local tableoffset = variationsoffset + offset + alternates + setposition(f,tableoffset) + local parameters = readulong(f) -- feature parameters + local noflookups = readushort(f) + local lookups = { } + for i=1,noflookups do + lookups[i] = readushort(f) -- not sure what to do with these + end + -- todo : resolve to proper lookups + record.substitutions = lookups + end + end + end end - -- - local lookups = readlookups(f,lookupoffset,lookuptypes,featurehash,featureorder) - -- - if lookups then - resolvelookups(f,lookupoffset,fontdata,lookups,lookuptypes,lookuphandlers,what) + setvariabledata(fontdata,"features",records) + end + + local function readscripts(f,fontdata,what,lookuptypes,lookuphandlers,lookupstoo) + local tableoffset = gotodatatable(f,fontdata,what,true) + if tableoffset then + local version = readulong(f) + local scriptoffset = tableoffset + readushort(f) + local featureoffset = tableoffset + readushort(f) + local lookupoffset = tableoffset + readushort(f) + local variationsoffset = version > 0x00010000 and (tableoffset + readulong(f)) or 0 + if not scriptoffset then + return + end + local scripts = readscriplan(f,fontdata,scriptoffset) + local features = readfeatures(f,fontdata,featureoffset) + -- + local scriptlangs, featurehash, featureorder = reorderfeatures(fontdata,scripts,features) + -- + if fontdata.features then + fontdata.features[what] = scriptlangs + else + fontdata.features = { [what] = scriptlangs } + end + -- + if not lookupstoo then + return + end + -- + local lookups = readlookups(f,lookupoffset,lookuptypes,featurehash,featureorder) + -- + if lookups then + resolvelookups(f,lookupoffset,fontdata,lookups,lookuptypes,lookuphandlers,what,tableoffset) + end + -- + if variationsoffset > 0 then + loadvariations(f,fontdata,variationsoffset,lookuptypes,featurehash,featureorder) + end end end @@ -1836,7 +2393,7 @@ do local length = readushort(f) local coverage = readushort(f) -- bit 8-15 of coverage: format 0 or 2 - local format = bit32.rshift(coverage,8) -- is this ok? + local format = bit32.rshift(coverage,8) -- is this ok if format == 0 then local nofpairs = readushort(f) local searchrange = readushort(f) @@ -1904,91 +2461,126 @@ do end function readers.gdef(f,fontdata,specification) - if specification.glyphs then - local datatable = fontdata.tables.gdef - if datatable then - local tableoffset = datatable.offset - setposition(f,tableoffset) - local version = readulong(f) - local classoffset = tableoffset + readushort(f) - local attachmentoffset = tableoffset + readushort(f) -- used for bitmaps - local ligaturecarets = tableoffset + readushort(f) -- used in editors (maybe nice for tracing) - local markclassoffset = tableoffset + readushort(f) - local marksetsoffset = version == 0x00010002 and (tableoffset + readushort(f)) - local glyphs = fontdata.glyphs - local marks = { } - local markclasses = setmetatableindex("table") - local marksets = setmetatableindex("table") - fontdata.marks = marks - fontdata.markclasses = markclasses - fontdata.marksets = marksets - -- class definitions - setposition(f,classoffset) - local classformat = readushort(f) - if classformat == 1 then - local firstindex = readushort(f) - local lastindex = firstindex + readushort(f) - 1 - for index=firstindex,lastindex do - local class = classes[readushort(f)] - if class == "mark" then - marks[index] = true - end - glyphs[index].class = class + if not specification.glyphs then + return + end + local datatable = fontdata.tables.gdef + if datatable then + local tableoffset = datatable.offset + setposition(f,tableoffset) + local version = readulong(f) + local classoffset = tableoffset + readushort(f) + local attachmentoffset = tableoffset + readushort(f) -- used for bitmaps + local ligaturecarets = tableoffset + readushort(f) -- used in editors (maybe nice for tracing) + local markclassoffset = tableoffset + readushort(f) + local marksetsoffset = version >= 0x00010002 and (tableoffset + readushort(f)) + local varsetsoffset = version >= 0x00010003 and (tableoffset + readulong(f)) + local glyphs = fontdata.glyphs + local marks = { } + local markclasses = setmetatableindex("table") + local marksets = setmetatableindex("table") + fontdata.marks = marks + fontdata.markclasses = markclasses + fontdata.marksets = marksets + -- class definitions + setposition(f,classoffset) + local classformat = readushort(f) + if classformat == 1 then + local firstindex = readushort(f) + local lastindex = firstindex + readushort(f) - 1 + for index=firstindex,lastindex do + local class = classes[readushort(f)] + if class == "mark" then + marks[index] = true end - elseif classformat == 2 then - local nofranges = readushort(f) - for i=1,nofranges do - local firstindex = readushort(f) - local lastindex = readushort(f) - local class = classes[readushort(f)] - if class then - for index=firstindex,lastindex do - glyphs[index].class = class - if class == "mark" then - marks[index] = true - end + glyphs[index].class = class + end + elseif classformat == 2 then + local nofranges = readushort(f) + for i=1,nofranges do + local firstindex = readushort(f) + local lastindex = readushort(f) + local class = classes[readushort(f)] + if class then + for index=firstindex,lastindex do + glyphs[index].class = class + if class == "mark" then + marks[index] = true end end end end - -- mark classes - setposition(f,markclassoffset) - local classformat = readushort(f) - if classformat == 1 then + end + -- mark classes + setposition(f,markclassoffset) + local classformat = readushort(f) + if classformat == 1 then + local firstindex = readushort(f) + local lastindex = firstindex + readushort(f) - 1 + for index=firstindex,lastindex do + markclasses[readushort(f)][index] = true + end + elseif classformat == 2 then + local nofranges = readushort(f) + for i=1,nofranges do local firstindex = readushort(f) - local lastindex = firstindex + readushort(f) - 1 + local lastindex = readushort(f) + local class = markclasses[readushort(f)] for index=firstindex,lastindex do - markclasses[readushort(f)][index] = true + class[index] = true end - elseif classformat == 2 then - local nofranges = readushort(f) - for i=1,nofranges do - local firstindex = readushort(f) - local lastindex = readushort(f) - local class = markclasses[readushort(f)] - for index=firstindex,lastindex do - class[index] = true + end + end + -- mark sets : todo: just make the same as class sets above + if marksetsoffset and marksetsoffset > tableoffset then -- zero offset means no table + setposition(f,marksetsoffset) + local format = readushort(f) + if format == 1 then + local nofsets = readushort(f) + local sets = { } + for i=1,nofsets do + sets[i] = readulong(f) + end + for i=1,nofsets do + local offset = sets[i] + if offset ~= 0 then + marksets[i] = readcoverage(f,marksetsoffset+offset) end end end - -- mark sets : todo: just make the same as class sets above - if marksetsoffset and marksetsoffset > tableoffset then -- zero offset means no table - setposition(f,marksetsoffset) - local format = readushort(f) - if format == 1 then - local nofsets = readushort(f) - local sets = { } - for i=1,nofsets do - sets[i] = readulong(f) - end - for i=1,nofsets do - local offset = sets[i] - if offset ~= 0 then - marksets[i] = readcoverage(f,marksetsoffset+offset) + end + + local factors = specification.factors + + if (specification.variable or factors) and varsetsoffset and varsetsoffset > tableoffset then + + local regions, deltas = readvariationdata(f,varsetsoffset,factors) + + -- setvariabledata(fontdata,"gregions",regions) + + if factors then + fontdata.temporary.getdelta = function(outer,inner) + local delta = deltas[outer+1] + if delta then + local d = delta.deltas[inner+1] + if d then + local scales = delta.scales + local dd = 0 + for i=1,#scales do + local di = d[i] + if di then + dd = dd + scales[i] * di + else + break + end + end + return round(dd) end end + return 0 end end + end end end @@ -2277,173 +2869,534 @@ local function readmathvariants(f,fontdata,offset) end function readers.math(f,fontdata,specification) - if specification.glyphs then - local datatable = fontdata.tables.math - if datatable then - local tableoffset = datatable.offset - setposition(f,tableoffset) - local version = readulong(f) - if version ~= 0x00010000 then - report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"math",fontdata.filename) - return - end - local constants = readushort(f) - local glyphinfo = readushort(f) - local variants = readushort(f) - if constants == 0 then - report("the math table of %a has no constants",fontdata.filename) - else - readmathconstants(f,fontdata,tableoffset+constants) - end - if glyphinfo ~= 0 then - readmathglyphinfo(f,fontdata,tableoffset+glyphinfo) - end - if variants ~= 0 then - readmathvariants(f,fontdata,tableoffset+variants) - end + local tableoffset = gotodatatable(f,fontdata,"math",specification.glyphs) + if tableoffset then + local version = readulong(f) + -- if version ~= 0x00010000 then + -- report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"math",fontdata.filename) + -- return + -- end + local constants = readushort(f) + local glyphinfo = readushort(f) + local variants = readushort(f) + if constants == 0 then + report("the math table of %a has no constants",fontdata.filename) + else + readmathconstants(f,fontdata,tableoffset+constants) + end + if glyphinfo ~= 0 then + readmathglyphinfo(f,fontdata,tableoffset+glyphinfo) + end + if variants ~= 0 then + readmathvariants(f,fontdata,tableoffset+variants) end end end function readers.colr(f,fontdata,specification) - local datatable = fontdata.tables.colr - if datatable then - if specification.glyphs then - local tableoffset = datatable.offset - setposition(f,tableoffset) - local version = readushort(f) - if version ~= 0 then - report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"colr",fontdata.filename) - return - end - if not fontdata.tables.cpal then - report("color table %a in font %a has no mandate %a table","colr",fontdata.filename,"cpal") - fontdata.colorpalettes = { } - end - local glyphs = fontdata.glyphs - local nofglyphs = readushort(f) - local baseoffset = readulong(f) - local layeroffset = readulong(f) - local noflayers = readushort(f) - local layerrecords = { } - local maxclass = 0 - -- The special value 0xFFFF is foreground (but we index from 1). It - -- more looks like indices into a palette so 'class' is a better name - -- than 'palette'. - setposition(f,tableoffset + layeroffset) - for i=1,noflayers do - local slot = readushort(f) - local class = readushort(f) - if class < 0xFFFF then - class = class + 1 - if class > maxclass then - maxclass = class - end + local tableoffset = gotodatatable(f,fontdata,"colr",specification.glyphs) + if tableoffset then + local version = readushort(f) + if version ~= 0 then + report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"colr",fontdata.filename) + return + end + if not fontdata.tables.cpal then + report("color table %a in font %a has no mandate %a table","colr",fontdata.filename,"cpal") + fontdata.colorpalettes = { } + end + local glyphs = fontdata.glyphs + local nofglyphs = readushort(f) + local baseoffset = readulong(f) + local layeroffset = readulong(f) + local noflayers = readushort(f) + local layerrecords = { } + local maxclass = 0 + -- The special value 0xFFFF is foreground (but we index from 1). It + -- more looks like indices into a palette so 'class' is a better name + -- than 'palette'. + setposition(f,tableoffset + layeroffset) + for i=1,noflayers do + local slot = readushort(f) + local class = readushort(f) + if class < 0xFFFF then + class = class + 1 + if class > maxclass then + maxclass = class end - layerrecords[i] = { - slot = slot, - class = class, - } end - fontdata.maxcolorclass = maxclass - setposition(f,tableoffset + baseoffset) - for i=0,nofglyphs-1 do - local glyphindex = readushort(f) - local firstlayer = readushort(f) - local noflayers = readushort(f) - local t = { } - for i=1,noflayers do - t[i] = layerrecords[firstlayer+i] - end - glyphs[glyphindex].colors = t + layerrecords[i] = { + slot = slot, + class = class, + } + end + fontdata.maxcolorclass = maxclass + setposition(f,tableoffset + baseoffset) + for i=0,nofglyphs-1 do + local glyphindex = readushort(f) + local firstlayer = readushort(f) + local noflayers = readushort(f) + local t = { } + for i=1,noflayers do + t[i] = layerrecords[firstlayer+i] end + glyphs[glyphindex].colors = t end - fontdata.hascolor = true end + fontdata.hascolor = true end function readers.cpal(f,fontdata,specification) - if specification.glyphs then - local datatable = fontdata.tables.cpal - if datatable then - local tableoffset = datatable.offset - setposition(f,tableoffset) - local version = readushort(f) - if version > 1 then - report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"cpal",fontdata.filename) - return + local tableoffset = gotodatatable(f,fontdata,"cpal",specification.glyphs) + if tableoffset then + local version = readushort(f) + -- if version > 1 then + -- report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"cpal",fontdata.filename) + -- return + -- end + local nofpaletteentries = readushort(f) + local nofpalettes = readushort(f) + local nofcolorrecords = readushort(f) + local firstcoloroffset = readulong(f) + local colorrecords = { } + local palettes = { } + for i=1,nofpalettes do + palettes[i] = readushort(f) + end + if version == 1 then + -- used for guis + local palettettypesoffset = readulong(f) + local palettelabelsoffset = readulong(f) + local paletteentryoffset = readulong(f) + end + setposition(f,tableoffset+firstcoloroffset) + for i=1,nofcolorrecords do + local b, g, r, a = readbytes(f,4) + colorrecords[i] = { + r, g, b, a ~= 255 and a or nil, + } + end + for i=1,nofpalettes do + local p = { } + local o = palettes[i] + for j=1,nofpaletteentries do + p[j] = colorrecords[o+j] end - local nofpaletteentries = readushort(f) - local nofpalettes = readushort(f) - local nofcolorrecords = readushort(f) - local firstcoloroffset = readulong(f) - local colorrecords = { } - local palettes = { } - for i=1,nofpalettes do - palettes[i] = readushort(f) - end - if version == 1 then - -- used for guis - local palettettypesoffset = readulong(f) - local palettelabelsoffset = readulong(f) - local paletteentryoffset = readulong(f) - end - setposition(f,tableoffset+firstcoloroffset) - for i=1,nofcolorrecords do - local b, g, r, a = readbytes(f,4) - colorrecords[i] = { - r, g, b, a ~= 255 and a or nil, + palettes[i] = p + end + fontdata.colorpalettes = palettes + end +end + +function readers.svg(f,fontdata,specification) + local tableoffset = gotodatatable(f,fontdata,"svg",specification.glyphs) + if tableoffset then + local version = readushort(f) + -- if version ~= 0 then + -- report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"svg",fontdata.filename) + -- return + -- end + local glyphs = fontdata.glyphs + local indexoffset = tableoffset + readulong(f) + local reserved = readulong(f) + setposition(f,indexoffset) + local nofentries = readushort(f) + local entries = { } + for i=1,nofentries do + entries[i] = { + first = readushort(f), + last = readushort(f), + offset = indexoffset + readulong(f), + length = readulong(f), + } + end + for i=1,nofentries do + local entry = entries[i] + setposition(f,entry.offset) + entries[i] = { + first = entry.first, + last = entry.last, + data = readstring(f,entry.length) + } + end + fontdata.svgshapes = entries + end + fontdata.hascolor = true +end + +-- + AVAR : optional +-- + CFF2 : otf outlines +-- - CVAR : ttf hinting, not needed +-- + FVAR : the variations +-- + GVAR : ttf outline changes +-- + HVAR : horizontal changes +-- + MVAR : metric changes +-- + STAT : relations within fonts +-- * VVAR : vertical changes +-- +-- * BASE : extra baseline adjustments +-- - GASP : not needed +-- + GDEF : not needed (carets) +-- + GPOS : adapted device tables (needed?) +-- + GSUB : new table +-- + NAME : 25 added + +function readers.stat(f,fontdata,specification) + local tableoffset = gotodatatable(f,fontdata,"stat",true) -- specification.variable + if tableoffset then + local extras = fontdata.extras + local version = readulong(f) -- 0x00010000 + local axissize = readushort(f) + local nofaxis = readushort(f) + local axisoffset = readulong(f) + local nofvalues = readushort(f) + local valuesoffset = readulong(f) + local fallbackname = extras[readushort(f)] -- beta fonts mess up + local axis = { } + local values = { } + setposition(f,tableoffset+axisoffset) + for i=1,nofaxis do + axis[i] = { + tag = readtag(f), + name = lower(extras[readushort(f)]), + ordering = readushort(f), -- maybe gaps + variants = { } + } + end + -- flags: + -- + -- 0x0001 : OlderSiblingFontAttribute + -- 0x0002 : ElidableAxisValueName + -- 0xFFFC : reservedFlags + -- + setposition(f,tableoffset+valuesoffset) + for i=1,nofvalues do + values[i] = readushort(f) + end + for i=1,nofvalues do + setposition(f,tableoffset + valuesoffset + values[i]) + local format = readushort(f) + local index = readushort(f) + 1 + local flags = readushort(f) + local name = lower(extras[readushort(f)]) + local value = readfixed(f) + local variant + if format == 1 then + variant = { + flags = flags, + name = name, + value = value, + } + elseif format == 2 then + variant = { + flags = flags, + name = name, + value = value, + minimum = readfixed(f), + maximum = readfixed(f), + } + elseif format == 3 then + variant = { + flags = flags, + name = name, + value = value, + link = readfixed(f), } end - for i=1,nofpalettes do - local p = { } - local o = palettes[i] - for j=1,nofpaletteentries do - p[j] = colorrecords[o+j] + insert(axis[index].variants,variant) + end + sort(axis,function(a,b) + return a.ordering < b.ordering + end) + for i=1,#axis do + local a = axis[i] + sort(a.variants,function(a,b) + return a.name < b.name + end) + a.ordering = nil + end + setvariabledata(fontdata,"designaxis",axis) + setvariabledata(fontdata,"fallbackname",fallbackname) + end +end + +-- The avar table is optional and used in combination with fvar. Given the +-- detailed explanation about bad valeus we expect the worst and do some +-- checking. + +function readers.avar(f,fontdata,specification) + local tableoffset = gotodatatable(f,fontdata,"avar",true) -- specification.variable + if tableoffset then + + local function collect() + local nofvalues = readulong(f) + local values = { } + local lastfrom = false + local lastto = false + for i=1,nofvalues do + local f, t = read2dot14(f), read2dot14(f) + if lastfrom and f <= lastfrom then + -- ignore + elseif lastto and t >= lastto then + -- ignore + else + values[#values+1] = { f, t } + lasfrom, lastto = f, t end - palettes[i] = p end - fontdata.colorpalettes = palettes + nofvalues = #values + if nofvalues > 2 then + local some = values[1] + if some[1] == -1 and some[2] == -1 then + some = values[nofvalues] + if some[1] == 1 and some[2] == 1 then + for i=2,size-1 do + some = values[i] + if some[1] == 0 and some[2] == 0 then + return values + end + end + end + end + end + return false + end + + local version = readulong(f) -- 1.0 + local reserved = readulong(f) + local nofaxis = readulong(f) + local segments = { } + for i=1,nofaxis do + segments[i] = collect() end + setvariabledata(fontdata,"segments",segments) end end -function readers.svg(f,fontdata,specification) - local datatable = fontdata.tables.svg - if datatable then - if specification.glyphs then - local tableoffset = datatable.offset - setposition(f,tableoffset) - local version = readushort(f) - if version ~= 0 then - report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"svg",fontdata.filename) - return +function readers.fvar(f,fontdata,specification) + local tableoffset = gotodatatable(f,fontdata,"fvar",true) -- specification.variable or specification.instancenames + if tableoffset then + local version = readulong(f) -- 1.0 + local offsettoaxis = tableoffset + readushort(f) + local reserved = skipshort(f) + -- pair 1 + local nofaxis = readushort(f) + local sizeofaxis = readushort(f) + -- pair 2 + local nofinstances = readushort(f) + local sizeofinstances = readushort(f) + -- + local extras = fontdata.extras + local axis = { } + local instances = { } + -- + setposition(f,offsettoaxis) + -- + for i=1,nofaxis do + axis[i] = { + tag = readtag(f), -- ital opsz slnt wdth wght + minimum = readfixed(f), -- we get weird values from a test font ... to be checked + default = readfixed(f), -- idem + maximum = readfixed(f), -- idem + flags = readushort(f), + name = lower(extras[readushort(f)]), + } + local n = sizeofaxis - 20 + if n > 0 then + skipbytes(f,n) + elseif n < 0 then + -- error end - local glyphs = fontdata.glyphs - local indexoffset = tableoffset + readulong(f) - local reserved = readulong(f) - setposition(f,indexoffset) - local nofentries = readushort(f) - local entries = { } - for i=1,nofentries do - entries[i] = { - first = readushort(f), - last = readushort(f), - offset = indexoffset + readulong(f), - length = readulong(f), + end + -- + local nofbytes = 2 + 2 + 2 + nofaxis * 4 + local readpsname = nofbytes <= sizeofinstances + local skippable = sizeofinstances - nofbytes + for i=1,nofinstances do + local subfamid = readushort(f) + local flags = readushort(f) -- 0, not used yet + local values = { } + for i=1,nofaxis do + -- depends on what we want to see: + -- + -- values[axis[i].tag] = readfixed(f) + -- + values[i] = { + axis = axis[i].tag, + value = readfixed(f), } end - for i=1,nofentries do - local entry = entries[i] - setposition(f,entry.offset) - entries[i] = { - first = entry.first, - last = entry.last, - data = readstring(f,entry.length) - } + local psnameid = readpsname and readushort(f) or 0xFFFF + if subfamid == 2 or subfamid == 17 then + -- okay + elseif subfamid == 0xFFFF then + subfamid = nil + elseif subfamid <= 256 or subfamid >= 32768 then + subfamid = nil -- actually an error + end + if psnameid == 6 then + -- okay + elseif psnameid == 0xFFFF then + psnameid = nil + elseif psnameid <= 256 or psnameid >= 32768 then + psnameid = nil -- actually an error + end + instances[i] = { + -- flags = flags, + subfamily = extras[subfamid], + psname = psnameid and extras[psnameid] or nil, + values = values, + } + if skippable > 0 then + skipbytes(f,skippable) + end + end + setvariabledata(fontdata,"axis",axis) + setvariabledata(fontdata,"instances",instances) + end +end + +function readers.hvar(f,fontdata,specification) + local factors = specification.factors + if not factors then + return + end + local tableoffset = gotodatatable(f,fontdata,"hvar",specification.variable) + if not tableoffset then + return + end + + local version = readulong(f) -- 1.0 + local variationoffset = tableoffset + readulong(f) -- the store + local advanceoffset = tableoffset + readulong(f) + local lsboffset = tableoffset + readulong(f) + local rsboffset = tableoffset + readulong(f) + + local regions = { } + local variations = { } + local innerindex = { } -- size is mapcount + local outerindex = { } -- size is mapcount + + if variationoffset > 0 then + regions, deltas = readvariationdata(f,variationoffset,factors) + end + + if not regions then + -- for now .. what to do ? + return + end + + if advanceoffset > 0 then + -- + -- innerIndexBitCountMask = 0x000F + -- mapEntrySizeMask = 0x0030 + -- reservedFlags = 0xFFC0 + -- + -- outerIndex = entry >> ((entryFormat & innerIndexBitCountMask) + 1) + -- innerIndex = entry & ((1 << ((entryFormat & innerIndexBitCountMask) + 1)) - 1) + -- + setposition(f,advanceoffset) + local format = readushort(f) -- todo: check + local mapcount = readushort(f) + local entrysize = rshift(band(format,0x0030),4) + 1 + local nofinnerbits = band(format,0x000F) + 1 -- n of inner bits + local innermask = lshift(1,nofinnerbits) - 1 + local readcardinal = read_cardinal[entrysize] -- 1 upto 4 bytes + for i=0,mapcount-1 do + local mapdata = readcardinal(f) + outerindex[i] = rshift(mapdata,nofinnerbits) + innerindex[i] = band(mapdata,innermask) + end + -- use last entry when no match i + local glyphs = fontdata.glyphs + for i=0,fontdata.nofglyphs-1 do + local glyph = glyphs[i] + local width = glyph.width + if width then + local outer = outerindex[i] or 0 + local inner = innerindex[i] or i + if outer and inner then -- not needed + local delta = deltas[outer+1] + if delta then + local d = delta.deltas[inner+1] + if d then + local scales = delta.scales + local deltaw = 0 + for i=1,#scales do + local di = d[i] + if di then + deltaw = deltaw + scales[i] * di + else + break -- can't happen + end + end +-- report("index: %i, outer: %i, inner: %i, deltas: %|t, scales: %|t, width: %i, delta %i", +-- i,outer,inner,d,scales,width,round(deltaw)) + glyph.width = width + round(deltaw) + end + end + end + end + end + + end + + -- if lsboffset > 0 then + -- -- we don't use left side bearings + -- end + + -- if rsboffset > 0 then + -- -- we don't use right side bearings + -- end + + -- setvariabledata(fontdata,"hregions",regions) + +end + +function readers.vvar(f,fontdata,specification) + if not specification.variable then + return + end +end + +function readers.mvar(f,fontdata,specification) + local tableoffset = gotodatatable(f,fontdata,"mvar",specification.variable) + if tableoffset then + local version = readulong(f) -- 1.0 + local reserved = skipshort(f,1) + local recordsize = readushort(f) + local nofrecords = readushort(f) + local offsettostore = tableoffset + readushort(f) + local dimensions = { } + local factors = specification.factors + if factors then + local regions, deltas = readvariationdata(f,offsettostore,factors) + for i=1,nofrecords do + local tag = readtag(f) + local var = variabletags[tag] + if var then + local outer = readushort(f) + local inner = readushort(f) + local delta = deltas[outer+1] + if delta then + local d = delta.deltas[inner+1] + if d then + local scales = delta.scales + local dd = 0 + for i=1,#scales do + dd = dd + scales[i] * d[i] + end + var(fontdata,round(dd)) + end + end + else + skipshort(f,2) + end + if recordsize > 8 then -- 4 + 2 + 2 + skipbytes(recordsize-8) + end end - fontdata.svgshapes = entries end - fontdata.hascolor = true + -- setvariabledata(fontdata,"mregions",regions) end end diff --git a/tex/context/base/mkiv/font-fil.mkvi b/tex/context/base/mkiv/font-fil.mkvi index fbe4b8442..ba9d5e2c6 100644 --- a/tex/context/base/mkiv/font-fil.mkvi +++ b/tex/context/base/mkiv/font-fil.mkvi @@ -469,4 +469,8 @@ \setxvalue{\??fontclass\fontclass#style\s!designsize}{#designsize}% \setxvalue{\??fontclass\fontclass#style\s!direction }{#direction}} +% bonus + +\let\currentfontinstancespec\clf_currentfontinstancespec % expandable + \protect \endinput diff --git a/tex/context/base/mkiv/font-lib.mkvi b/tex/context/base/mkiv/font-lib.mkvi index fa8797394..e37da2545 100644 --- a/tex/context/base/mkiv/font-lib.mkvi +++ b/tex/context/base/mkiv/font-lib.mkvi @@ -61,10 +61,6 @@ %registerctxluafile{font-afm}{1.001} \registerctxluafile{font-afk}{1.001} -% shapes - -\registerctxluafile{font-shp}{1.001} - % tfm \registerctxluafile{font-tfm}{1.001} @@ -74,6 +70,10 @@ \registerctxluafile{font-syn}{1.001} \registerctxluafile{font-trt}{1.001} +% shapes + +\registerctxluafile{font-shp}{1.001} + % so far \registerctxluafile{font-pat}{1.001} % patchers diff --git a/tex/context/base/mkiv/font-mps.lua b/tex/context/base/mkiv/font-mps.lua index 6c441699b..69b2af68c 100644 --- a/tex/context/base/mkiv/font-mps.lua +++ b/tex/context/base/mkiv/font-mps.lua @@ -307,8 +307,12 @@ function metapost.output(kind,font,char,advance,shift,ex) xfactor = xfactor * wfactor end local paths = topaths(glyf,xfactor,yfactor) - local code = f_code(kind,#paths,advance,shift,paths) - return code, character.width * fc * wfactor + if paths then + local code = f_code(kind,#paths,advance,shift,paths) + return code, character.width * fc * wfactor + else + return "", 0 + end end end end diff --git a/tex/context/base/mkiv/font-onr.lua b/tex/context/base/mkiv/font-onr.lua index d986a0ddc..7fa51bf05 100644 --- a/tex/context/base/mkiv/font-onr.lua +++ b/tex/context/base/mkiv/font-onr.lua @@ -209,7 +209,7 @@ do } }, } - fonts.handlers.otf.readers.parsecharstrings(data,glyphs,true,true) + fonts.handlers.otf.readers.parsecharstrings(false,data,glyphs,true,true) else lpegmatch(p_filternames,binary) end diff --git a/tex/context/base/mkiv/font-otc.lua b/tex/context/base/mkiv/font-otc.lua index a99d3db9f..dc855a74d 100644 --- a/tex/context/base/mkiv/font-otc.lua +++ b/tex/context/base/mkiv/font-otc.lua @@ -318,9 +318,18 @@ local function addfeature(data,feature,specifications) return coverage end + local function resetspacekerns() + -- a bit of a hack, this nil setting but it forces a + -- rehash of the resources needed .. the feature itself + -- should be a kern (at least for now) + data.properties.hasspacekerns = true + data.resources .spacekerns = nil + end + local function prepare_kern(list,featuretype) local coverage = { } local cover = coveractions[featuretype] + local isspace = false for code, replacement in next, list do local unicode = tounicode(code) local description = descriptions[unicode] @@ -330,11 +339,17 @@ local function addfeature(data,feature,specifications) local u = tounicode(k) if u then r[u] = v + if u == 32 then + isspace = true + end end end if next(r) then cover(coverage,unicode,r) done = done + 1 + if unicode == 32 then + isspace = true + end else skip = skip + 1 end @@ -342,6 +357,9 @@ local function addfeature(data,feature,specifications) skip = skip + 1 end end + if isspace then + resetspacekerns() + end return coverage end @@ -358,11 +376,17 @@ local function addfeature(data,feature,specifications) local u = tounicode(k) if u then r[u] = v + if u == 32 then + isspace = true + end end end if next(r) then cover(coverage,unicode,r) done = done + 1 + if unicode == 32 then + isspace = true + end else skip = skip + 1 end @@ -370,6 +394,9 @@ local function addfeature(data,feature,specifications) skip = skip + 1 end end + if isspace then + resetspacekerns() + end else report_otf("unknown cover type %a",featuretype) end diff --git a/tex/context/base/mkiv/font-oti.lua b/tex/context/base/mkiv/font-oti.lua index e10a261cd..4c6053be0 100644 --- a/tex/context/base/mkiv/font-oti.lua +++ b/tex/context/base/mkiv/font-oti.lua @@ -160,3 +160,103 @@ function otffeatures.checkeddefaultlanguage(featuretype,autolanguage,languages) end end end + +-- the following might become available generic in due time but for now +-- this is some context playground (development code) + +-- if not context then +-- return +-- end + +-- local helpers = otf.readers.helpers +-- local axistofactors = helpers.axistofactors +-- local normalizedaxis = helpers.normalizedaxis +-- local getaxisscale = helpers.getaxisscale +-- local cleanname = containers.cleanname + +-- local function validvariable(tfmdata) +-- if tfmdata.properties.factors then +-- return +-- end +-- local resources = tfmdata.resources +-- local variabledata = resources and resources.variabledata +-- if not variabledata then +-- return +-- end +-- local instances = variabledata.instances +-- local axis = variabledata.axis +-- local segments = variabledata.segments +-- if instances and axis then +-- return instances, axis, segments +-- end +-- end + +-- local function initializeinstance(tfmdata,value) +-- if type(value) == "string" then +-- local instances, axis, segments = validvariable(tfmdata) +-- if instances then +-- local values +-- for i=1,#instances do +-- local instance = instances[i] +-- if cleanname(instance.subfamily) == value then +-- values = instance.values +-- break +-- end +-- end +-- if values then +-- local factors = { } +-- for i=1,#axis do +-- local a = axis[i] +-- factors[i] = getaxisscale(segments,a.minimum,a.default,a.maximum,values[i].value) +-- end +-- tfmdata.properties.instance = { +-- hash = instance, +-- factors = factors, +-- } +-- end +-- else +-- report("incomplete variable data") +-- end +-- end +-- end + +-- local function initializeaxis(tfmdata,value) +-- if type(value) == "string" then +-- local instances, axis, segments = validvariable(tfmdata) +-- if instances then +-- local values = axistofactors(value) +-- if values then +-- local factors = { } +-- for i=1,#axis do +-- local a = axis[i] +-- local d = a.default +-- factors[i] = getaxisscale(segments,a.minimum,d,a.maximum,values[a.name or a.tag] or d) +-- end +-- tfmdata.properties.instance = { +-- hash = cleanname(value), +-- factors = factors, +-- } +-- end +-- else +-- report("incomplete variable data") +-- end +-- end +-- end + +-- registerotffeature { +-- name = "instance", +-- description = "variation instance", +-- initializers = { +-- node = initializeinstance, +-- base = initializeinstance, +-- } +-- } + +-- registerotffeature { +-- name = "axis", +-- description = "variation axis", +-- initializers = { +-- node = initializeaxis, +-- base = initializeaxis, +-- } +-- } diff --git a/tex/context/base/mkiv/font-otl.lua b/tex/context/base/mkiv/font-otl.lua index 4b97a90a2..9e4e255e3 100644 --- a/tex/context/base/mkiv/font-otl.lua +++ b/tex/context/base/mkiv/font-otl.lua @@ -52,7 +52,7 @@ local report_otf = logs.reporter("fonts","otf loading") local fonts = fonts local otf = fonts.handlers.otf -otf.version = 3.027 -- beware: also sync font-mis.lua and in mtx-fonts +otf.version = 3.028 -- beware: also sync font-mis.lua and in mtx-fonts otf.cache = containers.define("fonts", "otl", otf.version, true) otf.svgcache = containers.define("fonts", "svg", otf.version, true) otf.pdfcache = containers.define("fonts", "pdf", otf.version, true) @@ -93,31 +93,16 @@ registerdirective("fonts.otf.loader.force", function(v) forceload = registerdirective("fonts.otf.loader.syncspace", function(v) syncspace = v end) registerdirective("fonts.otf.loader.forcenotdef", function(v) forcenotdef = v end) --- local function load_featurefile(raw,featurefile) --- if featurefile and featurefile ~= "" then --- if trace_loading then --- report_otf("using featurefile %a", featurefile) --- end --- -- TODO: apply_featurefile(raw, featurefile) --- end --- end - -- otfenhancers.patch("before","migrate metadata","cambria",function() end) registerotfenhancer("check extra features", function() end) -- placeholder -function otf.load(filename,sub,featurefile) -- second argument (format) is gone ! - -- - local featurefile = nil -- not supported (yet) - -- +function otf.load(filename,sub,instance) local base = file.basename(file.removesuffix(filename)) - local name = file.removesuffix(base) + local name = file.removesuffix(base) -- already no suffix local attr = lfs.attributes(filename) local size = attr and attr.size or 0 local time = attr and attr.modification or 0 - if featurefile then - name = name .. "@" .. file.removesuffix(file.basename(featurefile)) - end -- sub can be number of string if sub == "" then sub = false @@ -126,69 +111,22 @@ function otf.load(filename,sub,featurefile) -- second argument (format) is gone if sub then hash = hash .. "-" .. sub end + if instance then + hash = hash .. "-" .. instance + end hash = containers.cleanname(hash) - -- local featurefiles - -- if featurefile then - -- featurefiles = { } - -- for s in gmatch(featurefile,"[^,]+") do - -- local name = resolvers.findfile(file.addsuffix(s,'fea'),'fea') or "" - -- if name == "" then - -- report_otf("loading error, no featurefile %a",s) - -- else - -- local attr = lfs.attributes(name) - -- featurefiles[#featurefiles+1] = { - -- name = name, - -- size = attr and attr.size or 0, - -- time = attr and attr.modification or 0, - -- } - -- end - -- end - -- if #featurefiles == 0 then - -- featurefiles = nil - -- end - -- end local data = containers.read(otf.cache,hash) local reload = not data or data.size ~= size or data.time ~= time or data.tableversion ~= otfreaders.tableversion if forceload then report_otf("forced reload of %a due to hard coded flag",filename) reload = true end - -- if not reload then - -- local featuredata = data.featuredata - -- if featurefiles then - -- if not featuredata or #featuredata ~= #featurefiles then - -- reload = true - -- else - -- for i=1,#featurefiles do - -- local fi, fd = featurefiles[i], featuredata[i] - -- if fi.name ~= fd.name or fi.size ~= fd.size or fi.time ~= fd.time then - -- reload = true - -- break - -- end - -- end - -- end - -- elseif featuredata then - -- reload = true - -- end - -- if reload then - -- report_otf("loading: forced reload due to changed featurefile specification %a",featurefile) - -- end - -- end if reload then report_otf("loading %a, hash %a",filename,hash) -- starttiming(otfreaders) - data = otfreaders.loadfont(filename,sub or 1) -- we can pass the number instead (if it comes from a name search) - -- - -- if featurefiles then - -- for i=1,#featurefiles do - -- load_featurefile(data,featurefiles[i].name) - -- end - -- end - -- - -- + data = otfreaders.loadfont(filename,sub or 1,instance) -- we can pass the number instead (if it comes from a name search) if data then - -- local resources = data.resources local svgshapes = resources.svgshapes if svgshapes then @@ -206,7 +144,6 @@ function otf.load(filename,sub,featurefile) -- second argument (format) is gone } end end - -- otfreaders.compact(data) otfreaders.rehash(data,"unicodes") otfreaders.addunicodetable(data) @@ -550,7 +487,8 @@ local function otftotfm(specification) local subindex = specification.subindex local filename = specification.filename local features = specification.features.normal - local rawdata = otf.load(filename,sub,features and features.featurefile) + local instance = specification.instance or (features and features.axis) + local rawdata = otf.load(filename,sub,instance) if rawdata and next(rawdata) then local descriptions = rawdata.descriptions rawdata.lookuphash = { } -- to be done diff --git a/tex/context/base/mkiv/font-otr.lua b/tex/context/base/mkiv/font-otr.lua index 43a059bb9..eab33f8b6 100644 --- a/tex/context/base/mkiv/font-otr.lua +++ b/tex/context/base/mkiv/font-otr.lua @@ -75,12 +75,13 @@ local setmetatableindex = table.setmetatableindex local formatters = string.formatters local sortedkeys = table.sortedkeys local sortedhash = table.sortedhash -local stripstring = string.strip +local stripstring = string.nospaces local utf16_to_utf8_be = utf.utf16_to_utf8_be local report = logs.reporter("otf reader") local trace_cmap = false -- only for checking issues +local trace_cmap_detail = false -- only for checking issues fonts = fonts or { } local handlers = fonts.handlers or { } @@ -703,6 +704,34 @@ local panosewidths = { -- We implement a reader per table. +-- helper + +local helpers = { } +readers.helpers = helpers + +local function gotodatatable(f,fontdata,tag,criterium) + if criterium and f then + local datatable = fontdata.tables[tag] + if datatable then + local tableoffset = datatable.offset + setposition(f,tableoffset) + return tableoffset + end + end +end + +local function setvariabledata(fontdata,tag,data) + local variabledata = fontdata.variabledata + if variabledata then + variabledata[tag] = data + else + fontdata.variabledata = { [tag] = data } + end +end + +helpers.gotodatatable = gotodatatable +helpers.setvariabledata = setvariabledata + -- The name table is probably the first one to load. After all this one provides -- useful information about what we deal with. The complication is that we need -- to filter the best one available. @@ -718,14 +747,13 @@ local platformnames = { } function readers.name(f,fontdata,specification) - local datatable = fontdata.tables.name - if datatable then - setposition(f,datatable.offset) + local tableoffset = gotodatatable(f,fontdata,"name",true) + if tableoffset then local format = readushort(f) local nofnames = readushort(f) local offset = readushort(f) -- we can also provide a raw list as extra, todo as option - local start = datatable.offset + offset + local start = tableoffset + offset local namelists = { unicode = { }, windows = { }, @@ -748,25 +776,15 @@ function readers.name(f,fontdata,specification) if encoding and language then local index = readushort(f) local name = reservednames[index] - if name then - namelist[#namelist+1] = { - platform = platform, - encoding = encoding, - language = language, - name = name, - length = readushort(f), - offset = start + readushort(f), - } - else -namelist[#namelist+1] = { - platform = platform, - encoding = encoding, - language = language, - length = readushort(f), - offset = start + readushort(f), -} --- skipshort(f,2) - end + namelist[#namelist+1] = { + platform = platform, + encoding = encoding, + language = language, + name = name, + index = index, + length = readushort(f), + offset = start + readushort(f), + } else skipshort(f,3) end @@ -803,6 +821,7 @@ namelist[#namelist+1] = { for i=1,#namelist do local name = namelist[i] local nametag = name.name + local index = name.index if not done[nametag or i] then local encoding = name.encoding local language = name.language @@ -824,7 +843,7 @@ namelist[#namelist+1] = { language = language, } end - extras[i-1] = content + extras[index] = content done[nametag or i] = true end end @@ -896,9 +915,8 @@ end -- properties table afterwards. readers["os/2"] = function(f,fontdata) - local datatable = fontdata.tables["os/2"] - if datatable then - setposition(f,datatable.offset) + local tableoffset = gotodatatable(f,fontdata,"os/2",true) + if tableoffset then local version = readushort(f) local windowsmetrics = { version = version, @@ -957,9 +975,8 @@ readers["os/2"] = function(f,fontdata) end readers.head = function(f,fontdata) - local datatable = fontdata.tables.head - if datatable then - setposition(f,datatable.offset) + local tableoffset = gotodatatable(f,fontdata,"head",true) + if tableoffset then local fontheader = { version = readfixed(f), revision = readfixed(f), @@ -990,68 +1007,60 @@ end -- variables are not used but nofmetrics is quite important. readers.hhea = function(f,fontdata,specification) - if specification.details then - local datatable = fontdata.tables.hhea - if datatable then - setposition(f,datatable.offset) - fontdata.horizontalheader = { - version = readfixed(f), -- two ushorts: major minor - ascender = readfword(f), - descender = readfword(f), - linegap = readfword(f), - maxadvancewidth = readufword(f), - minleftsidebearing = readfword(f), - minrightsidebearing = readfword(f), - maxextent = readfword(f), - caretsloperise = readshort(f), - caretsloperun = readshort(f), - caretoffset = readshort(f), - reserved_1 = readshort(f), - reserved_2 = readshort(f), - reserved_3 = readshort(f), - reserved_4 = readshort(f), - metricdataformat = readshort(f), - nofmetrics = readushort(f), - } - else - fontdata.horizontalheader = { - nofmetrics = 0, - } - end + local tableoffset = gotodatatable(f,fontdata,"hhea",specification.details) + if tableoffset then + fontdata.horizontalheader = { + version = readfixed(f), -- two ushorts: major minor + ascender = readfword(f), + descender = readfword(f), + linegap = readfword(f), + maxadvancewidth = readufword(f), + minleftsidebearing = readfword(f), + minrightsidebearing = readfword(f), + maxextent = readfword(f), + caretsloperise = readshort(f), + caretsloperun = readshort(f), + caretoffset = readshort(f), + reserved_1 = readshort(f), + reserved_2 = readshort(f), + reserved_3 = readshort(f), + reserved_4 = readshort(f), + metricdataformat = readshort(f), + nofmetrics = readushort(f), + } + else + fontdata.horizontalheader = { + nofmetrics = 0, + } end end readers.vhea = function(f,fontdata,specification) - if specification.details then - local datatable = fontdata.tables.vhea - if datatable then - setposition(f,datatable.offset) - local version = readfixed(f) - fontdata.verticalheader = { - version = version, - ascender = readfword(f), - descender = readfword(f), - linegap = readfword(f), - maxadvanceheight = readufword(f), - mintopsidebearing = readfword(f), - minbottomsidebearing = readfword(f), - maxextent = readfword(f), - caretsloperise = readshort(f), - caretsloperun = readshort(f), - caretoffset = readshort(f), - reserved_1 = readshort(f), - reserved_2 = readshort(f), - reserved_3 = readshort(f), - reserved_4 = readshort(f), - metricdataformat = readshort(f), - nofmetrics = readushort(f), - } --- inspect(fontdata.verticalheader) - else - fontdata.verticalheader = { - nofmetrics = 0, - } - end + local tableoffset = gotodatatable(f,fontdata,"vhea",specification.details) + if tableoffset then + fontdata.verticalheader = { + version = readfixed(f), + ascender = readfword(f), + descender = readfword(f), + linegap = readfword(f), + maxadvanceheight = readufword(f), + mintopsidebearing = readfword(f), + minbottomsidebearing = readfword(f), + maxextent = readfword(f), + caretsloperise = readshort(f), + caretsloperun = readshort(f), + caretoffset = readshort(f), + reserved_1 = readshort(f), + reserved_2 = readshort(f), + reserved_3 = readshort(f), + reserved_4 = readshort(f), + metricdataformat = readshort(f), + nofmetrics = readushort(f), + } + else + fontdata.verticalheader = { + nofmetrics = 0, + } end end @@ -1061,44 +1070,40 @@ end -- fontdata.maximumprofile can be bad readers.maxp = function(f,fontdata,specification) - if specification.details then - local datatable = fontdata.tables.maxp - if datatable then - setposition(f,datatable.offset) - local version = readfixed(f) - local nofglyphs = readushort(f) - fontdata.nofglyphs = nofglyphs - if version == 0.5 then - fontdata.maximumprofile = { - version = version, - nofglyphs = nofglyphs, - } - return - elseif version == 1.0 then - fontdata.maximumprofile = { - version = version, - nofglyphs = nofglyphs, - points = readushort(f), - contours = readushort(f), - compositepoints = readushort(f), - compositecontours = readushort(f), - zones = readushort(f), - twilightpoints = readushort(f), - storage = readushort(f), - functiondefs = readushort(f), - instructiondefs = readushort(f), - stackelements = readushort(f), - sizeofinstructions = readushort(f), - componentelements = readushort(f), - componentdepth = readushort(f), - } - return - end + local tableoffset = gotodatatable(f,fontdata,"maxp",specification.details) + if tableoffset then + local version = readfixed(f) + local nofglyphs = readushort(f) + fontdata.nofglyphs = nofglyphs + if version == 0.5 then + fontdata.maximumprofile = { + version = version, + nofglyphs = nofglyphs, + } + elseif version == 1.0 then + fontdata.maximumprofile = { + version = version, + nofglyphs = nofglyphs, + points = readushort(f), + contours = readushort(f), + compositepoints = readushort(f), + compositecontours = readushort(f), + zones = readushort(f), + twilightpoints = readushort(f), + storage = readushort(f), + functiondefs = readushort(f), + instructiondefs = readushort(f), + stackelements = readushort(f), + sizeofinstructions = readushort(f), + componentelements = readushort(f), + componentdepth = readushort(f), + } + else + fontdata.maximumprofile = { + version = version, + nofglyphs = 0, + } end - fontdata.maximumprofile = { - version = version, - nofglyphs = 0, - } end end @@ -1106,86 +1111,78 @@ end -- course). readers.hmtx = function(f,fontdata,specification) - if specification.glyphs then - local datatable = fontdata.tables.hmtx - if datatable then - setposition(f,datatable.offset) - local horizontalheader = fontdata.horizontalheader - local nofmetrics = horizontalheader.nofmetrics - local glyphs = fontdata.glyphs - local nofglyphs = fontdata.nofglyphs - local width = 0 -- advance - local leftsidebearing = 0 - for i=0,nofmetrics-1 do - local glyph = glyphs[i] - width = readshort(f) - leftsidebearing = readshort(f) - if width ~= 0 then - glyph.width = width - end - -- if leftsidebearing ~= 0 then - -- glyph.lsb = leftsidebearing - -- end + local tableoffset = gotodatatable(f,fontdata,"hmtx",specification.glyphs) + if tableoffset then + local horizontalheader = fontdata.horizontalheader + local nofmetrics = horizontalheader.nofmetrics + local glyphs = fontdata.glyphs + local nofglyphs = fontdata.nofglyphs + local width = 0 -- advance + local leftsidebearing = 0 + for i=0,nofmetrics-1 do + local glyph = glyphs[i] + width = readshort(f) + leftsidebearing = readshort(f) + if width ~= 0 then + glyph.width = width end - -- The next can happen in for instance a monospace font or in a cjk font - -- with fixed widths. - for i=nofmetrics,nofglyphs-1 do - local glyph = glyphs[i] - if width ~= 0 then - glyph.width = width - end - -- if leftsidebearing ~= 0 then - -- glyph.lsb = leftsidebearing - -- end + -- if leftsidebearing ~= 0 then + -- glyph.lsb = leftsidebearing + -- end + end + -- The next can happen in for instance a monospace font or in a cjk font + -- with fixed widths. + for i=nofmetrics,nofglyphs-1 do + local glyph = glyphs[i] + if width ~= 0 then + glyph.width = width end + -- if leftsidebearing ~= 0 then + -- glyph.lsb = leftsidebearing + -- end end + -- hm, there can be a lsb here end end readers.vmtx = function(f,fontdata,specification) - if specification.glyphs then - local datatable = fontdata.tables.vmtx - if datatable then - setposition(f,datatable.offset) - local verticalheader = fontdata.verticalheader - local nofmetrics = verticalheader.nofmetrics - local glyphs = fontdata.glyphs - local nofglyphs = fontdata.nofglyphs - local vheight = 0 - local vdefault = verticalheader.ascender + verticalheader.descender - local topsidebearing = 0 - for i=0,nofmetrics-1 do - local glyph = glyphs[i] - vheight = readshort(f) - topsidebearing = readshort(f) - if vheight ~= 0 and vheight ~= vdefault then - glyph.vheight = vheight - end - -- if topsidebearing ~= 0 then - -- glyph.tsb = topsidebearing - -- end + local tableoffset = gotodatatable(f,fontdata,"vmtx",specification.glyphs) + if tableoffset then + local verticalheader = fontdata.verticalheader + local nofmetrics = verticalheader.nofmetrics + local glyphs = fontdata.glyphs + local nofglyphs = fontdata.nofglyphs + local vheight = 0 + local vdefault = verticalheader.ascender + verticalheader.descender + local topsidebearing = 0 + for i=0,nofmetrics-1 do + local glyph = glyphs[i] + vheight = readshort(f) + topsidebearing = readshort(f) + if vheight ~= 0 and vheight ~= vdefault then + glyph.vheight = vheight end - -- The next can happen in for instance a monospace font or in a cjk font - -- with fixed heights. - for i=nofmetrics,nofglyphs-1 do - local glyph = glyphs[i] - if vheight ~= 0 and vheight ~= vdefault then - glyph.vheight = vheight - end - -- if topsidebearing ~= 0 then - -- glyph.tsb = topsidebearing - -- end + -- if topsidebearing ~= 0 then + -- glyph.tsb = topsidebearing + -- end + end + -- The next can happen in for instance a monospace font or in a cjk font + -- with fixed heights. + for i=nofmetrics,nofglyphs-1 do + local glyph = glyphs[i] + if vheight ~= 0 and vheight ~= vdefault then + glyph.vheight = vheight end + -- if topsidebearing ~= 0 then + -- glyph.tsb = topsidebearing + -- end end end end readers.vorg = function(f,fontdata,specification) if specification.glyphs then - local datatable = fontdata.tables.vorg - if datatable then - report("todo: %s","vorg") - end + -- reportskippedtable("vorg") end end @@ -1194,9 +1191,8 @@ end -- description is somewhat fuzzy but it is a hybrid with overloads. readers.post = function(f,fontdata,specification) - local datatable = fontdata.tables.post - if datatable then - setposition(f,datatable.offset) + local tableoffset = gotodatatable(f,fontdata,"post",true) + if tableoffset then local version = readfixed(f) fontdata.postscript = { version = version, @@ -1225,7 +1221,7 @@ readers.post = function(f,fontdata,specification) for i=0,nofglyphs-1 do local nameindex = readushort(f) if nameindex >= 258 then - maxnames = maxnames + 1 + maxnames = maxnames + 1 nameindex = nameindex - 257 indices[nameindex] = i else @@ -1238,7 +1234,7 @@ readers.post = function(f,fontdata,specification) report("quit post name fetching at %a of %a: %s",i,maxnames,"no index") break else - local length = readbyte(f) + local length = readbyte(f) if length > 0 then glyphs[mapping].name = readstring(f,length) else @@ -1365,7 +1361,7 @@ formatreaders[4] = function(f,fontdata,offset) elseif offset == 0xFFFF then -- bad encoding elseif offset == 0 then - if trace_cmap then + if trace_cmap_detail then report("format 4.%i segment %2i from %C upto %C at index %H",1,segment,startchar,endchar,(startchar + delta) % 65536) end for unicode=startchar,endchar do @@ -1398,7 +1394,7 @@ formatreaders[4] = function(f,fontdata,offset) end else local shift = (segment-nofsegments+offset/2) - startchar - if trace_cmap then + if trace_cmap_detail then report("format 4.%i segment %2i from %C upto %C at index %H",0,segment,startchar,endchar,(startchar + delta) % 65536) end for unicode=startchar,endchar do @@ -1448,7 +1444,7 @@ formatreaders[6] = function(f,fontdata,offset) local count = readushort(f) local stop = start+count-1 local nofdone = 0 - if trace_cmap then + if trace_cmap_detail then report("format 6 from %C to %C",2,start,stop) end for unicode=start,stop do @@ -1485,7 +1481,7 @@ formatreaders[12] = function(f,fontdata,offset) local first = readulong(f) local last = readulong(f) local index = readulong(f) - if trace_cmap then + if trace_cmap_detail then report("format 12 from %C to %C starts at index %i",first,last,index) end for unicode=first,last do @@ -1529,7 +1525,7 @@ formatreaders[13] = function(f,fontdata,offset) local last = readulong(f) local index = readulong(f) if first < privateoffset then - if trace_cmap then + if trace_cmap_detail then report("format 13 from %C to %C get index %i",first,last,index) end local glyph = glyphs[index] @@ -1635,76 +1631,82 @@ local function checkcmap(f,fontdata,records,platform,encoding,format) local p = platforms[platform] local e = encodings[p] local n = reader(f,fontdata,data) or 0 - report("cmap checked: platform %i (%s), encoding %i (%s), format %i, new unicodes %i",platform,p,encoding,e and e[encoding] or "?",format,n) + if trace_cmap then + report("cmap checked: platform %i (%s), encoding %i (%s), format %i, new unicodes %i",platform,p,encoding,e and e[encoding] or "?",format,n) + end return n end function readers.cmap(f,fontdata,specification) - if specification.glyphs then - local datatable = fontdata.tables.cmap - if datatable then - local tableoffset = datatable.offset - setposition(f,tableoffset) - local version = readushort(f) - local noftables = readushort(f) - local records = { } - local unicodecid = false - local variantcid = false - local variants = { } - local duplicates = fontdata.duplicates or { } - fontdata.duplicates = duplicates - for i=1,noftables do - local platform = readushort(f) - local encoding = readushort(f) - local offset = readulong(f) - local record = records[platform] - if not record then - records[platform] = { - [encoding] = { - offsets = { offset }, - formats = { }, - } + local tableoffset = gotodatatable(f,fontdata,"cmap",specification.glyphs) + if tableoffset then + local version = readushort(f) + local noftables = readushort(f) + local records = { } + local unicodecid = false + local variantcid = false + local variants = { } + local duplicates = fontdata.duplicates or { } + fontdata.duplicates = duplicates + for i=1,noftables do + local platform = readushort(f) + local encoding = readushort(f) + local offset = readulong(f) + local record = records[platform] + if not record then + records[platform] = { + [encoding] = { + offsets = { offset }, + formats = { }, + } + } + else + local subtables = record[encoding] + if not subtables then + record[encoding] = { + offsets = { offset }, + formats = { }, } else - local subtables = record[encoding] - if not subtables then - record[encoding] = { - offsets = { offset }, - formats = { }, - } - else - local offsets = subtables.offsets - offsets[#offsets+1] = offset - end + local offsets = subtables.offsets + offsets[#offsets+1] = offset end end + end + if trace_cmap then report("found cmaps:") - for platform, record in sortedhash(records) do - local p = platforms[platform] - local e = encodings[p] - local sp = supported[platform] - local ps = p or "?" + end + for platform, record in sortedhash(records) do + local p = platforms[platform] + local e = encodings[p] + local sp = supported[platform] + local ps = p or "?" + if trace_cmap then if sp then report(" platform %i: %s",platform,ps) else report(" platform %i: %s (unsupported)",platform,ps) end - for encoding, subtables in sortedhash(record) do - local se = sp and sp[encoding] - local es = e and e[encoding] or "?" + end + for encoding, subtables in sortedhash(record) do + local se = sp and sp[encoding] + local es = e and e[encoding] or "?" + if trace_cmap then if se then report(" encoding %i: %s",encoding,es) else report(" encoding %i: %s (unsupported)",encoding,es) end - local offsets = subtables.offsets - local formats = subtables.formats - for i=1,#offsets do - local offset = tableoffset + offsets[i] - setposition(f,offset) - formats[readushort(f)] = offset - end - record[encoding] = formats + end + local offsets = subtables.offsets + local formats = subtables.formats + for i=1,#offsets do + local offset = tableoffset + offsets[i] + setposition(f,offset) + formats[readushort(f)] = offset + end + record[encoding] = formats + if trace_cmap then local list = sortedkeys(formats) for i=1,#list do if not (se and se[list[i]]) then @@ -1714,27 +1716,27 @@ function readers.cmap(f,fontdata,specification) report(" formats: % t",list) end end - -- - local ok = false - for i=1,#sequence do - local si = sequence[i] - local sp, se, sf = si[1], si[2], si[3] - if checkcmap(f,fontdata,records,sp,se,sf) > 0 then - ok = true - end - end - if not ok then - report("no useable unicode cmap found") + end + -- + local ok = false + for i=1,#sequence do + local si = sequence[i] + local sp, se, sf = si[1], si[2], si[3] + if checkcmap(f,fontdata,records,sp,se,sf) > 0 then + ok = true end - -- - fontdata.cidmaps = { - version = version, - noftables = noftables, - records = records, - } - else - fontdata.cidmaps = { } end + if not ok then + report("no useable unicode cmap found") + end + -- + fontdata.cidmaps = { + version = version, + noftables = noftables, + records = records, + } + else + fontdata.cidmaps = { } end end @@ -1778,42 +1780,39 @@ end -- can also be available. Todo: we need a 'fake' lookup for this (analogue to ff). function readers.kern(f,fontdata,specification) - if specification.kerns then - local datatable = fontdata.tables.kern - if datatable then - setposition(f,datatable.offset) - local version = readushort(f) - local noftables = readushort(f) - for i=1,noftables do - local version = readushort(f) - local length = readushort(f) - local coverage = readushort(f) - -- bit 8-15 of coverage: format 0 or 2 - local format = bit32.rshift(coverage,8) -- is this ok? - if format == 0 then - local nofpairs = readushort(f) - local searchrange = readushort(f) - local entryselector = readushort(f) - local rangeshift = readushort(f) - local kerns = { } - local glyphs = fontdata.glyphs - for i=1,nofpairs do - local left = readushort(f) - local right = readushort(f) - local kern = readfword(f) - local glyph = glyphs[left] - local kerns = glyph.kerns - if kerns then - kerns[right] = kern - else - glyph.kerns = { [right] = kern } - end + local tableoffset = gotodatatable(f,fontdata,"kern",specification.kerns) + if tableoffset then + local version = readushort(f) + local noftables = readushort(f) + for i=1,noftables do + local version = readushort(f) + local length = readushort(f) + local coverage = readushort(f) + -- bit 8-15 of coverage: format 0 or 2 + local format = bit32.rshift(coverage,8) -- is this ok? + if format == 0 then + local nofpairs = readushort(f) + local searchrange = readushort(f) + local entryselector = readushort(f) + local rangeshift = readushort(f) + local kerns = { } + local glyphs = fontdata.glyphs + for i=1,nofpairs do + local left = readushort(f) + local right = readushort(f) + local kern = readfword(f) + local glyph = glyphs[left] + local kerns = glyph.kerns + if kerns then + kerns[right] = kern + else + glyph.kerns = { [right] = kern } end - elseif format == 2 then - report("todo: kern classes") - else - report("todo: kerns") end + elseif format == 2 then + report("todo: kern classes") + else + report("todo: kerns") end end end @@ -1847,7 +1846,7 @@ end -- some properties in order to read following tables. When details is true we also -- initialize the glyphs data. -local function getinfo(maindata,sub,platformnames,rawfamilynames,metricstoo) +local function getinfo(maindata,sub,platformnames,rawfamilynames,metricstoo,instancenames) local fontdata = sub and maindata.subfonts and maindata.subfonts[sub] or maindata local names = fontdata.names local info = nil @@ -1872,6 +1871,25 @@ local function getinfo(maindata,sub,platformnames,rawfamilynames,metricstoo) if not familyname then familyname = family end if not subfamilyname then subfamilyname = subfamily end end + if platformnames then + platformnames = fontdata.platformnames + end + if instancenames then + local variabledata = fontdata.variabledata + if variabledata then + local instances = variabledata and variabledata.instances + if instances then + instancenames = { } + for i=1,#instances do + instancenames[i] = lower(stripstring(instances[i].subfamily)) + end + else + instancenames = nil + end + else + instancenames = nil + end + end info = { -- we inherit some inconsistencies/choices from ff subfontindex = fontdata.subfontindex or sub or 0, -- filename = filename, @@ -1902,7 +1920,8 @@ local function getinfo(maindata,sub,platformnames,rawfamilynames,metricstoo) capheight = metrics.capheight, -- not always present and probably crap ascender = metrics.typoascender, descender = metrics.typodescender, - platformnames = platformnames and fontdata.platformnames or nil, + platformnames = platformnames or nil, + instancenames = instancenames or nil, } if metricstoo then local keys = { @@ -1960,6 +1979,7 @@ local function loadtables(f,specification,offset) entryselector = readushort(f), -- not needed rangeshift = readushort(f), -- not needed tables = tables, + foundtables = false, } for i=1,fontdata.noftables do local tag = lower(stripstring(readstring(f,4))) @@ -1975,7 +1995,8 @@ local function loadtables(f,specification,offset) length = length, } end - if tables.cff then + fontdata.foundtables = sortedkeys(tables) + if tables.cff or tables.cff2 then fontdata.format = "opentype" else fontdata.format = "truetype" @@ -1996,23 +2017,35 @@ local function prepareglyps(fontdata) fontdata.mapping = { } end -local function readtable(tag,f,fontdata,specification) +local function readtable(tag,f,fontdata,specification,...) local reader = readers[tag] if reader then -- local t = os.clock() - reader(f,fontdata,specification) + reader(f,fontdata,specification,...) -- report("reading table %a took %0.4f seconds",tag,os.clock()-t) end end +local variablefonts_supported = context and true or false + local function readdata(f,offset,specification) + local fontdata = loadtables(f,specification,offset) + if specification.glyphs then prepareglyps(fontdata) end - -- + + if not variablefonts_supported then + specification.instance = nil + specification.variable = nil + specification.factors = nil + end + + fontdata.temporary = { } + readtable("name",f,fontdata,specification) - -- + local askedname = specification.askedname if askedname then local fullname = getname(fontdata,"fullname") or "" @@ -2022,7 +2055,35 @@ local function readdata(f,offset,specification) return -- keep searching end end - -- + + readtable("stat",f,fontdata,specification) + readtable("avar",f,fontdata,specification) + readtable("fvar",f,fontdata,specification) + + if variablefonts_supported then + + if not specification.factors then + local instance = specification.instance + if type(instance) == "string" then + local factors = helpers.getfactors(fontdata,instance) + specification.factors = factors + fontdata.factors = factors + fontdata.instance = instance + report("user instance: %s, factors: % t",instance,factors) + end + end + if not fontdata.factors then + if fontdata.variabledata then + local factors = helpers.getfactors(fontdata,true) + specification.factors = factors + fontdata.factors = factors + fontdata.instance = instance + report("font instance: %s, factors: % t",instance,factors) + end + end + + end + readtable("os/2",f,fontdata,specification) readtable("head",f,fontdata,specification) readtable("maxp",f,fontdata,specification) @@ -2032,32 +2093,35 @@ local function readdata(f,offset,specification) readtable("vmtx",f,fontdata,specification) readtable("vorg",f,fontdata,specification) readtable("post",f,fontdata,specification) + + readtable("mvar",f,fontdata,specification) + readtable("hvar",f,fontdata,specification) + readtable("vvar",f,fontdata,specification) + + readtable("gdef",f,fontdata,specification) + readtable("cff" ,f,fontdata,specification) + readtable("cff2",f,fontdata,specification) + readtable("cmap",f,fontdata,specification) - readtable("loca",f,fontdata,specification) - readtable("glyf",f,fontdata,specification) + readtable("loca",f,fontdata,specification) -- maybe load it in glyf + readtable("glyf",f,fontdata,specification) -- loads gvar + readtable("colr",f,fontdata,specification) readtable("cpal",f,fontdata,specification) readtable("svg" ,f,fontdata,specification) + readtable("kern",f,fontdata,specification) - readtable("gdef",f,fontdata,specification) readtable("gsub",f,fontdata,specification) readtable("gpos",f,fontdata,specification) + readtable("math",f,fontdata,specification) - -- - -- there are no proper fonts yet: - -- - readtable("fvar",f,fontdata,specification) -- probably - readtable("hvar",f,fontdata,specification) - readtable("vvar",f,fontdata,specification) - readtable("mvar",f,fontdata,specification) -- probably - readtable("vorg",f,fontdata,specification) - -- + fontdata.locations = nil fontdata.tables = nil fontdata.cidmaps = nil fontdata.dictionaries = nil - -- fontdata.cff = nil + -- fontdata.cff = nil return fontdata end @@ -2132,7 +2196,7 @@ local function loadfontdata(specification) end end -local function loadfont(specification,n) +local function loadfont(specification,n,instance) if type(specification) == "string" then specification = { filename = specification, @@ -2147,6 +2211,7 @@ local function loadfont(specification,n) -- true or number: subfont = n or true, tounicode = false, + instance = instance } end -- if shapes only then @@ -2162,6 +2227,10 @@ local function loadfont(specification,n) if specification.platformnames then specification.platformnames = true -- not really used any more end + if specification.instance or instance then + specification.variable = true + specification.instance = specification.instance or instance + end local function message(str) report("fatal error in file %a: %s\n%s",specification.filename,str,debug.traceback()) end @@ -2173,11 +2242,14 @@ end -- we need even less, but we can have a 'detail' variant -function readers.loadshapes(filename,n) +function readers.loadshapes(filename,n,instance,streams) local fontdata = loadfont { filename = filename, shapes = true, + streams = streams, + variable = true, subfont = n, + instance = instance, } if fontdata then -- easier on luajit but still we can hit the 64 K stack constants issue @@ -2202,7 +2274,7 @@ function readers.loadshapes(filename,n) } end -function readers.loadfont(filename,n) +function readers.loadfont(filename,n,instance) local fontdata = loadfont { filename = filename, glyphs = true, @@ -2212,9 +2284,9 @@ function readers.loadfont(filename,n) -- kerns = true, -- globalkerns = true, -- only for testing, e.g. cambria has different gpos and kern subfont = n, + instance = instance, } if fontdata then - -- return { tableversion = tableversion, creator = "context mkiv", @@ -2224,11 +2296,13 @@ function readers.loadfont(filename,n) descriptions = fontdata.descriptions, format = fontdata.format, goodies = { }, - metadata = getinfo(fontdata,n,false,false,true), -- no platformnames here ! + metadata = getinfo(fontdata,n,false,false,true,true), -- no platformnames here ! properties = { hasitalics = fontdata.hasitalics or false, maxcolorclass = fontdata.maxcolorclass, hascolor = fontdata.hascolor or false, + instance = fontdata.instance, + factors = fontdata.factors, }, resources = { -- filename = fontdata.filename, @@ -2247,7 +2321,8 @@ function readers.loadfont(filename,n) mathconstants = fontdata.mathconstants, colorpalettes = fontdata.colorpalettes, svgshapes = fontdata.svgshapes, - variable = fontdata.variable, + variabledata = fontdata.variabledata, + foundtables = fontdata.foundtables, }, } end @@ -2259,6 +2334,7 @@ function readers.getinfo(filename,specification) -- string, nil|number|table local subfont = nil local platformnames = false local rawfamilynames = false + local instancenames = true if type(specification) == "table" then subfont = tonumber(specification.subfont) platformnames = specification.platformnames @@ -2270,20 +2346,21 @@ function readers.getinfo(filename,specification) -- string, nil|number|table filename = filename, details = true, platformnames = platformnames, + instancenames = true, -- rawfamilynames = rawfamilynames, } if fontdata then local subfonts = fontdata.subfonts if not subfonts then - return getinfo(fontdata,nil,platformnames,rawfamilynames) + return getinfo(fontdata,nil,platformnames,rawfamilynames,false,instancenames) elseif not subfont then local info = { } for i=1,#subfonts do - info[i] = getinfo(fontdata,i,platformnames,rawfamilynames) + info[i] = getinfo(fontdata,i,platformnames,rawfamilynames,false,instancenames) end return info elseif subfont >= 1 and subfont <= #subfonts then - return getinfo(fontdata,subfont,platformnames,rawfamilynames) + return getinfo(fontdata,subfont,platformnames,rawfamilynames,false,instancenames) else return { filename = filename, diff --git a/tex/context/base/mkiv/font-ott.lua b/tex/context/base/mkiv/font-ott.lua index 48d05d492..cba3758dc 100644 --- a/tex/context/base/mkiv/font-ott.lua +++ b/tex/context/base/mkiv/font-ott.lua @@ -1082,6 +1082,8 @@ table.setmetatableindex(usedfeatures, function(t,k) if k then local v = { } t[k] storage.register("fonts/otf/usedfeatures", usedfeatures, "fonts.handlers.otf.statistics.usedfeatures" ) +local normalizedaxis = otf.readers.helpers.normalizedaxis or function(s) return s end + function otffeatures.normalize(features) if features then local h = { } @@ -1093,6 +1095,11 @@ function otffeatures.normalize(features) elseif k == "script" then local v = gsub(lower(value),"[^a-z0-9]","") h.script = rawget(verbosescripts,v) or (scripts[v] and v) or "dflt" -- auto adds + elseif k == "axis" then + h[k] = normalizedaxis(value) +if not callbacks.supported.glyph_stream_provider then + h.variableshapes = true -- for the moment +end else local uk = usedfeatures[key] local uv = uk[value] diff --git a/tex/context/base/mkiv/font-oup.lua b/tex/context/base/mkiv/font-oup.lua index cfa90c794..bbc8436b2 100644 --- a/tex/context/base/mkiv/font-oup.lua +++ b/tex/context/base/mkiv/font-oup.lua @@ -1202,6 +1202,7 @@ function readers.pack(data) local sublookups = resources.sublookups local features = resources.features local palettes = resources.colorpalettes + local variable = resources.variabledata local chardata = characters and characters.data local descriptions = data.descriptions or data.glyphs @@ -1377,6 +1378,53 @@ function readers.pack(data) end + if variable then + + -- todo: segments + + local instances = variable.instances + if instances then + for i=1,#instances do + local v = instances[i].values + for j=1,#v do + v[j] = pack_normal(v[j]) + end + end + end + + local function packdeltas(main) + if main then + local deltas = main.deltas + if deltas then + for i=1,#deltas do + local di = deltas[i] + local d = di.deltas + local r = di.regions + for j=1,#d do + d[j] = pack_indexed(d[j]) + end + di.regions = pack_indexed(di.regions) + end + end + local regions = main.regions + if regions then + for i=1,#regions do + local r = regions[i] + for j=1,#r do + r[j] = pack_normal(r[j]) + end + end + end + end + end + + packdeltas(variable.global) + packdeltas(variable.horizontal) + packdeltas(variable.vertical) + packdeltas(variable.metrics) + + end + if not success(1,pass) then return end @@ -1453,10 +1501,23 @@ function readers.pack(data) if sublookups then packthem(sublookups) end - -- features - if not success(2,pass) then - -- return + if variable then + local function unpackdeltas(main) + if main then + local regions = main.regions + if regions then + main.regions = pack_normal(regions) + end + end + end + unpackdeltas(variable.global) + unpackdeltas(variable.horizontal) + unpackdeltas(variable.vertical) + unpackdeltas(variable.metrics) end + -- if not success(2,pass) then + -- -- return + -- end end for pass=1,2 do @@ -1525,6 +1586,7 @@ function readers.unpack(data) local sublookups = resources.sublookups local features = resources.features local palettes = resources.colorpalettes + local variable = resources.variabledata local unpacked = { } setmetatable(unpacked,unpacked_mt) for unicode, description in next, descriptions do @@ -1810,6 +1872,70 @@ function readers.unpack(data) end end + if variable then + + -- todo: segments + + local instances = variable.instances + if instances then + for i=1,#instances do + local v = instances[i].values + for j=1,#v do + local tv = tables[v[j]] + if tv then + v[j] = tv + end + end + end + end + + local function unpackdeltas(main) + if main then + local deltas = main.deltas + if deltas then + for i=1,#deltas do + local di = deltas[i] + local d = di.deltas + local r = di.regions + for j=1,#d do + local tv = tables[d[j]] + if tv then + d[j] = tv + end + end + local tv = di.regions + if tv then + di.regions = tv + end + end + end + local regions = main.regions + if regions then + local tv = tables[regions] + if tv then + main.regions = tv + regions = tv + end + for i=1,#regions do + local r = regions[i] + for j=1,#r do + local tv = tables[r[j]] + if tv then + r[j] = tv + end + end + end + end + end + end + + unpackdeltas(variable.global) + unpackdeltas(variable.horizontal) + unpackdeltas(variable.vertical) + unpackdeltas(variable.metrics) + + end + data.tables = nil end end diff --git a/tex/context/base/mkiv/font-shp.lua b/tex/context/base/mkiv/font-shp.lua index 92ff70127..9447af94c 100644 --- a/tex/context/base/mkiv/font-shp.lua +++ b/tex/context/base/mkiv/font-shp.lua @@ -6,17 +6,19 @@ if not modules then modules = { } end modules ['font-shp'] = { license = "see context related readme files" } +local tonumber = tonumber local concat = table.concat -local load, tonumber = load, tonumber +local formatters = string.formatters -local otf = fonts.handlers.otf -local afm = fonts.handlers.afm +local otf = fonts.handlers.otf +local afm = fonts.handlers.afm -local hashes = fonts.hashes -local identifiers = hashes.identifiers +local hashes = fonts.hashes +local identifiers = hashes.identifiers -local version = 0.006 -local cache = containers.define("fonts", "shapes", version, true) +local version = 0.007 +local shapescache = containers.define("fonts", "shapes", version, true) +local streamscache = containers.define("fonts", "streams", version, true) -- shapes (can be come a separate file at some point) @@ -128,7 +130,19 @@ end -- todo: loaders per format -local function load(filename,sub) +local readers = otf.readers +local cleanname = readers.helpers.cleanname + +local function makehash(filename,sub,instance) + local name = cleanname(file.basename(filename)) + if instance then + return formatters["%s-%s-%s"](name,sub or 0,cleanname(instance)) + else + return formatters["%s-%s"] (name,sub or 0) + end +end + +local function loadoutlines(cache,filename,sub,instance) local base = file.basename(filename) local name = file.removesuffix(base) local kind = file.suffix(filename) @@ -140,13 +154,10 @@ local function load(filename,sub) -- fonts.formats if size > 0 and (kind == "otf" or kind == "ttf" or kind == "tcc") then - local hash = containers.cleanname(base) -- including suffix - if sub then - hash = hash .. "-" .. sub - end + local hash = makehash(filename,sub,instance) data = containers.read(cache,hash) if not data or data.time ~= time or data.size ~= size then - data = otf.readers.loadshapes(filename,sub) + data = readers.loadshapes(filename,sub,instance) if data then data.size = size data.format = data.format or (kind == "otf" and "opentype") or "truetype" @@ -185,9 +196,196 @@ local function load(filename,sub) return data end +local function loadstreams(cache,filename,sub,instance) + local base = file.basename(filename) + local name = file.removesuffix(base) + local kind = file.suffix(filename) + local attr = lfs.attributes(filename) + local size = attr and attr.size or 0 + local time = attr and attr.modification or 0 + local sub = tonumber(sub) + + -- fonts.formats + + if size > 0 and (kind == "otf" or kind == "ttf" or kind == "tcc") then + local hash = makehash(filename,sub,instance) + data = containers.read(cache,hash) + if not data or data.time ~= time or data.size ~= size then + data = readers.loadshapes(filename,sub,instance,true) + if data then + local glyphs = data.glyphs + local streams = { } + if glyphs then + for i=0,#glyphs do + streams[i] = glyphs[i].stream or "" + end + end + data.streams = streams + data.glyphs = nil + data.size = size + data.format = data.format or (kind == "otf" and "opentype") or "truetype" + data.time = time + containers.write(cache,hash,data) + data = containers.read(cache,hash) -- frees old mem + end + end + else + data = { + filename = filename, + size = 0, + time = time, + format = "unknown", + glyphs = { } + } + end + return data +end + +local loadedshapes = { } +local loadedstreams = { } + +local function loadoutlinedata(fontdata,streams) + local properties = fontdata.properties + local filename = properties.filename + local subindex = fontdata.subindex + local instance = properties.instance + local hash = makehash(filename,subindex,instance) + local loaded = loadedshapes[hash] + if not loaded then + loaded = loadoutlines(shapescache,filename,subindex,instance) + loadedshapes[hash] = loaded + end + return loaded +end + hashes.shapes = table.setmetatableindex(function(t,k) - local d = identifiers[k] - local v = load(d.properties.filename,d.subindex) - t[k] = v - return v + local f = identifiers[k] + if f then + return loadoutlinedata(f) + end end) + +local function loadstreamdata(fontdata,streams) + local properties = fontdata.properties + local filename = properties.filename + local subindex = fontdata.subindex + local instance = properties.instance + local hash = makehash(filename,subindex,instance) + local loaded = loadedstreams[hash] + if not loaded then + loaded = loadstreams(streamscache,filename,subindex,instance) + loadedstreams[hash] = loaded + end + return loaded +end + +hashes.streams = table.setmetatableindex(function(t,k) + local f = identifiers[k] + if f then + return loadstreamdata(f,true) + end +end) + +otf.loadoutlinedata = loadoutlinedata -- not public +otf.loadstreamdata = loadstreamdata -- not public +otf.loadshapes = loadshapes + +-- experimental code, for me only ... unsupported + +local f_c = string.formatters["%F %F %F %F %F %F c"] +local f_l = string.formatters["%F %F l"] +local f_m = string.formatters["%F %F m"] + +local function segmentstopdf(segments,factor,bt,et) + local t = { } + local n = #segments + for i=1,n do + local s = segments[i] + local m = #s + local w = s[m] + if w == "c" then + t[i] = f_c(s[1]*factor,s[2]*factor,s[3]*factor,s[4]*factor,s[5]*factor,s[6]*factor) + elseif w == "l" then + t[i] = f_l(s[1]*factor,s[2]*factor) + elseif w == "m" then + t[i] = f_m(s[1]*factor,s[2]*factor) + else + t[i] = "" + end + end + t[n+1] = "h f" -- B* + if bt and et then + t[0] = bt + t[n+2] = et + return concat(t,"\n",0,n+2) + else + return concat(t,"\n") + end +end + +local function addvariableshapes(tfmdata,key,value) + if value then + local shapes = otf.loadoutlinedata(tfmdata) + if not shapes then + return + end + local glyphs = shapes.glyphs + if not glyphs then + return + end + local characters = tfmdata.characters + local parameters = tfmdata.parameters + local hfactor = parameters.hfactor * (7200/7227) + local factor = hfactor / 65536 + local getactualtext = otf.getactualtext + for unicode, char in next, characters do + if not char.commands then + local shape = glyphs[char.index] + if shape then + local segments = shape.segments + if segments then + -- we need inline in order to support color + local bt, et = getactualtext(char.tounicode or char.unicode or unicode) + char.commands = { + { "special", "pdf:" .. segmentstopdf(segments,factor,bt,et) } + } + end + end + end + end + end +end + +otf.features.register { + name = "variableshapes", -- enforced for now + description = "variable shapes", + manipulators = { + base = addvariableshapes, + node = addvariableshapes, + } +} + +-- In the end it is easier to just provide the new charstring (cff) and points (ttdf). First +-- of all we already have the right information so there is no need to patch the already complex +-- backend code (we only need to make sure the cff is valid). Also, I prototyped support for +-- these fonts using (converted to) normal postscript shapes, a functionality that was already +-- present for a while for metafun. This solution even permits us to come up with usage of such +-- fonts in unexpected ways. It also opens the road to shapes generated with metafun includes +-- as real cff (or ttf) shapes instead of virtual in-line shapes. +-- +-- This is probably a prelude to writing a complete backend font inclusion plugin in lua. After +-- all I already have most info. For this we just need to pass a list of used glyphs (or analyze +-- them ourselves). + +local streams = fonts.hashes.streams + +callback.register("glyph_stream_provider",function(id,index,mode) + if id > 0 then + local streams = streams[id].streams + -- print(id,index,streams[index]) + if streams then + return streams[index] or "" + end + end + return "" + end) diff --git a/tex/context/base/mkiv/font-syn.lua b/tex/context/base/mkiv/font-syn.lua index a383370f5..558d07fe7 100644 --- a/tex/context/base/mkiv/font-syn.lua +++ b/tex/context/base/mkiv/font-syn.lua @@ -16,7 +16,7 @@ if not modules then modules = { } end modules ['font-syn'] = { local next, tonumber, type, tostring = next, tonumber, type, tostring local sub, gsub, match, find, lower, upper = string.sub, string.gsub, string.match, string.find, string.lower, string.upper -local concat, sort = table.concat, table.sort +local concat, sort, fastcopy = table.concat, table.sort, table.fastcopy local serialize, sortedhash = table.serialize, table.sortedhash local lpegmatch = lpeg.match local unpack = unpack or table.unpack @@ -623,6 +623,8 @@ local function check_name(data,result,filename,modification,suffix,subfont) local pfmwidth = result.pfmwidth or 0 local pfmweight = result.pfmweight or 0 -- + local instancenames = result.instancenames + -- specifications[#specifications+1] = { filename = filename, -- unresolved cleanfilename = cleanfilename, @@ -650,6 +652,7 @@ local function check_name(data,result,filename,modification,suffix,subfont) maxsize = maxsize ~= 0 and maxsize or nil, designsize = designsize ~= 0 and designsize or nil, modification = modification ~= 0 and modification or nil, + instancenames = instancenames or nil, } end @@ -806,6 +809,7 @@ local function collecthashes() local format = specification.format local fullname = specification.fullname local fontname = specification.fontname + -- local rawname = specification.rawname -- local compatiblename = specification.compatiblename -- local cfffullname = specification.cfffullname local familyname = specification.familyname or specification.family @@ -814,6 +818,7 @@ local function collecthashes() local weight = specification.weight local mapping = mappings[format] local fallback = fallbacks[format] + local instancenames = specification.instancenames if fullname and not mapping[fullname] then mapping[fullname] = index nofmappings = nofmappings + 1 @@ -822,6 +827,14 @@ local function collecthashes() mapping[fontname] = index nofmappings = nofmappings + 1 end + if instancenames then + for i=1,#instancenames do + local instance = fullname .. instancenames[i] + mapping[instance] = index + nofmappings = nofmappings + 1 + + end + end -- if compatiblename and not mapping[compatiblename] then -- mapping[compatiblename] = index -- nofmappings = nofmappings + 1 @@ -1365,6 +1378,23 @@ end -- we could cache a lookup .. maybe some day ... (only when auto loaded!) +local function checkinstance(found,askedname) + local instancenames = found.instancenames + if instancenames then + local fullname = found.fullname + for i=1,#instancenames do + local instancename = instancenames[i] + if fullname .. instancename == askedname then + local f = fastcopy(found) + f.instances = nil + f.instance = instancename + return f + end + end + end + return found +end + local function foundname(name,sub) -- sub is not used currently local data = names.data local mappings = data.mappings @@ -1382,7 +1412,7 @@ local function foundname(name,sub) -- sub is not used currently if trace_names then report_names("resolved via direct name match: %a",name) end - return found + return checkinstance(found,name) end end for i=1,#list do @@ -1392,7 +1422,7 @@ local function foundname(name,sub) -- sub is not used currently if trace_names then report_names("resolved via fuzzy name match: %a onto %a",name,fname) end - return found + return checkinstance(found,name) end end for i=1,#list do @@ -1402,7 +1432,7 @@ local function foundname(name,sub) -- sub is not used currently if trace_names then report_names("resolved via direct fallback match: %a",name) end - return found + return checkinstance(found,name) end end for i=1,#list do @@ -1412,7 +1442,7 @@ local function foundname(name,sub) -- sub is not used currently if trace_names then report_names("resolved via fuzzy fallback match: %a onto %a",name,fname) end - return found + return checkinstance(found,name) end end if trace_names then @@ -1435,7 +1465,7 @@ end function names.resolve(askedname,sub) local found = names.resolvedspecification(askedname,sub) if found then - return found.filename, found.subfont and found.rawname, found.subfont + return found.filename, found.subfont and found.rawname, found.subfont, found.instance end end diff --git a/tex/context/base/mkiv/font-ttf.lua b/tex/context/base/mkiv/font-ttf.lua index 6df339214..d222de4ba 100644 --- a/tex/context/base/mkiv/font-ttf.lua +++ b/tex/context/base/mkiv/font-ttf.lua @@ -6,42 +6,81 @@ if not modules then modules = { } end modules ['font-ttf'] = { 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 bittest = bit32.btest -local sqrt = math.sqrt +local bittest, band, rshift = bit32.btest, bit32.band, bit32.rshift +local sqrt, round = math.sqrt, math.round +local char = string.char +local concat = table.concat -local report = logs.reporter("otf reader","ttf") +local report = logs.reporter("otf reader","ttf") -local readers = fonts.handlers.otf.readers -local streamreader = readers.streamreader +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 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 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 = merge(subindex,subshape,subcomponents) + subcontours, subpoints = merge(subindex,subshape,subcomponents) end end - if subcontours then + if subpoints then local matrix = component.matrix local xscale = matrix[1] local xrotate = matrix[2] @@ -49,36 +88,39 @@ local function mergecomposites(glyphs,shapes) local yscale = matrix[4] local xoffset = matrix[5] local yoffset = matrix[6] + for i=1,#subpoints 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 for i=1,#subcontours do - local points = subcontours[i] - local result = { } - for i=1,#points do - local p = points[i] - local x = p[1] - local y = p[2] - result[i] = { - xscale * x + xrotate * y + xoffset, - yscale * y + yrotate * x + yoffset, - p[3] - } - end nofcontours = nofcontours + 1 - contours[nofcontours] = result + contours[nofcontours] = offset + subcontours[i] end + offset = offset + #subpoints 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 + return contours, points end for index=1,#glyphs do - local shape = shapes[index] - local components = shape.components - if components then - merge(index,shape,components) + local shape = shapes[index] + if shape then + local components = shape.components + if components then + merge(index,shape,components) + end end end @@ -92,9 +134,6 @@ end -- begin of converter --- make paths: the ff code is quite complex but it looks like we need to deal --- with all kind of on curve border cases - 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), @@ -105,132 +144,530 @@ end -- We could omit the operator which saves some 10%: -- --- #2=lineto #4=quadratic #6=cubic #3=moveto (with "m") +-- #2=lineto #4=quadratic #6=cubic #3=moveto (with "m") -- --- For the moment we keep the original outlines but that default might change --- in the future. In any case, a backend should support both. +-- 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). -- --- 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. +-- 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 contours2outlines(glyphs,shapes) +local function applyaxis(glyph,shape,points,deltas) + if points then + local nofpoints = #points +-- local h = nofpoints + 2 -- weird, the example font seems to have left first +-- ----- l = nofpoints + 2 +-- ----- v = nofpoints + 3 +-- ----- t = nofpoints + 4 +-- local width = glyph.width + 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[d] + if x and x ~= 0 then + p[1] = p[1] + factor * x + end + end + if yvalues then + local y = yvalues[d] + if y and y ~= 0 then + p[2] = p[2] + factor * y + end + end + elseif width then +-- weird one-off and bad values +-- +-- if d == h then +-- print("index",d) +-- inspect(dpoints) +-- inspect(xvalues) +-- local x = xvalues[i] +-- if x then +-- print("phantom h advance",width,factor*x) +-- width = width + 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 +-- todo : phantom point hadvance + end + end +-- glyph.width = width + end +end + +-- round or not ? + +local function contours2outlines_normal(glyphs,shapes) -- maybe accept the bbox overhead local quadratic = true -- local quadratic = false for index=1,#glyphs do - local glyph = glyphs[index] - local shape = shapes[index] - local contours = shape.contours - if contours then - local nofcontours = #contours - local segments = { } - local nofsegments = 0 - glyph.segments = segments - if nofcontours > 0 then - for i=1,nofcontours do - local contour = contours[i] - local nofcontour = #contour - if nofcontour > 0 then - local first_pt = contour[1] - local first_on = first_pt[3] - -- todo no new tables but reuse lineto and quadratic - if nofcontour == 1 then - -- this can influence the boundingbox - 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 = contour[nofcontour] - 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 + 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, 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 + 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 - first_pt = { (first_pt[1]+last_pt[1])/2, (first_pt[2]+last_pt[2])/2, false } + 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 - control_pt = first_pt - end - nofsegments = nofsegments + 1 - segments[nofsegments] = { first_pt[1], first_pt[2], "m" } -- "moveto" - local previous_pt = first_pt - for i=start,nofcontour do - local current_pt = contour[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 = first_pt[1], first_pt[2] + if not done then + xmin, ymin, xmax, ymax = x, y, x, y + done = true + end + nofsegments = nofsegments + 1 + segments[nofsegments] = { x, y, "m" } -- "moveto" + if not quadratic then + px, py = x, 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, y1 = control_pt[1], control_pt[2] + local x2, y2 = current_pt[1], current_pt[2] nofsegments = nofsegments + 1 - segments[nofsegments] = { current_pt[1], current_pt[2], "l" } -- "lineto" + if quadratic then + segments[nofsegments] = { x1, y1, x2, y2, "q" } -- "quadraticto" + else + x1, x2, x2, y2, px, py = curveto(x1, x2, px, py, x2, y2) + segments[nofsegments] = { x1, y1, x2, y2, px, py, "c" } -- "curveto" + end + control_pt = false else + local x2, y2 = (previous_pt[1]+current_pt[1])/2, (previous_pt[2]+current_pt[2])/2 + local x1, y1 = control_pt[1], control_pt[2] + nofsegments = nofsegments + 1 + if quadratic then + segments[nofsegments] = { x1, y1, x2, y2, "q" } -- "quadraticto" + else + x1, x2, x2, y2, px, py = curveto(x1, x2, px, py, x2, y2) + segments[nofsegments] = { x1, y1, x2, y2, px, py, "c" } -- "curveto" + end control_pt = current_pt end - elseif current_on then - local ps = segments[nofsegments] + previous_pt = current_pt + end + if first_pt == last_pt then + -- we're already done, probably a simple curve + else nofsegments = nofsegments + 1 - if quadratic then - segments[nofsegments] = { control_pt[1], control_pt[2], current_pt[1], current_pt[2], "q" } -- "quadraticto" + if not control_pt then + segments[nofsegments] = { first_pt[1], first_pt[2], "l" } -- "lineto" + elseif quadratic then + local x1, y1 = control_pt[1], control_pt[2] + -- local x2, y2 = first_pt[1], first_pt[2] + segments[nofsegments] = { x1, y1, first_pt[1], first_pt[2], "q" } -- "quadraticto" + else + local x1, y1 = control_pt[1], control_pt[2] + local x2, y2 = first_pt[1], first_pt[2] + x1, x2, x2, y2, px, py = curveto(x1, x2, px, py, x2, y2) + segments[nofsegments] = { x1, y1, y2, 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) + local quadratic = true + -- local quadratic = false + for index=1,#glyphs 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 - local p = segments[nofsegments-1] local n = #p - segments[nofsegments] = curveto(control_pt[1],control_pt[2],p[n-2],p[n-1],current_pt[1],current_pt[2]) + first_pt = { (first_pt[1]+last_pt[1])/2, (first_pt[2]+last_pt[2])/2, false } end - control_pt = false + control_pt = first_pt + end + local x, y = first_pt[1], 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 - local halfway_x = (previous_pt[1]+current_pt[1])/2 - local halfway_y = (previous_pt[2]+current_pt[2])/2 - if quadratic then - segments[nofsegments] = { control_pt[1], control_pt[2], halfway_x, halfway_y, "q" } -- "quadraticto" + segments[nofsegments] = { x, y, "m" } -- "moveto" + end + if not quadratic then + px, py = x, 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] + 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, py = x, y + end + else + control_pt = current_pt + end + elseif current_on then + local x1, y1 = control_pt[1], control_pt[2] + local x2, y2 = current_pt[1], 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, x2, x2, y2, px, py = curveto(x1, x2, 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 p = segments[nofsegments-1] local n = #p - segments[nofsegments] = curveto(control_pt[1],control_pt[2],p[n-2],p[n-1],halfway_x,halfway_y) + local x2, y2 = (previous_pt[1]+current_pt[1])/2, (previous_pt[2]+current_pt[2])/2 + local x1, y1 = control_pt[1], 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, x2, x2, y2, px, py = curveto(x1, x2, 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 - control_pt = current_pt + previous_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 - if not control_pt then - segments[nofsegments] = { first_pt[1], first_pt[2], "l" } -- "lineto" + 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 elseif quadratic then - segments[nofsegments] = { control_pt[1], control_pt[2], first_pt[1], first_pt[2], "q" } -- "quadraticto" + local x1, y1 = control_pt[1], control_pt[2] + -- local x2, y2 = first_pt[1], 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 keepcurve then + nofsegments = nofsegments + 1 + segments[nofsegments] = { x1, y1, first_pt[1], first_pt[2], "q" } -- "quadraticto" + end else - local p = last_pt local n = #p - segments[nofsegments] = curveto(control_pt[1],control_pt[2],p[n-2],p[n-1],first_pt[1],first_pt[2]) + local x1, y1 = control_pt[1], control_pt[2] + local x2, y2 = first_pt[1], first_pt[2] + x1, x2, x2, y2, px, py = curveto(x1, x2, 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, y2, y2, px, py, "c" } -- "curveto" + end + -- px, py = x2, y2 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 function toushort(n) + return char(band(rshift(n,8),0xFF),band(n,0xFF)) +end + +local function toshort(n) + if n < 0 then + n = n + 0x10000 + end + return char(band(rshift(n,8),0xFF),band(n,0xFF)) +end + +-- todo: we can reuse result, xpoints and ypoints + +local function repackpoints(glyphs,shapes) + local noboundingbox = { 0, 0, 0, 0 } + local result = { } -- reused + for index=1,#glyphs do + local shape = shapes[index] + if shape then + local r = 0 + local glyph = glyphs[index] + if false then -- shape.type == "composite" + -- we merged them + else + local contours = shape.contours + local nofcontours = #contours + 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) + if dx < -255 or dx > 255 then + x = x + 1 xpoints[x] = toshort(dx) + elseif dx < 0 then + fl = fl + 0x02 + x = x + 1 xpoints[x] = char(-dx) + elseif dx > 0 then + fl = fl + 0x12 + x = x + 1 xpoints[x] = char(dx) + else + fl = fl + 0x02 + x = x + 1 xpoints[x] = c_zero + end + end + if py == currenty then + fl = fl + 0x20 + else + local dy = round(py - currenty) + if dy < -255 or dy > 255 then + y = y + 1 ypoints[y] = toshort(dy) + elseif dy < 0 then + fl = fl + 0x04 + y = y + 1 ypoints[y] = char(-dy) + elseif dy > 0 then + fl = fl + 0x24 + y = y + 1 ypoints[y] = char(dy) + else + fl = fl + 0x04 + y = y + 1 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) + 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) + 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) end end + glyph.stream = concat(result,"",1,r) + else + -- fatal end end end -- end of converter -local function readglyph(f,nofcontours) +local function readglyph(f,nofcontours) -- read deltas here, saves space local points = { } - local endpoints = { } + local contours = { } local instructions = { } local flags = { } for i=1,nofcontours do - endpoints[i] = readshort(f) + 1 + contours[i] = readshort(f) + 1 end - local nofpoints = endpoints[nofcontours] + local nofpoints = contours[nofcontours] local nofinstructions = readushort(f) --- f:seek("set",f:seek()+nofinstructions) 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) @@ -238,7 +675,7 @@ local function readglyph(f,nofcontours) while i <= nofpoints do local flag = readbyte(f) flags[i] = flag - if bittest(flag,0x0008) then + if bittest(flag,0x08) then for j=1,readbyte(f) do i = i + 1 flags[i] = flag @@ -251,8 +688,8 @@ local function readglyph(f,nofcontours) local x = 0 for i=1,nofpoints do local flag = flags[i] - local short = bittest(flag,0x0002) - local same = bittest(flag,0x0010) + local short = bittest(flag,0x02) + local same = bittest(flag,0x10) if short then if same then x = x + readbyte(f) @@ -264,13 +701,13 @@ local function readglyph(f,nofcontours) else x = x + readshort(f) end - points[i] = { x, y, bittest(flag,0x0001) } + points[i] = { x, y, bittest(flag,0x01) } end local y = 0 for i=1,nofpoints do local flag = flags[i] - local short = bittest(flag,0x0004) - local same = bittest(flag,0x0020) + local short = bittest(flag,0x04) + local same = bittest(flag,0x20) if short then if same then y = y + readbyte(f) @@ -284,17 +721,11 @@ local function readglyph(f,nofcontours) end points[i][2] = y end - -- we could integrate this if needed - local first = 1 - for i=1,#endpoints do - local last = endpoints[i] - endpoints[i] = { unpack(points,first,last) } - first = last + 1 - end return { - type = "glyph", - -- points = points, - contours = endpoints, + type = "glyph", + points = points, + contours = contours, + nofpoints = nofpoints, } end @@ -384,8 +815,8 @@ local function readcomposite(f) end end return { - type = "composite", - components = components, + type = "composite", + components = components, } end @@ -407,15 +838,13 @@ function readers.loca(f,fontdata,specification) local locations = { } setposition(f,datatable.offset) if format == 1 then - local nofglyphs = datatable.length/4 - 1 - -1 + local nofglyphs = datatable.length/4 - 2 for i=0,nofglyphs do locations[i] = offset + readulong(f) end fontdata.nofglyphs = nofglyphs else - local nofglyphs = datatable.length/2 - 1 - -1 + local nofglyphs = datatable.length/2 - 2 for i=0,nofglyphs do locations[i] = offset + readushort(f) * 2 end @@ -427,52 +856,309 @@ function readers.loca(f,fontdata,specification) end function readers.glyf(f,fontdata,specification) -- part goes to cff module - if specification.glyphs then - local datatable = fontdata.tables.glyf - if datatable 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 - for index=0,nofglyphs do - local location = locations[index] - 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 location > 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,nofcontours) - elseif nofcontours > 0 then - shapes[index] = readglyph(f,nofcontours) + 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 + for index=0,nofglyphs do + local location = locations[index] + 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 location > 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,nofcontours) + elseif nofcontours > 0 then + shapes[index] = readglyph(f,nofcontours) + else + shapes[index] = readcomposite(f,nofcontours) + end + else + if loadshapes then + shapes[index] = { } + 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.loadshapes then + contours2outlines_normal(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 + +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 + else + count = band(count,0x80) * 256 + readbyte(f) + end + local points = { } + local p = 0 + local n = 1 + while p < count do + local control = readbyte(f) + local runreader = bittest(control,0x80) 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 n = 0 + local z = false + while nofpoints > 0 do + local control = readbyte(f) + local allzero = bittest(control,0x80) + local runreader = bittest(control,0x40) and readshort or readinteger + local runlength = band(control,0x3F) + 1 + if allzero then + z = runlength + else + if z then + for i=1,z do + p = p + 1 + deltas[p] = 0 + end + z = false + end + for i=1,runlength do + p = p + 1 + deltas[p] = runreader(f) + end + end + nofpoints = nofpoints - runlength + end + -- saves space + -- if z 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 + +function readers.gvar(f,fontdata,specification,glyphdata,shapedata) + 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 = readulong(f) -- shared + local nofglyphs = readushort(f) + local flags = readushort(f) + local dataoffset = tableoffset + readulong(f) + local data = { } + local tuples = { } + local glyphdata = fontdata.glyphs + -- 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 bittest(flags,0x0001) then + for i=1,nofglyphs do + data[i] = readulong(f) + end + else + for i=1,nofglyphs do + data[i] = 2*readushort(f) + end + end + -- + setposition(f,tableoffset+tupleoffset) + for i=1,noftuples do + tuples[i] = readtuplerecord(f,nofaxis) -- used ? + end + local lastoffset = false + for i=1,nofglyphs do -- hm one more cf spec + local shape = shapedata[i-1] -- todo 0 + if shape then + local startoffset = dataoffset + data[i] + if startoffset == lastoffset then + -- in the font that i used for testing there were the same offsets so + -- we can assume that this indicates a zero entry + else + -- todo: args_are_xy_values mess .. i have to be really bored + -- and motivated to deal with it + + lastoffset = startoffset + setposition(f,startoffset) + local flags = readushort(f) + local count = band(flags,0x0FFF) + local points = bittest(flags,0x8000) + local offset = startoffset + readushort(f) -- to serialized + local deltas = { } + local nofpoints = 0 + local allpoints = (shape.nofpoints or 0) + 1 + if points then + -- go to the packed stream (get them once) + local current = getposition(f) + setposition(f,offset) + points, nofpoints = readpoints(f) + offset = getposition(f) + setposition(f,current) + -- and back to the table + else + points, nofpoints = nil, 0 + end + for i=1,count do + local currentstart = getposition(f) + local size = readushort(f) -- check + local flags = readushort(f) + local index = band(flags,0x0FFF) + local haspeak = bittest(flags,0x8000) + local intermediate = bittest(flags,0x4000) + local private = bittest(flags,0x1000) + local peak = nil + local start = nil + local stop = nil + local xvalues = nil + local yvalues = nil + local points = points -- we default to shared + local nofpoints = nofpoints -- we default to shared + local advance = 4 + if peak then + peak = readtuplerecord(f,nofaxis) + advance = advance + 2*nofaxis else - shapes[index] = readcomposite(f,nofcontours) + if index+1 > #tuples then + print("error, bad index",index) + end + peak = tuples[index+1] -- hm, needs checking, only peak? end - else - if loadshapes then - shapes[index] = { } +-- what start otherwise ? + if intermediate then + start = readtuplerecord(f,nofaxis) + stop = readtuplerecord(f,nofaxis) + advance = advance + 4*nofaxis + end + -- get the deltas + if size > 0 then + -- goto the packed stream + setposition(f,offset) + if private then + points, nofpoints = readpoints(f) + elseif nofpoints == 0 then + nofpoints = allpoints + end + if nofpoints > 0 then + xvalues = readdeltas(f,nofpoints) + yvalues = readdeltas(f,nofpoints) + end + offset = getposition(f) + -- back to the table + setposition(f,currentstart+advance) + end + if not xvalues and not yvalues then + points = nil + end + local s = 1 + for i=1,nofaxis do + local f = factors[i] + local start = start and start[i] or 0 + local peak = peak and peak [i] or 0 + local stop = stop and stop [i] 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 + deltas[#deltas+1] = { + factor = s, + points = points, + xvalues = xvalues, + yvalues = yvalues, + } end - glyphs[index].boundingbox = nothing end - end - if loadshapes then - mergecomposites(glyphs,shapes) - contours2outlines(glyphs,shapes) + if shape.type == "glyph" then + applyaxis(glyphdata[i],shape,shape.points,deltas) + else + shape.deltas = deltas + end end end end diff --git a/tex/context/base/mkiv/l-lpeg.lua b/tex/context/base/mkiv/l-lpeg.lua index 877dae644..c34ba6ad4 100644 --- a/tex/context/base/mkiv/l-lpeg.lua +++ b/tex/context/base/mkiv/l-lpeg.lua @@ -187,18 +187,20 @@ local fullstripper = whitespace^0 * C((whitespace^0 * nonwhitespace^1)^0) ----- collapser = Cs(spacer^0/"" * ((spacer^1 * endofstring / "") + (spacer^1/" ") + P(1))^0) local collapser = Cs(spacer^0/"" * nonspacer^0 * ((spacer^0/" " * nonspacer^1)^0)) +local nospacer = Cs((whitespace^1/"" + nonwhitespace^1)^0) local b_collapser = Cs( whitespace^0 /"" * (nonwhitespace^1 + whitespace^1/" ")^0) local e_collapser = Cs((whitespace^1 * P(-1)/"" + nonwhitespace^1 + whitespace^1/" ")^0) local m_collapser = Cs( (nonwhitespace^1 + whitespace^1/" ")^0) -local b_stripper = Cs( spacer^0 /"" * (nonspacer^1 + spacer^1/" ")^0) -local e_stripper = Cs((spacer^1 * P(-1)/"" + nonspacer^1 + spacer^1/" ")^0) -local m_stripper = Cs( (nonspacer^1 + spacer^1/" ")^0) +local b_stripper = Cs( spacer^0 /"" * (nonspacer^1 + spacer^1/" ")^0) +local e_stripper = Cs((spacer^1 * P(-1)/"" + nonspacer^1 + spacer^1/" ")^0) +local m_stripper = Cs( (nonspacer^1 + spacer^1/" ")^0) patterns.stripper = stripper patterns.fullstripper = fullstripper patterns.collapser = collapser +patterns.nospacer = nospacer patterns.b_collapser = b_collapser patterns.m_collapser = m_collapser diff --git a/tex/context/base/mkiv/l-md5.lua b/tex/context/base/mkiv/l-md5.lua index 00272c873..6758fa444 100644 --- a/tex/context/base/mkiv/l-md5.lua +++ b/tex/context/base/mkiv/l-md5.lua @@ -48,6 +48,9 @@ do if not md5.hex then function md5.hex(str) if str then return lpegmatch(bytestohex,md5sum(str)) end end end if not md5.dec then function md5.dec(str) if str then return lpegmatch(bytestodec,md5sum(str)) end end end + md5.sumhexa = md5.hex + md5.sumHEXA = md5.HEX + end end diff --git a/tex/context/base/mkiv/l-string.lua b/tex/context/base/mkiv/l-string.lua index 2e8c61196..e0fb28445 100644 --- a/tex/context/base/mkiv/l-string.lua +++ b/tex/context/base/mkiv/l-string.lua @@ -72,6 +72,7 @@ end local stripper = patterns.stripper local fullstripper = patterns.fullstripper local collapser = patterns.collapser +local nospacer = patterns.nospacer local longtostring = patterns.longtostring function string.strip(str) @@ -86,6 +87,10 @@ function string.collapsespaces(str) return str and lpegmatch(collapser,str) or "" end +function string.nospaces(str) + return str and lpegmatch(nospacer,str) or "" +end + function string.longtostring(str) return str and lpegmatch(longtostring,str) or "" end diff --git a/tex/context/base/mkiv/lpdf-ini.lua b/tex/context/base/mkiv/lpdf-ini.lua index b4c8be7b1..1b24269a6 100644 --- a/tex/context/base/mkiv/lpdf-ini.lua +++ b/tex/context/base/mkiv/lpdf-ini.lua @@ -1188,15 +1188,17 @@ end do - local f_actual_text_one = formatters["BT /Span << /ActualText >> BDC %s EMC ET"] - local f_actual_text_two = formatters["BT /Span << /ActualText >> BDC %s EMC ET"] - local f_actual_text_one_b = formatters["BT /Span << /ActualText >> BDC"] - local f_actual_text_two_b = formatters["BT /Span << /ActualText >> BDC"] - local f_actual_text_b = formatters["BT /Span << /ActualText >> BDC"] - local s_actual_text_e = "EMC ET" - local f_actual_text_b_not = formatters["/Span << /ActualText >> BDC"] - local s_actual_text_e_not = "EMC" - local f_actual_text = formatters["/Span <> BDC"] + local f_actual_text_one = formatters["BT /Span << /ActualText >> BDC %s EMC ET"] + local f_actual_text_two = formatters["BT /Span << /ActualText >> BDC %s EMC ET"] + local f_actual_text_one_b = formatters["BT /Span << /ActualText >> BDC"] + local f_actual_text_two_b = formatters["BT /Span << /ActualText >> BDC"] + local f_actual_text_b = formatters["BT /Span << /ActualText >> BDC"] + local s_actual_text_e = "EMC ET" + local f_actual_text_b_not = formatters["/Span << /ActualText >> BDC"] + local f_actual_text_one_b_not = formatters["/Span << /ActualText >> BDC"] + local f_actual_text_two_b_not = formatters["/Span << /ActualText >> BDC"] + local s_actual_text_e_not = "EMC" + local f_actual_text = formatters["/Span <> BDC"] local context = context local pdfdirect = nodes.pool.pdfdirect @@ -1226,7 +1228,13 @@ do end function codeinjections.startunicodetoactualtextdirect(unicode) - return f_actual_text_b_not(unicode) + if type(unicode) == "string" then + return f_actual_text_b_not(unicode) + elseif unicode < 0x10000 then + return f_actual_text_one_b_not(unicode) + else + return f_actual_text_two_b_not(unicode/1024+0xD800,unicode%1024+0xDC00) + end end function codeinjections.stopunicodetoactualtextdirect() diff --git a/tex/context/base/mkiv/luat-cbk.lua b/tex/context/base/mkiv/luat-cbk.lua index 7b28b3be4..6fcfdc7f2 100644 --- a/tex/context/base/mkiv/luat-cbk.lua +++ b/tex/context/base/mkiv/luat-cbk.lua @@ -81,11 +81,15 @@ if not list then -- otherwise counters get reset list = utilities.storage.allocate(list_callbacks()) + local supported = { } + for k in next, list do - list[k] = 0 + list[k] = 0 + supported[k] = true end - callbacks.list = list + callbacks.list = list + callbacks.supported = supported end diff --git a/tex/context/base/mkiv/lxml-tab.lua b/tex/context/base/mkiv/lxml-tab.lua index f2d38a654..0c216bd3d 100644 --- a/tex/context/base/mkiv/lxml-tab.lua +++ b/tex/context/base/mkiv/lxml-tab.lua @@ -264,7 +264,7 @@ local function add_empty(spacing, namespace, tag) tg = tag, at = at, dt = { }, - ni = nil, -- preset slot + ni = nt, -- set slot, needed for css filtering __p__ = top } dt[nt] = t @@ -287,7 +287,7 @@ local function add_begin(spacing, namespace, tag) tg = tag, at = at, dt = { }, - ni = nil, -- preset slot + ni = nil, -- preset slot, needed for css filtering __p__ = stack[level] } setmetatable(top, mt) @@ -316,6 +316,7 @@ local function add_end(spacing, namespace, tag) dt = top.dt nt = #dt + 1 dt[nt] = toclose + toclose.ni = nt -- update slot, needed for css filtering if toclose.at.xmlns then remove(xmlns) end @@ -325,7 +326,7 @@ end -- -- will be an option: dataonly -- --- if #text == 0 or lpegmatch(spaceonly,text) then +-- if #text == 0 or lpegmatch(spaceonly,text) then -- return -- end diff --git a/tex/context/base/mkiv/m-fonts-plugins.mkiv b/tex/context/base/mkiv/m-fonts-plugins.mkiv new file mode 100644 index 000000000..ecb311694 --- /dev/null +++ b/tex/context/base/mkiv/m-fonts-plugins.mkiv @@ -0,0 +1,406 @@ +%D \module +%D [ file=m-fonts-plugins, +%D version=2016.10.10, +%D title=\CONTEXT\ Fonts, +%D subtitle=Font Engine Plugins, +%D author=Hans Hagen, +%D date=\currentdate, +%D copyright={PRAGMA ADE \& \CONTEXT\ Development Team}] +%C +%C This module is part of the \CONTEXT\ macro||package and is +%C therefore copyrighted by \PRAGMA. See mreadme.pdf for +%C details. + +%D See source code for comments. I wrote this a follow up on a presentation by +%D Kai Eigner, left it for a while, and sort of finalized it the last quarter of +%D 2016. As I don't use this module, apart from maybe testing something, it is +%D not guaranteed to work. Also, plugins can interfere with other functionality +%D in \CONTEXT\ so don't expect too much support. The two modules mentioned +%D below should work in the generic loader too. It's anyhow an illustration of +%D how \type {ffi} can work be used in a practical application. + +\registerctxluafile{font-txt}{1.001} % generic text handler +\registerctxluafile{font-phb}{1.001} % harfbuzz plugin + +\startluacode + + local function processlist(data) + local list = data.list + local timings = data.results + for i=1,#list do + local name = list[i] + local data = timings[name] + local none = data["context none"] or 0 + local node = data["context node"] or 0 + if node > 0.1 then + context.starttabulate { "|l|c|c|c|c|c|" } + context.NC() context.bold(name) + context.NC() context([[$t$]]) + context.NC() context([[$t - t_{\hbox{\tx none}}$]]) + context.NC() context([[$t - t_{\hbox{\tx node}}$]]) + context.NC() context([[$t / t_{\hbox{\tx node}}$]]) + context.NC() context([[$\frac{t - t_{\hbox{\txx none}}}{t_{\hbox{\txx node}} - t_{\hbox{\txx none}}}$]]) + context.NC() context.NR() + context.TL() + for k, v in table.sortedhash(data) do + context.NC() context(k) + context.NC() context("%0.2f",v) + context.NC() context("%0.2f",v - none) + context.NC() context("%0.2f",v - node) + context.NC() context("%0.2f",v / node) + context.NC() if node ~= none then context("%0.2f",(v-none) / (node-none)) end + context.NC() context.NR() + end + context.stoptabulate() + end + end + end + + moduledata.plugins = { + processlist = processlist, + } + +\stopluacode + +\continueifinputfile{m-fonts-plugins.mkiv} + +\usemodule[art-01] + +\starttext + +\edef\tufte{\cldloadfile{tufte.tex}} +\edef\khatt{\cldloadfile{khatt-ar.tex}} + +\startbuffer[latin-definitions] +\definefont[TestA][Serif*test] +\definefont[TestB][SerifItalic*test] +\definefont[TestC][SerifBold*test] +\stopbuffer + +\startbuffer[latin-text] +\TestA \tufte \par +\TestB \tufte \par +\TestC \tufte \par +\dorecurse {10} {% + \TestA Fluffy Test Font A + \TestB Fluffy Test Font B + \TestC Fluffy Test Font C +}\par +\stopbuffer + +\startbuffer[arabic-definitions] +\definedfont[Arabic*test at 14pt] +\setupinterlinespace[line=18pt] +\setupalign[r2l] +\stopbuffer + +\startbuffer[arabic-text] +\dorecurse {10} { + \khatt\space + \khatt\space + \khatt + \blank +} +\stopbuffer + +\startbuffer[mixed-definitions] +\definefont[TestL][Serif*test] +\definefont[TestA][Arabic*test at 14pt] +\setupinterlinespace[line=18pt] +\setupalign[r2l] +\stopbuffer + +\startbuffer[mixed-text] +\dorecurse {2} { + {\TestA\khatt\space\khatt\space\khatt} + {\TestL\lefttoright\tufte} + \blank + \dorecurse{10}{% + {\TestA وَ قَرْمِطْ بَيْنَ الْحُرُوفِ؛ فَإِنَّ} + {\TestL\lefttoright A snippet text that makes no sense.} + } +} +\stopbuffer + +\definefontfeature + [test-none] + [mode=none] + +\definefontfeature + [test-base] + [mode=base, + liga=yes, + kern=yes] + +\definefontfeature + [test-node] + [mode=node, + script=auto, + autoscript=position, + autolanguage=position, + ccmp=yes, + liga=yes, + % rlig=yes, + % hlig=yes, + % dlig=yes, + clig=yes, + kern=yes, + mark=yes, + mkmk=yes, + curs=yes] + +\definefontfeature + [test-text] + [mode=plug, + features=text] + +\definefontfeature + [test-native] + [mode=plug, + features=harfbuzz, + %liga=yes, + %kern=yes, + shaper=native] + +\definefontfeature + [test-uniscribe] + [mode=plug, + features=harfbuzz, + %liga=yes, + %kern=yes, + shaper=uniscribe] + +\definefontfeature + [test-binary] + [mode=plug, + features=harfbuzz, + %liga=yes, + %kern=yes, + shaper=uniscribe, + method=binary] + +\definefontfeature + [arabic-node] + [arabic] + +\definefontfeature + [arabic-native] + [mode=plug, + features=harfbuzz, + % method=binary, + script=arab,language=dflt, +% ccmp=yes, +% init=yes,medi=yes,fina=yes,isol=yes, +% liga=yes,dlig=yes,rlig=yes,clig=yes,calt=yes, +% mark=yes,mkmk=yes,kern=yes,curs=yes, + shaper=native] + +\definefontfeature + [arabic-uniscribe] + [mode=plug, + features=harfbuzz, + script=arab,language=dflt,ccmp=yes, + init=yes,medi=yes,fina=yes,isol=yes, + liga=yes,dlig=yes,rlig=yes,clig=yes,calt=yes, + mark=yes,mkmk=yes,kern=yes,curs=yes, + shaper=uniscribe] + +\starttexdefinition RunLatinTest #1#2#3#4#5 + \start + \dontcomplain + \definefontfeature[test][test-#4] + \writestatus{warning}{#1 #3 #4 (1 initial run)} + \page + \startluacode + collectgarbage("collect") + \stopluacode + \title{#1 #3 #4} + \start + \getbuffer[#5-definitions] + \showfontkerns + \showmakeup[discretionary] + \enabletrackers[fonts.plugins.hb.colors]% + \testfeatureonce{1}{ + \getbuffer[#5-text] + } + \stop + \page + \startluacode + collectgarbage("collect") + \stopluacode + \ifnum#2>1\relax + \writestatus{warning}{#1 #3 #4 (#2 timing runs)} + \start + \getbuffer[#5-definitions] + \testfeatureonce{#2}{ + \setbox\scratchbox\hbox{\getbuffer[#5-text]} + } + \stop + \writestatus{warning}{done} + \fi + \startluacode + document.collected_timings.timings["#5"].results["#1"]["#3 #4"] = \elapsedtime\space + collectgarbage("collect") + \stopluacode + \stop +\stoptexdefinition + +\starttexdefinition RunArabicTest #1#2#3#4#5 + \start + \dontcomplain + \definefontsynonym[Arabic][#1] + \definefontfeature[test][arabic-#4] + \writestatus{warning}{#1 #3 #4 #5 (1 initial run)} + \page + \startluacode + collectgarbage("collect") + \stopluacode + \title{#1 #3 #4} + \start + \getbuffer[#5-definitions] + \enabletrackers[fonts.plugins.hb.colors]% + \testfeatureonce{1}{ + \setupalign[flushleft] % easier to compare + \getbuffer[#5-text] + } + \par + \stop + \page + \ifnum#2>1\relax + \writestatus{warning}{#1 #3 #4 #5 (#2 timing runs)} + \start + \getbuffer[#5-definitions] + \testfeatureonce{#2}{ + \setbox\scratchbox\hbox{\getbuffer[#5-text]} + } + \stop + \writestatus{warning}{done} + \fi + \startluacode + document.collected_timings.timings["#5"].results["#1"]["#3 #4"] = \elapsedtime\space + collectgarbage("collect") + \stopluacode + \stop +\stoptexdefinition + +\startluacode + local processlist = moduledata.plugins.processlist + + local data = { + timings = { }, + engine = jit and "luajittex" or "luatex", + } + + document.collected_timings = data + + -- LATIN + + local list = { + "modern", + "pagella", + "dejavu", + "cambria", + "ebgaramond", + "lucidaot" + } + + data.timings["latin"] = { + list = list, + results = table.setmetatableindex("table"), + } + + for i=1,#list do + + local name = list[i] + + context.setupbodyfont { name } + context.RunLatinTest (name, 100, "context", "none", "latin") + context.RunLatinTest (name, 100, "context", "base", "latin") + context.RunLatinTest (name, 100, "context", "node", "latin") + context.RunLatinTest (name, 100, "harfbuzz", "native", "latin") + -- context.RunLatinTest (name, 100, "harfbuzz", "uniscribe", "latin") + -- context.RunLatinTest (name, 1, "context", "text", "latin") + -- context.RunLatinTest (name, 1, "harfbuzz", "binary", "latin") + + end + + context(function() + context.page() + context.title((jit and "luajittex" or "luatex") .. " latin") + processlist(data.timings["latin"]) + context.page() + end) + + -- ARABIC + + local list = { + "arabtype" + } + + data.timings["arabic"] = { + list = list, + results = table.setmetatableindex("table") + } + + for i=1,#list do + + local name = list[i] + + context.setupbodyfont { name } + context.RunArabicTest (name, 100, "context", "none", "arabic") + context.RunArabicTest (name, 100, "context", "base", "arabic") + context.RunArabicTest (name, 100, "context", "node", "arabic") + context.RunArabicTest (name, 100, "harfbuzz", "native", "arabic") + -- context.RunArabicTest (name, 100, "harfbuzz", "uniscribe", "arabic") + -- context.RunArabicTest (name, 1, "context", "text", "arabic") + -- context.RunArabicTest (name, 1, "harfbuzz", "binary", "arabic") + + end + + context(function() + context.page() + context.title((jit and "luajittex" or "luatex") .. " arabic") + processlist(data.timings["arabic"]) + context.page() + end) + + -- MIXED + + local list = { + "arabtype" + } + + data.timings["mixed"] = { + list = list, + results = table.setmetatableindex("table") + } + + for i=1,#list do + + local name = list[i] + + context.setupbodyfont { name } + context.RunArabicTest (name, 100, "context", "none", "mixed") + context.RunArabicTest (name, 100, "context", "base", "mixed") + context.RunArabicTest (name, 100, "context", "node", "mixed") + context.RunArabicTest (name, 100, "harfbuzz", "native", "mixed") + -- context.RunArabicTest (name, 100, "harfbuzz", "uniscribe", "mixed") + -- context.RunArabicTest (name, 1, "context", "text", "mixed") + -- context.RunArabicTest (name, 1, "harfbuzz", "binary", "mixed") + + end + + context(function() + context.page() + context.title((jit and "luajittex" or "luatex") .. " mixed") + processlist(data.timings["mixed"]) + context.page() + end) + + context(function() + table.save("m-fonts-plugins-timings-" .. (jit and "luajittex" or "luatex") .. ".lua",data) + end) + +\stopluacode + +\stoptext diff --git a/tex/context/base/mkiv/mlib-pdf.lua b/tex/context/base/mkiv/mlib-pdf.lua index 4d1756c43..0c2945316 100644 --- a/tex/context/base/mkiv/mlib-pdf.lua +++ b/tex/context/base/mkiv/mlib-pdf.lua @@ -571,7 +571,7 @@ function metapost.flush(result,flusher,askedfig) result[#result+1] = evenodd and "h f*" or "h f" -- f* = eo elseif objecttype == "outline" then if both then - result[#result+1] = evenodd and "h B*" or "h B" -- f* = eo + result[#result+1] = evenodd and "h B*" or "h B" -- B* = eo else result[#result+1] = open and "S" or "h S" end diff --git a/tex/context/base/mkiv/node-tra.lua b/tex/context/base/mkiv/node-tra.lua index 0d3192559..8c79e0ab8 100644 --- a/tex/context/base/mkiv/node-tra.lua +++ b/tex/context/base/mkiv/node-tra.lua @@ -437,12 +437,12 @@ dimenfactors[""] = dimenfactors.pt local function numbertodimen(d,unit,fmt) if not d or d == 0 then - if not unit or unit == "pt" then - return "0pt" - elseif fmt then - return formatters[fmt](0,unit) - else + if fmt then + return formatters[fmt](0,unit or "pt") + elseif unit then return 0 .. unit + else + return "0pt" end elseif fmt then if not unit then diff --git a/tex/context/base/mkiv/status-files.pdf b/tex/context/base/mkiv/status-files.pdf index dbcb86a1f..641067bfc 100644 Binary files a/tex/context/base/mkiv/status-files.pdf and b/tex/context/base/mkiv/status-files.pdf differ diff --git a/tex/context/base/mkiv/status-lua.pdf b/tex/context/base/mkiv/status-lua.pdf index 637ce005e..6c11b6984 100644 Binary files a/tex/context/base/mkiv/status-lua.pdf and b/tex/context/base/mkiv/status-lua.pdf differ diff --git a/tex/context/base/mkiv/util-fil.lua b/tex/context/base/mkiv/util-fil.lua index d0ffe07c6..cb36db7be 100644 --- a/tex/context/base/mkiv/util-fil.lua +++ b/tex/context/base/mkiv/util-fil.lua @@ -116,6 +116,7 @@ function files.readcardinal2(f) local a, b = byte(f:read(2),1,2) return 0x100 * a + b end + function files.readcardinal2le(f) local b, a = byte(f:read(2),1,2) return 0x100 * a + b @@ -123,27 +124,17 @@ end function files.readinteger2(f) local a, b = byte(f:read(2),1,2) - local n = 0x100 * a + b - if n >= 0x8000 then - -- return n - 0xFFFF - 1 - return n - 0x10000 + if a >= 0x80 then + return 0x100 * a + b - 0x10000 else - return n + return 0x100 * a + b end end - function files.readinteger2(f) - local a, b = byte(f:read(2),1,2) - if a >= 0x80 then - return 0x100 * a + b - 0x10000 - else - return 0x100 * a + b - end - end + function files.readinteger2le(f) local b, a = byte(f:read(2),1,2) local n = 0x100 * a + b if n >= 0x8000 then - -- return n - 0xFFFF - 1 return n - 0x10000 else return n @@ -154,6 +145,7 @@ function files.readcardinal3(f) local a, b, c = byte(f:read(3),1,3) return 0x10000 * a + 0x100 * b + c end + function files.readcardinal3le(f) local c, b, a = byte(f:read(3),1,3) return 0x10000 * a + 0x100 * b + c @@ -163,17 +155,16 @@ function files.readinteger3(f) local a, b, c = byte(f:read(3),1,3) local n = 0x10000 * a + 0x100 * b + c if n >= 0x80000 then - -- return n - 0xFFFFFF - 1 return n - 0x1000000 else return n end end + function files.readinteger3le(f) local c, b, a = byte(f:read(3),1,3) local n = 0x10000 * a + 0x100 * b + c if n >= 0x80000 then - -- return n - 0xFFFFFF - 1 return n - 0x1000000 else return n @@ -184,21 +175,12 @@ function files.readcardinal4(f) local a, b, c, d = byte(f:read(4),1,4) return 0x1000000 * a + 0x10000 * b + 0x100 * c + d end + function files.readcardinal4le(f) local d, c, b, a = byte(f:read(4),1,4) return 0x1000000 * a + 0x10000 * b + 0x100 * c + d end --- function files.readinteger4(f) --- local a, b, c, d = byte(f:read(4),1,4) --- local n = 0x1000000 * a + 0x10000 * b + 0x100 * c + d --- if n >= 0x8000000 then --- -- return n - 0xFFFFFFFF - 1 --- return n - 0x100000000 --- else --- return n --- end --- end function files.readinteger4(f) local a, b, c, d = byte(f:read(4),1,4) if a >= 0x80 then @@ -207,11 +189,11 @@ function files.readinteger4(f) return 0x1000000 * a + 0x10000 * b + 0x100 * c + d end end + function files.readinteger4le(f) local d, c, b, a = byte(f:read(4),1,4) local n = 0x1000000 * a + 0x10000 * b + 0x100 * c + d if n >= 0x8000000 then - -- return n - 0xFFFFFFFF - 1 return n - 0x100000000 else return n @@ -227,6 +209,8 @@ function files.readfixed2(f) end end +-- (real) (n>>16) + ((n&0xffff)/65536.0)) + function files.readfixed4(f) local a, b, c, d = byte(f:read(4),1,4) if a >= 0x80 then @@ -234,7 +218,6 @@ function files.readfixed4(f) else return (0x1000000 * a + 0x10000 * b + 0x100 * c + d)/65536.0 end - end if extract then @@ -242,10 +225,17 @@ if extract then local extract = bit32.extract local band = bit32.band + -- (real) ((n<<16)>>(16+14)) + ((n&0x3fff)/16384.0)) + function files.read2dot14(f) local a, b = byte(f:read(2),1,2) - local n = 0x100 * a + b - return extract(n,14,2) + (band(n,0x3FFF) / 16384.0) + if a >= 0x80 then + local n = -(0x100 * a + b) + return - (extract(n,14,2) + (band(n,0x3FFF) / 16384.0)) + else + local n = 0x100 * a + b + return (extract(n,14,2) + (band(n,0x3FFF) / 16384.0)) + end end end diff --git a/tex/context/interface/mkiv/i-context.pdf b/tex/context/interface/mkiv/i-context.pdf index ef5687741..496d93c5f 100644 Binary files a/tex/context/interface/mkiv/i-context.pdf and b/tex/context/interface/mkiv/i-context.pdf differ diff --git a/tex/context/interface/mkiv/i-readme.pdf b/tex/context/interface/mkiv/i-readme.pdf index 59eabb096..d69065fa9 100644 Binary files a/tex/context/interface/mkiv/i-readme.pdf and b/tex/context/interface/mkiv/i-readme.pdf differ diff --git a/tex/context/modules/mkiv/m-ipsum.mkiv b/tex/context/modules/mkiv/m-ipsum.mkiv index 1c5901d86..7ba78ee2e 100644 --- a/tex/context/modules/mkiv/m-ipsum.mkiv +++ b/tex/context/modules/mkiv/m-ipsum.mkiv @@ -127,6 +127,7 @@ end \startsetups[handler:action:ipsum] \useipsumstyleandcolor\c!style\c!color + % hm, also changes dates \uselanguageparameter\ipsumparameter \ctxlua{moduledata.ipsum.typeset { alternative = "\ipsumparameter\c!alternative", diff --git a/tex/context/modules/mkiv/s-fonts-shapes.lua b/tex/context/modules/mkiv/s-fonts-shapes.lua index c07fad285..ebdf04c22 100644 --- a/tex/context/modules/mkiv/s-fonts-shapes.lua +++ b/tex/context/modules/mkiv/s-fonts-shapes.lua @@ -113,7 +113,7 @@ local function showglyphshape(specification) local tfmdata = fontdata[id] local charnum = tonumber(specification.character) if not charnum then - charnum = fonts.helpers.nametoslot(n) + charnum = fonts.helpers.nametoslot(specification.character) end local characters = tfmdata.characters local descriptions = tfmdata.descriptions diff --git a/tex/context/modules/mkiv/s-fonts-shapes.mkiv b/tex/context/modules/mkiv/s-fonts-shapes.mkiv index 3e2bc26f6..4a0377ada 100644 --- a/tex/context/modules/mkiv/s-fonts-shapes.mkiv +++ b/tex/context/modules/mkiv/s-fonts-shapes.mkiv @@ -18,7 +18,7 @@ \registerctxluafile{s-fonts-shapes}{} \installmodulecommandluasingle \showfontshapes {moduledata.fonts.shapes.showlist} -\installmodulecommandluasingle \showglyphshape {moduledata.fonts.shapes.showglypshape} +\installmodulecommandluasingle \showglyphshape {moduledata.fonts.shapes.showglyphshape} \installmodulecommandluatwo \showlastglyphshapefield {moduledata.fonts.shapes.showlastglyphshapefield} \installmodulecommandluasingle \showallglyphshapes {moduledata.fonts.shapes.showallglypshapes} diff --git a/tex/context/modules/mkiv/s-fonts-variable.lua b/tex/context/modules/mkiv/s-fonts-variable.lua new file mode 100644 index 000000000..d507141e2 --- /dev/null +++ b/tex/context/modules/mkiv/s-fonts-variable.lua @@ -0,0 +1,264 @@ +if not modules then modules = { } end modules ['s-fonts-variable'] = { + version = 1.001, + comment = "companion to s-fonts-variable.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +moduledata.fonts = moduledata.fonts or { } +moduledata.fonts.variable = moduledata.fonts.variable or { } + +local format = string.format +local stripstring = string.nospaces +local lower = string.lower +local rep = string.rep + +local context = context +local NC, NR, HL = context.NC, context.NR, context.HL +local bold, monobold, mono, formattedmono = context.bold, context.monobold, context.mono, context.formatted.mono + +function moduledata.fonts.variable.showvariations(specification) + + specification = interfaces.checkedspecification(specification) + + local fontfile = specification.font + local fontname = format("testfont-%s",i) + local fontsize = tex.dimen.bodyfontsize + if not fontfile then + return + end + + local id, fontdata = fonts.definers.define { + name = fontfile, + size = fontsize, + cs = fontname, + } + + local resources = fontdata.resources + + if not resources then + return + end + + local variabledata = resources.variabledata + + if not variabledata then + return + end + + context.starttitle { title = fontdata.shared.rawdata.metadata.fullname } + + local parameters = fontdata.parameters + + context.startsubject { title = "parameters" } + if parameters then + context.starttabulate { "|||" } + NC() monobold("ascender") NC() context("%p",parameters.ascender) NC() NR() + NC() monobold("descender") NC() context("%p",parameters.descender) NC() NR() + NC() monobold("emwidth") NC() context("%p",parameters.em) NC() NR() + NC() monobold("exheight") NC() context("%p",parameters.ex) NC() NR() + NC() monobold("size") NC() context("%p",parameters.size) NC() NR() + NC() monobold("slant") NC() context("%s",parameters.slant) NC() NR() + NC() monobold("space") NC() context("%p",parameters.space) NC() NR() + NC() monobold("shrink") NC() context("%p",parameters.spaceshrink) NC() NR() + NC() monobold("stretch") NC() context("%p",parameters.spacestretch) NC() NR() + NC() monobold("units") NC() context("%s",parameters.units) NC() NR() + context.stoptabulate() + else + context("no parameters") + end + context.stopsubject() + + local features = fontdata.shared.rawdata.resources.features + + context.startsubject { title = "features" } + if features then + local function f(g) + if g then + local t = table.sortedkeys(g) + local n = 0 + for i=1,#t do + if #t[i] <= 4 then + n = n + 1 + t[n] = t[i] + end + end + return table.concat(t," ",1,n) + end + end + context.starttabulate { "||p|" } + NC() monobold("gpos") NC() mono(f(features.gpos)) NC() NR() + NC() monobold("gsub") NC() mono(f(features.gsub)) NC() NR() + context.stoptabulate() + else + context("no features") + end + context.stopsubject() + + local designaxis = variabledata.designaxis + + context.startsubject { title = "design axis" } + if designaxis then + context.starttabulate { "||||c|c|c|c|c|" } + NC() bold("tag") + NC() bold("name") + NC() bold("variant") + NC() bold("flags") + NC() bold("value") + NC() bold("min") + NC() bold("max") + NC() bold("link") + NC() NR() + HL() + for k=1,#designaxis do + local axis = designaxis[k] + local tag = axis.tag + local name = axis.name + local variants = axis.variants + local haslimits = variants[1].maximum + local haslink = variants[1].link + for i=1,#variants do + local variant = variants[i] + NC() monobold(tag) + NC() context(name) + NC() context(variant.name) + NC() formattedmono("0x%04x",variant.flags) + NC() context(variant.value) + NC() context(variant.minimum or "-") + NC() context(variant.maximum or "-") + NC() context(variant.link or "-") + NC() NR() + tag = nil + name = nil + end + end + context.stoptabulate() + else + context("no design axis defined (no \\type{stat} table)") + end + context.stopsubject() + + local axis = variabledata.axis + local instances = variabledata.instances + local list = { } + + context.startsubject { title = "axis" } + if axis then + context.starttabulate { "|||c|c|c|" } + NC() bold("tag") + NC() bold("name") + NC() bold("min") + NC() bold("def") + NC() bold("max") + NC() NR() + HL() + for k=1,#axis do + local a = axis[k] + NC() monobold(a.tag) + NC() context(a.name) + NC() context(a.minimum) + NC() context(a.default) + NC() context(a.maximum) + NC() NR() + list[#list+1] = a.tag + end + context.stoptabulate() + else + context("no axis defined, incomplete \\type{fvar} table") + end + context.stopsubject() + + local collected = { } + + context.startsubject { title = "instances" } + if instances and #list > 0 then + context.starttabulate { "||" .. rep("c|",#list) .. "|" } + NC() + for i=1,#list do + NC() monobold(list[i]) + end + NC() + local fullname = lower(stripstring(fontdata.shared.rawdata.metadata.fullname)) + formattedmono("%s*",fullname) + NC() NR() + HL() + for k=1,#instances do + local i = instances[k] + NC() monobold(i.subfamily) + local values = i.values + local hash = { } + for k=1,#values do + local v = values[k] + hash[v.axis] = v.value + end + for i=1,#list do + NC() context(hash[list[i]]) + end + NC() + local instance = lower(stripstring(i.subfamily)) + mono(instance) + collected[#collected+1] = fullname .. instance + NC() NR() + end + context.stoptabulate() + else + context("no instances defined, incomplete \\type{fvar} table") + end + context.stopsubject() + + for i=1,#collected do + + local instance = collected[i] + context.startsubject { title = instance } + context.start() + context.definedfont { "name:" .. instance .. "*default" } + context.input("zapf.tex") + context.par() + context.stop() + context.stopsubject() + end + + -- local function showregions(tag) + -- + -- local regions = variabledata[tag] + -- + -- context.startsubject { title = tag } + -- if regions then + -- context.starttabulate { "|r|c|r|r|r|" } + -- NC() bold("n") + -- NC() bold("axis") + -- NC() bold("start") + -- NC() bold("peak") + -- NC() bold("stop") + -- NC() NR() + -- HL() + -- local designaxis = designaxis or axis + -- for i=1,#regions do + -- local axis = regions[i] + -- for j=1,#axis do + -- local a = axis[j] + -- NC() monobold(i) + -- NC() monobold(designaxis[j].tag) + -- NC() context("%0.3f",a.start) + -- NC() context("%0.3f",a.peak) + -- NC() context("%0.3f",a.stop) + -- NC() NR() + -- i = nil + -- end + -- end + -- context.stoptabulate() + -- else + -- context("no %s defined",tag) + -- end + -- context.stopsubject() + -- + -- end + -- + -- showregions("gregions") + -- showregions("mregions") + -- showregions("hregions") + + context.stoptitle() + +end diff --git a/tex/context/modules/mkiv/s-fonts-variable.mkiv b/tex/context/modules/mkiv/s-fonts-variable.mkiv new file mode 100644 index 000000000..564c40e11 --- /dev/null +++ b/tex/context/modules/mkiv/s-fonts-variable.mkiv @@ -0,0 +1,46 @@ +%D \module +%D [ file=s-fonts-variable, +%D version=2017.02.18, +%D title=\CONTEXT\ Style File, +%D subtitle=Show Variable Font Properties , +%D author=Hans Hagen, +%D date=\currentdate, +%D copyright={PRAGMA ADE \& \CONTEXT\ Development Team}] +%C +%C This module is part of the \CONTEXT\ macro||package and is +%C therefore copyrighted by \PRAGMA. See mreadme.pdf for +%C details. + +% begin info +% +% title : variable information (experimental) +% +% comment : variable fonts contain extra information styles, axis, regions +% status : experimental, used for luatex testing +% +% end info + +\startmodule[fonts-variable] + +\registerctxluafile{s-fonts-variable}{} + +\installmodulecommandluasingle \showfontvariations {moduledata.fonts.variable.showvariations} + +\stopmodule + +\continueifinputfile{s-fonts-variable.mkiv} + +\usemodule[art-01] + +\starttext + + \showfontvariations + [font=file:adobevfprototype.otf] + + \showfontvariations + [font=file:avenirnextvariable.ttf] + +% \showfontvariations +% [font=file:kairossansvariable.ttf] + +\stoptext diff --git a/tex/generic/context/luatex/luatex-core.lua b/tex/generic/context/luatex/luatex-core.lua index ac552e70a..b32be8095 100644 --- a/tex/generic/context/luatex/luatex-core.lua +++ b/tex/generic/context/luatex/luatex-core.lua @@ -5,7 +5,7 @@ -- copyright = 'LuaTeX Development Team', -- } -LUATEXCOREVERSION = 1.001 +LUATEXCOREVERSION = 1.002 -- This file overloads some Lua functions. The readline variants provide the same -- functionality as LuaTeX <= 1.04 and doing it this way permits us to keep the @@ -24,7 +24,6 @@ local fio_recordfilename = fio.recordfilename local mt = getmetatable(io.stderr) local mt_lines = mt.lines - local saferoption = status.safer_option local shellescape = status.shell_escape -- 0 (disabled) 1 (restricted) 2 (everything) local kpseused = status.kpse_used -- 0 1 @@ -98,45 +97,68 @@ if kpseused == 1 then io.open = luatex_io_open io.popen = luatex_io_popen - if saferoption then +end - os.execute = nil - os.spawn = nil - os.exec = nil - os.setenv = nil - os.tempdir = nil +if saferoption == 1 then - io.popen = nil - io.open = nil + os.execute = nil + os.spawn = nil + os.exec = nil + os.setenv = nil + os.tempdir = nil - os.rename = nil - os.remove = nil + io.popen = nil + io.open = nil - io.tmpfile = nil - io.output = nil + os.rename = nil + os.remove = nil - lfs.chdir = nil - lfs.lock = nil - lfs.touch = nil - lfs.rmdir = nil - lfs.mkdir = nil + io.tmpfile = nil + io.output = nil - io.saved_popen = nil - io.saved_open = luatex_io_open_readonly + lfs.chdir = nil + lfs.lock = nil + lfs.touch = nil + lfs.rmdir = nil + lfs.mkdir = nil - end + io.saved_popen = nil + io.saved_open = luatex_io_open_readonly - if saferoption or shellescape ~= 2 then - local ffi = require('ffi') - for k, v in next, ffi do - if k ~= 'gc' then - ffi[k] = nil - end - ffi = nil +end + +if saferoption == 1 or shellescape ~= 2 then + + ffi = require('ffi') + for k, v in next, ffi do + if k ~= 'gc' then + ffi[k] = nil end end + ffi = nil +end + +-- os.[execute|os.spawn|os.exec] already are shellescape aware) + + +if md5 then + + local sum = md5.sum + local gsub = string.gsub + local format = string.format + local byte = string.byte + + function md5.sumhexa(k) + return (gsub(sum(k), ".", function(c) + return format("%02x",byte(c)) + end)) + end - -- os.[execute|os.spawn|os.exec] already are shellescape aware) + function md5.sumHEXA(k) + return (gsub(sum(k), ".", function(c) + return format("%02X",byte(c)) + end)) + end end @@ -152,10 +174,17 @@ if utilities and utilities.merger and utilities.merger.compact then local d = gsub(data,'\r\n','\n') -- be nice for unix local s = utilities.merger.compact(d) -- no comments and less spaces + t[#t+1] = '/* generated from and by luatex-core.lua */' + t[#t+1] = '' -- t[#t+1] = format('/*\n\n%s\n\n*/',d) + -- t[#t+1] = '' + t[#t+1] = '#include "lua.h"' + t[#t+1] = '#include "lauxlib.h"' + t[#t+1] = '' + t[#t+1] = 'int load_luatex_core_lua (lua_State * L);' + t[#t+1] = '' t[#t+1] = 'int load_luatex_core_lua (lua_State * L)' t[#t+1] = '{' - t[#t+1] = ' /* generated from and by luatex-core.lua */' t[#t+1] = ' static unsigned char luatex_core_lua[] = {' for c in gmatch(d,'.') do if n == 16 then diff --git a/tex/generic/context/luatex/luatex-fonts-merged.lua b/tex/generic/context/luatex/luatex-fonts-merged.lua index 2e5aa99c0..d946dedfd 100644 --- a/tex/generic/context/luatex/luatex-fonts-merged.lua +++ b/tex/generic/context/luatex/luatex-fonts-merged.lua @@ -1,6 +1,6 @@ -- merged file : c:/data/develop/context/sources/luatex-fonts-merged.lua -- parent file : c:/data/develop/context/sources/luatex-fonts.lua --- merge date : 03/02/17 22:23:29 +-- merge date : 03/20/17 17:33:01 do -- begin closure to overcome local limits and interference @@ -213,6 +213,7 @@ patterns.nonwhitespace=nonwhitespace local stripper=spacer^0*C((spacer^0*nonspacer^1)^0) local fullstripper=whitespace^0*C((whitespace^0*nonwhitespace^1)^0) local collapser=Cs(spacer^0/""*nonspacer^0*((spacer^0/" "*nonspacer^1)^0)) +local nospacer=Cs((whitespace^1/""+nonwhitespace^1)^0) local b_collapser=Cs(whitespace^0/""*(nonwhitespace^1+whitespace^1/" ")^0) local e_collapser=Cs((whitespace^1*P(-1)/""+nonwhitespace^1+whitespace^1/" ")^0) local m_collapser=Cs((nonwhitespace^1+whitespace^1/" ")^0) @@ -222,6 +223,7 @@ local m_stripper=Cs((nonspacer^1+spacer^1/" ")^0) patterns.stripper=stripper patterns.fullstripper=fullstripper patterns.collapser=collapser +patterns.nospacer=nospacer patterns.b_collapser=b_collapser patterns.m_collapser=m_collapser patterns.e_collapser=e_collapser @@ -955,6 +957,7 @@ end local stripper=patterns.stripper local fullstripper=patterns.fullstripper local collapser=patterns.collapser +local nospacer=patterns.nospacer local longtostring=patterns.longtostring function string.strip(str) return str and lpegmatch(stripper,str) or "" @@ -965,6 +968,9 @@ end function string.collapsespaces(str) return str and lpegmatch(collapser,str) or "" end +function string.nospaces(str) + return str and lpegmatch(nospacer,str) or "" +end function string.longtostring(str) return str and lpegmatch(longtostring,str) or "" end @@ -4360,21 +4366,12 @@ function files.readcardinal2le(f) end function files.readinteger2(f) local a,b=byte(f:read(2),1,2) - local n=0x100*a+b - if n>=0x8000 then - return n-0x10000 + if a>=0x80 then + return 0x100*a+b-0x10000 else - return n + return 0x100*a+b end end - function files.readinteger2(f) - local a,b=byte(f:read(2),1,2) - if a>=0x80 then - return 0x100*a+b-0x10000 - else - return 0x100*a+b - end - end function files.readinteger2le(f) local b,a=byte(f:read(2),1,2) local n=0x100*a+b @@ -4456,8 +4453,13 @@ if extract then local band=bit32.band function files.read2dot14(f) local a,b=byte(f:read(2),1,2) - local n=0x100*a+b - return extract(n,14,2)+(band(n,0x3FFF)/16384.0) + if a>=0x80 then + local n=-(0x100*a+b) + return-(extract(n,14,2)+(band(n,0x3FFF)/16384.0)) + else + local n=0x100*a+b + return (extract(n,14,2)+(band(n,0x3FFF)/16384.0)) + end end end function files.skipshort(f,n) @@ -6099,7 +6101,8 @@ if not modules then modules={} end modules ['font-con']={ } local next,tostring,rawget=next,tostring,rawget local format,match,lower,gsub,find=string.format,string.match,string.lower,string.gsub,string.find -local sort,insert,concat,sortedkeys,serialize,fastcopy=table.sort,table.insert,table.concat,table.sortedkeys,table.serialize,table.fastcopy +local sort,insert,concat=table.sort,table.insert,table.concat +local sortedkeys,sortedhash,serialize,fastcopy=table.sortedkeys,table.sortedhash,table.serialize,table.fastcopy local derivetable=table.derive local ioflush=io.flush local trace_defining=false trackers.register("fonts.defining",function(v) trace_defining=v end) @@ -6844,20 +6847,20 @@ constructors.hashmethods=hashmethods function constructors.hashfeatures(specification) local features=specification.features if features then - local t,tn={},0 - for category,list in next,features do + local t,n={},0 + for category,list in sortedhash(features) do if next(list) then local hasher=hashmethods[category] if hasher then local hash=hasher(list) if hash then - tn=tn+1 - t[tn]=category..":"..hash + n=n+1 + t[n]=category..":"..hash end end end end - if tn>0 then + if n>0 then return concat(t," & ") end end @@ -8130,10 +8133,11 @@ local setmetatableindex=table.setmetatableindex local formatters=string.formatters local sortedkeys=table.sortedkeys local sortedhash=table.sortedhash -local stripstring=string.strip +local stripstring=string.nospaces local utf16_to_utf8_be=utf.utf16_to_utf8_be local report=logs.reporter("otf reader") local trace_cmap=false +local trace_cmap_detail=false fonts=fonts or {} local handlers=fonts.handlers or {} fonts.handlers=handlers @@ -8370,6 +8374,28 @@ local panosewidths={ [ 8]="verycondensed", [ 9]="monospaced", } +local helpers={} +readers.helpers=helpers +local function gotodatatable(f,fontdata,tag,criterium) + if criterium and f then + local datatable=fontdata.tables[tag] + if datatable then + local tableoffset=datatable.offset + setposition(f,tableoffset) + return tableoffset + end + end +end +local function setvariabledata(fontdata,tag,data) + local variabledata=fontdata.variabledata + if variabledata then + variabledata[tag]=data + else + fontdata.variabledata={ [tag]=data } + end +end +helpers.gotodatatable=gotodatatable +helpers.setvariabledata=setvariabledata local platformnames={ postscriptname=true, fullname=true, @@ -8380,13 +8406,12 @@ local platformnames={ compatiblefullname=true, } function readers.name(f,fontdata,specification) - local datatable=fontdata.tables.name - if datatable then - setposition(f,datatable.offset) + local tableoffset=gotodatatable(f,fontdata,"name",true) + if tableoffset then local format=readushort(f) local nofnames=readushort(f) local offset=readushort(f) - local start=datatable.offset+offset + local start=tableoffset+offset local namelists={ unicode={}, windows={}, @@ -8407,24 +8432,15 @@ function readers.name(f,fontdata,specification) if encoding and language then local index=readushort(f) local name=reservednames[index] - if name then - namelist[#namelist+1]={ - platform=platform, - encoding=encoding, - language=language, - name=name, - length=readushort(f), - offset=start+readushort(f), - } - else -namelist[#namelist+1]={ - platform=platform, - encoding=encoding, - language=language, - length=readushort(f), - offset=start+readushort(f), -} - end + namelist[#namelist+1]={ + platform=platform, + encoding=encoding, + language=language, + name=name, + index=index, + length=readushort(f), + offset=start+readushort(f), + } else skipshort(f,3) end @@ -8446,6 +8462,7 @@ namelist[#namelist+1]={ for i=1,#namelist do local name=namelist[i] local nametag=name.name + local index=name.index if not done[nametag or i] then local encoding=name.encoding local language=name.language @@ -8467,7 +8484,7 @@ namelist[#namelist+1]={ language=language, } end - extras[i-1]=content + extras[index]=content done[nametag or i]=true end end @@ -8527,9 +8544,8 @@ local function getname(fontdata,key) end end readers["os/2"]=function(f,fontdata) - local datatable=fontdata.tables["os/2"] - if datatable then - setposition(f,datatable.offset) + local tableoffset=gotodatatable(f,fontdata,"os/2",true) + if tableoffset then local version=readushort(f) local windowsmetrics={ version=version, @@ -8579,9 +8595,8 @@ readers["os/2"]=function(f,fontdata) end end readers.head=function(f,fontdata) - local datatable=fontdata.tables.head - if datatable then - setposition(f,datatable.offset) + local tableoffset=gotodatatable(f,fontdata,"head",true) + if tableoffset then local fontheader={ version=readfixed(f), revision=readfixed(f), @@ -8608,178 +8623,156 @@ readers.head=function(f,fontdata) fontdata.nofglyphs=0 end readers.hhea=function(f,fontdata,specification) - if specification.details then - local datatable=fontdata.tables.hhea - if datatable then - setposition(f,datatable.offset) - fontdata.horizontalheader={ - version=readfixed(f), - ascender=readfword(f), - descender=readfword(f), - linegap=readfword(f), - maxadvancewidth=readufword(f), - minleftsidebearing=readfword(f), - minrightsidebearing=readfword(f), - maxextent=readfword(f), - caretsloperise=readshort(f), - caretsloperun=readshort(f), - caretoffset=readshort(f), - reserved_1=readshort(f), - reserved_2=readshort(f), - reserved_3=readshort(f), - reserved_4=readshort(f), - metricdataformat=readshort(f), - nofmetrics=readushort(f), - } - else - fontdata.horizontalheader={ - nofmetrics=0, - } - end + local tableoffset=gotodatatable(f,fontdata,"hhea",specification.details) + if tableoffset then + fontdata.horizontalheader={ + version=readfixed(f), + ascender=readfword(f), + descender=readfword(f), + linegap=readfword(f), + maxadvancewidth=readufword(f), + minleftsidebearing=readfword(f), + minrightsidebearing=readfword(f), + maxextent=readfword(f), + caretsloperise=readshort(f), + caretsloperun=readshort(f), + caretoffset=readshort(f), + reserved_1=readshort(f), + reserved_2=readshort(f), + reserved_3=readshort(f), + reserved_4=readshort(f), + metricdataformat=readshort(f), + nofmetrics=readushort(f), + } + else + fontdata.horizontalheader={ + nofmetrics=0, + } end end readers.vhea=function(f,fontdata,specification) - if specification.details then - local datatable=fontdata.tables.vhea - if datatable then - setposition(f,datatable.offset) - local version=readfixed(f) - fontdata.verticalheader={ + local tableoffset=gotodatatable(f,fontdata,"vhea",specification.details) + if tableoffset then + fontdata.verticalheader={ + version=readfixed(f), + ascender=readfword(f), + descender=readfword(f), + linegap=readfword(f), + maxadvanceheight=readufword(f), + mintopsidebearing=readfword(f), + minbottomsidebearing=readfword(f), + maxextent=readfword(f), + caretsloperise=readshort(f), + caretsloperun=readshort(f), + caretoffset=readshort(f), + reserved_1=readshort(f), + reserved_2=readshort(f), + reserved_3=readshort(f), + reserved_4=readshort(f), + metricdataformat=readshort(f), + nofmetrics=readushort(f), + } + else + fontdata.verticalheader={ + nofmetrics=0, + } + end +end +readers.maxp=function(f,fontdata,specification) + local tableoffset=gotodatatable(f,fontdata,"maxp",specification.details) + if tableoffset then + local version=readfixed(f) + local nofglyphs=readushort(f) + fontdata.nofglyphs=nofglyphs + if version==0.5 then + fontdata.maximumprofile={ version=version, - ascender=readfword(f), - descender=readfword(f), - linegap=readfword(f), - maxadvanceheight=readufword(f), - mintopsidebearing=readfword(f), - minbottomsidebearing=readfword(f), - maxextent=readfword(f), - caretsloperise=readshort(f), - caretsloperun=readshort(f), - caretoffset=readshort(f), - reserved_1=readshort(f), - reserved_2=readshort(f), - reserved_3=readshort(f), - reserved_4=readshort(f), - metricdataformat=readshort(f), - nofmetrics=readushort(f), + nofglyphs=nofglyphs, + } + elseif version==1.0 then + fontdata.maximumprofile={ + version=version, + nofglyphs=nofglyphs, + points=readushort(f), + contours=readushort(f), + compositepoints=readushort(f), + compositecontours=readushort(f), + zones=readushort(f), + twilightpoints=readushort(f), + storage=readushort(f), + functiondefs=readushort(f), + instructiondefs=readushort(f), + stackelements=readushort(f), + sizeofinstructions=readushort(f), + componentelements=readushort(f), + componentdepth=readushort(f), } else - fontdata.verticalheader={ - nofmetrics=0, + fontdata.maximumprofile={ + version=version, + nofglyphs=0, } end end end -readers.maxp=function(f,fontdata,specification) - if specification.details then - local datatable=fontdata.tables.maxp - if datatable then - setposition(f,datatable.offset) - local version=readfixed(f) - local nofglyphs=readushort(f) - fontdata.nofglyphs=nofglyphs - if version==0.5 then - fontdata.maximumprofile={ - version=version, - nofglyphs=nofglyphs, - } - return - elseif version==1.0 then - fontdata.maximumprofile={ - version=version, - nofglyphs=nofglyphs, - points=readushort(f), - contours=readushort(f), - compositepoints=readushort(f), - compositecontours=readushort(f), - zones=readushort(f), - twilightpoints=readushort(f), - storage=readushort(f), - functiondefs=readushort(f), - instructiondefs=readushort(f), - stackelements=readushort(f), - sizeofinstructions=readushort(f), - componentelements=readushort(f), - componentdepth=readushort(f), - } - return - end - end - fontdata.maximumprofile={ - version=version, - nofglyphs=0, - } - end -end readers.hmtx=function(f,fontdata,specification) - if specification.glyphs then - local datatable=fontdata.tables.hmtx - if datatable then - setposition(f,datatable.offset) - local horizontalheader=fontdata.horizontalheader - local nofmetrics=horizontalheader.nofmetrics - local glyphs=fontdata.glyphs - local nofglyphs=fontdata.nofglyphs - local width=0 - local leftsidebearing=0 - for i=0,nofmetrics-1 do - local glyph=glyphs[i] - width=readshort(f) - leftsidebearing=readshort(f) - if width~=0 then - glyph.width=width - end + local tableoffset=gotodatatable(f,fontdata,"hmtx",specification.glyphs) + if tableoffset then + local horizontalheader=fontdata.horizontalheader + local nofmetrics=horizontalheader.nofmetrics + local glyphs=fontdata.glyphs + local nofglyphs=fontdata.nofglyphs + local width=0 + local leftsidebearing=0 + for i=0,nofmetrics-1 do + local glyph=glyphs[i] + width=readshort(f) + leftsidebearing=readshort(f) + if width~=0 then + glyph.width=width end - for i=nofmetrics,nofglyphs-1 do - local glyph=glyphs[i] - if width~=0 then - glyph.width=width - end + end + for i=nofmetrics,nofglyphs-1 do + local glyph=glyphs[i] + if width~=0 then + glyph.width=width end end end end readers.vmtx=function(f,fontdata,specification) - if specification.glyphs then - local datatable=fontdata.tables.vmtx - if datatable then - setposition(f,datatable.offset) - local verticalheader=fontdata.verticalheader - local nofmetrics=verticalheader.nofmetrics - local glyphs=fontdata.glyphs - local nofglyphs=fontdata.nofglyphs - local vheight=0 - local vdefault=verticalheader.ascender+verticalheader.descender - local topsidebearing=0 - for i=0,nofmetrics-1 do - local glyph=glyphs[i] - vheight=readshort(f) - topsidebearing=readshort(f) - if vheight~=0 and vheight~=vdefault then - glyph.vheight=vheight - end + local tableoffset=gotodatatable(f,fontdata,"vmtx",specification.glyphs) + if tableoffset then + local verticalheader=fontdata.verticalheader + local nofmetrics=verticalheader.nofmetrics + local glyphs=fontdata.glyphs + local nofglyphs=fontdata.nofglyphs + local vheight=0 + local vdefault=verticalheader.ascender+verticalheader.descender + local topsidebearing=0 + for i=0,nofmetrics-1 do + local glyph=glyphs[i] + vheight=readshort(f) + topsidebearing=readshort(f) + if vheight~=0 and vheight~=vdefault then + glyph.vheight=vheight end - for i=nofmetrics,nofglyphs-1 do - local glyph=glyphs[i] - if vheight~=0 and vheight~=vdefault then - glyph.vheight=vheight - end + end + for i=nofmetrics,nofglyphs-1 do + local glyph=glyphs[i] + if vheight~=0 and vheight~=vdefault then + glyph.vheight=vheight end end end end readers.vorg=function(f,fontdata,specification) if specification.glyphs then - local datatable=fontdata.tables.vorg - if datatable then - report("todo: %s","vorg") - end end end readers.post=function(f,fontdata,specification) - local datatable=fontdata.tables.post - if datatable then - setposition(f,datatable.offset) + local tableoffset=gotodatatable(f,fontdata,"post",true) + if tableoffset then local version=readfixed(f) fontdata.postscript={ version=version, @@ -8909,7 +8902,7 @@ formatreaders[4]=function(f,fontdata,offset) elseif startchar==0xFFFF and offset==0 then elseif offset==0xFFFF then elseif offset==0 then - if trace_cmap then + if trace_cmap_detail then report("format 4.%i segment %2i from %C upto %C at index %H",1,segment,startchar,endchar,(startchar+delta)%65536) end for unicode=startchar,endchar do @@ -8941,7 +8934,7 @@ formatreaders[4]=function(f,fontdata,offset) end else local shift=(segment-nofsegments+offset/2)-startchar - if trace_cmap then + if trace_cmap_detail then report("format 4.%i segment %2i from %C upto %C at index %H",0,segment,startchar,endchar,(startchar+delta)%65536) end for unicode=startchar,endchar do @@ -8989,7 +8982,7 @@ formatreaders[6]=function(f,fontdata,offset) local count=readushort(f) local stop=start+count-1 local nofdone=0 - if trace_cmap then + if trace_cmap_detail then report("format 6 from %C to %C",2,start,stop) end for unicode=start,stop do @@ -9022,7 +9015,7 @@ formatreaders[12]=function(f,fontdata,offset) local first=readulong(f) local last=readulong(f) local index=readulong(f) - if trace_cmap then + if trace_cmap_detail then report("format 12 from %C to %C starts at index %i",first,last,index) end for unicode=first,last do @@ -9061,7 +9054,7 @@ formatreaders[13]=function(f,fontdata,offset) local last=readulong(f) local index=readulong(f) if first0 then - ok=true - end - end - if not ok then - report("no useable unicode cmap found") + end + local ok=false + for i=1,#sequence do + local si=sequence[i] + local sp,se,sf=si[1],si[2],si[3] + if checkcmap(f,fontdata,records,sp,se,sf)>0 then + ok=true end - fontdata.cidmaps={ - version=version, - noftables=noftables, - records=records, - } - else - fontdata.cidmaps={} end + if not ok then + report("no useable unicode cmap found") + end + fontdata.cidmaps={ + version=version, + noftables=noftables, + records=records, + } + else + fontdata.cidmaps={} end end function readers.loca(f,fontdata,specification) @@ -9276,41 +9275,38 @@ function readers.svg(f,fontdata,specification) end end function readers.kern(f,fontdata,specification) - if specification.kerns then - local datatable=fontdata.tables.kern - if datatable then - setposition(f,datatable.offset) + local tableoffset=gotodatatable(f,fontdata,"kern",specification.kerns) + if tableoffset then + local version=readushort(f) + local noftables=readushort(f) + for i=1,noftables do local version=readushort(f) - local noftables=readushort(f) - for i=1,noftables do - local version=readushort(f) - local length=readushort(f) - local coverage=readushort(f) - local format=bit32.rshift(coverage,8) - if format==0 then - local nofpairs=readushort(f) - local searchrange=readushort(f) - local entryselector=readushort(f) - local rangeshift=readushort(f) - local kerns={} - local glyphs=fontdata.glyphs - for i=1,nofpairs do - local left=readushort(f) - local right=readushort(f) - local kern=readfword(f) - local glyph=glyphs[left] - local kerns=glyph.kerns - if kerns then - kerns[right]=kern - else - glyph.kerns={ [right]=kern } - end + local length=readushort(f) + local coverage=readushort(f) + local format=bit32.rshift(coverage,8) + if format==0 then + local nofpairs=readushort(f) + local searchrange=readushort(f) + local entryselector=readushort(f) + local rangeshift=readushort(f) + local kerns={} + local glyphs=fontdata.glyphs + for i=1,nofpairs do + local left=readushort(f) + local right=readushort(f) + local kern=readfword(f) + local glyph=glyphs[left] + local kerns=glyph.kerns + if kerns then + kerns[right]=kern + else + glyph.kerns={ [right]=kern } end - elseif format==2 then - report("todo: kern classes") - else - report("todo: kerns") end + elseif format==2 then + report("todo: kern classes") + else + report("todo: kerns") end end end @@ -9335,7 +9331,7 @@ function readers.math(f,fontdata,specification) reportskippedtable("math") end end -local function getinfo(maindata,sub,platformnames,rawfamilynames,metricstoo) +local function getinfo(maindata,sub,platformnames,rawfamilynames,metricstoo,instancenames) local fontdata=sub and maindata.subfonts and maindata.subfonts[sub] or maindata local names=fontdata.names local info=nil @@ -9359,6 +9355,25 @@ local function getinfo(maindata,sub,platformnames,rawfamilynames,metricstoo) if not familyname then familyname=family end if not subfamilyname then subfamilyname=subfamily end end + if platformnames then + platformnames=fontdata.platformnames + end + if instancenames then + local variabledata=fontdata.variabledata + if variabledata then + local instances=variabledata and variabledata.instances + if instances then + instancenames={} + for i=1,#instances do + instancenames[i]=lower(stripstring(instances[i].subfamily)) + end + else + instancenames=nil + end + else + instancenames=nil + end + end info={ subfontindex=fontdata.subfontindex or sub or 0, version=getname(fontdata,"version"), @@ -9386,7 +9401,8 @@ local function getinfo(maindata,sub,platformnames,rawfamilynames,metricstoo) capheight=metrics.capheight, ascender=metrics.typoascender, descender=metrics.typodescender, - platformnames=platformnames and fontdata.platformnames or nil, + platformnames=platformnames or nil, + instancenames=instancenames or nil, } if metricstoo then local keys={ @@ -9440,6 +9456,7 @@ local function loadtables(f,specification,offset) entryselector=readushort(f), rangeshift=readushort(f), tables=tables, + foundtables=false, } for i=1,fontdata.noftables do local tag=lower(stripstring(readstring(f,4))) @@ -9455,7 +9472,8 @@ local function loadtables(f,specification,offset) length=length, } end - if tables.cff then + fontdata.foundtables=sortedkeys(tables) + if tables.cff or tables.cff2 then fontdata.format="opentype" else fontdata.format="truetype" @@ -9473,17 +9491,24 @@ local function prepareglyps(fontdata) fontdata.glyphs=glyphs fontdata.mapping={} end -local function readtable(tag,f,fontdata,specification) +local function readtable(tag,f,fontdata,specification,...) local reader=readers[tag] if reader then - reader(f,fontdata,specification) + reader(f,fontdata,specification,...) end end +local variablefonts_supported=context and true or false local function readdata(f,offset,specification) local fontdata=loadtables(f,specification,offset) if specification.glyphs then prepareglyps(fontdata) end + if not variablefonts_supported then + specification.instance=nil + specification.variable=nil + specification.factors=nil + end + fontdata.temporary={} readtable("name",f,fontdata,specification) local askedname=specification.askedname if askedname then @@ -9494,6 +9519,30 @@ local function readdata(f,offset,specification) return end end + readtable("stat",f,fontdata,specification) + readtable("avar",f,fontdata,specification) + readtable("fvar",f,fontdata,specification) + if variablefonts_supported then + if not specification.factors then + local instance=specification.instance + if type(instance)=="string" then + local factors=helpers.getfactors(fontdata,instance) + specification.factors=factors + fontdata.factors=factors + fontdata.instance=instance + report("user instance: %s, factors: % t",instance,factors) + end + end + if not fontdata.factors then + if fontdata.variabledata then + local factors=helpers.getfactors(fontdata,true) + specification.factors=factors + fontdata.factors=factors + fontdata.instance=instance + report("font instance: %s, factors: % t",instance,factors) + end + end + end readtable("os/2",f,fontdata,specification) readtable("head",f,fontdata,specification) readtable("maxp",f,fontdata,specification) @@ -9503,23 +9552,22 @@ local function readdata(f,offset,specification) readtable("vmtx",f,fontdata,specification) readtable("vorg",f,fontdata,specification) readtable("post",f,fontdata,specification) + readtable("mvar",f,fontdata,specification) + readtable("hvar",f,fontdata,specification) + readtable("vvar",f,fontdata,specification) + readtable("gdef",f,fontdata,specification) readtable("cff",f,fontdata,specification) + readtable("cff2",f,fontdata,specification) readtable("cmap",f,fontdata,specification) - readtable("loca",f,fontdata,specification) - readtable("glyf",f,fontdata,specification) + readtable("loca",f,fontdata,specification) + readtable("glyf",f,fontdata,specification) readtable("colr",f,fontdata,specification) readtable("cpal",f,fontdata,specification) readtable("svg",f,fontdata,specification) readtable("kern",f,fontdata,specification) - readtable("gdef",f,fontdata,specification) readtable("gsub",f,fontdata,specification) readtable("gpos",f,fontdata,specification) readtable("math",f,fontdata,specification) - readtable("fvar",f,fontdata,specification) - readtable("hvar",f,fontdata,specification) - readtable("vvar",f,fontdata,specification) - readtable("mvar",f,fontdata,specification) - readtable("vorg",f,fontdata,specification) fontdata.locations=nil fontdata.tables=nil fontdata.cidmaps=nil @@ -9596,7 +9644,7 @@ local function loadfontdata(specification) return fontdata or {} end end -local function loadfont(specification,n) +local function loadfont(specification,n,instance) if type(specification)=="string" then specification={ filename=specification, @@ -9610,6 +9658,7 @@ local function loadfont(specification,n) lookups=true, subfont=n or true, tounicode=false, + instance=instance } end if specification.shapes or specification.lookups or specification.kerns then @@ -9624,6 +9673,10 @@ local function loadfont(specification,n) if specification.platformnames then specification.platformnames=true end + if specification.instance or instance then + specification.variable=true + specification.instance=specification.instance or instance + end local function message(str) report("fatal error in file %a: %s\n%s",specification.filename,str,debug.traceback()) end @@ -9632,11 +9685,14 @@ local function loadfont(specification,n) return result end end -function readers.loadshapes(filename,n) +function readers.loadshapes(filename,n,instance,streams) local fontdata=loadfont { filename=filename, shapes=true, + streams=streams, + variable=true, subfont=n, + instance=instance, } if fontdata then for k,v in next,fontdata.glyphs do @@ -9657,7 +9713,7 @@ function readers.loadshapes(filename,n) units=0, } end -function readers.loadfont(filename,n) +function readers.loadfont(filename,n,instance) local fontdata=loadfont { filename=filename, glyphs=true, @@ -9665,6 +9721,7 @@ function readers.loadfont(filename,n) lookups=true, variable=true, subfont=n, + instance=instance, } if fontdata then return { @@ -9676,11 +9733,13 @@ function readers.loadfont(filename,n) descriptions=fontdata.descriptions, format=fontdata.format, goodies={}, - metadata=getinfo(fontdata,n,false,false,true), + metadata=getinfo(fontdata,n,false,false,true,true), properties={ hasitalics=fontdata.hasitalics or false, maxcolorclass=fontdata.maxcolorclass, hascolor=fontdata.hascolor or false, + instance=fontdata.instance, + factors=fontdata.factors, }, resources={ filename=filename, @@ -9698,7 +9757,8 @@ function readers.loadfont(filename,n) mathconstants=fontdata.mathconstants, colorpalettes=fontdata.colorpalettes, svgshapes=fontdata.svgshapes, - variable=fontdata.variable, + variabledata=fontdata.variabledata, + foundtables=fontdata.foundtables, }, } end @@ -9707,6 +9767,7 @@ function readers.getinfo(filename,specification) local subfont=nil local platformnames=false local rawfamilynames=false + local instancenames=true if type(specification)=="table" then subfont=tonumber(specification.subfont) platformnames=specification.platformnames @@ -9718,19 +9779,20 @@ function readers.getinfo(filename,specification) filename=filename, details=true, platformnames=platformnames, + instancenames=true, } if fontdata then local subfonts=fontdata.subfonts if not subfonts then - return getinfo(fontdata,nil,platformnames,rawfamilynames) + return getinfo(fontdata,nil,platformnames,rawfamilynames,false,instancenames) elseif not subfont then local info={} for i=1,#subfonts do - info[i]=getinfo(fontdata,i,platformnames,rawfamilynames) + info[i]=getinfo(fontdata,i,platformnames,rawfamilynames,false,instancenames) end return info elseif subfont>=1 and subfont<=#subfonts then - return getinfo(fontdata,subfont,platformnames,rawfamilynames) + return getinfo(fontdata,subfont,platformnames,rawfamilynames,false,instancenames) else return { filename=filename, @@ -9789,7 +9851,7 @@ if not modules then modules={} end modules ['font-cff']={ license="see context related readme files" } local next,type,tonumber=next,type,tonumber -local byte,gmatch=string.byte,string.gmatch +local byte,char,gmatch=string.byte,string.char,string.gmatch local concat,remove=table.concat,table.remove local floor,abs,round,ceil,min,max=math.floor,math.abs,math.round,math.ceil,math.min,math.max local P,C,R,S,C,Cs,Ct=lpeg.P,lpeg.C,lpeg.R,lpeg.S,lpeg.C,lpeg.Cs,lpeg.Ct @@ -9814,6 +9876,8 @@ local parsecharstring local parsecharstrings local resetcharstrings local parseprivates +local startparsing +local stopparsing local defaultstrings={ [0]= ".notdef","space","exclam","quotedbl","numbersign","dollar","percent", "ampersand","quoteright","parenleft","parenright","asterisk","plus", @@ -9889,18 +9953,24 @@ local cffreaders={ } local function readheader(f) local offset=getposition(f) + local major=readbyte(f) local header={ offset=offset, - major=readbyte(f), + major=major, minor=readbyte(f), size=readbyte(f), - osize=readbyte(f), } + if major==1 then + header.dsize=readbyte(f) + elseif major==2 then + header.dsize=readushort(f) + else + end setposition(f,offset+header.size) return header end -local function readlengths(f) - local count=readushort(f) +local function readlengths(f,longcount) + local count=longcount and readulong(f) or readushort(f) if count==0 then return {} end @@ -9914,7 +9984,12 @@ local function readlengths(f) local previous=read(f) for i=1,count do local offset=read(f) - lengths[i]=offset-previous + local length=offset-previous + if length<0 then + report("bad offset: %i",length) + length=0 + end + lengths[i]=length previous=offset end return lengths @@ -9979,7 +10054,7 @@ do end+P("\16")/function() result.encoding=stack[top] top=0 - end+P("\17")/function() + end+P("\17")/function() result.charstrings=stack[top] top=0 end+P("\18")/function() @@ -9990,10 +10065,20 @@ do top=0 end+P("\19")/function() result.subroutines=stack[top] + top=0 end+P("\20")/function() result.defaultwidthx=stack[top] + top=0 end+P("\21")/function() result.nominalwidthx=stack[top] + top=0 + end ++P("\24")/function() + result.vstore=stack[top] + top=0 + end+P("\25")/function() + result.maxstack=stack[top] + top=0 end local p_double=P("\12")*( P("\00")/function() @@ -10017,7 +10102,7 @@ do end+P("\06")/function() result.charstringtype=stack[top] top=0 - end+P("\07")/function() + end+P("\07")/function() result.fontmatrix={ unpack(stack,1,6) } top=0 end+P("\08")/function() @@ -10055,10 +10140,10 @@ do end+P("\35")/function() result.cid.uidbase=stack[top] top=0 - end+P("\36")/function() + end+P("\36")/function() result.cid.fdarray=stack[top] top=0 - end+P("\37")/function() + end+P("\37")/function() result.cid.fdselect=stack[top] top=0 end+P("\38")/function() @@ -10123,12 +10208,12 @@ do local p_dictionary=( p_byte+p_positive+p_negative+p_short+p_long+p_nibbles+p_single+p_double+p_unsupported )^1 - parsedictionaries=function(data,dictionaries) + parsedictionaries=function(data,dictionaries,what) stack={} strings=data.strings for i=1,#dictionaries do top=0 - result={ + result=what=="cff" and { monospaced=false, italicangle=0, underlineposition=-100, @@ -10146,6 +10231,12 @@ do fonttype=0, count=8720, } + } or { + charstringtype=2, + charset=0, + vstore=0, + cid={ + }, } lpegmatch(p_dictionary,dictionaries[i]) dictionaries[i]=result @@ -10187,6 +10278,9 @@ do local stems=0 local globalbias=0 local localbias=0 + local nominalwidth=0 + local defaultwidth=0 + local charset=false local globals=false local locals=false local depth=1 @@ -10197,6 +10291,12 @@ do local checked=false local keepcurve=false local version=2 + local regions=false + local nofregions=0 + local region=false + local factors=false + local axis=false + local vsindex=0 local function showstate(where) report("%w%-10s : [%s] n=%i",depth*2,where,concat(stack," ",1,top),top) end @@ -10207,7 +10307,7 @@ do report("%w%-10s : %s",depth*2,where,tostring(value)) end end - local function moveto() + local function xymoveto() if keepcurve then r=r+1 result[r]={ x,y,"m" } @@ -10257,7 +10357,14 @@ do ymin=y end end - local function lineto() + local function moveto() + if trace_charstrings then + showstate("moveto") + end + top=0 + xymoveto() + end + local function xylineto() if keepcurve then r=r+1 result[r]={ x,y,"l" } @@ -10307,7 +10414,10 @@ do ymin=y end end - local function curveto(x1,y1,x2,y2,x3,y3) + local function xycurveto(x1,y1,x2,y2,x3,y3) + if trace_charstrings then + showstate("curveto") + end if keepcurve then r=r+1 result[r]={ x1,y1,x2,y2,x3,y3,"c" } @@ -10332,7 +10442,7 @@ do if top>2 then width=stack[1] if trace_charstrings then - showvalue("width",width) + showvalue("backtrack width",width) end else width=true @@ -10344,14 +10454,14 @@ do x=x+stack[top-1] y=y+stack[top] top=0 - moveto() + xymoveto() end local function hmoveto() if not width then if top>1 then width=stack[1] if trace_charstrings then - showvalue("width",width) + showvalue("backtrack width",width) end else width=true @@ -10369,7 +10479,7 @@ do if top>1 then width=stack[1] if trace_charstrings then - showvalue("width",width) + showvalue("backtrack width",width) end else width=true @@ -10389,7 +10499,7 @@ do for i=1,top,2 do x=x+stack[i] y=y+stack[i+1] - lineto() + xylineto() end top=0 end @@ -10450,7 +10560,7 @@ do local by=ay+stack[i+3] x=bx+stack[i+4] y=by+stack[i+5] - curveto(ax,ay,bx,by,x,y) + xycurveto(ax,ay,bx,by,x,y) end top=0 end @@ -10470,7 +10580,7 @@ do local by=ay+stack[i+2] x=bx+stack[i+3] y=by - curveto(ax,ay,bx,by,x,y) + xycurveto(ax,ay,bx,by,x,y) end top=0 end @@ -10491,7 +10601,7 @@ do local by=ay+stack[i+2] x=bx y=by+stack[i+3] - curveto(ax,ay,bx,by,x,y) + xycurveto(ax,ay,bx,by,x,y) d=0 end top=0 @@ -10528,7 +10638,7 @@ do end swap=true end - curveto(ax,ay,bx,by,x,y) + xycurveto(ax,ay,bx,by,x,y) end top=0 end @@ -10555,11 +10665,11 @@ do local by=ay+stack[i+3] x=bx+stack[i+4] y=by+stack[i+5] - curveto(ax,ay,bx,by,x,y) + xycurveto(ax,ay,bx,by,x,y) end x=x+stack[top-1] y=y+stack[top] - lineto() + xylineto() top=0 end local function rlinecurve() @@ -10570,7 +10680,7 @@ do for i=1,top-6,2 do x=x+stack[i] y=y+stack[i+1] - lineto() + xylineto() end end local ax=x+stack[top-5] @@ -10579,7 +10689,7 @@ do local by=ay+stack[top-2] x=bx+stack[top-1] y=by+stack[top] - curveto(ax,ay,bx,by,x,y) + xycurveto(ax,ay,bx,by,x,y) top=0 end local function flex() @@ -10592,14 +10702,14 @@ do local by=ay+stack[4] local cx=bx+stack[5] local cy=by+stack[6] - curveto(ax,ay,bx,by,cx,cy) + xycurveto(ax,ay,bx,by,cx,cy) local dx=cx+stack[7] local dy=cy+stack[8] local ex=dx+stack[9] local ey=dy+stack[10] x=ex+stack[11] y=ey+stack[12] - curveto(dx,dy,ex,ey,x,y) + xycurveto(dx,dy,ex,ey,x,y) top=0 end local function hflex() @@ -10612,13 +10722,13 @@ do local by=ay+stack[3] local cx=bx+stack[4] local cy=by - curveto(ax,ay,bx,by,cx,cy) + xycurveto(ax,ay,bx,by,cx,cy) local dx=cx+stack[5] local dy=by local ex=dx+stack[6] local ey=y x=ex+stack[7] - curveto(dx,dy,ex,ey,x,y) + xycurveto(dx,dy,ex,ey,x,y) top=0 end local function hflex1() @@ -10631,13 +10741,13 @@ do local by=ay+stack[4] local cx=bx+stack[5] local cy=by - curveto(ax,ay,bx,by,cx,cy) + xycurveto(ax,ay,bx,by,cx,cy) local dx=cx+stack[6] local dy=by local ex=dx+stack[7] local ey=dy+stack[8] x=ex+stack[9] - curveto(dx,dy,ex,ey,x,y) + xycurveto(dx,dy,ex,ey,x,y) top=0 end local function flex1() @@ -10650,7 +10760,7 @@ do local by=ay+stack[4] local cx=bx+stack[5] local cy=by+stack[6] - curveto(ax,ay,bx,by,cx,cy) + xycurveto(ax,ay,bx,by,cx,cy) local dx=cx+stack[7] local dy=cy+stack[8] local ex=dx+stack[9] @@ -10660,7 +10770,7 @@ do else y=ey+stack[11] end - curveto(dx,dy,ex,ey,x,y) + xycurveto(dx,dy,ex,ey,x,y) top=0 end local function getstem() @@ -10795,6 +10905,92 @@ do end top=0 end + local reginit=false + local function updateregions(n) + if regions then + local current=regions[n] or regions[1] + nofregions=#current + if axis and n~=reginit then + factors={} + for i=1,nofregions do + local region=current[i] + local s=1 + for j=1,#axis do + local f=axis[j] + local r=region[j] + local start=r.start + local peak=r.peak + local stop=r.stop + if start>peak or peak>stop then + elseif start<0 and stop>0 and peak~=0 then + elseif peak==0 then + elseif fstop then + s=0 + break + elseif fpeak then + s=s*(stop-f)/(stop-peak) + else + end + end + factors[i]=s + end + end + end + reginit=n + end + local function setvsindex() + local vsindex=stack[top] + if trace_charstrings then + showstate(formatters["vsindex %i"](vsindex)) + end + updateregions(vsindex) + top=top-1 + end + local function blend() + local n=stack[top] + top=top-1 + if axis then + if trace_charstrings then + local t=top-nofregions*n + local m=t-n + for i=1,n do + local k=m+i + local d=m+n+(i-1)*nofregions + local old=stack[k] + local new=old + for r=1,nofregions do + new=new+stack[d+r]*factors[r] + end + stack[k]=new + showstate(formatters["blend %i of %i: %s -> %s"](i,n,old,new)) + end + top=t + elseif n==1 then + top=top-nofregions + local v=stack[top] + for r=1,nofregions do + v=v+stack[top+r]*factors[r] + end + stack[top]=v + else + top=top-nofregions*n + local d=top + local k=top-n + for i=1,n do + k=k+1 + local v=stack[k] + for r=1,nofregions do + v=v+stack[d+r]*factors[r] + end + stack[k]=v + d=d+nofregions + end + end + else + end + end local actions={ [0]=unsupported, getstem, unsupported, @@ -10810,8 +11006,8 @@ do unsupported, hsbw, unsupported, - unsupported, - unsupported, + setvsindex, + blend, unsupported, getstem, getmask, @@ -10843,14 +11039,86 @@ do [036]=hflex1, [037]=flex1, } - local process - local function call(scope,list,bias) - depth=depth+1 - if top==0 then - showstate(formatters["unknown %s call"](scope)) - top=0 - else - local index=stack[top]+bias + local c_endchar=char(14) + local passon do + local rshift=bit32.rshift + local band=bit32.band + local round=math.round + local encode=table.setmetatableindex(function(t,i) + for i=-2048,-1130 do + t[i]=char(28,band(rshift(i,8),0xFF),band(i,0xFF)) + end + for i=-1131,-108 do + local v=0xFB00-i-108 + t[i]=char(band(rshift(v,8),0xFF),band(v,0xFF)) + end + for i=-107,107 do + t[i]=char(i+139) + end + for i=108,1131 do + local v=0xF700+i-108 + t[i]=char(band(rshift(v,8),0xFF),band(v,0xFF)) + end + for i=1132,2048 do + t[i]=char(28,band(rshift(i,8),0xFF),band(i,0xFF)) + end + return t[i] + end) + local function setvsindex() + local vsindex=stack[top] + updateregions(vsindex) + top=top-1 + end + local function blend() + local n=stack[top] + top=top-1 + if not axis then + elseif n==1 then + top=top-nofregions + local v=stack[top] + for r=1,nofregions do + v=v+stack[top+r]*factors[r] + end + stack[top]=round(v) + else + top=top-nofregions*n + local d=top + local k=top-n + for i=1,n do + k=k+1 + local v=stack[k] + for r=1,nofregions do + v=v+stack[d+r]*factors[r] + end + stack[k]=round(v) + d=d+nofregions + end + end + end + passon=function(operation) + if operation==15 then + setvsindex() + elseif operation==16 then + blend() + else + for i=1,top do + r=r+1 + result[r]=encode[stack[i]] + end + r=r+1 + result[r]=char(operation) + top=0 + end + end + end + local process + local function call(scope,list,bias) + depth=depth+1 + if top==0 then + showstate(formatters["unknown %s call"](scope)) + top=0 + else + local index=stack[top]+bias top=top-1 if trace_charstrings then showvalue(scope,index,true) @@ -10865,6 +11133,7 @@ do end depth=depth-1 end + local justpass=false process=function(tab) local i=1 local n=#tab @@ -10902,7 +11171,7 @@ do stack[top]=n end i=i+3 - elseif t==11 then + elseif t==11 then if trace_charstrings then showstate("return") end @@ -10940,6 +11209,9 @@ do top=0 end i=i+1 + elseif justpass then + passon(t) + i=i+1 else local a=actions[t] if a then @@ -10969,89 +11241,7 @@ do ((l<1240 and 107) or (l<33900 and 1131) or 32768)+1 end end - parsecharstrings=function(data,glyphs,doshapes,tversion) - local dictionary=data.dictionaries[1] - local charstrings=dictionary.charstrings - local charset=dictionary.charset - local private=dictionary.private or { data={} } - keepcurve=doshapes - version=tversion - stack={} - glyphs=glyphs or {} - strings=data.strings - globals=data.routines or {} - locals=dictionary.subroutines or {} - globalbias,localbias=setbias(globals,locals) - local nominalwidth=private.data.nominalwidthx or 0 - local defaultwidth=private.data.defaultwidthx or 0 - for i=1,#charstrings do - local tab=bytetable(charstrings[i]) - local index=i-1 - x=0 - y=0 - width=false - r=0 - top=0 - stems=0 - result={} - xmin=0 - xmax=0 - ymin=0 - ymax=0 - checked=false - if trace_charstrings then - report("glyph: %i",index) - report("data: % t",tab) - end - process(tab) - local boundingbox={ round(xmin),round(ymin),round(xmax),round(ymax) } - if width==true or width==false then - width=defaultwidth - else - width=nominalwidth+width - end - local glyph=glyphs[index] - if glyph then - glyph.segments=doshapes~=false and result or nil - glyph.boundingbox=boundingbox - if not glyph.width then - glyph.width=width - end - if charset and not glyph.name then - glyph.name=charset[index] - end - elseif doshapes then - glyphs[index]={ - segments=result, - boundingbox=boundingbox, - width=width, - name=charset[index], - } - else - glyphs[index]={ - boundingbox=boundingbox, - width=width, - name=charset[index], - } - end - if trace_charstrings then - report("width: %s",tostring(width)) - report("boundingbox: % t",boundingbox) - end - charstrings[i]=nil - end - return glyphs - end - parsecharstring=function(data,dictionary,tab,glyphs,index,doshapes,tversion) - local private=dictionary.private - keepcurve=doshapes - version=tversion - strings=data.strings - locals=dictionary.subroutines or {} - globals=data.routines or {} - globalbias,localbias=setbias(globals,locals) - local nominalwidth=private and private.data.nominalwidthx or 0 - local defaultwidth=private and private.data.defaultwidthx or 0 + local function processshape(tab,index) tab=bytetable(tab) x=0 y=0 @@ -11059,7 +11249,7 @@ do r=0 top=0 stems=0 - result={} + result={} xmin=0 xmax=0 ymin=0 @@ -11067,19 +11257,33 @@ do checked=false if trace_charstrings then report("glyph: %i",index) - report("data: % t",tab) + report("data : % t",tab) end + updateregions(vsindex) process(tab) - local boundingbox={ xmin,ymin,xmax,ymax } + local boundingbox={ + round(xmin), + round(ymin), + round(xmax), + round(ymax), + } if width==true or width==false then width=defaultwidth else width=nominalwidth+width end - index=index-1 local glyph=glyphs[index] - if glyph then - glyph.segments=doshapes~=false and result or nil + if justpass then + r=r+1 + result[r]=c_endchar + local stream=concat(result) + if glyph then + glyph.stream=stream + else + glyphs[index]={ stream=stream } + end + elseif glyph then + glyph.segments=keepcurve~=false and result or nil glyph.boundingbox=boundingbox if not glyph.width then glyph.width=width @@ -11087,29 +11291,87 @@ do if charset and not glyph.name then glyph.name=charset[index] end - elseif doshapes then + elseif keepcurve then glyphs[index]={ segments=result, boundingbox=boundingbox, width=width, - name=charset[index], + name=charset and charset[index] or nil, } else glyphs[index]={ boundingbox=boundingbox, width=width, - name=charset[index], + name=charset and charset[index] or nil, } end if trace_charstrings then - report("width: %s",tostring(width)) + report("width : %s",tostring(width)) report("boundingbox: % t",boundingbox) end end - resetcharstrings=function() + startparsing=function(fontdata,data,streams) + reginit=false + axis=false + regions=data.regions + justpass=streams==true + if regions then + regions={ regions } + axis=data.factors or false + end + end + stopparsing=function(fontdata,data) + stack={} + glyphs=false result={} top=0 - stack={} + locals=false + globals=false + strings=false + end + local function setwidths(private) + if not private then + return 0,0 + end + local privatedata=private.data + if not privatedata then + return 0,0 + end + return privatedata.nominalwidthx or 0,privatedata.defaultwidthx or 0 + end + parsecharstrings=function(fontdata,data,glphs,doshapes,tversion,streams) + local dictionary=data.dictionaries[1] + local charstrings=dictionary.charstrings + keepcurve=doshapes + version=tversion + strings=data.strings + globals=data.routines or {} + locals=dictionary.subroutines or {} + charset=dictionary.charset + vsindex=dictionary.vsindex or 0 + glyphs=glphs or {} + globalbias,localbias=setbias(globals,locals) + nominalwidth,defaultwidth=setwidths(dictionary.private) + startparsing(fontdata,data,streams) + for index=1,#charstrings do + processshape(charstrings[index],index-1) + charstrings[index]=nil + end + stopparsing(fontdata,data) + return glyphs + end + parsecharstring=function(fontdata,data,dictionary,tab,glphs,index,doshapes,tversion) + keepcurve=doshapes + version=tversion + strings=data.strings + globals=data.routines or {} + locals=dictionary.subroutines or {} + charset=false + vsindex=dictionary.vsindex or 0 + glyphs=glphs or {} + globalbias,localbias=setbias(globals,locals) + nominalwidth,defaultwidth=setwidths(dictionary.private) + processshape(tab,index-1) end end local function readglobals(f,data) @@ -11127,7 +11389,7 @@ local function readcharsets(f,data,dictionary) local strings=data.strings local nofglyphs=data.nofglyphs local charsetoffset=dictionary.charset - if charsetoffset~=0 then + if charsetoffset and charsetoffset~=0 then setposition(f,header.offset+charsetoffset) local format=readbyte(f) local charset={ [0]=".notdef" } @@ -11153,6 +11415,9 @@ local function readcharsets(f,data,dictionary) else report("cff parser: unsupported charset format %a",format) end + else + dictionary.nocharset=true + dictionary.charset=nil end end local function readprivates(f,data) @@ -11184,15 +11449,16 @@ local function readlocals(f,data,dictionary) dictionary.subroutines={} end end -local function readcharstrings(f,data) +local function readcharstrings(f,data,what) local header=data.header local dictionaries=data.dictionaries local dictionary=dictionaries[1] - local type=dictionary.charstringtype + local stringtype=dictionary.charstringtype local offset=dictionary.charstrings - if type==2 then + if type(offset)~="number" then + elseif stringtype==2 then setposition(f,header.offset+offset) - local charstrings=readlengths(f) + local charstrings=readlengths(f,what=="cff2") local nofglyphs=#charstrings for i=1,nofglyphs do charstrings[i]=readstring(f,charstrings[i]) @@ -11200,7 +11466,7 @@ local function readcharstrings(f,data) data.nofglyphs=nofglyphs dictionary.charstrings=charstrings else - report("unsupported charstr type %i",type) + report("unsupported charstr type %i",stringtype) data.nofglyphs=0 dictionary.charstrings={} end @@ -11218,29 +11484,34 @@ local function readcidprivates(f,data) end parseprivates(data,dictionaries) end -local function readnoselect(f,data,glyphs,doshapes,version) +readers.parsecharstrings=parsecharstrings +local function readnoselect(f,fontdata,data,glyphs,doshapes,version,streams) local dictionaries=data.dictionaries local dictionary=dictionaries[1] readglobals(f,data) - readcharstrings(f,data) - readencodings(f,data) - readcharsets(f,data,dictionary) + readcharstrings(f,data,version) + if version~="cff2" then + readencodings(f,data) + readcharsets(f,data,dictionary) + end readprivates(f,data) parseprivates(data,data.dictionaries) readlocals(f,data,dictionary) - parsecharstrings(data,glyphs,doshapes,version) - resetcharstrings() + startparsing(fontdata,data,streams) + parsecharstrings(fontdata,data,glyphs,doshapes,version,streams) + stopparsing(fontdata,data) end -readers.parsecharstrings=parsecharstrings -local function readfdselect(f,data,glyphs,doshapes,version) +local function readfdselect(f,fontdata,data,glyphs,doshapes,version,streams) local header=data.header local dictionaries=data.dictionaries local dictionary=dictionaries[1] local cid=dictionary.cid local cidselect=cid and cid.fdselect readglobals(f,data) - readcharstrings(f,data) - readencodings(f,data) + readcharstrings(f,data,version) + if version~="cff2" then + readencodings(f,data) + end local charstrings=dictionary.charstrings local fdindex={} local nofglyphs=data.nofglyphs @@ -11289,69 +11560,127 @@ local function readfdselect(f,data,glyphs,doshapes,version) for i=1,#dictionaries do readlocals(f,data,dictionaries[i]) end + startparsing(fontdata,data,streams) for i=1,#charstrings do - parsecharstring(data,dictionaries[fdindex[i]+1],charstrings[i],glyphs,i,doshapes,version) + parsecharstring(fontdata,data,dictionaries[fdindex[i]+1],charstrings[i],glyphs,i,doshapes,version) charstrings[i]=nil end - resetcharstrings() + stopparsing(fontdata,data) end end +local gotodatatable=readers.helpers.gotodatatable +local function cleanup(data,dictionaries) +end function readers.cff(f,fontdata,specification) - if specification.details then - local datatable=fontdata.tables.cff - if datatable then - local offset=datatable.offset - local glyphs=fontdata.glyphs - if not f then - report("invalid filehandle") - return - end - if offset then - setposition(f,offset) - end - local header=readheader(f) - if header.major>1 then - report("version mismatch") - return - end - local names=readfontnames(f) - local dictionaries=readtopdictionaries(f) - local strings=readstrings(f) - local data={ - header=header, - names=names, - dictionaries=dictionaries, - strings=strings, - nofglyphs=fontdata.nofglyphs, - } - parsedictionaries(data,data.dictionaries) - local d=dictionaries[1] - local c=d.cid - fontdata.cffinfo={ - familynamename=d.familyname, - fullname=d.fullname, - boundingbox=d.boundingbox, - weight=d.weight, - italicangle=d.italicangle, - underlineposition=d.underlineposition, - underlinethickness=d.underlinethickness, - monospaced=d.monospaced, - } - fontdata.cidinfo=c and { - registry=c.registry, - ordering=c.ordering, - supplement=c.supplement, - } - if not specification.glyphs then + local tableoffset=gotodatatable(f,fontdata,"cff",specification.details) + if tableoffset then + local header=readheader(f) + if header.major~=1 then + report("only version %s is supported for table %a",1,"cff") + return + end + local glyphs=fontdata.glyphs + local names=readfontnames(f) + local dictionaries=readtopdictionaries(f) + local strings=readstrings(f) + local data={ + header=header, + names=names, + dictionaries=dictionaries, + strings=strings, + nofglyphs=fontdata.nofglyphs, + } + parsedictionaries(data,dictionaries,"cff") + local dic=dictionaries[1] + local cid=dic.cid + fontdata.cffinfo={ + familynamename=dic.familyname, + fullname=dic.fullname, + boundingbox=dic.boundingbox, + weight=dic.weight, + italicangle=dic.italicangle, + underlineposition=dic.underlineposition, + underlinethickness=dic.underlinethickness, + monospaced=dic.monospaced, + } + fontdata.cidinfo=cid and { + registry=cid.registry, + ordering=cid.ordering, + supplement=cid.supplement, + } + if specification.glyphs then + local all=specification.shapes or false + if cid and cid.fdselect then + readfdselect(f,fontdata,data,glyphs,all,"cff") else - local cid=d.cid - if cid and cid.fdselect then - readfdselect(f,data,glyphs,specification.shapes or false) - else - readnoselect(f,data,glyphs,specification.shapes or false) - end + readnoselect(f,fontdata,data,glyphs,all,"cff") end end + cleanup(data,dictionaries) + end +end +function readers.cff2(f,fontdata,specification) + local tableoffset=gotodatatable(f,fontdata,"cff2",specification.glyphs) + if tableoffset then + local header=readheader(f) + if header.major~=2 then + report("only version %s is supported for table %a",2,"cff2") + return + end + local glyphs=fontdata.glyphs + local dictionaries={ readstring(f,header.dsize) } + local data={ + header=header, + dictionaries=dictionaries, + nofglyphs=fontdata.nofglyphs, + } + parsedictionaries(data,dictionaries,"cff2") + local storeoffset=dictionaries[1].vstore+data.header.offset+2 + local regions,deltas=readers.helpers.readvariationdata(f,storeoffset,factors) + data.regions=regions + data.deltas=deltas + data.factors=specification.factors + local cid=data.dictionaries[1].cid + local all=specification.shapes or false + if cid and cid.fdselect then + readfdselect(f,fontdata,data,glyphs,all,"cff2",specification.streams) + else + readnoselect(f,fontdata,data,glyphs,all,"cff2",specification.streams) + end + cleanup(data,dictionaries) + end +end +function readers.cffcheck(filename) + local f=io.open(filename,"rb") + if f then + local fontdata={ + glyphs={}, + } + local header=readheader(f) + if header.major~=1 then + report("only version %s is supported for table %a",1,"cff") + return + end + local names=readfontnames(f) + local dictionaries=readtopdictionaries(f) + local strings=readstrings(f) + local glyphs={} + local data={ + header=header, + names=names, + dictionaries=dictionaries, + strings=strings, + glyphs=glyphs, + nofglyphs=4, + } + parsedictionaries(data,dictionaries,"cff") + local cid=data.dictionaries[1].cid + if cid and cid.fdselect then + readfdselect(f,fontdata,data,glyphs,false) + else + readnoselect(f,fontdata,data,glyphs,false) + end + return data end end @@ -11367,8 +11696,10 @@ if not modules then modules={} end modules ['font-ttf']={ license="see context related readme files" } local next,type,unpack=next,type,unpack -local bittest=bit32.btest -local sqrt=math.sqrt +local bittest,band,rshift=bit32.btest,bit32.band,bit32.rshift +local sqrt,round=math.sqrt,math.round +local char=string.char +local concat=table.concat local report=logs.reporter("otf reader","ttf") local readers=fonts.handlers.otf.readers local streamreader=readers.streamreader @@ -11381,22 +11712,30 @@ local readulong=streamreader.readcardinal4 local readchar=streamreader.readinteger1 local readshort=streamreader.readinteger2 local read2dot14=streamreader.read2dot14 +local readinteger=streamreader.readinteger1 +local helpers=readers.helpers +local gotodatatable=helpers.gotodatatable local function mergecomposites(glyphs,shapes) 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=merge(subindex,subshape,subcomponents) + subcontours,subpoints=merge(subindex,subshape,subcomponents) end end - if subcontours then + if subpoints then local matrix=component.matrix local xscale=matrix[1] local xrotate=matrix[2] @@ -11404,35 +11743,38 @@ local function mergecomposites(glyphs,shapes) local yscale=matrix[4] local xoffset=matrix[5] local yoffset=matrix[6] + for i=1,#subpoints 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 for i=1,#subcontours do - local points=subcontours[i] - local result={} - for i=1,#points do - local p=points[i] - local x=p[1] - local y=p[2] - result[i]={ - xscale*x+xrotate*y+xoffset, - yscale*y+yrotate*x+yoffset, - p[3] - } - end nofcontours=nofcontours+1 - contours[nofcontours]=result + contours[nofcontours]=offset+subcontours[i] end + offset=offset+#subpoints else report("missing contours composite %s, component %s of %s, glyph %s",index,i,#components,subindex) end end + shape.points=points shape.contours=contours shape.components=nil - return contours + return contours,points end for index=1,#glyphs do local shape=shapes[index] - local components=shape.components - if components then - merge(index,shape,components) + if shape then + local components=shape.components + if components then + merge(index,shape,components) + end end end end @@ -11448,117 +11790,475 @@ local function curveto(m_x,m_y,l_x,l_y,r_x,r_y) r_x,r_y,"c" } end -local function contours2outlines(glyphs,shapes) +local function applyaxis(glyph,shape,points,deltas) + if points then + local nofpoints=#points + 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 + 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[d] + if x and x~=0 then + p[1]=p[1]+factor*x + end + end + if yvalues then + local y=yvalues[d] + if y and y~=0 then + p[2]=p[2]+factor*y + end + end + elseif width then + 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 + end + end + end +end +local function contours2outlines_normal(glyphs,shapes) local quadratic=true for index=1,#glyphs do - local glyph=glyphs[index] local shape=shapes[index] - local contours=shape.contours - if contours then - local nofcontours=#contours - local segments={} - local nofsegments=0 - glyph.segments=segments - if nofcontours>0 then - for i=1,nofcontours do - local contour=contours[i] - local nofcontour=#contour - if nofcontour>0 then - local first_pt=contour[1] - local first_on=first_pt[3] - if nofcontour==1 then - first_pt[3]="m" - nofsegments=nofsegments+1 - segments[nofsegments]=first_pt - else + 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,py=0,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] - local last_pt=contour[nofcontour] - 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 + if first==last then + first_pt[3]="m" + nofsegments=nofsegments+1 + segments[nofsegments]=first_pt + else + 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 - first_pt={ (first_pt[1]+last_pt[1])/2,(first_pt[2]+last_pt[2])/2,false } + 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 - control_pt=first_pt - end - nofsegments=nofsegments+1 - segments[nofsegments]={ first_pt[1],first_pt[2],"m" } - local previous_pt=first_pt - for i=start,nofcontour do - local current_pt=contour[i] - local current_on=current_pt[3] - local previous_on=previous_pt[3] - if previous_on then - if current_on then + local x,y=first_pt[1],first_pt[2] + if not done then + xmin,ymin,xmax,ymax=x,y,x,y + done=true + end + nofsegments=nofsegments+1 + segments[nofsegments]={ x,y,"m" } + if not quadratic then + px,py=x,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 + local x,y=current_pt[1],current_pt[2] + nofsegments=nofsegments+1 + segments[nofsegments]={ x,y,"l" } + if not quadratic then + px,py=x,y + end + else + control_pt=current_pt + end + elseif current_on then + local x1,y1=control_pt[1],control_pt[2] + local x2,y2=current_pt[1],current_pt[2] nofsegments=nofsegments+1 - segments[nofsegments]={ current_pt[1],current_pt[2],"l" } + if quadratic then + segments[nofsegments]={ x1,y1,x2,y2,"q" } + else + x1,x2,x2,y2,px,py=curveto(x1,x2,px,py,x2,y2) + segments[nofsegments]={ x1,y1,x2,y2,px,py,"c" } + end + control_pt=false else + local x2,y2=(previous_pt[1]+current_pt[1])/2,(previous_pt[2]+current_pt[2])/2 + local x1,y1=control_pt[1],control_pt[2] + nofsegments=nofsegments+1 + if quadratic then + segments[nofsegments]={ x1,y1,x2,y2,"q" } + else + x1,x2,x2,y2,px,py=curveto(x1,x2,px,py,x2,y2) + segments[nofsegments]={ x1,y1,x2,y2,px,py,"c" } + end control_pt=current_pt end - elseif current_on then - local ps=segments[nofsegments] + previous_pt=current_pt + end + if first_pt==last_pt then + else nofsegments=nofsegments+1 - if quadratic then - segments[nofsegments]={ control_pt[1],control_pt[2],current_pt[1],current_pt[2],"q" } + if not control_pt then + segments[nofsegments]={ first_pt[1],first_pt[2],"l" } + elseif quadratic then + local x1,y1=control_pt[1],control_pt[2] + segments[nofsegments]={ x1,y1,first_pt[1],first_pt[2],"q" } else - local p=segments[nofsegments-1] local n=#p - segments[nofsegments]=curveto(control_pt[1],control_pt[2],p[n-2],p[n-1],current_pt[1],current_pt[2]) + local x1,y1=control_pt[1],control_pt[2] + local x2,y2=first_pt[1],first_pt[2] + x1,x2,x2,y2,px,py=curveto(x1,x2,px,py,x2,y2) + segments[nofsegments]={ x1,y1,y2,y2,px,py,"c" } end - control_pt=false + end + end + end + first=last+1 + end + end + end + end + end +end +local function contours2outlines_shaped(glyphs,shapes,keepcurve) + local quadratic=true + for index=1,#glyphs 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 + 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] + if first==last then + if keepcurve then + first_pt[3]="m" + nofsegments=nofsegments+1 + segments[nofsegments]=first_pt + end + else + 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,y=first_pt[1],first_pt[2] + if not done then + xmin,ymin,xmax,ymax=x,y,x,y + done=true else + if xxmax then xmax=x end + if yymax then ymax=y end + end + if keepcurve then nofsegments=nofsegments+1 - local halfway_x=(previous_pt[1]+current_pt[1])/2 - local halfway_y=(previous_pt[2]+current_pt[2])/2 - if quadratic then - segments[nofsegments]={ control_pt[1],control_pt[2],halfway_x,halfway_y,"q" } + segments[nofsegments]={ x,y,"m" } + end + if not quadratic then + px,py=x,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 + local x,y=current_pt[1],current_pt[2] + if xxmax then xmax=x end + if yymax then ymax=y end + if keepcurve then + nofsegments=nofsegments+1 + segments[nofsegments]={ x,y,"l" } + end + if not quadratic then + px,py=x,y + end + else + control_pt=current_pt + end + elseif current_on then + local x1,y1=control_pt[1],control_pt[2] + local x2,y2=current_pt[1],current_pt[2] + if quadratic then + if x1xmax then xmax=x1 end + if y1ymax then ymax=y1 end + if keepcurve then + nofsegments=nofsegments+1 + segments[nofsegments]={ x1,y1,x2,y2,"q" } + end + else + x1,x2,x2,y2,px,py=curveto(x1,x2,px,py,x2,y2) + if x1xmax then xmax=x1 end + if y1ymax then ymax=y1 end + if x2xmax then xmax=x2 end + if y2ymax then ymax=y2 end + if pxxmax then xmax=px end + if pyymax then ymax=py end + if keepcurve then + nofsegments=nofsegments+1 + segments[nofsegments]={ x1,y1,x2,y2,px,py,"c" } + end + end + control_pt=false else - local p=segments[nofsegments-1] local n=#p - segments[nofsegments]=curveto(control_pt[1],control_pt[2],p[n-2],p[n-1],halfway_x,halfway_y) + local x2,y2=(previous_pt[1]+current_pt[1])/2,(previous_pt[2]+current_pt[2])/2 + local x1,y1=control_pt[1],control_pt[2] + if quadratic then + if x1xmax then xmax=x1 end + if y1ymax then ymax=y1 end + if keepcurve then + nofsegments=nofsegments+1 + segments[nofsegments]={ x1,y1,x2,y2,"q" } + end + else + x1,x2,x2,y2,px,py=curveto(x1,x2,px,py,x2,y2) + if x1xmax then xmax=x1 end + if y1ymax then ymax=y1 end + if x2xmax then xmax=x2 end + if y2ymax then ymax=y2 end + if pxxmax then xmax=px end + if pyymax then ymax=py end + if keepcurve then + nofsegments=nofsegments+1 + segments[nofsegments]={ x1,y1,x2,y2,px,py,"c" } + end + end + control_pt=current_pt end - control_pt=current_pt + previous_pt=current_pt end - previous_pt=current_pt - end - if first_pt==last_pt then - else - nofsegments=nofsegments+1 - if not control_pt then - segments[nofsegments]={ first_pt[1],first_pt[2],"l" } + if first_pt==last_pt then + elseif not control_pt then + if keepcurve then + nofsegments=nofsegments+1 + segments[nofsegments]={ first_pt[1],first_pt[2],"l" } + end elseif quadratic then - segments[nofsegments]={ control_pt[1],control_pt[2],first_pt[1],first_pt[2],"q" } + local x1,y1=control_pt[1],control_pt[2] + if x1xmax then xmax=x1 end + if y1ymax then ymax=y1 end + if keepcurve then + nofsegments=nofsegments+1 + segments[nofsegments]={ x1,y1,first_pt[1],first_pt[2],"q" } + end else - local p=last_pt local n=#p - segments[nofsegments]=curveto(control_pt[1],control_pt[2],p[n-2],p[n-1],first_pt[1],first_pt[2]) + local x1,y1=control_pt[1],control_pt[2] + local x2,y2=first_pt[1],first_pt[2] + x1,x2,x2,y2,px,py=curveto(x1,x2,px,py,x2,y2) + if x1xmax then xmax=x1 end + if y1ymax then ymax=y1 end + if x2xmax then xmax=x2 end + if y2ymax then ymax=y2 end + if pxxmax then xmax=px end + if pyymax then ymax=py end + if keepcurve then + nofsegments=nofsegments+1 + segments[nofsegments]={ x1,y1,y2,y2,px,py,"c" } + end end end end + first=last+1 end + glyph.boundingbox={ round(xmin),round(ymin),round(xmax),round(ymax) } end end end end end -local function readglyph(f,nofcontours) +local c_zero=char(0) +local s_zero=char(0,0) +local function toushort(n) + return char(band(rshift(n,8),0xFF),band(n,0xFF)) +end +local function toshort(n) + if n<0 then + n=n+0x10000 + end + return char(band(rshift(n,8),0xFF),band(n,0xFF)) +end +local function repackpoints(glyphs,shapes) + local noboundingbox={ 0,0,0,0 } + local result={} + for index=1,#glyphs do + local shape=shapes[index] + if shape then + local r=0 + local glyph=glyphs[index] + if false then + else + local contours=shape.contours + local nofcontours=#contours + local boundingbox=glyph.boundingbox or noboundingbox + r=r+1 result[r]=toshort(nofcontours) + r=r+1 result[r]=toshort(boundingbox[1]) + r=r+1 result[r]=toshort(boundingbox[2]) + r=r+1 result[r]=toshort(boundingbox[3]) + r=r+1 result[r]=toshort(boundingbox[4]) + 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 + 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) + if dx<-255 or dx>255 then + x=x+1 xpoints[x]=toshort(dx) + elseif dx<0 then + fl=fl+0x02 + x=x+1 xpoints[x]=char(-dx) + elseif dx>0 then + fl=fl+0x12 + x=x+1 xpoints[x]=char(dx) + else + fl=fl+0x02 + x=x+1 xpoints[x]=c_zero + end + end + if py==currenty then + fl=fl+0x20 + else + local dy=round(py-currenty) + if dy<-255 or dy>255 then + y=y+1 ypoints[y]=toshort(dy) + elseif dy<0 then + fl=fl+0x04 + y=y+1 ypoints[y]=char(-dy) + elseif dy>0 then + fl=fl+0x24 + y=y+1 ypoints[y]=char(dy) + else + fl=fl+0x04 + y=y+1 ypoints[y]=c_zero + end + end + currentx=px + currenty=py + if lastflag==fl then + nofflags=nofflags+1 + else + if nofflags==1 then + r=r+1 result[r]=char(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) + 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) + end + end + glyph.stream=concat(result,"",1,r) + else + end + end +end +local function readglyph(f,nofcontours) local points={} - local endpoints={} + local contours={} local instructions={} local flags={} for i=1,nofcontours do - endpoints[i]=readshort(f)+1 + contours[i]=readshort(f)+1 end - local nofpoints=endpoints[nofcontours] + local nofpoints=contours[nofcontours] local nofinstructions=readushort(f) skipbytes(f,nofinstructions) local i=1 while i<=nofpoints do local flag=readbyte(f) flags[i]=flag - if bittest(flag,0x0008) then + if bittest(flag,0x08) then for j=1,readbyte(f) do i=i+1 flags[i]=flag @@ -11569,8 +12269,8 @@ local function readglyph(f,nofcontours) local x=0 for i=1,nofpoints do local flag=flags[i] - local short=bittest(flag,0x0002) - local same=bittest(flag,0x0010) + local short=bittest(flag,0x02) + local same=bittest(flag,0x10) if short then if same then x=x+readbyte(f) @@ -11581,13 +12281,13 @@ local function readglyph(f,nofcontours) else x=x+readshort(f) end - points[i]={ x,y,bittest(flag,0x0001) } + points[i]={ x,y,bittest(flag,0x01) } end local y=0 for i=1,nofpoints do local flag=flags[i] - local short=bittest(flag,0x0004) - local same=bittest(flag,0x0020) + local short=bittest(flag,0x04) + local same=bittest(flag,0x20) if short then if same then y=y+readbyte(f) @@ -11600,15 +12300,11 @@ local function readglyph(f,nofcontours) end points[i][2]=y end - local first=1 - for i=1,#endpoints do - local last=endpoints[i] - endpoints[i]={ unpack(points,first,last) } - first=last+1 - end return { type="glyph", - contours=endpoints, + points=points, + contours=contours, + nofpoints=nofpoints, } end local function readcomposite(f) @@ -11699,15 +12395,13 @@ function readers.loca(f,fontdata,specification) local locations={} setposition(f,datatable.offset) if format==1 then - local nofglyphs=datatable.length/4-1 - -1 + local nofglyphs=datatable.length/4-2 for i=0,nofglyphs do locations[i]=offset+readulong(f) end fontdata.nofglyphs=nofglyphs else - local nofglyphs=datatable.length/2-1 - -1 + local nofglyphs=datatable.length/2-2 for i=0,nofglyphs do locations[i]=offset+readushort(f)*2 end @@ -11718,51 +12412,268 @@ function readers.loca(f,fontdata,specification) end end function readers.glyf(f,fontdata,specification) - if specification.glyphs then - local datatable=fontdata.tables.glyf - if datatable 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 - for index=0,nofglyphs do - local location=locations[index] - 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 location>0 then - setposition(f,location) - local nofcontours=readshort(f) - glyphs[index].boundingbox={ - readshort(f), - readshort(f), - readshort(f), - readshort(f), - } - if not loadshapes then - elseif nofcontours==0 then - shapes[index]=readnothing(f,nofcontours) - elseif nofcontours>0 then - shapes[index]=readglyph(f,nofcontours) + 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 + for index=0,nofglyphs do + local location=locations[index] + 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 location>0 then + setposition(f,location) + local nofcontours=readshort(f) + glyphs[index].boundingbox={ + readshort(f), + readshort(f), + readshort(f), + readshort(f), + } + if not loadshapes then + elseif nofcontours==0 then + shapes[index]=readnothing(f,nofcontours) + elseif nofcontours>0 then + shapes[index]=readglyph(f,nofcontours) + else + shapes[index]=readcomposite(f,nofcontours) + end + else + if loadshapes then + shapes[index]={} + 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.loadshapes then + contours2outlines_normal(glyphs,shapes) + end + end + end + end +end +local function readtuplerecord(f,nofaxis) + local record={} + for i=1,nofaxis do + record[i]=read2dot14(f) + end + return record +end +local function readpoints(f) + local count=readbyte(f) + if count==0 then + return nil,0 + else + if count<128 then + else + count=band(count,0x80)*256+readbyte(f) + end + local points={} + local p=0 + local n=1 + while p0 do + local control=readbyte(f) + local allzero=bittest(control,0x80) + local runreader=bittest(control,0x40) and readshort or readinteger + local runlength=band(control,0x3F)+1 + if allzero then + z=runlength + else + if z then + for i=1,z do + p=p+1 + deltas[p]=0 + end + z=false + end + for i=1,runlength do + p=p+1 + deltas[p]=runreader(f) + end + end + nofpoints=nofpoints-runlength + end + if p>0 then + return deltas + else + end +end +function readers.gvar(f,fontdata,specification,glyphdata,shapedata) + 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) + local nofaxis=readushort(f) + local noftuples=readushort(f) + local tupleoffset=readulong(f) + local nofglyphs=readushort(f) + local flags=readushort(f) + local dataoffset=tableoffset+readulong(f) + local data={} + local tuples={} + local glyphdata=fontdata.glyphs + if bittest(flags,0x0001) then + for i=1,nofglyphs do + data[i]=readulong(f) + end + else + for i=1,nofglyphs do + data[i]=2*readushort(f) + end + end + setposition(f,tableoffset+tupleoffset) + for i=1,noftuples do + tuples[i]=readtuplerecord(f,nofaxis) + end + local lastoffset=false + for i=1,nofglyphs do + local shape=shapedata[i-1] + if shape then + local startoffset=dataoffset+data[i] + if startoffset==lastoffset then + else + lastoffset=startoffset + setposition(f,startoffset) + local flags=readushort(f) + local count=band(flags,0x0FFF) + local points=bittest(flags,0x8000) + local offset=startoffset+readushort(f) + local deltas={} + local nofpoints=0 + local allpoints=(shape.nofpoints or 0)+1 + if points then + local current=getposition(f) + setposition(f,offset) + points,nofpoints=readpoints(f) + offset=getposition(f) + setposition(f,current) + else + points,nofpoints=nil,0 + end + for i=1,count do + local currentstart=getposition(f) + local size=readushort(f) + local flags=readushort(f) + local index=band(flags,0x0FFF) + local haspeak=bittest(flags,0x8000) + local intermediate=bittest(flags,0x4000) + local private=bittest(flags,0x1000) + local peak=nil + local start=nil + local stop=nil + local xvalues=nil + local yvalues=nil + local points=points + local nofpoints=nofpoints + local advance=4 + if peak then + peak=readtuplerecord(f,nofaxis) + advance=advance+2*nofaxis else - shapes[index]=readcomposite(f,nofcontours) + if index+1>#tuples then + print("error, bad index",index) + end + peak=tuples[index+1] end - else - if loadshapes then - shapes[index]={} + if intermediate then + start=readtuplerecord(f,nofaxis) + stop=readtuplerecord(f,nofaxis) + advance=advance+4*nofaxis + end + if size>0 then + setposition(f,offset) + if private then + points,nofpoints=readpoints(f) + elseif nofpoints==0 then + nofpoints=allpoints + end + if nofpoints>0 then + xvalues=readdeltas(f,nofpoints) + yvalues=readdeltas(f,nofpoints) + end + offset=getposition(f) + setposition(f,currentstart+advance) + end + if not xvalues and not yvalues then + points=nil + end + local s=1 + for i=1,nofaxis do + local f=factors[i] + local start=start and start[i] or 0 + local peak=peak and peak [i] or 0 + local stop=stop and stop [i] or 0 + if start>peak or peak>stop then + elseif start<0 and stop>0 and peak~=0 then + elseif peak==0 then + elseif fstop then + s=0 + break + elseif fpeak then + s=s*(stop-f)/(stop-peak) + else + end + end + if s~=0 then + deltas[#deltas+1]={ + factor=s, + points=points, + xvalues=xvalues, + yvalues=yvalues, + } end - glyphs[index].boundingbox=nothing end - end - if loadshapes then - mergecomposites(glyphs,shapes) - contours2outlines(glyphs,shapes) + if shape.type=="glyph" then + applyaxis(glyphdata[i],shape,shape.points,deltas) + else + shape.deltas=deltas + end end end end @@ -11783,16 +12694,22 @@ if not modules then modules={} end modules ['font-dsp']={ local next,type=next,type local bittest=bit32.btest local band=bit32.band +local extract=bit32.extract local bor=bit32.bor local lshift=bit32.lshift local rshift=bit32.rshift -local concat=table.concat +local gsub=string.gsub local lower=string.lower -local copy=table.copy local sub=string.sub local strip=string.strip local tohash=table.tohash +local concat=table.concat +local copy=table.copy local reversed=table.reversed +local sort=table.sort +local insert=table.insert +local round=math.round +local lpegmatch=lpeg.match local setmetatableindex=table.setmetatableindex local formatters=string.formatters local sortedkeys=table.sortedkeys @@ -11818,6 +12735,11 @@ local readbytetable=streamreader.readbytetable local readbyte=streamreader.readbyte local gsubhandlers={} local gposhandlers={} +readers.gsubhandlers=gsubhandlers +readers.gposhandlers=gposhandlers +local helpers=readers.helpers +local gotodatatable=helpers.gotodatatable +local setvariabledata=helpers.setvariabledata local lookupidoffset=-1 local classes={ "base", @@ -11851,6 +12773,51 @@ local chaindirections={ chainedcontext=1, reversechainedcontextsingle=-1, } +local function setmetrics(data,where,tag,d) + local w=data[where] + if w then + local v=w[tag] + if v then + w[tag]=v+d + end + end +end +local variabletags={ + hasc=function(data,d) setmetrics(data,"windowsmetrics","typoascender",d) end, + hdsc=function(data,d) setmetrics(data,"windowsmetrics","typodescender",d) end, + hlgp=function(data,d) setmetrics(data,"windowsmetrics","typolinegap",d) end, + hcla=function(data,d) setmetrics(data,"windowsmetrics","winascent",d) end, + hcld=function(data,d) setmetrics(data,"windowsmetrics","windescent",d) end, + vasc=function(data,d) setmetrics(data,"vhea not done","ascent",d) end, + vdsc=function(data,d) setmetrics(data,"vhea not done","descent",d) end, + vlgp=function(data,d) setmetrics(data,"vhea not done","linegap",d) end, + xhgt=function(data,d) setmetrics(data,"windowsmetrics","xheight",d) end, + cpht=function(data,d) setmetrics(data,"windowsmetrics","capheight",d) end, + sbxs=function(data,d) setmetrics(data,"windowsmetrics","subscriptxsize",d) end, + sbys=function(data,d) setmetrics(data,"windowsmetrics","subscriptysize",d) end, + sbxo=function(data,d) setmetrics(data,"windowsmetrics","subscriptxoffset",d) end, + sbyo=function(data,d) setmetrics(data,"windowsmetrics","subscriptyoffset",d) end, + spxs=function(data,d) setmetrics(data,"windowsmetrics","superscriptxsize",d) end, + spys=function(data,d) setmetrics(data,"windowsmetrics","superscriptysize",d) end, + spxo=function(data,d) setmetrics(data,"windowsmetrics","superscriptxoffset",d) end, + spyo=function(data,d) setmetrics(data,"windowsmetrics","superscriptyoffset",d) end, + strs=function(data,d) setmetrics(data,"windowsmetrics","strikeoutsize",d) end, + stro=function(data,d) setmetrics(data,"windowsmetrics","strikeoutpos",d) end, + unds=function(data,d) setmetrics(data,"postscript","underlineposition",d) end, + undo=function(data,d) setmetrics(data,"postscript","underlinethickness",d) end, +} +local read_cardinal={ + streamreader.readcardinal1, + streamreader.readcardinal2, + streamreader.readcardinal3, + streamreader.readcardinal4, +} +local read_integer={ + streamreader.readinteger1, + streamreader.readinteger2, + streamreader.readinteger3, + streamreader.readinteger4, +} local lookupnames={ gsub={ single="gsub_single", @@ -11882,6 +12849,210 @@ local lookupflags=setmetatableindex(function(t,k) t[k]=v return v end) +local pattern=lpeg.Cf ( + lpeg.Ct("")*lpeg.Cg ( + lpeg.C(lpeg.R("az")^1)*lpeg.S(" :=")*(lpeg.patterns.number/tonumber)*lpeg.S(" ,")^0 + )^1,rawset +) +local hash=table.setmetatableindex(function(t,k) + local v=lpegmatch(pattern,k) + local t={} + for k,v in sortedhash(v) do + t[#t+1]=k.."="..v + end + v=concat(t,",") + t[k]=v + return v +end) +helpers.normalizedaxishash=hash +local cleanname=fonts.names and fonts.names.cleanname or function(name) + return name and (gsub(lower(name),"[^%a%d]","")) or nil +end +helpers.cleanname=cleanname +function helpers.normalizedaxis(str) + return hash[str] or str +end +local function axistofactors(str) + return lpegmatch(pattern,str) +end +local function getaxisscale(segments,minimum,default,maximum,user) + if not minimum or not default or not maximum then + return false + end + if usermaximum then + user=maximum + end + if userdefault then + default=(user-default)/(maximum-default) + else + default=0 + end + if not segments then + return default + end + local e + for i=1,#segments do + local s=segments[i] + if s[1]>=default then + if s[2]==default then + return default + else + e=i + break + end + end + end + if e then + local b=segments[e-1] + local e=segments[e] + return b[2]+(e[2]-b[2])*(default-b[1])/(e[1]-b[1]) + else + return false + end +end +local function getfactors(data,instancespec) + if instancespec==true then + elseif type(instancespec)~="string" or instancespec=="" then + return + end + local variabledata=data.variabledata + if not variabledata then + return + end + local instances=variabledata.instances + local axis=variabledata.axis + local segments=variabledata.segments + if instances and axis then + local values + if instancespec==true then + values={} + for i=1,#axis do + values[i]={ + value=axis[i].default, + } + end + else + for i=1,#instances do + local instance=instances[i] + if cleanname(instance.subfamily)==instancespec then + values=instance.values + break + end + end + end + if values then + local factors={} + for i=1,#axis do + local a=axis[i] + factors[i]=getaxisscale(segments,a.minimum,a.default,a.maximum,values[i].value) + end + return factors + end + local values=axistofactors(hash[instancespec] or instancespec) + if values then + local factors={} + for i=1,#axis do + local a=axis[i] + local d=a.default + factors[i]=getaxisscale(segments,a.minimum,d,a.maximum,values[a.name or a.tag] or d) + end + return factors + end + end +end +local function getscales(regions,factors) + local scales={} + for i=1,#regions do + local region=regions[i] + local s=1 + for j=1,#region do + local axis=region[j] + local f=factors[j] + local start=axis.start + local peak=axis.peak + local stop=axis.stop + if start>peak or peak>stop then + elseif start<0 and stop>0 and peak~=0 then + elseif peak==0 then + elseif fstop then + s=0 + break + elseif fpeak then + s=s*(stop-f)/(stop-peak) + else + end + end + scales[i]=s + end + return scales +end +helpers.getaxisscale=getaxisscale +helpers.getfactors=getfactors +helpers.getscales=getscales +helpers.axistofactors=axistofactors +local function readvariationdata(f,storeoffset,factors) + local position=getposition(f) + setposition(f,storeoffset) + local format=readushort(f) + local regionoffset=storeoffset+readulong(f) + local nofdeltadata=readushort(f) + local deltadata={} + for i=1,nofdeltadata do + deltadata[i]=readulong(f) + end + setposition(f,regionoffset) + local nofaxis=readushort(f) + local nofregions=readushort(f) + local regions={} + for i=1,nofregions do + local t={} + for i=1,nofaxis do + t[i]={ + start=read2dot14(f), + peak=read2dot14(f), + stop=read2dot14(f), + } + end + regions[i]=t + end + if factors then + for i=1,nofdeltadata do + setposition(f,storeoffset+deltadata[i]) + local nofdeltasets=readushort(f) + local nofshorts=readushort(f) + local nofregions=readushort(f) + local usedregions={} + local deltas={} + for i=1,nofregions do + usedregions[i]=regions[readushort(f)+1] + end + for i=1,nofdeltasets do + local t={} + for i=1,nofshorts do + t[i]=readshort(f) + end + for i=nofshorts+1,nofregions do + t[i]=readinteger(f) + end + deltas[i]=t + end + deltadata[i]={ + regions=usedregions, + deltas=deltas, + scales=factors and getscales(usedregions,factors) or nil, + } + end + end + setposition(f,position) + return regions,deltadata +end +helpers.readvariationdata=readvariationdata local function readcoverage(f,offset,simple) setposition(f,offset) local coverageformat=readushort(f) @@ -11974,34 +13145,150 @@ local function classtocoverage(defs) return list end end -local function readposition(f,format) +local skips={ [0]=0, + 1, + 1, + 2, + 1, + 2, + 2, + 3, + 2, + 2, + 3, + 2, + 3, + 3, + 4, +} +local function readvariation(f,offset) + local p=getposition(f) + setposition(f,offset) + local outer=readushort(f) + local inner=readushort(f) + local format=readushort(f) + setposition(f,p) + if format==0x8000 then + return outer,inner + end +end +local function readposition(f,format,mainoffset,getdelta) if format==0 then - return nil + return end - local x=bittest(format,0x0001) and readshort(f) or 0 - local y=bittest(format,0x0002) and readshort(f) or 0 - local h=bittest(format,0x0004) and readshort(f) or 0 - local v=bittest(format,0x0008) and readshort(f) or 0 - if x==0 and y==0 and h==0 and v==0 then - return nil + if format==0x04 then + local h=readshort(f) + if h==0 then + return + else + return { 0,0,h,0 } + end + end + if format==0x05 then + local x=readshort(f) + local h=readshort(f) + if x==0 and h==0 then + return + else + return { x,0,h,0 } + end + end + if format==0x44 then + local h=readshort(f) + if getdelta then + local d=readshort(f) + if d>0 then + local outer,inner=readvariation(f,mainoffset+d) + if outer then + h=h+getdelta(outer,inner) + end + end + else + skipshort(f,1) + end + if h==0 then + return + else + return { 0,0,h,0 } + end + end + local x=bittest(format,0x01) and readshort(f) or 0 + local y=bittest(format,0x02) and readshort(f) or 0 + local h=bittest(format,0x04) and readshort(f) or 0 + local v=bittest(format,0x08) and readshort(f) or 0 + if format>=0x10 then + local X=bittest(format,0x10) and skipshort(f) or 0 + local Y=bittest(format,0x20) and skipshort(f) or 0 + local H=bittest(format,0x40) and skipshort(f) or 0 + local V=bittest(format,0x80) and skipshort(f) or 0 + local s=skips[extract(format,4,4)] + if s>0 then + skipshort(f,s) + end + if getdelta then + if X>0 then + local outer,inner=readvariation(f,mainoffset+X) + if outer then + x=x+getdelta(outer,inner) + end + end + if Y>0 then + local outer,inner=readvariation(f,mainoffset+Y) + if outer then + y=y+getdelta(outer,inner) + end + end + if H>0 then + local outer,inner=readvariation(f,mainoffset+H) + if outer then + h=h+getdelta(outer,inner) + end + end + if V>0 then + local outer,inner=readvariation(f,mainoffset+V) + if outer then + v=v+getdelta(outer,inner) + end + end + end + return { x,y,h,v } + elseif x==0 and y==0 and h==0 and v==0 then + return else return { x,y,h,v } end end -local function readanchor(f,offset) +local function readanchor(f,offset,getdelta) if not offset or offset==0 then return nil end setposition(f,offset) - local format=readshort(f) - if format==0 then - report("invalid anchor format %i @ position %i",format,offset) - return false - elseif format>3 then - report("unsupported anchor format %i @ position %i",format,offset) - return false + local format=readshort(f) + local x=readshort(f) + local y=readshort(f) + if format==3 then + if getdelta then + local X=readshort(f) + local Y=readshort(f) + if X>0 then + local outer,inner=readvariation(f,offset+X) + if outer then + x=x+getdelta(outer,inner) + end + end + if Y>0 then + local outer,inner=readvariation(f,offset+Y) + if outer then + y=y+getdelta(outer,inner) + end + end + else + skipshort(f,2) + end + return { x,y } + else + return { x,y } end - return { readshort(f),readshort(f) } end local function readfirst(f,offset) if offset then @@ -12521,20 +13808,21 @@ function gsubhandlers.reversechainedcontextsingle(f,fontdata,lookupid,lookupoffs report("unsupported subtype %a in %a substitution",subtype,"reversechainedcontextsingle") end end -local function readpairsets(f,tableoffset,sets,format1,format2) +local function readpairsets(f,tableoffset,sets,format1,format2,mainoffset,getdelta) local done={} for i=1,#sets do local offset=sets[i] local reused=done[offset] if not reused then - setposition(f,tableoffset+offset) + offset=tableoffset+offset + setposition(f,offset) local n=readushort(f) reused={} for i=1,n do reused[i]={ readushort(f), - readposition(f,format1), - readposition(f,format2) + readposition(f,format1,offset,getdelta), + readposition(f,format2,offset,getdelta), } end done[offset]=reused @@ -12543,14 +13831,14 @@ local function readpairsets(f,tableoffset,sets,format1,format2) end return sets end -local function readpairclasssets(f,nofclasses1,nofclasses2,format1,format2) +local function readpairclasssets(f,nofclasses1,nofclasses2,format1,format2,mainoffset,getdelta) local classlist1={} for i=1,nofclasses1 do local classlist2={} classlist1[i]=classlist2 for j=1,nofclasses2 do - local one=readposition(f,format1) - local two=readposition(f,format2) + local one=readposition(f,format1,mainoffset,getdelta) + local two=readposition(f,format2,mainoffset,getdelta) if one or two then classlist2[j]={ one,two } else @@ -12564,25 +13852,26 @@ function gposhandlers.single(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofg local tableoffset=lookupoffset+offset setposition(f,tableoffset) local subtype=readushort(f) + local getdelta=fontdata.temporary.getdelta if subtype==1 then local coverage=readushort(f) local format=readushort(f) - local value=readposition(f,format) + local value=readposition(f,format,tableoffset,getdelta) local coverage=readcoverage(f,tableoffset+coverage) for index,newindex in next,coverage do coverage[index]=value end return { format="pair", - coverage=coverage + coverage=coverage, } elseif subtype==2 then local coverage=readushort(f) local format=readushort(f) - local values={} local nofvalues=readushort(f) + local values={} for i=1,nofvalues do - values[i]=readposition(f,format) + values[i]=readposition(f,format,tableoffset,getdelta) end local coverage=readcoverage(f,tableoffset+coverage) for index,newindex in next,coverage do @@ -12590,7 +13879,7 @@ function gposhandlers.single(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofg end return { format="pair", - coverage=coverage + coverage=coverage, } else report("unsupported subtype %a in %a positioning",subtype,"single") @@ -12600,12 +13889,13 @@ function gposhandlers.pair(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofgly local tableoffset=lookupoffset+offset setposition(f,tableoffset) local subtype=readushort(f) + local getdelta=fontdata.temporary.getdelta if subtype==1 then local coverage=readushort(f) local format1=readushort(f) local format2=readushort(f) local sets=readarray(f) - sets=readpairsets(f,tableoffset,sets,format1,format2) + sets=readpairsets(f,tableoffset,sets,format1,format2,mainoffset,getdelta) coverage=readcoverage(f,tableoffset+coverage) for index,newindex in next,coverage do local set=sets[newindex+1] @@ -12627,7 +13917,7 @@ function gposhandlers.pair(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofgly end return { format="pair", - coverage=coverage + coverage=coverage, } elseif subtype==2 then local coverage=readushort(f) @@ -12637,7 +13927,7 @@ function gposhandlers.pair(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofgly local classdef2=readushort(f) local nofclasses1=readushort(f) local nofclasses2=readushort(f) - local classlist=readpairclasssets(f,nofclasses1,nofclasses2,format1,format2) + local classlist=readpairclasssets(f,nofclasses1,nofclasses2,format1,format2,tableoffset,getdelta) coverage=readcoverage(f,tableoffset+coverage) classdef1=readclassdef(f,tableoffset+classdef1,coverage) classdef2=readclassdef(f,tableoffset+classdef2,nofglyphs) @@ -12664,7 +13954,7 @@ function gposhandlers.pair(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofgly end return { format="pair", - coverage=usedcoverage + coverage=usedcoverage, } elseif subtype==3 then report("yet unsupported subtype %a in %a positioning",subtype,"pair") @@ -12676,6 +13966,7 @@ function gposhandlers.cursive(f,fontdata,lookupid,lookupoffset,offset,glyphs,nof local tableoffset=lookupoffset+offset setposition(f,tableoffset) local subtype=readushort(f) + local getdelta=fontdata.temporary.getdelta if subtype==1 then local coverage=tableoffset+readushort(f) local nofrecords=readushort(f) @@ -12693,15 +13984,15 @@ function gposhandlers.cursive(f,fontdata,lookupid,lookupoffset,offset,glyphs,nof local r=records[i] records[i]={ 1, - readanchor(f,r.entry) or nil, - readanchor(f,r.exit ) or nil, + readanchor(f,r.entry,getdelta) or nil, + readanchor(f,r.exit,getdelta) or nil, } end for index,newindex in next,coverage do coverage[index]=records[newindex+1] end return { - coverage=coverage + coverage=coverage, } else report("unsupported subtype %a in %a positioning",subtype,"cursive") @@ -12711,6 +14002,7 @@ local function handlemark(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyp local tableoffset=lookupoffset+offset setposition(f,tableoffset) local subtype=readushort(f) + local getdelta=fontdata.temporary.getdelta if subtype==1 then local markcoverage=tableoffset+readushort(f) local basecoverage=tableoffset+readushort(f) @@ -12737,7 +14029,7 @@ local function handlemark(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyp for i=1,nofmarkclasses do local mc=markclasses[i] if mc then - mc[2]=readanchor(f,mc[2]) + mc[2]=readanchor(f,mc[2],getdelta) end end setposition(f,baseoffset) @@ -12785,7 +14077,7 @@ local function handlemark(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyp local classes=components[c] if classes then for i=1,nofclasses do - local anchor=readanchor(f,classes[i]) + local anchor=readanchor(f,classes[i],getdelta) local bclass=baseclasses[i] local bentry=bclass[b] if bentry then @@ -12827,7 +14119,7 @@ local function handlemark(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyp local r=baserecords[i] local b=basecoverage[i] for j=1,nofclasses do - baseclasses[j][b]=readanchor(f,r[j]) + baseclasses[j][b]=readanchor(f,r[j],getdelta) end end for index,newindex in next,markcoverage do @@ -12870,7 +14162,7 @@ do setposition(f,offset) local designsize=readushort(f) if designsize>0 then - local fontstyle=readushort(f) + local fontstyleid=readushort(f) local guimenuid=readushort(f) local minsize=readushort(f) local maxsize=readushort(f) @@ -13046,8 +14338,8 @@ do lookups[i]=readushort(f) end for lookupid=1,noflookups do - local index=lookups[lookupid] - setposition(f,lookupoffset+index) + local offset=lookups[lookupid] + setposition(f,lookupoffset+offset) local subtables={} local typebits=readushort(f) local flagbits=readushort(f) @@ -13055,8 +14347,7 @@ do local lookupflags=lookupflags[flagbits] local nofsubtables=readushort(f) for j=1,nofsubtables do - local offset=readushort(f) - subtables[j]=offset+index + subtables[j]=offset+readushort(f) end local markclass=bittest(flagbits,0x0010) if markclass then @@ -13078,20 +14369,8 @@ do end return lookups end - local function readscriptoffsets(f,fontdata,tableoffset) - if not tableoffset then - return - end - setposition(f,tableoffset) - local version=readulong(f) - if version~=0x00010000 then - report("table version %a of %a is not supported (yet), maybe font %s is bad",version,what,fontdata.filename) - return - end - return tableoffset+readushort(f),tableoffset+readushort(f),tableoffset+readushort(f) - end local f_lookupname=formatters["%s_%s_%s"] - local function resolvelookups(f,lookupoffset,fontdata,lookups,lookuptypes,lookuphandlers,what) + local function resolvelookups(f,lookupoffset,fontdata,lookups,lookuptypes,lookuphandlers,what,tableoffset) local sequences=fontdata.sequences or {} local sublookuplist=fontdata.sublookups or {} fontdata.sequences=sequences @@ -13280,42 +14559,119 @@ do end end end - for i,n in sortedhash(sublookupcheck) do - local l=lookups[i] - local t=l.type - if n==0 and t~="extension" then - local d=l.done - report("%s lookup %s of type %a is not used",what,d and d.name or l.name,t) + for i,n in sortedhash(sublookupcheck) do + local l=lookups[i] + local t=l.type + if n==0 and t~="extension" then + local d=l.done + report("%s lookup %s of type %a is not used",what,d and d.name or l.name,t) + end + end + end + local function loadvariations(f,fontdata,variationsoffset,lookuptypes,featurehash,featureorder) + setposition(f,variationsoffset) + local version=readulong(f) + local nofrecords=readulong(f) + local records={} + for i=1,nofrecords do + records[i]={ + conditions=readulong(f), + substitutions=readulong(f), + } + end + for i=1,nofrecords do + local record=records[i] + local offset=record.conditions + if offset==0 then + record.condition=nil + record.matchtype="always" + else + setposition(f,variationsoffset+offset) + local nofconditions=readushort(f) + local conditions={} + for i=1,nofconditions do + conditions[i]=variationsoffset+offset+readulong(f) + end + record.conditions=conditions + record.matchtype="condition" + end + end + for i=1,nofrecords do + local record=records[i] + if record.matchtype=="condition" then + local conditions=record.conditions + for i=1,#conditions do + setposition(f,conditions[i]) + conditions[i]={ + format=readushort(f), + axis=readushort(f), + minvalue=read2dot14(f), + maxvalue=read2dot14(f), + } + end + end + end + for i=1,nofrecords do + local record=records[i] + local offset=record.substitutions + if offset==0 then + record.substitutions={} + else + setposition(f,variationsoffset+offset) + local version=readulong(f) + local nofsubstitutions=readushort(f) + local substitutions={} + for i=1,nofsubstitutions do + substitutions[readushort(f)]=readulong(f) + end + for index,alternates in sortedhash(substitutions) do + if index==0 then + record.substitutions=false + else + local tableoffset=variationsoffset+offset+alternates + setposition(f,tableoffset) + local parameters=readulong(f) + local noflookups=readushort(f) + local lookups={} + for i=1,noflookups do + lookups[i]=readushort(f) + end + record.substitutions=lookups + end + end end end + setvariabledata(fontdata,"features",records) end local function readscripts(f,fontdata,what,lookuptypes,lookuphandlers,lookupstoo) - local datatable=fontdata.tables[what] - if not datatable then - return - end - local tableoffset=datatable.offset - if not tableoffset then - return - end - local scriptoffset,featureoffset,lookupoffset=readscriptoffsets(f,fontdata,tableoffset) - if not scriptoffset then - return - end - local scripts=readscriplan(f,fontdata,scriptoffset) - local features=readfeatures(f,fontdata,featureoffset) - local scriptlangs,featurehash,featureorder=reorderfeatures(fontdata,scripts,features) - if fontdata.features then - fontdata.features[what]=scriptlangs - else - fontdata.features={ [what]=scriptlangs } - end - if not lookupstoo then - return - end - local lookups=readlookups(f,lookupoffset,lookuptypes,featurehash,featureorder) - if lookups then - resolvelookups(f,lookupoffset,fontdata,lookups,lookuptypes,lookuphandlers,what) + local tableoffset=gotodatatable(f,fontdata,what,true) + if tableoffset then + local version=readulong(f) + local scriptoffset=tableoffset+readushort(f) + local featureoffset=tableoffset+readushort(f) + local lookupoffset=tableoffset+readushort(f) + local variationsoffset=version>0x00010000 and (tableoffset+readulong(f)) or 0 + if not scriptoffset then + return + end + local scripts=readscriplan(f,fontdata,scriptoffset) + local features=readfeatures(f,fontdata,featureoffset) + local scriptlangs,featurehash,featureorder=reorderfeatures(fontdata,scripts,features) + if fontdata.features then + fontdata.features[what]=scriptlangs + else + fontdata.features={ [what]=scriptlangs } + end + if not lookupstoo then + return + end + local lookups=readlookups(f,lookupoffset,lookuptypes,featurehash,featureorder) + if lookups then + resolvelookups(f,lookupoffset,fontdata,lookups,lookuptypes,lookuphandlers,what,tableoffset) + end + if variationsoffset>0 then + loadvariations(f,fontdata,variationsoffset,lookuptypes,featurehash,featureorder) + end end end local function checkkerns(f,fontdata,specification) @@ -13405,86 +14761,114 @@ do end end function readers.gdef(f,fontdata,specification) - if specification.glyphs then - local datatable=fontdata.tables.gdef - if datatable then - local tableoffset=datatable.offset - setposition(f,tableoffset) - local version=readulong(f) - local classoffset=tableoffset+readushort(f) - local attachmentoffset=tableoffset+readushort(f) - local ligaturecarets=tableoffset+readushort(f) - local markclassoffset=tableoffset+readushort(f) - local marksetsoffset=version==0x00010002 and (tableoffset+readushort(f)) - local glyphs=fontdata.glyphs - local marks={} - local markclasses=setmetatableindex("table") - local marksets=setmetatableindex("table") - fontdata.marks=marks - fontdata.markclasses=markclasses - fontdata.marksets=marksets - setposition(f,classoffset) - local classformat=readushort(f) - if classformat==1 then + if not specification.glyphs then + return + end + local datatable=fontdata.tables.gdef + if datatable then + local tableoffset=datatable.offset + setposition(f,tableoffset) + local version=readulong(f) + local classoffset=tableoffset+readushort(f) + local attachmentoffset=tableoffset+readushort(f) + local ligaturecarets=tableoffset+readushort(f) + local markclassoffset=tableoffset+readushort(f) + local marksetsoffset=version>=0x00010002 and (tableoffset+readushort(f)) + local varsetsoffset=version>=0x00010003 and (tableoffset+readulong(f)) + local glyphs=fontdata.glyphs + local marks={} + local markclasses=setmetatableindex("table") + local marksets=setmetatableindex("table") + fontdata.marks=marks + fontdata.markclasses=markclasses + fontdata.marksets=marksets + setposition(f,classoffset) + local classformat=readushort(f) + if classformat==1 then + local firstindex=readushort(f) + local lastindex=firstindex+readushort(f)-1 + for index=firstindex,lastindex do + local class=classes[readushort(f)] + if class=="mark" then + marks[index]=true + end + glyphs[index].class=class + end + elseif classformat==2 then + local nofranges=readushort(f) + for i=1,nofranges do local firstindex=readushort(f) - local lastindex=firstindex+readushort(f)-1 - for index=firstindex,lastindex do - local class=classes[readushort(f)] - if class=="mark" then - marks[index]=true - end - glyphs[index].class=class - end - elseif classformat==2 then - local nofranges=readushort(f) - for i=1,nofranges do - local firstindex=readushort(f) - local lastindex=readushort(f) - local class=classes[readushort(f)] - if class then - for index=firstindex,lastindex do - glyphs[index].class=class - if class=="mark" then - marks[index]=true - end + local lastindex=readushort(f) + local class=classes[readushort(f)] + if class then + for index=firstindex,lastindex do + glyphs[index].class=class + if class=="mark" then + marks[index]=true end end end end - setposition(f,markclassoffset) - local classformat=readushort(f) - if classformat==1 then + end + setposition(f,markclassoffset) + local classformat=readushort(f) + if classformat==1 then + local firstindex=readushort(f) + local lastindex=firstindex+readushort(f)-1 + for index=firstindex,lastindex do + markclasses[readushort(f)][index]=true + end + elseif classformat==2 then + local nofranges=readushort(f) + for i=1,nofranges do local firstindex=readushort(f) - local lastindex=firstindex+readushort(f)-1 + local lastindex=readushort(f) + local class=markclasses[readushort(f)] for index=firstindex,lastindex do - markclasses[readushort(f)][index]=true - end - elseif classformat==2 then - local nofranges=readushort(f) - for i=1,nofranges do - local firstindex=readushort(f) - local lastindex=readushort(f) - local class=markclasses[readushort(f)] - for index=firstindex,lastindex do - class[index]=true - end + class[index]=true end end - if marksetsoffset and marksetsoffset>tableoffset then - setposition(f,marksetsoffset) - local format=readushort(f) - if format==1 then - local nofsets=readushort(f) - local sets={} - for i=1,nofsets do - sets[i]=readulong(f) - end - for i=1,nofsets do - local offset=sets[i] - if offset~=0 then - marksets[i]=readcoverage(f,marksetsoffset+offset) + end + if marksetsoffset and marksetsoffset>tableoffset then + setposition(f,marksetsoffset) + local format=readushort(f) + if format==1 then + local nofsets=readushort(f) + local sets={} + for i=1,nofsets do + sets[i]=readulong(f) + end + for i=1,nofsets do + local offset=sets[i] + if offset~=0 then + marksets[i]=readcoverage(f,marksetsoffset+offset) + end + end + end + end + local factors=specification.factors + if (specification.variable or factors) and varsetsoffset and varsetsoffset>tableoffset then + local regions,deltas=readvariationdata(f,varsetsoffset,factors) + if factors then + fontdata.temporary.getdelta=function(outer,inner) + local delta=deltas[outer+1] + if delta then + local d=delta.deltas[inner+1] + if d then + local scales=delta.scales + local dd=0 + for i=1,#scales do + local di=d[i] + if di then + dd=dd+scales[i]*di + else + break + end + end + return round(dd) end end + return 0 end end end @@ -13756,167 +15140,435 @@ local function readmathvariants(f,fontdata,offset) get(offset,hcoverage,hnofglyphs,hconstruction,"hvariants","hparts","hitalic") end function readers.math(f,fontdata,specification) - if specification.glyphs then - local datatable=fontdata.tables.math - if datatable then - local tableoffset=datatable.offset - setposition(f,tableoffset) - local version=readulong(f) - if version~=0x00010000 then - report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"math",fontdata.filename) - return - end - local constants=readushort(f) - local glyphinfo=readushort(f) - local variants=readushort(f) - if constants==0 then - report("the math table of %a has no constants",fontdata.filename) - else - readmathconstants(f,fontdata,tableoffset+constants) - end - if glyphinfo~=0 then - readmathglyphinfo(f,fontdata,tableoffset+glyphinfo) - end - if variants~=0 then - readmathvariants(f,fontdata,tableoffset+variants) - end + local tableoffset=gotodatatable(f,fontdata,"math",specification.glyphs) + if tableoffset then + local version=readulong(f) + local constants=readushort(f) + local glyphinfo=readushort(f) + local variants=readushort(f) + if constants==0 then + report("the math table of %a has no constants",fontdata.filename) + else + readmathconstants(f,fontdata,tableoffset+constants) + end + if glyphinfo~=0 then + readmathglyphinfo(f,fontdata,tableoffset+glyphinfo) + end + if variants~=0 then + readmathvariants(f,fontdata,tableoffset+variants) end end end function readers.colr(f,fontdata,specification) - local datatable=fontdata.tables.colr - if datatable then - if specification.glyphs then - local tableoffset=datatable.offset - setposition(f,tableoffset) - local version=readushort(f) - if version~=0 then - report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"colr",fontdata.filename) - return - end - if not fontdata.tables.cpal then - report("color table %a in font %a has no mandate %a table","colr",fontdata.filename,"cpal") - fontdata.colorpalettes={} - end - local glyphs=fontdata.glyphs - local nofglyphs=readushort(f) - local baseoffset=readulong(f) - local layeroffset=readulong(f) + local tableoffset=gotodatatable(f,fontdata,"colr",specification.glyphs) + if tableoffset then + local version=readushort(f) + if version~=0 then + report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"colr",fontdata.filename) + return + end + if not fontdata.tables.cpal then + report("color table %a in font %a has no mandate %a table","colr",fontdata.filename,"cpal") + fontdata.colorpalettes={} + end + local glyphs=fontdata.glyphs + local nofglyphs=readushort(f) + local baseoffset=readulong(f) + local layeroffset=readulong(f) + local noflayers=readushort(f) + local layerrecords={} + local maxclass=0 + setposition(f,tableoffset+layeroffset) + for i=1,noflayers do + local slot=readushort(f) + local class=readushort(f) + if class<0xFFFF then + class=class+1 + if class>maxclass then + maxclass=class + end + end + layerrecords[i]={ + slot=slot, + class=class, + } + end + fontdata.maxcolorclass=maxclass + setposition(f,tableoffset+baseoffset) + for i=0,nofglyphs-1 do + local glyphindex=readushort(f) + local firstlayer=readushort(f) local noflayers=readushort(f) - local layerrecords={} - local maxclass=0 - setposition(f,tableoffset+layeroffset) + local t={} for i=1,noflayers do - local slot=readushort(f) - local class=readushort(f) - if class<0xFFFF then - class=class+1 - if class>maxclass then - maxclass=class - end - end - layerrecords[i]={ - slot=slot, - class=class, - } - end - fontdata.maxcolorclass=maxclass - setposition(f,tableoffset+baseoffset) - for i=0,nofglyphs-1 do - local glyphindex=readushort(f) - local firstlayer=readushort(f) - local noflayers=readushort(f) - local t={} - for i=1,noflayers do - t[i]=layerrecords[firstlayer+i] - end - glyphs[glyphindex].colors=t + t[i]=layerrecords[firstlayer+i] end + glyphs[glyphindex].colors=t end - fontdata.hascolor=true end + fontdata.hascolor=true end function readers.cpal(f,fontdata,specification) - if specification.glyphs then - local datatable=fontdata.tables.cpal - if datatable then - local tableoffset=datatable.offset - setposition(f,tableoffset) - local version=readushort(f) - if version>1 then - report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"cpal",fontdata.filename) - return + local tableoffset=gotodatatable(f,fontdata,"cpal",specification.glyphs) + if tableoffset then + local version=readushort(f) + local nofpaletteentries=readushort(f) + local nofpalettes=readushort(f) + local nofcolorrecords=readushort(f) + local firstcoloroffset=readulong(f) + local colorrecords={} + local palettes={} + for i=1,nofpalettes do + palettes[i]=readushort(f) + end + if version==1 then + local palettettypesoffset=readulong(f) + local palettelabelsoffset=readulong(f) + local paletteentryoffset=readulong(f) + end + setposition(f,tableoffset+firstcoloroffset) + for i=1,nofcolorrecords do + local b,g,r,a=readbytes(f,4) + colorrecords[i]={ + r,g,b,a~=255 and a or nil, + } + end + for i=1,nofpalettes do + local p={} + local o=palettes[i] + for j=1,nofpaletteentries do + p[j]=colorrecords[o+j] end - local nofpaletteentries=readushort(f) - local nofpalettes=readushort(f) - local nofcolorrecords=readushort(f) - local firstcoloroffset=readulong(f) - local colorrecords={} - local palettes={} - for i=1,nofpalettes do - palettes[i]=readushort(f) - end - if version==1 then - local palettettypesoffset=readulong(f) - local palettelabelsoffset=readulong(f) - local paletteentryoffset=readulong(f) - end - setposition(f,tableoffset+firstcoloroffset) - for i=1,nofcolorrecords do - local b,g,r,a=readbytes(f,4) - colorrecords[i]={ - r,g,b,a~=255 and a or nil, + palettes[i]=p + end + fontdata.colorpalettes=palettes + end +end +function readers.svg(f,fontdata,specification) + local tableoffset=gotodatatable(f,fontdata,"svg",specification.glyphs) + if tableoffset then + local version=readushort(f) + local glyphs=fontdata.glyphs + local indexoffset=tableoffset+readulong(f) + local reserved=readulong(f) + setposition(f,indexoffset) + local nofentries=readushort(f) + local entries={} + for i=1,nofentries do + entries[i]={ + first=readushort(f), + last=readushort(f), + offset=indexoffset+readulong(f), + length=readulong(f), + } + end + for i=1,nofentries do + local entry=entries[i] + setposition(f,entry.offset) + entries[i]={ + first=entry.first, + last=entry.last, + data=readstring(f,entry.length) + } + end + fontdata.svgshapes=entries + end + fontdata.hascolor=true +end +function readers.stat(f,fontdata,specification) + local tableoffset=gotodatatable(f,fontdata,"stat",true) + if tableoffset then + local extras=fontdata.extras + local version=readulong(f) + local axissize=readushort(f) + local nofaxis=readushort(f) + local axisoffset=readulong(f) + local nofvalues=readushort(f) + local valuesoffset=readulong(f) + local fallbackname=extras[readushort(f)] + local axis={} + local values={} + setposition(f,tableoffset+axisoffset) + for i=1,nofaxis do + axis[i]={ + tag=readtag(f), + name=lower(extras[readushort(f)]), + ordering=readushort(f), + variants={} + } + end + setposition(f,tableoffset+valuesoffset) + for i=1,nofvalues do + values[i]=readushort(f) + end + for i=1,nofvalues do + setposition(f,tableoffset+valuesoffset+values[i]) + local format=readushort(f) + local index=readushort(f)+1 + local flags=readushort(f) + local name=lower(extras[readushort(f)]) + local value=readfixed(f) + local variant + if format==1 then + variant={ + flags=flags, + name=name, + value=value, + } + elseif format==2 then + variant={ + flags=flags, + name=name, + value=value, + minimum=readfixed(f), + maximum=readfixed(f), + } + elseif format==3 then + variant={ + flags=flags, + name=name, + value=value, + link=readfixed(f), } end - for i=1,nofpalettes do - local p={} - local o=palettes[i] - for j=1,nofpaletteentries do - p[j]=colorrecords[o+j] + insert(axis[index].variants,variant) + end + sort(axis,function(a,b) + return a.ordering=lastto then + else + values[#values+1]={ f,t } + lasfrom,lastto=f,t + end + end + nofvalues=#values + if nofvalues>2 then + local some=values[1] + if some[1]==-1 and some[2]==-1 then + some=values[nofvalues] + if some[1]==1 and some[2]==1 then + for i=2,size-1 do + some=values[i] + if some[1]==0 and some[2]==0 then + return values + end + end + end end - palettes[i]=p end - fontdata.colorpalettes=palettes + return false + end + local version=readulong(f) + local reserved=readulong(f) + local nofaxis=readulong(f) + local segments={} + for i=1,nofaxis do + segments[i]=collect() + end + setvariabledata(fontdata,"segments",segments) + end +end +function readers.fvar(f,fontdata,specification) + local tableoffset=gotodatatable(f,fontdata,"fvar",true) + if tableoffset then + local version=readulong(f) + local offsettoaxis=tableoffset+readushort(f) + local reserved=skipshort(f) + local nofaxis=readushort(f) + local sizeofaxis=readushort(f) + local nofinstances=readushort(f) + local sizeofinstances=readushort(f) + local extras=fontdata.extras + local axis={} + local instances={} + setposition(f,offsettoaxis) + for i=1,nofaxis do + axis[i]={ + tag=readtag(f), + minimum=readfixed(f), + default=readfixed(f), + maximum=readfixed(f), + flags=readushort(f), + name=lower(extras[readushort(f)]), + } + local n=sizeofaxis-20 + if n>0 then + skipbytes(f,n) + elseif n<0 then + end + end + local nofbytes=2+2+2+nofaxis*4 + local readpsname=nofbytes<=sizeofinstances + local skippable=sizeofinstances-nofbytes + for i=1,nofinstances do + local subfamid=readushort(f) + local flags=readushort(f) + local values={} + for i=1,nofaxis do + values[i]={ + axis=axis[i].tag, + value=readfixed(f), + } + end + local psnameid=readpsname and readushort(f) or 0xFFFF + if subfamid==2 or subfamid==17 then + elseif subfamid==0xFFFF then + subfamid=nil + elseif subfamid<=256 or subfamid>=32768 then + subfamid=nil + end + if psnameid==6 then + elseif psnameid==0xFFFF then + psnameid=nil + elseif psnameid<=256 or psnameid>=32768 then + psnameid=nil + end + instances[i]={ + subfamily=extras[subfamid], + psname=psnameid and extras[psnameid] or nil, + values=values, + } + if skippable>0 then + skipbytes(f,skippable) + end end + setvariabledata(fontdata,"axis",axis) + setvariabledata(fontdata,"instances",instances) end end -function readers.svg(f,fontdata,specification) - local datatable=fontdata.tables.svg - if datatable then - if specification.glyphs then - local tableoffset=datatable.offset - setposition(f,tableoffset) - local version=readushort(f) - if version~=0 then - report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"svg",fontdata.filename) - return - end - local glyphs=fontdata.glyphs - local indexoffset=tableoffset+readulong(f) - local reserved=readulong(f) - setposition(f,indexoffset) - local nofentries=readushort(f) - local entries={} - for i=1,nofentries do - entries[i]={ - first=readushort(f), - last=readushort(f), - offset=indexoffset+readulong(f), - length=readulong(f), - } +function readers.hvar(f,fontdata,specification) + local factors=specification.factors + if not factors then + return + end + local tableoffset=gotodatatable(f,fontdata,"hvar",specification.variable) + if not tableoffset then + return + end + local version=readulong(f) + local variationoffset=tableoffset+readulong(f) + local advanceoffset=tableoffset+readulong(f) + local lsboffset=tableoffset+readulong(f) + local rsboffset=tableoffset+readulong(f) + local regions={} + local variations={} + local innerindex={} + local outerindex={} + if variationoffset>0 then + regions,deltas=readvariationdata(f,variationoffset,factors) + end + if not regions then + return + end + if advanceoffset>0 then + setposition(f,advanceoffset) + local format=readushort(f) + local mapcount=readushort(f) + local entrysize=rshift(band(format,0x0030),4)+1 + local nofinnerbits=band(format,0x000F)+1 + local innermask=lshift(1,nofinnerbits)-1 + local readcardinal=read_cardinal[entrysize] + for i=0,mapcount-1 do + local mapdata=readcardinal(f) + outerindex[i]=rshift(mapdata,nofinnerbits) + innerindex[i]=band(mapdata,innermask) + end + local glyphs=fontdata.glyphs + for i=0,fontdata.nofglyphs-1 do + local glyph=glyphs[i] + local width=glyph.width + if width then + local outer=outerindex[i] or 0 + local inner=innerindex[i] or i + if outer and inner then + local delta=deltas[outer+1] + if delta then + local d=delta.deltas[inner+1] + if d then + local scales=delta.scales + local deltaw=0 + for i=1,#scales do + local di=d[i] + if di then + deltaw=deltaw+scales[i]*di + else + break + end + end + glyph.width=width+round(deltaw) + end + end + end end - for i=1,nofentries do - local entry=entries[i] - setposition(f,entry.offset) - entries[i]={ - first=entry.first, - last=entry.last, - data=readstring(f,entry.length) - } + end + end +end +function readers.vvar(f,fontdata,specification) + if not specification.variable then + return + end +end +function readers.mvar(f,fontdata,specification) + local tableoffset=gotodatatable(f,fontdata,"mvar",specification.variable) + if tableoffset then + local version=readulong(f) + local reserved=skipshort(f,1) + local recordsize=readushort(f) + local nofrecords=readushort(f) + local offsettostore=tableoffset+readushort(f) + local dimensions={} + local factors=specification.factors + if factors then + local regions,deltas=readvariationdata(f,offsettostore,factors) + for i=1,nofrecords do + local tag=readtag(f) + local var=variabletags[tag] + if var then + local outer=readushort(f) + local inner=readushort(f) + local delta=deltas[outer+1] + if delta then + local d=delta.deltas[inner+1] + if d then + local scales=delta.scales + local dd=0 + for i=1,#scales do + dd=dd+scales[i]*d[i] + end + var(fontdata,round(dd)) + end + end + else + skipshort(f,2) + end + if recordsize>8 then + skipbytes(recordsize-8) + end end - fontdata.svgshapes=entries end - fontdata.hascolor=true end end @@ -15015,6 +16667,7 @@ function readers.pack(data) local sublookups=resources.sublookups local features=resources.features local palettes=resources.colorpalettes + local variable=resources.variabledata local chardata=characters and characters.data local descriptions=data.descriptions or data.glyphs if not descriptions then @@ -15166,6 +16819,46 @@ function readers.pack(data) end end end + if variable then + local instances=variable.instances + if instances then + for i=1,#instances do + local v=instances[i].values + for j=1,#v do + v[j]=pack_normal(v[j]) + end + end + end + local function packdeltas(main) + if main then + local deltas=main.deltas + if deltas then + for i=1,#deltas do + local di=deltas[i] + local d=di.deltas + local r=di.regions + for j=1,#d do + d[j]=pack_indexed(d[j]) + end + di.regions=pack_indexed(di.regions) + end + end + local regions=main.regions + if regions then + for i=1,#regions do + local r=regions[i] + for j=1,#r do + r[j]=pack_normal(r[j]) + end + end + end + end + end + packdeltas(variable.global) + packdeltas(variable.horizontal) + packdeltas(variable.vertical) + packdeltas(variable.metrics) + end if not success(1,pass) then return end @@ -15229,7 +16922,19 @@ function readers.pack(data) if sublookups then packthem(sublookups) end - if not success(2,pass) then + if variable then + local function unpackdeltas(main) + if main then + local regions=main.regions + if regions then + main.regions=pack_normal(regions) + end + end + end + unpackdeltas(variable.global) + unpackdeltas(variable.horizontal) + unpackdeltas(variable.vertical) + unpackdeltas(variable.metrics) end end for pass=1,2 do @@ -15287,6 +16992,7 @@ function readers.unpack(data) local sublookups=resources.sublookups local features=resources.features local palettes=resources.colorpalettes + local variable=resources.variabledata local unpacked={} setmetatable(unpacked,unpacked_mt) for unicode,description in next,descriptions do @@ -15543,6 +17249,63 @@ function readers.unpack(data) end end end + if variable then + local instances=variable.instances + if instances then + for i=1,#instances do + local v=instances[i].values + for j=1,#v do + local tv=tables[v[j]] + if tv then + v[j]=tv + end + end + end + end + local function unpackdeltas(main) + if main then + local deltas=main.deltas + if deltas then + for i=1,#deltas do + local di=deltas[i] + local d=di.deltas + local r=di.regions + for j=1,#d do + local tv=tables[d[j]] + if tv then + d[j]=tv + end + end + local tv=di.regions + if tv then + di.regions=tv + end + end + end + local regions=main.regions + if regions then + local tv=tables[regions] + if tv then + main.regions=tv + regions=tv + end + for i=1,#regions do + local r=regions[i] + for j=1,#r do + local tv=tables[r[j]] + if tv then + r[j]=tv + end + end + end + end + end + end + unpackdeltas(variable.global) + unpackdeltas(variable.horizontal) + unpackdeltas(variable.vertical) + unpackdeltas(variable.metrics) + end data.tables=nil end end @@ -15995,7 +17758,7 @@ local trace_defining=false registertracker("fonts.defining",function(v) trace_de local report_otf=logs.reporter("fonts","otf loading") local fonts=fonts local otf=fonts.handlers.otf -otf.version=3.027 +otf.version=3.028 otf.cache=containers.define("fonts","otl",otf.version,true) otf.svgcache=containers.define("fonts","svg",otf.version,true) otf.pdfcache=containers.define("fonts","pdf",otf.version,true) @@ -16025,16 +17788,12 @@ registerdirective("fonts.otf.loader.force",function(v) forceload=v end) registerdirective("fonts.otf.loader.syncspace",function(v) syncspace=v end) registerdirective("fonts.otf.loader.forcenotdef",function(v) forcenotdef=v end) registerotfenhancer("check extra features",function() end) -function otf.load(filename,sub,featurefile) - local featurefile=nil +function otf.load(filename,sub,instance) local base=file.basename(file.removesuffix(filename)) - local name=file.removesuffix(base) + local name=file.removesuffix(base) local attr=lfs.attributes(filename) local size=attr and attr.size or 0 local time=attr and attr.modification or 0 - if featurefile then - name=name.."@"..file.removesuffix(file.basename(featurefile)) - end if sub=="" then sub=false end @@ -16042,6 +17801,9 @@ function otf.load(filename,sub,featurefile) if sub then hash=hash.."-"..sub end + if instance then + hash=hash.."-"..instance + end hash=containers.cleanname(hash) local data=containers.read(otf.cache,hash) local reload=not data or data.size~=size or data.time~=time or data.tableversion~=otfreaders.tableversion @@ -16052,7 +17814,7 @@ function otf.load(filename,sub,featurefile) if reload then report_otf("loading %a, hash %a",filename,hash) starttiming(otfreaders) - data=otfreaders.loadfont(filename,sub or 1) + data=otfreaders.loadfont(filename,sub or 1,instance) if data then local resources=data.resources local svgshapes=resources.svgshapes @@ -16351,7 +18113,8 @@ local function otftotfm(specification) local subindex=specification.subindex local filename=specification.filename local features=specification.features.normal - local rawdata=otf.load(filename,sub,features and features.featurefile) + local instance=specification.instance or (features and features.axis) + local rawdata=otf.load(filename,sub,instance) if rawdata and next(rawdata) then local descriptions=rawdata.descriptions rawdata.lookuphash={} @@ -24619,9 +26382,14 @@ local function addfeature(data,feature,specifications) end return coverage end + local function resetspacekerns() + data.properties.hasspacekerns=true + data.resources .spacekerns=nil + end local function prepare_kern(list,featuretype) local coverage={} local cover=coveractions[featuretype] + local isspace=false for code,replacement in next,list do local unicode=tounicode(code) local description=descriptions[unicode] @@ -24631,11 +26399,17 @@ local function addfeature(data,feature,specifications) local u=tounicode(k) if u then r[u]=v + if u==32 then + isspace=true + end end end if next(r) then cover(coverage,unicode,r) done=done+1 + if unicode==32 then + isspace=true + end else skip=skip+1 end @@ -24643,6 +26417,9 @@ local function addfeature(data,feature,specifications) skip=skip+1 end end + if isspace then + resetspacekerns() + end return coverage end local function prepare_pair(list,featuretype) @@ -24658,11 +26435,17 @@ local function addfeature(data,feature,specifications) local u=tounicode(k) if u then r[u]=v + if u==32 then + isspace=true + end end end if next(r) then cover(coverage,unicode,r) done=done+1 + if unicode==32 then + isspace=true + end else skip=skip+1 end @@ -24670,6 +26453,9 @@ local function addfeature(data,feature,specifications) skip=skip+1 end end + if isspace then + resetspacekerns() + end else report_otf("unknown cover type %a",featuretype) end @@ -25242,7 +27028,7 @@ do } }, } - fonts.handlers.otf.readers.parsecharstrings(data,glyphs,true,true) + fonts.handlers.otf.readers.parsecharstrings(false,data,glyphs,true,true) else lpegmatch(p_filternames,binary) end @@ -26983,11 +28769,28 @@ end function resolvers.name(specification) local resolve=fonts.names.resolve if resolve then - local resolved,sub,subindex=resolve(specification.name,specification.sub,specification) + local resolved,sub,subindex,instance=resolve(specification.name,specification.sub,specification) if resolved then specification.resolved=resolved specification.sub=sub specification.subindex=subindex + if instance then + specification.instance=instance + local features=specification.features + if not features then + features={} + specification.features=features + end + local normal=features.normal + if not normal then + normal={} + features.normal=normal + end + normal.instance=instance +if not callbacks.supported.glyph_stream_provider then + normal.variableshapes=true +end + end local suffix=lower(suffixonly(resolved)) if fonts.formats[suffix] then specification.forced=suffix -- cgit v1.2.3