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