lpdf-emb.lmt /size: 90 Kb    last modification: 2021-10-28 13:51
1if not modules then modules = { } end modules ['lpdf-ini'] = {
2    version   = 1.001,
3    optimize  = true,
4    comment   = "companion to lpdf-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-- vkgoeswild: Pink Floyd - Shine on You Crazy Diamond - piano cover (around that
11-- time I redid the code, a reminder so to say)
12
13-- At some point I wanted to have access to the shapes so that we could use them in
14-- metapost. So, after looking at the cff and ttf specifications, I decided to write
15-- parsers. At somepoint we needed a cff parser anyway in order to calculate the
16-- dimensions. Then variable fonts came around and a option was added to recreate
17-- streams of operators and a logical next step was to do all inclusion that way. It
18-- was only then that I found out that some of the juggling also happens in the the
19-- backend, but spread over places, so I could have saved myself some time
20-- deciphering the specifications. Anyway, here we go.
21--
22-- Color fonts are a bit messy. Catching issues with older fonts will break new ones
23-- so I don't think that it's wise to build in too many catches (like for fonts with
24-- zero boundingboxes, weird dimensions, transformations that in a next version are
25-- fixed, etc.). Better is then to wait till something gets fixed. If a spec doesn't
26-- tell me how to deal with it ... I'll happily wait till it does. After all we're
27-- not in a hurry as these fonts are mostly meant for the web or special purposes
28-- with manual tweaking in desk top publishing applications. Keep in mind that Emoji
29-- can have funny dimensions (e.g. to be consistent within a font, so no tight ones).
30
31-- When we have moved to lmtx I will document a bit more. Till then it's experimental
32-- and subjected to change.
33
34local next, type, unpack, rawget = next, type, unpack, rawget
35local char, byte, gsub, sub, match, rep, gmatch = string.char, string.byte, string.gsub, string.sub, string.match, string.rep, string.gmatch
36local formatters = string.formatters
37local format = string.format
38local concat, sortedhash, sort = table.concat, table.sortedhash, table.sort
39local utfchar = utf.char
40local random, round, max, abs, ceiling = math.random, math.round, math.max, math.abs, math.ceiling
41local setmetatableindex = table.setmetatableindex
42
43local pdfnull              = lpdf.null
44local pdfdictionary        = lpdf.dictionary
45local pdfarray             = lpdf.array
46local pdfconstant          = lpdf.constant
47local pdfstring            = lpdf.string
48local pdfreference         = lpdf.reference
49
50local pdfreserveobject     = lpdf.reserveobject
51local pdfflushobject       = lpdf.flushobject
52local pdfflushstreamobject = lpdf.flushstreamobject
53
54local report_fonts         = logs.reporter("backend","fonts")
55
56local trace_fonts          = false
57local trace_details        = false
58
59local dimenfactors  = number.dimenfactors
60local bpfactor      = dimenfactors.bp
61local ptfactor      = dimenfactors.pt
62
63trackers.register("backend.fonts",        function(v) trace_fonts   = v end)
64trackers.register("backend.fonts.details",function(v) trace_details = v end)
65
66local readers = fonts.handlers.otf.readers
67local getinfo = readers.getinfo
68
69local setposition = utilities.files.setposition
70local readstring  = utilities.files.readstring
71local openfile    = utilities.files.open
72local closefile   = utilities.files.close
73
74local getmapentry = fonts.mappings.getentry
75
76-- needs checking: signed vs unsigned
77
78-- todo: use streams.tocardinal4 etc
79
80local tocardinal1 = char
81
82local function tocardinal2(n)
83 -- return char(extract8(n,8),extract8(n,0))
84    return char((n>>8)&0xFF,(n>>0)&0xFF)
85end
86
87local function tocardinal3(n)
88 -- return char(extract8(n,16),extract8(n,8),extract8(n,0))
89    return char((n>>16)&0xFF,(n>>8)&0xFF,(n>>0)&0xFF)
90end
91
92local function tocardinal4(n)
93 -- return char(extract8(n,24),extract8(n,16),extract8(n,8),extract8(n,0))
94    return char((n>>24)&0xFF,(n>>16)&0xFF,(n>>8)&0xFF,(n>>0)&0xFF)
95end
96
97local function tointeger2(n)
98 -- return char(extract8(n,8),extract8(n,0))
99    return char((n>>8)&0xFF,(n>>0)&0xFF)
100end
101
102local function tointeger3(n)
103 -- return char(extract8(n,16),extract8(n,8),extract8(n,0))
104    return char((n>>16)&0xFF,(n>>8)&0xFF,(n>>0)&0xFF)
105end
106
107local function tointeger4(n)
108 -- return char(extract8(n,24),extract8(n,16),extract8(n,8),extract8(n,0))
109    return char((n>>24)&0xFF,(n>>16)&0xFF,(n>>8)&0xFF,(n>>0)&0xFF)
110end
111
112local function tocardinal8(n)
113    local l = n // 0x100000000
114    local r = n % 0x100000000
115 -- return char(extract8(l,24) & 0xFF,extract8(l,16) & 0xFF,extract8(l,8) & 0xFF,extract8(l,0) & 0xFF,
116 --             extract8(r,24) & 0xFF,extract8(r,16) & 0xFF,extract8(r,8) & 0xFF,extract8(r,0) & 0xFF)
117    return char((l>>24)&0xFF,(l>>16)&0xFF,(l>>8)&0xFF,(l>>0)&0xFF,
118                (r>>24)&0xFF,(r>>16)&0xFF,(r>>8)&0xFF,(r>>0)&0xFF)
119end
120
121-- A couple of shared helpers.
122
123local tounicodedictionary, widtharray, collectindices, subsetname, includecidset, forcecidset, tocidsetdictionary
124
125do
126
127    -- Because we supply tounicodes ourselves we only use bfchar mappings (as in the
128    -- backend). In fact, we can now no longer pass the tounicodes to the frontend but
129    -- pick them up from the descriptions.
130
131    local f_mapping_2 = formatters["<%02X> <%s>"]
132    local f_mapping_4 = formatters["<%04X> <%s>"]
133
134    local tounicode = fonts.mappings.tounicode
135
136local tounicode_template <const> = [[
137%%!PS-Adobe-3.0 Resource-CMap
138%%%%DocumentNeededResources: ProcSet (CIDInit)
139%%%%IncludeResource: ProcSet (CIDInit)
140%%%%BeginResource: CMap (TeX-%s-0)
141%%%%Title: (TeX-%s-0 TeX %s 0)|
142%%%%Version: 1.000
143%%%%EndComments
144/CIDInit /ProcSet findresource begin
145  12 dict begin
146    begincmap
147      /CIDSystemInfo
148        << /Registry (TeX) /Ordering (%s) /Supplement 0 >>
149      def
150      /CMapName
151        /TeX-Identity-%s
152      def
153      /CMapType
154        2
155      def
156      1 begincodespacerange
157        <%s> <%s>
158      endcodespacerange
159      %i beginbfchar
160%s
161      endbfchar
162    endcmap
163    CMapName currentdict /CMap defineresource pop
164  end
165end
166%%%%EndResource
167%%%%EOF]]
168
169    tounicodedictionary = function(details,indices,maxindex,name,wide)
170        local mapping = { }
171        local length  = 0
172        if maxindex > 0 then
173            local f_mapping = wide and f_mapping_4 or f_mapping_2
174            for index=1,maxindex do
175                local data = indices[index]
176                if data then
177                    length = length + 1
178                    local unicode = data.unicode
179                    if unicode then
180                        unicode = tounicode(unicode)
181                    else
182                        unicode = "FFFD" -- wide only
183                    end
184                    mapping[length] = f_mapping(index,unicode)
185                end
186            end
187        end
188        local name  = gsub(name,"%+","-") -- like luatex does
189        local first = wide and "0000" or "00"
190        local last  = wide and "FFFF" or "FF"
191        local blob  = format(tounicode_template,name,name,name,name,name,first,last,length,concat(mapping,"\n"))
192        return blob
193    end
194
195    widtharray = function(details,indices,maxindex,units,correction)
196        local widths = pdfarray()
197        local length = 0
198        local factor = 10000 / (units * correction)
199        if maxindex > 0 then
200            local lastindex = -1
201            local sublist   = nil
202            for index=1,maxindex do
203                local data = indices[index]
204                if data then
205                    local width = data.width -- hm, is inaccurate for cff, so take from elsewhere
206                    if width then
207                     -- width = round(width * 10000 / units) / 10
208                        width = round(width * factor) / 10
209                    else
210                        width = 0
211                    end
212                    if index == lastindex + 1 then
213                        sublist[#sublist+1] = width
214                    else
215                        if sublist then
216                            length = length + 1
217                            widths[length] = sublist
218                        end
219                        sublist = pdfarray { width }
220                        length  = length + 1
221                        widths[length] = index
222                    end
223                    lastindex = index
224                end
225            end
226            length = length + 1
227            widths[length] = sublist
228        end
229        return widths
230    end
231
232    -- we need to go through indices because descriptions can be different (take
233    -- basemode remappings)
234
235    collectindices = function(descriptions,indices,used,hash)
236        local minindex = 0xFFFF
237        local maxindex = 0
238        local reverse  = { }
239        local copies   = { }
240        local include  = { }
241        local resolved = { }
242
243        setmetatable(used,nil) -- prevent more index allocations
244
245        for unicode, data in next, descriptions do
246            local index = data.index
247            reverse[index or unicode] = data
248            if used[index] and data.dupindex then
249                copies[index] = data.dupindex
250            end
251        end
252        for index, usedindex in next, indices do
253            if usedindex > maxindex then
254                maxindex = usedindex
255            end
256            if usedindex < minindex then
257                minindex = usedindex
258            end
259            include[usedindex]  = copies[index] or index
260            resolved[usedindex] = reverse[index]
261        end
262        if minindex > maxindex then
263            minindex = maxindex
264        end
265        if trace_details then
266            report_fonts("embedded: hash %a, minindex %i, maxindex %i",hash,minindex,maxindex)
267            for k, v in sortedhash(include) do
268                local d = resolved[k]
269                if d.dupindex then
270                    report_fonts("  0x%04X : 0x%04X (duplicate 0x%05X)",k,v,d.index)
271                else
272                    report_fonts("  0x%04X : 0x%04X",k,v)
273                end
274            end
275        end
276        return resolved, include, minindex, maxindex
277    end
278
279    includecidset = false
280    forcecidset   = false -- for private testing only
281
282    function lpdf.setincludecidset(v)
283        -- incredible ... it's obselete, no viewer needs it so why still this crap
284        -- so in the end it had to be introduced again
285        includecidset = v
286    end
287
288    directives.register("backend.pdf.forcecidset",function(v)
289        forcecidset = v
290    end)
291
292    tocidsetdictionary = function(indices,min,max)
293        if includecidset or forcecidset then
294            local b = { }
295            local m = (max + 7) // 8
296            for i=0,max do
297                b[i] = 0
298            end
299            b[0] = b[0] | (1 << 7) -- force notdef into the file
300            for i=min,max do
301                if indices[i] then
302                    local bi = i // 8
303                    local ni = i % 8
304                    b[bi] = b[bi] | (1 << (7-ni))
305                end
306            end
307            b = char(unpack(b,0,#b))
308            return pdfreference(pdfflushstreamobject(b))
309        end
310    end
311
312    -- Actually we can use the same as we only embed once.
313
314    -- subsetname = function(name)
315    --     return "CONTEXT" .. name
316    -- end
317
318    local prefixes = { } -- todo: set fixed one
319
320    subsetname = function(name)
321        local prefix
322        while true do
323            prefix = utfchar(random(65,90),random(65,90),random(65,90),random(65,90),random(65,90),random(65,90))
324            if not prefixes[prefix] then
325                prefixes[prefix] = true
326                break
327            end
328        end
329        return prefix .. "+" .. name
330    end
331
332end
333
334-- The three writers: opentype, truetype and type1.
335
336local mainwriters  = { }
337
338do
339
340    -- advh = os2.ascender - os2.descender
341    -- tsb  = default_advh - os2.ascender
342
343    -- truetype has the following tables:
344
345    -- head : mandate
346    -- hhea : mandate
347    -- vhea : mandate
348    -- hmtx : mandate
349    -- maxp : mandate
350    -- glyf : mandate
351    -- loca : mandate
352    --
353    -- cvt  : if needed (but we flatten)
354    -- fpgm : if needed (but we flatten)
355    -- prep : if needed (but we flatten)
356    -- PCLT : if needed (but we flatten)
357    --
358    -- name : not needed for T2: backend does that
359    -- post : not needed for T2: backend does that
360    -- OS/2 : not needed for T2: backend does that
361    -- cmap : not needed for T2: backend does that
362
363    local streams       = utilities.streams
364    local openstring    = streams.openstring
365    local readcardinal2 = streams.readcardinal2
366    ----- readcardinal4 = streams.readcardinal4
367
368    local otfreaders    = fonts.handlers.otf.readers
369
370    local function readcardinal4(f) -- this needs to be sorted out
371        local a = readcardinal2(f)
372        local b = readcardinal2(f)
373        if a and b then
374            return a * 0x10000 + b
375        end
376    end
377
378    -- -- --
379
380    local tablereaders  = { }
381    local tablewriters  = { }
382    local tablecreators = { }
383    local tableloaders  = { }
384
385    local openfontfile, closefontfile, makefontfile, makemetadata  do
386
387        local details    = {
388            details        = true,
389            platformnames  = true,
390            platformextras = true,
391        }
392
393        -- .022 sec on luatex manual, neglectable:
394
395     -- local function checksum(data)
396     --     local s = openstring(data)
397     --     local n = 0
398     --     local d = #data
399     --     while true do
400     --         local c = readcardinal4(s)
401     --         if c then
402     --             n = (n + c) % 0x100000000
403     --         else
404     --             break
405     --         end
406     --     end
407     --     return n
408     -- end
409
410        local function checksum(data)
411            local s = openstring(data)
412            local n = 0
413            local d = #data
414            while true do
415                local a = readcardinal2(s)
416                local b = readcardinal2(s)
417                if b then
418                    n = (n + a * 0x10000 + b) % 0x100000000
419                else
420                    break
421                end
422            end
423            return n
424        end
425
426        openfontfile = function(details)
427            return {
428                offset  = 0,
429                order   = { },
430                used    = { },
431                details = details,
432                streams = details.streams,
433            }
434        end
435
436        closefontfile = function(fontfile)
437            for k, v in next, fontfile do
438                fontfile[k] = nil -- so it can be collected asap
439            end
440        end
441
442        local metakeys = {
443            "uniqueid", "version",
444            "copyright", "license", "licenseurl",
445            "manufacturer", "vendorurl",
446            "family", "subfamily",
447            "typographicfamily", "typographicsubfamily",
448            "fullname", "postscriptname",
449        }
450
451    local template <const> = [[
452<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
453  <x:xmpmeta xmlns:x="adobe:ns:meta/">
454    <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
455      <rdf:Description rdf:about="" xmlns:pdfx="http://ns.adobe.com/pdfx/1.3/">
456
457%s
458
459      </rdf:Description>
460    </rdf:RDF>
461  </x:xmpmeta>
462<?xpacket end="w"?>]]
463
464        makemetadata = function(fontfile)
465            local names  = fontfile.streams.names
466            local list   = { }
467            local f_name = formatters["<pdfx:%s>%s</pdfx:%s>"]
468            for i=1,#metakeys do
469                local m = metakeys[i]
470                local n = names[m]
471                if n then
472                    list[#list+1] = f_name(m,n,m)
473                end
474            end
475            return format(template,concat(list,"\n"))
476        end
477
478        makefontfile = function(fontfile)
479            local order = fontfile.order
480            local used  = fontfile.used
481            local count = 0
482            for i=1,#order do
483                local tag  = order[i]
484                local data = fontfile[tag]
485                if data and #data > 0 then
486                    count = count + 1
487                else
488                    fontfile[tag] = false
489                end
490            end
491            local offset = 12 + (count * 16)
492            local headof = 0
493            local list   = {
494                "" -- placeholder
495            }
496            local i = 1
497            local k = 0
498            while i <= count do
499                i = i << 1
500                k = k + 1
501            end
502            local searchrange   = i << 3
503            local entryselector = k - 1
504            local rangeshift    = (count  << 4) - (i << 3)
505            local index  = {
506                tocardinal4(0x00010000), -- tables.version
507                tocardinal2(count),
508                tocardinal2(searchrange),
509                tocardinal2(entryselector),
510                tocardinal2(rangeshift),
511            }
512            --
513            local ni = #index
514            local nl = #list
515            for i=1,#order do
516                local tag  = order[i]
517                local data = fontfile[tag]
518                if data then
519                    local csum    = checksum(data)
520                    local dlength = #data
521                    local length  = ((dlength + 3) // 4) * 4
522                    local padding = length - dlength
523                    nl = nl + 1 ; list[nl] = data
524                    for i=1,padding do
525                        nl = nl + 1 ; list[nl] = "\0"
526                    end
527                    if #tag == 3 then
528                        tag = tag .. " "
529                    end
530                    ni = ni + 1 ; index[ni] = tag -- must be 4 chars
531                    ni = ni + 1 ; index[ni] = tocardinal4(csum)
532                    ni = ni + 1 ; index[ni] = tocardinal4(offset)
533                    ni = ni + 1 ; index[ni] = tocardinal4(dlength)
534                    used[i] = offset -- not used
535                    if tag == "head" then
536                        headof = offset
537                    end
538                    offset = offset + length
539                end
540            end
541            list[1] = concat(index)
542            local off = #list[1] + headof + 1 + 8
543            list = concat(list)
544            local csum = (0xB1B0AFBA - checksum(list)) % 0x100000000
545            list = sub(list,1,off-1) .. tocardinal4(csum) .. sub(list,off+4,#list)
546            return list
547        end
548
549        local function register(fontfile,name)
550            local u = fontfile.used
551            local o = fontfile.order
552            if not u[name] then
553                o[#o+1] = name
554                u[name] = true
555            end
556        end
557
558        local function create(fontfile,name)
559            local t = { }
560            fontfile[name] = t
561            return t
562        end
563
564        local function write(fontfile,name)
565            local t = fontfile[name]
566            if not t then
567                return
568            end
569            register(fontfile,name)
570            if type(t) == "table" then
571                if t[0] then
572                    fontfile[name] = concat(t,"",0,#t)
573                elseif #t > 0 then
574                    fontfile[name] = concat(t)
575                else
576                    fontfile[name] = false
577                end
578            end
579        end
580
581        tablewriters.head = function(fontfile)
582            register(fontfile,"head")
583            local t = fontfile.streams.fontheader
584            fontfile.head = concat {
585                tocardinal4(t.version),
586                tocardinal4(t.fontversionnumber),
587                tocardinal4(t.checksum),
588                tocardinal4(t.magic),
589                tocardinal2(t.flags),
590                tocardinal2(t.units),
591                tocardinal8(t.created),
592                tocardinal8(t.modified),
593                tocardinal2(t.xmin),
594                tocardinal2(t.ymin),
595                tocardinal2(t.xmax),
596                tocardinal2(t.ymax),
597                tocardinal2(t.macstyle),
598                tocardinal2(t.smallpixels),
599                tocardinal2(t.directionhint),
600                tocardinal2(t.indextolocformat),
601                tocardinal2(t.glyphformat),
602            }
603        end
604
605        tablewriters.hhea = function(fontfile)
606            register(fontfile,"hhea")
607            local t = fontfile.streams.horizontalheader
608            local n = t and fontfile.nofglyphs or 0
609            fontfile.hhea = concat {
610                tocardinal4(t.version),
611                tocardinal2(t.ascender),
612                tocardinal2(t.descender),
613                tocardinal2(t.linegap),
614                tocardinal2(t.maxadvancewidth),
615                tocardinal2(t.minleftsidebearing),
616                tocardinal2(t.minrightsidebearing),
617                tocardinal2(t.maxextent),
618                tocardinal2(t.caretsloperise),
619                tocardinal2(t.caretsloperun),
620                tocardinal2(t.caretoffset),
621                tocardinal2(t.reserved_1),
622                tocardinal2(t.reserved_2),
623                tocardinal2(t.reserved_3),
624                tocardinal2(t.reserved_4),
625                tocardinal2(t.metricdataformat),
626                tocardinal2(n) -- t.nofmetrics
627            }
628        end
629
630        tablewriters.vhea = function(fontfile)
631            local t = fontfile.streams.verticalheader
632            local n = t and fontfile.nofglyphs or 0
633            register(fontfile,"vhea")
634            fontfile.vhea = concat {
635                tocardinal4(t.version),
636                tocardinal2(t.ascender),
637                tocardinal2(t.descender),
638                tocardinal2(t.linegap),
639                tocardinal2(t.maxadvanceheight),
640                tocardinal2(t.mintopsidebearing),
641                tocardinal2(t.minbottomsidebearing),
642                tocardinal2(t.maxextent),
643                tocardinal2(t.caretsloperise),
644                tocardinal2(t.caretsloperun),
645                tocardinal2(t.caretoffset),
646                tocardinal2(t.reserved_1),
647                tocardinal2(t.reserved_2),
648                tocardinal2(t.reserved_3),
649                tocardinal2(t.reserved_4),
650                tocardinal2(t.metricdataformat),
651                tocardinal2(n) -- t.nofmetrics
652            }
653        end
654
655        tablewriters.maxp = function(fontfile)
656            register(fontfile,"maxp")
657            local t = fontfile.streams.maximumprofile
658            local n = fontfile.nofglyphs
659            -- if fontfile.streams.cffinfo then
660                -- error
661            -- end
662            fontfile.maxp = concat {
663                tocardinal4(0x00010000),
664                tocardinal2(n),
665                tocardinal2(t.points),
666                tocardinal2(t.contours),
667                tocardinal2(t.compositepoints),
668                tocardinal2(t.compositecontours),
669                tocardinal2(t.zones),
670                tocardinal2(t.twilightpoints),
671                tocardinal2(t.storage),
672                tocardinal2(t.functiondefs),
673                tocardinal2(t.instructiondefs),
674                tocardinal2(t.stackelements),
675                tocardinal2(t.sizeofinstructions),
676                tocardinal2(t.componentelements),
677                tocardinal2(t.componentdepth),
678            }
679        end
680
681        tablecreators.loca = function(fontfile) return create(fontfile,"loca") end
682        tablewriters .loca = function(fontfile) return write (fontfile,"loca") end
683
684        tablecreators.glyf = function(fontfile) return create(fontfile,"glyf") end
685        tablewriters .glyf = function(fontfile) return write (fontfile,"glyf") end
686
687        tablecreators.hmtx = function(fontfile) return create(fontfile,"hmtx") end
688        tablewriters .hmtx = function(fontfile) return write (fontfile,"hmtx") end
689
690        tablecreators.vmtx = function(fontfile) return create(fontfile,"vmtx") end
691        tablewriters .vmtx = function(fontfile) return write (fontfile,"vmtx") end
692
693        tableloaders .cmap = function(fontfile) return read  (fontfile,"cmap") end
694        tablewriters .cmap = function(fontfile) return write (fontfile,"cmap") end
695
696        tableloaders .name = function(fontfile) return read  (fontfile,"name") end
697        tablewriters .name = function(fontfile) return write (fontfile,"name") end
698
699        tableloaders .post = function(fontfile) return read  (fontfile,"post") end
700        tablewriters .post = function(fontfile) return write (fontfile,"post") end
701
702    end
703
704    mainwriters["truetype"] = function(details)
705        --
706        local fontfile         = openfontfile(details)
707        local basefontname     = details.basefontname
708        local streams          = details.streams
709        local blobs            = streams.streams
710        local fontheader       = streams.fontheader
711        local horizontalheader = streams.horizontalheader
712        local verticalheader   = streams.verticalheader
713        local maximumprofile   = streams.maximumprofile
714        local names            = streams.names
715        local descriptions     = details.rawdata.descriptions
716        local metadata         = details.rawdata.metadata
717        local indices          = details.indices
718        local metabbox         = { fontheader.xmin, fontheader.ymin, fontheader.xmax, fontheader.ymax }
719        local indices,
720              include,
721              minindex,
722              maxindex         = collectindices(descriptions,indices,details.used,details.hash)
723        local glyphstreams     = tablecreators.glyf(fontfile)
724        local locations        = tablecreators.loca(fontfile)
725        local horizontals      = tablecreators.hmtx(fontfile)
726        local verticals        = tablecreators.vmtx(fontfile)
727        --
728        local zero2            = tocardinal2(0)
729        local zero4            = tocardinal4(0)
730        --
731        local horizontal       = horizontalheader.nofmetrics > 0
732        local vertical         = verticalheader.nofmetrics > 0
733        --
734        local streamoffset     = 0
735        local lastoffset       = zero4
736        local g, h, v          = 0, 0, 0
737        --
738        -- todo: locate notdef
739        --
740        if minindex > 0 then
741            local blob = blobs[0]
742            if blob and #blob > 0 then
743                locations[0] = lastoffset
744                g = g + 1 ; glyphstreams[g] = blob
745                h = h + 1 ; horizontals [h] = zero4
746                if vertical then
747                    v = v + 1 ; verticals[v] = zero4
748                end
749                streamoffset = streamoffset + #blob
750                lastoffset = tocardinal4(streamoffset)
751            else
752                report_fonts("missing .notdef in font %a",basefontname)
753            end
754            -- todo: use a rep for h/v
755            for index=1,minindex-1 do -- no needed in new low range
756                locations[index] = lastoffset
757                h = h + 1 ; horizontals[h] = zero4
758                if vertical then
759                    v = v + 1 ; verticals[v] = zero4
760                end
761            end
762        end
763
764        for index=minindex,maxindex do
765            locations[index] = lastoffset
766            local data = indices[index]
767            if data then
768                local blob = blobs[include[index]] -- we assume padding
769                if blob and #blob > 0 then
770                    g = g + 1 ; glyphstreams[g] = blob
771                    h = h + 1 ; horizontals [h] = tocardinal2(data.width or 0)
772                    h = h + 1 ; horizontals [h] = tocardinal2(data.boundingbox[1])
773                    if vertical then
774                        v = v + 1 ; verticals[v] = tocardinal2(data.height or 0)
775                        v = v + 1 ; verticals[v] = tocardinal2(data.boundingbox[3])
776                    end
777                    streamoffset = streamoffset + #blob
778                    lastoffset   = tocardinal4(streamoffset)
779                else
780                    h = h + 1 ; horizontals[h] = zero4
781                    if vertical then
782                        v = v + 1 ; verticals[v] = zero4
783                    end
784                    report_fonts("missing blob for index %i in font %a",index,basefontname)
785                end
786            else
787                h = h + 1 ; horizontals[h] = zero4
788                if vertical then
789                    v = v + 1 ; verticals[v] = zero4
790                end
791            end
792        end
793
794        locations[maxindex+1] = lastoffset -- cf spec
795        --
796        local nofglyphs             = maxindex + 1 -- include zero
797        --
798        fontheader.checksum         = 0
799        fontheader.indextolocformat = 1
800        maximumprofile.nofglyphs    = nofglyphs
801        --
802        fontfile.format             = "tff"
803        fontfile.basefontname       = basefontname
804        fontfile.nofglyphs          = nofglyphs
805        --
806        tablewriters.head(fontfile)
807        tablewriters.hhea(fontfile)
808        if vertical then
809            tablewriters.vhea(fontfile)
810        end
811        tablewriters.maxp(fontfile)
812
813        tablewriters.loca(fontfile)
814        tablewriters.glyf(fontfile)
815
816        tablewriters.hmtx(fontfile)
817        if vertical then
818            tablewriters.vmtx(fontfile)
819        end
820        --
821        local fontdata = makefontfile(fontfile)
822        local fontmeta = makemetadata(fontfile)
823        --
824        fontfile = closefontfile(fontfile)
825        --
826        local units       = metadata.units
827        local basefont    = pdfconstant(basefontname)
828        local widths      = widtharray(details,indices,maxindex,units,1)
829        local object      = details.objectnumber
830        local tounicode   = tounicodedictionary(details,indices,maxindex,basefontname,true)
831        local tocidset    = tocidsetdictionary(indices,minindex,maxindex)
832        local metabbox    = metadata.boundingbox or { 0, 0, 0, 0 }
833        local fontbbox    = pdfarray { unpack(metabbox) }
834        local ascender    = metadata.ascender
835        local descender   = metadata.descender
836        local capheight   = metadata.capheight or fontbbox[4]
837        local stemv       = metadata.weightclass
838        local italicangle = metadata.italicangle
839        local xheight     = metadata.xheight or fontbbox[4]
840        --
841        if stemv then
842            stemv = (stemv/65)^2 + 50
843        end
844        --
845        local function scale(n)
846            if n then
847                return round((n) * 10000 / units) / 10
848            else
849                return 0
850            end
851        end
852        --
853        local reserved  = pdfreserveobject()
854        local child = pdfdictionary {
855            Type           = pdfconstant("Font"),
856            Subtype        = pdfconstant("CIDFontType2"),
857            BaseFont       = basefont,
858            FontDescriptor = pdfreference(reserved),
859            W              = pdfreference(pdfflushobject(widths)),
860            CIDToGIDMap    = pdfconstant("Identity"),
861            CIDSystemInfo  = pdfdictionary {
862                Registry   = pdfstring("Adobe"),
863                Ordering   = pdfstring("Identity"),
864                Supplement = 0,
865            }
866        }
867        local descendants = pdfarray {
868            pdfreference(pdfflushobject(child)),
869        }
870        local descriptor = pdfdictionary {
871            Type        = pdfconstant("FontDescriptor"),
872            FontName    = basefont,
873            Flags       = 4,
874            FontBBox    = fontbbox,
875            Ascent      = scale(ascender),
876            Descent     = scale(descender),
877            ItalicAngle = round(italicangle or 0),
878            CapHeight   = scale(capheight),
879            StemV       = scale(stemv),
880            XHeight     = scale(xheight),
881            FontFile2   = pdfreference(pdfflushstreamobject(fontdata)),
882            CIDSet      = tocidset, -- no longer needed but verifyers want it
883            Metadata    = fontmeta and pdfreference(pdfflushstreamobject(fontmeta)) or nil,
884        }
885        local parent = pdfdictionary {
886            Type            = pdfconstant("Font"),
887            Subtype         = pdfconstant("Type0"),
888            Encoding        = pdfconstant(details.properties.writingmode == "vertical" and "Identity-V" or "Identity-H"),
889            BaseFont        = basefont,
890            DescendantFonts = descendants,
891            ToUnicode       = pdfreference(pdfflushstreamobject(tounicode)),
892        }
893        pdfflushobject(reserved,descriptor)
894        pdfflushobject(object,parent)
895        --
896     -- if trace_details then
897     --     local name = "temp.ttf"
898     --     report_fonts("saving %a",name)
899     --     io.savedata(name,fontdata)
900     --     inspect(fonts.handlers.otf.readers.loadfont(name))
901     -- end
902        --
903    end
904
905    do
906        -- todo : cff2
907
908        local details    = {
909            details        = true,
910            platformnames  = true,
911            platformextras = true,
912        }
913
914        tablecreators.cff = function(fontfile)
915            fontfile.charstrings  = { }
916            fontfile.charmappings = { }
917            fontfile.cffstrings   = { }
918            fontfile.cffhash      = { }
919            return fontfile.charstrings , fontfile.charmappings
920        end
921
922        local todictnumber, todictreal, todictinteger, todictoffset  do
923
924            local maxnum  =   0x7FFFFFFF
925            local minnum  = - 0x7FFFFFFF - 1
926            local epsilon = 1.0e-5
927
928            local int2tag = "\28"
929            local int4tag = "\29"
930            local realtag = "\30"
931
932            todictinteger = function(n)
933                if not n then
934                    return char(139 & 0xFF)
935                elseif n >= -107 and n <= 107 then
936                    return char((n + 139) & 0xFF)
937                elseif n >= 108 and n <= 1131 then
938                    n = 0xF700 + n - 108
939                    return char((n >> 8) & 0xFF, n & 0xFF)
940                elseif n >= -1131 and n <= -108 then
941                    n = 0xFB00 - n - 108
942                    return char((n >> 8) & 0xFF, n & 0xFF)
943                elseif n >= -32768 and n <= 32767 then
944                 -- return char(28,extract8(n,8),extract8(n,0))
945                    return char(28,(n>>8)&0xFF,(n>>0)&0xFF)
946                else
947                 -- return char(29,extract8(n,24&0xFF,extract8(n,16),extract8(n,8),extract8(n,0))
948                    return char(29,(n>>24)&0xFF,(n>>16)&0xFF,(n>>8)&0xFF,(n>>0)&0xFF)
949                end
950            end
951
952         -- -- not called that often
953         --
954         -- local encoder = readers.cffencoder
955         --
956         -- todictinteger = function(n)
957         --     if not n then
958         --         return encoder[0]
959         --     elseif n >= -1131 and n <= 1131 then
960         --         return encoder[n]
961         --     elseif n >= -32768 and n <= 32767 then
962         --      -- return char(28,extract8(n,8),extract8(n,0))
963         --         return char(28,(n>>8)&0xFF,(n>>0)&0xFF)
964         --     else
965         --      -- return char(29,extract8(n,24),extract8(n,16),extract8(n,8),extract8(n,0))
966         --         return char(29,(n>>24)&0xFF,(n>>16)&0xFF,(n>>8)&0xFF,(n>>0)&0xFF)
967         --     end
968         -- end
969
970            todictoffset = function(n)
971                return int4tag .. tointeger4(n)
972            end
973
974            local e  = false
975            local z  = byte("0")
976            local dp = 10
977            local ep = 11
978            local em = 12
979            local mn = 14
980            local es = 15
981
982            local fg = formatters["%g"]
983
984            todictreal = function(v)
985                local s = fg(v)
986                local t = { [0] = realtag }
987                local n = 0
988                for s in gmatch(s,".") do
989                    if s == "e" or s == "E" then
990                        e = true
991                    elseif s == "+" then
992                        -- skip
993                    elseif s == "-" then
994                        n = n + 1
995                        if e then
996                            t[n] = em
997                            e = false
998                        else
999                            t[n] = mn
1000                        end
1001                    else
1002                        if e then
1003                            n = n + 1
1004                            t[n] = ep
1005                            e = false
1006                        end
1007                        n = n + 1
1008                        if s == "." then
1009                            t[n] = dp
1010                        else
1011                            t[n] = byte(s) - z
1012                        end
1013                    end
1014                end
1015                n = n + 1
1016                t[n] = es
1017                if (n % 2) ~= 0 then
1018                    n = n + 1
1019                    t[n] = es
1020                end
1021                local j = 0
1022                for i=1,n,2 do
1023                    j = j + 1
1024                    t[j] = char(t[i]*0x10+t[i+1])
1025                end
1026                t = concat(t,"",0,j)
1027                return t
1028            end
1029
1030            todictnumber = function(n)
1031                if not n or n == 0 then
1032                    return todictinteger(0)
1033                elseif (n > maxnum or n < minnum or (abs(n - round(n)) > epsilon)) then
1034                    return todictreal(n)
1035                else
1036                    return todictinteger(n)
1037                end
1038            end
1039
1040        end
1041
1042        local todictkey = char
1043
1044        local function todictstring(fontfile,value)
1045            if not value then
1046                value = ""
1047            end
1048            local s = fontfile.cffstrings
1049            local h = fontfile.cffhash
1050            local n = h[value]
1051            if not n then
1052                n = #s + 1
1053                s[n] = value
1054                h[value] = n
1055            end
1056            return todictinteger(390+n)
1057        end
1058
1059        local function todictboolean(b)
1060            return todictinteger(b and 1 or 0)
1061        end
1062
1063        local function todictdeltas(t)
1064            local r = { }
1065            for i=1,#t do
1066                r[i] = todictnumber(t[i]-(t[i-1] or 0))
1067            end
1068            return concat(r)
1069        end
1070
1071        local function todictarray(t)
1072            local r = { }
1073            for i=1,#t do
1074                r[i] = todictnumber(t[i])
1075            end
1076            return concat(r)
1077        end
1078
1079        local function writestring(target,source,offset,what)
1080            target[#target+1] = source
1081         -- report_fonts("string : %-11s %06i # %05i",what,offset,#source)
1082            return offset + #source
1083        end
1084
1085        local function writetable(target,source,offset,what)
1086            source = concat(source)
1087            target[#target+1] = source
1088         -- report_fonts("table  : %-11s %06i # %05i",what,offset,#source)
1089            return offset + #source
1090        end
1091
1092        local function writeindex(target,source,offset,what)
1093            local n = #source
1094            local t = #target
1095            t = t + 1 ; target[t] = tocardinal2(n)
1096            if n > 0 then
1097                local data = concat(source)
1098                local size = #data -- assume the worst
1099                local offsetsize, tocardinal
1100                if size < 0xFF then
1101                    offsetsize, tocardinal = 1, tocardinal1
1102                elseif size < 0xFFFF then
1103                    offsetsize, tocardinal = 2, tocardinal2
1104                elseif size < 0xFFFFFF then
1105                    offsetsize, tocardinal = 3, tocardinal3
1106                elseif size < 0xFFFFFFFF then
1107                    offsetsize, tocardinal = 4, tocardinal4
1108                end
1109             -- report_fonts("index  : %-11s %06i # %05i (%i entries with offset size %i)",what,offset,#data,n,offsetsize)
1110                offset = offset + 2 + 1 + (n + 1) * offsetsize + size
1111                -- bytes per offset
1112                t = t + 1 ; target[t] = tocardinal1(offsetsize)
1113                -- list of offsets (one larger for length calculation)
1114                local offset = 1 -- mandate
1115                t = t + 1 ; target[t] = tocardinal(offset)
1116                for i=1,n do
1117                    offset = offset + #source[i]
1118                    t = t + 1 ; target[t] = tocardinal(offset)
1119                end
1120                t = t + 1 ; target[t] = data
1121            else
1122             -- report_fonts("index  : %-11s %06i # %05i (no entries)",what,offset,0)
1123                offset = offset + 2
1124            end
1125         -- print("offset",offset,#concat(target))
1126            return offset
1127        end
1128
1129        tablewriters.cff = function(fontfile)
1130            --
1131            local streams            = fontfile.streams
1132            local cffinfo            = streams.cffinfo or { }
1133            local names              = streams.names or { }
1134            local fontheader         = streams.fontheader or { }
1135            local basefontname       = fontfile.basefontname
1136            --
1137            local offset             = 0
1138            local dictof             = 0
1139            local target             = { }
1140            --
1141            local charstrings        = fontfile.charstrings
1142            local nofglyphs          = #charstrings + 1
1143            local fontmatrix         = { 0.001, 0, 0, 0.001, 0, 0 } -- todo
1144            local fontbbox           = fontfile.fontbbox
1145            local defaultwidth       = cffinfo.defaultwidth or 0
1146            local nominalwidth       = cffinfo.nominalwidth or 0
1147            local bluevalues         = cffinfo.bluevalues
1148            local otherblues         = cffinfo.otherblues
1149            local familyblues        = cffinfo.familyblues
1150            local familyotherblues   = cffinfo.familyotherblues
1151            local bluescale          = cffinfo.bluescale
1152            local blueshift          = cffinfo.blueshift
1153            local bluefuzz           = cffinfo.bluefuzz
1154            local stdhw              = cffinfo.stdhw
1155            local stdvw              = cffinfo.stdvw
1156            --
1157            if defaultwidth == 0 then defaultwidth     = nil end
1158            if nomimalwidth == 0 then nominalwidth     = nil end
1159            if bluevalues        then bluevalues       = todictarray(bluevalues) end
1160            if otherblues        then otherblues       = todictarray(otherblues) end
1161            if familyblues       then familyblues      = todictarray(familyblues) end
1162            if familyotherblues  then familyotherblues = todictarray(familyotherblues) end
1163            if bluescale         then bluescale        = todictnumber(bluescale) end
1164            if blueshift         then blueshift        = todictnumber(blueshift) end
1165            if bluefuzz          then bluefuzz         = todictnumber(bluefuzz) end
1166            if stdhw             then stdhw            = todictdeltas(stdhw) end
1167            if stdvw             then stdvw            = todictdeltas(stdvw) end
1168            --
1169            local fontversion        = todictstring(fontfile,fontheader.fontversion or "uknown version")
1170            local familyname         = todictstring(fontfile,cffinfo.familyname or names.family or basefontname)
1171            local fullname           = todictstring(fontfile,cffinfo.fullname or basefontname)
1172            local weight             = todictstring(fontfile,cffinfo.weight or "Normal")
1173            local fontbbox           = todictarray(fontbbox)
1174            local strokewidth        = todictnumber(cffinfo.strokewidth)
1175            local monospaced         = todictboolean(cffinfo.monospaced)
1176            local italicangle        = todictnumber(cffinfo.italicangle)
1177            local underlineposition  = todictnumber(cffinfo.underlineposition)
1178            local underlinethickness = todictnumber(cffinfo.underlinethickness)
1179            local charstringtype     = todictnumber(2)
1180            local fontmatrix         = todictarray(fontmatrix)
1181            local ros                = todictstring(fontfile,"Adobe")    -- registry
1182                                    .. todictstring(fontfile,"Identity") -- identity
1183                                    .. todictnumber(0)                   -- supplement
1184            local cidcount           = todictnumber(fontfile.nofglyphs)
1185            local fontname           = todictstring(fontfile,basefontname)
1186            local fdarrayoffset      = todictoffset(0)
1187            local fdselectoffset     = todictoffset(0)
1188            local charstringoffset   = todictoffset(0)
1189            local charsetoffset      = todictoffset(0)
1190            local privateoffset      = todictoffset(0)
1191            --
1192            local defaultwidthx      = todictnumber(defaultwidth)
1193            local nominalwidthx      = todictnumber(nominalwidth)
1194            local private            = ""
1195                                    .. (defaultwidthx and (defaultwidthx .. todictkey(20)) or "")
1196                                    .. (nominalwidthx and (nominalwidthx .. todictkey(21)) or "")
1197                                    .. (bluevalues and (bluevalues .. todictkey(6)) or "")
1198                                    .. (otherblues and (otherblues .. todictkey(7)) or "")
1199                                    .. (familyblues and (familyblues .. todictkey(8)) or "")
1200                                    .. (familyotherblues and (familyotherblues .. todictkey(9)) or "")
1201                                    .. (bluescale and (bluescale .. todictkey(12,9)) or "")
1202                                    .. (blueshift and (blueshift .. todictkey(12,10)) or "")
1203                                    .. (bluefuzz and (bluefuzz .. todictkey(12,11)) or "")
1204                                    .. (stdhw and (stdhw .. todictkey(12,12)) or "")
1205                                    .. (stdvw and (stdvw .. todictkey(12,13)) or "")
1206            local privatesize        = todictnumber(#private)
1207            local privatespec        = privatesize .. privateoffset
1208            --
1209            -- header (fixed @ 1)
1210            --
1211            local header =
1212                tocardinal1(1) -- major
1213             .. tocardinal1(0) -- minor
1214             .. tocardinal1(4) -- header size
1215             .. tocardinal1(4) -- offset size
1216            --
1217            offset = writestring(target,header,offset,"header")
1218            --
1219            -- name index (fixed @ 2) (has to be sorted)
1220            --
1221            local names = {
1222                basefontname,
1223            }
1224            --
1225            offset = writeindex(target,names,offset,"names")
1226            --
1227            -- topdict index (fixed @ 3)
1228            --
1229            local topvars =
1230                charstringoffset .. todictkey(17)
1231             .. charsetoffset    .. todictkey(15)
1232             .. fdarrayoffset    .. todictkey(12,36)
1233             .. fdselectoffset   .. todictkey(12,37)
1234             .. privatespec      .. todictkey(18)
1235            --
1236            local topdict = {
1237                ros                   .. todictkey(12,30) -- first
1238             .. cidcount              .. todictkey(12,34)
1239             .. familyname            .. todictkey( 3)
1240             .. fullname              .. todictkey( 2)
1241             .. weight                .. todictkey( 4)
1242             .. fontbbox              .. todictkey( 5)
1243             .. monospaced            .. todictkey(12, 1)
1244             .. italicangle           .. todictkey(12, 2)
1245             .. underlineposition     .. todictkey(12, 3)
1246             .. underlinethickness    .. todictkey(12, 4)
1247             .. charstringtype        .. todictkey(12, 6)
1248             .. fontmatrix            .. todictkey(12, 7)
1249             .. strokewidth           .. todictkey(12, 8)
1250             .. topvars
1251            }
1252            --
1253            offset = writeindex(target,topdict,offset,"topdict")
1254            dictof = #target
1255            --
1256            -- string index (fixed @ 4)
1257            --
1258            offset = writeindex(target,fontfile.cffstrings,offset,"strings")
1259            --
1260            -- global subroutine index (fixed @ 5)
1261            --
1262            offset = writeindex(target,{},offset,"globals")
1263            --
1264            -- Encoding (cff1)
1265            --
1266            -- offset = writeindex(target,{},offset,"encoding")
1267            --
1268            -- Charsets
1269            --
1270            charsetoffset = todictoffset(offset)
1271            offset        = writetable(target,fontfile.charmappings,offset,"charsets")
1272            --
1273            -- fdselect
1274            --
1275            local fdselect =
1276                tocardinal1(3) -- format
1277             .. tocardinal2(1) -- n of ranges
1278             -- entry 1
1279             .. tocardinal2(0) -- first gid
1280             .. tocardinal1(0) -- fd index
1281             -- entry 2
1282          -- .. tocardinal2(fontfile.sparsemax-1) -- sentinel
1283             .. tocardinal2(fontfile.sparsemax) -- sentinel
1284            --
1285            fdselectoffset = todictoffset(offset)
1286            offset         = writestring(target,fdselect,offset,"fdselect")
1287            --
1288            -- charstrings
1289            --
1290            charstringoffset = todictoffset(offset)
1291            offset           = writeindex(target,charstrings,offset,"charstrings")
1292            --
1293            -- font dict
1294            --
1295            -- offset = writeindex(target,{},offset,"fontdict")
1296            --
1297            -- private
1298            --
1299            privateoffset = todictoffset(offset)
1300            privatespec   = privatesize .. privateoffset
1301            offset        = writestring(target,private,offset,"private")
1302            --
1303            local fdarray = {
1304                fontname    .. todictkey(12,38)
1305             .. privatespec .. todictkey(18)
1306            }
1307            fdarrayoffset = todictoffset(offset)
1308            offset        = writeindex(target,fdarray,offset,"fdarray")
1309            --
1310            topdict = target[dictof]
1311            topdict = sub(topdict,1,#topdict-#topvars)
1312            topvars =
1313                charstringoffset .. todictkey(17)
1314             .. charsetoffset    .. todictkey(15)
1315             .. fdarrayoffset    .. todictkey(12,36)
1316             .. fdselectoffset   .. todictkey(12,37)
1317             .. privatespec      .. todictkey(18)
1318            target[dictof] = topdict .. topvars
1319            --
1320            target = concat(target)
1321         -- if trace_details then
1322         --     local name = "temp.cff"
1323         --     report_fonts("saving %a",name)
1324         --     io.savedata(name,target)
1325         --     inspect(fonts.handlers.otf.readers.cffcheck(name))
1326         -- end
1327            return target
1328        end
1329
1330    end
1331
1332    -- todo: check widths (missing a decimal)
1333
1334    mainwriters["opentype"] = function(details)
1335        --
1336        local fontfile       = openfontfile(details)
1337        local basefontname   = details.basefontname
1338        local streams        = details.streams
1339        local blobs          = streams.streams
1340        local fontheader     = streams.fontheader
1341        local maximumprofile = streams.maximumprofile
1342        local names          = streams.names -- not used
1343        local descriptions   = details.rawdata.descriptions
1344        local metadata       = details.rawdata.metadata
1345        local indices        = details.indices
1346        local metabbox       = { fontheader.xmin, fontheader.ymin, fontheader.xmax, fontheader.ymax }
1347        local correction     = 1
1348
1349        -- (*) We share code with type1 and when we have old school tfm with
1350        -- pfb but without descriptions we're kind of toast.
1351
1352        if not descriptions or not next(descriptions) then
1353            -- This is good enough, we only need indices and widths.
1354            descriptions = details.fontdata.characters
1355            -- This is a hack, we have no basepoints.
1356            correction   = details.fontdata.parameters.size / 1000
1357            -- And this needs checking.
1358            correction   = correction * bpfactor / ptfactor
1359            metadata     = { }
1360        end
1361        --
1362        local indices,
1363              include,
1364              minindex,
1365              maxindex       = collectindices(descriptions,indices,details.used,details.hash)
1366
1367        local streamoffset   = 0
1368        local glyphstreams,
1369              charmappings   = tablecreators.cff(fontfile)
1370        --
1371        local zero2          = tocardinal2(0)
1372        local zero4          = tocardinal4(0)
1373        --
1374        -- we need to locate notdef (or store its unicode someplace)
1375        --
1376        local blob              = blobs[0] or "\14"
1377        local sparsemax         = 1
1378        local lastoffset        = zero4
1379        glyphstreams[sparsemax] = blob
1380        charmappings[sparsemax] = tocardinal1(0) -- format 0
1381        streamoffset            = streamoffset + #blob
1382        lastoffset              = tocardinal4(streamoffset)
1383        if minindex == 0 then
1384            minindex = 1
1385        end
1386        --
1387        for index=minindex,maxindex do
1388            local idx = include[index]
1389            if idx then
1390                local blob = blobs[idx]
1391                if not blob then
1392                    blob = "\14"
1393                end
1394                sparsemax               = sparsemax + 1
1395                glyphstreams[sparsemax] = blob
1396                charmappings[sparsemax] = tocardinal2(index)
1397                streamoffset            = streamoffset + #blob
1398                lastoffset              = tocardinal4(streamoffset)
1399            end
1400        end
1401        --
1402        fontfile.nofglyphs    = maxindex + 1
1403        fontfile.sparsemax    = sparsemax
1404        fontfile.format       = "cff"
1405        fontfile.basefontname = basefontname
1406        fontfile.fontbbox     = metabbox
1407        --
1408        local fontdata = tablewriters.cff(fontfile)
1409        local fontmeta = makemetadata(fontfile)
1410        --
1411        fontfile = closefontfile(fontfile)
1412        --
1413        local units = fontheader.units or metadata.units
1414        if units ~= 1000 then
1415            -- maybe only otf
1416            -- public sans has 2000 so we need to mess different from e.g. ttf
1417            report_fonts("width units in %a are %i, forcing 1000 instead",basefontname,units)
1418            units = 1000
1419        end
1420        --
1421        local basefont    = pdfconstant(basefontname)
1422        local widths      = widtharray(details,indices,maxindex,units,correction)
1423        local object      = details.objectnumber
1424        local tounicode   = tounicodedictionary(details,indices,maxindex,basefontname,true)
1425        local tocidset    = tocidsetdictionary(indices,minindex,maxindex)
1426        local fontbbox    = pdfarray { unpack(metabbox) }
1427        local ascender    = metadata.ascender or 0
1428        local descender   = metadata.descender or 0
1429        local capheight   = metadata.capheight or fontbbox[4]
1430        local stemv       = metadata.weightclass
1431        local italicangle = metadata.italicangle
1432        local xheight     = metadata.xheight or fontbbox[4]
1433        if stemv then
1434            stemv = (stemv/65)^2 + 50
1435        else
1436         -- stemv = 2
1437        end
1438        --
1439        local function scale(n)
1440            if n then
1441                return round((n) * 10000 / units) / 10
1442            else
1443                return 0
1444            end
1445        end
1446        --
1447        local reserved  = pdfreserveobject()
1448        local child = pdfdictionary {
1449            Type           = pdfconstant("Font"),
1450            Subtype        = pdfconstant("CIDFontType0"),
1451            BaseFont       = basefont,
1452            FontDescriptor = pdfreference(reserved),
1453            W              = pdfreference(pdfflushobject(widths)),
1454            CIDSystemInfo  = pdfdictionary {
1455                Registry   = pdfstring("Adobe"),
1456                Ordering   = pdfstring("Identity"),
1457                Supplement = 0,
1458            }
1459        }
1460        local descendants = pdfarray {
1461            pdfreference(pdfflushobject(child)),
1462        }
1463        local fontstream = pdfdictionary {
1464            Subtype = pdfconstant("CIDFontType0C"),
1465        }
1466        local descriptor = pdfdictionary {
1467            Type        = pdfconstant("FontDescriptor"),
1468            FontName    = basefont,
1469            Flags       = 4,
1470            FontBBox    = fontbbox,
1471            Ascent      = scale(ascender),
1472            Descent     = scale(descender),
1473            ItalicAngle = round(italicangle or 0),
1474            CapHeight   = scale(capheight),
1475            StemV       = scale(stemv),
1476            XHeight     = scale(xheight),
1477            CIDSet      = tocidset,
1478            FontFile3   = pdfreference(pdfflushstreamobject(fontdata,fontstream())),
1479            Metadata    = fontmeta and pdfreference(pdfflushstreamobject(fontmeta)) or nil,
1480        }
1481        local parent = pdfdictionary {
1482            Type            = pdfconstant("Font"),
1483            Subtype         = pdfconstant("Type0"),
1484            Encoding        = pdfconstant(details.properties.writingmode == "vertical" and "Identity-V" or "Identity-H"),
1485            BaseFont        = basefont,
1486            DescendantFonts = descendants,
1487            ToUnicode       = pdfreference(pdfflushstreamobject(tounicode)),
1488        }
1489        pdfflushobject(reserved,descriptor)
1490        pdfflushobject(object,parent)
1491    end
1492
1493    mainwriters["type1"] = function(details)
1494        -- We abuse the cff includer which is ok but for special cases like
1495        -- tfm -> pfb we don't have the right descriptions and scale so this
1496        -- is why we cheat elsewhere. Maybe I should just drop that kind of
1497        -- support and assume afm files to be present. See (*) above.
1498        local s = details.streams
1499        local m = details.rawdata.metadata
1500        if m then
1501            local h = s.horizontalheader
1502            local c = s.cffinfo
1503            local n = s.names
1504            h.ascender  = m.ascender      or h.ascender
1505            h.descender = m.descender     or h.descender
1506            n.copyright = m.copyright     or n.copyright
1507            n.family    = m.familyname    or n.familyname
1508            n.fullname  = m.fullname      or n.fullname
1509            n.fontname  = m.fontname      or n.fontname
1510            n.subfamily = m.subfamilyname or n.subfamilyname
1511            n.version   = m.version       or n.version
1512            setmetatableindex(h,m)
1513            setmetatableindex(c,m)
1514            setmetatableindex(n,m)
1515        end
1516        mainwriters["opentype"](details)
1517    end
1518
1519    do
1520
1521        -- The methods might become plugins.
1522
1523        local methods    = { }
1524
1525        local pdfimage   = lpdf.epdf.image
1526        local openpdf    = pdfimage.open
1527        local closepdf   = pdfimage.close
1528        local copypage   = pdfimage.copy
1529
1530        local embedimage = images.embed
1531
1532        local f_glyph      = formatters["G%d"]
1533        local f_char       = formatters["BT /V%d 1 Tf [<%04X>] TJ ET"]
1534        local f_width      = formatters["%.6N 0 d0"]
1535        local f_index      = formatters["I%d"]
1536        local f_image_xy   = formatters["%.6N 0 d0 1 0 0 1 %.6N %.6N cm /%s Do"]
1537        local f_image_c    = formatters["/%s Do"]
1538        local f_image_c_xy = formatters["%.6N 0 0 %.6N %.6N %.6N cm /%s Do"]
1539        local f_image_w    = formatters["%.6N 0 d0 %s"]
1540        local f_image_d    = formatters["%.6N 0 d0 1 0 0 1 0 %.6N cm /%s Do"]
1541        local f_stream     = formatters["%.6N 0 d0 %s"]
1542        local f_stream_c   = formatters["%.6N 0 0 0 0 0 d1 %s"] -- last four bbox
1543        local f_stream_d   = formatters["%.6N 0 d0 1 0 0 1 0 %.6N cm %s"]
1544     -- local f_stream_s   = formatters["%.6N 0 0 %.6N 0 0 cm /%s Do"]
1545
1546        -- A type 3 font has at most 256 characters and Acrobat also wants a zero slot
1547        -- to be filled. We can share a mandate zero slot character. We also need to
1548        -- make sure that we use bytes as index in the page stream as well as in the
1549        -- tounicode vector.
1550
1551        -- All this type three magic is a tribute to Don Knuth who uses bitmap fonts in
1552        -- his books.
1553
1554        local c_notdef = nil
1555        local r_notdef = nil
1556        local w_notdef = nil
1557        local fontbbox = nil
1558
1559        -- pk inclusion (not really tested but not really used either)
1560
1561        function methods.pk(filename)
1562            local pkfullname  = resolvers.findpk(filename,resolution)
1563            if not pkfullname or pkfullname == "" then
1564                return
1565            end
1566            local readers = fonts.handlers.tfm.readers
1567            local result  = readers.loadpk(pkfullname)
1568            local convert = readers.pktopdf
1569            if not result or result.error then
1570                return
1571            end
1572            local resolution  = 600
1573            local widthfactor = resolution / 72
1574            local scalefactor = 72 / resolution / 10
1575            local factor      = widthfactor / 65536
1576            local function pktopdf(glyph,data)
1577                return convert(glyph,data,factor) -- return pdfcode, width
1578            end
1579            return result.glyphs, scalefactor, pktopdf, false, false
1580        end
1581
1582        -- pdf inclusion
1583
1584        local used = setmetatableindex("table")
1585
1586     -- function lpdf.registerfontmethod(name,f)
1587     --     if f and not methods[name] then
1588     --         methods[name] = f
1589     --     end
1590     -- end
1591
1592        function methods.pdf(filename,details)
1593            local properties = details.properties
1594            local pdfshapes  = properties.indexdata[1]
1595            local pdfdoc     = openpdf(pdfshapes.filename)
1596            local xforms     = pdfdictionary()
1597            local nofglyphs  = 0
1598            if pdfdoc then
1599                local scale    = 10 * details.parameters.size/details.parameters.designsize
1600                local units    = details.parameters.units
1601                local factor   = units * bpfactor / scale
1602                local fixdepth = pdfshapes.fixdepth
1603                local useddoc  = used[pdfdoc]
1604                local function pdftopdf(glyph,data)
1605                    local width     = (data.width or 0) * factor
1606                    local image     = useddoc[glyph]
1607                    local reference = nil
1608                    if not image then
1609                        image        = embedimage(copypage(pdfdoc,glyph))
1610                        nofglyphs    = nofglyphs + 1
1611                        local name   = f_glyph(nofglyphs)
1612                        local stream = nil
1613                        if fixdepth then
1614                            local depth = data.depth  or 0
1615                            if depth ~= 0 then
1616                                local d     = data.dropin.descriptions[data.index]
1617                                local b     = d.boundingbox
1618                                local l     = b[1]
1619                                local r     = b[3]
1620                                local w     = r - l
1621                                local scale = w / d.width
1622                                local x     = l
1623                             -- local y     = - b[4] - b[2] - (d.depth or 0)
1624                                local y     = - (d.depth or 0)
1625                                local scale = w / (image.width * bpfactor)
1626                                stream = f_image_c_xy(scale,scale,x,y,name)
1627                            end
1628                        end
1629                        if not stream then
1630                            stream = f_image_c(name)
1631                        end
1632                        useddoc[glyph]           = image
1633                        image.embedded_name      = name
1634                        image.embedded_stream    = stream
1635                        image.embedded_reference = pdfreference(image.objnum)
1636                    end
1637                    xforms[image.embedded_name] = image.embedded_reference
1638                    return f_image_w(width,image.embedded_stream), width
1639                end
1640                local function closepdf()
1641                 -- closepdf(pdfdoc)
1642                end
1643                local function getresources()
1644                    return pdfdictionary { XObject = xforms }
1645                end
1646                return pdfshapes, 1/units, pdftopdf, closepdf, getresources
1647            end
1648        end
1649
1650        -- box inclusion (todo: we can share identical glyphs if needed but the gain
1651        -- is minimal especially when we use compact font mode)
1652
1653        function methods.box(filename,details)
1654            local properties = details.properties
1655            local boxes      = properties.indexdata[1]
1656            local xforms     = pdfdictionary()
1657            local nofglyphs  = 0
1658            local scale      = 10 * details.parameters.size/details.parameters.designsize
1659            local units      = details.parameters.units
1660            local function boxtopdf(image,data) -- image == glyph
1661                nofglyphs    = nofglyphs + 1
1662                local scale  = units / scale -- e.g. 1000 / 12
1663                local width  = (data.width or 0) * bpfactor * scale
1664                local depth  = - (data.depth or 0) * bpfactor * scale
1665                local name   = f_glyph(nofglyphs)
1666                local stream = f_image_c_xy(scale,scale,0,depth,name)
1667                image.embedded_name      = name
1668                image.embedded_stream    = stream
1669                image.embedded_reference = pdfreference(image.objnum)
1670                xforms[name] = image.embedded_reference
1671                return f_image_w(width,stream), width
1672            end
1673            local function wrapup()
1674            end
1675            local function getresources()
1676                return pdfdictionary { XObject = xforms }
1677            end
1678            return boxes, 1/units, boxtopdf, wrapup, getresources
1679        end
1680
1681        -- mps inclusion
1682
1683        local decompress      = gzip.decompress
1684        local metapost        = metapost
1685        local simplemprun     = metapost.simple
1686        local setparameterset = metapost.setparameterset
1687
1688        function methods.mps(filename,details)
1689            local properties = details.properties
1690            local parameters = details.parameters
1691            local mpshapes   = properties.indexdata[1] -- indexdata will change
1692            if mpshapes then
1693                local scale            = 10 * parameters.size/parameters.designsize
1694                local units            = mpshapes.units or parameters.units
1695                local factor           = units * bpfactor / scale
1696                local fixdepth         = mpshapes.fixdepth
1697                local usecolor         = mpshapes.usecolor
1698                local specification    = mpshapes.specification or { }
1699                local shapedefinitions = mpshapes.shapes
1700                local instance         = mpshapes.instance
1701                --
1702                simplemprun(instance,"begingroup;",true,true)
1703                setparameterset("mpsfont",specification)
1704                specification.scale        = specification.scale or scale
1705                specification.parameters   = parameters
1706                specification.properties   = properties
1707                specification.parentdata   = details.fontdata.parentdata
1708                -------------.characters   = details.fontdata.characters
1709                -------------.descriptions = details.fontdata.descriptions
1710                if shapedefinitions then
1711                    local preamble = shapedefinitions.parameters.preamble
1712                    if preamble then
1713                        simplemprun(instance,preamble,true,true)
1714                    end
1715                end
1716                --
1717                local function mpstopdf(mp,data)
1718                    local width = data.width
1719                    if decompress then
1720                        mp = decompress(mp)
1721                    end
1722                    local pdf   = simplemprun(instance,mp,true)
1723                    local width = width * factor
1724                    if usecolor then
1725                        return f_stream_c(width,pdf), width
1726                    elseif fixdepth then
1727                        local depth  = data.depth  or 0
1728                        local height = data.height or 0
1729                        if depth ~= 0 or height ~= 0 then
1730                            return f_stream_d(width,(-height-depth)*factor,pdf), width
1731                        end
1732                    end
1733                    return f_stream(width,pdf), width
1734                end
1735                --
1736                local function resetmps()
1737                    setparameterset("mpsfont")
1738                    simplemprun(instance,"endgroup;",true,true)
1739                    specification.parameters   = nil
1740                    specification.properties   = nil
1741                    specification.parentdata   = nil
1742                    -------------.characters   = nil
1743                    -------------.descriptions = nil
1744                end
1745                --
1746                local function getresources()
1747                    return lpdf.collectedresources {
1748                        serialize  = false,
1749                    }
1750                end
1751                --
1752                return mpshapes, 1/units, mpstopdf, resetmps, getresources
1753            end
1754        end
1755
1756        -- png inclusion
1757
1758        -- With d1 the image mask is used when given and obeys color. So it is needed for pure bw
1759        -- bitmap fonts, so here we really need d0.
1760        --
1761        -- Acrobat X pro only seems to see the image mask but other viewers are doing it ok. Acrobat
1762        -- reader crashes. We really need to add a notdef!
1763
1764        local files       = utilities.files
1765        local openfile    = files.open
1766        local closefile   = files.close
1767        local setposition = files.setposition
1768        local readstring  = files.readstring
1769
1770        function methods.png(filename,details)
1771            local properties = details.properties
1772            local pngshapes  = properties.indexdata[1]
1773            if pngshapes then
1774                local parameters = details.parameters
1775                local png        = properties.png
1776                local hash       = png.hash
1777                local xforms     = pdfdictionary()
1778                local nofglyphs  = 0
1779                local scale      = 10 * parameters.size/parameters.designsize
1780                local factor     = bpfactor / scale
1781             -- local units      = parameters.units -- / 1000
1782                local units      = 1000
1783                local filehandle = openfile(details.filename,true)
1784                local function pngtopdf(glyph,data)
1785                 -- local info    = graphics.identifiers.png(glyph.data,"string")
1786                    local offset  = glyph.o
1787                    local size    = glyph.s
1788                    local pdfdata = nil
1789                    if offset and size then
1790                        setposition(filehandle,offset)
1791                        local blob = readstring(filehandle,size)
1792                        local info = graphics.identifiers.png(blob,"string")
1793                        info.enforcecmyk = pngshapes.enforcecmyk
1794                        local image   = lpdf.injectors.png(info,"string")
1795                        local width   = (data.width or 0) * factor
1796                        if image then
1797                            embedimage(image)
1798                            nofglyphs     = nofglyphs + 1
1799                            local xoffset = (glyph.x or 0) / units
1800                            local yoffset = (glyph.y or 0) / units
1801                            local name    = f_glyph(nofglyphs)
1802                            xforms[name]  = pdfreference(image.objnum)
1803                            pdfdata       = f_image_xy(width,xoffset,yoffset,name)
1804                        end
1805                    end
1806                    return pdfdata or f_stream(width), width
1807                end
1808                local function closepng()
1809                    if filehandle then
1810                        closefile(filehandle)
1811                    end
1812                    pngshapes = nil
1813                end
1814                local function getresources()
1815                    return pdfdictionary { XObject = xforms }
1816                end
1817                return pngshapes, 1, pngtopdf, closepng, getresources
1818            end
1819        end
1820
1821        local function registercolors(hash)
1822            local kind     = hash.kind
1823            local data     = hash.data
1824            local direct   = lpdf.fonts.color_direct
1825            local indirect = lpdf.fonts.color_indirect
1826            if kind == "font" then
1827                return setmetatableindex(function(t,k)
1828                    local h = data[k]
1829                    local v = direct(h[1]/255,h[2]/255,h[3]/255)
1830                    t[k] = v
1831                    return v
1832                end)
1833            elseif kind == "user" then
1834                return setmetatableindex(function(t,k)
1835                    local list = data[k]
1836                    local v
1837                    if list then
1838                        local kind = list.kind
1839                        if kind == "values" then
1840                            local d = list.data
1841                            v = direct(d.r or 0,d.g or 0,d.b or 0)
1842                        elseif kind == "attributes" then
1843                            v = indirect(list.color,list.transparency)
1844                        else
1845                            v = false -- textcolor
1846                        end
1847                    else
1848                        v = false -- textcolor
1849                    end
1850                    t[k] = v
1851                    return v
1852                end)
1853            else
1854                return { }
1855            end
1856        end
1857
1858        -- we register way too much ... we can delay the t3 definition
1859
1860        local usedcharacters = lpdf.usedcharacters
1861
1862        function methods.color(filename,details)
1863            local colrshapes = details.properties.indexdata[1]
1864            local colrvalues = details.properties.indexdata[2]
1865            local usedfonts  = { }
1866            local function colrtopdf(description,data)
1867                -- descriptions by index
1868                local colorlist = description.colors
1869                if colorlist then
1870                    local dropdata     = data.dropin
1871                    local dropid       = dropdata.properties.id
1872                    local dropunits    = dropdata.parameters.units -- shared
1873                    local descriptions = dropdata.descriptions
1874                    local directcolors = registercolors(colrvalues)
1875                    local fontslots    = usedcharacters[dropid]
1876                    usedfonts[dropid]  = dropid
1877                    local w = description.width or 0
1878                    local s = #colorlist
1879                    local l = false
1880                    local t = { f_width(w) }
1881                    local n = 1
1882                    local d = #colrvalues
1883                    for i=1,s do
1884                        local entry = colorlist[i]
1885                        local class = entry.class or d
1886                        if class then
1887                            -- false is textcolor (we should actually go back)
1888                            local c = directcolors[class]
1889                            if c and l ~= c then
1890                                n = n + 1 ; t[n] = c
1891                                l = c
1892                            end
1893                        end
1894                        local e = descriptions[entry.slot]
1895                        if e then
1896                            n = n + 1 ; t[n] = f_char(dropid,fontslots[e.index])
1897                        end
1898                    end
1899                    -- we're not going to hash this ... could be done if needed (but who mixes different
1900                    -- color schemes ...)
1901                    t = concat(t," ")
1902                 -- print(t)
1903                    return t, w / dropunits
1904                end
1905            end
1906            local function getresources()
1907                return lpdf.collectedresources {
1908                    serialize  = false,
1909                    fonts      = usedfonts,
1910                    fontprefix = "V",
1911                }
1912            end
1913            return colrshapes, 1, colrtopdf, false, getresources
1914        end
1915
1916        mainwriters["type3"] = function(details)
1917            local properties   = details.properties
1918            local basefontname = properties.basefontname or details.basefontname or properties.name -- messy
1919            local askedmethod  = properties.method or "pk"
1920            local method       = methods[askedmethod] or methods.pk
1921            if not method then
1922                return
1923            end
1924            local glyphs, scalefactor, glyphtopdf, reset, getresources = method(basefontname,details)
1925            if not glyphs then
1926                return
1927            end
1928            local parameters  = details.parameters
1929            local object      = details.objectnumber
1930            local factor      = parameters.factor -- normally 1
1931            local fontmatrix  = pdfarray { scalefactor, 0, 0, scalefactor, 0, 0 }
1932            local indices,
1933                  include,
1934                  minindex,
1935                  maxindex    = collectindices(details.fontdata.characters,details.indices,details.used,details.hash)
1936            local widths      = pdfarray()
1937            local differences = pdfarray()
1938            local charprocs   = pdfdictionary()
1939            local basefont    = pdfconstant(basefontname)
1940            local d           = 0
1941            local w           = 0
1942            local forcenotdef = minindex > 0
1943            local lastindex   = -0xFF
1944            if forcenotdef then
1945                widths[0] = 0
1946                minindex  = 0
1947                lastindex = 0
1948                d         = 2
1949                if not c_notdef then
1950                    w_notdef = 0
1951                    c_notdef = pdfconstant(".notdef")
1952                    r_notdef = pdfreference(pdfflushstreamobject("0 0 d0"))
1953                end
1954                differences[1]       = w_notdef
1955                differences[2]       = c_notdef
1956                charprocs[".notdef"] = r_notdef
1957            end
1958
1959            for i=1,maxindex-minindex+1 do
1960                widths[i] = 0
1961            end
1962
1963            for index, data in sortedhash(indices) do
1964                local name  = f_index(index)
1965                local glyph = glyphs[include[index]]
1966                if glyph then
1967                    local stream, width = glyphtopdf(glyph,data)
1968                    if stream then
1969                        if index - 1 ~= lastindex then
1970                            d = d + 1 differences[d] = index
1971                        end
1972                        lastindex = index
1973                        d = d + 1 differences[d] = pdfconstant(name)
1974                        charprocs[name]          = pdfreference(pdfflushstreamobject(stream))
1975                        widths[index-minindex+1] = width
1976                    end
1977                else
1978                    report_fonts("missing glyph %i in type3 font %a",index,basefontname)
1979                end
1980            end
1981            if not fontbbox then
1982                -- The specification permits zero values and these are actually also more
1983                -- robust as then there are no assumptions and no accuracy is needed.
1984                fontbbox = pdfarray { 0, 0, 0, 0 }
1985            end
1986            local encoding = pdfdictionary {
1987                Type        = pdfconstant("Encoding"),
1988                Differences = differences,
1989            }
1990            local tounicode  = tounicodedictionary(details,indices,maxindex,basefontname,false)
1991            local resources  = getresources and getresources()
1992            if not resources or not next(resources) then
1993             -- resources = lpdf.procset(true)
1994                resources = nil
1995            end
1996            local descriptor = pdfdictionary {
1997                -- most is optional in type3
1998                Type        = pdfconstant("FontDescriptor"),
1999                FontName    = basefont,
2000                Flags       = 4,
2001                ItalicAngle = 0,
2002            }
2003            local parent = pdfdictionary {
2004                Type           = pdfconstant("Font"),
2005                Subtype        = pdfconstant("Type3"),
2006                Name           = basefont,
2007                FontBBox       = fontbbox,
2008                FontMatrix     = fontmatrix,
2009                CharProcs      = pdfreference(pdfflushobject(charprocs)),
2010                Encoding       = pdfreference(pdfflushobject(encoding)),
2011                FirstChar      = minindex,
2012                LastChar       = maxindex,
2013                Widths         = pdfreference(pdfflushobject(widths)),
2014                FontDescriptor = pdfreference(pdfflushobject(descriptor)),
2015                Resources      = resources,
2016                ToUnicode      = tounicode and pdfreference(pdfflushstreamobject(tounicode)),
2017            }
2018            pdfflushobject(object,parent)
2019            if reset then
2020                reset()
2021            end
2022        end
2023
2024    end
2025
2026end
2027
2028-- writingmode
2029
2030local usedfonts = fonts.hashes.identifiers -- for now
2031local noffonts  = 0
2032
2033-- The main injector.
2034
2035-- here we need to test for sharing otherwise we reserve too many
2036-- objects
2037
2038local getstreamhash  = fonts.handlers.otf.getstreamhash
2039local loadstreamdata = fonts.handlers.otf.loadstreamdata
2040
2041-- we can actually now number upwards (so not use fontid in /F)
2042
2043local objects = setmetatableindex(lpdf.usedfontobjects,function(t,k) -- defined in lpdf-lmt.lmt
2044    local v
2045    if type(k) == "number" then
2046        local h = getstreamhash(k)
2047        v = rawget(t,h)
2048        if not v then
2049            v = pdfreserveobject()
2050            t[h] = v
2051        end
2052        if trace_fonts then
2053            report_fonts("font id %i bound to hash %s and object %i",k,h,v)
2054        end
2055    else
2056        -- no problem as it can be svg only
2057     -- report_fonts("fatal error, hash %s asked but not used",k,h,v)
2058        v = pdfreserveobject()
2059        t[k] = v
2060    end
2061    return v
2062end)
2063
2064local n = 0
2065
2066local names = setmetatableindex(lpdf.usedfontnames,function(t,k) -- defined in lpdf-lmt.lmt
2067    local v
2068    if type(k) == "number" then
2069        local h = getstreamhash(k)
2070        v = rawget(t,h)
2071        if not v then
2072            n = n + 1
2073            v = n
2074            t[h] = v
2075        end
2076        if trace_fonts then
2077            report_fonts("font id %i bound to hash %s and name %i",k,h,n)
2078        end
2079    end
2080    t[k] = v
2081    return v
2082end)
2083
2084function lpdf.flushfonts()
2085
2086    local mainfonts = { }
2087
2088    statistics.starttiming(objects)
2089
2090    -- We loop over used characters (old approach, when we wanted to be equivalent wrt
2091    -- indices with luatex) but can also decide to use usedindices. However, we then
2092    -- don't have the id.
2093
2094    -- we can combine the two for loops .. todo
2095
2096    for fontid, used in sortedhash(lpdf.usedcharacters) do
2097
2098     -- for a bitmap we need a different hash unless we stick to a fixed high
2099     -- resolution which makes much sense
2100
2101        local hash = getstreamhash(fontid)
2102        if hash then
2103            local parent = mainfonts[hash]
2104-- print("before",fontid,hash,parent)
2105            if not parent then
2106                local fontdata   = usedfonts[fontid]
2107                local rawdata    = fontdata.shared and fontdata.shared.rawdata
2108                local resources  = fontdata.resources
2109                local properties = fontdata.properties -- writingmode and type3
2110                local parameters = fontdata.parameters -- used in type3
2111                if not rawdata then
2112                    -- we have a virtual font that loaded directly ... at some point i will
2113                    -- sort this out (in readanddefine we need to do a bit more) .. the problem
2114                    -- is that we have a hybrid font then
2115                    for xfontid, xfontdata in next, fonts.hashes.identifiers do
2116                        if fontid ~= xfontid then
2117                            local xhash = getstreamhash(xfontid)
2118                            if hash == xhash then
2119                                rawdata  = xfontdata.shared and xfontdata.shared.rawdata
2120                                if rawdata then
2121                                    resources  = xfontdata.resources
2122                                    properties = xfontdata.properties
2123                                    parameters = xfontdata.parameters
2124                                    break
2125                                end
2126                            end
2127                        end
2128                    end
2129                end
2130                if rawdata then
2131                    parent = {
2132                        hash         = hash,
2133                        fontdata     = fontdata,
2134                        filename     = resources.filename or properties.filename or "unset",
2135                        indices      = { },
2136                        used         = used,
2137                        rawdata      = rawdata,
2138                        properties   = properties, -- we assume consistency
2139                        parameters   = parameters, -- we assume consistency
2140                        streams      = { },
2141                        objectnumber = objects[hash],
2142                        basefontname = subsetname(properties.psname or properties.name or "unset"),
2143                        name         = names[hash],
2144                    }
2145                    mainfonts[hash] = parent
2146                    noffonts = noffonts + 1
2147                end
2148            end
2149-- print("after ",fontid,hash,parent)
2150            if parent then
2151                local indices = parent.indices
2152                for k, v in next, used do
2153                    indices[k] = v
2154                end
2155            end
2156        end
2157    end
2158
2159    -- this is no not yet ok for tfm / type 1 .. we need to move the nested blobs ourside the loop
2160
2161    for hash, details in sortedhash(mainfonts) do
2162        local filename = details.filename
2163        if next(details.indices) then
2164            local properties = details.properties
2165            local bitmap     = properties.usedbitmap
2166            local method     = properties.method -- will be pk | pdf | svg | ...
2167            if trace_fonts then
2168                if method then
2169                    report_fonts("embedding %a hashed as %a using method %a",filename,hash,method)
2170                else
2171                    report_fonts("embedding %a hashed as %a",filename,hash)
2172                end
2173            end
2174            if bitmap or method then
2175                local format = "type3"
2176                local writer = mainwriters[format]
2177                if trace_fonts then
2178                    report_fonts("using main writer %a",format)
2179                end
2180                writer(details)
2181            else
2182                local format = properties.format
2183                local writer = mainwriters[format]
2184                if not writer then
2185                    -- This will move to the tpk module where we will also deal
2186                    -- with bitmaps then.
2187                    local encoding, pfbfile, encfile = getmapentry(filename)
2188                    if trace_fonts then
2189                        report_fonts("file %a resolved to encoding %a and file %a",filename,encoding,pfbfile)
2190                    end
2191                    if encoding and pfbfile then
2192                        filename = pfbfile
2193                        format   = "type1"
2194                        --
2195                        -- another (temp) hack
2196                        local size   = details.fontdata.parameters.size
2197                        local factor = details.fontdata.parameters.factor
2198                        local descriptions = { }
2199                        local characters   = details.fontdata.characters
2200                        --
2201                        local names, _, _, metadata = fonts.constructors.handlers.pfb.loadvector(pfbfile)
2202                        local reverse  = table.swapped(names)
2203                        local vector   = encoding.vector
2204                        local indices  = details.indices
2205                        local remapped = { }
2206                        local factor   = bpfactor * size / 65536
2207                        for k, v in next, indices do
2208                            local name  = vector[k]
2209                            local index = reverse[name] or 0
2210                            local width = factor * (characters[k].width or 0)
2211                            descriptions[k] = {
2212                                width = width,
2213                                index = index,
2214                                name  = name,
2215                            }
2216                            remapped[index] = true
2217                        end
2218                        details.indices = remapped
2219                        --
2220                        details.rawdata.descriptions = descriptions
2221                        details.filename             = filename
2222                        details.rawdata.metadata     = { }
2223                        --
2224                        properties.filename = filename
2225                        properties.format   = format
2226                        writer = mainwriters[format]
2227                    end
2228                end
2229                if not writer then
2230                    local pfbfile = file.replacesuffix(filename,"pfb")
2231                    if encoding and pfbfile then
2232                        filename = pfbfile
2233                        format   = "type1"
2234                        --
2235                        -- another (temp) hack
2236                        local size   = details.fontdata.parameters.size
2237                        local factor = details.fontdata.parameters.factor
2238                        local descriptions = { }
2239                        local characters   = details.fontdata.characters
2240                        --
2241                        local names, _, _, metadata = fonts.constructors.handlers.pfb.loadvector(pfbfile)
2242                        local reverse  = table.swapped(names)
2243                        local vector   = encoding.vector
2244                        local indices  = details.indices
2245                        local remapped = { }
2246                        local factor   = bpfactor * size / 65536
2247                        for k, v in next, indices do
2248                            local name  = vector[k]
2249                            local index = reverse[name] or 0
2250                            local width = factor * (characters[k].width or 0)
2251                            descriptions[k] = {
2252                                width = width,
2253                                index = index,
2254                                name  = name,
2255                            }
2256                            remapped[index] = true
2257                        end
2258                        details.indices = remapped
2259                        --
2260                        details.rawdata.descriptions = descriptions
2261                        details.filename             = filename
2262                        details.rawdata.metadata     = { }
2263                        --
2264                        properties.filename = filename
2265                        properties.format   = format
2266                        writer = mainwriters[format]
2267                    end
2268                end
2269                if writer then
2270                    if trace_fonts then
2271                        report_fonts("using main writer %a",format)
2272                    end
2273                    -- better move this test to the writers .. cleaner
2274                    local streams = loadstreamdata(details.fontdata)
2275                    if streams and streams.fontheader and streams.names then
2276                        details.streams = streams
2277                        writer(details)
2278                        details.streams = { }
2279                    elseif trace_fonts then
2280                        -- can be ok for e.g. emoji
2281                        report_fonts("no streams in %a",filename)
2282                    end
2283                    -- free some  memory
2284                else -- if trace_fonts then
2285                    report_fonts("no %a writer for %a",format,filename)
2286                end
2287            end
2288        else -- not problem for svg ...
2289         -- report_fonts("no indices for %a",filename)
2290        end
2291        if trace_fonts then
2292            local indices = details.indices
2293            if indices and next(indices) then
2294                report_fonts("embedded indices: % t",table.sortedkeys(details.indices))
2295            end
2296        end
2297        mainfonts[details.hash] = false -- done
2298    end
2299
2300    statistics.stoptiming(objects)
2301
2302end
2303
2304statistics.register("font embedding time",function()
2305    if noffonts > 0 then
2306        return format("%s seconds, %s fonts", statistics.elapsedtime(objects),noffonts)
2307    end
2308end)
2309
2310-- this is temporary
2311
2312function lpdf.getfontobjectnumber(k)
2313    return objects[k]
2314end
2315
2316function lpdf.getfontname(k)
2317    return names[k]
2318end
2319
2320lpdf.registerdocumentfinalizer(lpdf.flushfonts,1,"wrapping up fonts")
2321