font-cff.lua /size: 89 Kb    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['font-cff'] = {
2    version   = 1.001,
3    optimize  = true,
4    comment   = "companion to font-ini.mkiv",
5    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
6    copyright = "PRAGMA ADE / ConTeXt Development Team",
7    license   = "see context related readme files"
8}
9
10-- todo: option.outlines
11-- todo: option.boundingbox
12-- per charstring (less memory)
13
14-- This is a heavy one as it is a rather packed format. We don't need al the information
15-- now but we might need it later (who know what magic we can do with metapost). So at
16-- some point this might become a module. We just follow Adobe Technical Notes #5176 and
17-- #5177. In case of doubt I looked in the fontforge code that comes with LuaTeX but
18-- it's not the easiest source to read (and doesn't cover cff2).
19
20-- For now we save the segments in a list of segments with the operator last in an entry
21-- because that reflects the original. But it might make more sense to use a single array
22-- per segment. For pdf a simple concat works ok, but for other purposes a operator first
23-- flush is nicer.
24--
25-- In retrospect I could have looked into the backend code of LuaTeX but it never
26-- occurred to me that parsing charstrings was needed there (which has to to
27-- with merging subroutines and flattening, not so much with calculations.) On
28-- the other hand, we can now feed back cff2 stuff.
29
30local next, type, tonumber, rawget = next, type, tonumber, rawget
31local byte, char, gmatch, sub = string.byte, string.char, string.gmatch, string.sub
32local concat, insert, remove, unpack = table.concat, table.insert, table.remove, table.unpack
33local floor, abs, round, ceil, min, max = math.floor, math.abs, math.round, math.ceil, math.min, math.max
34local P, C, R, S, C, Cs, Ct = lpeg.P, lpeg.C, lpeg.R, lpeg.S, lpeg.C, lpeg.Cs, lpeg.Ct
35local lpegmatch = lpeg.match
36local formatters = string.formatters
37local bytetable = string.bytetable
38local idiv = number.idiv
39local rshift, band, extract = bit32.rshift, bit32.band, bit32.extract
40
41local readers           = fonts.handlers.otf.readers
42local streamreader      = readers.streamreader
43
44local readstring        = streamreader.readstring
45local readbyte          = streamreader.readcardinal1  --  8-bit unsigned integer
46local readushort        = streamreader.readcardinal2  -- 16-bit unsigned integer
47local readuint          = streamreader.readcardinal3  -- 24-bit unsigned integer
48local readulong         = streamreader.readcardinal4  -- 32-bit unsigned integer
49local setposition       = streamreader.setposition
50local getposition       = streamreader.getposition
51local readbytetable     = streamreader.readbytetable
52
53directives.register("fonts.streamreader",function()
54
55    streamreader  = utilities.streams
56
57    readstring    = streamreader.readstring
58    readbyte      = streamreader.readcardinal1
59    readushort    = streamreader.readcardinal2
60    readuint      = streamreader.readcardinal3
61    readulong     = streamreader.readcardinal4
62    setposition   = streamreader.setposition
63    getposition   = streamreader.getposition
64    readbytetable = streamreader.readbytetable
65
66end)
67
68local setmetatableindex = table.setmetatableindex
69
70local trace_charstrings = false trackers.register("fonts.cff.charstrings",function(v) trace_charstrings = v end)
71local report            = logs.reporter("otf reader","cff")
72
73local parsedictionaries
74local parsecharstring
75local parsecharstrings
76local resetcharstrings
77local parseprivates
78local startparsing
79local stopparsing
80
81local defaultstrings = { [0] = -- taken from ff
82    ".notdef", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent",
83    "ampersand", "quoteright", "parenleft", "parenright", "asterisk", "plus",
84    "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four",
85    "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less",
86    "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H",
87    "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W",
88    "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum",
89    "underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j",
90    "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y",
91    "z", "braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent",
92    "sterling", "fraction", "yen", "florin", "section", "currency",
93    "quotesingle", "quotedblleft", "guillemotleft", "guilsinglleft",
94    "guilsinglright", "fi", "fl", "endash", "dagger", "daggerdbl",
95    "periodcentered", "paragraph", "bullet", "quotesinglbase", "quotedblbase",
96    "quotedblright", "guillemotright", "ellipsis", "perthousand", "questiondown",
97    "grave", "acute", "circumflex", "tilde", "macron", "breve", "dotaccent",
98    "dieresis", "ring", "cedilla", "hungarumlaut", "ogonek", "caron", "emdash",
99    "AE", "ordfeminine", "Lslash", "Oslash", "OE", "ordmasculine", "ae",
100    "dotlessi", "lslash", "oslash", "oe", "germandbls", "onesuperior",
101    "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus", "Thorn",
102    "onequarter", "divide", "brokenbar", "degree", "thorn", "threequarters",
103    "twosuperior", "registered", "minus", "eth", "multiply", "threesuperior",
104    "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave", "Aring",
105    "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave",
106    "Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute",
107    "Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute",
108    "Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron",
109    "aacute", "acircumflex", "adieresis", "agrave", "aring", "atilde",
110    "ccedilla", "eacute", "ecircumflex", "edieresis", "egrave", "iacute",
111    "icircumflex", "idieresis", "igrave", "ntilde", "oacute", "ocircumflex",
112    "odieresis", "ograve", "otilde", "scaron", "uacute", "ucircumflex",
113    "udieresis", "ugrave", "yacute", "ydieresis", "zcaron", "exclamsmall",
114    "Hungarumlautsmall", "dollaroldstyle", "dollarsuperior", "ampersandsmall",
115    "Acutesmall", "parenleftsuperior", "parenrightsuperior", "twodotenleader",
116    "onedotenleader", "zerooldstyle", "oneoldstyle", "twooldstyle",
117    "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle",
118    "sevenoldstyle", "eightoldstyle", "nineoldstyle", "commasuperior",
119    "threequartersemdash", "periodsuperior", "questionsmall", "asuperior",
120    "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior",
121    "lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior",
122    "tsuperior", "ff", "ffi", "ffl", "parenleftinferior", "parenrightinferior",
123    "Circumflexsmall", "hyphensuperior", "Gravesmall", "Asmall", "Bsmall",
124    "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", "Ismall",
125    "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall",
126    "Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall",
127    "Xsmall", "Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah",
128    "Tildesmall", "exclamdownsmall", "centoldstyle", "Lslashsmall",
129    "Scaronsmall", "Zcaronsmall", "Dieresissmall", "Brevesmall", "Caronsmall",
130    "Dotaccentsmall", "Macronsmall", "figuredash", "hypheninferior",
131    "Ogoneksmall", "Ringsmall", "Cedillasmall", "questiondownsmall", "oneeighth",
132    "threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds",
133    "zerosuperior", "foursuperior", "fivesuperior", "sixsuperior",
134    "sevensuperior", "eightsuperior", "ninesuperior", "zeroinferior",
135    "oneinferior", "twoinferior", "threeinferior", "fourinferior",
136    "fiveinferior", "sixinferior", "seveninferior", "eightinferior",
137    "nineinferior", "centinferior", "dollarinferior", "periodinferior",
138    "commainferior", "Agravesmall", "Aacutesmall", "Acircumflexsmall",
139    "Atildesmall", "Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall",
140    "Egravesmall", "Eacutesmall", "Ecircumflexsmall", "Edieresissmall",
141    "Igravesmall", "Iacutesmall", "Icircumflexsmall", "Idieresissmall",
142    "Ethsmall", "Ntildesmall", "Ogravesmall", "Oacutesmall", "Ocircumflexsmall",
143    "Otildesmall", "Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall",
144    "Uacutesmall", "Ucircumflexsmall", "Udieresissmall", "Yacutesmall",
145    "Thornsmall", "Ydieresissmall", "001.000", "001.001", "001.002", "001.003",
146    "Black", "Bold", "Book", "Light", "Medium", "Regular", "Roman", "Semibold",
147}
148
149local standardnames = { [0] = -- needed for seac
150    false, false, false, false, false, false, false, false, false, false, false,
151    false, false, false, false, false, false, false, false, false, false, false,
152    false, false, false, false, false, false, false, false, false, false,
153    "space", "exclam", "quotedbl", "numbersign", "dollar", "percent",
154    "ampersand", "quoteright", "parenleft", "parenright", "asterisk", "plus",
155    "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four",
156    "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less",
157    "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H",
158    "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W",
159    "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum",
160    "underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j",
161    "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y",
162    "z", "braceleft", "bar", "braceright", "asciitilde", false, false, false,
163    false, false, false, false, false, false, false, false, false, false, false,
164    false, false, false, false, false, false, false, false, false, false, false,
165    false, false, false, false, false, false, false, false, false, "exclamdown",
166    "cent", "sterling", "fraction", "yen", "florin", "section", "currency",
167    "quotesingle", "quotedblleft", "guillemotleft", "guilsinglleft",
168    "guilsinglright", "fi", "fl", false, "endash", "dagger", "daggerdbl",
169    "periodcentered", false, "paragraph", "bullet", "quotesinglbase",
170    "quotedblbase", "quotedblright", "guillemotright", "ellipsis", "perthousand",
171    false, "questiondown", false, "grave", "acute", "circumflex", "tilde",
172    "macron", "breve", "dotaccent", "dieresis", false, "ring", "cedilla", false,
173    "hungarumlaut", "ogonek", "caron", "emdash", false, false, false, false,
174    false, false, false, false, false, false, false, false, false, false, false,
175    false, "AE", false, "ordfeminine", false, false, false, false, "Lslash",
176    "Oslash", "OE", "ordmasculine", false, false, false, false, false, "ae",
177    false, false, false, "dotlessi", false, false, "lslash", "oslash", "oe",
178    "germandbls", false, false, false, false
179}
180
181local cffreaders = {
182    readbyte,
183    readushort,
184    readuint,
185    readulong,
186}
187
188directives.register("fonts.streamreader",function()
189    cffreaders = {
190        readbyte,
191        readushort,
192        readuint,
193        readulong,
194    }
195end)
196
197-- The header contains information about its own size.
198
199local function readheader(f)
200    local offset = getposition(f)
201    local major  = readbyte(f)
202    local header = {
203        offset = offset,
204        major  = major,
205        minor  = readbyte(f),
206        size   = readbyte(f), -- headersize
207    }
208    if major == 1 then
209        header.dsize = readbyte(f)   -- list of dict offsets
210    elseif major == 2 then
211        header.dsize = readushort(f) -- topdict size
212    else
213        -- I'm probably no longer around by then and we use AI's to
214        -- handle this kind of stuff, if we typeset documents at all.
215    end
216    setposition(f,offset+header.size)
217    return header
218end
219
220-- The indexes all look the same, so we share a loader. We could pass a handler
221-- and run over the array but why bother, we only have a few uses.
222
223local function readlengths(f,longcount)
224    local count = longcount and readulong(f) or readushort(f)
225    if count == 0 then
226        return { }
227    end
228    local osize = readbyte(f)
229    local read  = cffreaders[osize]
230    if not read then
231        report("bad offset size: %i",osize)
232        return { }
233    end
234    local lengths  = { }
235    local previous = read(f)
236    for i=1,count do
237        local offset = read(f)
238        local length = offset - previous
239        if length < 0 then
240            report("bad offset: %i",length)
241            length = 0
242        end
243        lengths[i] = length
244        previous = offset
245    end
246    return lengths
247end
248
249-- There can be subfonts so names is an array. However, in our case it's always
250-- one font. The same is true for the top dictionaries. Watch how we only load
251-- the dictionary string as for interpretation we need to have the strings loaded
252-- as well.
253
254local function readfontnames(f)
255    local names = readlengths(f)
256    for i=1,#names do
257        names[i] = readstring(f,names[i])
258    end
259    return names
260end
261
262local function readtopdictionaries(f)
263    local dictionaries = readlengths(f)
264    for i=1,#dictionaries do
265        dictionaries[i] = readstring(f,dictionaries[i])
266    end
267    return dictionaries
268end
269
270-- Strings are added to a list of standard strings so we start the font specific
271-- one with an offset. Strings are shared so we have one table.
272
273local function readstrings(f)
274    local lengths = readlengths(f)
275    local strings = setmetatableindex({ }, defaultstrings)
276    local index   = #defaultstrings
277    for i=1,#lengths do
278        index = index + 1
279        strings[index] = readstring(f,lengths[i])
280    end
281    return strings
282end
283
284-- Parsing the dictionaries is delayed till we have the strings loaded. The parser
285-- is stack based so the operands come before the operator (like in postscript).
286
287-- local function delta(t)
288--     local n = #t
289--     if n > 1 then
290--         local p = t[1]
291--         for i=2,n do
292--             local c = t[i]
293--             t[i] = c + p
294--             p = c
295--         end
296--     end
297-- end
298
299do
300
301    -- We use a closure so that we don't need to pass too much around. For cff2 we can
302    -- at some point use a simple version as there is less.
303
304    local stack   = { }
305    local top     = 0
306    local result  = { }
307    local strings = { }
308
309    local p_single =
310        P("\00") / function()
311            result.version = strings[stack[top]] or "unset"
312            top = 0
313        end
314      + P("\01") / function()
315            result.notice = strings[stack[top]] or "unset"
316            top = 0
317        end
318      + P("\02") / function()
319            result.fullname = strings[stack[top]] or "unset"
320            top = 0
321        end
322      + P("\03") / function()
323            result.familyname = strings[stack[top]] or "unset"
324            top = 0
325        end
326      + P("\04") / function()
327            result.weight = strings[stack[top]] or "unset"
328            top = 0
329        end
330      + P("\05") / function()
331            result.fontbbox = { unpack(stack,1,4) }
332            top = 0
333        end
334      + P("\06") / function()
335            result.bluevalues = { unpack(stack,1,top) }
336            top = 0
337        end
338      + P("\07") / function()
339            result.otherblues = { unpack(stack,1,top) }
340            top = 0
341        end
342      + P("\08") / function()
343            result.familyblues = { unpack(stack,1,top) }
344            top = 0
345        end
346      + P("\09") / function()
347            result.familyotherblues = { unpack(stack,1,top) }
348            top = 0
349        end
350      + P("\10") / function()
351            result.strhw = stack[top]
352            top = 0
353        end
354      + P("\11") / function()
355            result.strvw = stack[top]
356            top = 0
357        end
358      + P("\13") / function()
359            result.uniqueid = stack[top]
360            top = 0
361        end
362      + P("\14") / function()
363            result.xuid = concat(stack,"",1,top)
364            top = 0
365        end
366      + P("\15") / function()
367            result.charset = stack[top]
368            top = 0
369        end
370      + P("\16") / function()
371            result.encoding = stack[top]
372            top = 0
373        end
374      + P("\17") / function() -- valid cff2
375            result.charstrings = stack[top]
376            top = 0
377        end
378      + P("\18") / function()
379            result.private = {
380                size   = stack[top-1],
381                offset = stack[top],
382            }
383            top = 0
384        end
385      + P("\19") / function()
386            result.subroutines = stack[top]
387            top = 0 -- new, forgotten ?
388        end
389      + P("\20") / function()
390            result.defaultwidthx = stack[top]
391            top = 0 -- new, forgotten ?
392        end
393      + P("\21") / function()
394            result.nominalwidthx = stack[top]
395            top = 0 -- new, forgotten ?
396        end
397   -- + P("\22") / function() -- reserved
398   --   end
399   -- + P("\23") / function() -- reserved
400   --   end
401      + P("\24") / function() -- new in cff2
402            result.vstore = stack[top]
403            top = 0
404        end
405      + P("\25") / function() -- new in cff2
406            result.maxstack = stack[top]
407            top = 0
408        end
409   -- + P("\26") / function() -- reserved
410   --   end
411   -- + P("\27") / function() -- reserved
412   --   end
413
414    local p_double = P("\12") * (
415        P("\00") / function()
416            result.copyright = stack[top]
417            top = 0
418        end
419      + P("\01") / function()
420            result.monospaced = stack[top] == 1 and true or false -- isfixedpitch
421            top = 0
422        end
423      + P("\02") / function()
424            result.italicangle = stack[top]
425            top = 0
426        end
427      + P("\03") / function()
428            result.underlineposition = stack[top]
429            top = 0
430        end
431      + P("\04") / function()
432            result.underlinethickness = stack[top]
433            top = 0
434        end
435      + P("\05") / function()
436            result.painttype = stack[top]
437            top = 0
438        end
439      + P("\06") / function()
440            result.charstringtype = stack[top]
441            top = 0
442        end
443      + P("\07") / function() -- valid cff2
444            result.fontmatrix = { unpack(stack,1,6) }
445            top = 0
446        end
447      + P("\08") / function()
448            result.strokewidth = stack[top]
449            top = 0
450        end
451      + P("\09") / function()
452            result.bluescale = stack[top]
453            top = 0
454        end
455      + P("\10") / function()
456            result.bluesnap = stack[top]
457            top = 0
458        end
459      + P("\11") / function()
460            result.bluefuzz = stack[top]
461            top = 0
462        end
463      + P("\12") / function()
464            result.stemsnaph = { unpack(stack,1,top) }
465            top = 0
466        end
467      + P("\13") / function()
468            result.stemsnapv = { unpack(stack,1,top) }
469            top = 0
470        end
471      + P("\20") / function()
472            result.syntheticbase = stack[top]
473            top = 0
474        end
475      + P("\21") / function()
476            result.postscript = strings[stack[top]] or "unset"
477            top = 0
478        end
479      + P("\22") / function()
480            result.basefontname = strings[stack[top]] or "unset"
481            top = 0
482        end
483      + P("\21") / function()
484            result.basefontblend = stack[top]
485            top = 0
486        end
487      + P("\30") / function()
488            result.cid.registry   = strings[stack[top-2]] or "unset"
489            result.cid.ordering   = strings[stack[top-1]] or "unset"
490            result.cid.supplement = stack[top]
491            top = 0
492        end
493      + P("\31") / function()
494            result.cid.fontversion = stack[top]
495            top = 0
496        end
497      + P("\32") / function()
498            result.cid.fontrevision= stack[top]
499            top = 0
500        end
501      + P("\33") / function()
502            result.cid.fonttype = stack[top]
503            top = 0
504        end
505      + P("\34") / function()
506            result.cid.count = stack[top]
507            top = 0
508        end
509      + P("\35") / function()
510            result.cid.uidbase = stack[top]
511            top = 0
512        end
513      + P("\36") / function() -- valid cff2
514            result.cid.fdarray = stack[top]
515            top = 0
516        end
517      + P("\37") / function() -- valid cff2
518            result.cid.fdselect = stack[top]
519            top = 0
520        end
521      + P("\38") / function()
522            result.cid.fontname = strings[stack[top]] or "unset"
523            top = 0
524        end
525    )
526
527    -- Some lpeg fun ... a first variant split the byte and made a new string but
528    -- the second variant is much faster. Not that it matters much as we don't see
529    -- such numbers often.
530
531    local remap = {
532        ["\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",
533        ["\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",
534        ["\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",
535        ["\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",
536        ["\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",
537        ["\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",
538        ["\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",
539        ["\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",
540        ["\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",
541        ["\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",
542        ["\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"] = ".",
543        ["\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",
544        ["\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-",
545        ["\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"] = "-",
546    }
547
548    local p_last = S("\x0F\x1F\x2F\x3F\x4F\x5F\x6F\x7F\x8F\x9F\xAF\xBF")
549                 + R("\xF0\xFF")
550
551    local p_nibbles = P("\30") * Cs(((1-p_last)/remap)^0 * (P(1)/remap)) / function(n)
552        -- 0-9=digit a=. b=E c=E- d=reserved e=- f=finish
553        top = top + 1
554        stack[top] = tonumber(n) or 0
555    end
556
557    local p_byte = C(R("\32\246")) / function(b0)
558        -- -107 .. +107
559        top = top + 1
560        stack[top] = byte(b0) - 139
561    end
562
563    local p_positive =  C(R("\247\250")) * C(1) / function(b0,b1)
564        -- +108 .. +1131
565        top = top + 1
566        stack[top] = (byte(b0)-247)*256 + byte(b1) + 108
567    end
568
569    local p_negative = C(R("\251\254")) * C(1) / function(b0,b1)
570        -- -1131 .. -108
571        top = top + 1
572        stack[top] = -(byte(b0)-251)*256 - byte(b1) - 108
573    end
574
575 -- local p_float = P("\255") * C(1) * C(1) * C(1) * C(1) / function(b0,b1,b2,b3)
576 --     top = top + 1
577 --     stack[top] = 0
578 -- end
579
580    local p_short = P("\28") * C(1) * C(1) / function(b1,b2)
581        -- -32768 .. +32767 : b1<<8 | b2
582        top = top + 1
583        local n = 0x100 * byte(b1) + byte(b2)
584        if n  >= 0x8000 then
585            stack[top] = n - 0xFFFF - 1
586        else
587            stack[top] =  n
588        end
589    end
590
591    local p_long = P("\29") * C(1) * C(1) * C(1) * C(1) / function(b1,b2,b3,b4)
592        -- -2^31 .. +2^31-1 : b1<<24 | b2<<16 | b3<<8 | b4
593        top = top + 1
594        local n = 0x1000000 * byte(b1) + 0x10000 * byte(b2) + 0x100 * byte(b3) + byte(b4)
595        if n >= 0x8000000 then
596            stack[top] = n - 0xFFFFFFFF - 1
597        else
598            stack[top] = n
599        end
600    end
601
602    local p_unsupported = P(1) / function(detail)
603        top = 0
604    end
605
606    local p_dictionary = (
607        p_byte
608      + p_positive
609      + p_negative
610      + p_short
611      + p_long
612      + p_nibbles
613      + p_single
614      + p_double
615   -- + p_float
616      + p_unsupported
617    )^1
618
619    parsedictionaries = function(data,dictionaries,version)
620        stack   = { }
621        strings = data.strings
622        if trace_charstrings then
623            report("charstring format %a",version)
624        end
625        for i=1,#dictionaries do
626            top    = 0
627            result = version == "cff" and {
628                monospaced         = false,
629                italicangle        = 0,
630                underlineposition  = -100,
631                underlinethickness = 50,
632                painttype          = 0,
633                charstringtype     = 2,
634                fontmatrix         = { 0.001, 0, 0, 0.001, 0, 0 },
635                fontbbox           = { 0, 0, 0, 0 },
636                strokewidth        = 0,
637                charset            = 0,
638                encoding           = 0,
639                cid = {
640                    fontversion     = 0,
641                    fontrevision    = 0,
642                    fonttype        = 0,
643                    count           = 8720,
644                }
645            } or {
646                charstringtype     = 2,
647                charset            = 0,
648                vstore             = 0,
649                cid = {
650                    -- nothing yet
651                },
652            }
653            lpegmatch(p_dictionary,dictionaries[i])
654            dictionaries[i] = result
655        end
656        --
657        result = { }
658        top    = 0
659        stack  = { }
660    end
661
662    parseprivates = function(data,dictionaries)
663        stack   = { }
664        strings = data.strings
665        for i=1,#dictionaries do
666            local private = dictionaries[i].private
667            if private and private.data then
668                top     = 0
669                result  = {
670                    forcebold         = false,
671                    languagegroup     = 0,
672                    expansionfactor   = 0.06,
673                    initialrandomseed = 0,
674                    subroutines       = 0,
675                    defaultwidthx     = 0,
676                    nominalwidthx     = 0,
677                    cid               = {
678                        -- actually an error
679                    },
680                }
681                lpegmatch(p_dictionary,private.data)
682                private.data = result
683            end
684        end
685        result = { }
686        top    = 0
687        stack  = { }
688    end
689
690    -- All bezier curves have 6 points with successive pairs relative to
691    -- the previous pair. Some can be left out and are then copied or zero
692    -- (optimization).
693    --
694    -- We are not really interested in all the details of a glyph because we
695    -- only need to calculate the boundingbox. So, todo: a quick no result but
696    -- calculate only variant.
697    --
698    -- The conversion is straightforward and the specification os clear once
699    -- you understand that the x and y needs to be updates each step. It's also
700    -- quite easy to test because in mp a shape will look bad when a few variables
701    -- are swapped. But still there might be bugs down here because not all
702    -- variants are seen in a font so far. We are less compact that the ff code
703    -- because there quite some variants are done in one helper with a lot of
704    -- testing for states.
705
706    local x            = 0
707    local y            = 0
708    local width        = false
709    local lsb          = 0
710local result = { }
711    local r            = 0
712    local stems        = 0
713    local globalbias   = 0
714    local localbias    = 0
715    local nominalwidth = 0
716    local defaultwidth = 0
717    local charset      = false
718    local globals      = false
719    local locals       = false
720    local depth        = 1
721    local xmin         = 0
722    local xmax         = 0
723    local ymin         = 0
724    local ymax         = 0
725    local checked      = false
726    local keepcurve    = false
727    local version      = 2
728    local regions      = false
729    local nofregions   = 0
730    local region       = false
731    local factors      = false
732    local axis         = false
733    local vsindex      = 0
734    local justpass     = false
735    local seacs        = { }
736    local procidx      = nil
737
738    local function showstate(where)
739        report("%w%-10s : [%s] n=%i",depth*2,where,concat(stack," ",1,top),top)
740    end
741
742    local function showvalue(where,value,showstack)
743        if showstack then
744            report("%w%-10s : %s : [%s] n=%i",depth*2,where,tostring(value),concat(stack," ",1,top),top)
745        else
746            report("%w%-10s : %s",depth*2,where,tostring(value))
747        end
748    end
749
750    -- All these indirect calls make this run slower but it's cleaner this way
751    -- and we cache the result. As we moved the boundingbox code inline we gain
752    -- some back. I inlined some of then and a bit speed can be gained by more
753    -- inlining but not that much.
754
755    -- Maybe have several action tables:
756    --
757    -- keep curve / checked
758    -- keep curve / not checked
759    -- checked
760    -- not checked
761
762    local function xymoveto()
763        if keepcurve then
764            r = r + 1
765            result[r] = { x, y, "m" }
766        end
767        if checked then
768            if x > xmax then xmax = x elseif x < xmin then xmin = x end
769            if y > ymax then ymax = y elseif y < ymin then ymin = y end
770        else
771            xmin = x
772            ymin = y
773            xmax = x
774            ymax = y
775            checked = true
776        end
777    end
778
779    local function xmoveto() -- slight speedup
780        if keepcurve then
781            r = r + 1
782            result[r] = { x, y, "m" }
783        end
784        if not checked then
785            xmin = x
786            ymin = y
787            xmax = x
788            ymax = y
789            checked = true
790        elseif x > xmax then
791            xmax = x
792        elseif x < xmin then
793            xmin = x
794        end
795    end
796
797    local function ymoveto() -- slight speedup
798        if keepcurve then
799            r = r + 1
800            result[r] = { x, y, "m" }
801        end
802        if not checked then
803            xmin = x
804            ymin = y
805            xmax = x
806            ymax = y
807            checked = true
808        elseif y > ymax then
809            ymax = y
810        elseif y < ymin then
811            ymin = y
812        end
813    end
814
815    local function moveto()
816        if trace_charstrings then
817            showstate("moveto")
818        end
819        top = 0 -- forgotten
820        xymoveto()
821    end
822
823    local function xylineto() -- we could inline, no blend
824        if keepcurve then
825            r = r + 1
826            result[r] = { x, y, "l" }
827        end
828        if checked then
829            if x > xmax then xmax = x elseif x < xmin then xmin = x end
830            if y > ymax then ymax = y elseif y < ymin then ymin = y end
831        else
832            xmin = x
833            ymin = y
834            xmax = x
835            ymax = y
836            checked = true
837        end
838    end
839
840    local function xlineto() -- slight speedup
841        if keepcurve then
842            r = r + 1
843            result[r] = { x, y, "l" }
844        end
845        if not checked then
846            xmin = x
847            ymin = y
848            xmax = x
849            ymax = y
850            checked = true
851        elseif x > xmax then
852            xmax = x
853        elseif x < xmin then
854            xmin = x
855        end
856    end
857
858    local function ylineto() -- slight speedup
859        if keepcurve then
860            r = r + 1
861            result[r] = { x, y, "l" }
862        end
863        if not checked then
864            xmin = x
865            ymin = y
866            xmax = x
867            ymax = y
868            checked = true
869        elseif y > ymax then
870            ymax = y
871        elseif y < ymin then
872            ymin = y
873        end
874    end
875
876    local function xycurveto(x1,y1,x2,y2,x3,y3) -- called local so no blend here
877        if trace_charstrings then
878            showstate("curveto")
879        end
880        if keepcurve then
881            r = r + 1
882            result[r] = { x1, y1, x2, y2, x3, y3, "c" }
883        end
884        if checked then
885            if x1 > xmax then xmax = x1 elseif x1 < xmin then xmin = x1 end
886            if y1 > ymax then ymax = y1 elseif y1 < ymin then ymin = y1 end
887        else
888            xmin = x1
889            ymin = y1
890            xmax = x1
891            ymax = y1
892            checked = true
893        end
894        if x2 > xmax then xmax = x2 elseif x2 < xmin then xmin = x2 end
895        if y2 > ymax then ymax = y2 elseif y2 < ymin then ymin = y2 end
896        if x3 > xmax then xmax = x3 elseif x3 < xmin then xmin = x3 end
897        if y3 > ymax then ymax = y3 elseif y3 < ymin then ymin = y3 end
898    end
899
900    local function rmoveto()
901        if not width then
902            if top > 2 then
903                width = stack[1]
904                if trace_charstrings then
905                    showvalue("backtrack width",width)
906                end
907            else
908                width = true
909            end
910        end
911        if trace_charstrings then
912            showstate("rmoveto")
913        end
914        x = x + stack[top-1] -- dx1
915        y = y + stack[top]   -- dy1
916        top = 0
917        xymoveto()
918    end
919
920    local function hmoveto()
921        if not width then
922            if top > 1 then
923                width = stack[1]
924                if trace_charstrings then
925                    showvalue("backtrack width",width)
926                end
927            else
928                width = true
929            end
930        end
931        if trace_charstrings then
932            showstate("hmoveto")
933        end
934        x = x + stack[top] -- dx1
935        top = 0
936        xmoveto()
937    end
938
939    local function vmoveto()
940        if not width then
941            if top > 1 then
942                width = stack[1]
943                if trace_charstrings then
944                    showvalue("backtrack width",width)
945                end
946            else
947                width = true
948            end
949        end
950        if trace_charstrings then
951            showstate("vmoveto")
952        end
953        y = y + stack[top] -- dy1
954        top = 0
955        ymoveto()
956    end
957
958    local function rlineto()
959        if trace_charstrings then
960            showstate("rlineto")
961        end
962        for i=1,top,2 do
963            x = x + stack[i]   -- dxa
964            y = y + stack[i+1] -- dya
965            xylineto()
966        end
967        top = 0
968    end
969
970    local function hlineto() -- x (y,x)+ | (x,y)+
971        if trace_charstrings then
972            showstate("hlineto")
973        end
974        if top == 1 then
975            x = x + stack[1]
976            xlineto()
977        else
978            local swap = true
979            for i=1,top do
980                if swap then
981                    x = x + stack[i]
982                    xlineto()
983                    swap = false
984                else
985                    y = y + stack[i]
986                    ylineto()
987                    swap = true
988                end
989            end
990        end
991        top = 0
992    end
993
994    local function vlineto() -- y (x,y)+ | (y,x)+
995        if trace_charstrings then
996            showstate("vlineto")
997        end
998        if top == 1 then
999            y = y + stack[1]
1000            ylineto()
1001        else
1002            local swap = false
1003            for i=1,top do
1004                if swap then
1005                    x = x + stack[i]
1006                    xlineto()
1007                    swap = false
1008                else
1009                    y = y + stack[i]
1010                    ylineto()
1011                    swap = true
1012                end
1013            end
1014        end
1015        top = 0
1016    end
1017
1018    local function rrcurveto()
1019        if trace_charstrings then
1020            showstate("rrcurveto")
1021        end
1022        for i=1,top,6 do
1023            local ax = x  + stack[i]   -- dxa
1024            local ay = y  + stack[i+1] -- dya
1025            local bx = ax + stack[i+2] -- dxb
1026            local by = ay + stack[i+3] -- dyb
1027            x = bx + stack[i+4]        -- dxc
1028            y = by + stack[i+5]        -- dyc
1029            xycurveto(ax,ay,bx,by,x,y)
1030        end
1031        top = 0
1032    end
1033
1034    local function hhcurveto()
1035        if trace_charstrings then
1036            showstate("hhcurveto")
1037        end
1038        local s = 1
1039        if top % 2 ~= 0 then
1040            y = y + stack[1]           -- dy1
1041            s = 2
1042        end
1043        for i=s,top,4 do
1044            local ax = x + stack[i]    -- dxa
1045            local ay = y
1046            local bx = ax + stack[i+1] -- dxb
1047            local by = ay + stack[i+2] -- dyb
1048            x = bx + stack[i+3]        -- dxc
1049            y = by
1050            xycurveto(ax,ay,bx,by,x,y)
1051        end
1052        top = 0
1053    end
1054
1055    local function vvcurveto()
1056        if trace_charstrings then
1057            showstate("vvcurveto")
1058        end
1059        local s = 1
1060        local d = 0
1061        if top % 2 ~= 0 then
1062            d = stack[1]               -- dx1
1063            s = 2
1064        end
1065        for i=s,top,4 do
1066            local ax = x + d
1067            local ay = y + stack[i]    -- dya
1068            local bx = ax + stack[i+1] -- dxb
1069            local by = ay + stack[i+2] -- dyb
1070            x = bx
1071            y = by + stack[i+3]        -- dyc
1072            xycurveto(ax,ay,bx,by,x,y)
1073            d = 0
1074        end
1075        top = 0
1076    end
1077
1078    local function xxcurveto(swap)
1079        local last = top % 4 ~= 0 and stack[top]
1080        if last then
1081            top = top - 1
1082        end
1083        for i=1,top,4 do
1084            local ax, ay, bx, by
1085            if swap then
1086                ax = x  + stack[i]
1087                ay = y
1088                bx = ax + stack[i+1]
1089                by = ay + stack[i+2]
1090                y  = by + stack[i+3]
1091                if last and i+3 == top then
1092                    x = bx + last
1093                else
1094                    x = bx
1095                end
1096                swap = false
1097            else
1098                ax = x
1099                ay = y  + stack[i]
1100                bx = ax + stack[i+1]
1101                by = ay + stack[i+2]
1102                x  = bx + stack[i+3]
1103                if last and i+3 == top then
1104                    y = by + last
1105                else
1106                    y = by
1107                end
1108                swap = true
1109            end
1110            xycurveto(ax,ay,bx,by,x,y)
1111        end
1112        top = 0
1113    end
1114
1115    local function hvcurveto()
1116        if trace_charstrings then
1117            showstate("hvcurveto")
1118        end
1119        xxcurveto(true)
1120    end
1121
1122    local function vhcurveto()
1123        if trace_charstrings then
1124            showstate("vhcurveto")
1125        end
1126        xxcurveto(false)
1127    end
1128
1129    local function rcurveline()
1130        if trace_charstrings then
1131            showstate("rcurveline")
1132        end
1133        for i=1,top-2,6 do
1134            local ax = x  + stack[i]   -- dxa
1135            local ay = y  + stack[i+1] -- dya
1136            local bx = ax + stack[i+2] -- dxb
1137            local by = ay + stack[i+3] -- dyb
1138            x = bx + stack[i+4] -- dxc
1139            y = by + stack[i+5] -- dyc
1140            xycurveto(ax,ay,bx,by,x,y)
1141        end
1142        x = x + stack[top-1] -- dxc
1143        y = y + stack[top]   -- dyc
1144        xylineto()
1145        top = 0
1146    end
1147
1148    local function rlinecurve()
1149        if trace_charstrings then
1150            showstate("rlinecurve")
1151        end
1152        if top > 6 then
1153            for i=1,top-6,2 do
1154                x = x + stack[i]
1155                y = y + stack[i+1]
1156                xylineto()
1157            end
1158        end
1159        local ax = x  + stack[top-5]
1160        local ay = y  + stack[top-4]
1161        local bx = ax + stack[top-3]
1162        local by = ay + stack[top-2]
1163        x = bx + stack[top-1]
1164        y = by + stack[top]
1165        xycurveto(ax,ay,bx,by,x,y)
1166        top = 0
1167    end
1168
1169    -- flex is not yet tested! no loop
1170
1171    local function flex() -- fd not used
1172        if trace_charstrings then
1173            showstate("flex")
1174        end
1175        local ax = x  + stack[1]  -- dx1
1176        local ay = y  + stack[2]  -- dy1
1177        local bx = ax + stack[3]  -- dx2
1178        local by = ay + stack[4]  -- dy2
1179        local cx = bx + stack[5]  -- dx3
1180        local cy = by + stack[6]  -- dy3
1181        xycurveto(ax,ay,bx,by,cx,cy)
1182        local dx = cx + stack[7]  -- dx4
1183        local dy = cy + stack[8]  -- dy4
1184        local ex = dx + stack[9]  -- dx5
1185        local ey = dy + stack[10] -- dy5
1186        x = ex + stack[11]        -- dx6
1187        y = ey + stack[12]        -- dy6
1188        xycurveto(dx,dy,ex,ey,x,y)
1189        top = 0
1190    end
1191
1192    local function hflex()
1193        if trace_charstrings then
1194            showstate("hflex")
1195        end
1196        local ax = x  + stack[1] -- dx1
1197        local ay = y
1198        local bx = ax + stack[2] -- dx2
1199        local by = ay + stack[3] -- dy2
1200        local cx = bx + stack[4] -- dx3
1201        local cy = by
1202        xycurveto(ax,ay,bx,by,cx,cy)
1203        local dx = cx + stack[5] -- dx4
1204        local dy = by
1205        local ex = dx + stack[6] -- dx5
1206        local ey = y
1207        x = ex + stack[7]        -- dx6
1208        xycurveto(dx,dy,ex,ey,x,y)
1209        top = 0
1210    end
1211
1212    local function hflex1()
1213        if trace_charstrings then
1214            showstate("hflex1")
1215        end
1216        local ax = x  + stack[1] -- dx1
1217        local ay = y  + stack[2] -- dy1
1218        local bx = ax + stack[3] -- dx2
1219        local by = ay + stack[4] -- dy2
1220        local cx = bx + stack[5] -- dx3
1221        local cy = by
1222        xycurveto(ax,ay,bx,by,cx,cy)
1223        local dx = cx + stack[6] -- dx4
1224        local dy = by
1225        local ex = dx + stack[7] -- dx5
1226        local ey = dy + stack[8] -- dy5
1227        x = ex + stack[9]        -- dx6
1228        xycurveto(dx,dy,ex,ey,x,y)
1229        top = 0
1230    end
1231
1232    local function flex1()
1233        if trace_charstrings then
1234            showstate("flex1")
1235        end
1236        local ax = x  + stack[1]  --dx1
1237        local ay = y  + stack[2]  --dy1
1238        local bx = ax + stack[3]  --dx2
1239        local by = ay + stack[4]  --dy2
1240        local cx = bx + stack[5]  --dx3
1241        local cy = by + stack[6]  --dy3
1242        xycurveto(ax,ay,bx,by,cx,cy)
1243        local dx = cx + stack[7]  --dx4
1244        local dy = cy + stack[8]  --dy4
1245        local ex = dx + stack[9]  --dx5
1246        local ey = dy + stack[10] --dy5
1247        if abs(ex - x) > abs(ey - y) then -- spec: abs(dx) > abs(dy)
1248            x = ex + stack[11]
1249        else
1250            y = ey + stack[11]
1251        end
1252        xycurveto(dx,dy,ex,ey,x,y)
1253        top = 0
1254    end
1255
1256    local function getstem()
1257        if top == 0 then
1258            -- bad
1259        elseif top % 2 ~= 0 then
1260            if width then
1261                remove(stack,1)
1262            else
1263                width = remove(stack,1)
1264                if trace_charstrings then
1265                    showvalue("width",width)
1266                end
1267            end
1268            top = top - 1
1269        end
1270        if trace_charstrings then
1271            showstate("stem")
1272        end
1273        stems = stems + idiv(top,2)
1274        top   = 0
1275    end
1276
1277    local function getmask()
1278        if top == 0 then
1279            -- bad
1280        elseif top % 2 ~= 0 then
1281            if width then
1282                remove(stack,1)
1283            else
1284                width = remove(stack,1)
1285                if trace_charstrings then
1286                    showvalue("width",width)
1287                end
1288            end
1289            top = top - 1
1290        end
1291        if trace_charstrings then
1292            showstate(operator == 19 and "hintmark" or "cntrmask")
1293        end
1294        stems = stems + idiv(top,2)
1295        top   = 0
1296        if stems == 0 then
1297            -- forget about it
1298        elseif stems <= 8 then
1299            return 1
1300        else
1301         -- return floor((stems+7)/8)
1302            return idiv(stems+7,8)
1303        end
1304    end
1305
1306    local function unsupported(t)
1307        if trace_charstrings then
1308            showstate("unsupported " .. t)
1309        end
1310        top = 0
1311    end
1312
1313    local function unsupportedsub(t)
1314        if trace_charstrings then
1315            showstate("unsupported sub " .. t)
1316        end
1317        top = 0
1318    end
1319
1320    -- type 1 (not used in type 2)
1321
1322    local function getstem3()
1323        if trace_charstrings then
1324            showstate("stem3")
1325        end
1326        top = 0
1327    end
1328
1329    local function divide()
1330        if version == "cff" then
1331            local d = stack[top]
1332            top = top - 1
1333            stack[top] = stack[top] / d
1334        end
1335    end
1336
1337    local function closepath()
1338        if version == "cff" then
1339            if trace_charstrings then
1340                showstate("closepath")
1341            end
1342        end
1343        top = 0
1344    end
1345
1346    local function hsbw()
1347        if version == "cff" then
1348            if trace_charstrings then
1349                showstate("hsbw")
1350            end
1351            lsb   = stack[top-1] or 0
1352            width = stack[top]
1353        end
1354        top = 0
1355    end
1356
1357    local function sbw()
1358        if version == "cff" then
1359            if trace_charstrings then
1360                showstate("sbw")
1361            end
1362            lsb   = stack[top-3]
1363            width = stack[top-1]
1364        end
1365        top = 0
1366    end
1367
1368    -- asb adx ady bchar achar seac (accented characters)
1369
1370    local function seac()
1371        if version == "cff" then
1372            if trace_charstrings then
1373                showstate("seac")
1374            end
1375        end
1376        top = 0
1377    end
1378
1379    -- These are probably used for special cases i.e. call out to the
1380    -- postscript interpreter (p 61 of the spec as well as chapter 8).
1381    --
1382    -- This needs checking (I have to ask Taco next time we meet.)
1383
1384    local popped = 3
1385    local hints  = 3
1386
1387    -- arg1 ... argn n othersubr# <callothersubr> (on postscript stack)
1388
1389    local function callothersubr()
1390        if version == "cff" then
1391            if trace_charstrings then
1392                showstate("callothersubr")
1393            end
1394            if stack[top] == hints then
1395                popped = stack[top-2]
1396            else
1397                popped = 3
1398            end
1399            local t = stack[top-1]
1400            if t then
1401                top = top - (t + 2)
1402                if top < 0 then
1403                    top = 0
1404                end
1405            else
1406                top = 0
1407            end
1408        else
1409            top = 0
1410        end
1411    end
1412
1413    -- <pop> number (from postscript stack)
1414
1415    local function pop()
1416        if version == "cff" then
1417            if trace_charstrings then
1418                showstate("pop")
1419            end
1420            top = top + 1
1421            stack[top] = popped
1422        else
1423            top = 0
1424        end
1425    end
1426
1427    local function setcurrentpoint()
1428        if version == "cff" then
1429            if trace_charstrings then
1430                showstate("setcurrentpoint (unsupported)")
1431            end
1432            x = x + stack[top-1]
1433            y = y + stack[top]
1434        end
1435        top = 0
1436    end
1437
1438    -- So far for unsupported postscript. Now some cff2 magic. As I still need
1439    -- to wrap my head around the rather complex variable font specification
1440    -- with regions and axis, the following approach kind of works but is more
1441    -- some trial and error trick. It's still not clear how much of the complex
1442    -- truetype description applies to cff. Once there are fonts out there we'll
1443    -- get there. (Marcel and friends did some tests with recent cff2 fonts so
1444    -- the code has been adapted accordingly.)
1445
1446    local reginit = false
1447
1448    local function updateregions(n) -- n + 1
1449        if regions then
1450            local current = regions[n+1] or regions[1]
1451            nofregions = #current
1452            if axis and n ~= reginit then
1453                factors = { }
1454                for i=1,nofregions do
1455                    local region = current[i]
1456                    local s = 1
1457                    for j=1,#axis do
1458                        local f = axis[j]
1459                        local r = region[j]
1460                        local start = r.start
1461                        local peak  = r.peak
1462                        local stop  = r.stop
1463                        if start > peak or peak > stop then
1464                            -- * 1
1465                        elseif start < 0 and stop > 0 and peak ~= 0 then
1466                            -- * 1
1467                        elseif peak == 0 then
1468                            -- * 1
1469                        elseif f < start or f > stop then
1470                            -- * 0
1471                            s = 0
1472                            break
1473                        elseif f < peak then
1474                            s = s * (f - start) / (peak - start)
1475                        elseif f > peak then
1476                            s = s * (stop - f) / (stop - peak)
1477                        else
1478                            -- * 1
1479                        end
1480                    end
1481                    factors[i] = s
1482                end
1483            end
1484        end
1485        reginit = n
1486    end
1487
1488    local function setvsindex()
1489        local vsindex = stack[top]
1490        if trace_charstrings then
1491            showstate(formatters["vsindex %i"](vsindex))
1492        end
1493        updateregions(vsindex)
1494        top = top - 1
1495    end
1496
1497    local function blend()
1498        local n = stack[top]
1499        top = top - 1
1500        if axis then
1501            --  x    (r1x,r2x,r3x)
1502            -- (x,y) (r1x,r2x,r3x) (r1y,r2y,r3y)
1503            if trace_charstrings then
1504                local t = top - nofregions * n
1505                local m = t - n
1506                for i=1,n do
1507                    local k   = m + i
1508                    local d   = m + n + (i-1)*nofregions
1509                    local old = stack[k]
1510                    local new = old
1511                    for r=1,nofregions do
1512                        new = new + stack[d+r] * factors[r]
1513                    end
1514                    stack[k] = new
1515                    showstate(formatters["blend %i of %i: %s -> %s"](i,n,old,new))
1516                end
1517                top = t
1518            elseif n == 1 then
1519                top = top - nofregions
1520                local v = stack[top]
1521                for r=1,nofregions do
1522                    v = v + stack[top+r] * factors[r]
1523                end
1524                stack[top] = v
1525            else
1526                top = top - nofregions * n
1527                local d = top
1528                local k = top - n
1529                for i=1,n do
1530                    k = k + 1
1531                    local v = stack[k]
1532                    for r=1,nofregions do
1533                        v = v + stack[d+r] * factors[r]
1534                    end
1535                    stack[k] = v
1536                    d = d + nofregions
1537                end
1538            end
1539        else
1540            top = top - nofregions * n
1541        end
1542    end
1543
1544    -- Bah, we cannot use a fast lpeg because a hint has an unknown size and a
1545    -- runtime capture cannot handle that well.
1546
1547    local actions = { [0] =
1548        unsupported,  --  0
1549        getstem,      --  1 -- hstem
1550        unsupported,  --  2
1551        getstem,      --  3 -- vstem
1552        vmoveto,      --  4
1553        rlineto,      --  5
1554        hlineto,      --  6
1555        vlineto,      --  7
1556        rrcurveto,    --  8
1557        unsupported,  --  9 -- closepath
1558        unsupported,  -- 10 -- calllocal,
1559        unsupported,  -- 11 -- callreturn,
1560        unsupported,  -- 12 -- elsewhere
1561        hsbw,         -- 13 -- hsbw (type 1 cff)
1562        unsupported,  -- 14 -- endchar,
1563        setvsindex,   -- 15 -- cff2
1564        blend,        -- 16 -- cff2
1565        unsupported,  -- 17
1566        getstem,      -- 18 -- hstemhm
1567        getmask,      -- 19 -- hintmask
1568        getmask,      -- 20 -- cntrmask
1569        rmoveto,      -- 21
1570        hmoveto,      -- 22
1571        getstem,      -- 23 -- vstemhm
1572        rcurveline,   -- 24
1573        rlinecurve,   -- 25
1574        vvcurveto,    -- 26
1575        hhcurveto,    -- 27
1576        unsupported,  -- 28 -- elsewhere
1577        unsupported,  -- 29 -- elsewhere
1578        vhcurveto,    -- 30
1579        hvcurveto,    -- 31
1580    }
1581
1582    local reverse = { [0] =
1583        "unsupported",
1584        "getstem",
1585        "unsupported",
1586        "getstem",
1587        "vmoveto",
1588        "rlineto",
1589        "hlineto",
1590        "vlineto",
1591        "rrcurveto",
1592        "unsupported",
1593        "unsupported",
1594        "unsupported",
1595        "unsupported",
1596        "hsbw",
1597        "unsupported",
1598        "setvsindex",
1599        "blend",
1600        "unsupported",
1601        "getstem",
1602        "getmask",
1603        "getmask",
1604        "rmoveto",
1605        "hmoveto",
1606        "getstem",
1607        "rcurveline",
1608        "rlinecurve",
1609        "vvcurveto",
1610        "hhcurveto",
1611        "unsupported",
1612        "unsupported",
1613        "vhcurveto",
1614        "hvcurveto",
1615    }
1616
1617    local subactions = {
1618        -- cff 1
1619        [000] = dotsection,
1620        [001] = getstem3,
1621        [002] = getstem3,
1622        [006] = seac,
1623        [007] = sbw,
1624        [012] = divide,
1625        [016] = callothersubr,
1626        [017] = pop,
1627        [033] = setcurrentpoint,
1628        -- cff 2
1629        [034] = hflex,
1630        [035] = flex,
1631        [036] = hflex1,
1632        [037] = flex1,
1633    }
1634
1635    local chars = setmetatableindex(function (t,k)
1636        local v = char(k)
1637        t[k] = v
1638        return v
1639    end)
1640
1641    local c_endchar = chars[14]
1642
1643    -- todo: round in blend
1644
1645    local encode = { }
1646
1647    -- this eventually can become a helper
1648
1649    setmetatableindex(encode,function(t,i)
1650        for i=-2048,-1130 do
1651            t[i] = char(28,band(rshift(i,8),0xFF),band(i,0xFF))
1652        end
1653        for i=-1131,-108 do
1654            local v = 0xFB00 - i - 108
1655            t[i] = char(band(rshift(v,8),0xFF),band(v,0xFF))
1656        end
1657        for i=-107,107 do
1658            t[i] = chars[i + 139]
1659        end
1660        for i=108,1131 do
1661            local v = 0xF700 + i - 108
1662--             t[i] = char(band(rshift(v,8),0xFF),band(v,0xFF))
1663            t[i] = char(extract(v,8,8),extract(v,0,8))
1664        end
1665        for i=1132,2048 do
1666            t[i] = char(28,band(rshift(i,8),0xFF),band(i,0xFF))
1667        end
1668        -- we could inline some ...
1669        setmetatableindex(encode,function(t,k)
1670            -- 16.16-bit signed fixed value
1671            local r = round(k)
1672            local v = rawget(t,r)
1673            if v then
1674                return v
1675            end
1676            local v1 = floor(k)
1677            local v2 = floor((k - v1) * 0x10000)
1678            return char(255,extract(v1,8,8),extract(v1,0,8),extract(v2,8,8),extract(v2,0,8))
1679        end)
1680        return t[i]
1681    end)
1682
1683    readers.cffencoder = encode
1684
1685    local function p_setvsindex()
1686        local vsindex = stack[top]
1687        updateregions(vsindex)
1688        top = top - 1
1689    end
1690
1691    local function p_blend()
1692        -- leaves n values on stack
1693        local n = stack[top]
1694        top = top - 1
1695        if not axis then
1696            -- fatal error
1697        elseif n == 1 then
1698            top = top - nofregions
1699            local v = stack[top]
1700            for r=1,nofregions do
1701                v = v + stack[top+r] * factors[r]
1702            end
1703            stack[top] = round(v)
1704        else
1705            top = top - nofregions * n
1706            local d = top
1707            local k = top - n
1708            for i=1,n do
1709                k = k + 1
1710                local v = stack[k]
1711                for r=1,nofregions do
1712                    v = v + stack[d+r] * factors[r]
1713                end
1714                stack[k] = round(v)
1715                d = d + nofregions
1716            end
1717        end
1718    end
1719
1720    local function p_getstem()
1721        local n = 0
1722        if top % 2 ~= 0 then
1723            n = 1
1724        end
1725        if top > n then
1726            stems = stems + idiv(top-n,2)
1727        end
1728    end
1729
1730    local function p_getmask()
1731        local n = 0
1732        if top % 2 ~= 0 then
1733            n = 1
1734        end
1735        if top > n then
1736            stems = stems + idiv(top-n,2)
1737        end
1738        if stems == 0 then
1739            return 0
1740        elseif stems <= 8 then
1741            return 1
1742        else
1743            return idiv(stems+7,8)
1744        end
1745    end
1746
1747    -- end of experiment
1748
1749    local process
1750
1751    local function call(scope,list,bias) -- ,process)
1752        depth = depth + 1
1753        if top == 0 then
1754            showstate(formatters["unknown %s call %s, case %s"](scope,"?",1))
1755            top = 0
1756        else
1757            local index = stack[top] + bias
1758            top = top - 1
1759            if trace_charstrings then
1760                showvalue(scope,index,true)
1761            end
1762            local tab = list[index]
1763            if tab then
1764                process(tab)
1765            else
1766                showstate(formatters["unknown %s call %s, case %s"](scope,index,2))
1767                top = 0
1768            end
1769        end
1770        depth = depth - 1
1771    end
1772
1773    -- precompiling and reuse is much slower than redoing the calls
1774
1775 -- local function decode(str)
1776 --     local a, b, c, d, e = byte(str,1,5)
1777 --     if a == 28 then
1778 --         if c then
1779 --             local n = 0x100 * b + c
1780 --             if n >= 0x8000 then
1781 --                 return n - 0x10000
1782 --             else
1783 --                 return n
1784 --             end
1785 --         end
1786 --     elseif a < 32 then
1787 --         return false
1788 --     elseif a <= 246 then
1789 --         return  a - 139
1790 --     elseif a <= 250 then
1791 --         if b then
1792 --             return  a*256 - 63124 + b
1793 --         end
1794 --     elseif a <= 254 then
1795 --         if b then
1796 --             return -a*256 + 64148 - b
1797 --         end
1798 --     else
1799 --         if e then
1800 --             local n = 0x100 * b + c
1801 --             if n >= 0x8000 then
1802 --                 return n - 0x10000 + (0x100 * d + e)/0xFFFF
1803 --             else
1804 --                 return n           + (0x100 * d + e)/0xFFFF
1805 --             end
1806 --         end
1807 --     end
1808 --     return false
1809 -- end
1810
1811    process = function(tab)
1812        local i = 1
1813        local n = #tab
1814        while i <= n do
1815            local t = tab[i]
1816            if t >= 32 then
1817                top = top + 1
1818                if t <= 246 then
1819                    -- -107 .. +107
1820                    stack[top] = t - 139
1821                    i = i + 1
1822                elseif t <= 250 then
1823                    -- +108 .. +1131
1824                 -- stack[top] = (t-247)*256 + tab[i+1] + 108
1825                 -- stack[top] = t*256 - 247*256 + tab[i+1] + 108
1826                    stack[top] = t*256 - 63124 + tab[i+1]
1827                    i = i + 2
1828                elseif t <= 254 then
1829                    -- -1131 .. -108
1830                 -- stack[top] = -(t-251)*256 - tab[i+1] - 108
1831                 -- stack[top] = -t*256 + 251*256 - tab[i+1] - 108
1832                    stack[top] = -t*256 + 64148 - tab[i+1]
1833                    i = i + 2
1834                else
1835                    -- a 16.16 float (used for italic but pretty unreliable)
1836                    local n1 = 0x100 * tab[i+1] + tab[i+2]
1837                    local n2 = 0x100 * tab[i+3] + tab[i+4]
1838                    if n1 >= 0x8000 then
1839                        n1 = n1 - 0x10000
1840                    end
1841                    stack[top] = n1 + n2/0xFFFF
1842                    i = i + 5
1843                end
1844            elseif t == 28 then
1845                -- -32768 .. +32767 : b1<<8 | b2
1846                top = top + 1
1847                local n = 0x100 * tab[i+1] + tab[i+2]
1848                if n  >= 0x8000 then
1849                 -- stack[top] = n - 0xFFFF - 1
1850                    stack[top] = n - 0x10000
1851                else
1852                    stack[top] = n
1853                end
1854                i = i + 3
1855            elseif t == 11 then -- not in cff2
1856                if trace_charstrings then
1857                    showstate("return")
1858                end
1859                return
1860            elseif t == 10 then
1861                call("local",locals,localbias) -- ,process)
1862                i = i + 1
1863            elseif t == 14 then -- not in cff2
1864                if width then
1865                    -- okay
1866                elseif top > 0 then
1867                    width = stack[1]
1868                    if trace_charstrings then
1869                        showvalue("width",width)
1870                    end
1871                else
1872                    width = true
1873                end
1874                if trace_charstrings then
1875                    showstate("endchar")
1876                end
1877                return
1878            elseif t == 29 then
1879                call("global",globals,globalbias) -- ,process)
1880                i = i + 1
1881            elseif t == 12 then
1882                i = i + 1
1883                local t = tab[i]
1884                if justpass then
1885                    if t >= 34 and t <= 37 then -- flexes
1886                        for i=1,top do
1887                            r = r + 1 ; result[r] = encode[stack[i]]
1888                        end
1889                        r = r + 1 ; result[r] = chars[12]
1890                        r = r + 1 ; result[r] = chars[t]
1891                        top = 0
1892                    elseif t == 6 then
1893                        seacs[procidx] = {
1894                            asb    = stack[1],
1895                            adx    = stack[2],
1896                            ady    = stack[3],
1897                            base   = stack[4],
1898                            accent = stack[5],
1899                            width  = width,
1900                            lsb    = lsb,
1901                        }
1902                        top = 0
1903                    else
1904                        local a = subactions[t]
1905                        if a then
1906                            a(t)
1907                        else
1908                            top = 0
1909                        end
1910                    end
1911                else
1912                    local a = subactions[t]
1913                    if a then
1914                        a(t)
1915                    else
1916                        if trace_charstrings then
1917                            showvalue("<subaction>",t)
1918                        end
1919                        top = 0
1920                    end
1921                end
1922                i = i + 1
1923            elseif justpass then
1924                -- todo: local a = passactions
1925                if t == 15 then
1926                    p_setvsindex()
1927                    i = i + 1
1928                elseif t == 16 then
1929                    local s = p_blend() or 0
1930                    i = i + s + 1
1931                -- cff 1: (when cff2 strip them)
1932                elseif t == 1 or t == 3 or t == 18 or operation == 23 then
1933                    p_getstem() -- at the start
1934                    if true then
1935                        if top > 0 then
1936                            for i=1,top do
1937                                r = r + 1 ; result[r] = encode[stack[i]]
1938                            end
1939                            top = 0
1940                        end
1941                        r = r + 1 ; result[r] = chars[t]
1942                    else
1943                        top = 0
1944                    end
1945                    i = i + 1
1946                -- cff 1: (when cff2 strip them)
1947                elseif t == 19 or t == 20 then
1948                    local s = p_getmask() or 0 -- after the stems
1949                    if true then
1950                        if top > 0 then
1951                            for i=1,top do
1952                                r = r + 1 ; result[r] = encode[stack[i]]
1953                            end
1954                            top = 0
1955                        end
1956                        r = r + 1 ; result[r] = chars[t]
1957                        for j=1,s do
1958                            i = i + 1
1959                            r = r + 1 ; result[r] = chars[tab[i]]
1960                        end
1961                    else
1962                        i = i + s
1963                        top = 0
1964                    end
1965                    i = i + 1
1966                -- cff 1: closepath
1967                elseif t == 9 then
1968                    top = 0
1969                    i = i + 1
1970                elseif t == 13 then
1971                    hsbw()
1972                    if version == "cff" then
1973                        -- we do a moveto over lsb
1974                        r = r + 1 ; result[r] = encode[lsb]
1975                        r = r + 1 ; result[r] = chars[22]
1976                    else
1977                        -- lsb is supposed to be zero
1978                    end
1979                    i = i + 1
1980                else
1981                    if trace_charstrings then
1982                        showstate(reverse[t] or "<action>")
1983                    end
1984                    if top > 0 then
1985                     -- if t == 8 and top > 42 then
1986                        if t == 8 and top > 48 then
1987                            -- let's assume this only happens for rrcurveto .. the other ones would need some more
1988                            -- complex handling (cff2 stuff)
1989                            local n = 0
1990                            for i=1,top do
1991                             -- if n == 42 then
1992                                if n == 48 then
1993                                    local zero = encode[0]
1994                                    local res3 = result[r-3]
1995                                    local res2 = result[r-2]
1996                                    local res1 = result[r-1]
1997                                    local res0 = result[r]
1998                                    result[r-3] = zero
1999                                    result[r-2] = zero
2000                                    r = r + 1 ; result[r] = chars[t]
2001                                    r = r + 1 ; result[r] = zero
2002                                    r = r + 1 ; result[r] = zero
2003                                    r = r + 1 ; result[r] = res3
2004                                    r = r + 1 ; result[r] = res2
2005                                    r = r + 1 ; result[r] = res1
2006                                    r = r + 1 ; result[r] = res0
2007                                    n = 1
2008                                else
2009                                    n = n + 1
2010                                end
2011                                r = r + 1 ; result[r] = encode[stack[i]]
2012                            end
2013                        else
2014                            for i=1,top do
2015                                r = r + 1 ; result[r] = encode[stack[i]]
2016                            end
2017                        end
2018                        top = 0
2019                    end
2020                    r = r + 1 ; result[r] = chars[t]
2021                    i = i + 1
2022                end
2023            else
2024                local a = actions[t]
2025                if a then
2026                    local s = a(t)
2027                    if s then
2028                        i = i + s + 1
2029                    else
2030                        i = i + 1
2031                    end
2032                else
2033                    if trace_charstrings then
2034                        showstate(reverse[t] or "<action>")
2035                    end
2036                    top = 0
2037                    i = i + 1
2038                end
2039            end
2040        end
2041    end
2042
2043 -- local function calculatebounds(segments,x,y)
2044 --     local nofsegments = #segments
2045 --     if nofsegments == 0 then
2046 --         return { x, y, x, y }
2047 --     else
2048 --         local xmin =  10000
2049 --         local xmax = -10000
2050 --         local ymin =  10000
2051 --         local ymax = -10000
2052 --         if x < xmin then xmin = x end
2053 --         if x > xmax then xmax = x end
2054 --         if y < ymin then ymin = y end
2055 --         if y > ymax then ymax = y end
2056 --         -- we now have a reasonable start so we could
2057 --         -- simplify the next checks
2058 --         for i=1,nofsegments do
2059 --             local s = segments[i]
2060 --             local x = s[1]
2061 --             local y = s[2]
2062 --             if x < xmin then xmin = x end
2063 --             if x > xmax then xmax = x end
2064 --             if y < ymin then ymin = y end
2065 --             if y > ymax then ymax = y end
2066 --             if s[#s] == "c" then -- "curveto"
2067 --                 local x = s[3]
2068 --                 local y = s[4]
2069 --                 if x < xmin then xmin = x elseif x > xmax then xmax = x end
2070 --                 if y < ymin then ymin = y elseif y > ymax then ymax = y end
2071 --                 local x = s[5]
2072 --                 local y = s[6]
2073 --                 if x < xmin then xmin = x elseif x > xmax then xmax = x end
2074 --                 if y < ymin then ymin = y elseif y > ymax then ymax = y end
2075 --             end
2076 --         end
2077 --         return { round(xmin), round(ymin), round(xmax), round(ymax) } -- doesn't make ceil more sense
2078 --     end
2079 -- end
2080
2081    local function setbias(globals,locals,nobias)
2082        if nobias then
2083            return 0, 0
2084        else
2085            local g = #globals
2086            local l = #locals
2087            return
2088                ((g < 1240 and 107) or (g < 33900 and 1131) or 32768) + 1,
2089                ((l < 1240 and 107) or (l < 33900 and 1131) or 32768) + 1
2090        end
2091    end
2092
2093    local function processshape(tab,index,hack)
2094
2095        if not tab then
2096            glyphs[index] = {
2097                boundingbox = { 0, 0, 0, 0 },
2098                width       = 0,
2099                name        = charset and charset[index] or nil,
2100            }
2101            return
2102        end
2103
2104        tab     = bytetable(tab)
2105
2106        x       = 0
2107        y       = 0
2108        width   = false
2109        lsb     = 0
2110        r       = 0
2111        top     = 0
2112        stems   = 0
2113        result  = { } -- we could reuse it when only boundingbox calculations are needed
2114        popped  = 3
2115        procidx = index
2116
2117        xmin    = 0
2118        xmax    = 0
2119        ymin    = 0
2120        ymax    = 0
2121        checked = false
2122        if trace_charstrings then
2123            report("glyph: %i",index)
2124            report("data : % t",tab)
2125        end
2126
2127        if regions then
2128            updateregions(vsindex)
2129        end
2130
2131        process(tab)
2132        if hack then
2133            return x, y
2134        end
2135
2136        local boundingbox = {
2137            round(xmin),
2138            round(ymin),
2139            round(xmax),
2140            round(ymax),
2141        }
2142
2143        if width == true or width == false then
2144            width = defaultwidth
2145        else
2146            width = nominalwidth + width
2147        end
2148
2149        local glyph = glyphs[index] -- can be autodefined in otr
2150        if justpass then
2151            r = r + 1
2152            result[r] = c_endchar
2153            local stream = concat(result)
2154result = nil
2155         -- if trace_charstrings then
2156         --     report("vdata: %s",stream)
2157         -- end
2158            if glyph then
2159                glyph.stream  = stream
2160            else
2161                glyphs[index] = { stream = stream }
2162            end
2163        elseif glyph then
2164            glyph.segments    = keepcurve ~= false and result or nil
2165            glyph.boundingbox = boundingbox
2166            if not glyph.width then
2167                glyph.width = width
2168            end
2169            if charset and not glyph.name then
2170                glyph.name = charset[index]
2171            end
2172         -- glyph.sidebearing = 0 -- todo
2173        elseif keepcurve then
2174            glyphs[index] = {
2175                segments    = result,
2176                boundingbox = boundingbox,
2177                width       = width,
2178                name        = charset and charset[index] or nil,
2179             -- sidebearing = 0,
2180            }
2181result = nil
2182        else
2183            glyphs[index] = {
2184                boundingbox = boundingbox,
2185                width       = width,
2186                name        = charset and charset[index] or nil,
2187            }
2188        end
2189
2190        if trace_charstrings then
2191            report("width      : %s",tostring(width))
2192            report("boundingbox: % t",boundingbox)
2193        end
2194
2195    end
2196
2197    startparsing = function(fontdata,data,streams)
2198        reginit  = false
2199        axis     = false
2200        regions  = data.regions
2201        justpass = streams == true
2202        popped   = 3
2203        seacs    = { }
2204        if regions then
2205            -- this was:
2206         -- regions = { regions } -- needs checking
2207            -- and is now (MFC):
2208            regions = { }
2209            local deltas = data.deltas
2210            for i = 1, #deltas do
2211                regions[i] = deltas[i].regions
2212            end
2213            axis = data.factors or false
2214        end
2215    end
2216
2217    stopparsing = function(fontdata,data)
2218        stack   = { }
2219        glyphs  = false
2220        result  = { }
2221        top     = 0
2222        locals  = false
2223        globals = false
2224        strings = false
2225        popped  = 3
2226        seacs   = { }
2227    end
2228
2229    local function setwidths(private)
2230        if not private then
2231            return 0, 0
2232        end
2233        local privatedata  = private.data
2234        if not privatedata then
2235            return 0, 0
2236        end
2237        return privatedata.nominalwidthx or 0, privatedata.defaultwidthx or 0
2238    end
2239
2240    parsecharstrings = function(fontdata,data,glphs,doshapes,tversion,streams,nobias)
2241
2242        local dictionary  = data.dictionaries[1]
2243        local charstrings = dictionary.charstrings
2244
2245        keepcurve = doshapes
2246        version   = tversion
2247        strings   = data.strings
2248        globals   = data.routines or { }
2249        locals    = dictionary.subroutines or { }
2250        charset   = dictionary.charset
2251        vsindex   = dictionary.vsindex or 0
2252        glyphs    = glphs or { }
2253
2254        globalbias,   localbias    = setbias(globals,locals,nobias)
2255        nominalwidth, defaultwidth = setwidths(dictionary.private)
2256
2257        if charstrings then
2258            startparsing(fontdata,data,streams)
2259            for index=1,#charstrings do
2260                processshape(charstrings[index],index-1)
2261            end
2262            if justpass and next(seacs) then
2263                -- old type 1 stuff ... seacs
2264                local charset = data.dictionaries[1].charset
2265                if charset then
2266                    local lookup = table.swapped(charset)
2267                    for index, v in next, seacs do
2268                        local bindex = lookup[standardnames[v.base]]
2269                        local aindex = lookup[standardnames[v.accent]]
2270                        local bglyph = bindex and glyphs[bindex]
2271                        local aglyph = aindex and glyphs[aindex]
2272                        if bglyph and aglyph then
2273                            -- this is a real ugly hack but we seldom enter this branch (e.g. old lbr)
2274                            local jp = justpass
2275                            justpass = false
2276                            local x, y = processshape(charstrings[bindex+1],bindex,true)
2277                            justpass = jp
2278                            --
2279                            local base   = bglyph.stream
2280                            local accent = aglyph.stream
2281                            local moveto = encode[-x-v.asb+v.adx] .. chars[22]
2282                                        .. encode[-y      +v.ady] .. chars[ 4]
2283                            -- prune an endchar
2284                            base = sub(base,1,#base-1)
2285                            -- combine them
2286                            glyphs[index].stream = base .. moveto .. accent
2287                        end
2288                    end
2289                end
2290            end
2291            stopparsing(fontdata,data)
2292        else
2293            report("no charstrings")
2294        end
2295        return glyphs
2296    end
2297
2298    parsecharstring = function(fontdata,data,dictionary,tab,glphs,index,doshapes,tversion,streams)
2299
2300        keepcurve = doshapes
2301        version   = tversion
2302        strings   = data.strings
2303        globals   = data.routines or { }
2304        locals    = dictionary.subroutines or { }
2305        charset   = false
2306        vsindex   = dictionary.vsindex or 0
2307        glyphs    = glphs or { }
2308
2309        justpass = streams == true
2310        seacs    = { }
2311
2312        globalbias,   localbias    = setbias(globals,locals,nobias)
2313        nominalwidth, defaultwidth = setwidths(dictionary.private)
2314
2315        processshape(tab,index-1)
2316
2317     -- return glyphs[index]
2318    end
2319
2320end
2321
2322local function readglobals(f,data,version)
2323    local routines = readlengths(f,version == "cff2")
2324    for i=1,#routines do
2325        routines[i] = readbytetable(f,routines[i])
2326    end
2327    data.routines = routines
2328end
2329
2330local function readencodings(f,data)
2331    data.encodings = { }
2332end
2333
2334local function readcharsets(f,data,dictionary)
2335    local header        = data.header
2336    local strings       = data.strings
2337    local nofglyphs     = data.nofglyphs
2338    local charsetoffset = dictionary.charset
2339    if charsetoffset and charsetoffset ~= 0 then
2340        setposition(f,header.offset+charsetoffset)
2341        local format       = readbyte(f)
2342        local charset      = { [0] = ".notdef" }
2343        dictionary.charset = charset
2344        if format == 0 then
2345            for i=1,nofglyphs do
2346                charset[i] = strings[readushort(f)]
2347            end
2348        elseif format == 1 or format == 2 then
2349            local readcount = format == 1 and readbyte or readushort
2350            local i = 1
2351            while i <= nofglyphs do
2352                local sid = readushort(f)
2353                local n   = readcount(f)
2354                for s=sid,sid+n do
2355                    charset[i] = strings[s]
2356                    i = i + 1
2357                    if i > nofglyphs then
2358                        break
2359                    end
2360                end
2361            end
2362        else
2363            report("cff parser: unsupported charset format %a",format)
2364        end
2365    else
2366        dictionary.nocharset = true
2367        dictionary.charset   = nil
2368    end
2369end
2370
2371local function readprivates(f,data)
2372    local header       = data.header
2373    local dictionaries = data.dictionaries
2374    local private      = dictionaries[1].private
2375    if private then
2376        setposition(f,header.offset+private.offset)
2377        private.data = readstring(f,private.size)
2378    end
2379end
2380
2381local function readlocals(f,data,dictionary,version)
2382    local header  = data.header
2383    local private = dictionary.private
2384    if private then
2385        local subroutineoffset = private.data.subroutines
2386        if subroutineoffset ~= 0 then
2387            setposition(f,header.offset+private.offset+subroutineoffset)
2388            local subroutines = readlengths(f,version == "cff2")
2389            for i=1,#subroutines do
2390                subroutines[i] = readbytetable(f,subroutines[i])
2391            end
2392            dictionary.subroutines = subroutines
2393            private.data.subroutines = nil
2394        else
2395            dictionary.subroutines = { }
2396        end
2397    else
2398        dictionary.subroutines = { }
2399    end
2400end
2401
2402-- These charstrings are little programs and described in: Technical Note #5177. A truetype
2403-- font has only one dictionary.
2404
2405local function readcharstrings(f,data,version)
2406    local header       = data.header
2407    local dictionaries = data.dictionaries
2408    local dictionary   = dictionaries[1]
2409    local stringtype   = dictionary.charstringtype
2410    local offset       = dictionary.charstrings
2411    if type(offset) ~= "number" then
2412        -- weird
2413    elseif stringtype == 2 then
2414        setposition(f,header.offset+offset)
2415        -- could be a metatable .. delayed loading
2416        local charstrings = readlengths(f,version=="cff2")
2417        local nofglyphs   = #charstrings
2418        for i=1,nofglyphs do
2419            charstrings[i] = readstring(f,charstrings[i])
2420        end
2421        data.nofglyphs         = nofglyphs
2422        dictionary.charstrings = charstrings
2423    else
2424        report("unsupported charstr type %i",stringtype)
2425        data.nofglyphs         = 0
2426        dictionary.charstrings = { }
2427    end
2428end
2429
2430-- cid (maybe do this stepwise so less mem) -- share with above
2431
2432local function readcidprivates(f,data)
2433    local header       = data.header
2434    local dictionaries = data.dictionaries[1].cid.dictionaries
2435    for i=1,#dictionaries do
2436        local dictionary = dictionaries[i]
2437        local private    = dictionary.private
2438        if private then
2439            setposition(f,header.offset+private.offset)
2440            private.data = readstring(f,private.size)
2441        end
2442    end
2443    parseprivates(data,dictionaries)
2444end
2445
2446readers.parsecharstrings = parsecharstrings -- used in font-onr.lua (type 1)
2447
2448local function readnoselect(f,fontdata,data,glyphs,doshapes,version,streams)
2449    local dictionaries = data.dictionaries
2450    local dictionary   = dictionaries[1]
2451    local cid          = not dictionary.private and dictionary.cid
2452    readglobals(f,data,version)
2453    readcharstrings(f,data,version)
2454    if version == "cff2" then
2455        dictionary.charset = nil
2456    else
2457        readencodings(f,data)
2458        readcharsets(f,data,dictionary)
2459    end
2460    if cid then
2461        local fdarray = cid.fdarray
2462        if fdarray then
2463            setposition(f,data.header.offset + fdarray)
2464            local dictionaries    = readlengths(f,version=="cff2")
2465            local nofdictionaries = #dictionaries
2466            if nofdictionaries > 0 then
2467                for i=1,nofdictionaries do
2468                    dictionaries[i] = readstring(f,dictionaries[i])
2469                end
2470                parsedictionaries(data,dictionaries)
2471                dictionary.private = dictionaries[1].private
2472                if nofdictionaries > 1 then
2473                    report("ignoring dictionaries > 1 in cid font")
2474                end
2475            end
2476        end
2477    end
2478    readprivates(f,data)
2479    parseprivates(data,data.dictionaries)
2480    readlocals(f,data,dictionary,version)
2481    startparsing(fontdata,data,streams)
2482    parsecharstrings(fontdata,data,glyphs,doshapes,version,streams)
2483    stopparsing(fontdata,data)
2484end
2485
2486local function readfdselect(f,fontdata,data,glyphs,doshapes,version,streams)
2487    local header       = data.header
2488    local dictionaries = data.dictionaries
2489    local dictionary   = dictionaries[1]
2490    local cid          = dictionary.cid
2491    local cidselect    = cid and cid.fdselect
2492    readglobals(f,data,version)
2493    readcharstrings(f,data,version)
2494    if version ~= "cff2" then
2495        readencodings(f,data)
2496    end
2497    local charstrings  = dictionary.charstrings
2498    local fdindex      = { }
2499    local nofglyphs    = data.nofglyphs
2500    local maxindex     = -1
2501    setposition(f,header.offset+cidselect)
2502    local format       = readbyte(f)
2503    if format == 1 then
2504        for i=0,nofglyphs do -- notdef included (needs checking)
2505            local index = readbyte(f)
2506            fdindex[i] = index
2507            if index > maxindex then
2508                maxindex = index
2509            end
2510        end
2511    elseif format == 3 then
2512        local nofranges = readushort(f)
2513        local first     = readushort(f)
2514        local index     = readbyte(f)
2515        while true do
2516            local last = readushort(f)
2517            if index > maxindex then
2518                maxindex = index
2519            end
2520            for i=first,last do
2521                fdindex[i] = index
2522            end
2523            if last >= nofglyphs then
2524                break
2525            else
2526                first = last + 1
2527                index = readbyte(f)
2528            end
2529        end
2530    else
2531        -- unsupported format
2532    end
2533    -- hm, always
2534    if maxindex >= 0 then
2535        local cidarray = cid.fdarray
2536        if cidarray then
2537            setposition(f,header.offset+cidarray)
2538            local dictionaries = readlengths(f,version == "cff2")
2539            for i=1,#dictionaries do
2540                dictionaries[i] = readstring(f,dictionaries[i])
2541            end
2542            parsedictionaries(data,dictionaries)
2543            cid.dictionaries = dictionaries
2544            readcidprivates(f,data)
2545            for i=1,#dictionaries do
2546                readlocals(f,data,dictionaries[i],version)
2547            end
2548            startparsing(fontdata,data,streams)
2549            for i=1,#charstrings do
2550                parsecharstring(fontdata,data,dictionaries[fdindex[i]+1],charstrings[i],glyphs,i,doshapes,version,streams)
2551--                 charstrings[i] = nil
2552            end
2553            stopparsing(fontdata,data)
2554        else
2555            report("no cid array")
2556        end
2557    end
2558end
2559
2560local gotodatatable = readers.helpers.gotodatatable
2561
2562local function cleanup(data,dictionaries)
2563 -- for i=1,#dictionaries do
2564 --     local d = dictionaries[i]
2565 --     d.subroutines = nil
2566 -- end
2567 -- data.strings = nil
2568 -- if data then
2569 --     data.charstrings  = nil
2570 --     data.routines     = nil
2571 -- end
2572end
2573
2574function readers.cff(f,fontdata,specification)
2575    local tableoffset = gotodatatable(f,fontdata,"cff",specification.details or specification.glyphs)
2576    if tableoffset then
2577        local header = readheader(f)
2578        if header.major ~= 1 then
2579            report("only version %s is supported for table %a",1,"cff")
2580            return
2581        end
2582        local glyphs       = fontdata.glyphs
2583        local names        = readfontnames(f)
2584        local dictionaries = readtopdictionaries(f)
2585        local strings      = readstrings(f)
2586        local data = {
2587            header       = header,
2588            names        = names,
2589            dictionaries = dictionaries,
2590            strings      = strings,
2591            nofglyphs    = fontdata.nofglyphs,
2592        }
2593        --
2594        parsedictionaries(data,dictionaries,"cff")
2595        --
2596        local dic = dictionaries[1]
2597        local cid = dic.cid
2598        --
2599        local cffinfo = {
2600            familyname         = dic.familyname,
2601            fullname           = dic.fullname,
2602            boundingbox        = dic.boundingbox,
2603            weight             = dic.weight,
2604            italicangle        = dic.italicangle,
2605            underlineposition  = dic.underlineposition,
2606            underlinethickness = dic.underlinethickness,
2607            defaultwidth       = dic.defaultwidthx,
2608            nominalwidth       = dic.nominalwidthx,
2609            monospaced         = dic.monospaced,
2610        }
2611        fontdata.cidinfo = cid and {
2612            registry   = cid.registry,
2613            ordering   = cid.ordering,
2614            supplement = cid.supplement,
2615        }
2616        fontdata.cffinfo = cffinfo
2617        --
2618        local all = specification.shapes or specification.streams or false
2619        if specification.glyphs or all then
2620            if cid and cid.fdselect then
2621                readfdselect(f,fontdata,data,glyphs,all,"cff",specification.streams)
2622            else
2623                readnoselect(f,fontdata,data,glyphs,all,"cff",specification.streams)
2624            end
2625        end
2626        local private = dic.private
2627        if private then
2628            local data = private.data
2629            if type(data) == "table" then
2630                cffinfo.defaultwidth     = data.defaultwidthx or cffinfo.defaultwidth
2631                cffinfo.nominalwidth     = data.nominalwidthx or cffinfo.nominalwidth
2632                cffinfo.bluevalues       = data.bluevalues
2633                cffinfo.otherblues       = data.otherblues
2634                cffinfo.familyblues      = data.familyblues
2635                cffinfo.familyotherblues = data.familyotherblues
2636                cffinfo.bluescale        = data.bluescale
2637                cffinfo.blueshift        = data.blueshift
2638                cffinfo.bluefuzz         = data.bluefuzz
2639                cffinfo.stdhw            = data.stdhw
2640                cffinfo.stdvw            = data.stdvw
2641            end
2642        end
2643        cleanup(data,dictionaries)
2644    end
2645end
2646
2647function readers.cff2(f,fontdata,specification)
2648    local tableoffset = gotodatatable(f,fontdata,"cff2",specification.glyphs)
2649    if tableoffset then
2650        local header = readheader(f)
2651        if header.major ~= 2 then
2652            report("only version %s is supported for table %a",2,"cff2")
2653            return
2654        end
2655        local glyphs       = fontdata.glyphs
2656        local dictionaries = { readstring(f,header.dsize) }
2657        local data = {
2658            header       = header,
2659            dictionaries = dictionaries,
2660            nofglyphs    = fontdata.nofglyphs,
2661        }
2662        --
2663        parsedictionaries(data,dictionaries,"cff2")
2664        --
2665        local offset = dictionaries[1].vstore
2666        if offset > 0 then
2667            local storeoffset = dictionaries[1].vstore + data.header.offset + 2 -- cff has a preceding size field
2668            local regions, deltas = readers.helpers.readvariationdata(f,storeoffset,factors)
2669            --
2670            data.regions  = regions
2671            data.deltas   = deltas
2672        else
2673            data.regions  = { }
2674            data.deltas   = { }
2675        end
2676        data.factors  = specification.factors
2677        --
2678        local cid = data.dictionaries[1].cid
2679        local all = specification.shapes or specification.streams or false
2680        if cid and cid.fdselect then
2681            readfdselect(f,fontdata,data,glyphs,all,"cff2",specification.streams)
2682        else
2683            readnoselect(f,fontdata,data,glyphs,all,"cff2",specification.streams)
2684        end
2685        cleanup(data,dictionaries)
2686    end
2687end
2688
2689-- temporary helper needed for checking backend patches
2690
2691function readers.cffcheck(filename)
2692    local f = io.open(filename,"rb")
2693    if f then
2694        local fontdata = {
2695            glyphs = { },
2696        }
2697        local header = readheader(f)
2698        if header.major ~= 1 then
2699            report("only version %s is supported for table %a",1,"cff")
2700            return
2701        end
2702        local names        = readfontnames(f)
2703        local dictionaries = readtopdictionaries(f)
2704        local strings      = readstrings(f)
2705        local glyphs       = { }
2706        local data = {
2707            header       = header,
2708            names        = names,
2709            dictionaries = dictionaries,
2710            strings      = strings,
2711            glyphs       = glyphs,
2712            nofglyphs    = 0,
2713        }
2714        --
2715        parsedictionaries(data,dictionaries,"cff")
2716        --
2717        local cid = data.dictionaries[1].cid
2718        if cid and cid.fdselect then
2719            readfdselect(f,fontdata,data,glyphs,false)
2720        else
2721            readnoselect(f,fontdata,data,glyphs,false)
2722        end
2723        return data
2724    end
2725end
2726