diff options
author | Hans Hagen <pragma@wxs.nl> | 2017-03-20 18:51:53 +0100 |
---|---|---|
committer | Context Git Mirror Bot <phg42.2a@gmail.com> | 2017-03-20 18:51:53 +0100 |
commit | 8e0089484acf80066e7393b1245d59dda211be09 (patch) | |
tree | a444a174ea8de55816cb4a9c3def00fb4521e7d8 /tex/context/base | |
parent | 8f5c555274eb48fcaaa3d7f340ee77710846fb7e (diff) | |
download | context-8e0089484acf80066e7393b1245d59dda211be09.tar.gz |
2017-03-20 17:38:00
Diffstat (limited to 'tex/context/base')
35 files changed, 4403 insertions, 1399 deletions
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 <feff%04x> >> BDC %s EMC ET"] - local f_actual_text_two = formatters["BT /Span << /ActualText <feff%04x%04x> >> BDC %s EMC ET"] - local f_actual_text_one_b = formatters["BT /Span << /ActualText <feff%04x> >> BDC"] - local f_actual_text_two_b = formatters["BT /Span << /ActualText <feff%04x%04x> >> BDC"] - local f_actual_text_b = formatters["BT /Span << /ActualText <feff%s> >> BDC"] - local s_actual_text_e = "EMC ET" - local f_actual_text_b_not = formatters["/Span << /ActualText <feff%s> >> BDC"] - local s_actual_text_e_not = "EMC" - local f_actual_text = formatters["/Span <</ActualText %s >> BDC"] + local f_actual_text_one = formatters["BT /Span << /ActualText <feff%04x> >> BDC %s EMC ET"] + local f_actual_text_two = formatters["BT /Span << /ActualText <feff%04x%04x> >> BDC %s EMC ET"] + local f_actual_text_one_b = formatters["BT /Span << /ActualText <feff%04x> >> BDC"] + local f_actual_text_two_b = formatters["BT /Span << /ActualText <feff%04x%04x> >> BDC"] + local f_actual_text_b = formatters["BT /Span << /ActualText <feff%s> >> BDC"] + local s_actual_text_e = "EMC ET" + local f_actual_text_b_not = formatters["/Span << /ActualText <feff%s> >> BDC"] + local f_actual_text_one_b_not = formatters["/Span << /ActualText <feff%04x> >> BDC"] + local f_actual_text_two_b_not = formatters["/Span << /ActualText <feff%04x%04x> >> BDC"] + local s_actual_text_e_not = "EMC" + local f_actual_text = formatters["/Span <</ActualText %s >> 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 Binary files differindex dbcb86a1f..641067bfc 100644 --- a/tex/context/base/mkiv/status-files.pdf +++ b/tex/context/base/mkiv/status-files.pdf diff --git a/tex/context/base/mkiv/status-lua.pdf b/tex/context/base/mkiv/status-lua.pdf Binary files differindex 637ce005e..6c11b6984 100644 --- a/tex/context/base/mkiv/status-lua.pdf +++ b/tex/context/base/mkiv/status-lua.pdf 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 |