font-ocm.lua /size: 31 Kb    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['font-ocm'] = {
2    version   = 1.001,
3    comment   = "companion to font-ini.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9if not context then
10    return
11elseif CONTEXTLMTXMODE and CONTEXTLMTXMODE > 0 then
12    return
13else
14 -- Maybe I'll also make a generic variant but for now I just test this in
15 -- MkIV. After all, color fonts are not that much used (and generic is for
16 -- serious looking articles and books and not for fancy documents using
17 -- emoji.) Below is a quick and dirty implementation. Also, it looks like
18 -- these features were never used outside context anyway (in spite of being
19 -- in generic).
20end
21
22local tostring, tonumber, next = tostring, tonumber, next
23local round, max = math.round, math.round
24local sortedkeys, sortedhash, concat = table.sortedkeys, table.sortedhash, table.concat
25local setmetatableindex = table.setmetatableindex
26local formatters   = string.formatters
27
28local otf         = fonts.handlers.otf
29local otfregister = otf.features.register
30local bpfactor    = number.dimenfactors.bp
31local typethree   = { }
32
33callback.register("provide_charproc_data",function(action,f,...)
34    local registered = typethree[f]
35    if registered then
36        return registered(action,f,...)
37    else
38        return 0, 0 -- this will also disable further calls
39    end
40end)
41
42local defaults = {
43    function() return 0, 0 end,
44    function() return 0, 0 end,
45    function() return 0.001, "" end,
46}
47
48local function registeractions(t)
49    return {
50        t.preroll or defaults[1],
51        t.collect or defaults[2],
52        t.wrapup  or defaults[3],
53    }
54end
55
56local function registertypethreeresource(specification,n,o)
57    specification.usedobjects["X"..n] = lpdf.reference(o)
58end
59
60local function registertypethreefont(specification,n,o)
61    specification.usedfonts["F"..n] = lpdf.reference(o)
62end
63
64local function typethreeresources(specification)
65    local usedobjects = specification.usedobjects
66    local usedfonts   = specification.usedfonts
67    local resources   = { }
68    if next(usedobjects) then
69        resources[#resources+1] = "/XObject << " .. usedobjects() .. " >>"
70    end
71    if next(usedfonts) then
72        resources[#resources+1] = "/Font << " .. usedfonts() .. " >>"
73    end
74 -- resources[#resources+1] = lpdf.collectedresources()
75    specification.usedfonts   = nil
76    specification.usedobjects = nil
77    return concat(resources, " ")
78end
79
80local function registerfont(specification,actions)
81    specification.usedfonts   = lpdf.dictionary()
82    specification.usedobjects = lpdf.dictionary()
83    typethree[specification.id] = function(action,f,c)
84        return actions[action](specification,f,c)
85    end
86end
87
88fonts.handlers.typethree = {
89    register = function(id,handler)
90        -- needed for manual
91        if not typethree[id] then
92            logs.report("fonts","low level Type3 handler registered for font with id %i",id)
93            typethree[id] = handler
94        end
95    end
96}
97
98local initializeoverlay  do
99
100    local f_color         = formatters["%.3f %.3f %.3f rg"]
101    local f_gray          = formatters["%.3f g"]
102    local sharedpalettes  = { }
103    local colors          = attributes.list[attributes.private('color')] or { }
104    local transparencies  = attributes.list[attributes.private('transparency')] or { }
105
106    function otf.registerpalette(name,values)
107        sharedpalettes[name] = values
108        local color          = lpdf.color
109        local transparency   = lpdf.transparency
110        local register       = colors.register
111        for i=1,#values do
112            local v = values[i]
113            if v == "textcolor" then
114                values[i] = false
115            else
116                local c = nil
117                local t = nil
118                if type(v) == "table" then
119                    c = register(name,"rgb",
120                        max(round((v.r or 0)*255),255)/255,
121                        max(round((v.g or 0)*255),255)/255,
122                        max(round((v.b or 0)*255),255)/255
123                    )
124                else
125                    c = colors[v]
126                    t = transparencies[v]
127                end
128                if c and t then
129                    values[i] = color(1,c) .. " " .. transparency(t)
130                elseif c then
131                    values[i] = color(1,c)
132                elseif t then
133                    values[i] = color(1,t)
134                end
135            end
136        end
137    end
138
139    local function convert(t,k)
140        local v = { }
141        for i=1,#k do
142            local p = k[i]
143            local r, g, b = p[1], p[2], p[3]
144            if r == g and g == b then
145                v[i] = f_gray(r/255)
146            else
147                v[i] = f_color(r/255,g/255,b/255)
148            end
149        end
150        t[k] = v
151        return v
152    end
153
154    -- This is by no means watertight (the id mess) especially because we
155    -- don't know it yet. Instead we can just assemble here and avoid the
156    -- box approach. I might do that (so then we need to pass fonts and
157    -- extra resource entries.
158
159    local f_stream  = formatters["%s 0 d0 %s 0 0 %s 0 %s cm /X%i Do"]
160    local fontorder = 0
161    local actions   = registeractions {
162
163        preroll = function(specification,f,c)
164            local data        = specification.delegated[c]
165            local colorlist   = data.colorlist
166            local colorvalues = specification.colorvalues
167            local default     = specification.default
168            local mainid      = specification.mainid
169            local t = { "\\typethreefont{", mainid, "}" }
170            local n = 3
171            local l = nil
172            local m = #colorlist
173            for i=1,m do
174                local entry = colorlist[i]
175                local v = colorvalues[entry.class] or default
176                if v and l ~= v then
177                    n = n + 1 ; t[n] = "\\typethreecode{"
178                    n = n + 1 ; t[n] = v
179                    n = n + 1 ; t[n] = "}"
180                    l = v
181                end
182                if i < m then
183                    n = n + 1 ; t[n] = "\\typethreechar{"
184                else
185                    n = n + 1 ; t[n] = "\\typethreelast{"
186                end
187                n = n + 1 ; t[n] = entry.slot
188                n = n + 1 ; t[n] = "}"
189            end
190            token.set_macro("typethreemacro",concat(t))
191            tex.runlocal("typethreetoks")
192            registertypethreeresource(specification,c,tex.saveboxresource(0,nil,lpdf.collectedresources(),true))
193         -- registertypethreefont(specification,mainid,lpdf.reference(lpdf.getfontobjnumber(mainid)))
194            return 0, 0
195        end,
196
197        collect = function(specification,f,c)
198            local parameters = specification.parameters
199            local data       = specification.delegated[c]
200            local factor     = parameters.hfactor
201            local units      = parameters.units
202            local width      = (data.width or 0) / factor
203            local scale      = 100
204            local factor     = units * bpfactor -- / scale
205            local depth      = (data.depth or 0)*factor
206            local shift      = - depth / (10*units/1000)
207            local object     = pdf.immediateobj("stream",f_stream(width,scale,scale,shift,c))
208            return object, width
209        end,
210
211        wrapup = function(specification,f)
212            return 0.001, typethreeresources(specification)
213        end,
214
215    }
216
217    local function register(specification)
218        registerfont(specification,actions)
219    end
220
221    initializeoverlay = function(tfmdata,kind,value)
222        if value then
223            local resources = tfmdata.resources
224            local palettes  = resources.colorpalettes
225            if palettes then
226                local converted = resources.converted
227                if not converted then
228                    converted = setmetatableindex(convert)
229                    resources.converted = converted
230                end
231                local colorvalues = sharedpalettes[value]
232                local default     = false -- so the text color (bad for icon overloads)
233                if colorvalues then
234                    default = colorvalues[#colorvalues]
235                else
236                    colorvalues = converted[palettes[tonumber(value) or 1] or palettes[1]] or { }
237                end
238                local classes = #colorvalues
239                if classes == 0 then
240                    return
241                end
242                --
243                local characters   = tfmdata.characters
244                local descriptions = tfmdata.descriptions
245                local properties   = tfmdata.properties
246                local parameters   = tfmdata.parameters
247                --
248                properties.virtualized = true
249                --
250                local delegated = { }
251                local index     = 0
252                local fonts     = tfmdata.fonts or { }
253                local fontindex = #fonts + 1
254                tfmdata.fonts   = fonts
255
256                local function flush()
257                    if index > 0 then
258                        fontorder = fontorder + 1
259                        local f = {
260                            characters = delegated,
261                            parameters = parameters,
262                            tounicode  = true,
263                            format     = "type3",
264                            name       = "InternalTypeThreeFont" , -- .. fontorder,
265                            psname     = "none",
266                        }
267                        fonts[fontindex] = {
268                            id          = font.define(f),
269                            delegated   = delegated,
270                            parameters  = parameters,
271                            colorvalues = colorvalues,
272                            default     = default,
273                        }
274                    end
275                    fontindex = fontindex + 1
276                    index     = 0
277                    delegated = { }
278                end
279
280                for unicode, character in sortedhash(characters) do
281                    local description = descriptions[unicode]
282                    if description then
283                        local colorlist = description.colors
284                        if colorlist then
285                            if index == 255 then
286                                flush()
287                            end
288                            index = index + 1
289                            delegated[index] = {
290                                width     = character.width,
291                                height    = character.height,
292                                depth     = character.depth,
293                                tounicode = character.tounicode,
294                                colorlist = colorlist,
295                            }
296                            character.commands = {
297                                { "slot", fontindex, index },
298                            }
299                        end
300                    end
301                end
302
303                flush()
304                local mainid = font.nextid()
305                for i=1,#fonts do
306                    local f = fonts[i]
307                    if f.delegated then
308                        f.mainid = mainid
309                        register(f)
310                    end
311                end
312
313                return true
314            end
315        end
316    end
317
318    otfregister {
319        name         = "colr",
320        description  = "color glyphs",
321        manipulators = {
322            base = initializeoverlay,
323            node = initializeoverlay,
324        }
325    }
326
327end
328
329do
330
331    local nofstreams = 0
332    local f_name     = formatters[ [[pdf-glyph-%05i]] ]
333    local f_used     = context and formatters[ [[original:///%s]] ] or formatters[ [[%s]] ]
334    local hashed     = { }
335    local cache      = { }
336
337    local openpdf = pdfe.new
338
339    function otf.storepdfdata(pdf)
340        if pdf then
341            local done = hashed[pdf]
342            if not done then
343                nofstreams = nofstreams + 1
344                local f = f_name(nofstreams)
345                local n = openpdf(pdf,#pdf,f)
346                done = f_used(n)
347                hashed[pdf] = done
348            end
349            return done
350        end
351    end
352
353end
354
355local pdftovirtual  do
356
357    local f_stream  = formatters["%s 0 d0 %s 0 0 %s %s %s cm /X%i Do"]
358    local fontorder = 0
359    local shared    = { }
360    local actions   = registeractions {
361
362        preroll = function(specification,f,c)
363            return 0, 0
364        end,
365
366        collect = function(specification,f,c)
367            local parameters = specification.parameters
368            local data       = specification.delegated[c]
369            local desdata    = data.desdata
370            local pdfdata    = data.pdfdata
371            local width      = desdata.width or 0
372            local height     = desdata.height or 0
373            local depth      = desdata.depth or 0
374            local factor     = parameters.hfactor
375            local units      = parameters.units
376            local typ        = type(pdfdata)
377
378            local dx         = 0
379            local dy         = 0
380            local scale      = 1
381
382            if typ == "table" then
383                data  = pdfdata.data
384                dx    = pdfdata.x or pdfdata.dx or 0
385                dy    = pdfdata.y or pdfdata.dy or 0
386                scale = pdfdata.scale or 1
387            elseif typ == "string" then
388                data = pdfdata
389                dx   = 0
390                dy   = 0
391            else
392                return 0, 0
393            end
394
395            if not data then
396                return 0, 0
397            end
398
399            local name  = otf.storepdfdata(data)
400            local xform = shared[name]
401
402            if not xform then
403                xform = images.embed(images.create { filename = name })
404                shared[name] = xform
405            end
406
407            registertypethreeresource(specification,c,xform.objnum)
408
409            scale = scale * (width / (xform.width * bpfactor))
410            dy = - depth + dy
411-- png .. no time to figure it out now
412-- dx = 0
413-- dy = 0
414            local object = pdf.immediateobj("stream",f_stream(width,scale,scale,dx,dy,c)), width
415
416            return object, width
417        end,
418
419        wrapup = function(specification,f)
420            return 1/specification.parameters.units, typethreeresources(specification)
421        end,
422
423    }
424
425    local function register(specification)
426        registerfont(specification,actions)
427    end
428
429    pdftovirtual = function(tfmdata,pdfshapes,kind) -- kind = png|svg
430        if not tfmdata or not pdfshapes or not kind then
431            return
432        end
433        --
434        local characters   = tfmdata.characters
435        local descriptions = tfmdata.descriptions
436        local properties   = tfmdata.properties
437        local parameters   = tfmdata.parameters
438        local hfactor      = parameters.hfactor
439        --
440        properties.virtualized = true
441        --
442        local storepdfdata  = otf.storepdfdata
443        --
444        local delegated = { }
445        local index     = 0
446        local fonts     = tfmdata.fonts or { }
447        local fontindex = #fonts + 1
448        tfmdata.fonts   = fonts
449
450        local function flush()
451            if index > 0 then
452                fontorder = fontorder + 1
453                local f = {
454                    characters = delegated,
455                    parameters = parameters,
456                    tounicode  = true,
457                    format     = "type3",
458                    name       = "InternalTypeThreeFont" .. fontorder,
459                    psname     = "none",
460                    size       = parameters.size,
461                }
462                fonts[fontindex] = {
463                    id         = font.define(f),
464                    delegated  = delegated,
465                    parameters = parameters,
466                }
467            end
468            fontindex = fontindex + 1
469            index     = 0
470            delegated = { }
471        end
472
473        for unicode, character in sortedhash(characters) do
474            local idx = character.index
475            if idx then
476                local pdfdata     = pdfshapes[idx]
477                local description = descriptions[unicode]
478                if pdfdata and description then
479                    if index == 255 then
480                        flush()
481                    end
482                    index = index + 1
483                    delegated[index] = {
484                        desdata   = description,
485                        width     = character.width,
486                        height    = character.width,
487                        depth     = character.width,
488                        tounicode = character.tounicode,
489                        pdfdata   = pdfdata,
490                    }
491                    character.commands = {
492                        { "slot", fontindex, index },
493                    }
494                end
495            end
496        end
497        --
498        flush()
499        local mainid = font.nextid()
500        for i=1,#fonts do
501            local f = fonts[i]
502            if f.delegated then
503                f.mainid = mainid
504                register(f)
505            end
506        end
507        --
508    end
509
510end
511
512local initializesvg  do
513
514    local otfsvg   = otf.svg or { }
515    otf.svg        = otfsvg
516    otf.svgenabled = true
517
518    local report_svg = logs.reporter("fonts","svg conversion")
519
520    local loaddata   = io.loaddata
521    local savedata   = io.savedata
522    local remove     = os.remove
523
524    local xmlconvert = xml.convert
525    local xmlfirst   = xml.first
526
527    function otfsvg.filterglyph(entry,index)
528        local d = entry.data
529        if gzip.compressed(d) then
530            d = gzip.decompress(d) or d
531        end
532        local svg  = xmlconvert(d)
533        local root = svg and xmlfirst(svg,"/svg[@id='glyph"..index.."']")
534        local data = root and tostring(root)
535        return data
536    end
537
538    local runner = sandbox and sandbox.registerrunner {
539        name     = "otfsvg",
540        program  = "inkscape",
541        method   = "pipeto",
542        template = "--export-area-drawing --shell > temp-otf-svg-shape.log",
543        reporter = report_svg,
544    }
545
546    if not runner then
547        --
548        -- poor mans variant for generic:
549        --
550        runner = function()
551            return io.popen("inkscape --export-area-drawing --shell > temp-otf-svg-shape.log","w")
552        end
553    end
554
555    -- There are svg out there with bad viewBox specifications where shapes lay outside that area,
556    -- but trying to correct that didn't work out well enough so I discarded that code. BTW, we
557    -- decouple the inskape run and the loading run because inkscape is working in the background
558    -- in the files so we need to have unique files.
559    --
560    -- Because a generic setup can be flawed we need to catch bad inkscape runs which add a bit of
561    -- ugly overhead. Bah.
562    --
563    -- In the long run this method is a dead end because we cannot rely on command line arguments
564    -- etc to be upward compatible (so no real batch tool).
565
566    local new = nil
567
568    local function inkscapeformat(suffix)
569        if new == nil then
570            new = os.resultof("inkscape --version") or ""
571            new = new == "" or not find(new,"Inkscape%s*0")
572        end
573        return new and "filename" or suffix
574    end
575
576    function otfsvg.topdf(svgshapes,tfmdata)
577        local pdfshapes = { }
578        local inkscape  = runner()
579        if inkscape then
580         -- local indices      = fonts.getindices(tfmdata)
581            local descriptions = tfmdata.descriptions
582            local nofshapes    = #svgshapes
583            local s_format     = inkscapeformat("pdf") -- hack, this will go away when is >= 0 is everywhere
584            local f_svgfile    = formatters["temp-otf-svg-shape-%i.svg"]
585            local f_pdffile    = formatters["temp-otf-svg-shape-%i.pdf"]
586            local f_convert    = formatters[new and "file-open:%s; export-%s:%s; export-do\n" or "%s --export-%s=%s\n"]
587            local filterglyph  = otfsvg.filterglyph
588            local nofdone      = 0
589            local processed    = { }
590            report_svg("processing %i svg containers",nofshapes)
591            statistics.starttiming()
592            for i=1,nofshapes do
593                local entry = svgshapes[i]
594                for index=entry.first,entry.last do
595                    local data = filterglyph(entry,index)
596                    if data and data ~= "" then
597                        local svgfile = f_svgfile(index)
598                        local pdffile = f_pdffile(index)
599                        savedata(svgfile,data)
600                        inkscape:write(f_convert(svgfile,s_format,pdffile))
601                        processed[index] = true
602                        nofdone = nofdone + 1
603                        if nofdone % 25 == 0 then
604                            report_svg("%i shapes submitted",nofdone)
605                        end
606                    end
607                end
608            end
609            if nofdone % 25 ~= 0 then
610                report_svg("%i shapes submitted",nofdone)
611            end
612            report_svg("processing can be going on for a while")
613            inkscape:write("quit\n")
614            inkscape:close()
615            report_svg("processing %i pdf results",nofshapes)
616            for index in next, processed do
617                local svgfile = f_svgfile(index)
618                local pdffile = f_pdffile(index)
619             -- local fntdata = descriptions[indices[index]]
620             -- local bounds  = fntdata and fntdata.boundingbox
621                local pdfdata = loaddata(pdffile)
622                if pdfdata and pdfdata ~= "" then
623                    pdfshapes[index] = {
624                        data = pdfdata,
625                     -- x    = bounds and bounds[1] or 0,
626                     -- y    = bounds and bounds[2] or 0,
627                    }
628                end
629                remove(svgfile)
630                remove(pdffile)
631            end
632            local characters = tfmdata.characters
633            for k, v in next, characters do
634                local d = descriptions[k]
635                local i = d.index
636                if i then
637                    local p = pdfshapes[i]
638                    if p then
639                        local w = d.width
640                        local l = d.boundingbox[1]
641                        local r = d.boundingbox[3]
642                        p.scale = (r - l) / w
643                        p.x     = l
644                    end
645                end
646            end
647            if not next(pdfshapes) then
648                report_svg("there are no converted shapes, fix your setup")
649            end
650            statistics.stoptiming()
651            if statistics.elapsedseconds then
652                report_svg("svg conversion time %s",statistics.elapsedseconds() or "-")
653            end
654        end
655        return pdfshapes
656    end
657
658    initializesvg = function(tfmdata,kind,value) -- hm, always value
659        if value and otf.svgenabled then
660            local svg       = tfmdata.properties.svg
661            local hash      = svg and svg.hash
662            local timestamp = svg and svg.timestamp
663            if not hash then
664                return
665            end
666            local pdffile   = containers.read(otf.pdfcache,hash)
667            local pdfshapes = pdffile and pdffile.pdfshapes
668            if not pdfshapes or pdffile.timestamp ~= timestamp or not next(pdfshapes) then
669                -- the next test tries to catch errors in generic usage but of course can result
670                -- in running again and again
671                local svgfile   = containers.read(otf.svgcache,hash)
672                local svgshapes = svgfile and svgfile.svgshapes
673                pdfshapes = svgshapes and otfsvg.topdf(svgshapes,tfmdata,otf.pdfcache.writable,hash) or { }
674                containers.write(otf.pdfcache, hash, {
675                    pdfshapes = pdfshapes,
676                    timestamp = timestamp,
677                })
678            end
679            pdftovirtual(tfmdata,pdfshapes,"svg")
680            return true
681        end
682    end
683
684    otfregister {
685        name         = "svg",
686        description  = "svg glyphs",
687        manipulators = {
688            base = initializesvg,
689            node = initializesvg,
690        }
691    }
692
693end
694
695-- This can be done differently e.g. with ffi and gm and we can share code anway. Using
696-- batchmode in gm is not faster and as it accumulates we would need to flush all
697-- individual shapes. But ... in context lmtx (and maybe the backport) we will use
698-- a different and more efficient method anyway. I'm still wondering if I should
699-- keep color code in generic. Maybe it should be optional.
700
701local initializepng  do
702
703    -- Alternatively we can create a single pdf file with -adjoin and then pick up pages from
704    -- that file but creating thousands of small files is no fun either.
705
706    local otfpng     = otf.png or { }
707    otf.png          = otfpng
708    otf.pngenabled   = true
709    local report_png = logs.reporter("fonts","png conversion")
710    local loaddata   = io.loaddata
711    local savedata   = io.savedata
712    local remove     = os.remove
713    local texhack    = [[\startTEXpage\externalfigure[temp-otf-png-shape.png]\stopTEXpage]]
714    local runner     = false
715    local method     = "gm"
716
717    local function initialize(v)
718        if v == "lmtx" then
719            report_png("using lmtx converter, slow but okay")
720            runner = sandbox.registerrunner {
721             -- reporter = report_png,
722                name     = "otfpng",
723                program  = "mtxrun --script context",
724                template = "--once --batch --silent temp-otf-png-shape.tex > temp-otf-svg-shape.log",
725            }
726            method = v
727        elseif v == "mutool" then
728            report_png("using lmtx converter, no mask, black background")
729            runner = sandbox.registerrunner {
730             -- reporter = report_png,
731                name     = "otfpng",
732                program  = "mutool",
733                template = "convert -o temp-otf-png-shape.pdf temp-otf-png-shape.png",
734            }
735            method = v
736        else
737            report_png("using lmtx converter, no mask, white background")
738            runner = sandbox.registerrunner {
739             -- reporter = report_png,
740                name     = "otfpng",
741                program  = "gm",
742                template = "convert -quality 100 temp-otf-png-shape.png temp-otf-png-shape.pdf > temp-otf-svg-shape.log",
743            }
744            method = "gm"
745        end
746        return runner
747    end
748
749    directives.register("backend.otfpng.method",initialize)
750
751    local files       = utilities.files
752    local openfile    = files.open
753    local closefile   = files.close
754    local setposition = files.setposition
755    local readstring  = files.readstring
756
757    function otfpng.topdf(pngshapes,filename)
758        if pngshapes and filename then
759            local pdfshapes  = { }
760            local pngfile    = "temp-otf-png-shape.png"
761            local pdffile    = "temp-otf-png-shape.pdf"
762            local logfile    = "temp-otf-png-shape.log"
763            local texfile    = "temp-otf-png-shape.tex"
764            local tucfile    = "temp-otf-png-shape.tuc"
765            local nofdone    = 0
766            local indices    = sortedkeys(pngshapes) -- can be sparse
767            local nofindices = #indices
768            report_png("processing %i png containers",nofindices)
769            statistics.starttiming()
770            local filehandle = openfile(filename)
771            savedata(texfile,texhack) -- not always used but who cares
772            if not runner then
773                initialize()
774            end
775            for i=1,nofindices do
776                local index  = indices[i]
777                local entry  = pngshapes[index]
778             -- local data   = entry.data -- or placeholder
779                local offset = entry.o
780                local size   = entry.s
781                local x      = entry.x
782                local y      = entry.y
783                local data   = nil
784                if offset and size then
785                    setposition(filehandle,offset)
786                    data = readstring(filehandle,size)
787                    savedata(pngfile,data)
788                    runner()
789                    data = loaddata(pdffile)
790                end
791                pdfshapes[index] = {
792--                     x    = x ~= 0 and x or nil,
793--                     y    = y ~= 0 and y or nil,
794                    data = data,
795                }
796                nofdone = nofdone + 1
797                if nofdone % 100 == 0 then
798                    report_png("%i shapes processed",nofdone)
799                end
800            end
801            closefile(filehandle)
802            report_png("processing %i pdf results",nofindices)
803            remove(pngfile)
804            remove(pdffile)
805            remove(logfile)
806            remove(texfile)
807            remove(tucfile)
808            statistics.stoptiming()
809            if statistics.elapsedseconds then
810                report_png("png conversion time %s",statistics.elapsedseconds() or "-")
811            end
812            return pdfshapes
813        end
814    end
815
816    initializepng = function(tfmdata,kind,value) -- hm, always value
817        if value and otf.pngenabled then
818            local png       = tfmdata.properties.png
819            local hash      = png and png.hash
820            local timestamp = png and png.timestamp
821            if not hash then
822                return
823            end
824            local pdffile   = containers.read(otf.pdfcache,hash)
825            local pdfshapes = pdffile and pdffile.pdfshapes
826            if not pdfshapes or pdffile.timestamp ~= timestamp or pdffile.timestamp ~= method then
827                local pngfile   = containers.read(otf.pngcache,hash)
828                local filename  = tfmdata.resources.filename
829                local pngshapes = pngfile and pngfile.pngshapes
830                pdfshapes = pngshapes and otfpng.topdf(pngshapes,filename) or { }
831                containers.write(otf.pdfcache, hash, {
832                    pdfshapes = pdfshapes,
833                    timestamp = timestamp,
834                    method    = method,
835                })
836            end
837            --
838            pdftovirtual(tfmdata,pdfshapes,"png")
839            return true
840        end
841    end
842
843    otfregister {
844        name         = "sbix",
845        description  = "sbix glyphs",
846        manipulators = {
847            base = initializepng,
848            node = initializepng,
849        }
850    }
851
852    otfregister {
853        name         = "cblc",
854        description  = "cblc glyphs",
855        manipulators = {
856            base = initializepng,
857            node = initializepng,
858        }
859    }
860
861end
862
863do
864
865    local function initializecolor(tfmdata,kind,value)
866        if value == "auto" then
867            return
868                initializeoverlay(tfmdata,kind,value) or
869                initializesvg    (tfmdata,kind,value) or
870                initializepng    (tfmdata,kind,value)
871        elseif value == "overlay" then
872            return initializeoverlay(tfmdata,kind,value)
873        elseif value == "svg" then
874            return initializesvg(tfmdata,kind,value)
875        elseif value == "png" or value == "bitmap" then
876            return initializepng(tfmdata,kind,value)
877        else
878            -- forget about it
879        end
880    end
881
882    otfregister {
883        name         = "color",
884        description  = "color glyphs",
885        manipulators = {
886            base = initializecolor,
887            node = initializecolor,
888        }
889    }
890
891end
892
893-- Old stuff:
894
895do
896
897    local startactualtext = nil
898    local stopactualtext  = nil
899
900    function otf.getactualtext(s)
901        if not startactualtext then
902            startactualtext = backends.codeinjections.startunicodetoactualtextdirect
903            stopactualtext  = backends.codeinjections.stopunicodetoactualtextdirect
904        end
905        return startactualtext(s), stopactualtext()
906    end
907
908end
909
910