font-cff.lua / last modification: 2020-01-30 14:16
if not modules then modules = { } end modules ['font-cff'] = {
    version   = 1.001,
    comment   = "companion to font-ini.mkiv",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"
}

-- 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, remove, unpack = table.concat, 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
local idiv = number.idiv
local 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,
}

-- 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.strhw = stack[top]
            top = 0
        end
      + P("\11") / function()
            result.strvw = 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.bluesnap = 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 = {
        ["\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 p_last = S("\x0F\x1F\x2F\x3F\x4F\x5F\x6F\x7F\x8F\x9F\xAF\xBF")
                 + R("\xF0\xFF")

    local p_nibbles = P("\30") * Cs(((1-p_last)/remap)^0 * (P(1)/remap)) / 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_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_unsupported
    )^1

    parsedictionaries = function(data,dictionaries,what)
        stack   = { }
        strings = data.strings
        for i=1,#dictionaries do
            top    = 0
            result = what == "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 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)
        report("%w%-10s : [%s] n=%i",depth*2,where,concat(stack," ",1,top),top)
    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.

    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) -- 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" }
        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
        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)
        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
        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)
        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
        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)
            d = 0
        end
        top = 0
    end

    local function xxcurveto(swap)
        local last = top % 4 ~= 0 and stack[top]
        if last then
            top = top - 1
        end
        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)
        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)
        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 + idiv(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 + idiv(top,2)
        top   = 0
        if stems == 0 then
            -- forget about it
        elseif stems <= 8 then
            return 1
        else
         -- return floor((stems+7)/8)
            return idiv(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.

    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.

    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 = { }

    -- 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))
        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] = chars[i + 139]
        end
        for i=108,1131 do
            local v = 0xF700 + i - 108
--             t[i] = char(band(rshift(v,8),0xFF),band(v,0xFF))
            t[i] = char(extract(v,8,8),extract(v,0,8))
        end
        for i=1132,2048 do
            t[i] = char(28,band(rshift(i,8),0xFF),band(i,0xFF))
        end
        -- we could inline some ...
        setmetatableindex(encode,function(t,k)
            -- 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))
        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 + idiv(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 + idiv(top-n,2)
        end
        if stems == 0 then
            return 0
        elseif stems <= 8 then
            return 1
        else
            return idiv(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

 -- local function decode(str)
 --     local a, b, c, d, e = byte(str,1,5)
 --     if a == 28 then
 --         if c then
 --             local n = 0x100 * b + c
 --             if n >= 0x8000 then
 --                 return n - 0x10000
 --             else
 --                 return n
 --             end
 --         end
 --     elseif a < 32 then
 --         return false
 --     elseif a <= 246 then
 --         return  a - 139
 --     elseif a <= 250 then
 --         if b then
 --             return  a*256 - 63124 + b
 --         end
 --     elseif a <= 254 then
 --         if b then
 --             return -a*256 + 64148 - b
 --         end
 --     else
 --         if e then
 --             local n = 0x100 * b + c
 --             if n >= 0x8000 then
 --                 return n - 0x10000 + (0x100 * d + e)/0xFFFF
 --             else
 --                 return n           + (0x100 * d + e)/0xFFFF
 --             end
 --         end
 --     end
 --     return false
 -- end

    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
                else
                    -- a 16.16 float
                    local n = 0x100 * tab[i+1] + tab[i+2]
                    if n >= 0x8000 then
                        stack[top] = n - 0x10000 + (0x100 * tab[i+3] + tab[i+4])/0xFFFF
                    else
                        stack[top] = n           + (0x100 * tab[i+3] + tab[i+4])/0xFFFF
                    end
                    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 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 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
                        -- 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
                        for i=1,top do
                            r = r + 1 ; result[r] = encode[stack[i]]
                        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(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)
         -- if trace_charstrings then
         --     report("vdata: %s",stream)
         -- end
            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
            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,
            }
        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
            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
        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)

        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,nobias)
        nominalwidth, defaultwidth = setwidths(dictionary.private)

        if charstrings then
            startparsing(fontdata,data,streams)
            for index=1,#charstrings do
                processshape(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(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
        glyphs    = glphs or { }

        justpass = streams == true
        seacs    = { }

        globalbias,   localbias    = setbias(globals,locals,nobias)
        nominalwidth, defaultwidth = setwidths(dictionary.private)

        processshape(tab,index-1)

     -- return glyphs[index]
    end

end

local function readglobals(f,data)
    local routines = readlengths(f)
    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)
    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)
            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,what)
    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,what=="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]
    readglobals(f,data)
    readcharstrings(f,data,version)
    if version == "cff2" then
        dictionary.charset = nil
    else
        readencodings(f,data)
        readcharsets(f,data,dictionary)
    end
    readprivates(f,data)
    parseprivates(data,data.dictionaries)
    readlocals(f,data,dictionary)
    startparsing(fontdata,data,streams)
    parsecharstrings(fontdata,data,glyphs,doshapes,version,streams)
    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)
    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
        -- unsupported format
    end
    -- hm, always
    if maxindex >= 0 then
        local cidarray = cid.fdarray
        if cidarray then
            setposition(f,header.offset+cidarray)
            local dictionaries = readlengths(f)
            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])
            end
            startparsing(fontdata,data,streams)
            for i=1,#charstrings do
                parsecharstring(fontdata,data,dictionaries[fdindex[i]+1],charstrings[i],glyphs,i,doshapes,version,streams)
--                 charstrings[i] = nil
            end
            stopparsing(fontdata,data)
        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
            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