font-cff.lmt /size: 115 Kb    last modification: 2025-02-21 11:03
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_dictionary  do
309
310        local p_single =
311            P("\00") / function()
312                result.version = strings[stack[top]] or "unset"
313                top = 0
314            end
315          + P("\01") / function()
316                result.notice = strings[stack[top]] or "unset"
317                top = 0
318            end
319          + P("\02") / function()
320                result.fullname = strings[stack[top]] or "unset"
321                top = 0
322            end
323          + P("\03") / function()
324                result.familyname = strings[stack[top]] or "unset"
325                top = 0
326            end
327          + P("\04") / function()
328                result.weight = strings[stack[top]] or "unset"
329                top = 0
330            end
331          + P("\05") / function()
332                result.fontbbox = { unpack(stack,1,4) }
333                top = 0
334            end
335          + P("\06") / function()
336                result.bluevalues = { unpack(stack,1,top) }
337                top = 0
338            end
339          + P("\07") / function()
340                result.otherblues = { unpack(stack,1,top) }
341                top = 0
342            end
343          + P("\08") / function()
344                result.familyblues = { unpack(stack,1,top) }
345                top = 0
346            end
347          + P("\09") / function()
348                result.familyotherblues = { unpack(stack,1,top) }
349                top = 0
350            end
351          + P("\10") / function()
352                result.stdhw = stack[top]
353                top = 0
354            end
355          + P("\11") / function()
356                result.stdvw = stack[top]
357                top = 0
358            end
359          + P("\13") / function()
360                result.uniqueid = stack[top]
361                top = 0
362            end
363          + P("\14") / function()
364                result.xuid = concat(stack,"",1,top)
365                top = 0
366            end
367          + P("\15") / function()
368                result.charset = stack[top]
369                top = 0
370            end
371          + P("\16") / function()
372                result.encoding = stack[top]
373                top = 0
374            end
375          + P("\17") / function() -- valid cff2
376                result.charstrings = stack[top]
377                top = 0
378            end
379          + P("\18") / function()
380                result.private = {
381                    size   = stack[top-1],
382                    offset = stack[top],
383                }
384                top = 0
385            end
386          + P("\19") / function()
387                result.subroutines = stack[top]
388                top = 0 -- new, forgotten ?
389            end
390          + P("\20") / function()
391                result.defaultwidthx = stack[top]
392                top = 0 -- new, forgotten ?
393            end
394          + P("\21") / function()
395                result.nominalwidthx = stack[top]
396                top = 0 -- new, forgotten ?
397            end
398       -- + P("\22") / function() -- reserved
399       --   end
400       -- + P("\23") / function() -- reserved
401       --   end
402          + P("\24") / function() -- new in cff2
403                result.vstore = stack[top]
404                top = 0
405            end
406          + P("\25") / function() -- new in cff2
407                result.maxstack = stack[top]
408                top = 0
409            end
410       -- + P("\26") / function() -- reserved
411       --   end
412       -- + P("\27") / function() -- reserved
413       --   end
414
415        local p_double = P("\12") * (
416            P("\00") / function()
417                result.copyright = stack[top]
418                top = 0
419            end
420          + P("\01") / function()
421                result.monospaced = stack[top] == 1 and true or false -- isfixedpitch
422                top = 0
423            end
424          + P("\02") / function()
425                result.italicangle = stack[top]
426                top = 0
427            end
428          + P("\03") / function()
429                result.underlineposition = stack[top]
430                top = 0
431            end
432          + P("\04") / function()
433                result.underlinethickness = stack[top]
434                top = 0
435            end
436          + P("\05") / function()
437                result.painttype = stack[top]
438                top = 0
439            end
440          + P("\06") / function()
441                result.charstringtype = stack[top]
442                top = 0
443            end
444          + P("\07") / function() -- valid cff2
445                result.fontmatrix = { unpack(stack,1,6) }
446                top = 0
447            end
448          + P("\08") / function()
449                result.strokewidth = stack[top]
450                top = 0
451            end
452          + P("\09") / function()
453                result.bluescale = stack[top]
454                top = 0
455            end
456          + P("\10") / function()
457                result.blueshift = stack[top]
458                top = 0
459            end
460          + P("\11") / function()
461                result.bluefuzz = stack[top]
462                top = 0
463            end
464          + P("\12") / function()
465                result.stemsnaph = { unpack(stack,1,top) }
466                top = 0
467            end
468          + P("\13") / function()
469                result.stemsnapv = { unpack(stack,1,top) }
470                top = 0
471            end
472          + P("\20") / function()
473                result.syntheticbase = stack[top]
474                top = 0
475            end
476          + P("\21") / function()
477                result.postscript = strings[stack[top]] or "unset"
478                top = 0
479            end
480          + P("\22") / function()
481                result.basefontname = strings[stack[top]] or "unset"
482                top = 0
483            end
484          + P("\21") / function()
485                result.basefontblend = stack[top]
486                top = 0
487            end
488          + P("\30") / function()
489                result.cid.registry   = strings[stack[top-2]] or "unset"
490                result.cid.ordering   = strings[stack[top-1]] or "unset"
491                result.cid.supplement = stack[top]
492                top = 0
493            end
494          + P("\31") / function()
495                result.cid.fontversion = stack[top]
496                top = 0
497            end
498          + P("\32") / function()
499                result.cid.fontrevision= stack[top]
500                top = 0
501            end
502          + P("\33") / function()
503                result.cid.fonttype = stack[top]
504                top = 0
505            end
506          + P("\34") / function()
507                result.cid.count = stack[top]
508                top = 0
509            end
510          + P("\35") / function()
511                result.cid.uidbase = stack[top]
512                top = 0
513            end
514          + P("\36") / function() -- valid cff2
515                result.cid.fdarray = stack[top]
516                top = 0
517            end
518          + P("\37") / function() -- valid cff2
519                result.cid.fdselect = stack[top]
520                top = 0
521            end
522          + P("\38") / function()
523                result.cid.fontname = strings[stack[top]] or "unset"
524                top = 0
525            end
526        )
527
528        -- Some lpeg fun ... a first variant split the byte and made a new string but
529        -- the second variant is much faster. Not that it matters much as we don't see
530        -- such numbers often.
531
532        local remap_1 = {
533            ["\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",
534            ["\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",
535            ["\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",
536            ["\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",
537            ["\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",
538            ["\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",
539            ["\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",
540            ["\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",
541            ["\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",
542            ["\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",
543            ["\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"] = ".",
544            ["\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",
545            ["\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-",
546            ["\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"] = "-",
547        }
548        local remap_2 = {
549            ["\x0F"] = "0", ["\x1F"] = "1", ["\x2F"] = "2", ["\x3F"] = "3", ["\x4F"] = "4",
550            ["\x5F"] = "5", ["\x6F"] = "6", ["\x7F"] = "7", ["\x8F"] = "8", ["\x9F"] = "9",
551        }
552
553        local p_last_1 = S("\x0F\x1F\x2F\x3F\x4F\x5F\x6F\x7F\x8F\x9F\xAF\xBF")
554        local p_last_2 = R("\xF0\xFF")
555
556        -- tricky, we don't want to append last
557
558     -- local p_nibbles = P("\30") * Cs(((1-p_last)/remap)^0 * (P(1)/remap)) / function(n)
559        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)
560            -- 0-9=digit a=. b=E c=E- d=reserved e=- f=finish
561            top = top + 1
562            stack[top] = tonumber(n) or 0
563        end
564
565        local p_byte = C(R("\32\246")) / function(b0)
566            -- -107 .. +107
567            top = top + 1
568            stack[top] = byte(b0) - 139
569        end
570
571        local p_positive =  C(R("\247\250")) * C(1) / function(b0,b1)
572            -- +108 .. +1131
573            top = top + 1
574            stack[top] = (byte(b0)-247)*256 + byte(b1) + 108
575        end
576
577        local p_negative = C(R("\251\254")) * C(1) / function(b0,b1)
578            -- -1131 .. -108
579            top = top + 1
580            stack[top] = -(byte(b0)-251)*256 - byte(b1) - 108
581        end
582
583     -- local p_float = P("\255") * C(1) * C(1) * C(1) * C(1) / function(b0,b1,b2,b3)
584     --     top = top + 1
585     --     stack[top] = 0
586     -- end
587
588        local p_short = P("\28") * C(1) * C(1) / function(b1,b2)
589            -- -32768 .. +32767 : b1<<8 | b2
590            top = top + 1
591            local n = 0x100 * byte(b1) + byte(b2)
592            if n  >= 0x8000 then
593                stack[top] = n - 0xFFFF - 1
594            else
595                stack[top] =  n
596            end
597        end
598
599        local p_long = P("\29") * C(1) * C(1) * C(1) * C(1) / function(b1,b2,b3,b4)
600            -- -2^31 .. +2^31-1 : b1<<24 | b2<<16 | b3<<8 | b4
601            top = top + 1
602            local n = 0x1000000 * byte(b1) + 0x10000 * byte(b2) + 0x100 * byte(b3) + byte(b4)
603            if n >= 0x8000000 then
604                stack[top] = n - 0xFFFFFFFF - 1
605            else
606                stack[top] = n
607            end
608        end
609
610        local p_unsupported = P(1) / function(detail)
611            top = 0
612        end
613
614        p_dictionary = (
615            p_byte
616          + p_positive
617          + p_negative
618          + p_short
619          + p_long
620          + p_nibbles
621          + p_single
622          + p_double
623       -- + p_float
624          + p_unsupported
625        )^1
626
627    end
628
629    parsedictionaries = function(data,dictionaries,version)
630        stack   = { }
631        strings = data.strings
632        if trace_charstrings then
633            report("charstring format %a",version)
634        end
635        for i=1,#dictionaries do
636            top    = 0
637            result = version == "cff" and {
638                monospaced         = false,
639                italicangle        = 0,
640                underlineposition  = -100,
641                underlinethickness = 50,
642                painttype          = 0,
643                charstringtype     = 2,
644                fontmatrix         = { 0.001, 0, 0, 0.001, 0, 0 },
645                fontbbox           = { 0, 0, 0, 0 },
646                strokewidth        = 0,
647                charset            = 0,
648                encoding           = 0,
649                cid = {
650                    fontversion     = 0,
651                    fontrevision    = 0,
652                    fonttype        = 0,
653                    count           = 8720,
654                }
655            } or {
656                charstringtype     = 2,
657                charset            = 0,
658                vstore             = 0,
659                cid = {
660                    -- nothing yet
661                },
662            }
663            lpegmatch(p_dictionary,dictionaries[i])
664            dictionaries[i] = result
665        end
666        --
667        result = { }
668        top    = 0
669        stack  = { }
670    end
671
672    parseprivates = function(data,dictionaries)
673        stack   = { }
674        strings = data.strings
675        for i=1,#dictionaries do
676            local private = dictionaries[i].private
677            if private and private.data then
678                top     = 0
679                result  = {
680                    forcebold         = false,
681                    languagegroup     = 0,
682                    expansionfactor   = 0.06,
683                    initialrandomseed = 0,
684                    subroutines       = 0,
685                    defaultwidthx     = 0,
686                    nominalwidthx     = 0,
687                    cid               = {
688                        -- actually an error
689                    },
690                }
691                lpegmatch(p_dictionary,private.data)
692                private.data = result
693            end
694        end
695        result = { }
696        top    = 0
697        stack  = { }
698    end
699
700    -- All bezier curves have 6 points with successive pairs relative to
701    -- the previous pair. Some can be left out and are then copied or zero
702    -- (optimization).
703    --
704    -- We are not really interested in all the details of a glyph because we
705    -- only need to calculate the boundingbox. So, todo: a quick no result but
706    -- calculate only variant.
707    --
708    -- The conversion is straightforward and the specification os clear once
709    -- you understand that the x and y needs to be updates each step. It's also
710    -- quite easy to test because in mp a shape will look bad when a few variables
711    -- are swapped. But still there might be bugs down here because not all
712    -- variants are seen in a font so far. We are less compact that the ff code
713    -- because there quite some variants are done in one helper with a lot of
714    -- testing for states.
715
716    local x            = 0
717    local y            = 0
718    local width        = false
719    local lsb          = 0
720    local result       = { }
721    local r            = 0
722    local stems        = 0
723    local globalbias   = 0
724    local localbias    = 0
725    local nominalwidth = 0
726    local defaultwidth = 0
727    local charset      = false
728    local globals      = false
729    local locals       = false
730    local depth        = 1
731    local xmin         = 0
732    local xmax         = 0
733    local ymin         = 0
734    local ymax         = 0
735    local checked      = false
736    local keepcurve    = false
737    local version      = 2
738    local regions      = false
739    local nofregions   = 0
740    local region       = false
741    local factors      = false
742    local axis         = false
743    local vsindex      = 0
744    local justpass     = false
745    local seacs        = { }
746    local procidx      = nil
747    local flexhints    = { }
748    local ignorehint   = false
749    local nofflexhints = 0
750
751    local encode       = { }
752    local chars        = { }
753    local typeone      = false
754
755    local function showstate(where,i,n)
756        if i then
757            local j = i + n - 1
758            report("%w%-10s : [%s] step",depth*2+2,where,concat(stack," ",i,j <= top and j or top))
759        else
760            report("%w%-10s : [%s] n=%i",depth*2,where,concat(stack," ",1,top),top)
761        end
762    end
763
764    local function showvalue(where,value,showstack)
765        if showstack then
766            report("%w%-10s : %s : [%s] n=%i",depth*2,where,tostring(value),concat(stack," ",1,top),top)
767        else
768            report("%w%-10s : %s",depth*2,where,tostring(value))
769        end
770    end
771
772    -- All these indirect calls make this run slower but it's cleaner this way
773    -- and we cache the result. As we moved the boundingbox code inline we gain
774    -- some back. I inlined some of then and a bit speed can be gained by more
775    -- inlining but not that much.
776
777    setmetatableindex(chars,function (t,k) -- also make a helper
778        local v = char(k)
779        t[k] = v
780        return v
781    end)
782
783    local c_endchar   <const> = chars[14]
784
785    local c_closepath <const> = chars[09]
786    local c_endchar   <const> = chars[14]
787
788    local c_command   <const> = chars[12]
789
790    local pack_result_tagged, pack_result_untagged do
791
792        local c_vlineto   <const> = chars[07]
793        local c_hlineto   <const> = chars[06]
794        local c_rlineto   <const> = chars[05]
795
796        local c_vhcurveto <const> = chars[30]
797        local c_hvcurveto <const> = chars[31]
798        local c_rrcurveto <const> = chars[08]
799
800        local c_vmoveto   <const> = chars[04]
801        local c_hmoveto   <const> = chars[22]
802        local c_rmoveto   <const> = chars[21]
803
804        local function pack_curveto(result,r,x,y,x1,y1,x2,y2,x3,y3)
805            local d1 = x2 - x
806            local d2 = y2 - y
807            local d3 = x3 - x2
808            local d4 = y3 - y2
809            local d5 = x1 - x3
810            local d6 = y1 - y3
811            if d1 == 0 and d6 == 0 then
812                r = r + 1 ; result[r] = encode[d2]
813                r = r + 1 ; result[r] = encode[d3]
814                r = r + 1 ; result[r] = encode[d4]
815                r = r + 1 ; result[r] = encode[d5]
816                r = r + 1 ; result[r] = c_vhcurveto
817            elseif d2 == 0 and d5 == 0 then
818                r = r + 1 ; result[r] = encode[d1]
819                r = r + 1 ; result[r] = encode[d3]
820                r = r + 1 ; result[r] = encode[d4]
821                r = r + 1 ; result[r] = encode[d6]
822                r = r + 1 ; result[r] = c_hvcurveto
823            else
824                r = r + 1 ; result[r] = encode[d1]
825                r = r + 1 ; result[r] = encode[d2]
826                r = r + 1 ; result[r] = encode[d3]
827                r = r + 1 ; result[r] = encode[d4]
828                r = r + 1 ; result[r] = encode[d5]
829                r = r + 1 ; result[r] = encode[d6]
830                r = r + 1 ; result[r] = c_rrcurveto
831            end
832            return r, x1, y1
833        end
834
835        local function pack_moveto(result,r,x,y,x1,y1)
836            x = x1 - x
837            y = y1 - y
838            if x == 0 then
839                r = r + 1 ; result[r] = encode[y]
840                r = r + 1 ; result[r] = c_vmoveto
841            elseif y == 0 then
842                r = r + 1 ; result[r] = encode[x]
843                r = r + 1 ; result[r] = c_hmoveto
844            else
845                r = r + 1 ; result[r] = encode[x]
846                r = r + 1 ; result[r] = encode[y]
847                r = r + 1 ; result[r] = c_rmoveto
848            end
849            return r, x1, y1
850        end
851
852        local function pack_lineto(result,r,x,y,x1,y1)
853            x = x1 - x
854            y = y1 - y
855            if x == 0 then
856                r = r + 1 ; result[r] = encode[y]
857                r = r + 1 ; result[r] = c_vlineto
858            elseif y == 0 then
859                r = r + 1 ; result[r] = encode[x]
860                r = r + 1 ; result[r] = c_hlineto
861            else
862                r = r + 1 ; result[r] = encode[x]
863                r = r + 1 ; result[r] = encode[y]
864                r = r + 1 ; result[r] = c_rlineto
865            end
866            return r, x1, y1
867        end
868
869        pack_result_tagged = function(t,width,xoffset,yoffset)
870--             local result = { encode[width] }
871--             local r = 1
872            local result = { encode[width], encode[xoffset or 0], c_hmoveto }
873            local r = 3
874            local n = #t
875            local x = 0
876            local y = yoffset or 0
877--             if xoffset and yoffset then
878--                 r, x, y = pack_moveto(result,r,0,0,x,y)
879--             end
880            for i=1,n do
881                local ti = t[i]
882                local tg = ti[#ti]
883                if tg == "c" then
884                    r, x, y = pack_curveto(result,r,x,y,ti[5],ti[6],ti[1],ti[2],ti[3],ti[4])
885                elseif tg == "l" then
886                    r, x, y = pack_lineto(result,r,x,y,ti[1],ti[2])
887                elseif tg == "m" then
888                    r, x, y = pack_moveto(result,r,x,y,ti[1],ti[2])
889                elseif tg == "close" then
890                    r = r + 1 ; result[r] = c_closepath
891                end
892            end
893            r = r + 1 ; result[r] = c_closepath
894            r = r + 1 ; result[r] = c_endchar
895            return concat(result)
896        end
897
898        pack_result_untagged = function(t,width,xoffset,yoffset)
899            local result = { encode[width], encode[xoffset or 0], c_hmoveto }
900            local r = 3
901            local n = #t
902            local x = 0
903            local y = yoffset or 0
904            for i=1,n do
905                local ti = t[i]
906                local ni = #ti
907                for j=1,ni do
908                    local tj = ti[j]
909                    local nj = #tj
910                    if nj == 6 then
911                        r, x, y = pack_curveto(result,r,x,y,tj[1],tj[2],tj[3],tj[4],tj[5],tj[6])
912                    elseif j == 1 then
913                        r, x, y = pack_moveto(result,r,x,y,tj[1],tj[2])
914                    else
915                        r, x, y = pack_lineto(result,r,x,y,tj[1],tj[2])
916                    end
917                end
918            end
919            r = r + 1 ; result[r] = c_closepath
920            r = r + 1 ; result[r] = c_endchar
921            return concat(result)
922        end
923
924    end
925
926    -- Maybe have several action tables:
927    --
928    -- keep curve / checked
929    -- keep curve / not checked
930    -- checked
931    -- not checked
932
933    local xycurveto, xylineto, xymoveto
934
935    local function report_hints(verdict)
936        report("%w%-10s : [%s] n=%i, %s",depth*2,"hints",concat(flexhints," ",1,nofflexhints),nofflexhints,verdict)
937    end
938
939    local function flushflexhints()
940        if trace_charstrings then
941            showstate("flush flex hints")
942        end
943        if not ignorehint then
944            -- whatever
945        elseif nofflexhints == 14 then
946            if trace_charstrings then
947                report_hints("flex curves")
948            end
949            local ax =  x + flexhints[01] + flexhints[03]
950            local ay =  y + flexhints[02] + flexhints[04]
951            local bx = ax + flexhints[05]
952            local by = ay + flexhints[06]
953            local cx = bx + flexhints[07]
954            local cy = by + flexhints[08]
955            local dx = cx + flexhints[09]
956            local dy = cy + flexhints[10]
957            local ex = dx + flexhints[11]
958            local ey = dy + flexhints[12]
959                   x = ex + flexhints[13]
960                   y = ey + flexhints[14]
961            xycurveto(ax,ay,bx,by,cx,cy)
962            xycurveto(dx,dy,ex,ey,x,y)
963        else
964            if trace_charstrings then
965                report_hints(nofflexhints == 0 and "no values" or "incomplete")
966            end
967        end
968        ignorehint   = false
969        nofflexhints = 0
970    end
971
972    -- Do we need a level counter?
973
974    local function collectflexhints()
975        if ignorehint then
976            if trace_charstrings then
977                showstate("already collecting flex hints")
978            end
979        else
980            nofflexhints = 0
981            ignorehint   = true
982            if trace_charstrings then
983                showstate("collect flex hints")
984            end
985        end
986    end
987
988    local function resetflexhints(where)
989        if trace_charstrings then
990            showstate("reset flex hints")
991            report_hints(where)
992        end
993        ignorehint   = false
994        nofflexhints = 0
995    end
996
997    local closepath, addmoveto, fixmoveto, setwidthfrommove, setwidthfromstem
998
999    do
1000
1001        local close_path = { "close", "path" }
1002        local close_move = { "close", "move" }
1003
1004        closepath = function()
1005            if version == "cff" then
1006                if trace_charstrings then
1007                    showstate("closepath")
1008                end
1009                -- maybe also cff 2
1010                if r > 0 then
1011                    local rr = result[r]
1012                    local cc = rr == close_move or rr == close_path
1013                    local mm = rr[3] == "m"
1014                    if cc then
1015                        -- don't add close
1016                    elseif mm then
1017                        -- don't add close and wipe moveto
1018                        r = r - 1
1019                    else
1020                        r = r + 1 ; result[r] = close_path
1021                    end
1022                end
1023            end
1024            top = 0
1025        end
1026
1027        addmoveto = function(x,y)
1028            if r > 0 then
1029                local rr = result[r]
1030                local cc = rr == close_move or rr == close_path
1031                local mm = rr[3] == "m"
1032                if cc then
1033                    -- don't add close
1034                elseif mm then
1035                    -- don't add close and keep moveto
1036                    rr[1] = x
1037                    rr[2] = y
1038                    return
1039                else
1040                    r = r + 1 ; result[r] = close_move
1041                end
1042            end
1043            r = r + 1 ; result[r] = { x, y, "m" }
1044        end
1045
1046        fixmoveto = function()
1047            x = x + flexhints[1]
1048            y = y + flexhints[2]
1049            addmoveto(x,y)
1050            if trace_charstrings then
1051                report_hints("fix moveto")
1052            end
1053            ignorehint   = false
1054            nofflexhints = 0
1055        end
1056
1057        setwidthfrommove = function()
1058            if top > 1 then
1059                width = stack[1]
1060                if trace_charstrings then
1061                    showvalue("width from move",width)
1062                end
1063            else
1064                width = true
1065            end
1066        end
1067
1068        setwidthfromstem = function()
1069            if width then
1070                remove(stack,1)
1071            else
1072                width = remove(stack,1)
1073                if trace_charstrings then
1074                    showvalue("width from stem",width)
1075                end
1076            end
1077            top = top - 1
1078        end
1079
1080    end
1081
1082    xymoveto = function()
1083        if keepcurve then
1084            addmoveto(x,y)
1085        end
1086        if checked then
1087            if x > xmax then xmax = x elseif x < xmin then xmin = x end
1088            if y > ymax then ymax = y elseif y < ymin then ymin = y end
1089        else
1090            xmin = x
1091            ymin = y
1092            xmax = x
1093            ymax = y
1094            checked = true
1095        end
1096    end
1097
1098    local function xmoveto() -- slight speedup
1099        if keepcurve then
1100            addmoveto(x,y)
1101        end
1102        if not checked then
1103            xmin = x
1104            ymin = y
1105            xmax = x
1106            ymax = y
1107            checked = true
1108        elseif x > xmax then
1109            xmax = x
1110        elseif x < xmin then
1111            xmin = x
1112        end
1113    end
1114
1115    local function ymoveto() -- slight speedup
1116        if keepcurve then
1117            addmoveto(x,y)
1118        end
1119        if not checked then
1120            xmin = x
1121            ymin = y
1122            xmax = x
1123            ymax = y
1124            checked = true
1125        elseif y > ymax then
1126            ymax = y
1127        elseif y < ymin then
1128            ymin = y
1129        end
1130    end
1131
1132    local function moveto() -- not used
1133        if trace_charstrings then
1134            showstate("moveto")
1135        end
1136        top = 0 -- forgotten
1137        xymoveto()
1138    end
1139
1140    xylineto = function() -- we could inline, no blend
1141        if keepcurve then
1142            r = r + 1 ; result[r] = { x, y, "l" }
1143        end
1144        if checked then
1145            if x > xmax then xmax = x elseif x < xmin then xmin = x end
1146            if y > ymax then ymax = y elseif y < ymin then ymin = y end
1147        else
1148            xmin = x
1149            ymin = y
1150            xmax = x
1151            ymax = y
1152            checked = true
1153        end
1154    end
1155
1156    local function xlineto() -- slight speedup
1157        if keepcurve then
1158            r = r + 1 ; result[r] = { x, y, "l" }
1159        end
1160        if not checked then
1161            xmin = x
1162            ymin = y
1163            xmax = x
1164            ymax = y
1165            checked = true
1166        elseif x > xmax then
1167            xmax = x
1168        elseif x < xmin then
1169            xmin = x
1170        end
1171    end
1172
1173    local function ylineto() -- slight speedup
1174        if keepcurve then
1175            r = r + 1 ; result[r] = { x, y, "l" }
1176        end
1177        if not checked then
1178            xmin = x
1179            ymin = y
1180            xmax = x
1181            ymax = y
1182            checked = true
1183        elseif y > ymax then
1184            ymax = y
1185        elseif y < ymin then
1186            ymin = y
1187        end
1188    end
1189
1190    xycurveto = function(x1,y1,x2,y2,x3,y3,i,n) -- called local so no blend here
1191        if trace_charstrings then
1192            showstate("curveto",i,n)
1193        end
1194        if keepcurve then
1195            r = r + 1 ; result[r] = { x1, y1, x2, y2, x3, y3, "c" }
1196        end
1197        if checked then
1198            if x1 > xmax then xmax = x1 elseif x1 < xmin then xmin = x1 end
1199            if y1 > ymax then ymax = y1 elseif y1 < ymin then ymin = y1 end
1200        else
1201            xmin = x1
1202            ymin = y1
1203            xmax = x1
1204            ymax = y1
1205            checked = true
1206        end
1207        if x2 > xmax then xmax = x2 elseif x2 < xmin then xmin = x2 end
1208        if y2 > ymax then ymax = y2 elseif y2 < ymin then ymin = y2 end
1209        if x3 > xmax then xmax = x3 elseif x3 < xmin then xmin = x3 end
1210        if y3 > ymax then ymax = y3 elseif y3 < ymin then ymin = y3 end
1211    end
1212
1213    local function rmoveto()
1214        if ignorehint then
1215            if trace_charstrings then
1216                showstate("rmoveto (flex)")
1217            end
1218            nofflexhints = nofflexhints + 1 ; flexhints[nofflexhints] = stack[top-1]
1219            nofflexhints = nofflexhints + 1 ; flexhints[nofflexhints] = stack[top]
1220            top = 0
1221        else
1222            if trace_charstrings then
1223                showstate("rmoveto")
1224            end
1225            if not width then
1226                setwidthfrommove()
1227            end
1228            x = x + stack[top-1] -- dx1
1229            y = y + stack[top]   -- dy1
1230            top = 0
1231            xymoveto()
1232        end
1233    end
1234
1235    local function hmoveto()
1236        if ignorehint then
1237            if trace_charstrings then
1238                showstate("hmoveto (flex)")
1239            end
1240            nofflexhints = nofflexhints + 1 ; flexhints[nofflexhints] = stack[top]
1241            nofflexhints = nofflexhints + 1 ; flexhints[nofflexhints] = 0
1242            top = 0
1243        else
1244            if trace_charstrings then
1245                showstate("hmoveto")
1246            end
1247            if not width then
1248                setwidthfrommove()
1249            end
1250            x = x + stack[top] -- dx1
1251            top = 0
1252            xmoveto()
1253        end
1254    end
1255
1256    local function vmoveto()
1257        if ignorehint then
1258            if trace_charstrings then
1259                showstate("vmoveto (flex)")
1260            end
1261            nofflexhints = nofflexhints + 1 ; flexhints[nofflexhints] = 0
1262            nofflexhints = nofflexhints + 1 ; flexhints[nofflexhints] = stack[top]
1263            top = 0
1264        else
1265            if trace_charstrings then
1266                showstate("vmoveto")
1267            end
1268            if not width then
1269                setwidthfrommove()
1270            end
1271            y = y + stack[top] -- dy1
1272            top = 0
1273            ymoveto()
1274        end
1275    end
1276
1277    local function rlineto()
1278        if nofflexhints > 0 then
1279            fixmoveto()
1280        end
1281        if trace_charstrings then
1282            showstate("rlineto")
1283        end
1284        for i=1,top,2 do
1285            x = x + stack[i]   -- dxa
1286            y = y + stack[i+1] -- dya
1287            xylineto()
1288        end
1289        top = 0
1290    end
1291
1292    local function hlineto() -- x (y,x)+ | (x,y)+
1293        if nofflexhints > 0 then
1294            fixmoveto()
1295        end
1296        if trace_charstrings then
1297            showstate("hlineto")
1298        end
1299        if top == 1 then
1300            x = x + stack[1]
1301            xlineto()
1302        else
1303            local swap = true
1304            for i=1,top do
1305                local d = stack[i]
1306                if swap then
1307                    x = x + d
1308                    xlineto()
1309                    swap = false
1310                else
1311                    y = y + d
1312                    ylineto()
1313                    swap = true
1314                end
1315            end
1316        end
1317        top = 0
1318    end
1319
1320    local function vlineto() -- y (x,y)+ | (y,x)+
1321        if nofflexhints > 0 then
1322            fixmoveto()
1323        end
1324        if trace_charstrings then
1325            showstate("vlineto")
1326        end
1327        if top == 1 then
1328            y = y + stack[1]
1329            ylineto()
1330        else
1331            local swap = false
1332            for i=1,top do
1333                local d = stack[i]
1334                if swap then
1335                    x = x + d
1336                    xlineto()
1337                    swap = false
1338                else
1339                    y = y + d
1340                    ylineto()
1341                    swap = true
1342                end
1343            end
1344        end
1345        top = 0
1346    end
1347
1348    local function rrcurveto()
1349        if nofflexhints > 0 then
1350            fixmoveto()
1351        end
1352        if trace_charstrings then
1353            showstate("rrcurveto")
1354        end
1355        if top == 6 then
1356            local ax = x  + stack[1] -- dxa
1357            local ay = y  + stack[2] -- dya
1358            local bx = ax + stack[3] -- dxb
1359            local by = ay + stack[4] -- dyb
1360            x = bx + stack[5]        -- dxc
1361            y = by + stack[6]        -- dyc
1362            xycurveto(ax,ay,bx,by,x,y,1,6)
1363        else
1364            for i=1,top,6 do
1365                local ax = x  + stack[i]   -- dxa
1366                local ay = y  + stack[i+1] -- dya
1367                local bx = ax + stack[i+2] -- dxb
1368                local by = ay + stack[i+3] -- dyb
1369                x = bx + stack[i+4]        -- dxc
1370                y = by + stack[i+5]        -- dyc
1371                xycurveto(ax,ay,bx,by,x,y,i,6)
1372            end
1373        end
1374        top = 0
1375    end
1376
1377    local function hhcurveto()
1378        if nofflexhints > 0 then
1379            fixmoveto()
1380        end
1381        if trace_charstrings then
1382            showstate("hhcurveto")
1383        end
1384        if top == 4 then
1385            local ax = x  + stack[1] -- dxa
1386            local ay = y
1387            local bx = ax + stack[2] -- dxb
1388            local by = ay + stack[3] -- dyb
1389            x = bx + stack[4]        -- dxc
1390            y = by
1391            xycurveto(ax,ay,bx,by,x,y,1,4)
1392        elseif top == 5 then
1393            local ax = x  + stack[2] -- dxa
1394            local ay = y  + stack[1]
1395            local bx = ax + stack[3] -- dxb
1396            local by = ay + stack[4] -- dyb
1397            x = bx + stack[5]        -- dxc
1398            y = by
1399            xycurveto(ax,ay,bx,by,x,y,2,4)
1400        else
1401            local s
1402            if top % 2 ~= 0 then
1403                y = y + stack[1] -- dy1
1404                s = 2
1405            else
1406                s = 1
1407            end
1408            for i=s,top,4 do
1409                local ax = x  + stack[i]   -- dxa
1410                local ay = y
1411                local bx = ax + stack[i+1] -- dxb
1412                local by = ay + stack[i+2] -- dyb
1413                x = bx + stack[i+3]        -- dxc
1414                y = by
1415                xycurveto(ax,ay,bx,by,x,y,i,4)
1416            end
1417        end
1418        top = 0
1419    end
1420
1421    local function vvcurveto()
1422        if nofflexhints > 0 then
1423            fixmoveto()
1424        end
1425        if trace_charstrings then
1426            showstate("vvcurveto")
1427        end
1428        if top == 4 then
1429            local ax = x
1430            local ay = y  + stack[1] -- dya
1431            local bx = ax + stack[2] -- dxb
1432            local by = ay + stack[3] -- dyb
1433            x = bx
1434            y = by + stack[4]        -- dyc
1435            xycurveto(ax,ay,bx,by,x,y,1,4)
1436        elseif top == 5 then
1437            local ax = x  + stack[1]
1438            local ay = y  + stack[2] -- dya
1439            local bx = ax + stack[3] -- dxb
1440            local by = ay + stack[4] -- dyb
1441            x = bx
1442            y = by + stack[5]        -- dyc
1443            xycurveto(ax,ay,bx,by,x,y,2,4)
1444        else
1445            local s
1446            if top % 2 ~= 0 then
1447                x = x + stack[1] -- dx1
1448                s = 2
1449            else
1450                s = 1
1451            end
1452            for i=s,top,4 do
1453                local ax = x
1454                local ay = y  + stack[i]   -- dya
1455                local bx = ax + stack[i+1] -- dxb
1456                local by = ay + stack[i+2] -- dyb
1457                x = bx
1458                y = by + stack[i+3]        -- dyc
1459                xycurveto(ax,ay,bx,by,x,y,i,4)
1460            end
1461        end
1462        top = 0
1463    end
1464
1465    -- just use copy ... can be faster
1466
1467    local function xxcurveto(swap)
1468        local last = top % 4 ~= 0 and stack[top]
1469        if last then
1470            top = top - 1
1471        end
1472        if top == 4 then -- also catches 5
1473            local ax, ay, bx, by
1474            if swap then
1475                ax = x + stack[1]
1476                ay = y
1477                bx = ax + stack[2]
1478                by = ay + stack[3]
1479                x  = bx
1480                y  = by + stack[4]
1481                if last then
1482                    x = x + last
1483                end
1484            else
1485                ax = x
1486                ay = y  + stack[1]
1487                bx = ax + stack[2]
1488                by = ay + stack[3]
1489                x  = bx + stack[4]
1490                y  = by
1491                if last then
1492                    y = y + last
1493                end
1494            end
1495            xycurveto(ax,ay,bx,by,x,y,1,4)
1496        else
1497            local done = top - 3
1498            for i=1,top,4 do
1499                local ax, ay, bx, by
1500                if swap then
1501                    ax = x  + stack[i]
1502                    ay = y
1503                    bx = ax + stack[i+1]
1504                    by = ay + stack[i+2]
1505                    y  = by + stack[i+3]
1506                    x  = bx
1507                    swap = false
1508                else
1509                    ax = x
1510                    ay = y  + stack[i]
1511                    bx = ax + stack[i+1]
1512                    by = ay + stack[i+2]
1513                    x  = bx + stack[i+3]
1514                    y  = by
1515                    swap = true
1516                end
1517                if last and i == done then
1518                    if swap then
1519                        y = y + last
1520                    else
1521                        x = x + last
1522                    end
1523                end
1524                xycurveto(ax,ay,bx,by,x,y,i,4)
1525            end
1526        end
1527        top = 0
1528    end
1529
1530    local function hvcurveto()
1531        if nofflexhints > 0 then
1532            fixmoveto()
1533        end
1534        if trace_charstrings then
1535            showstate("hvcurveto")
1536        end
1537        xxcurveto(true)
1538    end
1539
1540    local function vhcurveto()
1541        if nofflexhints > 0 then
1542            fixmoveto()
1543        end
1544        if trace_charstrings then
1545            showstate("vhcurveto")
1546        end
1547        xxcurveto(false)
1548    end
1549
1550    local function rcurveline()
1551        if nofflexhints > 0 then
1552            fixmoveto()
1553        end
1554        if trace_charstrings then
1555            showstate("rcurveline")
1556        end
1557        for i=1,top-2,6 do
1558            local ax = x  + stack[i]   -- dxa
1559            local ay = y  + stack[i+1] -- dya
1560            local bx = ax + stack[i+2] -- dxb
1561            local by = ay + stack[i+3] -- dyb
1562            x = bx + stack[i+4] -- dxc
1563            y = by + stack[i+5] -- dyc
1564            xycurveto(ax,ay,bx,by,x,y,i,6)
1565        end
1566        x = x + stack[top-1] -- dxc
1567        y = y + stack[top]   -- dyc
1568        xylineto()
1569        top = 0
1570    end
1571
1572    local function rlinecurve()
1573        if nofflexhints > 0 then
1574            fixmoveto()
1575        end
1576        if trace_charstrings then
1577            showstate("rlinecurve")
1578        end
1579        if top > 6 then
1580            for i=1,top-6,2 do
1581                x = x + stack[i]
1582                y = y + stack[i+1]
1583                xylineto()
1584            end
1585        end
1586        local ax = x  + stack[top-5]
1587        local ay = y  + stack[top-4]
1588        local bx = ax + stack[top-3]
1589        local by = ay + stack[top-2]
1590        x = bx + stack[top-1]
1591        y = by + stack[top]
1592        xycurveto(ax,ay,bx,by,x,y)
1593        top = 0
1594    end
1595
1596    -- flex is not yet tested! no loop
1597
1598    local function flex() -- fd not used
1599        if nofflexhints > 0 then
1600            fixmoveto()
1601        end
1602        if trace_charstrings then
1603            showstate("flex")
1604        end
1605        local ax = x  + stack[1]  -- dx1
1606        local ay = y  + stack[2]  -- dy1
1607        local bx = ax + stack[3]  -- dx2
1608        local by = ay + stack[4]  -- dy2
1609        local cx = bx + stack[5]  -- dx3
1610        local cy = by + stack[6]  -- dy3
1611        local dx = cx + stack[7]  -- dx4
1612        local dy = cy + stack[8]  -- dy4
1613        local ex = dx + stack[9]  -- dx5
1614        local ey = dy + stack[10] -- dy5
1615               x = ex + stack[11] -- dx6
1616               y = ey + stack[12] -- dy6
1617        xycurveto(ax,ay,bx,by,cx,cy)
1618        xycurveto(dx,dy,ex,ey,x,y)
1619        top = 0
1620    end
1621
1622    local function hflex()
1623        if nofflexhints > 0 then
1624            fixmoveto()
1625        end
1626        if trace_charstrings then
1627            showstate("hflex")
1628        end
1629        local ax = x  + stack[1] -- dx1
1630        local ay = y
1631        local bx = ax + stack[2] -- dx2
1632        local by = ay + stack[3] -- dy2
1633        local cx = bx + stack[4] -- dx3
1634        local cy = by
1635        xycurveto(ax,ay,bx,by,cx,cy)
1636        local dx = cx + stack[5] -- dx4
1637        local dy = by
1638        local ex = dx + stack[6] -- dx5
1639        local ey = y
1640        x = ex + stack[7]        -- dx6
1641        xycurveto(dx,dy,ex,ey,x,y)
1642        top = 0
1643    end
1644
1645    local function hflex1()
1646        if nofflexhints > 0 then
1647            fixmoveto()
1648        end
1649        if trace_charstrings then
1650            showstate("hflex1")
1651        end
1652        local ax = x  + stack[1] -- dx1
1653        local ay = y  + stack[2] -- dy1
1654        local bx = ax + stack[3] -- dx2
1655        local by = ay + stack[4] -- dy2
1656        local cx = bx + stack[5] -- dx3
1657        local cy = by
1658        xycurveto(ax,ay,bx,by,cx,cy)
1659        local dx = cx + stack[6] -- dx4
1660        local dy = by
1661        local ex = dx + stack[7] -- dx5
1662        local ey = dy + stack[8] -- dy5
1663        x = ex + stack[9]        -- dx6
1664        xycurveto(dx,dy,ex,ey,x,y)
1665        top = 0
1666    end
1667
1668    local function flex1()
1669        if nofflexhints > 0 then
1670            fixmoveto()
1671        end
1672        if trace_charstrings then
1673            showstate("flex1")
1674        end
1675        local ax = x  + stack[1]  --dx1
1676        local ay = y  + stack[2]  --dy1
1677        local bx = ax + stack[3]  --dx2
1678        local by = ay + stack[4]  --dy2
1679        local cx = bx + stack[5]  --dx3
1680        local cy = by + stack[6]  --dy3
1681        xycurveto(ax,ay,bx,by,cx,cy)
1682        local dx = cx + stack[7]  --dx4
1683        local dy = cy + stack[8]  --dy4
1684        local ex = dx + stack[9]  --dx5
1685        local ey = dy + stack[10] --dy5
1686        if abs(ex - x) > abs(ey - y) then -- spec: abs(dx) > abs(dy)
1687            x = ex + stack[11]
1688        else
1689            y = ey + stack[11]
1690        end
1691        xycurveto(dx,dy,ex,ey,x,y)
1692        top = 0
1693    end
1694
1695    local function getstem()
1696        if top == 0 then
1697            -- bad
1698        elseif top % 2 ~= 0 then
1699            setwidthfromstem()
1700        end
1701        if trace_charstrings then
1702            showstate("stem")
1703        end
1704        stems = stems + (top // 2)
1705        top   = 0
1706    end
1707
1708    local function getmask()
1709        if top == 0 then
1710            -- bad
1711        elseif top % 2 ~= 0 then
1712            setwidthfromstem()
1713        end
1714        if trace_charstrings then
1715            showstate("mask")
1716        end
1717        stems = stems + (top // 2)
1718        top   = 0
1719        if stems == 0 then
1720            -- forget about it
1721        elseif stems <= 8 then
1722            return 1
1723        else
1724            return (stems + 7) // 8
1725        end
1726    end
1727
1728    local function unsupported(t)
1729        if trace_charstrings then
1730            showstate("unsupported " .. t)
1731        end
1732        top = 0
1733    end
1734
1735    local function unsupportedsub(t)
1736        if trace_charstrings then
1737            showstate("unsupported sub " .. t)
1738        end
1739        top = 0
1740    end
1741
1742    -- type 1 (not used in type 2)
1743
1744    local function getvstem3()
1745        if trace_charstrings then
1746            showstate("vstem3")
1747        end
1748        top = 0
1749    end
1750
1751    local function gethstem3()
1752        if trace_charstrings then
1753            showstate("hstem3")
1754        end
1755        top = 0
1756    end
1757
1758    local function divide()
1759        if version == "cff" then
1760            if trace_charstrings then
1761                showstate("divide")
1762            end
1763            local d = stack[top]
1764            top = top - 1
1765            stack[top] = stack[top] / d
1766        end
1767    end
1768
1769    local function hsbw()
1770        if version == "cff" then
1771            if trace_charstrings then
1772                showstate("hsbw")
1773            end
1774            lsb   = stack[top-1] or 0
1775            width = stack[top]
1776        end
1777        top = 0
1778    end
1779
1780    local function sbw()
1781        if version == "cff" then
1782            if trace_charstrings then
1783                showstate("sbw")
1784            end
1785            lsb   = stack[top-3]
1786            width = stack[top-1]
1787        end
1788        top = 0
1789    end
1790
1791    -- asb adx ady bchar achar seac (accented characters)
1792
1793    local function seac()
1794        if version == "cff" then
1795            if trace_charstrings then
1796                showstate("seac")
1797            end
1798        end
1799        top = 0
1800    end
1801
1802    -- These are probably used for special cases i.e. call out to the
1803    -- postscript interpreter (p 61 of the spec as well as chapter 8).
1804
1805    local hints  <const> = 3
1806    local popped         = 3
1807
1808    -- arg1 ... argn n othersubr# <callothersubr> (on postscript stack)
1809
1810    -- 0 1 2 : implements flex
1811    -- 3     : implements hint
1812
1813    local function callothersubr()
1814        if version == "cff" then
1815            if trace_charstrings then
1816                showstate("callothersubr")
1817            end
1818            if stack[top] == hints then
1819                popped = stack[top-2]
1820            else
1821                popped = 3 --ff no, taco yes
1822            end
1823            local t = stack[top-1]
1824            if t then
1825                top = top - (t + 2)
1826                if top < 0 then
1827                    top = 0
1828                end
1829            else
1830                top = 0
1831            end
1832        else
1833            top = 0
1834        end
1835    end
1836
1837    local function pop()
1838        if version == "cff" then
1839            if trace_charstrings then
1840                showstate("pop")
1841            end
1842            top = top + 1
1843            stack[top] = popped
1844        else
1845            top = 0
1846        end
1847    end
1848
1849    local function setcurrentpoint()
1850        if version == "cff" then
1851            if trace_charstrings then
1852                showstate("setcurrentpoint (unsupported)")
1853            end
1854            -- can be an option, doesn't always work out well
1855            x = x + stack[top-1]
1856            y = y + stack[top]
1857        end
1858        top = 0
1859    end
1860
1861    -- So far for unsupported postscript. Now some cff2 magic. As I still need
1862    -- to wrap my head around the rather complex variable font specification
1863    -- with regions and axis, the following approach kind of works but is more
1864    -- some trial and error trick. It's still not clear how much of the complex
1865    -- truetype description applies to cff. Once there are fonts out there we'll
1866    -- get there. (Marcel and friends did some tests with recent cff2 fonts so
1867    -- the code has been adapted accordingly.)
1868
1869    local reginit = false
1870
1871    local function updateregions(n) -- n + 1
1872        if regions then
1873            local current = regions[n+1] or regions[1]
1874            nofregions = #current
1875            if axis and n ~= reginit then
1876                factors = { }
1877                for i=1,nofregions do
1878                    local region = current[i]
1879                    local s = 1
1880                    for j=1,#axis do
1881                        local f = axis[j]
1882                        local r = region[j]
1883                        local start = r.start
1884                        local peak  = r.peak
1885                        local stop  = r.stop
1886                        if start > peak or peak > stop then
1887                            -- * 1
1888                        elseif start < 0 and stop > 0 and peak ~= 0 then
1889                            -- * 1
1890                        elseif peak == 0 then
1891                            -- * 1
1892                        elseif f < start or f > stop then
1893                            -- * 0
1894                            s = 0
1895                            break
1896                        elseif f < peak then
1897                            s = s * (f - start) / (peak - start)
1898                        elseif f > peak then
1899                            s = s * (stop - f) / (stop - peak)
1900                        else
1901                            -- * 1
1902                        end
1903                    end
1904                    factors[i] = s
1905                end
1906            end
1907        end
1908        reginit = n
1909    end
1910
1911    local function setvsindex()
1912        local vsindex = stack[top]
1913        if trace_charstrings then
1914            showstate(formatters["vsindex %i"](vsindex))
1915        end
1916        updateregions(vsindex)
1917        top = top - 1
1918    end
1919
1920    local function blend()
1921        local n = stack[top]
1922        top = top - 1
1923        if axis then
1924            --  x    (r1x,r2x,r3x)
1925            -- (x,y) (r1x,r2x,r3x) (r1y,r2y,r3y)
1926            if trace_charstrings then
1927                local t = top - nofregions * n
1928                local m = t - n
1929                for i=1,n do
1930                    local k   = m + i
1931                    local d   = m + n + (i-1)*nofregions
1932                    local old = stack[k]
1933                    local new = old
1934                    for r=1,nofregions do
1935                        new = new + stack[d+r] * factors[r]
1936                    end
1937                    stack[k] = new
1938                    showstate(formatters["blend %i of %i: %s -> %s"](i,n,old,new))
1939                end
1940                top = t
1941            elseif n == 1 then
1942                top = top - nofregions
1943                local v = stack[top]
1944                for r=1,nofregions do
1945                    v = v + stack[top+r] * factors[r]
1946                end
1947                stack[top] = v
1948            else
1949                top = top - nofregions * n
1950                local d = top
1951                local k = top - n
1952                for i=1,n do
1953                    k = k + 1
1954                    local v = stack[k]
1955                    for r=1,nofregions do
1956                        v = v + stack[d+r] * factors[r]
1957                    end
1958                    stack[k] = v
1959                    d = d + nofregions
1960                end
1961            end
1962        else
1963            top = top - nofregions * n
1964        end
1965    end
1966
1967    -- Bah, we cannot use a fast lpeg because a hint has an unknown size and a
1968    -- runtime capture cannot handle that well.
1969
1970    local actions = { [0] =
1971        unsupported,  --  0
1972        getstem,      --  1 -- hstem
1973        unsupported,  --  2
1974        getstem,      --  3 -- vstem
1975        vmoveto,      --  4
1976        rlineto,      --  5
1977        hlineto,      --  6
1978        vlineto,      --  7
1979        rrcurveto,    --  8
1980        closepath,    --  9 -- closepath
1981        unsupported,  -- 10 -- calllocal,
1982        unsupported,  -- 11 -- callreturn,
1983        unsupported,  -- 12 -- elsewhere
1984        hsbw,         -- 13 -- hsbw (type 1 cff)
1985        unsupported,  -- 14 -- endchar,
1986        setvsindex,   -- 15 -- cff2
1987        blend,        -- 16 -- cff2
1988        unsupported,  -- 17
1989        getstem,      -- 18 -- hstemhm
1990        getmask,      -- 19 -- hintmask
1991        getmask,      -- 20 -- cntrmask
1992        rmoveto,      -- 21
1993        hmoveto,      -- 22
1994        getstem,      -- 23 -- vstemhm
1995        rcurveline,   -- 24
1996        rlinecurve,   -- 25
1997        vvcurveto,    -- 26
1998        hhcurveto,    -- 27
1999        unsupported,  -- 28 -- elsewhere
2000        unsupported,  -- 29 -- elsewhere
2001        vhcurveto,    -- 30
2002        hvcurveto,    -- 31
2003    }
2004
2005    local reverse = { [0] =
2006        "unsupported",
2007        "getstem",
2008        "unsupported",
2009        "getstem",
2010        "vmoveto",
2011        "rlineto",
2012        "hlineto",
2013        "vlineto",
2014        "rrcurveto",
2015        "unsupported",
2016        "unsupported",
2017        "unsupported",
2018        "unsupported",
2019        "hsbw",
2020        "unsupported",
2021        "setvsindex",
2022        "blend",
2023        "unsupported",
2024        "getstem",
2025        "getmask",
2026        "getmask",
2027        "rmoveto",
2028        "hmoveto",
2029        "getstem",
2030        "rcurveline",
2031        "rlinecurve",
2032        "vvcurveto",
2033        "hhcurveto",
2034        "unsupported",
2035        "unsupported",
2036        "vhcurveto",
2037        "hvcurveto",
2038    }
2039
2040    local subactions = {
2041        -- cff 1
2042        [000] = dotsection,      -- (cff1)
2043        [001] = getvstem3,       -- (cff1)
2044        [002] = gethstem3,       -- (cff1)
2045        [003] = false,           -- (cff2) todo: logicaland,
2046        [004] = false,           -- (cff2) todo: logicalor,
2047        [005] = false,           -- (cff2) todo: logicalnot,
2048        [006] = seac,            -- (cff1)
2049        [007] = sbw,             -- (cff1)
2050        [008] = false,           -- reserved
2051        [009] = false,           -- (cff2) todo: absolute,
2052        [010] = false,           -- (cff2) todo: addition,
2053        [011] = false,           -- (cff2) todo: subtract,
2054        [012] = divide,          -- (cff1/2)
2055        [013] = false,           -- reserved
2056        [014] = false,           -- (cff2) todo: negate,
2057        [015] = false,           -- (cff2) todo: equal,
2058        [016] = callothersubr,   -- (cff1)
2059        [017] = pop,             -- (cff1)
2060        [018] = false,           -- (cff2) todo: drop,
2061        [019] = false,           -- reserved
2062        [020] = false,           -- (cff2) todo: put,
2063        [021] = false,           -- (cff2) todo: get,
2064        [022] = false,           -- (cff2) todo: ifelse,
2065        [023] = false,           -- (cff2) todo: random,
2066        [024] = false,           -- (cff2) todo: multiply,
2067        [025] = false,           -- reserved
2068        [026] = false,           -- (cff2) todo: sqrt,
2069        [027] = false,           -- (cff2) todo: dup,
2070        [028] = false,           -- (cff2) todo: exchange,
2071        [029] = false,           -- (cff2) todo: index,
2072        [030] = false,           -- (cff2) todo: roll,
2073        [032] = false,           -- reserved
2074        [033] = false,           -- reserved
2075        [033] = setcurrentpoint, -- (cff1)
2076        [034] = hflex,           -- (cff2)
2077        [035] = flex,            -- (cff2)
2078        [036] = hflex1,          -- (cff2)
2079        [037] = flex1,           -- (cff2)
2080    }
2081
2082    -- todo: round in blend
2083
2084    -- this eventually can become a helper
2085
2086    setmetatableindex(encode,function(t,i)
2087        for i=-2048,-1130 do
2088         -- t[i] = char(28,band(rshift(i,8),0xFF),band(i,0xFF))
2089            t[i] = char(28,(i >> 8) & 0xFF,i & 0xFF)
2090        end
2091        for i=-1131,-108 do
2092            local v = 0xFB00 - i - 108
2093         -- t[i] = char(band(rshift(v,8),0xFF),band(v,0xFF))
2094            t[i] = char((v >> 8) & 0xFF,v & 0xFF)
2095        end
2096        for i=-107,107 do
2097            t[i] = chars[i + 139]
2098        end
2099        for i=108,1131 do
2100            local v = 0xF700 + i - 108
2101         -- t[i] = char(extract(v,8,8),extract(v,0,8))
2102            t[i] = char((v >> 8) & 0xFF,v & 0xFF)
2103        end
2104        for i=1132,2048 do
2105         -- t[i] = char(28,band(rshift(i,8),0xFF),band(i,0xFF))
2106            t[i] = char(28,(i >> 8) & 0xFF,i & 0xFF)
2107        end
2108        setmetatableindex(encode,function(t,k)
2109            -- as we're cff2 we write 16.16-bit signed fixed value
2110            local r = round(k)
2111            local v = rawget(t,r)
2112            if v then
2113                return v
2114            end
2115            local v1 = floor(k)
2116            local v2 = floor((k - v1) * 0x10000)
2117         -- return char(255,extract(v1,8,8),extract(v1,0,8),extract(v2,8,8),extract(v2,0,8))
2118            return char(255,(v1 >> 8) & 0xFF,v1 & 0xFF,(v2 >> 8) & 0xFF,v2 & 0xFF)
2119        end)
2120        return t[i]
2121    end)
2122
2123    readers.cffencoder = encode
2124
2125    local function p_setvsindex()
2126        local vsindex = stack[top]
2127        updateregions(vsindex)
2128        top = top - 1
2129    end
2130
2131    local function p_blend()
2132        -- leaves n values on stack
2133        local n = stack[top]
2134        top = top - 1
2135        if not axis then
2136            -- fatal error
2137        elseif n == 1 then
2138            top = top - nofregions
2139            local v = stack[top]
2140            for r=1,nofregions do
2141                v = v + stack[top+r] * factors[r]
2142            end
2143            stack[top] = round(v)
2144        else
2145            top = top - nofregions * n
2146            local d = top
2147            local k = top - n
2148            for i=1,n do
2149                k = k + 1
2150                local v = stack[k]
2151                for r=1,nofregions do
2152                    v = v + stack[d+r] * factors[r]
2153                end
2154                stack[k] = round(v)
2155                d = d + nofregions
2156            end
2157        end
2158    end
2159
2160    local function p_getstem()
2161        local n = 0
2162        if top % 2 ~= 0 then
2163            n = 1
2164        end
2165        if top > n then
2166            stems = stems + ((top - n) // 2)
2167        end
2168    end
2169
2170    local function p_getmask()
2171        local n = 0
2172        if top % 2 ~= 0 then
2173            n = 1
2174        end
2175        if top > n then
2176            stems = stems + ((top - n) // 2)
2177        end
2178        if stems == 0 then
2179            return 0
2180        elseif stems <= 8 then
2181            return 1
2182        else
2183            return (stems + 7) // 8
2184        end
2185    end
2186
2187    -- end of experiment
2188
2189    -- After years of using this I ran into an old font (sabon) that came out bad and
2190    -- after messing around a bit I realized that Taco had added Type1 outlines to mplib
2191    -- so I peeked in there and adapted the flexhint code here to his approach. (In
2192    -- LuaMetaTex we don't have that code in the MetaPost library, otherwise I might
2193    -- have noticed it sooner.) Anyway, here we also need to accumulate the h and v
2194    -- moves in order to get it working and we need to deal with fonts that have bad
2195    -- hitn accumulation. Then I still has some misses and decided to see what
2196    -- fontfforge does, but it does things somewhat different and also seems to catch
2197    -- issues with fonts. The LuaTeX backend has deifferent code for Type1 and OpenType
2198    -- fonts but I never really checked that because with these things I just start from
2199    -- the specifications. All that said, one reason why fonts that have issues often
2200    -- come out well, is because the renderer handles these even-odd cases well. When we
2201    -- want outlines (that feed into for instance MetaPost) we need to avoid artifacts
2202    -- due to border cases where subpaths get connected by for lines. I tested with a
2203    -- few thousand OpenType and Type1 fonts and with a few exceptions (bad font files)
2204    -- it all works okay now. If needed we can now also flatten the OpenType fonts which
2205    -- sometimes gives smaller files but initial caching takes a bit more time.
2206
2207    local process
2208
2209    local function call(scope,list,bias) -- ,process)
2210        if top == 0 then
2211            showstate(formatters["unknown %s call %s, bias %i, case %s"](scope,"?",bias-1,1))
2212            top = 0
2213        else
2214            local index = bias + stack[top]
2215            top = top - 1
2216            if trace_charstrings then
2217                showvalue(scope,index,true)
2218            end
2219            depth = depth + 1
2220            -- 0 1 2 : work with flex
2221            -- 3     : work with hint
2222            if version == "cff" then
2223                if index == 0 then
2224                    flushflexhints()
2225                elseif index == 1 then
2226                    collectflexhints()
2227             -- elseif index == 2 then
2228                -- no-op
2229             -- elseif index == 3  then
2230                -- no-op
2231                end
2232            end
2233            local tab = list[index]
2234            if tab then
2235                process(tab)
2236            else
2237                showstate(formatters["unknown %s call %s, bias %i, case %s"](scope,index,bias-1,2))
2238                top = 0
2239            end
2240            depth = depth - 1
2241        end
2242    end
2243
2244    -- precompiling and reuse is much slower than redoing the calls
2245    -- todo: split into two
2246
2247    do
2248
2249        local op_hstem      <const> =  1
2250     -- local op_reserved_1 <const> =  2
2251        local op_vstem      <const> =  3
2252     -- local op_vmoveto    <const> =  4
2253     -- local op_rlineto    <const> =  5
2254     -- local op_hlineto    <const> =  6
2255     -- local op_vlineto    <const> =  7
2256        local op_rrcurveto  <const> =  8
2257        local op_closepath  <const> =  9
2258        local op_callsubr   <const> = 10
2259        local op_return     <const> = 11
2260        local op_escape     <const> = 12
2261        local op_hsbw       <const> = 13
2262        local op_endchar    <const> = 14
2263        local op_setvsindex <const> = 15
2264        local op_blend      <const> = 16
2265     -- local op_reserved_2 <const> = 17
2266        local op_hstemhm    <const> = 18
2267        local op_hintmask   <const> = 19
2268        local op_cntrmask   <const> = 20
2269     -- local op_rmoveto    <const> = 21
2270     -- local op_hmoveto    <const> = 22
2271        local op_vstemhm    <const> = 23
2272     -- local op_rcurveline <const> = 24
2273     -- local op_rlinecurve <const> = 25
2274     -- local op_vvcurveto  <const> = 26
2275     -- local op_hhcurveto  <const> = 27
2276        local op_integer    <const> = 28
2277        local op_callgsubr  <const> = 29
2278     -- local op_vhcurveto  <const> = 30
2279     -- local op_hvcurveto  <const> = 31
2280
2281        process = function(tab)
2282            local i = 1
2283            local n = #tab
2284            while i <= n do
2285                local t = tab[i]
2286                if t >= 32 then
2287                    top = top + 1
2288                    if t <= 246 then
2289                        -- -107 .. +107
2290                        stack[top] = t - 139
2291                        i = i + 1
2292                    elseif t <= 250 then
2293                        -- +108 .. +1131
2294                     -- stack[top] = (t-247)*256 + tab[i+1] + 108
2295                     -- stack[top] = t*256 - 247*256 + tab[i+1] + 108
2296                        stack[top] = t*256 - 63124 + tab[i+1]
2297                        i = i + 2
2298                    elseif t <= 254 then
2299                        -- -1131 .. -108
2300                     -- stack[top] = -(t-251)*256 - tab[i+1] - 108
2301                     -- stack[top] = -t*256 + 251*256 - tab[i+1] - 108
2302                        stack[top] = -t*256 + 64148 - tab[i+1]
2303                        i = i + 2
2304                    elseif typeone then
2305                        local n = 0x1000000 * tab[i+1] + 0x10000 * tab[i+2] + 0x100 * tab[i+3] + tab[i+4]
2306                        if n >= 0x8000000 then
2307                            n = n - 0xFFFFFFFF - 1
2308                        end
2309                        stack[top] = n
2310                        i = i + 5
2311                    else
2312                        local n1 = 0x100 * tab[i+1] + tab[i+2]
2313                        local n2 = 0x100 * tab[i+3] + tab[i+4]
2314                        if n1 >= 0x8000 then
2315                            n1 = n1 - 0x10000
2316                        end
2317                        stack[top] = n1 + n2/0xFFFF
2318                        i = i + 5
2319                    end
2320                elseif t == op_integer then
2321                    -- -32768 .. +32767 : b1<<8 | b2
2322                    top = top + 1
2323                    local n = 0x100 * tab[i+1] + tab[i+2]
2324                    if n  >= 0x8000 then
2325                     -- stack[top] = n - 0xFFFF - 1
2326                        stack[top] = n - 0x10000
2327                    else
2328                        stack[top] = n
2329                    end
2330                    i = i + 3
2331                elseif t == op_return then -- not in cff2
2332                    if trace_charstrings then
2333                        showstate("return")
2334                    end
2335                    return
2336                elseif t == op_callsubr then
2337                    call("local",locals,localbias)
2338                    i = i + 1
2339                elseif t == op_endchar then -- not in cff2
2340                    if width then
2341                        -- okay
2342                    elseif top > 0 then
2343                        width = stack[1]
2344                        if r == 0 and justpass then
2345                            -- we  need to set the width of space (lm) and other shapeless things
2346                            r = r + 1 ; result[r] = encode[width]
2347                            r = r + 1 ; result[r] = c_endchar
2348                        end
2349                        if trace_charstrings then
2350                            showvalue("width",width)
2351                        end
2352                    else
2353                        width = true
2354                    end
2355                    if trace_charstrings then
2356                        showstate("endchar")
2357                    end
2358                    return
2359                elseif t == op_callgsubr then
2360                    call("global",globals,globalbias)
2361                    i = i + 1
2362                elseif t == op_escape then
2363                    i = i + 1
2364                    local t = tab[i]
2365                    if justpass then
2366                        if t >= 34 and t <= 37 then
2367                            -- cff2: hflex, flex, hflex1, flex1
2368                            for i=1,top do
2369                                r = r + 1 ; result[r] = encode[stack[i]]
2370                            end
2371                            r = r + 1 ; result[r] = c_command
2372                            r = r + 1 ; result[r] = chars[t]
2373                            top = 0
2374                        elseif t == 6 then
2375                            -- cff1: seac
2376                            seacs[procidx] = {
2377                                asb    = stack[1],
2378                                adx    = stack[2],
2379                                ady    = stack[3],
2380                                base   = stack[4],
2381                                accent = stack[5],
2382                                width  = width,
2383                                lsb    = lsb,
2384                            }
2385                            top = 0
2386                        else
2387                            -- cff1: dotsection, vstem3, hstem3, [seac,] sbw, divide, callothersubr,
2388                            --       pop, setcurrentpoint
2389                            --
2390                            -- If we end up here something is wrong because cff0 now goes via shapes
2391                            -- and opentype cff1 doesn't have this.
2392                            local a = subactions[t]
2393                            if a then
2394                                a(t)
2395                            else
2396                                top = 0
2397                            end
2398                        end
2399                    else
2400                        local a = subactions[t]
2401                        if a then
2402                            a(t)
2403                        else
2404                            if trace_charstrings then
2405                                showvalue("<subaction>",t)
2406                            end
2407                            top = 0
2408                        end
2409                    end
2410                    i = i + 1
2411                elseif justpass then
2412                    -- todo: local a = passactions
2413                    if t == op_setvsindex then
2414                        p_setvsindex()
2415                        i = i + 1
2416                    elseif t == op_blend then
2417                        local s = p_blend() or 0
2418                        i = i + s + 1
2419                    -- cff 1: (when cff2 strip them)
2420                    elseif t == op_hstem or t == op_vstem or t == op_hstemhm or operation == op_vstemhm then
2421                        p_getstem() -- at the start
2422                        if version == "cff" then
2423                         -- if true then
2424                            if top > 0 then
2425                                for i=1,top do
2426                                    r = r + 1 ; result[r] = encode[stack[i]]
2427                                end
2428                                top = 0
2429                            end
2430                            r = r + 1 ; result[r] = chars[t]
2431                        else
2432                            top = 0
2433                        end
2434                        i = i + 1
2435                    -- cff 1: (when cff2 strip them)
2436                    elseif t == op_hintmask or t == op_cntrmask then
2437                        local s = p_getmask() or 0 -- after the stems
2438                     -- if version == "cff" then
2439                        if true then
2440                            if top > 0 then
2441                                for i=1,top do
2442                                    r = r + 1 ; result[r] = encode[stack[i]]
2443                                end
2444                                top = 0
2445                            end
2446                            r = r + 1 ; result[r] = chars[t]
2447                            for j=1,s do
2448                                i = i + 1
2449                                r = r + 1 ; result[r] = chars[tab[i]]
2450                            end
2451                        else
2452                            i = i + s
2453                            top = 0
2454                        end
2455                        i = i + 1
2456                    elseif t == op_closepath then
2457                        if version == "cff" then
2458                            r = r + 1 ; result[r] = c_closepath
2459                        end
2460                        top = 0
2461                        i = i + 1
2462                    elseif t == op_hsbw then
2463                        hsbw()
2464                    --  if version == "cff" then
2465                        if true then
2466                            -- we do a moveto over lsb
2467                            r = r + 1 ; result[r] = encode[lsb]
2468                            r = r + 1 ; result[r] = chars[22] -- c_hmoveto
2469                        else
2470                            -- lsb is supposed to be zero
2471                        end
2472                        i = i + 1
2473                    else
2474                        if trace_charstrings then
2475                            showstate(reverse[t] or "<action>")
2476                        end
2477                        if top > 0 then
2478                         -- if t == op_rrcurveto and top > 42 then
2479                            if t == op_rrcurveto and top > 48 then
2480                                -- let's assume this only happens for rrcurveto .. the other ones would need some more
2481                                -- complex handling (cff2 stuff)
2482                                --
2483                                -- dx1 dy1 (dx1+dx2) (dy1+dy2) (dx1+dx2+dx3) (dy1+dy2+dy3) rcurveto.
2484                                local n = 0
2485                                for i=1,top do
2486                                 -- if n == 42 then
2487                                    if n == 48 then
2488                                        r = r + 1 ; result[r] = chars[t]
2489                                        n = 1
2490                                    else
2491                                        n = n + 1
2492                                    end
2493                                    r = r + 1 ; result[r] = encode[stack[i]]
2494                                end
2495                            else
2496                                for i=1,top do
2497                                    r = r + 1 ; result[r] = encode[stack[i]]
2498                                end
2499                            end
2500                            top = 0
2501                        end
2502                        r = r + 1 ; result[r] = chars[t]
2503                        i = i + 1
2504                    end
2505                else
2506                    local a = actions[t]
2507                    if a then
2508                        local s = a(t)
2509                        if s then
2510                            i = i + s + 1
2511                        else
2512                            i = i + 1
2513                        end
2514                    else
2515                        if trace_charstrings then
2516                            showstate(reverse[t] or "<action>")
2517                        end
2518                        top = 0
2519                        i = i + 1
2520                    end
2521                end
2522            end
2523        end
2524
2525    end
2526
2527 -- local function calculatebounds(segments,x,y)
2528 --     local nofsegments = #segments
2529 --     if nofsegments == 0 then
2530 --         return { x, y, x, y }
2531 --     else
2532 --         local xmin =  10000
2533 --         local xmax = -10000
2534 --         local ymin =  10000
2535 --         local ymax = -10000
2536 --         if x < xmin then xmin = x end
2537 --         if x > xmax then xmax = x end
2538 --         if y < ymin then ymin = y end
2539 --         if y > ymax then ymax = y end
2540 --         -- we now have a reasonable start so we could
2541 --         -- simplify the next checks
2542 --         for i=1,nofsegments do
2543 --             local s = segments[i]
2544 --             local x = s[1]
2545 --             local y = s[2]
2546 --             if x < xmin then xmin = x end
2547 --             if x > xmax then xmax = x end
2548 --             if y < ymin then ymin = y end
2549 --             if y > ymax then ymax = y end
2550 --             if s[#s] == "c" then -- "curveto"
2551 --                 local x = s[3]
2552 --                 local y = s[4]
2553 --                 if x < xmin then xmin = x elseif x > xmax then xmax = x end
2554 --                 if y < ymin then ymin = y elseif y > ymax then ymax = y end
2555 --                 local x = s[5]
2556 --                 local y = s[6]
2557 --                 if x < xmin then xmin = x elseif x > xmax then xmax = x end
2558 --                 if y < ymin then ymin = y elseif y > ymax then ymax = y end
2559 --             end
2560 --         end
2561 --         return { round(xmin), round(ymin), round(xmax), round(ymax) } -- doesn't make ceil more sense
2562 --     end
2563 -- end
2564
2565    -- begin of bonus
2566
2567    readers.potracetocff = potrace and function(glyph,settings)
2568        local xsize   = glyph.xsize or 0
2569        local ysize   = glyph.ysize or 0
2570        local xoffset = glyph.xoffset or 0
2571        local yoffset = glyph.yoffset or 0
2572        local llx     = - xoffset
2573        local lly     = yoffset - ysize + 1
2574        local urx     = llx + xsize + 1
2575        local ury     = lly + ysize
2576        --
2577        local b, w, h = fonts.handlers.tfm.readers.showpk(glyph,true)
2578        if not b then
2579            return
2580        end
2581        local t = potrace.convert(b,settings,w,h)
2582        --
2583        llx     = round(10*llx)
2584        lly     = round(10*lly)
2585        urx     = round(10*urx)
2586        ury     = round(10*ury)
2587        xoffset =  llx
2588        yoffset = -lly
2589        --
2590        local width = round(10*glyph.width/65536)
2591        --
2592        for i=1,#t do
2593            local ti = t[i]
2594            for j=1,#ti do
2595                local tj = ti[j]
2596                for k=1,#tj do
2597                    tj[k] = round(10*tj[k])
2598                end
2599            end
2600        end
2601        return llx, lly, urx, ury, pack_result_untagged(t,width,xoffset,yoffset)
2602    end or function()
2603        return 0, 0, 0, 0, c_endchar
2604    end
2605
2606    -- end of bonus
2607
2608    -- (n < 1240 and 107) or (n < 33900 and 1131) or 32768) + 1
2609
2610    local function getbias(t)
2611        local n = #t
2612        if     n <  1240 then return   108 --   107 + 1
2613        elseif n < 33900 then return  1132 --  1131 + 1
2614        else                  return 32769 -- 32768 + 1
2615        end
2616    end
2617
2618    -- We have three cases, where (cff == cff1):
2619    --
2620    -- cff0 : this is cff1 but with the need to process the flex hints
2621    -- cff1 : this is the opentype version that has a bit less than the type one
2622    -- cff2 : this is the latest version with some operators dropped and a few added
2623    --
2624    -- Because we need to process flexes we now aleays go through shapes for cff0
2625    -- which is more reliable.
2626
2627    local force_flatten = false
2628
2629    directives.register("fonts.cff.flatten", function(v) force_flatten = v end )
2630
2631    local function processshape(glyphs,tab,index,hack)
2632
2633        local glyphname = charset and charset[index] or nil
2634
2635        if not tab then
2636            glyphs[index] = {
2637                boundingbox = { 0, 0, 0, 0 },
2638                width       = 0,
2639                name        = glyphname,
2640            }
2641            return
2642        end
2643
2644        tab     = bytetable(tab)
2645
2646        x       = 0
2647        y       = 0
2648        width   = false
2649        lsb     = 0
2650        r       = 0
2651        top     = 0
2652        stems   = 0
2653        result  = { } -- we could reuse it when only boundingbox calculations are needed
2654        popped  = 3
2655        procidx = index
2656
2657        nofflexhints = 0
2658        ignorehint   = false
2659
2660        xmin    = 0
2661        xmax    = 0
2662        ymin    = 0
2663        ymax    = 0
2664        checked = false
2665        if trace_charstrings then
2666            report("glyph: %i %s",index,glyphname or "")
2667            report("data : % t",tab)
2668        end
2669
2670        if regions then
2671            updateregions(vsindex)
2672        end
2673
2674        local jp = justpass
2675
2676        if justpass and version == "cff0" or typeone then
2677            version  = "cff"
2678            justpass = false
2679        end
2680
2681        if force_flatten then
2682            justpass = false
2683        end
2684
2685        process(tab)
2686        if hack then
2687            return x, y
2688        end
2689
2690        local boundingbox = {
2691            round(xmin),
2692            round(ymin),
2693            round(xmax),
2694            round(ymax),
2695        }
2696
2697        if width == true or width == false then
2698            width = defaultwidth
2699        else
2700            width = nominalwidth + width
2701        end
2702
2703        local glyph = glyphs[index] -- can be autodefined in otr
2704
2705        if jp then
2706            local stream
2707            if justpass then
2708                r = r + 1
2709                result[r] = c_endchar
2710                stream = concat(result)
2711                result = nil
2712            else
2713                stream = pack_result_tagged(result,width,lsb) -- version == "cff" and -lsb or 0
2714                justpass = jp
2715            end
2716         -- if trace_charstrings then
2717         --     report("vdata: %s",stream)
2718         -- end
2719            if glyph then
2720                glyph.stream = stream
2721                glyph.width  = width
2722            else
2723                glyphs[index] = { stream = stream, width = width }
2724            end
2725        elseif glyph then
2726            glyph.segments    = keepcurve ~= false and result or nil
2727            glyph.boundingbox = boundingbox
2728            if not glyph.width then
2729             -- better not because later we will default ro maybe only when we
2730             -- have segments
2731                glyph.width = round(width)
2732            end
2733            if not glyph.name then
2734                glyph.name = glyphname
2735            end
2736         -- glyph.sidebearing = 0 -- todo
2737        elseif keepcurve then
2738            glyphs[index] = {
2739                segments    = result,
2740                boundingbox = boundingbox,
2741                width       = width,
2742                name        = glyphname,
2743             -- sidebearing = 0,
2744            }
2745            result = nil
2746        else
2747            glyphs[index] = {
2748                boundingbox = boundingbox,
2749                width       = width,
2750                name        = glyphname,
2751            }
2752        end
2753        if trace_charstrings then
2754            report("width      : %s",tostring(width))
2755            report("boundingbox: % t",boundingbox)
2756        end
2757
2758    end
2759
2760    startparsing = function(fontdata,data,streams)
2761        reginit  = false
2762        axis     = false
2763        regions  = data.regions
2764        justpass = streams == true
2765        popped   = 3
2766        seacs    = { }
2767        if regions then
2768            -- this was:
2769         -- regions = { regions } -- needs checking
2770            -- and is now (MFC):
2771            regions = { }
2772            local deltas = data.deltas
2773            for i = 1, #deltas do
2774                regions[i] = deltas[i].regions
2775            end
2776            axis = data.factors or false
2777        end
2778    end
2779
2780    stopparsing = function(fontdata,data)
2781        stack        = { }
2782        glyphs       = false
2783        result       = { }
2784        top          = 0
2785        locals       = false
2786        globals      = false
2787        strings      = false
2788        popped       = 3
2789        seacs        = { }
2790        nofflexhints = 0
2791        ignorehint   = false
2792    end
2793
2794    local function setwidths(private)
2795        if not private then
2796            return 0, 0
2797        end
2798        local privatedata  = private.data
2799        if not privatedata then
2800            return 0, 0
2801        end
2802        return privatedata.nominalwidthx or 0, privatedata.defaultwidthx or 0
2803    end
2804
2805    parsecharstrings = function(fontdata,data,glphs,doshapes,tversion,streams,nobias,istypeone)
2806
2807        local dictionary  = data.dictionaries[1]
2808        local charstrings = dictionary.charstrings
2809
2810        keepcurve = doshapes
2811        version   = tversion
2812        typeone   = istypeone or false
2813        strings   = data.strings
2814        globals   = data.routines or { }
2815        locals    = dictionary.subroutines or { }
2816        charset   = dictionary.charset
2817        vsindex   = dictionary.vsindex or 0
2818
2819        local glyphs = glphs or { }
2820
2821        if nobias then
2822            globalbias = 0
2823            localbias  = 0
2824        else
2825            globalbias = getbias(globals)
2826            localbias  = getbias(locals)
2827        end
2828
2829        nominalwidth, defaultwidth = setwidths(dictionary.private)
2830
2831        if charstrings then
2832            startparsing(fontdata,data,streams)
2833            for index=1,#charstrings do
2834                processshape(glyphs,charstrings[index],index-1)
2835            end
2836            if justpass and next(seacs) then
2837                -- old type 1 stuff ... seacs
2838                local charset = data.dictionaries[1].charset
2839                if charset then
2840                    local lookup = table.swapped(charset)
2841                    for index, v in next, seacs do
2842                        local bindex = lookup[standardnames[v.base]]
2843                        local aindex = lookup[standardnames[v.accent]]
2844                        local bglyph = bindex and glyphs[bindex]
2845                        local aglyph = aindex and glyphs[aindex]
2846                        if bglyph and aglyph then
2847                            -- this is a real ugly hack but we seldom enter this branch (e.g. old lbr)
2848                            local jp = justpass
2849                            justpass = false
2850                            local x, y = processshape(glyphs,charstrings[bindex+1],bindex,true)
2851                            justpass = jp
2852                            --
2853                            local base   = bglyph.stream
2854                            local accent = aglyph.stream
2855                            local moveto = encode[-x-v.asb+v.adx] .. chars[22] -- c_hmoveto
2856                                        .. encode[-y      +v.ady] .. chars[04] -- c_vmoveto
2857                            -- prune an endchar
2858                            base = sub(base,1,#base-1)
2859                            -- combine them
2860                            glyphs[index].stream = base .. moveto .. accent
2861                        end
2862                    end
2863                end
2864            end
2865            stopparsing(fontdata,data)
2866        else
2867            report("no charstrings")
2868        end
2869        return glyphs
2870    end
2871
2872    parsecharstring = function(fontdata,data,dictionary,tab,glphs,index,doshapes,tversion,streams)
2873
2874        keepcurve = doshapes
2875        version   = tversion
2876        strings   = data.strings
2877        globals   = data.routines or { }
2878        locals    = dictionary.subroutines or { }
2879        typeone   = false -- actually unknown but we can use the version cff0
2880        charset   = false
2881        vsindex   = dictionary.vsindex or 0
2882
2883        local glyphs = glphs or { }
2884
2885        justpass = streams == true
2886        seacs    = { }
2887
2888        if nobias then
2889            globalbias = 0
2890            localbias  = 0
2891        else
2892            globalbias = getbias(globals)
2893            localbias  = getbias(locals)
2894        end
2895
2896        nominalwidth, defaultwidth = setwidths(dictionary.private)
2897
2898        processshape(glyphs,tab,index-1)
2899
2900        return glyphs[index]
2901    end
2902
2903end
2904
2905do
2906
2907    local function readglobals(f,data,version)
2908        local routines = readlengths(f,version == "cff2")
2909        for i=1,#routines do
2910            routines[i] = readbytetable(f,routines[i])
2911        end
2912        data.routines = routines
2913    end
2914
2915    local function readencodings(f,data)
2916        data.encodings = { }
2917    end
2918
2919    local function readcharsets(f,data,dictionary)
2920        local header        = data.header
2921        local strings       = data.strings
2922        local nofglyphs     = data.nofglyphs
2923        local charsetoffset = dictionary.charset
2924        if charsetoffset and charsetoffset ~= 0 then
2925            setposition(f,header.offset+charsetoffset)
2926            local format       = readbyte(f)
2927            local charset      = { [0] = ".notdef" }
2928            dictionary.charset = charset
2929            if format == 0 then
2930                for i=1,nofglyphs do
2931                    charset[i] = strings[readushort(f)]
2932                end
2933            elseif format == 1 or format == 2 then
2934                local readcount = format == 1 and readbyte or readushort
2935                local i = 1
2936                while i <= nofglyphs do
2937                    local sid = readushort(f)
2938                    local n   = readcount(f)
2939                    for s=sid,sid+n do
2940                        charset[i] = strings[s]
2941                        i = i + 1
2942                        if i > nofglyphs then
2943                            break
2944                        end
2945                    end
2946                end
2947            else
2948                report("cff parser: unsupported charset format %a",format)
2949            end
2950        else
2951            dictionary.nocharset = true
2952            dictionary.charset   = nil
2953        end
2954    end
2955
2956    local function readprivates(f,data)
2957        local header       = data.header
2958        local dictionaries = data.dictionaries
2959        local private      = dictionaries[1].private
2960        if private then
2961            setposition(f,header.offset+private.offset)
2962            private.data = readstring(f,private.size)
2963        end
2964    end
2965
2966    local function readlocals(f,data,dictionary,cff2)
2967        local header  = data.header
2968        local private = dictionary.private
2969        if private then
2970            local subroutineoffset = private.data.subroutines
2971            if subroutineoffset ~= 0 then
2972                setposition(f,header.offset+private.offset+subroutineoffset)
2973                local subroutines = readlengths(f,version == "cff2")
2974                for i=1,#subroutines do
2975                    subroutines[i] = readbytetable(f,subroutines[i])
2976                end
2977                dictionary.subroutines = subroutines
2978                private.data.subroutines = nil
2979            else
2980                dictionary.subroutines = { }
2981            end
2982        else
2983            dictionary.subroutines = { }
2984        end
2985    end
2986
2987    -- These charstrings are little programs and described in: Technical Note #5177. A truetype
2988    -- font has only one dictionary.
2989
2990    local function readcharstrings(f,data,version)
2991        local header       = data.header
2992        local dictionaries = data.dictionaries
2993        local dictionary   = dictionaries[1]
2994        local stringtype   = dictionary.charstringtype
2995        local offset       = dictionary.charstrings
2996        if type(offset) ~= "number" then
2997            -- weird
2998        elseif stringtype == 2 then
2999            setposition(f,header.offset+offset)
3000            -- could be a metatable .. delayed loading
3001            local charstrings = readlengths(f,version=="cff2")
3002            local nofglyphs   = #charstrings
3003            for i=1,nofglyphs do
3004                charstrings[i] = readstring(f,charstrings[i])
3005            end
3006            data.nofglyphs         = nofglyphs
3007            dictionary.charstrings = charstrings
3008        else
3009            report("unsupported charstr type %i",stringtype)
3010            data.nofglyphs         = 0
3011            dictionary.charstrings = { }
3012        end
3013    end
3014
3015    -- cid (maybe do this stepwise so less mem) -- share with above
3016
3017    local function readcidprivates(f,data)
3018        local header       = data.header
3019        local dictionaries = data.dictionaries[1].cid.dictionaries
3020        for i=1,#dictionaries do
3021            local dictionary = dictionaries[i]
3022            local private    = dictionary.private
3023            if private then
3024                setposition(f,header.offset+private.offset)
3025                private.data = readstring(f,private.size)
3026            end
3027        end
3028        parseprivates(data,dictionaries)
3029    end
3030
3031    readers.parsecharstrings = parsecharstrings -- used in font-onr.lua (type 1)
3032
3033    local function readnoselect(f,fontdata,data,glyphs,doshapes,version,streams)
3034        local dictionaries = data.dictionaries
3035        local dictionary   = dictionaries[1]
3036        local cid          = not dictionary.private and dictionary.cid
3037        readglobals(f,data,version)
3038        readcharstrings(f,data,version)
3039        if version == "cff2" then
3040            dictionary.charset = nil
3041        else
3042            readencodings(f,data)
3043            readcharsets(f,data,dictionary)
3044        end
3045        if cid then
3046            local fdarray = cid.fdarray
3047            if fdarray then
3048                setposition(f,data.header.offset + fdarray)
3049                local dictionaries    = readlengths(f,version=="cff2")
3050                local nofdictionaries = #dictionaries
3051                if nofdictionaries > 0 then
3052                    for i=1,nofdictionaries do
3053                        dictionaries[i] = readstring(f,dictionaries[i])
3054                    end
3055                    parsedictionaries(data,dictionaries)
3056                    dictionary.private = dictionaries[1].private
3057                    if nofdictionaries > 1 then
3058                        report("ignoring dictionaries > 1 in cid font")
3059                    end
3060                end
3061            end
3062        end
3063        readprivates(f,data)
3064        parseprivates(data,data.dictionaries)
3065        readlocals(f,data,dictionary,version)
3066        startparsing(fontdata,data,streams)
3067        parsecharstrings(fontdata,data,glyphs,doshapes,version,streams,false)
3068        stopparsing(fontdata,data)
3069    end
3070
3071    local function readfdselect(f,fontdata,data,glyphs,doshapes,version,streams)
3072        local header       = data.header
3073        local dictionaries = data.dictionaries
3074        local dictionary   = dictionaries[1]
3075        local cid          = dictionary.cid
3076        local cidselect    = cid and cid.fdselect
3077        readglobals(f,data,version)
3078        readcharstrings(f,data,version)
3079        if version ~= "cff2" then
3080            readencodings(f,data)
3081        end
3082        local charstrings  = dictionary.charstrings
3083        local fdindex      = { }
3084        local nofglyphs    = data.nofglyphs
3085        local maxindex     = -1
3086        setposition(f,header.offset+cidselect)
3087        local format       = readbyte(f)
3088        if format == 1 then
3089            for i=0,nofglyphs do -- notdef included (needs checking)
3090                local index = readbyte(f)
3091                fdindex[i] = index
3092                if index > maxindex then
3093                    maxindex = index
3094                end
3095            end
3096        elseif format == 3 then
3097            local nofranges = readushort(f)
3098            local first     = readushort(f)
3099            local index     = readbyte(f)
3100            while true do
3101                local last = readushort(f)
3102                if index > maxindex then
3103                    maxindex = index
3104                end
3105                for i=first,last do
3106                    fdindex[i] = index
3107                end
3108                if last >= nofglyphs then
3109                    break
3110                else
3111                    first = last + 1
3112                    index = readbyte(f)
3113                end
3114            end
3115        else
3116            report("unsupported fd index format %i",format)
3117        end
3118        -- hm, always
3119        if maxindex >= 0 then
3120            local cidarray = cid.fdarray
3121            if cidarray then
3122                setposition(f,header.offset+cidarray)
3123                local dictionaries = readlengths(f,version == "cff2")
3124                if #dictionaries > 0 then
3125                    for i=1,#dictionaries do
3126                        dictionaries[i] = readstring(f,dictionaries[i])
3127                    end
3128                    parsedictionaries(data,dictionaries)
3129                    cid.dictionaries = dictionaries
3130                    readcidprivates(f,data)
3131                    for i=1,#dictionaries do
3132                        readlocals(f,data,dictionaries[i],version)
3133                    end
3134                    startparsing(fontdata,data,streams)
3135                    for i=1,#charstrings do
3136                        local dictionary = dictionaries[fdindex[i]+1]
3137                        if dictionary then
3138                            parsecharstring(fontdata,data,dictionary,charstrings[i],glyphs,i,doshapes,version,streams)
3139                        else
3140                         -- report("no dictionary for %a : %a => %a",version,i,fdindex[i]+1)
3141                        end
3142                     -- charstrings[i] = false
3143                    end
3144                    stopparsing(fontdata,data)
3145                else
3146                    report("no cid dictionaries")
3147                end
3148            else
3149                report("no cid array")
3150            end
3151        end
3152    end
3153
3154    local gotodatatable = readers.helpers.gotodatatable
3155
3156    local function cleanup(data,dictionaries)
3157     -- for i=1,#dictionaries do
3158     --     local d = dictionaries[i]
3159     --     d.subroutines = nil
3160     -- end
3161     -- data.strings = nil
3162     -- if data then
3163     --     data.charstrings  = nil
3164     --     data.routines     = nil
3165     -- end
3166    end
3167
3168    function readers.cff(f,fontdata,specification)
3169        local tableoffset = gotodatatable(f,fontdata,"cff",specification.details or specification.glyphs)
3170        if tableoffset then
3171            local header = readheader(f)
3172            if header.major ~= 1 then
3173                report("only version %s is supported for table %a",1,"cff")
3174                return
3175            end
3176            local glyphs       = fontdata.glyphs
3177            local names        = readfontnames(f)
3178            local dictionaries = readtopdictionaries(f)
3179            local strings      = readstrings(f)
3180            local data = {
3181                header       = header,
3182                names        = names,
3183                dictionaries = dictionaries,
3184                strings      = strings,
3185                nofglyphs    = fontdata.nofglyphs,
3186            }
3187            --
3188            parsedictionaries(data,dictionaries,"cff")
3189            --
3190            local dic = dictionaries[1]
3191            local cid = dic.cid
3192            --
3193            local cffinfo = {
3194                familyname         = dic.familyname,
3195                fullname           = dic.fullname,
3196                boundingbox        = dic.boundingbox,
3197                weight             = dic.weight,
3198                italicangle        = dic.italicangle,
3199                underlineposition  = dic.underlineposition,
3200                underlinethickness = dic.underlinethickness,
3201                defaultwidth       = dic.defaultwidthx,
3202                nominalwidth       = dic.nominalwidthx,
3203                monospaced         = dic.monospaced,
3204            }
3205            fontdata.cidinfo = cid and {
3206                registry   = cid.registry,
3207                ordering   = cid.ordering,
3208                supplement = cid.supplement,
3209            }
3210            fontdata.cffinfo = cffinfo
3211            --
3212            local all = specification.shapes or specification.streams or false
3213            if specification.glyphs or all then
3214                if cid and cid.fdselect then
3215                    readfdselect(f,fontdata,data,glyphs,all,"cff",specification.streams)
3216                else
3217                    readnoselect(f,fontdata,data,glyphs,all,"cff",specification.streams)
3218                end
3219            end
3220            local private = dic.private
3221            if private then
3222                local data = private.data
3223                if type(data) == "table" then
3224                    cffinfo.defaultwidth     = data.defaultwidthx or cffinfo.defaultwidth
3225                    cffinfo.nominalwidth     = data.nominalwidthx or cffinfo.nominalwidth
3226                    cffinfo.bluevalues       = data.bluevalues
3227                    cffinfo.otherblues       = data.otherblues
3228                    cffinfo.familyblues      = data.familyblues
3229                    cffinfo.familyotherblues = data.familyotherblues
3230                    cffinfo.bluescale        = data.bluescale
3231                    cffinfo.blueshift        = data.blueshift
3232                    cffinfo.bluefuzz         = data.bluefuzz
3233                    cffinfo.stdhw            = data.stdhw
3234                    cffinfo.stdvw            = data.stdvw
3235                    cffinfo.stemsnaph        = data.stemsnaph
3236                    cffinfo.stemsnapv        = data.stemsnapv
3237                end
3238            end
3239            cleanup(data,dictionaries)
3240        end
3241    end
3242
3243    function readers.cff2(f,fontdata,specification)
3244        local tableoffset = gotodatatable(f,fontdata,"cff2",specification.glyphs)
3245        if tableoffset then
3246            local header = readheader(f)
3247            if header.major ~= 2 then
3248                report("only version %s is supported for table %a",2,"cff2")
3249                return
3250            end
3251            local glyphs       = fontdata.glyphs
3252            local dictionaries = { readstring(f,header.dsize) }
3253            local data = {
3254                header       = header,
3255                dictionaries = dictionaries,
3256                nofglyphs    = fontdata.nofglyphs,
3257            }
3258            --
3259            parsedictionaries(data,dictionaries,"cff2")
3260            --
3261            local offset = dictionaries[1].vstore
3262            if offset > 0 then
3263                local storeoffset = dictionaries[1].vstore + data.header.offset + 2 -- cff has a preceding size field
3264                local regions, deltas = readers.helpers.readvariationdata(f,storeoffset,factors)
3265                --
3266                data.regions  = regions
3267                data.deltas   = deltas
3268            else
3269                data.regions  = { }
3270                data.deltas   = { }
3271            end
3272            data.factors  = specification.factors
3273            --
3274            local cid = data.dictionaries[1].cid
3275            local all = specification.shapes or specification.streams or false
3276            if cid and cid.fdselect then
3277                readfdselect(f,fontdata,data,glyphs,all,"cff2",specification.streams)
3278            else
3279                readnoselect(f,fontdata,data,glyphs,all,"cff2",specification.streams)
3280            end
3281            cleanup(data,dictionaries)
3282        end
3283    end
3284
3285    -- temporary helper needed for checking backend patches
3286
3287    -- function readers.cffcheck(filename)
3288    --     local f = io.open(filename,"rb")
3289    --     if f then
3290    --         local fontdata = {
3291    --             glyphs = { },
3292    --         }
3293    --         local header = readheader(f)
3294    --         if header.major ~= 1 then
3295    --             report("only version %s is supported for table %a",1,"cff")
3296    --             return
3297    --         end
3298    --         local names        = readfontnames(f)
3299    --         local dictionaries = readtopdictionaries(f)
3300    --         local strings      = readstrings(f)
3301    --         local glyphs       = { }
3302    --         local data = {
3303    --             header       = header,
3304    --             names        = names,
3305    --             dictionaries = dictionaries,
3306    --             strings      = strings,
3307    --             glyphs       = glyphs,
3308    --             nofglyphs    = 0,
3309    --         }
3310    --         --
3311    --         parsedictionaries(data,dictionaries,"cff")
3312    --         --
3313    --         local cid = data.dictionaries[1].cid
3314    --         if cid and cid.fdselect then
3315    --             readfdselect(f,fontdata,data,glyphs,false)
3316    --         else
3317    --             readnoselect(f,fontdata,data,glyphs,false)
3318    --         end
3319    --         return data
3320    --     end
3321    -- end
3322
3323end
3324