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