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