diff options
| -rw-r--r-- | luaotfload-font-afm.lua | 971 | 
1 files changed, 971 insertions, 0 deletions
| diff --git a/luaotfload-font-afm.lua b/luaotfload-font-afm.lua new file mode 100644 index 0000000..cb0c243 --- /dev/null +++ b/luaotfload-font-afm.lua @@ -0,0 +1,971 @@ +if not modules then modules = { } end modules ['font-afm'] = { +    version   = 1.001, +    comment   = "companion to font-ini.mkiv", +    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL", +    copyright = "PRAGMA ADE / ConTeXt Development Team", +    license   = "see context related readme files" +} + +--[[ldx-- +<p>Some code may look a bit obscure but this has to do with the +fact that we also use this code for testing and much code evolved +in the transition from <l n='tfm'/> to <l n='afm'/> to <l +n='otf'/>.</p> + +<p>The following code still has traces of intermediate font support +where we handles font encodings. Eventually font encoding goes +away.</p> +--ldx]]-- + +local fonts, logs, trackers, containers, resolvers = fonts, logs, trackers, containers, resolvers + +local next, type, tonumber = next, type, tonumber +local format, match, gmatch, lower, gsub, strip = string.format, string.match, string.gmatch, string.lower, string.gsub, string.strip +local abs = math.abs +local P, S, C, R, lpegmatch, patterns = lpeg.P, lpeg.S, lpeg.C, lpeg.R, lpeg.match, lpeg.patterns +local derivetable = table.derive + +local trace_features     = false  trackers.register("afm.features",   function(v) trace_features = v end) +local trace_indexing     = false  trackers.register("afm.indexing",   function(v) trace_indexing = v end) +local trace_loading      = false  trackers.register("afm.loading",    function(v) trace_loading  = v end) +local trace_defining     = false  trackers.register("fonts.defining", function(v) trace_defining = v end) + +local report_afm         = logs.reporter("fonts","afm loading") + +local findbinfile        = resolvers.findbinfile + +local definers           = fonts.definers +local readers            = fonts.readers +local constructors       = fonts.constructors + +local afm                = constructors.newhandler("afm") +local pfb                = constructors.newhandler("pfb") + +local afmfeatures        = constructors.newfeatures("afm") +local registerafmfeature = afmfeatures.register + +afm.version              = 1.410 -- incrementing this number one up will force a re-cache +afm.cache                = containers.define("fonts", "afm", afm.version, true) +afm.autoprefixed         = true -- this will become false some day (catches texnansi-blabla.*) + +afm.helpdata             = { }  -- set later on so no local for this +afm.syncspace            = true -- when true, nicer stretch values +afm.addligatures         = true -- best leave this set to true +afm.addtexligatures      = true -- best leave this set to true +afm.addkerns             = true -- best leave this set to true + +local function setmode(tfmdata,value) +    if value then +        tfmdata.properties.mode = lower(value) +    end +end + +registerafmfeature { +    name         = "mode", +    description  = "mode", +    initializers = { +        base = setmode, +        node = setmode, +    } +} + +--[[ldx-- +<p>We start with the basic reader which we give a name similar to the +built in <l n='tfm'/> and <l n='otf'/> reader.</p> +--ldx]]-- + +--~ Comment FONTIDENTIFIER LMMATHSYMBOLS10 +--~ Comment CODINGSCHEME TEX MATH SYMBOLS +--~ Comment DESIGNSIZE 10.0 pt +--~ Comment CHECKSUM O 4261307036 +--~ Comment SPACE 0 plus 0 minus 0 +--~ Comment QUAD 1000 +--~ Comment EXTRASPACE 0 +--~ Comment NUM 676.508 393.732 443.731 +--~ Comment DENOM 685.951 344.841 +--~ Comment SUP 412.892 362.892 288.889 +--~ Comment SUB 150 247.217 +--~ Comment SUPDROP 386.108 +--~ Comment SUBDROP 50 +--~ Comment DELIM 2390 1010 +--~ Comment AXISHEIGHT 250 + +local comment = P("Comment") +local spacing = patterns.spacer  -- S(" \t")^1 +local lineend = patterns.newline -- S("\n\r") +local words   = C((1 - lineend)^1) +local number  = C((R("09") + S("."))^1) / tonumber * spacing^0 +local data    = lpeg.Carg(1) + +local pattern = ( -- needs testing ... not used anyway as we no longer need math afm's +    comment * spacing * +        ( +            data * ( +                ("CODINGSCHEME" * spacing * words                                      ) / function(fd,a)                                      end + +                ("DESIGNSIZE"   * spacing * number * words                             ) / function(fd,a)     fd[ 1]                 = a       end + +                ("CHECKSUM"     * spacing * number * words                             ) / function(fd,a)     fd[ 2]                 = a       end + +                ("SPACE"        * spacing * number * "plus" * number * "minus" * number) / function(fd,a,b,c) fd[ 3], fd[ 4], fd[ 5] = a, b, c end + +                ("QUAD"         * spacing * number                                     ) / function(fd,a)     fd[ 6]                 = a       end + +                ("EXTRASPACE"   * spacing * number                                     ) / function(fd,a)     fd[ 7]                 = a       end + +                ("NUM"          * spacing * number * number * number                   ) / function(fd,a,b,c) fd[ 8], fd[ 9], fd[10] = a, b, c end + +                ("DENOM"        * spacing * number * number                            ) / function(fd,a,b  ) fd[11], fd[12]         = a, b    end + +                ("SUP"          * spacing * number * number * number                   ) / function(fd,a,b,c) fd[13], fd[14], fd[15] = a, b, c end + +                ("SUB"          * spacing * number * number                            ) / function(fd,a,b)   fd[16], fd[17]         = a, b    end + +                ("SUPDROP"      * spacing * number                                     ) / function(fd,a)     fd[18]                 = a       end + +                ("SUBDROP"      * spacing * number                                     ) / function(fd,a)     fd[19]                 = a       end + +                ("DELIM"        * spacing * number * number                            ) / function(fd,a,b)   fd[20], fd[21]         = a, b    end + +                ("AXISHEIGHT"   * spacing * number                                     ) / function(fd,a)     fd[22]                 = a       end +            ) +          + (1-lineend)^0 +        ) +  + (1-comment)^1 +)^0 + +local function scan_comment(str) +    local fd = { } +    lpegmatch(pattern,str,1,fd) +    return fd +end + +-- On a rainy day I will rewrite this in lpeg ... or we can use the (slower) fontloader +-- as in now supports afm/pfb loading but it's not too bad to have different methods +-- for testing approaches. + +local keys = { } + +function keys.FontName    (data,line) data.metadata.fontname     = strip    (line) -- get rid of spaces +                                      data.metadata.fullname     = strip    (line) end +function keys.ItalicAngle (data,line) data.metadata.italicangle  = tonumber (line) end +function keys.IsFixedPitch(data,line) data.metadata.isfixedpitch = toboolean(line,true) end +function keys.CharWidth   (data,line) data.metadata.charwidth    = tonumber (line) end +function keys.XHeight     (data,line) data.metadata.xheight      = tonumber (line) end +function keys.Descender   (data,line) data.metadata.descender    = tonumber (line) end +function keys.Ascender    (data,line) data.metadata.ascender     = tonumber (line) end +function keys.Comment     (data,line) + -- Comment DesignSize 12 (pts) + -- Comment TFM designsize: 12 (in points) +    line = lower(line) +    local designsize = match(line,"designsize[^%d]*(%d+)") +    if designsize then data.metadata.designsize = tonumber(designsize) end +end + +local function get_charmetrics(data,charmetrics,vector) +    local characters = data.characters +    local chr, ind = { }, 0 +    for k,v in gmatch(charmetrics,"([%a]+) +(.-) *;") do +        if k == 'C'  then +            v = tonumber(v) +            if v < 0 then +                ind = ind + 1 -- ? +            else +                ind = v +            end +            chr = { +                index = ind +            } +        elseif k == 'WX' then +            chr.width = tonumber(v) +        elseif k == 'N'  then +            characters[v] = chr +        elseif k == 'B'  then +            local llx, lly, urx, ury = match(v,"^ *(.-) +(.-) +(.-) +(.-)$") +            chr.boundingbox = { tonumber(llx), tonumber(lly), tonumber(urx), tonumber(ury) } +        elseif k == 'L'  then +            local plus, becomes = match(v,"^(.-) +(.-)$") +            local ligatures = chr.ligatures +            if ligatures then +                ligatures[plus] = becomes +            else +                chr.ligatures = { [plus] = becomes } +            end +        end +    end +end + +local function get_kernpairs(data,kernpairs) +    local characters = data.characters +    for one, two, value in gmatch(kernpairs,"KPX +(.-) +(.-) +(.-)\n") do +        local chr = characters[one] +        if chr then +            local kerns = chr.kerns +            if kerns then +                kerns[two] = tonumber(value) +            else +                chr.kerns = { [two] = tonumber(value) } +            end +        end +    end +end + +local function get_variables(data,fontmetrics) +    for key, rest in gmatch(fontmetrics,"(%a+) *(.-)[\n\r]") do +        local keyhandler = keys[key] +        if keyhandler then +            keyhandler(data,rest) +        end +    end +end + +local function get_indexes(data,pfbname) +    data.resources.filename = resolvers.unresolve(pfbname) -- no shortcut +    local pfbblob = fontloader.open(pfbname) +    if pfbblob then +        local characters = data.characters +        local pfbdata = fontloader.to_table(pfbblob) +        if pfbdata then +            local glyphs = pfbdata.glyphs +            if glyphs then +                if trace_loading then +                    report_afm("getting index data from %a",pfbname) +                end +                for index, glyph in next, glyphs do +                    local name = glyph.name +                    if name then +                        local char = characters[name] +                        if char then +                            if trace_indexing then +                                report_afm("glyph %a has index %a",name,index) +                            end +                            char.index = index +                        end +                    end +                end +            elseif trace_loading then +                report_afm("no glyph data in pfb file %a",pfbname) +            end +        elseif trace_loading then +            report_afm("no data in pfb file %a",pfbname) +        end +        fontloader.close(pfbblob) +    elseif trace_loading then +        report_afm("invalid pfb file %a",pfbname) +    end +end + +local function readafm(filename) +    local ok, afmblob, size = resolvers.loadbinfile(filename) -- has logging +    if ok and afmblob then +        local data = { +            resources = { +                filename = resolvers.unresolve(filename), +                version  = afm.version, +                creator  = "context mkiv", +            }, +            properties = { +                hasitalics = false, +            }, +            goodies = { +            }, +            metadata   = { +                filename = file.removesuffix(file.basename(filename)) +            }, +            characters = { +                -- a temporary store +            }, +            descriptions = { +                -- the final store +            }, +        } +        afmblob = gsub(afmblob,"StartCharMetrics(.-)EndCharMetrics", function(charmetrics) +            if trace_loading then +                report_afm("loading char metrics") +            end +            get_charmetrics(data,charmetrics,vector) +            return "" +        end) +        afmblob = gsub(afmblob,"StartKernPairs(.-)EndKernPairs", function(kernpairs) +            if trace_loading then +                report_afm("loading kern pairs") +            end +            get_kernpairs(data,kernpairs) +            return "" +        end) +        afmblob = gsub(afmblob,"StartFontMetrics%s+([%d%.]+)(.-)EndFontMetrics", function(version,fontmetrics) +            if trace_loading then +                report_afm("loading variables") +            end +            data.afmversion = version +            get_variables(data,fontmetrics) +            data.fontdimens = scan_comment(fontmetrics) -- todo: all lpeg, no time now +            return "" +        end) +        return data +    else +        if trace_loading then +            report_afm("no valid afm file %a",filename) +        end +        return nil +    end +end + +--[[ldx-- +<p>We cache files. Caching is taken care of in the loader. We cheat a bit +by adding ligatures and kern information to the afm derived data. That +way we can set them faster when defining a font.</p> +--ldx]]-- + +local addkerns, addligatures, addtexligatures, unify, normalize -- we will implement these later + +function afm.load(filename) +    -- hm, for some reasons not resolved yet +    filename = resolvers.findfile(filename,'afm') or "" +    if filename ~= "" then +        local name = file.removesuffix(file.basename(filename)) +        local data = containers.read(afm.cache,name) +        local attr = lfs.attributes(filename) +        local size, time = attr.size or 0, attr.modification or 0 +        -- +        local pfbfile = file.replacesuffix(name,"pfb") +        local pfbname = resolvers.findfile(pfbfile,"pfb") or "" +        if pfbname == "" then +            pfbname = resolvers.findfile(file.basename(pfbfile),"pfb") or "" +        end +        local pfbsize, pfbtime = 0, 0 +        if pfbname ~= "" then +            local attr = lfs.attributes(pfbname) +            pfbsize = attr.size or 0 +            pfbtime = attr.modification or 0 +        end +        if not data or data.size ~= size or data.time ~= time or data.pfbsize ~= pfbsize or data.pfbtime ~= pfbtime then +            report_afm("reading %a",filename) +            data = readafm(filename) +            if data then +                if pfbname ~= "" then +                    get_indexes(data,pfbname) +                elseif trace_loading then +                    report_afm("no pfb file for %a",filename) +                end +                report_afm("unifying %a",filename) +                unify(data,filename) +                if afm.addligatures then +                    report_afm("add ligatures") +                    addligatures(data) +                end +                if afm.addtexligatures then +                    report_afm("add tex ligatures") +                    addtexligatures(data) +                end +                if afm.addkerns then +                    report_afm("add extra kerns") +                    addkerns(data) +                end +                normalize(data) +                report_afm("add tounicode data") +                fonts.mappings.addtounicode(data,filename) +                data.size = size +                data.time = time +                data.pfbsize = pfbsize +                data.pfbtime = pfbtime +                report_afm("saving %a in cache",name) +                data = containers.write(afm.cache, name, data) +                data = containers.read(afm.cache,name) +            end +        end +        return data +    else +        return nil +    end +end + +local uparser = fonts.mappings.makenameparser() + +unify = function(data, filename) +    local unicodevector = fonts.encodings.agl.unicodes -- loaded runtime in context +    local unicodes, names = { }, { } +    local private = constructors.privateoffset +    local descriptions = data.descriptions +    for name, blob in next, data.characters do +        local code = unicodevector[name] -- or characters.name_to_unicode[name] +        if not code then +            code = lpegmatch(uparser,name) +            if not code then +                code = private +                private = private + 1 +                report_afm("assigning private slot %U for unknown glyph name %a",code,name) +            end +        end +        local index = blob.index +        unicodes[name] = code +        names[name] = index +        blob.name = name +        descriptions[code] = { +            boundingbox = blob.boundingbox, +            width       = blob.width, +            kerns       = blob.kerns, +            index       = index, +            name        = name, +        } +    end +    for unicode, description in next, descriptions do +        local kerns = description.kerns +        if kerns then +            local krn = { } +            for name, kern in next, kerns do +                local unicode = unicodes[name] +                if unicode then +                    krn[unicode] = kern +                else +                    print(unicode,name) +                end +            end +            description.kerns = krn +        end +    end +    data.characters = nil +    local resources = data.resources +    local filename = resources.filename or file.removesuffix(file.basename(filename)) +    resources.filename = resolvers.unresolve(filename) -- no shortcut +    resources.unicodes = unicodes -- name to unicode +    resources.marks = { } -- todo +    resources.names = names -- name to index +    resources.private = private +end + +normalize = function(data) +end + +--[[ldx-- +<p>These helpers extend the basic table with extra ligatures, texligatures +and extra kerns. This saves quite some lookups later.</p> +--ldx]]-- + +local addthem = function(rawdata,ligatures) +    if ligatures then +        local descriptions = rawdata.descriptions +        local resources    = rawdata.resources +        local unicodes     = resources.unicodes +        local names        = resources.names +        for ligname, ligdata in next, ligatures do +            local one = descriptions[unicodes[ligname]] +            if one then +                for _, pair in next, ligdata do +                    local two, three = unicodes[pair[1]], unicodes[pair[2]] +                    if two and three then +                        local ol = one.ligatures +                        if ol then +                            if not ol[two] then +                                ol[two] = three +                            end +                        else +                            one.ligatures = { [two] = three } +                        end +                    end +                end +            end +        end +    end +end + +addligatures    = function(rawdata) addthem(rawdata,afm.helpdata.ligatures   ) end +addtexligatures = function(rawdata) addthem(rawdata,afm.helpdata.texligatures) end + +--[[ldx-- +<p>We keep the extra kerns in separate kerning tables so that we can use +them selectively.</p> +--ldx]]-- + +-- This is rather old code (from the beginning when we had only tfm). If +-- we unify the afm data (now we have names all over the place) then +-- we can use shcodes but there will be many more looping then. But we +-- could get rid of the tables in char-cmp then. Als, in the generic version +-- we don't use the character database. (Ok, we can have a context specific +-- variant). + +addkerns = function(rawdata) -- using shcodes is not robust here +    local descriptions = rawdata.descriptions +    local resources    = rawdata.resources +    local unicodes     = resources.unicodes +    local function do_it_left(what) +        if what then +            for unicode, description in next, descriptions do +                local kerns = description.kerns +                if kerns then +                    local extrakerns +                    for complex, simple in next, what do +                        complex = unicodes[complex] +                        simple = unicodes[simple] +                        if complex and simple then +                            local ks = kerns[simple] +                            if ks and not kerns[complex] then +                                if extrakerns then +                                    extrakerns[complex] = ks +                                else +                                    extrakerns = { [complex] = ks } +                                end +                            end +                        end +                    end +                    if extrakerns then +                        description.extrakerns = extrakerns +                    end +                end +            end +        end +    end +    local function do_it_copy(what) +        if what then +            for complex, simple in next, what do +                complex = unicodes[complex] +                simple = unicodes[simple] +                if complex and simple then +                    local complexdescription = descriptions[complex] +                    if complexdescription then -- optional +                        local simpledescription = descriptions[complex] +                        if simpledescription then +                            local extrakerns +                            local kerns = simpledescription.kerns +                            if kerns then +                                for unicode, kern in next, kerns do +                                    if extrakerns then +                                        extrakerns[unicode] = kern +                                    else +                                        extrakerns = { [unicode] = kern } +                                    end +                                end +                            end +                            local extrakerns = simpledescription.extrakerns +                            if extrakerns then +                                for unicode, kern in next, extrakerns do +                                    if extrakerns then +                                        extrakerns[unicode] = kern +                                    else +                                        extrakerns = { [unicode] = kern } +                                    end +                                end +                            end +                            if extrakerns then +                                complexdescription.extrakerns = extrakerns +                            end +                        end +                    end +                end +            end +        end +    end +    -- add complex with values of simplified when present +    do_it_left(afm.helpdata.leftkerned) +    do_it_left(afm.helpdata.bothkerned) +    -- copy kerns from simple char to complex char unless set +    do_it_copy(afm.helpdata.bothkerned) +    do_it_copy(afm.helpdata.rightkerned) +end + +--[[ldx-- +<p>The copying routine looks messy (and is indeed a bit messy).</p> +--ldx]]-- + +local function adddimensions(data) -- we need to normalize afm to otf i.e. indexed table instead of name +    if data then +        for unicode, description in next, data.descriptions do +            local bb = description.boundingbox +            if bb then +                local ht, dp = bb[4], -bb[2] +                if ht == 0 or ht < 0 then +                    -- no need to set it and no negative heights, nil == 0 +                else +                    description.height = ht +                end +                if dp == 0 or dp < 0 then +                    -- no negative depths and no negative depths, nil == 0 +                else +                    description.depth  = dp +                end +            end +        end +    end +end + +local function copytotfm(data) +    if data and data.descriptions then +        local metadata     = data.metadata +        local resources    = data.resources +        local properties   = derivetable(data.properties) +        local descriptions = derivetable(data.descriptions) +        local goodies      = derivetable(data.goodies) +        local characters   = { } +        local parameters   = { } +        local unicodes     = resources.unicodes +        -- +        for unicode, description in next, data.descriptions do -- use parent table +            characters[unicode] = { } +        end +        -- +        local filename   = constructors.checkedfilename(resources) +        local fontname   = metadata.fontname or metadata.fullname +        local fullname   = metadata.fullname or metadata.fontname +        local endash     = unicodes['space'] +        local emdash     = unicodes['emdash'] +        local spacer     = "space" +        local spaceunits = 500 +        -- +        local monospaced  = metadata.isfixedpitch +        local charwidth   = metadata.charwidth +        local italicangle = metadata.italicangle +        local charxheight = metadata.xheight and metadata.xheight > 0 and metadata.xheight +        properties.monospaced  = monospaced +        parameters.italicangle = italicangle +        parameters.charwidth   = charwidth +        parameters.charxheight = charxheight +        -- same as otf +        if properties.monospaced then +            if descriptions[endash] then +                spaceunits, spacer = descriptions[endash].width, "space" +            end +            if not spaceunits and descriptions[emdash] then +                spaceunits, spacer = descriptions[emdash].width, "emdash" +            end +            if not spaceunits and charwidth then +                spaceunits, spacer = charwidth, "charwidth" +            end +        else +            if descriptions[endash] then +                spaceunits, spacer = descriptions[endash].width, "space" +            end +            if not spaceunits and charwidth then +                spaceunits, spacer = charwidth, "charwidth" +            end +        end +        spaceunits = tonumber(spaceunits) +        if spaceunits < 200 then +            -- todo: warning +        end +        -- +        parameters.slant         = 0 +        parameters.space         = spaceunits +        parameters.space_stretch = 500 +        parameters.space_shrink  = 333 +        parameters.x_height      = 400 +        parameters.quad          = 1000 +        -- +        if italicangle then +            parameters.italicangle  = italicangle +            parameters.italicfactor = math.cos(math.rad(90+italicangle)) +            parameters.slant        = - math.round(math.tan(italicangle*math.pi/180)) +        end +        if monospaced then +            parameters.space_stretch = 0 +            parameters.space_shrink  = 0 +        elseif afm.syncspace then +            parameters.space_stretch = spaceunits/2 +            parameters.space_shrink  = spaceunits/3 +        end +        parameters.extra_space = parameters.space_shrink +        if charxheight then +            parameters.x_height = charxheight +        else +            -- same as otf +            local x = unicodes['x'] +            if x then +                local x = descriptions[x] +                if x then +                    parameters.x_height = x.height +                end +            end +            -- +        end +        local fd = data.fontdimens +        if fd and fd[8] and fd[9] and fd[10] then -- math +            for k,v in next, fd do +                parameters[k] = v +            end +        end +        -- +        parameters.designsize = (metadata.designsize or 10)*65536 +        parameters.ascender   = abs(metadata.ascender  or 0) +        parameters.descender  = abs(metadata.descender or 0) +        parameters.units      = 1000 +        -- +        properties.spacer        = spacer +        properties.encodingbytes = 2 +        properties.format        = fonts.formats[filename] or "type1" +        properties.filename      = filename +        properties.fontname      = fontname +        properties.fullname      = fullname +        properties.psname        = fullname +        properties.name          = filename or fullname or fontname +        -- +        if next(characters) then +            return { +                characters   = characters, +                descriptions = descriptions, +                parameters   = parameters, +                resources    = resources, +                properties   = properties, +                goodies      = goodies, +            } +        end +    end +    return nil +end + +--[[ldx-- +<p>Originally we had features kind of hard coded for <l n='afm'/> +files but since I expect to support more font formats, I decided +to treat this fontformat like any other and handle features in a +more configurable way.</p> +--ldx]]-- + +function afm.setfeatures(tfmdata,features) +    local okay = constructors.initializefeatures("afm",tfmdata,features,trace_features,report_afm) +    if okay then +        return constructors.collectprocessors("afm",tfmdata,features,trace_features,report_afm) +    else +        return { } -- will become false +    end +end + +local function checkfeatures(specification) +end + +local function afmtotfm(specification) +    local afmname = specification.filename or specification.name +    if specification.forced == "afm" or specification.format == "afm" then -- move this one up +        if trace_loading then +            report_afm("forcing afm format for %a",afmname) +        end +    else +        local tfmname = findbinfile(afmname,"ofm") or "" +        if tfmname ~= "" then +            if trace_loading then +                report_afm("fallback from afm to tfm for %a",afmname) +            end +            return -- just that +        end +    end +    if afmname ~= "" then +        -- weird, isn't this already done then? +        local features = constructors.checkedfeatures("afm",specification.features.normal) +        specification.features.normal = features +        constructors.hashinstance(specification,true) -- also weird here +        -- +        specification = definers.resolve(specification) -- new, was forgotten +        local cache_id = specification.hash +        local tfmdata  = containers.read(constructors.cache, cache_id) -- cache with features applied +        if not tfmdata then +            local rawdata = afm.load(afmname) +            if rawdata and next(rawdata) then +                adddimensions(rawdata) +                tfmdata = copytotfm(rawdata) +                if tfmdata and next(tfmdata) then +                    local shared = tfmdata.shared +                    if not shared then +                        shared         = { } +                        tfmdata.shared = shared +                    end +                    shared.rawdata   = rawdata +                    shared.features  = features +                    shared.processes = afm.setfeatures(tfmdata,features) +                end +            elseif trace_loading then +                report_afm("no (valid) afm file found with name %a",afmname) +            end +            tfmdata = containers.write(constructors.cache,cache_id,tfmdata) +        end +        return tfmdata +    end +end + +--[[ldx-- +<p>As soon as we could intercept the <l n='tfm'/> reader, I implemented an +<l n='afm'/> reader. Since traditional <l n='pdftex'/> could use <l n='opentype'/> +fonts with <l n='afm'/> companions, the following method also could handle +those cases, but now that we can handle <l n='opentype'/> directly we no longer +need this features.</p> +--ldx]]-- + +local function read_from_afm(specification) +    local tfmdata = afmtotfm(specification) +    if tfmdata then +        tfmdata.properties.name = specification.name +        tfmdata = constructors.scale(tfmdata, specification) +        local allfeatures = tfmdata.shared.features or specification.features.normal +        constructors.applymanipulators("afm",tfmdata,allfeatures,trace_features,report_afm) +        fonts.loggers.register(tfmdata,'afm',specification) +    end +    return tfmdata +end + +--[[ldx-- +<p>Here comes the implementation of a few features. We only implement +those that make sense for this format.</p> +--ldx]]-- + +local function prepareligatures(tfmdata,ligatures,value) +    if value then +        local descriptions = tfmdata.descriptions +        for unicode, character in next, tfmdata.characters do +            local description = descriptions[unicode] +            local dligatures = description.ligatures +            if dligatures then +                local cligatures = character.ligatures +                if not cligatures then +                    cligatures = { } +                    character.ligatures = cligatures +                end +                for unicode, ligature in next, dligatures do +                    cligatures[unicode] = { +                        char = ligature, +                        type = 0 +                    } +                end +            end +        end +    end +end + +local function preparekerns(tfmdata,kerns,value) +    if value then +        local rawdata = tfmdata.shared.rawdata +        local resources = rawdata.resources +        local unicodes = resources.unicodes +        local descriptions = tfmdata.descriptions +        for u, chr in next, tfmdata.characters do +            local d = descriptions[u] +            local newkerns = d[kerns] +            if newkerns then +                local kerns = chr.kerns +                if not kerns then +                    kerns = { } +                    chr.kerns = kerns +                end +                for k,v in next, newkerns do +                    local uk = unicodes[k] +                    if uk then +                        kerns[uk] = v +                    end +                end +            end +        end +    end +end + +local list = { + -- [0x0022] = 0x201D, +    [0x0027] = 0x2019, + -- [0x0060] = 0x2018, +} + +local function texreplacements(tfmdata,value) +    local descriptions = tfmdata.descriptions +    local characters   = tfmdata.characters +    for k, v in next, list do +        characters  [k] = characters  [v] -- we forget about kerns +        descriptions[k] = descriptions[v] -- we forget about kerns +    end +end + +local function ligatures   (tfmdata,value) prepareligatures(tfmdata,'ligatures',   value) end +local function texligatures(tfmdata,value) prepareligatures(tfmdata,'texligatures',value) end +local function kerns       (tfmdata,value) preparekerns    (tfmdata,'kerns',       value) end +local function extrakerns  (tfmdata,value) preparekerns    (tfmdata,'extrakerns',  value) end + +registerafmfeature { +    name         = "liga", +    description  = "traditional ligatures", +    initializers = { +        base = ligatures, +        node = ligatures, +    } +} + +registerafmfeature { +    name         = "kern", +    description  = "intercharacter kerning", +    initializers = { +        base = kerns, +        node = kerns, +    } +} + +registerafmfeature { +    name         = "extrakerns", +    description  = "additional intercharacter kerning", +    initializers = { +        base = extrakerns, +        node = extrakerns, +    } +} + +registerafmfeature { +    name         = 'tlig', +    description  = 'tex ligatures', +    initializers = { +        base = texligatures, +        node = texligatures, +    } +} + +registerafmfeature { +    name         = 'trep', +    description  = 'tex replacements', +    initializers = { +        base = texreplacements, +        node = texreplacements, +    } +} + +-- readers + +local check_tfm   = readers.check_tfm + +fonts.formats.afm = "type1" +fonts.formats.pfb = "type1" + +local function check_afm(specification,fullname) +    local foundname = findbinfile(fullname, 'afm') or "" -- just to be sure +    if foundname == "" then +        foundname = fonts.names.getfilename(fullname,"afm") or "" +    end +    if foundname == "" and afm.autoprefixed then +        local encoding, shortname = match(fullname,"^(.-)%-(.*)$") -- context: encoding-name.* +        if encoding and shortname and fonts.encodings.known[encoding] then +            shortname = findbinfile(shortname,'afm') or "" -- just to be sure +            if shortname ~= "" then +                foundname = shortname +                if trace_defining then +                    report_afm("stripping encoding prefix from filename %a",afmname) +                end +            end +        end +    end +    if foundname ~= "" then +        specification.filename = foundname +        specification.format   = "afm" +        return read_from_afm(specification) +    end +end + +function readers.afm(specification,method) +    local fullname, tfmdata = specification.filename or "", nil +    if fullname == "" then +        local forced = specification.forced or "" +        if forced ~= "" then +            tfmdata = check_afm(specification,specification.name .. "." .. forced) +        end +        if not tfmdata then +            method = method or definers.method or "afm or tfm" +            if method == "tfm" then +                tfmdata = check_tfm(specification,specification.name) +            elseif method == "afm" then +                tfmdata = check_afm(specification,specification.name) +            elseif method == "tfm or afm" then +                tfmdata = check_tfm(specification,specification.name) or check_afm(specification,specification.name) +            else -- method == "afm or tfm" or method == "" then +                tfmdata = check_afm(specification,specification.name) or check_tfm(specification,specification.name) +            end +        end +    else +        tfmdata = check_afm(specification,fullname) +    end +    return tfmdata +end + +function readers.pfb(specification,method) -- only called when forced +    local original = specification.specification +    if trace_defining then +        report_afm("using afm reader for %a",original) +    end +    specification.specification = gsub(original,"%.pfb",".afm") +    specification.forced = "afm" +    return readers.afm(specification,method) +end | 
