diff options
Diffstat (limited to 'tex/context/base/mkxl/font-cff.lmt')
-rw-r--r-- | tex/context/base/mkxl/font-cff.lmt | 2788 |
1 files changed, 2788 insertions, 0 deletions
diff --git a/tex/context/base/mkxl/font-cff.lmt b/tex/context/base/mkxl/font-cff.lmt new file mode 100644 index 000000000..dc5f98382 --- /dev/null +++ b/tex/context/base/mkxl/font-cff.lmt @@ -0,0 +1,2788 @@ +if not modules then modules = { } end modules ['font-cff'] = { + version = 1.001, + optimize = true, + comment = "companion to font-ini.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +-- todo: option.outlines +-- todo: option.boundingbox +-- per charstring (less memory) + +-- 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 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, rawget = next, type, tonumber, rawget +local byte, char, gmatch, sub = string.byte, string.char, string.gmatch, string.sub +local concat, insert, remove, unpack = table.concat, table.insert, table.remove, table.unpack +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 +local lpegmatch = lpeg.match +local formatters = string.formatters +local bytetable = string.bytetable +----- rshift, band, extract = bit32.rshift, bit32.band, bit32.extract + +local readers = fonts.handlers.otf.readers +local streamreader = readers.streamreader + +local readstring = streamreader.readstring +local readbyte = streamreader.readcardinal1 -- 8-bit unsigned integer +local readushort = streamreader.readcardinal2 -- 16-bit unsigned integer +local readuint = streamreader.readcardinal3 -- 24-bit unsigned integer +local readulong = streamreader.readcardinal4 -- 32-bit unsigned integer +local setposition = streamreader.setposition +local getposition = streamreader.getposition +local readbytetable = streamreader.readbytetable + +directives.register("fonts.streamreader",function() + + streamreader = utilities.streams + + readstring = streamreader.readstring + readbyte = streamreader.readcardinal1 + readushort = streamreader.readcardinal2 + readuint = streamreader.readcardinal3 + readulong = streamreader.readcardinal4 + setposition = streamreader.setposition + getposition = streamreader.getposition + readbytetable = streamreader.readbytetable + +end) + +local setmetatableindex = table.setmetatableindex + +local trace_charstrings = false trackers.register("fonts.cff.charstrings",function(v) trace_charstrings = v end) +local report = logs.reporter("otf reader","cff") + +local parsedictionaries +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", + "ampersand", "quoteright", "parenleft", "parenright", "asterisk", "plus", + "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four", + "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", + "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H", + "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", + "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", + "underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", + "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", + "z", "braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent", + "sterling", "fraction", "yen", "florin", "section", "currency", + "quotesingle", "quotedblleft", "guillemotleft", "guilsinglleft", + "guilsinglright", "fi", "fl", "endash", "dagger", "daggerdbl", + "periodcentered", "paragraph", "bullet", "quotesinglbase", "quotedblbase", + "quotedblright", "guillemotright", "ellipsis", "perthousand", "questiondown", + "grave", "acute", "circumflex", "tilde", "macron", "breve", "dotaccent", + "dieresis", "ring", "cedilla", "hungarumlaut", "ogonek", "caron", "emdash", + "AE", "ordfeminine", "Lslash", "Oslash", "OE", "ordmasculine", "ae", + "dotlessi", "lslash", "oslash", "oe", "germandbls", "onesuperior", + "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus", "Thorn", + "onequarter", "divide", "brokenbar", "degree", "thorn", "threequarters", + "twosuperior", "registered", "minus", "eth", "multiply", "threesuperior", + "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave", "Aring", + "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave", + "Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute", + "Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute", + "Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", + "aacute", "acircumflex", "adieresis", "agrave", "aring", "atilde", + "ccedilla", "eacute", "ecircumflex", "edieresis", "egrave", "iacute", + "icircumflex", "idieresis", "igrave", "ntilde", "oacute", "ocircumflex", + "odieresis", "ograve", "otilde", "scaron", "uacute", "ucircumflex", + "udieresis", "ugrave", "yacute", "ydieresis", "zcaron", "exclamsmall", + "Hungarumlautsmall", "dollaroldstyle", "dollarsuperior", "ampersandsmall", + "Acutesmall", "parenleftsuperior", "parenrightsuperior", "twodotenleader", + "onedotenleader", "zerooldstyle", "oneoldstyle", "twooldstyle", + "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", + "sevenoldstyle", "eightoldstyle", "nineoldstyle", "commasuperior", + "threequartersemdash", "periodsuperior", "questionsmall", "asuperior", + "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", + "lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", + "tsuperior", "ff", "ffi", "ffl", "parenleftinferior", "parenrightinferior", + "Circumflexsmall", "hyphensuperior", "Gravesmall", "Asmall", "Bsmall", + "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", "Ismall", + "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall", + "Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", + "Xsmall", "Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", + "Tildesmall", "exclamdownsmall", "centoldstyle", "Lslashsmall", + "Scaronsmall", "Zcaronsmall", "Dieresissmall", "Brevesmall", "Caronsmall", + "Dotaccentsmall", "Macronsmall", "figuredash", "hypheninferior", + "Ogoneksmall", "Ringsmall", "Cedillasmall", "questiondownsmall", "oneeighth", + "threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", + "zerosuperior", "foursuperior", "fivesuperior", "sixsuperior", + "sevensuperior", "eightsuperior", "ninesuperior", "zeroinferior", + "oneinferior", "twoinferior", "threeinferior", "fourinferior", + "fiveinferior", "sixinferior", "seveninferior", "eightinferior", + "nineinferior", "centinferior", "dollarinferior", "periodinferior", + "commainferior", "Agravesmall", "Aacutesmall", "Acircumflexsmall", + "Atildesmall", "Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", + "Egravesmall", "Eacutesmall", "Ecircumflexsmall", "Edieresissmall", + "Igravesmall", "Iacutesmall", "Icircumflexsmall", "Idieresissmall", + "Ethsmall", "Ntildesmall", "Ogravesmall", "Oacutesmall", "Ocircumflexsmall", + "Otildesmall", "Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", + "Uacutesmall", "Ucircumflexsmall", "Udieresissmall", "Yacutesmall", + "Thornsmall", "Ydieresissmall", "001.000", "001.001", "001.002", "001.003", + "Black", "Bold", "Book", "Light", "Medium", "Regular", "Roman", "Semibold", +} + +local standardnames = { [0] = -- needed for seac + false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", + "ampersand", "quoteright", "parenleft", "parenright", "asterisk", "plus", + "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four", + "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", + "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H", + "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", + "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", + "underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", + "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", + "z", "braceleft", "bar", "braceright", "asciitilde", false, false, false, + false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, "exclamdown", + "cent", "sterling", "fraction", "yen", "florin", "section", "currency", + "quotesingle", "quotedblleft", "guillemotleft", "guilsinglleft", + "guilsinglright", "fi", "fl", false, "endash", "dagger", "daggerdbl", + "periodcentered", false, "paragraph", "bullet", "quotesinglbase", + "quotedblbase", "quotedblright", "guillemotright", "ellipsis", "perthousand", + false, "questiondown", false, "grave", "acute", "circumflex", "tilde", + "macron", "breve", "dotaccent", "dieresis", false, "ring", "cedilla", false, + "hungarumlaut", "ogonek", "caron", "emdash", false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, + false, "AE", false, "ordfeminine", false, false, false, false, "Lslash", + "Oslash", "OE", "ordmasculine", false, false, false, false, false, "ae", + false, false, false, "dotlessi", false, false, "lslash", "oslash", "oe", + "germandbls", false, false, false, false +} + +local cffreaders = { + readbyte, + readushort, + readuint, + readulong, +} + +directives.register("fonts.streamreader",function() + cffreaders = { + readbyte, + readushort, + readuint, + readulong, + } +end) + +-- The header contains information about its own size. + +local function readheader(f) + local offset = getposition(f) + local major = readbyte(f) + local header = { + offset = offset, + major = major, + minor = readbyte(f), + size = readbyte(f), -- headersize + } + 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 + +-- 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,longcount) + local count = longcount and readulong(f) or readushort(f) + if count == 0 then + return { } + end + local osize = readbyte(f) + local read = cffreaders[osize] + if not read then + report("bad offset size: %i",osize) + return { } + end + local lengths = { } + local previous = read(f) + for i=1,count do + local offset = read(f) + local length = offset - previous + if length < 0 then + report("bad offset: %i",length) + length = 0 + end + lengths[i] = length + previous = offset + end + return lengths +end + +-- There can be subfonts so names is an array. However, in our case it's always +-- one font. The same is true for the top dictionaries. Watch how we only load +-- the dictionary string as for interpretation we need to have the strings loaded +-- as well. + +local function readfontnames(f) + local names = readlengths(f) + for i=1,#names do + names[i] = readstring(f,names[i]) + end + return names +end + +local function readtopdictionaries(f) + local dictionaries = readlengths(f) + for i=1,#dictionaries do + dictionaries[i] = readstring(f,dictionaries[i]) + end + return dictionaries +end + +-- Strings are added to a list of standard strings so we start the font specific +-- one with an offset. Strings are shared so we have one table. + +local function readstrings(f) + local lengths = readlengths(f) + local strings = setmetatableindex({ }, defaultstrings) + local index = #defaultstrings + for i=1,#lengths do + index = index + 1 + strings[index] = readstring(f,lengths[i]) + end + return strings +end + +-- Parsing the dictionaries is delayed till we have the strings loaded. The parser +-- is stack based so the operands come before the operator (like in postscript). + +-- local function delta(t) +-- local n = #t +-- if n > 1 then +-- local p = t[1] +-- for i=2,n do +-- local c = t[i] +-- t[i] = c + p +-- p = c +-- end +-- end +-- end + +do + + -- 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 + local result = { } + local strings = { } + + local p_single = + P("\00") / function() + result.version = strings[stack[top]] or "unset" + top = 0 + end + + P("\01") / function() + result.notice = strings[stack[top]] or "unset" + top = 0 + end + + P("\02") / function() + result.fullname = strings[stack[top]] or "unset" + top = 0 + end + + P("\03") / function() + result.familyname = strings[stack[top]] or "unset" + top = 0 + end + + P("\04") / function() + result.weight = strings[stack[top]] or "unset" + top = 0 + end + + P("\05") / function() + result.fontbbox = { unpack(stack,1,4) } + top = 0 + end + + P("\06") / function() + result.bluevalues = { unpack(stack,1,top) } + top = 0 + end + + P("\07") / function() + result.otherblues = { unpack(stack,1,top) } + top = 0 + end + + P("\08") / function() + result.familyblues = { unpack(stack,1,top) } + top = 0 + end + + P("\09") / function() + result.familyotherblues = { unpack(stack,1,top) } + top = 0 + end + + P("\10") / function() + result.stdhw = stack[top] + top = 0 + end + + P("\11") / function() + result.stdvw = stack[top] + top = 0 + end + + P("\13") / function() + result.uniqueid = stack[top] + top = 0 + end + + P("\14") / function() + result.xuid = concat(stack,"",1,top) + top = 0 + end + + P("\15") / function() + result.charset = stack[top] + top = 0 + end + + P("\16") / function() + result.encoding = stack[top] + top = 0 + end + + P("\17") / function() -- valid cff2 + result.charstrings = stack[top] + top = 0 + end + + P("\18") / function() + result.private = { + size = stack[top-1], + offset = stack[top], + } + top = 0 + 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("\26") / function() -- reserved + -- end + -- + P("\27") / function() -- reserved + -- end + + local p_double = P("\12") * ( + P("\00") / function() + result.copyright = stack[top] + top = 0 + end + + P("\01") / function() + result.monospaced = stack[top] == 1 and true or false -- isfixedpitch + top = 0 + end + + P("\02") / function() + result.italicangle = stack[top] + top = 0 + end + + P("\03") / function() + result.underlineposition = stack[top] + top = 0 + end + + P("\04") / function() + result.underlinethickness = stack[top] + top = 0 + end + + P("\05") / function() + result.painttype = stack[top] + top = 0 + end + + P("\06") / function() + result.charstringtype = stack[top] + top = 0 + end + + P("\07") / function() -- valid cff2 + result.fontmatrix = { unpack(stack,1,6) } + top = 0 + end + + P("\08") / function() + result.strokewidth = stack[top] + top = 0 + end + + P("\09") / function() + result.bluescale = stack[top] + top = 0 + end + + P("\10") / function() + result.blueshift = stack[top] + top = 0 + end + + P("\11") / function() + result.bluefuzz = stack[top] + top = 0 + end + + P("\12") / function() + result.stemsnaph = { unpack(stack,1,top) } + top = 0 + end + + P("\13") / function() + result.stemsnapv = { unpack(stack,1,top) } + top = 0 + end + + P("\20") / function() + result.syntheticbase = stack[top] + top = 0 + end + + P("\21") / function() + result.postscript = strings[stack[top]] or "unset" + top = 0 + end + + P("\22") / function() + result.basefontname = strings[stack[top]] or "unset" + top = 0 + end + + P("\21") / function() + result.basefontblend = stack[top] + top = 0 + end + + P("\30") / function() + result.cid.registry = strings[stack[top-2]] or "unset" + result.cid.ordering = strings[stack[top-1]] or "unset" + result.cid.supplement = stack[top] + top = 0 + end + + P("\31") / function() + result.cid.fontversion = stack[top] + top = 0 + end + + P("\32") / function() + result.cid.fontrevision= stack[top] + top = 0 + end + + P("\33") / function() + result.cid.fonttype = stack[top] + top = 0 + end + + P("\34") / function() + result.cid.count = stack[top] + top = 0 + end + + P("\35") / function() + result.cid.uidbase = stack[top] + top = 0 + end + + P("\36") / function() -- valid cff2 + result.cid.fdarray = stack[top] + top = 0 + end + + P("\37") / function() -- valid cff2 + result.cid.fdselect = stack[top] + top = 0 + end + + P("\38") / function() + result.cid.fontname = strings[stack[top]] or "unset" + top = 0 + end + ) + + -- Some lpeg fun ... a first variant split the byte and made a new string but + -- the second variant is much faster. Not that it matters much as we don't see + -- such numbers often. + + local remap_1 = { + ["\x00"] = "00", ["\x01"] = "01", ["\x02"] = "02", ["\x03"] = "03", ["\x04"] = "04", ["\x05"] = "05", ["\x06"] = "06", ["\x07"] = "07", ["\x08"] = "08", ["\x09"] = "09", ["\x0A"] = "0.", ["\x0B"] = "0E", ["\x0C"] = "0E-", ["\x0D"] = "0", ["\x0E"] = "0-", ["\x0F"] = "0", + ["\x10"] = "10", ["\x11"] = "11", ["\x12"] = "12", ["\x13"] = "13", ["\x14"] = "14", ["\x15"] = "15", ["\x16"] = "16", ["\x17"] = "17", ["\x18"] = "18", ["\x19"] = "19", ["\x1A"] = "1.", ["\x1B"] = "1E", ["\x1C"] = "1E-", ["\x1D"] = "1", ["\x1E"] = "1-", ["\x1F"] = "1", + ["\x20"] = "20", ["\x21"] = "21", ["\x22"] = "22", ["\x23"] = "23", ["\x24"] = "24", ["\x25"] = "25", ["\x26"] = "26", ["\x27"] = "27", ["\x28"] = "28", ["\x29"] = "29", ["\x2A"] = "2.", ["\x2B"] = "2E", ["\x2C"] = "2E-", ["\x2D"] = "2", ["\x2E"] = "2-", ["\x2F"] = "2", + ["\x30"] = "30", ["\x31"] = "31", ["\x32"] = "32", ["\x33"] = "33", ["\x34"] = "34", ["\x35"] = "35", ["\x36"] = "36", ["\x37"] = "37", ["\x38"] = "38", ["\x39"] = "39", ["\x3A"] = "3.", ["\x3B"] = "3E", ["\x3C"] = "3E-", ["\x3D"] = "3", ["\x3E"] = "3-", ["\x3F"] = "3", + ["\x40"] = "40", ["\x41"] = "41", ["\x42"] = "42", ["\x43"] = "43", ["\x44"] = "44", ["\x45"] = "45", ["\x46"] = "46", ["\x47"] = "47", ["\x48"] = "48", ["\x49"] = "49", ["\x4A"] = "4.", ["\x4B"] = "4E", ["\x4C"] = "4E-", ["\x4D"] = "4", ["\x4E"] = "4-", ["\x4F"] = "4", + ["\x50"] = "50", ["\x51"] = "51", ["\x52"] = "52", ["\x53"] = "53", ["\x54"] = "54", ["\x55"] = "55", ["\x56"] = "56", ["\x57"] = "57", ["\x58"] = "58", ["\x59"] = "59", ["\x5A"] = "5.", ["\x5B"] = "5E", ["\x5C"] = "5E-", ["\x5D"] = "5", ["\x5E"] = "5-", ["\x5F"] = "5", + ["\x60"] = "60", ["\x61"] = "61", ["\x62"] = "62", ["\x63"] = "63", ["\x64"] = "64", ["\x65"] = "65", ["\x66"] = "66", ["\x67"] = "67", ["\x68"] = "68", ["\x69"] = "69", ["\x6A"] = "6.", ["\x6B"] = "6E", ["\x6C"] = "6E-", ["\x6D"] = "6", ["\x6E"] = "6-", ["\x6F"] = "6", + ["\x70"] = "70", ["\x71"] = "71", ["\x72"] = "72", ["\x73"] = "73", ["\x74"] = "74", ["\x75"] = "75", ["\x76"] = "76", ["\x77"] = "77", ["\x78"] = "78", ["\x79"] = "79", ["\x7A"] = "7.", ["\x7B"] = "7E", ["\x7C"] = "7E-", ["\x7D"] = "7", ["\x7E"] = "7-", ["\x7F"] = "7", + ["\x80"] = "80", ["\x81"] = "81", ["\x82"] = "82", ["\x83"] = "83", ["\x84"] = "84", ["\x85"] = "85", ["\x86"] = "86", ["\x87"] = "87", ["\x88"] = "88", ["\x89"] = "89", ["\x8A"] = "8.", ["\x8B"] = "8E", ["\x8C"] = "8E-", ["\x8D"] = "8", ["\x8E"] = "8-", ["\x8F"] = "8", + ["\x90"] = "90", ["\x91"] = "91", ["\x92"] = "92", ["\x93"] = "93", ["\x94"] = "94", ["\x95"] = "95", ["\x96"] = "96", ["\x97"] = "97", ["\x98"] = "98", ["\x99"] = "99", ["\x9A"] = "9.", ["\x9B"] = "9E", ["\x9C"] = "9E-", ["\x9D"] = "9", ["\x9E"] = "9-", ["\x9F"] = "9", + ["\xA0"] = ".0", ["\xA1"] = ".1", ["\xA2"] = ".2", ["\xA3"] = ".3", ["\xA4"] = ".4", ["\xA5"] = ".5", ["\xA6"] = ".6", ["\xA7"] = ".7", ["\xA8"] = ".8", ["\xA9"] = ".9", ["\xAA"] = "..", ["\xAB"] = ".E", ["\xAC"] = ".E-", ["\xAD"] = ".", ["\xAE"] = ".-", ["\xAF"] = ".", + ["\xB0"] = "E0", ["\xB1"] = "E1", ["\xB2"] = "E2", ["\xB3"] = "E3", ["\xB4"] = "E4", ["\xB5"] = "E5", ["\xB6"] = "E6", ["\xB7"] = "E7", ["\xB8"] = "E8", ["\xB9"] = "E9", ["\xBA"] = "E.", ["\xBB"] = "EE", ["\xBC"] = "EE-", ["\xBD"] = "E", ["\xBE"] = "E-", ["\xBF"] = "E", + ["\xC0"] = "E-0", ["\xC1"] = "E-1", ["\xC2"] = "E-2", ["\xC3"] = "E-3", ["\xC4"] = "E-4", ["\xC5"] = "E-5", ["\xC6"] = "E-6", ["\xC7"] = "E-7", ["\xC8"] = "E-8", ["\xC9"] = "E-9", ["\xCA"] = "E-.", ["\xCB"] = "E-E", ["\xCC"] = "E-E-", ["\xCD"] = "E-", ["\xCE"] = "E--", ["\xCF"] = "E-", + ["\xD0"] = "-0", ["\xD1"] = "-1", ["\xD2"] = "-2", ["\xD3"] = "-3", ["\xD4"] = "-4", ["\xD5"] = "-5", ["\xD6"] = "-6", ["\xD7"] = "-7", ["\xD8"] = "-8", ["\xD9"] = "-9", ["\xDA"] = "-.", ["\xDB"] = "-E", ["\xDC"] = "-E-", ["\xDD"] = "-", ["\xDE"] = "--", ["\xDF"] = "-", + } + local remap_2 = { + ["\x0F"] = "0", ["\x1F"] = "1", ["\x2F"] = "2", ["\x3F"] = "3", ["\x4F"] = "4", + ["\x5F"] = "5", ["\x6F"] = "6", ["\x7F"] = "7", ["\x8F"] = "8", ["\x9F"] = "9", + } + + local p_last_1 = S("\x0F\x1F\x2F\x3F\x4F\x5F\x6F\x7F\x8F\x9F\xAF\xBF") + local p_last_2 = R("\xF0\xFF") + + -- tricky, we don't want to append last + + -- local p_nibbles = P("\30") * Cs(((1-p_last)/remap)^0 * (P(1)/remap)) / function(n) + local p_nibbles = P("\30") * Cs(((1-(p_last_1+p_last_2))/remap_1)^0 * (p_last_1/remap_2 + p_last_2/"")) / function(n) + -- 0-9=digit a=. b=E c=E- d=reserved e=- f=finish + top = top + 1 + stack[top] = tonumber(n) or 0 + end + + local p_byte = C(R("\32\246")) / function(b0) + -- -107 .. +107 + top = top + 1 + stack[top] = byte(b0) - 139 + end + + local p_positive = C(R("\247\250")) * C(1) / function(b0,b1) + -- +108 .. +1131 + top = top + 1 + stack[top] = (byte(b0)-247)*256 + byte(b1) + 108 + end + + local p_negative = C(R("\251\254")) * C(1) / function(b0,b1) + -- -1131 .. -108 + top = top + 1 + stack[top] = -(byte(b0)-251)*256 - byte(b1) - 108 + end + + -- local p_float = P("\255") * C(1) * C(1) * C(1) * C(1) / function(b0,b1,b2,b3) + -- top = top + 1 + -- stack[top] = 0 + -- end + + local p_short = P("\28") * C(1) * C(1) / function(b1,b2) + -- -32768 .. +32767 : b1<<8 | b2 + top = top + 1 + local n = 0x100 * byte(b1) + byte(b2) + if n >= 0x8000 then + stack[top] = n - 0xFFFF - 1 + else + stack[top] = n + end + end + + local p_long = P("\29") * C(1) * C(1) * C(1) * C(1) / function(b1,b2,b3,b4) + -- -2^31 .. +2^31-1 : b1<<24 | b2<<16 | b3<<8 | b4 + top = top + 1 + local n = 0x1000000 * byte(b1) + 0x10000 * byte(b2) + 0x100 * byte(b3) + byte(b4) + if n >= 0x8000000 then + stack[top] = n - 0xFFFFFFFF - 1 + else + stack[top] = n + end + end + + local p_unsupported = P(1) / function(detail) + top = 0 + end + + local p_dictionary = ( + p_byte + + p_positive + + p_negative + + p_short + + p_long + + p_nibbles + + p_single + + p_double + -- + p_float + + p_unsupported + )^1 + + parsedictionaries = function(data,dictionaries,version) + stack = { } + strings = data.strings + if trace_charstrings then + report("charstring format %a",version) + end + for i=1,#dictionaries do + top = 0 + result = version == "cff" and { + monospaced = false, + italicangle = 0, + underlineposition = -100, + underlinethickness = 50, + painttype = 0, + charstringtype = 2, + fontmatrix = { 0.001, 0, 0, 0.001, 0, 0 }, + fontbbox = { 0, 0, 0, 0 }, + strokewidth = 0, + charset = 0, + encoding = 0, + cid = { + fontversion = 0, + fontrevision = 0, + fonttype = 0, + count = 8720, + } + } or { + charstringtype = 2, + charset = 0, + vstore = 0, + cid = { + -- nothing yet + }, + } + lpegmatch(p_dictionary,dictionaries[i]) + dictionaries[i] = result + end + -- + result = { } + top = 0 + stack = { } + end + + parseprivates = function(data,dictionaries) + stack = { } + strings = data.strings + for i=1,#dictionaries do + local private = dictionaries[i].private + if private and private.data then + top = 0 + result = { + forcebold = false, + languagegroup = 0, + expansionfactor = 0.06, + initialrandomseed = 0, + subroutines = 0, + defaultwidthx = 0, + nominalwidthx = 0, + cid = { + -- actually an error + }, + } + lpegmatch(p_dictionary,private.data) + private.data = result + end + end + result = { } + top = 0 + stack = { } + end + + -- All bezier curves have 6 points with successive pairs relative to + -- the previous pair. Some can be left out and are then copied or zero + -- (optimization). + -- + -- We are not really interested in all the details of a glyph because we + -- only need to calculate the boundingbox. So, todo: a quick no result but + -- calculate only variant. + -- + -- The conversion is straightforward and the specification os clear once + -- you understand that the x and y needs to be updates each step. It's also + -- quite easy to test because in mp a shape will look bad when a few variables + -- are swapped. But still there might be bugs down here because not all + -- variants are seen in a font so far. We are less compact that the ff code + -- 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 lsb = 0 + local result = { } + 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 justpass = false + local seacs = { } + local procidx = nil + + local function showstate(where,i,n) + if i then + local j = i + n - 1 + report("%w%-10s : [%s] step",depth*2+2,where,concat(stack," ",i,j <= top and j or top)) + else + report("%w%-10s : [%s] n=%i",depth*2,where,concat(stack," ",1,top),top) + end + end + + local function showvalue(where,value,showstack) + if showstack then + report("%w%-10s : %s : [%s] n=%i",depth*2,where,tostring(value),concat(stack," ",1,top),top) + else + report("%w%-10s : %s",depth*2,where,tostring(value)) + end + end + + -- All these indirect calls make this run slower but it's cleaner this way + -- and we cache the result. As we moved the boundingbox code inline we gain + -- some back. I inlined some of then and a bit speed can be gained by more + -- inlining but not that much. + + -- Maybe have several action tables: + -- + -- keep curve / checked + -- keep curve / not checked + -- checked + -- not checked + + local function xymoveto() + if keepcurve then + r = r + 1 + result[r] = { x, y, "m" } + end + if checked then + if x > xmax then xmax = x elseif x < xmin then xmin = x end + if y > ymax then ymax = y elseif y < ymin then ymin = y end + else + xmin = x + ymin = y + xmax = x + ymax = y + checked = true + end + end + + local function xmoveto() -- slight speedup + if keepcurve then + r = r + 1 + result[r] = { x, y, "m" } + end + if not checked then + xmin = x + ymin = y + xmax = x + ymax = y + checked = true + elseif x > xmax then + xmax = x + elseif x < xmin then + xmin = x + end + end + + local function ymoveto() -- slight speedup + if keepcurve then + r = r + 1 + result[r] = { x, y, "m" } + end + if not checked then + xmin = x + ymin = y + xmax = x + ymax = y + checked = true + elseif y > ymax then + ymax = y + elseif y < ymin then + ymin = y + end + end + + 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" } + end + if checked then + if x > xmax then xmax = x elseif x < xmin then xmin = x end + if y > ymax then ymax = y elseif y < ymin then ymin = y end + else + xmin = x + ymin = y + xmax = x + ymax = y + checked = true + end + end + + local function xlineto() -- slight speedup + if keepcurve then + r = r + 1 + result[r] = { x, y, "l" } + end + if not checked then + xmin = x + ymin = y + xmax = x + ymax = y + checked = true + elseif x > xmax then + xmax = x + elseif x < xmin then + xmin = x + end + end + + local function ylineto() -- slight speedup + if keepcurve then + r = r + 1 + result[r] = { x, y, "l" } + end + if not checked then + xmin = x + ymin = y + xmax = x + ymax = y + checked = true + elseif y > ymax then + ymax = y + elseif y < ymin then + ymin = y + end + end + + local function xycurveto(x1,y1,x2,y2,x3,y3,i,n) -- called local so no blend here + if trace_charstrings then + showstate("curveto",i,n) + end + if keepcurve then + r = r + 1 + result[r] = { x1, y1, x2, y2, x3, y3, "c" } + end + if checked then + if x1 > xmax then xmax = x1 elseif x1 < xmin then xmin = x1 end + if y1 > ymax then ymax = y1 elseif y1 < ymin then ymin = y1 end + else + xmin = x1 + ymin = y1 + xmax = x1 + ymax = y1 + checked = true + end + if x2 > xmax then xmax = x2 elseif x2 < xmin then xmin = x2 end + if y2 > ymax then ymax = y2 elseif y2 < ymin then ymin = y2 end + if x3 > xmax then xmax = x3 elseif x3 < xmin then xmin = x3 end + if y3 > ymax then ymax = y3 elseif y3 < ymin then ymin = y3 end + end + + local function rmoveto() + if not width then + if top > 2 then + width = stack[1] + if trace_charstrings then + showvalue("backtrack width",width) + end + else + width = true + end + end + if trace_charstrings then + showstate("rmoveto") + end + x = x + stack[top-1] -- dx1 + y = y + stack[top] -- dy1 + top = 0 + xymoveto() + end + + local function hmoveto() + if not width then + if top > 1 then + width = stack[1] + if trace_charstrings then + showvalue("backtrack width",width) + end + else + width = true + end + end + if trace_charstrings then + showstate("hmoveto") + end + x = x + stack[top] -- dx1 + top = 0 + xmoveto() + end + + local function vmoveto() + if not width then + if top > 1 then + width = stack[1] + if trace_charstrings then + showvalue("backtrack width",width) + end + else + width = true + end + end + if trace_charstrings then + showstate("vmoveto") + end + y = y + stack[top] -- dy1 + top = 0 + ymoveto() + end + + local function rlineto() + if trace_charstrings then + showstate("rlineto") + end + for i=1,top,2 do + x = x + stack[i] -- dxa + y = y + stack[i+1] -- dya + xylineto() + end + top = 0 + end + + local function hlineto() -- x (y,x)+ | (x,y)+ + if trace_charstrings then + showstate("hlineto") + end + if top == 1 then + x = x + stack[1] + xlineto() + else + local swap = true + for i=1,top do + if swap then + x = x + stack[i] + xlineto() + swap = false + else + y = y + stack[i] + ylineto() + swap = true + end + end + end + top = 0 + end + + local function vlineto() -- y (x,y)+ | (y,x)+ + if trace_charstrings then + showstate("vlineto") + end + if top == 1 then + y = y + stack[1] + ylineto() + else + local swap = false + for i=1,top do + if swap then + x = x + stack[i] + xlineto() + swap = false + else + y = y + stack[i] + ylineto() + swap = true + end + end + end + top = 0 + end + + local function rrcurveto() + if trace_charstrings then + showstate("rrcurveto") + end +if top == 6 then + local ax = x + stack[1] -- dxa + local ay = y + stack[2] -- dya + local bx = ax + stack[3] -- dxb + local by = ay + stack[4] -- dyb + x = bx + stack[5] -- dxc + y = by + stack[6] -- dyc + xycurveto(ax,ay,bx,by,x,y,1,6) +else +-- print("rr",top==6,top) + for i=1,top,6 do + local ax = x + stack[i] -- dxa + local ay = y + stack[i+1] -- dya + local bx = ax + stack[i+2] -- dxb + local by = ay + stack[i+3] -- dyb + x = bx + stack[i+4] -- dxc + y = by + stack[i+5] -- dyc + xycurveto(ax,ay,bx,by,x,y,i,6) + end +end + top = 0 + end + + local function hhcurveto() + if trace_charstrings then + showstate("hhcurveto") + end + local s = 1 + if top % 2 ~= 0 then + y = y + stack[1] -- dy1 + s = 2 + end +if top == 4 then + local ax = x + stack[1] -- dxa + local ay = y + local bx = ax + stack[2] -- dxb + local by = ay + stack[3] -- dyb + x = bx + stack[4] -- dxc + y = by + xycurveto(ax,ay,bx,by,x,y,1,4) +else + for i=s,top,4 do + local ax = x + stack[i] -- dxa + local ay = y + local bx = ax + stack[i+1] -- dxb + local by = ay + stack[i+2] -- dyb + x = bx + stack[i+3] -- dxc + y = by + xycurveto(ax,ay,bx,by,x,y,i,4) + end +end + top = 0 + end + + local function vvcurveto() + if trace_charstrings then + showstate("vvcurveto") + end + local s = 1 + local d = 0 + if top % 2 ~= 0 then + d = stack[1] -- dx1 + s = 2 + end +if top == 4 then + local ax = x + d + local ay = y + stack[1] -- dya + local bx = ax + stack[2] -- dxb + local by = ay + stack[3] -- dyb + x = bx + y = by + stack[4] -- dyc + xycurveto(ax,ay,bx,by,x,y,1,4) + d = 0 +else + for i=s,top,4 do + local ax = x + d + local ay = y + stack[i] -- dya + local bx = ax + stack[i+1] -- dxb + local by = ay + stack[i+2] -- dyb + x = bx + y = by + stack[i+3] -- dyc + xycurveto(ax,ay,bx,by,x,y,i,4) + d = 0 + end +end + top = 0 + end + + local function xxcurveto(swap) + local last = top % 4 ~= 0 and stack[top] + if last then + top = top - 1 + end +if top == 4 then + local ax, ay, bx, by + if swap then + ax = x + stack[1] + ay = y + bx = ax + stack[2] + by = ay + stack[3] + y = by + stack[4] + if last then + x = bx + last + else + x = bx + end + else + ax = x + ay = y + stack[1] + bx = ax + stack[2] + by = ay + stack[3] + x = bx + stack[4] + if last then + y = by + last + else + y = by + end + end + xycurveto(ax,ay,bx,by,x,y,1 ,4) +else + for i=1,top,4 do + local ax, ay, bx, by + if swap then + ax = x + stack[i] + ay = y + bx = ax + stack[i+1] + by = ay + stack[i+2] + y = by + stack[i+3] + if last and i+3 == top then + x = bx + last + else + x = bx + end + swap = false + else + ax = x + ay = y + stack[i] + bx = ax + stack[i+1] + by = ay + stack[i+2] + x = bx + stack[i+3] + if last and i+3 == top then + y = by + last + else + y = by + end + swap = true + end + xycurveto(ax,ay,bx,by,x,y,i,4) + end +end + top = 0 + end + + local function hvcurveto() + if trace_charstrings then + showstate("hvcurveto") + end + xxcurveto(true) + end + + local function vhcurveto() + if trace_charstrings then + showstate("vhcurveto") + end + xxcurveto(false) + end + + local function rcurveline() + if trace_charstrings then + showstate("rcurveline") + end + for i=1,top-2,6 do + local ax = x + stack[i] -- dxa + local ay = y + stack[i+1] -- dya + local bx = ax + stack[i+2] -- dxb + local by = ay + stack[i+3] -- dyb + x = bx + stack[i+4] -- dxc + y = by + stack[i+5] -- dyc + xycurveto(ax,ay,bx,by,x,y,i,6) + end + x = x + stack[top-1] -- dxc + y = y + stack[top] -- dyc + xylineto() + top = 0 + end + + local function rlinecurve() + if trace_charstrings then + showstate("rlinecurve") + end + if top > 6 then + for i=1,top-6,2 do + x = x + stack[i] + y = y + stack[i+1] + xylineto() + end + end + local ax = x + stack[top-5] + local ay = y + stack[top-4] + local bx = ax + stack[top-3] + local by = ay + stack[top-2] + x = bx + stack[top-1] + y = by + stack[top] + xycurveto(ax,ay,bx,by,x,y) + top = 0 + end + + -- flex is not yet tested! no loop + + local function flex() -- fd not used + if trace_charstrings then + showstate("flex") + end + local ax = x + stack[1] -- dx1 + local ay = y + stack[2] -- dy1 + local bx = ax + stack[3] -- dx2 + local by = ay + stack[4] -- dy2 + local cx = bx + stack[5] -- dx3 + local cy = by + stack[6] -- dy3 + 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 + xycurveto(dx,dy,ex,ey,x,y) + top = 0 + end + + local function hflex() + if trace_charstrings then + showstate("hflex") + end + local ax = x + stack[1] -- dx1 + local ay = y + local bx = ax + stack[2] -- dx2 + local by = ay + stack[3] -- dy2 + local cx = bx + stack[4] -- dx3 + local cy = by + 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 + xycurveto(dx,dy,ex,ey,x,y) + top = 0 + end + + local function hflex1() + if trace_charstrings then + showstate("hflex1") + end + local ax = x + stack[1] -- dx1 + local ay = y + stack[2] -- dy1 + local bx = ax + stack[3] -- dx2 + local by = ay + stack[4] -- dy2 + local cx = bx + stack[5] -- dx3 + local cy = by + 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 + xycurveto(dx,dy,ex,ey,x,y) + top = 0 + end + + local function flex1() + if trace_charstrings then + showstate("flex1") + end + local ax = x + stack[1] --dx1 + local ay = y + stack[2] --dy1 + local bx = ax + stack[3] --dx2 + local by = ay + stack[4] --dy2 + local cx = bx + stack[5] --dx3 + local cy = by + stack[6] --dy3 + 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 + if abs(ex - x) > abs(ey - y) then -- spec: abs(dx) > abs(dy) + x = ex + stack[11] + else + y = ey + stack[11] + end + xycurveto(dx,dy,ex,ey,x,y) + top = 0 + end + + local function getstem() + if top == 0 then + -- bad + elseif top % 2 ~= 0 then + if width then + remove(stack,1) + else + width = remove(stack,1) + if trace_charstrings then + showvalue("width",width) + end + end + top = top - 1 + end + if trace_charstrings then + showstate("stem") + end + stems = stems + (top // 2) + top = 0 + end + + local function getmask() + if top == 0 then + -- bad + elseif top % 2 ~= 0 then + if width then + remove(stack,1) + else + width = remove(stack,1) + if trace_charstrings then + showvalue("width",width) + end + end + top = top - 1 + end + if trace_charstrings then + showstate(operator == 19 and "hintmark" or "cntrmask") + end + stems = stems + (top // 2) + top = 0 + if stems == 0 then + -- forget about it + elseif stems <= 8 then + return 1 + else + return (stems + 7) // 8 + end + end + + local function unsupported(t) + if trace_charstrings then + showstate("unsupported " .. t) + end + top = 0 + end + + local function unsupportedsub(t) + if trace_charstrings then + showstate("unsupported sub " .. t) + end + top = 0 + end + + -- type 1 (not used in type 2) + + local function getstem3() + if trace_charstrings then + showstate("stem3") + end + top = 0 + end + + local function divide() + if version == "cff" then + local d = stack[top] + top = top - 1 + stack[top] = stack[top] / d + end + end + + local function closepath() + if version == "cff" then + if trace_charstrings then + showstate("closepath") + end + end + top = 0 + end + + local function hsbw() + if version == "cff" then + if trace_charstrings then + showstate("hsbw") + end + lsb = stack[top-1] or 0 + width = stack[top] + end + top = 0 + end + + local function sbw() + if version == "cff" then + if trace_charstrings then + showstate("sbw") + end + lsb = stack[top-3] + width = stack[top-1] + end + top = 0 + end + + -- asb adx ady bchar achar seac (accented characters) + + local function seac() + if version == "cff" then + if trace_charstrings then + showstate("seac") + end + end + top = 0 + end + + -- These are probably used for special cases i.e. call out to the + -- postscript interpreter (p 61 of the spec as well as chapter 8). + -- + -- This needs checking (I have to ask Taco next time we meet.) + + local popped = 3 + local hints = 3 + + -- arg1 ... argn n othersubr# <callothersubr> (on postscript stack) + + local function callothersubr() + if version == "cff" then + if trace_charstrings then + showstate("callothersubr") + end + if stack[top] == hints then + popped = stack[top-2] + else + popped = 3 + end + local t = stack[top-1] + if t then + top = top - (t + 2) + if top < 0 then + top = 0 + end + else + top = 0 + end + else + top = 0 + end + end + + -- <pop> number (from postscript stack) + + local function pop() + if version == "cff" then + if trace_charstrings then + showstate("pop") + end + top = top + 1 + stack[top] = popped + else + top = 0 + end + end + + local function setcurrentpoint() + if version == "cff" then + if trace_charstrings then + showstate("setcurrentpoint (unsupported)") + end + x = x + stack[top-1] + y = y + stack[top] + end + top = 0 + end + + -- 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. Once there are fonts out there we'll + -- get there. (Marcel and friends did some tests with recent cff2 fonts so + -- the code has been adapted accordingly.) + + local reginit = false + + local function updateregions(n) -- n + 1 + if regions then + local current = regions[n+1] 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 + top = top - nofregions * n + end + end + + -- Bah, we cannot use a fast lpeg because a hint has an unknown size and a + -- runtime capture cannot handle that well. + + local actions = { [0] = + unsupported, -- 0 + getstem, -- 1 -- hstem + unsupported, -- 2 + getstem, -- 3 -- vstem + vmoveto, -- 4 + rlineto, -- 5 + hlineto, -- 6 + vlineto, -- 7 + rrcurveto, -- 8 + unsupported, -- 9 -- closepath + unsupported, -- 10 -- calllocal, + unsupported, -- 11 -- callreturn, + unsupported, -- 12 -- elsewhere + hsbw, -- 13 -- hsbw (type 1 cff) + unsupported, -- 14 -- endchar, + setvsindex, -- 15 -- cff2 + blend, -- 16 -- cff2 + unsupported, -- 17 + getstem, -- 18 -- hstemhm + getmask, -- 19 -- hintmask + getmask, -- 20 -- cntrmask + rmoveto, -- 21 + hmoveto, -- 22 + getstem, -- 23 -- vstemhm + rcurveline, -- 24 + rlinecurve, -- 25 + vvcurveto, -- 26 + hhcurveto, -- 27 + unsupported, -- 28 -- elsewhere + unsupported, -- 29 -- elsewhere + vhcurveto, -- 30 + hvcurveto, -- 31 + } + + local reverse = { [0] = + "unsupported", + "getstem", + "unsupported", + "getstem", + "vmoveto", + "rlineto", + "hlineto", + "vlineto", + "rrcurveto", + "unsupported", + "unsupported", + "unsupported", + "unsupported", + "hsbw", + "unsupported", + "setvsindex", + "blend", + "unsupported", + "getstem", + "getmask", + "getmask", + "rmoveto", + "hmoveto", + "getstem", + "rcurveline", + "rlinecurve", + "vvcurveto", + "hhcurveto", + "unsupported", + "unsupported", + "vhcurveto", + "hvcurveto", + } + + local subactions = { + -- cff 1 + [000] = dotsection, + [001] = getstem3, + [002] = getstem3, + [006] = seac, + [007] = sbw, + [012] = divide, + [016] = callothersubr, + [017] = pop, + [033] = setcurrentpoint, + -- cff 2 + [034] = hflex, + [035] = flex, + [036] = hflex1, + [037] = flex1, + } + + local chars = setmetatableindex(function (t,k) + local v = char(k) + t[k] = v + return v + end) + + local c_endchar = chars[14] + + -- todo: round in blend + + local encode = { } + local typeone = false + + -- this eventually can become a helper + + setmetatableindex(encode,function(t,i) + for i=-2048,-1130 do + -- t[i] = char(28,band(rshift(i,8),0xFF),band(i,0xFF)) + t[i] = char(28,(i >> 8) & 0xFF,i & 0xFF) + end + for i=-1131,-108 do + local v = 0xFB00 - i - 108 + -- t[i] = char(band(rshift(v,8),0xFF),band(v,0xFF)) + t[i] = char((v >> 8) & 0xFF,v & 0xFF) + end + for i=-107,107 do + t[i] = chars[i + 139] + end + for i=108,1131 do + local v = 0xF700 + i - 108 + -- t[i] = char(extract(v,8,8),extract(v,0,8)) + t[i] = char((v >> 8) & 0xFF,v & 0xFF) + end + for i=1132,2048 do + -- t[i] = char(28,band(rshift(i,8),0xFF),band(i,0xFF)) + t[i] = char(28,(i >> 8) & 0xFF,i & 0xFF) + end + setmetatableindex(encode,function(t,k) + -- as we're cff2 we write 16.16-bit signed fixed value + local r = round(k) + local v = rawget(t,r) + if v then + return v + end + local v1 = floor(k) + local v2 = floor((k - v1) * 0x10000) + -- return char(255,extract(v1,8,8),extract(v1,0,8),extract(v2,8,8),extract(v2,0,8)) + return char(255,(v1 >> 8) & 0xFF,v1 & 0xFF,(v2 >> 8) & 0xFF,v2 & 0xFF) + end) + return t[i] + end) + + readers.cffencoder = encode + + local function p_setvsindex() + local vsindex = stack[top] + updateregions(vsindex) + top = top - 1 + end + + local function p_blend() + -- leaves n values on stack + 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 + + local function p_getstem() + local n = 0 + if top % 2 ~= 0 then + n = 1 + end + if top > n then + stems = stems + ((top - n) // 2) + end + end + + local function p_getmask() + local n = 0 + if top % 2 ~= 0 then + n = 1 + end + if top > n then + stems = stems + ((top - n) // 2) + end + if stems == 0 then + return 0 + elseif stems <= 8 then + return 1 + else + return (stems + 7) // 8 + end + end + + -- end of experiment + + local process + + local function call(scope,list,bias) -- ,process) + depth = depth + 1 + if top == 0 then + showstate(formatters["unknown %s call %s, case %s"](scope,"?",1)) + top = 0 + else + local index = stack[top] + bias + top = top - 1 + if trace_charstrings then + showvalue(scope,index,true) + end + local tab = list[index] + if tab then + process(tab) + else + showstate(formatters["unknown %s call %s, case %s"](scope,index,2)) + top = 0 + end + end + depth = depth - 1 + end + + -- precompiling and reuse is much slower than redoing the calls + + process = function(tab) + local i = 1 + local n = #tab + while i <= n do + local t = tab[i] + if t >= 32 then + top = top + 1 + if t <= 246 then + -- -107 .. +107 + stack[top] = t - 139 + i = i + 1 + elseif t <= 250 then + -- +108 .. +1131 + -- stack[top] = (t-247)*256 + tab[i+1] + 108 + -- stack[top] = t*256 - 247*256 + tab[i+1] + 108 + stack[top] = t*256 - 63124 + tab[i+1] + i = i + 2 + elseif t <= 254 then + -- -1131 .. -108 + -- stack[top] = -(t-251)*256 - tab[i+1] - 108 + -- stack[top] = -t*256 + 251*256 - tab[i+1] - 108 + stack[top] = -t*256 + 64148 - tab[i+1] + i = i + 2 + elseif typeone then + local n = 0x1000000 * tab[i+1] + 0x10000 * tab[i+2] + 0x100 * tab[i+3] + tab[i+4] + if n >= 0x8000000 then + n = n - 0xFFFFFFFF - 1 + end + stack[top] = n + i = i + 5 + else + local n1 = 0x100 * tab[i+1] + tab[i+2] + local n2 = 0x100 * tab[i+3] + tab[i+4] + if n1 >= 0x8000 then + n1 = n1 - 0x10000 + end + stack[top] = n1 + n2/0xFFFF + i = i + 5 + end + elseif t == 28 then + -- -32768 .. +32767 : b1<<8 | b2 + top = top + 1 + local n = 0x100 * tab[i+1] + tab[i+2] + if n >= 0x8000 then + -- stack[top] = n - 0xFFFF - 1 + stack[top] = n - 0x10000 + else + stack[top] = n + end + i = i + 3 + elseif t == 11 then -- not in cff2 + if trace_charstrings then + showstate("return") + end + return + elseif t == 10 then + call("local",locals,localbias) -- ,process) + i = i + 1 + elseif t == 14 then -- not in cff2 + if width then + -- okay + elseif top > 0 then + width = stack[1] + if trace_charstrings then + showvalue("width",width) + end + else + width = true + end + if trace_charstrings then + showstate("endchar") + end + return + elseif t == 29 then + call("global",globals,globalbias) -- ,process) + i = i + 1 + elseif t == 12 then + i = i + 1 + local t = tab[i] + if justpass then + if t >= 34 and t <= 37 then -- flexes + for i=1,top do + r = r + 1 ; result[r] = encode[stack[i]] + end + r = r + 1 ; result[r] = chars[12] + r = r + 1 ; result[r] = chars[t] + top = 0 + elseif t == 6 then + seacs[procidx] = { + asb = stack[1], + adx = stack[2], + ady = stack[3], + base = stack[4], + accent = stack[5], + width = width, + lsb = lsb, + } + top = 0 + else + local a = subactions[t] + if a then + a(t) + else + top = 0 + end + end + else + local a = subactions[t] + if a then + a(t) + else + if trace_charstrings then + showvalue("<subaction>",t) + end + top = 0 + end + end + i = i + 1 + elseif justpass then + -- todo: local a = passactions + if t == 15 then + p_setvsindex() + i = i + 1 + elseif t == 16 then + local s = p_blend() or 0 + i = i + s + 1 + -- cff 1: (when cff2 strip them) + elseif t == 1 or t == 3 or t == 18 or operation == 23 then + p_getstem() -- at the start + if version == "cff" then +-- if true then + if top > 0 then + for i=1,top do + r = r + 1 ; result[r] = encode[stack[i]] + end + top = 0 + end + r = r + 1 ; result[r] = chars[t] + else + top = 0 + end + i = i + 1 + -- cff 1: (when cff2 strip them) + elseif t == 19 or t == 20 then + local s = p_getmask() or 0 -- after the stems +-- if version == "cff" then + if true then + if top > 0 then + for i=1,top do + r = r + 1 ; result[r] = encode[stack[i]] + end + top = 0 + end + r = r + 1 ; result[r] = chars[t] + for j=1,s do + i = i + 1 + r = r + 1 ; result[r] = chars[tab[i]] + end + else + i = i + s + top = 0 + end + i = i + 1 + -- cff 1: closepath + elseif t == 9 then + top = 0 + i = i + 1 + elseif t == 13 then + hsbw() +-- if version == "cff" then + if true then + -- we do a moveto over lsb + r = r + 1 ; result[r] = encode[lsb] + r = r + 1 ; result[r] = chars[22] + else + -- lsb is supposed to be zero + end + i = i + 1 + else + if trace_charstrings then + showstate(reverse[t] or "<action>") + end + if top > 0 then + -- if t == 8 and top > 42 then + if t == 8 and top > 48 then + -- let's assume this only happens for rrcurveto .. the other ones would need some more + -- complex handling (cff2 stuff) + -- + -- dx1 dy1 (dx1+dx2) (dy1+dy2) (dx1+dx2+dx3) (dy1+dy2+dy3) rcurveto. + local n = 0 + for i=1,top do + -- if n == 42 then + if n == 48 then +-- local zero = encode[0] +-- local res3 = result[r-3] +-- local res2 = result[r-2] +-- local res1 = result[r-1] +-- local res0 = result[r] +-- result[r-3] = zero +-- result[r-2] = zero + r = r + 1 ; result[r] = chars[t] +-- r = r + 1 ; result[r] = zero +-- r = r + 1 ; result[r] = zero +-- r = r + 1 ; result[r] = res3 +-- r = r + 1 ; result[r] = res2 +-- r = r + 1 ; result[r] = res1 +-- r = r + 1 ; result[r] = res0 + n = 1 + else + n = n + 1 + end + r = r + 1 ; result[r] = encode[stack[i]] + end + else + for i=1,top do + r = r + 1 ; result[r] = encode[stack[i]] + end + end + top = 0 + end + r = r + 1 ; result[r] = chars[t] + i = i + 1 + end + else + local a = actions[t] + if a then + local s = a(t) + if s then + i = i + s + 1 + else + i = i + 1 + end + else + if trace_charstrings then + showstate(reverse[t] or "<action>") + end + top = 0 + i = i + 1 + end + end + end + end + + -- local function calculatebounds(segments,x,y) + -- local nofsegments = #segments + -- if nofsegments == 0 then + -- return { x, y, x, y } + -- else + -- local xmin = 10000 + -- local xmax = -10000 + -- local ymin = 10000 + -- local ymax = -10000 + -- if x < xmin then xmin = x end + -- if x > xmax then xmax = x end + -- if y < ymin then ymin = y end + -- if y > ymax then ymax = y end + -- -- we now have a reasonable start so we could + -- -- simplify the next checks + -- for i=1,nofsegments do + -- local s = segments[i] + -- local x = s[1] + -- local y = s[2] + -- if x < xmin then xmin = x end + -- if x > xmax then xmax = x end + -- if y < ymin then ymin = y end + -- if y > ymax then ymax = y end + -- if s[#s] == "c" then -- "curveto" + -- local x = s[3] + -- local y = s[4] + -- 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 + -- local x = s[5] + -- local y = s[6] + -- 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 + -- end + -- return { round(xmin), round(ymin), round(xmax), round(ymax) } -- doesn't make ceil more sense + -- end + -- end + + local function setbias(globals,locals,nobias) + if nobias then + return 0, 0 + else + local g = #globals + local l = #locals + return + ((g < 1240 and 107) or (g < 33900 and 1131) or 32768) + 1, + ((l < 1240 and 107) or (l < 33900 and 1131) or 32768) + 1 + end + end + + local function processshape(glyphs,tab,index,hack) + + if not tab then + glyphs[index] = { + boundingbox = { 0, 0, 0, 0 }, + width = 0, + name = charset and charset[index] or nil, + } + return + end + + tab = bytetable(tab) + + x = 0 + y = 0 + width = false + lsb = 0 + r = 0 + top = 0 + stems = 0 + result = { } -- we could reuse it when only boundingbox calculations are needed + popped = 3 + procidx = index + + xmin = 0 + xmax = 0 + ymin = 0 + ymax = 0 + checked = false + if trace_charstrings then + report("glyph: %i",index) + report("data : % t",tab) + end + + if regions then + updateregions(vsindex) + end + + process(tab) + if hack then + return x, y + end + + local boundingbox = { + round(xmin), + round(ymin), + round(xmax), + round(ymax), + } + + if width == true or width == false then + width = defaultwidth + else + width = nominalwidth + width + end + + local glyph = glyphs[index] -- can be autodefined in otr + if justpass then + r = r + 1 + result[r] = c_endchar + local stream = concat(result) +result = nil + -- if trace_charstrings then + -- report("vdata: %s",stream) + -- end + if glyph then + glyph.stream = stream + glyph.width = width + else + glyphs[index] = { stream = stream, width = width } + end + elseif glyph then + glyph.segments = keepcurve ~= 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 keepcurve then + glyphs[index] = { + segments = result, + boundingbox = boundingbox, + width = width, + name = charset and charset[index] or nil, + -- sidebearing = 0, + } +result = nil + else + glyphs[index] = { + boundingbox = boundingbox, + width = width, + name = charset and charset[index] or nil, + } + end + if trace_charstrings then + report("width : %s",tostring(width)) + report("boundingbox: % t",boundingbox) + end + + end + + startparsing = function(fontdata,data,streams) + reginit = false + axis = false + regions = data.regions + justpass = streams == true + popped = 3 + seacs = { } + if regions then + -- this was: + -- regions = { regions } -- needs checking + -- and is now (MFC): + regions = { } + local deltas = data.deltas + for i = 1, #deltas do + regions[i] = deltas[i].regions + end + axis = data.factors or false + end + end + + stopparsing = function(fontdata,data) + stack = { } + glyphs = false + result = { } + top = 0 + locals = false + globals = false + strings = false + popped = 3 + seacs = { } + 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,nobias,istypeone) + + local dictionary = data.dictionaries[1] + local charstrings = dictionary.charstrings + + keepcurve = doshapes + version = tversion + typeone = istypeone or false + strings = data.strings + globals = data.routines or { } + locals = dictionary.subroutines or { } + charset = dictionary.charset + vsindex = dictionary.vsindex or 0 + + local glyphs = glphs or { } + + globalbias, localbias = setbias(globals,locals,nobias) + nominalwidth, defaultwidth = setwidths(dictionary.private) + + if charstrings then + startparsing(fontdata,data,streams) + for index=1,#charstrings do + processshape(glyphs,charstrings[index],index-1) + end + if justpass and next(seacs) then + -- old type 1 stuff ... seacs + local charset = data.dictionaries[1].charset + if charset then + local lookup = table.swapped(charset) + for index, v in next, seacs do + local bindex = lookup[standardnames[v.base]] + local aindex = lookup[standardnames[v.accent]] + local bglyph = bindex and glyphs[bindex] + local aglyph = aindex and glyphs[aindex] + if bglyph and aglyph then + -- this is a real ugly hack but we seldom enter this branch (e.g. old lbr) + local jp = justpass + justpass = false + local x, y = processshape(glyphs,charstrings[bindex+1],bindex,true) + justpass = jp + -- + local base = bglyph.stream + local accent = aglyph.stream + local moveto = encode[-x-v.asb+v.adx] .. chars[22] + .. encode[-y +v.ady] .. chars[ 4] + -- prune an endchar + base = sub(base,1,#base-1) + -- combine them + glyphs[index].stream = base .. moveto .. accent + end + end + end + end + stopparsing(fontdata,data) + else + report("no charstrings") + end + return glyphs + end + + parsecharstring = function(fontdata,data,dictionary,tab,glphs,index,doshapes,tversion,streams) + + keepcurve = doshapes + version = tversion + strings = data.strings + globals = data.routines or { } + locals = dictionary.subroutines or { } + charset = false + vsindex = dictionary.vsindex or 0 + + local glyphs = glphs or { } + + justpass = streams == true + seacs = { } + + globalbias, localbias = setbias(globals,locals,nobias) + nominalwidth, defaultwidth = setwidths(dictionary.private) + + processshape(glyphs,tab,index-1) + + return glyphs[index] + end + +end + +local function readglobals(f,data,version) + local routines = readlengths(f,version == "cff2") + for i=1,#routines do + routines[i] = readbytetable(f,routines[i]) + end + data.routines = routines +end + +local function readencodings(f,data) + data.encodings = { } +end + +local function readcharsets(f,data,dictionary) + local header = data.header + local strings = data.strings + local nofglyphs = data.nofglyphs + local charsetoffset = dictionary.charset + if charsetoffset and charsetoffset ~= 0 then + setposition(f,header.offset+charsetoffset) + local format = readbyte(f) + local charset = { [0] = ".notdef" } + dictionary.charset = charset + if format == 0 then + for i=1,nofglyphs do + charset[i] = strings[readushort(f)] + end + elseif format == 1 or format == 2 then + local readcount = format == 1 and readbyte or readushort + local i = 1 + while i <= nofglyphs do + local sid = readushort(f) + local n = readcount(f) + for s=sid,sid+n do + charset[i] = strings[s] + i = i + 1 + if i > nofglyphs then + break + end + end + end + else + report("cff parser: unsupported charset format %a",format) + end + else + dictionary.nocharset = true + dictionary.charset = nil + end +end + +local function readprivates(f,data) + local header = data.header + local dictionaries = data.dictionaries + local private = dictionaries[1].private + if private then + setposition(f,header.offset+private.offset) + private.data = readstring(f,private.size) + end +end + +local function readlocals(f,data,dictionary,version) + local header = data.header + local private = dictionary.private + if private then + local subroutineoffset = private.data.subroutines + if subroutineoffset ~= 0 then + setposition(f,header.offset+private.offset+subroutineoffset) + local subroutines = readlengths(f,version == "cff2") + for i=1,#subroutines do + subroutines[i] = readbytetable(f,subroutines[i]) + end + dictionary.subroutines = subroutines + private.data.subroutines = nil + else + dictionary.subroutines = { } + end + else + dictionary.subroutines = { } + end +end + +-- These charstrings are little programs and described in: Technical Note #5177. A truetype +-- font has only one dictionary. + +local function readcharstrings(f,data,version) + local header = data.header + local dictionaries = data.dictionaries + local dictionary = dictionaries[1] + local stringtype = dictionary.charstringtype + local offset = dictionary.charstrings + 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,version=="cff2") + local nofglyphs = #charstrings + for i=1,nofglyphs do + charstrings[i] = readstring(f,charstrings[i]) + end + data.nofglyphs = nofglyphs + dictionary.charstrings = charstrings + else + report("unsupported charstr type %i",stringtype) + data.nofglyphs = 0 + dictionary.charstrings = { } + end +end + +-- cid (maybe do this stepwise so less mem) -- share with above + +local function readcidprivates(f,data) + local header = data.header + local dictionaries = data.dictionaries[1].cid.dictionaries + for i=1,#dictionaries do + local dictionary = dictionaries[i] + local private = dictionary.private + if private then + setposition(f,header.offset+private.offset) + private.data = readstring(f,private.size) + end + end + parseprivates(data,dictionaries) +end + +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] + local cid = not dictionary.private and dictionary.cid + readglobals(f,data,version) + readcharstrings(f,data,version) + if version == "cff2" then + dictionary.charset = nil + else + readencodings(f,data) + readcharsets(f,data,dictionary) + end + if cid then + local fdarray = cid.fdarray + if fdarray then + setposition(f,data.header.offset + fdarray) + local dictionaries = readlengths(f,version=="cff2") + local nofdictionaries = #dictionaries + if nofdictionaries > 0 then + for i=1,nofdictionaries do + dictionaries[i] = readstring(f,dictionaries[i]) + end + parsedictionaries(data,dictionaries) + dictionary.private = dictionaries[1].private + if nofdictionaries > 1 then + report("ignoring dictionaries > 1 in cid font") + end + end + end + end + readprivates(f,data) + parseprivates(data,data.dictionaries) + readlocals(f,data,dictionary,version) + startparsing(fontdata,data,streams) + parsecharstrings(fontdata,data,glyphs,doshapes,version,streams,false) + stopparsing(fontdata,data) +end + +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,version) + readcharstrings(f,data,version) + if version ~= "cff2" then + readencodings(f,data) + end + local charstrings = dictionary.charstrings + local fdindex = { } + local nofglyphs = data.nofglyphs + local maxindex = -1 + setposition(f,header.offset+cidselect) + local format = readbyte(f) + if format == 1 then + for i=0,nofglyphs do -- notdef included (needs checking) + local index = readbyte(f) + fdindex[i] = index + if index > maxindex then + maxindex = index + end + end + elseif format == 3 then + local nofranges = readushort(f) + local first = readushort(f) + local index = readbyte(f) + while true do + local last = readushort(f) + if index > maxindex then + maxindex = index + end + for i=first,last do + fdindex[i] = index + end + if last >= nofglyphs then + break + else + first = last + 1 + index = readbyte(f) + end + end + else + report("unsupported fd index format %i",format) + end + -- hm, always + if maxindex >= 0 then + local cidarray = cid.fdarray + if cidarray then + setposition(f,header.offset+cidarray) + local dictionaries = readlengths(f,version == "cff2") + if #dictionaries > 0 then + for i=1,#dictionaries do + dictionaries[i] = readstring(f,dictionaries[i]) + end + parsedictionaries(data,dictionaries) + cid.dictionaries = dictionaries + readcidprivates(f,data) + for i=1,#dictionaries do + readlocals(f,data,dictionaries[i],version) + end + startparsing(fontdata,data,streams) + for i=1,#charstrings do + local dictionary = dictionaries[fdindex[i]+1] + if dictionary then + parsecharstring(fontdata,data,dictionary,charstrings[i],glyphs,i,doshapes,version,streams) + else + -- report("no dictionary for %a : %a => %a",version,i,fdindex[i]+1) + end + -- charstrings[i] = false + end + stopparsing(fontdata,data) + else + report("no cid dictionaries") + end + else + report("no cid array") + end + 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) + local tableoffset = gotodatatable(f,fontdata,"cff",specification.details or specification.glyphs) + 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 + -- + local cffinfo = { + familyname = dic.familyname, + fullname = dic.fullname, + boundingbox = dic.boundingbox, + weight = dic.weight, + italicangle = dic.italicangle, + underlineposition = dic.underlineposition, + underlinethickness = dic.underlinethickness, + defaultwidth = dic.defaultwidthx, + nominalwidth = dic.nominalwidthx, + monospaced = dic.monospaced, + } + fontdata.cidinfo = cid and { + registry = cid.registry, + ordering = cid.ordering, + supplement = cid.supplement, + } + fontdata.cffinfo = cffinfo + -- + local all = specification.shapes or specification.streams or false + if specification.glyphs or all then + if cid and cid.fdselect then + readfdselect(f,fontdata,data,glyphs,all,"cff",specification.streams) + else + readnoselect(f,fontdata,data,glyphs,all,"cff",specification.streams) + end + end + local private = dic.private + if private then + local data = private.data + if type(data) == "table" then + cffinfo.defaultwidth = data.defaultwidthx or cffinfo.defaultwidth + cffinfo.nominalwidth = data.nominalwidthx or cffinfo.nominalwidth + cffinfo.bluevalues = data.bluevalues + cffinfo.otherblues = data.otherblues + cffinfo.familyblues = data.familyblues + cffinfo.familyotherblues = data.familyotherblues + cffinfo.bluescale = data.bluescale + cffinfo.blueshift = data.blueshift + cffinfo.bluefuzz = data.bluefuzz + cffinfo.stdhw = data.stdhw + cffinfo.stdvw = data.stdvw + cffinfo.stemsnaph = data.stemsnaph + cffinfo.stemsnapv = data.stemsnapv + 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 offset = dictionaries[1].vstore + if offset > 0 then + 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 + else + data.regions = { } + data.deltas = { } + end + data.factors = specification.factors + -- + local cid = data.dictionaries[1].cid + local all = specification.shapes or specification.streams 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 = 0, +-- } +-- -- +-- 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 |