From bf235a901844972cf6c64df72153b7b85d26aacc Mon Sep 17 00:00:00 2001
From: Marius <mariausol@gmail.com>
Date: Sat, 24 Aug 2013 15:20:23 +0300
Subject: beta 2013.08.24 13:59

---
 doc/context/scripts/mkiv/mtx-plain.html            |   1 +
 doc/context/scripts/mkiv/mtx-plain.man             |   3 +
 doc/context/scripts/mkiv/mtx-plain.xml             |   1 +
 scripts/context/lua/mtx-fonts.lua                  |   6 +-
 scripts/context/lua/mtx-plain.lua                  |   7 +
 tex/context/base/cont-new.mkiv                     |   2 +-
 tex/context/base/context-version.pdf               | Bin 4107 -> 4112 bytes
 tex/context/base/context.mkiv                      |   2 +-
 tex/context/base/font-tfm.lua                      |   2 +-
 tex/context/base/status-files.pdf                  | Bin 24739 -> 24768 bytes
 tex/context/base/status-lua.log                    |   2 +-
 tex/generic/context/luatex/luatex-basics-gen.lua   |   6 +
 tex/generic/context/luatex/luatex-fonts-merged.lua | 929 ++++++++++++++++++++-
 tex/generic/context/luatex/luatex-fonts.lua        |   4 +
 tex/generic/context/luatex/luatex-test.tex         |   4 +
 15 files changed, 961 insertions(+), 8 deletions(-)

diff --git a/doc/context/scripts/mkiv/mtx-plain.html b/doc/context/scripts/mkiv/mtx-plain.html
index 3e4ee2223..d7600397a 100644
--- a/doc/context/scripts/mkiv/mtx-plain.html
+++ b/doc/context/scripts/mkiv/mtx-plain.html
@@ -40,6 +40,7 @@
     <tr><th style="width: 10em">flag</th><th style="width: 8em">value</th><th>description</th></tr>
         <tr><th/><td/><td/></tr>
         <tr><th>--make</th><td></td><td>create format file</td></tr>
+        <tr><th>--fonts</th><td></td><td>create plain font database</td></tr>
         <tr><th>--run</th><td></td><td>process file</td></tr>
         <tr><th>--format</th><td>string</td><td>format name (default: luatex-plain)</td></tr>
         <tr><th>--engine</th><td>string</td><td>engine to use (default: luatex)</td></tr>
diff --git a/doc/context/scripts/mkiv/mtx-plain.man b/doc/context/scripts/mkiv/mtx-plain.man
index 0dbb24475..528c6a9fa 100644
--- a/doc/context/scripts/mkiv/mtx-plain.man
+++ b/doc/context/scripts/mkiv/mtx-plain.man
@@ -14,6 +14,9 @@
 .B --make
 create format file
 .TP
+.B --fonts
+create plain font database
+.TP
 .B --run
 process file
 .TP
diff --git a/doc/context/scripts/mkiv/mtx-plain.xml b/doc/context/scripts/mkiv/mtx-plain.xml
index 1c1b49170..a0d73279e 100644
--- a/doc/context/scripts/mkiv/mtx-plain.xml
+++ b/doc/context/scripts/mkiv/mtx-plain.xml
@@ -9,6 +9,7 @@
   <category name="basic">
    <subcategory>
     <flag name="make"><short>create format file</short></flag>
+    <flag name="fonts"><short>create plain font database</short></flag>
     <flag name="run"><short>process file</short></flag>
     <flag name="format" value="string"><short>format name (default: luatex-plain)</short></flag>
     <flag name="engine" value="string"><short>engine to use (default: luatex)</short></flag>
diff --git a/scripts/context/lua/mtx-fonts.lua b/scripts/context/lua/mtx-fonts.lua
index b171dd611..745f7b90a 100644
--- a/scripts/context/lua/mtx-fonts.lua
+++ b/scripts/context/lua/mtx-fonts.lua
@@ -132,9 +132,9 @@ function fonts.names.statistics()
 
 end
 
-function fonts.names.simple()
+function fonts.names.simple(alsotypeone)
     local simpleversion = 1.001
-    local simplelist = { "ttf", "otf", "ttc", "dfont" }
+    local simplelist = { "ttf", "otf", "ttc", "dfont", alsotypeone and "afm" or nil }
     local name = "luatex-fonts-names.lua"
     local path = file.collapsepath(caches.getwritablepath("..","..","generic","fonts","data"))
     fonts.names.filters.list = simplelist
@@ -182,7 +182,7 @@ end
 
 function scripts.fonts.reload()
     if getargument("simple") then
-        fonts.names.simple()
+        fonts.names.simple(getargument("typeone"))
     else
         fonts.names.load(true,getargument("force"))
     end
