diff options
| -rw-r--r-- | filegraph.dot | 26 | ||||
| -rw-r--r-- | luaotfload-basics-gen.lua | 6 | ||||
| -rw-r--r-- | luaotfload-database.lua | 14 | ||||
| -rw-r--r-- | luaotfload-font-afm.lua | 971 | ||||
| -rw-r--r-- | luaotfload-loaders.lua | 152 | ||||
| -rw-r--r-- | luaotfload-merged.lua | 929 | ||||
| -rw-r--r-- | luaotfload.dtx | 4 | ||||
| -rwxr-xr-x | mkstatus | 1 | 
8 files changed, 965 insertions, 1138 deletions
diff --git a/filegraph.dot b/filegraph.dot index f69c751..0837032 100644 --- a/filegraph.dot +++ b/filegraph.dot @@ -63,17 +63,8 @@ strict digraph luaotfload_files { //looks weird with circo ...      luaotfload_libs -> font_names [label="luaotfload-database.lua"]      luaotfload_libs -> typo_krn   [label="luaotfload-extralibs.lua"] -    luaotfload_libs -> font_afm   [label="luaotfload-loaders.lua"]      luaotfload_libs -> font_afk   [label="luaotfload-loaders.lua"] -    subgraph cluster_afm { rank = same; -                           label = "AFM support"; -                           node [style=filled, color=white]; -                           style     = "filled,rounded"; -                           color     = "grey90:white"; -                           font_afm; -                           font_afk; } -      mkstatus    -> status     [label="generates from distribution files",                                 style=dashed] @@ -166,14 +157,6 @@ strict digraph luaotfload_files { //looks weird with circo ...                style  = "filled,rounded",                penwidth=2] -    font_afm [label  = "luaotfload-font-afm.lua", -              shape  = rect, -              width  = "3.2cm", -              height = "1.2cm", -              color  = "#01012222", -              style  = "filled,rounded", -              penwidth=2] -      typo_krn [label  = "luaotfload-typo-krn.lua",                shape  = rect,                width  = "3.2cm", @@ -289,10 +272,11 @@ strict digraph luaotfload_files { //looks weird with circo ...              <table border="0">                  <th> <td colspan="2"> <font point-size="12" face="Iwona Italic">Font Loader (LuaTeX-Fonts)</font> </td> </th>                  <tr> <td>luatex-basics-gen.lua</td> <td>luatex-basics-nod.lua</td> </tr> -                <tr> <td>luatex-fonts-enc.lua</td>  <td>luatex-fonts-syn.lua</td> </tr> -                <tr> <td>luatex-fonts-tfm.lua</td>  <td>luatex-fonts-chr.lua</td> </tr> -                <tr> <td>luatex-fonts-lua.lua</td>  <td>luatex-fonts-def.lua</td> </tr> -                <tr> <td>luatex-fonts-ext.lua</td>  <td>luatex-fonts-cbk.lua</td> </tr> +                <tr> <td>luatex-fonts-enc.lua</td>  <td>luatex-fonts-syn.lua</td>  </tr> +                <tr> <td>luatex-font-tfm.lua</td>   <td>luatex-font-afm.lua</td>   </tr> +                <tr> <td>luatex-fonts-tfm.lua</td>  <td>luatex-fonts-chr.lua</td>  </tr> +                <tr> <td>luatex-fonts-lua.lua</td>  <td>luatex-fonts-def.lua</td>  </tr> +                <tr> <td>luatex-fonts-ext.lua</td>  <td>luatex-fonts-cbk.lua</td>  </tr>              </table>          >,      ] diff --git a/luaotfload-basics-gen.lua b/luaotfload-basics-gen.lua index 26c1edc..9cf5b93 100644 --- a/luaotfload-basics-gen.lua +++ b/luaotfload-basics-gen.lua @@ -89,6 +89,7 @@ local remapper = {      fea    = "font feature files",      pfa    = "type1 fonts", -- this is for Khaled, in ConTeXt we don't use this!      pfb    = "type1 fonts", -- this is for Khaled, in ConTeXt we don't use this! +    afm    = "afm",  }  function resolvers.findfile(name,fileformat) @@ -117,6 +118,11 @@ end  resolvers.findbinfile = resolvers.findfile +function resolvers.loadbinfile(filename,filetype) +    local data = io.loaddata(filename) +    return true, data, #data +end +  function resolvers.resolve(s)      return s  end diff --git a/luaotfload-database.lua b/luaotfload-database.lua index d8c934d..2dbc20c 100644 --- a/luaotfload-database.lua +++ b/luaotfload-database.lua @@ -810,8 +810,7 @@ resolve = function (_, _, specification) -- the 1st two parameters are used by C           --- but it’s not a table, e.g. it contains an integer.          if not fonts_reloaded then              return reload_db("invalid database; not a table", -                             resolve, nil, nil, specification -                   ) +                             resolve, nil, nil, specification)          end          --- unsucessfully reloaded; bail          return specification.name, false, false @@ -1004,12 +1003,21 @@ end  --- string -> ('a -> 'a) -> 'a list -> 'a  reload_db = function (why, caller, ...) -    report ("both", 1, "db", "Reload initiated; reason: %q", why) +    local namedata  = names.data +    local formats   = tableconcat (namedata.formats, ",") + +    report ("both", 1, "db", +            "Reload initiated (formats: %s); reason: %q", +            formats, why) + +    set_font_filter (formats)      names.data = update_names (names.data, false, false) +      if names.data then          fonts_reloaded = true          return caller (...)      end +      report ("both", 0, "db", "Database update unsuccessful.")  end diff --git a/luaotfload-font-afm.lua b/luaotfload-font-afm.lua deleted file mode 100644 index cb0c243..0000000 --- a/luaotfload-font-afm.lua +++ /dev/null @@ -1,971 +0,0 @@ -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 diff --git a/luaotfload-loaders.lua b/luaotfload-loaders.lua index 6d6f409..20eb277 100644 --- a/luaotfload-loaders.lua +++ b/luaotfload-loaders.lua @@ -11,158 +11,32 @@ local readers         = fonts.readers  local handlers        = fonts.handlers  local formats         = fonts.formats -local lfsisfile       = lfs.isfile -local fileaddsuffix   = file.addsuffix -local filebasename    = file.basename -local stringsub       = string.sub -local stringlower     = string.lower -local stringupper     = string.upper -local findbinfile     = resolvers.findbinfile - -local lpeg            = require "lpeg" -local lpegmatch       = lpeg.match -local P, S, Cp        = lpeg.P, lpeg.S, lpeg.Cp - -resolvers.openbinfile = function (filename) -    if filename and filename ~= "" then -        local f = io.open(filename,"rb") -        if f then -            --logs.show_load(filename) -            local s = f:read("*a") -- io.readall(f) is faster but we never have large files here -            if checkgarbage then -                checkgarbage(#s) -            end -            f:close() -            if s then -                return true, s, #s -            end -        end -    end -    return loaders.notfound() -end - -resolvers.loadbinfile = function (filename, filetype) - -    local fname = kpse.find_file (filename, filetype) - -    if fname and fname ~= "" then -        return resolvers.openbinfile(fname) -    else -        return resolvers.loaders.notfound() -    end - -end - ---- this function is required because AFM precedes TFM in the reader ---- chain (see definers.loadfont() in font-def.lua - -local check_tfm = function (specification, fullname) - -    local foundname = findbinfile (fullname, "tfm") or "" - -    if foundname == "" then -        foundname = findbinfile (fullname, "ofm") or "" -    end - -    if foundname == "" then -        foundname = fonts.names.getfilename (fullname,"tfm") or "" -    end - -    if foundname ~= "" then -        specification.filename = foundname -        specification.format   = "ofm" -        return font.read_tfm (specification.filename, -                              specification.size) -    end -end - -readers.check_tfm = check_tfm - ---[[ <EXPERIMENTAL> ]] -  --[[doc-- -  Here we load extra AFM libraries from Context. -  In fact, part of the AFM support is contained in font-ext.lua, for -  which the font loader has a replacement: luatex-fonts-ext.lua. -  However, this is only a stripped down version with everything AFM -  removed. For example, it lacks definitions of several AFM features -  like italic correction, protrusion, expansion and so on. In order to -  achieve full-fledged AFM support we will either have to implement our -  own version of these or consult with Hans whether he would consider -  including the AFM code with the font loader. +  As of 2013-08-25 AFM support has been included in the fontloader +  package. -  For the time being we stick with two AFM-specific libraries: -  font-afm.lua and font-afk.lua. When combined, these already supply us -  with basic features like kerning and ligatures. The rest can be added -  in due time. +  We still have to load the font feature definitions (font-afk.lua) to +  achieve kerning and ligatures.  --doc]]-- -require "luaotfload-font-afm.lua"  require "luaotfload-font-afk.lua" ---[[ </EXPERIMENTAL> ]] - ---[[doc-- - -    The PFB/PFA reader checks whether there is a corresponding AFM file -    and hands the spec over to the AFM loader if appropriate.  Context -    uses string.gsub() to accomplish this but that can cause collateral -    damage. - ---doc]]-- - -local mk_type1_reader = function (format) - -  format          = stringlower (format) -  local first     = stringsub (format, 1, 1) -  local second    = stringsub (format, 2, 2) -  local third     = stringsub (format, 3, 3) - -  local p_format      = P"." -                      * (P(first)   + P(stringupper (first))) -                      * (P(second)  + P(stringupper (second))) -                      * (P(third)   + P(stringupper (third))) -  ---                  we have to be careful here so we don’t affect -  ---                  harmless substrings -                      * (P"("    --- subfont -                       + P":"    --- feature list -                       + P(-1))  --- end of string -  local no_format     = 1 - p_format -  local p_format_file = no_format^1 * Cp() * p_format * Cp() - -  local reader = function (specification, method) - -    local afmfile = fileaddsuffix (specification.name, "afm") - -    if lfsisfile (afmfile) then -      --- switch to afm reader -      logs.names_report ("log", 0, "type1", -                         "Found corresponding AFM file %s, using that.", -                         filebasename (afmfile)) -      local oldspec = specification.specification -      local before, after = lpegmatch (p_format_file, oldspec) -      specification.specification = stringsub (oldspec, 1, before) -                                 .. "afm" -                                 .. stringsub (oldspec, after - 1) -      specification.forced = "afm" -      return readers.afm (specification, method) -    end - -    --- else read pfb via opentype mechanism -    return readers.opentype (specification, format, "type1") -  end - -  return reader +local pfb_reader = function (specification) +  return readers.opentype (specification, "pfb", "type1") +end  +  +local pfa_reader = function (specification) +  return readers.opentype (specification, "pfa", "type1")  end  formats.pfa  = "type1" -readers.pfa  = mk_type1_reader "pfa" +readers.pfa  = pfa_reader  handlers.pfa = { }  formats.pfb  = "type1" -readers.pfb  = mk_type1_reader "pfb" -handlers.pfb = { }  --- empty, as with tfm +readers.pfb  = pfb_reader +handlers.pfb = { }  -- vim:tw=71:sw=2:ts=2:expandtab diff --git a/luaotfload-merged.lua b/luaotfload-merged.lua index 0a19be1..b39d4a8 100644 --- a/luaotfload-merged.lua +++ b/luaotfload-merged.lua @@ -1,6 +1,6 @@  -- merged file : luatex-fonts-merged.lua  -- parent file : luatex-fonts.lua --- merge date  : 08/24/13 12:19:21 +-- merge date  : 08/24/13 13:59:18  do -- begin closure to overcome local limits and interference @@ -3060,6 +3060,7 @@ local remapper={    fea="font feature files",    pfa="type1 fonts",    pfb="type1 fonts", +  afm="afm",  }  function resolvers.findfile(name,fileformat)    name=string.gsub(name,"\\","/") @@ -3078,6 +3079,10 @@ function resolvers.findfile(name,fileformat)    return found  end  resolvers.findbinfile=resolvers.findfile +function resolvers.loadbinfile(filename,filetype) +  local data=io.loaddata(filename) +  return true,data,#data +end  function resolvers.resolve(s)    return s  end @@ -5065,6 +5070,928 @@ end -- closure  do -- begin closure to overcome local limits and interference +if not modules then modules={} end modules ['font-tfm']={ +  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" +} +local next=next +local match=string.match +local trace_defining=false trackers.register("fonts.defining",function(v) trace_defining=v end) +local trace_features=false trackers.register("tfm.features",function(v) trace_features=v end) +local report_defining=logs.reporter("fonts","defining") +local report_tfm=logs.reporter("fonts","tfm loading") +local findbinfile=resolvers.findbinfile +local fonts=fonts +local handlers=fonts.handlers +local readers=fonts.readers +local constructors=fonts.constructors +local encodings=fonts.encodings +local tfm=constructors.newhandler("tfm") +local tfmfeatures=constructors.newfeatures("tfm") +local registertfmfeature=tfmfeatures.register +constructors.resolvevirtualtoo=false  +fonts.formats.tfm="type1" +function tfm.setfeatures(tfmdata,features) +  local okay=constructors.initializefeatures("tfm",tfmdata,features,trace_features,report_tfm) +  if okay then +    return constructors.collectprocessors("tfm",tfmdata,features,trace_features,report_tfm) +  else +    return {}  +  end +end +local function read_from_tfm(specification) +  local filename=specification.filename +  local size=specification.size +  if trace_defining then +    report_defining("loading tfm file %a at size %s",filename,size) +  end +  local tfmdata=font.read_tfm(filename,size)  +  if tfmdata then +    local features=specification.features and specification.features.normal or {} +    local resources=tfmdata.resources or {} +    local properties=tfmdata.properties or {} +    local parameters=tfmdata.parameters or {} +    local shared=tfmdata.shared   or {} +    properties.name=tfmdata.name +    properties.fontname=tfmdata.fontname +    properties.psname=tfmdata.psname +    properties.filename=specification.filename +    parameters.size=size +    shared.rawdata={} +    shared.features=features +    shared.processes=next(features) and tfm.setfeatures(tfmdata,features) or nil +    tfmdata.properties=properties +    tfmdata.resources=resources +    tfmdata.parameters=parameters +    tfmdata.shared=shared +    parameters.slant=parameters.slant     or parameters[1] or 0 +    parameters.space=parameters.space     or parameters[2] or 0 +    parameters.space_stretch=parameters.space_stretch or parameters[3] or 0 +    parameters.space_shrink=parameters.space_shrink  or parameters[4] or 0 +    parameters.x_height=parameters.x_height    or parameters[5] or 0 +    parameters.quad=parameters.quad      or parameters[6] or 0 +    parameters.extra_space=parameters.extra_space  or parameters[7] or 0 +    constructors.enhanceparameters(parameters) +    if constructors.resolvevirtualtoo then +      fonts.loggers.register(tfmdata,file.suffix(filename),specification)  +      local vfname=findbinfile(specification.name,'ovf') +      if vfname and vfname~="" then +        local vfdata=font.read_vf(vfname,size)  +        if vfdata then +          local chars=tfmdata.characters +          for k,v in next,vfdata.characters do +            chars[k].commands=v.commands +          end +          properties.virtualized=true +          tfmdata.fonts=vfdata.fonts +        end +      end +    end +    local allfeatures=tfmdata.shared.features or specification.features.normal +    constructors.applymanipulators("tfm",tfmdata,allfeatures.normal,trace_features,report_tfm) +    if not features.encoding then +      local encoding,filename=match(properties.filename,"^(.-)%-(.*)$")  +      if filename and encoding and encodings.known and encodings.known[encoding] then +        features.encoding=encoding +      end +    end +    return tfmdata +  end +end +local function check_tfm(specification,fullname)  +  local foundname=findbinfile(fullname,'tfm') or "" +  if foundname=="" then +    foundname=findbinfile(fullname,'ofm') or ""  +  end +  if foundname=="" then +    foundname=fonts.names.getfilename(fullname,"tfm") or "" +  end +  if foundname~="" then +    specification.filename=foundname +    specification.format="ofm" +    return read_from_tfm(specification) +  elseif trace_defining then +    report_defining("loading tfm with name %a fails",specification.name) +  end +end +readers.check_tfm=check_tfm +function readers.tfm(specification) +  local fullname=specification.filename or "" +  if fullname=="" then +    local forced=specification.forced or "" +    if forced~="" then +      fullname=specification.name.."."..forced +    else +      fullname=specification.name +    end +  end +  return check_tfm(specification,fullname) +end + +end -- closure + +do -- begin closure to overcome local limits and interference + +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" +} +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  +afm.cache=containers.define("fonts","afm",afm.version,true) +afm.autoprefixed=true  +afm.helpdata={}  +afm.syncspace=true  +afm.addligatures=true  +afm.addtexligatures=true  +afm.addkerns=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, +  } +} +local comment=P("Comment") +local spacing=patterns.spacer  +local lineend=patterns.newline  +local words=C((1-lineend)^1) +local number=C((R("09")+S("."))^1)/tonumber*spacing^0 +local data=lpeg.Carg(1) +local pattern=( +  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 +local keys={} +function keys.FontName  (data,line) data.metadata.fontname=strip  (line)  +                   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) +  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)  +  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)  +  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={ +      }, +      descriptions={ +      }, +    } +    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)  +      return "" +    end) +    return data +  else +    if trace_loading then +      report_afm("no valid afm file %a",filename) +    end +    return nil +  end +end +local addkerns,addligatures,addtexligatures,unify,normalize  +function afm.load(filename) +  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  +  local unicodes,names={},{} +  local private=constructors.privateoffset +  local descriptions=data.descriptions +  for name,blob in next,data.characters do +    local code=unicodevector[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)  +  resources.unicodes=unicodes  +  resources.marks={}  +  resources.names=names  +  resources.private=private +end +normalize=function(data) +end +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 +addkerns=function(rawdata)  +  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  +            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 +  do_it_left(afm.helpdata.leftkerned) +  do_it_left(afm.helpdata.bothkerned) +  do_it_copy(afm.helpdata.bothkerned) +  do_it_copy(afm.helpdata.rightkerned) +end +local function adddimensions(data)  +  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 +        else +          description.height=ht +        end +        if dp==0 or dp<0 then +        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  +      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 +    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 +    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.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 +      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  +      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 +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 {}  +  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  +    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  +    end +  end +  if afmname~="" then +    local features=constructors.checkedfeatures("afm",specification.features.normal) +    specification.features.normal=features +    constructors.hashinstance(specification,true) +    specification=definers.resolve(specification)  +    local cache_id=specification.hash +    local tfmdata=containers.read(constructors.cache,cache_id)  +    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 +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 +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={ +  [0x0027]=0x2019, +} +local function texreplacements(tfmdata,value) +  local descriptions=tfmdata.descriptions +  local characters=tfmdata.characters +  for k,v in next,list do +    characters [k]=characters [v]  +    descriptions[k]=descriptions[v]  +  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, +  } +} +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 ""  +  if foundname=="" then +    foundname=fonts.names.getfilename(fullname,"afm") or "" +  end +  if foundname=="" and afm.autoprefixed then +    local encoding,shortname=match(fullname,"^(.-)%-(.*)$")  +    if encoding and shortname and fonts.encodings.known[encoding] then +      shortname=findbinfile(shortname,'afm') or ""  +      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  +        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)  +  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 + +end -- closure + +do -- begin closure to overcome local limits and interference +  if not modules then modules={} end modules ['luatex-fonts-tfm']={    version=1.001,    comment="companion to luatex-*.tex", diff --git a/luaotfload.dtx b/luaotfload.dtx index 56e9d4d..622c902 100644 --- a/luaotfload.dtx +++ b/luaotfload.dtx @@ -1217,10 +1217,10 @@ and the derived files  %                                            Context libraries.  %     \ouritem {luaotfload-letterspace.lua}  font-based letterspacing.  %     \ouritem {luaotfload-typo-krn.lua}     attribute-based letterspacing. -%     \ouritem {luaotfload-font-afm.lua}     \abbrev{afm} loading.  %     \ouritem {luaotfload-font-afk.lua}     supplementary glyph  %                                            information for \abbrev{afm} -%                                            fonts. +%                                            fonts (enables ligatures +%                                            and kerning).  % \end{itemize}  %  % \begin{figure}[b] @@ -43,7 +43,6 @@ local names = {    "luaotfload-extralibs.lua",    "luaotfload-features.lua",    "luaotfload-font-afk.lua", -  "luaotfload-font-afm.lua",    "luaotfload-fonts-cbk.lua",    "luaotfload-fonts-def.lua",    "luaotfload-fonts-enc.lua",  | 
