font-one.lua /size: 30 Kb    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['font-one'] = {
2    version   = 1.001,
3    optimize  = true,
4    comment   = "companion to font-ini.mkiv",
5    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
6    copyright = "PRAGMA ADE / ConTeXt Development Team",
7    license   = "see context related readme files"
8}
9
10--[[ldx--
11<p>Some code may look a bit obscure but this has to do with the fact that we also use
12this code for testing and much code evolved in the transition from <l n='tfm'/> to
13<l n='afm'/> to <l n='otf'/>.</p>
14
15<p>The following code still has traces of intermediate font support where we handles
16font encodings. Eventually font encoding went away but we kept some code around in
17other modules.</p>
18
19<p>This version implements a node mode approach so that users can also more easily
20add features.</p>
21--ldx]]--
22
23local fonts, logs, trackers, containers, resolvers = fonts, logs, trackers, containers, resolvers
24
25local next, type, tonumber, rawget = next, type, tonumber, rawget
26local match, gsub = string.match, string.gsub
27local abs = math.abs
28local P, S, R, Cmt, C, Ct, Cs, Carg = lpeg.P, lpeg.S, lpeg.R, lpeg.Cmt, lpeg.C, lpeg.Ct, lpeg.Cs, lpeg.Carg
29local lpegmatch, patterns = lpeg.match, lpeg.patterns
30local sortedhash = table.sortedhash
31
32local trace_features      = false  trackers.register("afm.features",   function(v) trace_features = v end)
33local trace_indexing      = false  trackers.register("afm.indexing",   function(v) trace_indexing = v end)
34local trace_loading       = false  trackers.register("afm.loading",    function(v) trace_loading  = v end)
35local trace_defining      = false  trackers.register("fonts.defining", function(v) trace_defining = v end)
36
37local report_afm          = logs.reporter("fonts","afm loading")
38
39local setmetatableindex   = table.setmetatableindex
40local derivetable         = table.derive
41
42local findbinfile         = resolvers.findbinfile
43
44local privateoffset       = fonts.constructors and fonts.constructors.privateoffset or 0xF0000 -- 0x10FFFF
45
46local definers            = fonts.definers
47local readers             = fonts.readers
48local constructors        = fonts.constructors
49
50local afm                 = constructors.handlers.afm
51local pfb                 = constructors.handlers.pfb
52local otf                 = fonts.handlers.otf
53
54local otfreaders          = otf.readers
55local otfenhancers        = otf.enhancers
56
57local afmfeatures         = constructors.features.afm
58local registerafmfeature  = afmfeatures.register
59
60local afmenhancers        = constructors.enhancers.afm
61local registerafmenhancer = afmenhancers.register
62
63afm.version               = 1.513 -- incrementing this number one up will force a re-cache
64afm.cache                 = containers.define("fonts", "one", afm.version, true)
65afm.autoprefixed          = true -- this will become false some day (catches texnansi-blabla.*)
66
67afm.helpdata              = { }  -- set later on so no local for this
68afm.syncspace             = true -- when true, nicer stretch values
69
70local overloads           = fonts.mappings.overloads
71
72local applyruntimefixes   = fonts.treatments and fonts.treatments.applyfixes
73
74--[[ldx--
75<p>We cache files. Caching is taken care of in the loader. We cheat a bit by adding
76ligatures and kern information to the afm derived data. That way we can set them faster
77when defining a font.</p>
78
79<p>We still keep the loading two phased: first we load the data in a traditional
80fashion and later we transform it to sequences. Then we apply some methods also
81used in opentype fonts (like <t>tlig</t>).</p>
82--ldx]]--
83
84function afm.load(filename)
85    filename = resolvers.findfile(filename,'afm') or ""
86    if filename ~= "" and not fonts.names.ignoredfile(filename) then
87        local name = file.removesuffix(file.basename(filename))
88        local data = containers.read(afm.cache,name)
89        local attr = lfs.attributes(filename)
90        local size = attr and attr.size or 0
91        local time = attr and attr.modification or 0
92        --
93        local pfbfile = file.replacesuffix(name,"pfb")
94        local pfbname = resolvers.findfile(pfbfile,"pfb") or ""
95        if pfbname == "" then
96            pfbname = resolvers.findfile(file.basename(pfbfile),"pfb") or ""
97        end
98        local pfbsize = 0
99        local pfbtime = 0
100        if pfbname ~= "" then
101            local attr = lfs.attributes(pfbname)
102            pfbsize = attr.size or 0
103            pfbtime = attr.modification or 0
104        end
105        if not data or data.size ~= size or data.time ~= time or data.pfbsize ~= pfbsize or data.pfbtime ~= pfbtime then
106            report_afm("reading %a",filename)
107            data = afm.readers.loadfont(filename,pfbname)
108            if data then
109                afmenhancers.apply(data,filename)
110             -- otfreaders.addunicodetable(data) -- only when not done yet
111                fonts.mappings.addtounicode(data,filename)
112                otfreaders.stripredundant(data)
113             -- otfreaders.extend(data)
114                otfreaders.pack(data)
115                data.size = size
116                data.time = time
117                data.pfbsize = pfbsize
118                data.pfbtime = pfbtime
119                report_afm("saving %a in cache",name)
120             -- data.resources.unicodes = nil -- consistent with otf but here we save not much
121                data = containers.write(afm.cache, name, data)
122                data = containers.read(afm.cache,name)
123            end
124        end
125        if data then
126         -- constructors.addcoreunicodes(unicodes)
127            otfreaders.unpack(data)
128            otfreaders.expand(data) -- inline tables
129            otfreaders.addunicodetable(data) -- only when not done yet
130            otfenhancers.apply(data,filename,data)
131            if applyruntimefixes then
132                applyruntimefixes(filename,data)
133            end
134        end
135        return data
136    end
137end
138
139-- we run a more advanced analyzer later on anyway
140
141local uparser = fonts.mappings.makenameparser() -- each time
142
143local function enhance_unify_names(data, filename)
144    local unicodevector = fonts.encodings.agl.unicodes -- loaded runtime in context
145    local unicodes      = { }
146    local names         = { }
147    local private       = data.private or privateoffset
148    local descriptions  = data.descriptions
149    for name, blob in sortedhash(data.characters) do -- sorting is nicer for privates
150        local code = unicodevector[name] -- or characters.name_to_unicode[name]
151        if not code then
152            code = lpegmatch(uparser,name)
153            if type(code) ~= "number" then
154                code = private
155                private = private + 1
156                report_afm("assigning private slot %U for unknown glyph name %a",code,name)
157            end
158        end
159        local index = blob.index
160        unicodes[name] = code
161        names[name] = index
162        blob.name = name
163        descriptions[code] = {
164            boundingbox = blob.boundingbox,
165            width       = blob.width,
166            kerns       = blob.kerns,
167            index       = index,
168            name        = name,
169        }
170    end
171    for unicode, description in next, descriptions do
172        local kerns = description.kerns
173        if kerns then
174            local krn = { }
175            for name, kern in next, kerns do
176                local unicode = unicodes[name]
177                if unicode then
178                    krn[unicode] = kern
179                else
180                 -- print(unicode,name)
181                end
182            end
183            description.kerns = krn
184        end
185    end
186    data.characters = nil
187    data.private    = private
188    local resources = data.resources
189    local filename  = resources.filename or file.removesuffix(file.basename(filename))
190    resources.filename = resolvers.unresolve(filename) -- no shortcut
191    resources.unicodes = unicodes -- name to unicode
192    resources.marks    = { } -- todo
193 -- resources.names    = names -- name to index
194end
195
196local everywhere = { ["*"] = { ["*"] = true } } -- or: { ["*"] = { "*" } }
197local noflags    = { false, false, false, false }
198
199local function enhance_normalize_features(data)
200    local ligatures  = setmetatableindex("table")
201    local kerns      = setmetatableindex("table")
202    local extrakerns = setmetatableindex("table")
203    for u, c in next, data.descriptions do
204        local l = c.ligatures
205        local k = c.kerns
206        local e = c.extrakerns
207        if l then
208            ligatures[u] = l
209            for u, v in next, l do
210                l[u] = { ligature = v }
211            end
212            c.ligatures = nil
213        end
214        if k then
215            kerns[u] = k
216            for u, v in next, k do
217                k[u] = v -- { v, 0 }
218            end
219            c.kerns = nil
220        end
221        if e then
222            extrakerns[u] = e
223            for u, v in next, e do
224                e[u] = v -- { v, 0 }
225            end
226            c.extrakerns = nil
227        end
228    end
229    local features = {
230        gpos = { },
231        gsub = { },
232    }
233    local sequences = {
234        -- only filled ones
235    }
236    if next(ligatures) then
237        features.gsub.liga = everywhere
238        data.properties.hasligatures = true
239        sequences[#sequences+1] = {
240            features = {
241                liga = everywhere,
242            },
243            flags    = noflags,
244            name     = "s_s_0",
245            nofsteps = 1,
246            order    = { "liga" },
247            type     = "gsub_ligature",
248            steps    = {
249                {
250                    coverage = ligatures,
251                },
252            },
253        }
254    end
255    if next(kerns) then
256        features.gpos.kern = everywhere
257        data.properties.haskerns = true
258        sequences[#sequences+1] = {
259            features = {
260                kern = everywhere,
261            },
262            flags    = noflags,
263            name     = "p_s_0",
264            nofsteps = 1,
265            order    = { "kern" },
266            type     = "gpos_pair",
267            steps    = {
268                {
269                    format   = "kern",
270                    coverage = kerns,
271                },
272            },
273        }
274    end
275    if next(extrakerns) then
276        features.gpos.extrakerns = everywhere
277        data.properties.haskerns = true
278        sequences[#sequences+1] = {
279            features = {
280                extrakerns = everywhere,
281            },
282            flags    = noflags,
283            name     = "p_s_1",
284            nofsteps = 1,
285            order    = { "extrakerns" },
286            type     = "gpos_pair",
287            steps    = {
288                {
289                    format   = "kern",
290                    coverage = extrakerns,
291                },
292            },
293        }
294    end
295    -- todo: compress kerns
296    data.resources.features  = features
297    data.resources.sequences = sequences
298end
299
300local function enhance_fix_names(data)
301    for k, v in next, data.descriptions do
302        local n = v.name
303        local r = overloads[n]
304        if r then
305            local name = r.name
306            if trace_indexing then
307                report_afm("renaming characters %a to %a",n,name)
308            end
309            v.name    = name
310            v.unicode = r.unicode
311        end
312    end
313end
314
315--[[ldx--
316<p>These helpers extend the basic table with extra ligatures, texligatures
317and extra kerns. This saves quite some lookups later.</p>
318--ldx]]--
319
320local addthem = function(rawdata,ligatures)
321    if ligatures then
322        local descriptions = rawdata.descriptions
323        local resources    = rawdata.resources
324        local unicodes     = resources.unicodes
325     -- local names        = resources.names
326        for ligname, ligdata in next, ligatures do
327            local one = descriptions[unicodes[ligname]]
328            if one then
329                for _, pair in next, ligdata do
330                    local two   = unicodes[pair[1]]
331                    local three = unicodes[pair[2]]
332                    if two and three then
333                        local ol = one.ligatures
334                        if ol then
335                            if not ol[two] then
336                                ol[two] = three
337                            end
338                        else
339                            one.ligatures = { [two] = three }
340                        end
341                    end
342                end
343            end
344        end
345    end
346end
347
348local function enhance_add_ligatures(rawdata)
349    addthem(rawdata,afm.helpdata.ligatures)
350end
351
352--[[ldx--
353<p>We keep the extra kerns in separate kerning tables so that we can use
354them selectively.</p>
355--ldx]]--
356
357-- This is rather old code (from the beginning when we had only tfm). If
358-- we unify the afm data (now we have names all over the place) then
359-- we can use shcodes but there will be many more looping then. But we
360-- could get rid of the tables in char-cmp then. Als, in the generic version
361-- we don't use the character database. (Ok, we can have a context specific
362-- variant).
363
364local function enhance_add_extra_kerns(rawdata) -- using shcodes is not robust here
365    local descriptions = rawdata.descriptions
366    local resources    = rawdata.resources
367    local unicodes     = resources.unicodes
368    local function do_it_left(what)
369        if what then
370            for unicode, description in next, descriptions do
371                local kerns = description.kerns
372                if kerns then
373                    local extrakerns
374                    for complex, simple in next, what do
375                        complex = unicodes[complex]
376                        simple = unicodes[simple]
377                        if complex and simple then
378                            local ks = kerns[simple]
379                            if ks and not kerns[complex] then
380                                if extrakerns then
381                                    extrakerns[complex] = ks
382                                else
383                                    extrakerns = { [complex] = ks }
384                                end
385                            end
386                        end
387                    end
388                    if extrakerns then
389                        description.extrakerns = extrakerns
390                    end
391                end
392            end
393        end
394    end
395    local function do_it_copy(what)
396        if what then
397            for complex, simple in next, what do
398                complex = unicodes[complex]
399                simple  = unicodes[simple]
400                if complex and simple then
401                    local complexdescription = descriptions[complex]
402                    if complexdescription then -- optional
403                        local simpledescription = descriptions[complex]
404                        if simpledescription then
405                            local extrakerns
406                            local kerns = simpledescription.kerns
407                            if kerns then
408                                for unicode, kern in next, kerns do
409                                    if extrakerns then
410                                        extrakerns[unicode] = kern
411                                    else
412                                        extrakerns = { [unicode] = kern }
413                                    end
414                                end
415                            end
416                            local extrakerns = simpledescription.extrakerns
417                            if extrakerns then
418                                for unicode, kern in next, extrakerns do
419                                    if extrakerns then
420                                        extrakerns[unicode] = kern
421                                    else
422                                        extrakerns = { [unicode] = kern }
423                                    end
424                                end
425                            end
426                            if extrakerns then
427                                complexdescription.extrakerns = extrakerns
428                            end
429                        end
430                    end
431                end
432            end
433        end
434    end
435    -- add complex with values of simplified when present
436    do_it_left(afm.helpdata.leftkerned)
437    do_it_left(afm.helpdata.bothkerned)
438    -- copy kerns from simple char to complex char unless set
439    do_it_copy(afm.helpdata.bothkerned)
440    do_it_copy(afm.helpdata.rightkerned)
441end
442
443--[[ldx--
444<p>The copying routine looks messy (and is indeed a bit messy).</p>
445--ldx]]--
446
447local function adddimensions(data) -- we need to normalize afm to otf i.e. indexed table instead of name
448    if data then
449        for unicode, description in next, data.descriptions do
450            local bb = description.boundingbox
451            if bb then
452                local ht =  bb[4]
453                local dp = -bb[2]
454                if ht == 0 or ht < 0 then
455                    -- no need to set it and no negative heights, nil == 0
456                else
457                    description.height = ht
458                end
459                if dp == 0 or dp < 0 then
460                    -- no negative depths and no negative depths, nil == 0
461                else
462                    description.depth  = dp
463                end
464            end
465        end
466    end
467end
468
469local function copytotfm(data)
470    if data and data.descriptions then
471        local metadata     = data.metadata
472        local resources    = data.resources
473        local properties   = derivetable(data.properties)
474        local descriptions = derivetable(data.descriptions)
475        local goodies      = derivetable(data.goodies)
476        local characters   = { }
477        local parameters   = { }
478        local unicodes     = resources.unicodes
479        --
480        for unicode, description in next, data.descriptions do -- use parent table
481            characters[unicode] = { }
482        end
483        --
484        local filename   = constructors.checkedfilename(resources)
485        local fontname   = metadata.fontname or metadata.fullname
486        local fullname   = metadata.fullname or metadata.fontname
487        local endash     = 0x2013
488        local emdash     = 0x2014
489        local space      = 0x0020 -- space
490        local spacer     = "space"
491        local spaceunits = 500
492        --
493        local monospaced  = metadata.monospaced
494        local charwidth   = metadata.charwidth
495        local italicangle = metadata.italicangle
496        local charxheight = metadata.xheight and metadata.xheight > 0 and metadata.xheight
497        properties.monospaced  = monospaced
498        parameters.italicangle = italicangle
499        parameters.charwidth   = charwidth
500        parameters.charxheight = charxheight
501        -- nearly the same as otf, catches
502        local d_endash = descriptions[endash]
503        local d_emdash = descriptions[emdash]
504        local d_space  = descriptions[space]
505        if not d_space or d_space == 0 then
506            d_space = d_endash
507        end
508        if d_space then
509            spaceunits, spacer = d_space.width or 0, "space"
510        end
511        if properties.monospaced then
512            if spaceunits == 0 and d_emdash then
513                spaceunits, spacer = d_emdash.width or 0, "emdash"
514            end
515        else
516            if spaceunits == 0 and d_endash then
517                spaceunits, spacer = d_emdash.width or 0, "endash"
518            end
519        end
520        if spaceunits == 0 and charwidth then
521            spaceunits, spacer = charwidth or 0, "charwidth"
522        end
523        if spaceunits == 0 then
524            spaceunits = tonumber(spaceunits) or 500
525        end
526        if spaceunits == 0 then
527            spaceunits = 500
528        end
529        --
530        parameters.slant         = 0
531        parameters.space         = spaceunits
532        parameters.space_stretch = 500
533        parameters.space_shrink  = 333
534        parameters.x_height      = 400
535        parameters.quad          = 1000
536        --
537        if italicangle and italicangle ~= 0 then
538            parameters.italicangle  = italicangle
539            parameters.italicfactor = math.cos(math.rad(90+italicangle))
540            parameters.slant        = - math.tan(italicangle*math.pi/180)
541        end
542        if monospaced then
543            parameters.space_stretch = 0
544            parameters.space_shrink  = 0
545        elseif afm.syncspace then
546            parameters.space_stretch = spaceunits/2
547            parameters.space_shrink  = spaceunits/3
548        end
549        parameters.extra_space = parameters.space_shrink
550        if charxheight then
551            parameters.x_height = charxheight
552        else
553            -- same as otf
554            local x = 0x0078 -- x
555            if x then
556                local x = descriptions[x]
557                if x then
558                    parameters.x_height = x.height
559                end
560            end
561            --
562        end
563        --
564        if metadata.sup then
565            local dummy    = { 0, 0, 0 }
566            parameters[ 1] = metadata.designsize        or 0
567            parameters[ 2] = metadata.checksum          or 0
568            parameters[ 3],
569            parameters[ 4],
570            parameters[ 5] = unpack(metadata.space      or dummy)
571            parameters[ 6] =        metadata.quad       or 0
572            parameters[ 7] =        metadata.extraspace or 0
573            parameters[ 8],
574            parameters[ 9],
575            parameters[10] = unpack(metadata.num        or dummy)
576            parameters[11],
577            parameters[12] = unpack(metadata.denom      or dummy)
578            parameters[13],
579            parameters[14],
580            parameters[15] = unpack(metadata.sup        or dummy)
581            parameters[16],
582            parameters[17] = unpack(metadata.sub        or dummy)
583            parameters[18] =        metadata.supdrop    or 0
584            parameters[19] =        metadata.subdrop    or 0
585            parameters[20],
586            parameters[21] = unpack(metadata.delim      or dummy)
587            parameters[22] =        metadata.axisheight or 0
588        end
589        --
590        parameters.designsize = (metadata.designsize or 10)*65536
591        parameters.ascender   = abs(metadata.ascender  or 0)
592        parameters.descender  = abs(metadata.descender or 0)
593        parameters.units      = 1000
594        --
595        properties.spacer   = spacer
596        properties.format   = fonts.formats[filename] or "type1"
597        properties.filename = filename
598        properties.fontname = fontname
599        properties.fullname = fullname
600        properties.psname   = fullname
601        properties.name     = filename or fullname or fontname
602        properties.private  = properties.private or data.private or privateoffset
603        --
604if not CONTEXTLMTXMODE or CONTEXTLMTXMODE == 0 then
605        properties.encodingbytes = 2
606end
607        --
608        if next(characters) then
609            return {
610                characters   = characters,
611                descriptions = descriptions,
612                parameters   = parameters,
613                resources    = resources,
614                properties   = properties,
615                goodies      = goodies,
616            }
617        end
618    end
619    return nil
620end
621
622--[[ldx--
623<p>Originally we had features kind of hard coded for <l n='afm'/> files but since I
624expect to support more font formats, I decided to treat this fontformat like any
625other and handle features in a more configurable way.</p>
626--ldx]]--
627
628function afm.setfeatures(tfmdata,features)
629    local okay = constructors.initializefeatures("afm",tfmdata,features,trace_features,report_afm)
630    if okay then
631        return constructors.collectprocessors("afm",tfmdata,features,trace_features,report_afm)
632    else
633        return { } -- will become false
634    end
635end
636
637local function addtables(data)
638    local resources  = data.resources
639    local lookuptags = resources.lookuptags
640    local unicodes   = resources.unicodes
641    if not lookuptags then
642        lookuptags = { }
643        resources.lookuptags = lookuptags
644    end
645    setmetatableindex(lookuptags,function(t,k)
646        local v = type(k) == "number" and ("lookup " .. k) or k
647        t[k] = v
648        return v
649    end)
650    if not unicodes then
651        unicodes = { }
652        resources.unicodes = unicodes
653        setmetatableindex(unicodes,function(t,k)
654            setmetatableindex(unicodes,nil)
655            for u, d in next, data.descriptions do
656                local n = d.name
657                if n then
658                    t[n] = u
659                end
660            end
661            return rawget(t,k)
662        end)
663    end
664    constructors.addcoreunicodes(unicodes) -- do we really need this?
665end
666
667local function afmtotfm(specification)
668    local afmname = specification.filename or specification.name
669    if specification.forced == "afm" or specification.format == "afm" then -- move this one up
670        if trace_loading then
671            report_afm("forcing afm format for %a",afmname)
672        end
673    else
674        local tfmname = findbinfile(afmname,"ofm") or ""
675        if tfmname ~= "" then
676            if trace_loading then
677                report_afm("fallback from afm to tfm for %a",afmname)
678            end
679            return -- just that
680        end
681    end
682    if afmname ~= "" then
683        -- weird, isn't this already done then?
684        local features = constructors.checkedfeatures("afm",specification.features.normal)
685        specification.features.normal = features
686        constructors.hashinstance(specification,true) -- also weird here
687        --
688        specification = definers.resolve(specification) -- new, was forgotten
689        local cache_id = specification.hash
690        local tfmdata  = containers.read(constructors.cache, cache_id) -- cache with features applied
691        if not tfmdata then
692            local rawdata = afm.load(afmname)
693            if rawdata and next(rawdata) then
694                addtables(rawdata)
695                adddimensions(rawdata)
696                tfmdata = copytotfm(rawdata)
697                if tfmdata and next(tfmdata) then
698                    local shared = tfmdata.shared
699                    if not shared then
700                        shared         = { }
701                        tfmdata.shared = shared
702                    end
703                    shared.rawdata   = rawdata
704                    shared.dynamics  = { }
705                    tfmdata.changed  = { }
706                    shared.features  = features
707                    shared.processes = afm.setfeatures(tfmdata,features)
708                end
709            elseif trace_loading then
710                report_afm("no (valid) afm file found with name %a",afmname)
711            end
712            tfmdata = containers.write(constructors.cache,cache_id,tfmdata)
713        end
714        return tfmdata
715    end
716end
717
718--[[ldx--
719<p>As soon as we could intercept the <l n='tfm'/> reader, I implemented an
720<l n='afm'/> reader. Since traditional <l n='pdftex'/> could use <l n='opentype'/>
721fonts with <l n='afm'/> companions, the following method also could handle
722those cases, but now that we can handle <l n='opentype'/> directly we no longer
723need this features.</p>
724--ldx]]--
725
726local function read_from_afm(specification)
727    local tfmdata = afmtotfm(specification)
728    if tfmdata then
729        tfmdata.properties.name = specification.name
730        tfmdata.properties.id   = specification.id
731        tfmdata = constructors.scale(tfmdata, specification)
732        local allfeatures = tfmdata.shared.features or specification.features.normal
733        constructors.applymanipulators("afm",tfmdata,allfeatures,trace_features,report_afm)
734        fonts.loggers.register(tfmdata,'afm',specification)
735    end
736    return tfmdata
737end
738
739--[[ldx--
740<p>We have the usual two modes and related features initializers and processors.</p>
741--ldx]]--
742
743registerafmfeature {
744    name         = "mode",
745    description  = "mode",
746    initializers = {
747        base = otf.modeinitializer,
748        node = otf.modeinitializer,
749    }
750}
751
752registerafmfeature {
753    name         = "features",
754    description  = "features",
755    default      = true,
756    initializers = {
757        node     = otf.nodemodeinitializer,
758        base     = otf.basemodeinitializer,
759    },
760    processors   = {
761        node     = otf.featuresprocessor,
762    }
763}
764
765-- readers
766
767fonts.formats.afm = "type1"
768fonts.formats.pfb = "type1"
769
770local function check_afm(specification,fullname)
771    local foundname = findbinfile(fullname, 'afm') or "" -- just to be sure
772    if foundname == "" then
773        foundname = fonts.names.getfilename(fullname,"afm") or ""
774    end
775    if fullname and foundname == "" and afm.autoprefixed then
776        local encoding, shortname = match(fullname,"^(.-)%-(.*)$") -- context: encoding-name.*
777        if encoding and shortname and fonts.encodings.known[encoding] then
778            shortname = findbinfile(shortname,'afm') or "" -- just to be sure
779            if shortname ~= "" then
780                foundname = shortname
781                if trace_defining then
782                    report_afm("stripping encoding prefix from filename %a",afmname)
783                end
784            end
785        end
786    end
787    if foundname ~= "" then
788        specification.filename = foundname
789        specification.format   = "afm"
790        return read_from_afm(specification)
791    end
792end
793
794function readers.afm(specification,method)
795    local fullname = specification.filename or ""
796    local tfmdata  = nil
797    if fullname == "" then
798        local forced = specification.forced or ""
799        if forced ~= "" then
800            tfmdata = check_afm(specification,specification.name .. "." .. forced)
801        end
802        if not tfmdata then
803            local check_tfm = readers.check_tfm
804            method = (check_tfm and (method or definers.method or "afm or tfm")) or "afm"
805            if method == "tfm" then
806                tfmdata = check_tfm(specification,specification.name)
807            elseif method == "afm" then
808                tfmdata = check_afm(specification,specification.name)
809            elseif method == "tfm or afm" then
810                tfmdata = check_tfm(specification,specification.name) or check_afm(specification,specification.name)
811            else -- method == "afm or tfm" or method == "" then
812                tfmdata = check_afm(specification,specification.name) or check_tfm(specification,specification.name)
813            end
814        end
815    else
816        tfmdata = check_afm(specification,fullname)
817    end
818    return tfmdata
819end
820
821function readers.pfb(specification,method) -- only called when forced
822    local original = specification.specification
823    if trace_defining then
824        report_afm("using afm reader for %a",original)
825    end
826    specification.forced = "afm"
827    local function swap(name)
828        local value = specification[swap]
829        if value then
830            specification[swap] = gsub("%.pfb",".afm",1)
831        end
832    end
833    swap("filename")
834    swap("fullname")
835    swap("forcedname")
836    swap("specification")
837    return readers.afm(specification,method)
838end
839
840-- now we register them
841
842registerafmenhancer("unify names",          enhance_unify_names)
843registerafmenhancer("add ligatures",        enhance_add_ligatures)
844registerafmenhancer("add extra kerns",      enhance_add_extra_kerns)
845registerafmenhancer("normalize features",   enhance_normalize_features)
846registerafmenhancer("check extra features", otfenhancers.enhance)
847registerafmenhancer("fix names",            enhance_fix_names)
848