diff --git a/scripts/context/lua/mtx-plain.lua b/scripts/context/lua/mtx-plain.lua
index f43dcdeaf..d10c21375 100644
--- a/scripts/context/lua/mtx-plain.lua
+++ b/scripts/context/lua/mtx-plain.lua
@@ -24,6 +24,7 @@ local helpinfo = [[
   <category name="basic">
    <subcategory>
     <flag name="make"><short>create format file</short></flag>
+    <flag name="fonts"><short>create plain font database</short></flag>
     <flag name="run"><short>process file</short></flag>
     <flag name="format" value="string"><short>format name (default: luatex-plain)</short></flag>
     <flag name="engine" value="string"><short>engine to use (default: luatex)</short></flag>
@@ -105,6 +106,10 @@ function scripts.plain.run(texengine,texformat,filename)
     execute('%s --fmt=%s "%s"',texengine,file.removesuffix(texformat),filename)
 end
 
+function scripts.plain.fonts()
+    execute('mtxrun --script fonts --reload --simple --typeone')
+end
+
 local texformat = environment.arguments.texformat or environment.arguments.format
 local texengine = environment.arguments.texengine or environment.arguments.engine
 
@@ -122,6 +127,8 @@ if environment.arguments.exporthelp then
     application.export(environment.arguments.exporthelp,filename)
 elseif environment.arguments.make then
     scripts.plain.make(texengine,texformat)
+elseif environment.arguments.fonts then
+    scripts.plain.fonts()
 elseif filename then
     scripts.plain.run(texengine,texformat,filename)
 else
diff --git a/tex/context/base/cont-new.mkiv b/tex/context/base/cont-new.mkiv
index d791de76e..6c39a564d 100644
--- a/tex/context/base/cont-new.mkiv
+++ b/tex/context/base/cont-new.mkiv
@@ -11,7 +11,7 @@
 %C therefore copyrighted by \PRAGMA. See mreadme.pdf for
 %C details.
 
-\newcontextversion{2013.08.24 12:19}
+\newcontextversion{2013.08.24 13:59}
 
 %D This file is loaded at runtime, thereby providing an excellent place for
 %D hacks, patches, extensions and new features.
diff --git a/tex/context/base/context-version.pdf b/tex/context/base/context-version.pdf
index 8a2c919c3..55ce97c90 100644
Binary files a/tex/context/base/context-version.pdf and b/tex/context/base/context-version.pdf differ
diff --git a/tex/context/base/context.mkiv b/tex/context/base/context.mkiv
index 200b758bd..39b48487d 100644
--- a/tex/context/base/context.mkiv
+++ b/tex/context/base/context.mkiv
@@ -25,7 +25,7 @@
 %D up and the dependencies are more consistent.
 
 \edef\contextformat {\jobname}
-\edef\contextversion{2013.08.24 12:19}
+\edef\contextversion{2013.08.24 13:59}
 \edef\contextkind   {beta}
 
 %D For those who want to use this:
diff --git a/tex/context/base/font-tfm.lua b/tex/context/base/font-tfm.lua
index 316b947a3..827d70586 100644
--- a/tex/context/base/font-tfm.lua
+++ b/tex/context/base/font-tfm.lua
@@ -110,7 +110,7 @@ local function read_from_tfm(specification)
         constructors.applymanipulators("tfm",tfmdata,allfeatures.normal,trace_features,report_tfm)
         if not features.encoding then
             local encoding, filename = match(properties.filename,"^(.-)%-(.*)$") -- context: encoding-name.*
-            if filename and encoding and encodings.known[encoding] then
+            if filename and encoding and encodings.known and encodings.known[encoding] then
                 features.encoding = encoding
             end
         end
diff --git a/tex/context/base/status-files.pdf b/tex/context/base/status-files.pdf
index 888b04a52..863d58a34 100644
Binary files a/tex/context/base/status-files.pdf and b/tex/context/base/status-files.pdf differ
diff --git a/tex/context/base/status-lua.log b/tex/context/base/status-lua.log
index c0defbaf7..f4469c869 100644
--- a/tex/context/base/status-lua.log
+++ b/tex/context/base/status-lua.log
@@ -1,6 +1,6 @@
 (cont-yes.mkiv
 
-ConTeXt  ver: 2013.08.24 12:19 MKIV beta  fmt: 2013.8.24  int: english/english
+ConTeXt  ver: 2013.08.24 13:59 MKIV beta  fmt: 2013.8.24  int: english/english
 
 system          > 'cont-new.mkiv' loaded
 (cont-new.mkiv)
diff --git a/tex/generic/context/luatex/luatex-basics-gen.lua b/tex/generic/context/luatex/luatex-basics-gen.lua
index 26c1edc72..9cf5b9317 100644
--- a/tex/generic/context/luatex/luatex-basics-gen.lua
+++ b/tex/generic/context/luatex/luatex-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/tex/generic/context/luatex/luatex-fonts-merged.lua b/tex/generic/context/luatex/luatex-fonts-merged.lua
index 0a19be1bc..b39d4a8f3 100644
--- a/tex/generic/context/luatex/luatex-fonts-merged.lua
+++ b/tex/generic/context/luatex/luatex-fonts-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/tex/generic/context/luatex/luatex-fonts.lua b/tex/generic/context/luatex/luatex-fonts.lua
index 88d6a2f0e..1437686dd 100644
--- a/tex/generic/context/luatex/luatex-fonts.lua
+++ b/tex/generic/context/luatex/luatex-fonts.lua
@@ -201,6 +201,10 @@ if non_generic_context.luatex_fonts.skip_loading ~= true then
         loadmodule('font-cid.lua')
         loadmodule('font-map.lua')         -- for loading lum file (will be stripped)
         loadmodule('luatex-fonts-syn.lua') -- deals with font names (synonyms)
+        -- begin of test
+        loadmodule('font-tfm.lua')         -- optional
+        loadmodule('font-afm.lua')         -- optional
+        -- end of test
         loadmodule('luatex-fonts-tfm.lua')
         loadmodule('font-oti.lua')
         loadmodule('font-otf.lua')
diff --git a/tex/generic/context/luatex/luatex-test.tex b/tex/generic/context/luatex/luatex-test.tex
index fcc837e70..fbf8ce3cf 100644
--- a/tex/generic/context/luatex/luatex-test.tex
+++ b/tex/generic/context/luatex/luatex-test.tex
@@ -80,4 +80,8 @@ $$\left( { {1} \over { {1} \over {x} } } \right) $$
 
 $$\sqrt {2} { { {1} \over { {1} \over {x} } } } $$
 
+\font\cows=file:koeieletters.afm at 50pt
+
+\cows Hello World!
+
 \end
-- 
cgit v1.2.3