From 68eaaa92a2235f4b56f45168e987146171cb1518 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <phg@phi-gamma.net>
Date: Mon, 21 Dec 2015 21:17:36 +0100
Subject: [features] import latest changes to the feature handler

---
 src/luaotfload-features.lua | 14 ++++++--------
 1 file changed, 6 insertions(+), 8 deletions(-)

diff --git a/src/luaotfload-features.lua b/src/luaotfload-features.lua
index d212df5..fc04bce 100644
--- a/src/luaotfload-features.lua
+++ b/src/luaotfload-features.lua
@@ -1110,7 +1110,7 @@ local function ancient_addfeature (data, feature, specifications)
     end
 end
 
-local function current_addfeature(data,feature,specifications)
+local function addfeature(data,feature,specifications)
     local descriptions = data.descriptions
     local resources    = data.resources
     local features     = resources.features
@@ -1158,18 +1158,17 @@ local function current_addfeature(data,feature,specifications)
             end
             local askedfeatures = specification.features or everywhere
             local askedsteps    = specifications.steps or specification.subtables or { specification.data } or { }
-            local defaulttype   = specification.type or "substitution"
+            local featuretype   = normalized[specification.type or "substitution"] or "substitution"
             local featureflags  = specification.flags or noflags
             local featureorder  = specification.order or { feature }
             local added         = false
             local nofsteps      = 0
             local steps         = { }
             for i=1,#askedsteps do
-                local list        = askedsteps[i]
-                local coverage    = { }
-                local cover       = coveractions[featuretype]
-                local format      = nil
-                local featuretype = normalized[list.type or defaulttype] or "substitution"
+                local list     = askedsteps[i]
+                local coverage = { }
+                local cover    = coveractions[featuretype]
+                local format   = nil
                 if not cover then
                     -- unknown
                 elseif featuretype == "substitution" then
@@ -1355,7 +1354,6 @@ local function current_addfeature(data,feature,specifications)
     end
 end
 
-
 ---[[ end snippet from font-otc.lua ]]
 
 local tlig_order = { "tlig" }
-- 
cgit v1.2.3


From bd7d7d0b5faf23ae1f00fe18e760fda909e022db Mon Sep 17 00:00:00 2001
From: Philipp Gesang <phg@phi-gamma.net>
Date: Mon, 21 Dec 2015 22:49:06 +0100
Subject: [init] set up stubs for pre-0.87 Luatex

---
 scripts/mkimport                             |   12 +-
 scripts/mkstatus                             |    4 +-
 src/fontloader/misc/fontloader-fonts-inj.lua | 1152 --------
 src/fontloader/misc/fontloader-fonts-otn.lua | 3848 --------------------------
 src/luaotfload-init.lua                      |   40 +-
 5 files changed, 44 insertions(+), 5012 deletions(-)
 delete mode 100644 src/fontloader/misc/fontloader-fonts-inj.lua
 delete mode 100644 src/fontloader/misc/fontloader-fonts-otn.lua

diff --git a/scripts/mkimport b/scripts/mkimport
index b3c71fe..08537d7 100755
--- a/scripts/mkimport
+++ b/scripts/mkimport
@@ -196,11 +196,9 @@ local imports = {
     { name = "fonts-demo-vf-1"   , ours = nil          , kind = kind_ignored   },
     { name = "fonts-enc"         , ours = nil          , kind = kind_merged    },
     { name = "fonts-ext"         , ours = nil          , kind = kind_merged    },
-    { name = "fonts-inj"         , ours = nil          , kind = kind_merged    },
     { name = "fonts-lua"         , ours = nil          , kind = kind_merged    },
     { name = "fonts-merged"      , ours = "reference"  , kind = kind_essential },
     { name = "fonts-ota"         , ours = nil          , kind = kind_merged    },
-    { name = "fonts-otn"         , ours = nil          , kind = kind_merged    },
     { name = "fonts"             , ours = nil          , kind = kind_merged    },
     { name = "fonts"             , ours = nil          , kind = kind_tex       },
     { name = "fonts-syn"         , ours = nil          , kind = kind_ignored   },
@@ -230,10 +228,12 @@ local imports = {
     { name = "font-con"          , ours = "font-con"          , kind = kind_merged    },
     { name = "font-def"          , ours = "font-def"          , kind = kind_merged    },
     { name = "font-ini"          , ours = "font-ini"          , kind = kind_merged    },
+    { name = "font-inj"          , ours = "font-inj"          , kind = kind_merged    },
     { name = "font-map"          , ours = "font-map"          , kind = kind_merged    },
     { name = "font-otb"          , ours = "font-otb"          , kind = kind_merged    },
     { name = "font-otf"          , ours = "font-otf"          , kind = kind_merged    },
     { name = "font-oti"          , ours = "font-oti"          , kind = kind_merged    },
+    { name = "font-otn"          , ours = "font-otn"          , kind = kind_merged    },
     { name = "font-otp"          , ours = "font-otp"          , kind = kind_merged    },
     { name = "font-tfm"          , ours = "font-tfm"          , kind = kind_merged    },
     { name = "l-boolean"         , ours = "l-boolean"         , kind = kind_lualibs   },
@@ -282,9 +282,9 @@ local package = {
 ---   [24] font-oti.lua
 ---   [25] font-otf.lua
 ---   [26] font-otb.lua
----   [27] luatex-fonts-inj.lua
+---   [27] font-inj.lua
 ---   [28] luatex-fonts-ota.lua
----   [29] luatex-fonts-otn.lua
+---   [30] font-otn.lua
 ---   [30] font-otp.lua
 ---   [31] luatex-fonts-lua.lua
 ---   [32] font-def.lua
@@ -336,9 +336,9 @@ local package = {
     "font-oti",
     "font-otf",
     "font-otb",
-    "fonts-inj",
+    "font-inj",
     "fonts-ota",
-    "fonts-otn",
+    "font-otn",
     "font-otp",
     "fonts-lua",
     "font-def",
diff --git a/scripts/mkstatus b/scripts/mkstatus
index 4b36a9d..62161a8 100755
--- a/scripts/mkstatus
+++ b/scripts/mkstatus
@@ -101,11 +101,11 @@ local names = {
   { miscdir,      "fontloader-fonts-demo-vf-1.lua", },
   { miscdir,      "fontloader-fonts-enc.lua",       },
   { miscdir,      "fontloader-fonts-ext.lua",       },
-  { miscdir,      "fontloader-fonts-inj.lua",       },
+  { miscdir,      "fontloader-font-inj.lua",        },
   { miscdir,      "fontloader-fonts.lua",           },
   { miscdir,      "fontloader-fonts-lua.lua",       },
   { miscdir,      "fontloader-fonts-ota.lua",       },
-  { miscdir,      "fontloader-fonts-otn.lua",       },
+  { miscdir,      "fontloader-font-otn.lua",        },
   { miscdir,      "fontloader-fonts-syn.lua",       },
   { miscdir,      "fontloader-fonts-tfm.lua",       },
   { miscdir,      "fontloader-font-tfm.lua",        },
diff --git a/src/fontloader/misc/fontloader-fonts-inj.lua b/src/fontloader/misc/fontloader-fonts-inj.lua
deleted file mode 100644
index 36781f7..0000000
--- a/src/fontloader/misc/fontloader-fonts-inj.lua
+++ /dev/null
@@ -1,1152 +0,0 @@
-if not modules then modules = { } end modules ['font-inj'] = {
-    version   = 1.001,
-    comment   = "companion to font-lib.mkiv",
-    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
-    copyright = "PRAGMA ADE / ConTeXt Development Team",
-    license   = "see context related readme files",
-}
-
--- This property based variant is not faster but looks nicer than the attribute one. We
--- need to use rawget (which is apbout 4 times slower than a direct access but we cannot
--- get/set that one for our purpose! This version does a bit more with discretionaries
--- (and Kai has tested it with his collection of weird fonts.)
-
--- There is some duplicate code here (especially in the the pre/post/replace branches) but
--- we go for speed. We could store a list of glyph and mark nodes when registering but it's
--- cleaner to have an identification pass here. Also, I need to keep tracing in mind so
--- being too clever here is dangerous.
-
--- The subtype test is not needed as there will be no (new) properties set, given that we
--- reset the properties.
-
-if not nodes.properties then return end
-
-local next, rawget = next, rawget
-local utfchar = utf.char
-local fastcopy = table.fastcopy
-
-local trace_injections = false  trackers.register("fonts.injections", function(v) trace_injections = v end)
-
-local report_injections = logs.reporter("fonts","injections")
-
-local attributes, nodes, node = attributes, nodes, node
-
-fonts                    = fonts
-local fontdata           = fonts.hashes.identifiers
-
-nodes.injections         = nodes.injections or { }
-local injections         = nodes.injections
-
-local nodecodes          = nodes.nodecodes
-local glyph_code         = nodecodes.glyph
-local disc_code          = nodecodes.disc
-local kern_code          = nodecodes.kern
-
-local nuts               = nodes.nuts
-local nodepool           = nuts.pool
-
-local newkern            = nodepool.kern
-
-local tonode             = nuts.tonode
-local tonut              = nuts.tonut
-
-local getfield           = nuts.getfield
-local setfield           = nuts.setfield
-local getnext            = nuts.getnext
-local getprev            = nuts.getprev
-local getid              = nuts.getid
-local getfont            = nuts.getfont
-local getsubtype         = nuts.getsubtype
-local getchar            = nuts.getchar
-
-local traverse_id        = nuts.traverse_id
-local insert_node_before = nuts.insert_before
-local insert_node_after  = nuts.insert_after
-local find_tail          = nuts.tail
-
-local properties         = nodes.properties.data
-
-function injections.installnewkern(nk)
-    newkern = nk or newkern
-end
-
-local nofregisteredkerns    = 0
-local nofregisteredpairs    = 0
-local nofregisteredmarks    = 0
-local nofregisteredcursives = 0
------ markanchors           = { } -- one base can have more marks
-local keepregisteredcounts  = false
-
-function injections.keepcounts()
-    keepregisteredcounts = true
-end
-
-function injections.resetcounts()
-    nofregisteredkerns    = 0
-    nofregisteredpairs    = 0
-    nofregisteredmarks    = 0
-    nofregisteredcursives = 0
-    keepregisteredcounts  = false
-end
-
--- We need to make sure that a possible metatable will not kick in unexpectedly.
-
-function injections.reset(n)
-    local p = rawget(properties,n)
-    if p and rawget(p,"injections") then
-        p.injections = nil
-    end
-end
-
-function injections.copy(target,source)
-    local sp = rawget(properties,source)
-    if sp then
-        local tp = rawget(properties,target)
-        local si = rawget(sp,"injections")
-        if si then
-            si = fastcopy(si)
-            if tp then
-                tp.injections = si
-            else
-                propertydata[target] = {
-                    injections = si,
-                }
-            end
-        else
-            if tp then
-                tp.injections = nil
-            end
-        end
-    end
-end
-
-function injections.setligaindex(n,index)
-    local p = rawget(properties,n)
-    if p then
-        local i = rawget(p,"injections")
-        if i then
-            i.ligaindex = index
-        else
-            p.injections = {
-                ligaindex = index
-            }
-        end
-    else
-        properties[n] = {
-            injections = {
-                ligaindex = index
-            }
-        }
-    end
-end
-
-function injections.getligaindex(n,default)
-    local p = rawget(properties,n)
-    if p then
-        local i = rawget(p,"injections")
-        if i then
-            return i.ligaindex or default
-        end
-    end
-    return default
-end
-
-function injections.setcursive(start,nxt,factor,rlmode,exit,entry,tfmstart,tfmnext) -- hm: nuts or nodes
-    local dx =  factor*(exit[1]-entry[1])
-    local dy = -factor*(exit[2]-entry[2])
-    local ws = tfmstart.width
-    local wn = tfmnext.width
-    nofregisteredcursives = nofregisteredcursives + 1
-    if rlmode < 0 then
-        dx = -(dx + wn)
-    else
-        dx = dx - ws
-    end
-    --
-    local p = rawget(properties,start)
-    if p then
-        local i = rawget(p,"injections")
-        if i then
-            i.cursiveanchor = true
-        else
-            p.injections = {
-                cursiveanchor = true,
-            }
-        end
-    else
-        properties[start] = {
-            injections = {
-                cursiveanchor = true,
-            },
-        }
-    end
-    local p = rawget(properties,nxt)
-    if p then
-        local i = rawget(p,"injections")
-        if i then
-            i.cursivex = dx
-            i.cursivey = dy
-        else
-            p.injections = {
-                cursivex = dx,
-                cursivey = dy,
-            }
-        end
-    else
-        properties[nxt] = {
-            injections = {
-                cursivex = dx,
-                cursivey = dy,
-            },
-        }
-    end
-    return dx, dy, nofregisteredcursives
-end
-
-function injections.setpair(current,factor,rlmode,r2lflag,spec,injection) -- r2lflag & tfmchr not used
-    local x = factor*spec[1]
-    local y = factor*spec[2]
-    local w = factor*spec[3]
-    local h = factor*spec[4]
-    if x ~= 0 or w ~= 0 or y ~= 0 or h ~= 0 then -- okay?
-        local yoffset   = y - h
-        local leftkern  = x      -- both kerns are set in a pair kern compared
-        local rightkern = w - x  -- to normal kerns where we set only leftkern
-        if leftkern ~= 0 or rightkern ~= 0 or yoffset ~= 0 then
-            nofregisteredpairs = nofregisteredpairs + 1
-            if rlmode and rlmode < 0 then
-                leftkern, rightkern = rightkern, leftkern
-            end
-            if not injection then
-                injection = "injections"
-            end
-            local p = rawget(properties,current)
-            if p then
-                local i = rawget(p,injection)
-                if i then
-                    if leftkern ~= 0 then
-                        i.leftkern  = (i.leftkern  or 0) + leftkern
-                    end
-                    if rightkern ~= 0 then
-                        i.rightkern = (i.rightkern or 0) + rightkern
-                    end
-                    if yoffset ~= 0 then
-                        i.yoffset = (i.yoffset or 0) + yoffset
-                    end
-                elseif leftkern ~= 0 or rightkern ~= 0 then
-                    p[injection] = {
-                        leftkern  = leftkern,
-                        rightkern = rightkern,
-                        yoffset   = yoffset,
-                    }
-                else
-                    p[injection] = {
-                        yoffset = yoffset,
-                    }
-                end
-            elseif leftkern ~= 0 or rightkern ~= 0 then
-                properties[current] = {
-                    [injection] = {
-                        leftkern  = leftkern,
-                        rightkern = rightkern,
-                        yoffset   = yoffset,
-                    },
-                }
-            else
-                properties[current] = {
-                    [injection] = {
-                        yoffset = yoffset,
-                    },
-                }
-            end
-            return x, y, w, h, nofregisteredpairs
-         end
-    end
-    return x, y, w, h -- no bound
-end
-
--- This needs checking for rl < 0 but it is unlikely that a r2l script uses kernclasses between
--- glyphs so we're probably safe (KE has a problematic font where marks interfere with rl < 0 in
--- the previous case)
-
-function injections.setkern(current,factor,rlmode,x,injection)
-    local dx = factor * x
-    if dx ~= 0 then
-        nofregisteredkerns = nofregisteredkerns + 1
-        local p = rawget(properties,current)
-        if not injection then
-            injection = "injections"
-        end
-        if p then
-            local i = rawget(p,injection)
-            if i then
-                i.leftkern = dx + (i.leftkern or 0)
-            else
-                p[injection] = {
-                    leftkern = dx,
-                }
-            end
-        else
-            properties[current] = {
-                [injection] = {
-                    leftkern = dx,
-                },
-            }
-        end
-        return dx, nofregisteredkerns
-    else
-        return 0, 0
-    end
-end
-
-function injections.setmark(start,base,factor,rlmode,ba,ma,tfmbase,mkmk) -- ba=baseanchor, ma=markanchor
-    local dx, dy = factor*(ba[1]-ma[1]), factor*(ba[2]-ma[2])
-    nofregisteredmarks = nofregisteredmarks + 1
- -- markanchors[nofregisteredmarks] = base
-    if rlmode >= 0 then
-        dx = tfmbase.width - dx -- see later commented ox
-    end
-    local p = rawget(properties,start)
-    -- hm, dejavu serif does a sloppy mark2mark before mark2base
-    if p then
-        local i = rawget(p,"injections")
-        if i then
-            if i.markmark then
-                -- out of order mkmk: yes or no or option
-            else
-                i.markx        = dx
-                i.marky        = dy
-                i.markdir      = rlmode or 0
-                i.markbase     = nofregisteredmarks
-                i.markbasenode = base
-                i.markmark     = mkmk
-            end
-        else
-            p.injections = {
-                markx        = dx,
-                marky        = dy,
-                markdir      = rlmode or 0,
-                markbase     = nofregisteredmarks,
-                markbasenode = base,
-                markmark     = mkmk,
-            }
-        end
-    else
-        properties[start] = {
-            injections = {
-                markx        = dx,
-                marky        = dy,
-                markdir      = rlmode or 0,
-                markbase     = nofregisteredmarks,
-                markbasenode = base,
-                markmark     = mkmk,
-            },
-        }
-    end
-    return dx, dy, nofregisteredmarks
-end
-
-local function dir(n)
-    return (n and n<0 and "r-to-l") or (n and n>0 and "l-to-r") or "unset"
-end
-
-local function showchar(n,nested)
-    local char = getchar(n)
-    report_injections("%wfont %s, char %U, glyph %c",nested and 2 or 0,getfont(n),char,char)
-end
-
-local function show(n,what,nested,symbol)
-    if n then
-        local p = rawget(properties,n)
-        if p then
-            local i = rawget(p,what)
-            if i then
-                local leftkern  = i.leftkern  or 0
-                local rightkern = i.rightkern or 0
-                local yoffset   = i.yoffset   or 0
-                local markx     = i.markx     or 0
-                local marky     = i.marky     or 0
-                local markdir   = i.markdir   or 0
-                local markbase  = i.markbase  or 0 -- will be markbasenode
-                local cursivex  = i.cursivex  or 0
-                local cursivey  = i.cursivey  or 0
-                local ligaindex = i.ligaindex or 0
-                local margin    = nested and 4 or 2
-                --
-                if rightkern ~= 0 or yoffset ~= 0 then
-                    report_injections("%w%s pair: lx %p, rx %p, dy %p",margin,symbol,leftkern,rightkern,yoffset)
-                elseif leftkern ~= 0 then
-                    report_injections("%w%s kern: dx %p",margin,symbol,leftkern)
-                end
-                if markx ~= 0 or marky ~= 0 or markbase ~= 0 then
-                    report_injections("%w%s mark: dx %p, dy %p, dir %s, base %s",margin,symbol,markx,marky,markdir,markbase ~= 0 and "yes" or "no")
-                end
-                if cursivex ~= 0 or cursivey ~= 0 then
-                    report_injections("%w%s curs: dx %p, dy %p",margin,symbol,cursivex,cursivey)
-                end
-                if ligaindex ~= 0 then
-                    report_injections("%w%s liga: index %i",margin,symbol,ligaindex)
-                end
-            end
-        end
-    end
-end
-
-local function showsub(n,what,where)
-    report_injections("begin subrun: %s",where)
-    for n in traverse_id(glyph_code,n) do
-        showchar(n,where)
-        show(n,what,where," ")
-    end
-    report_injections("end subrun")
-end
-
-local function trace(head,where)
-    report_injections("begin run %s: %s kerns, %s pairs, %s marks and %s cursives registered",
-        where or "",nofregisteredkerns,nofregisteredpairs,nofregisteredmarks,nofregisteredcursives)
-    local n = head
-    while n do
-        local id = getid(n)
-        if id == glyph_code then
-            showchar(n)
-            show(n,"injections",false," ")
-            show(n,"preinjections",false,"<")
-            show(n,"postinjections",false,">")
-            show(n,"replaceinjections",false,"=")
-        elseif id == disc_code then
-            local pre     = getfield(n,"pre")
-            local post    = getfield(n,"post")
-            local replace = getfield(n,"replace")
-            if pre then
-                showsub(pre,"preinjections","pre")
-            end
-            if post then
-                showsub(post,"postinjections","post")
-            end
-            if replace then
-                showsub(replace,"replaceinjections","replace")
-            end
-        end
-        n = getnext(n)
-    end
-    report_injections("end run")
-end
-
-local function show_result(head)
-    local current  = head
-    local skipping = false
-    while current do
-        local id = getid(current)
-        if id == glyph_code then
-            report_injections("char: %C, width %p, xoffset %p, yoffset %p",
-                getchar(current),getfield(current,"width"),getfield(current,"xoffset"),getfield(current,"yoffset"))
-            skipping = false
-        elseif id == kern_code then
-            report_injections("kern: %p",getfield(current,"kern"))
-            skipping = false
-        elseif not skipping then
-            report_injections()
-            skipping = true
-        end
-        current = getnext(current)
-    end
-end
-
-local function collect_glyphs(head,offsets)
-    local glyphs, glyphi, nofglyphs = { }, { }, 0
-    local marks, marki, nofmarks = { }, { }, 0
-    local nf, tm = nil, nil
-    local n = head
-
-    local function identify(n,what)
-        local f = getfont(n)
-        if f ~= nf then
-            nf = f
-            -- other hash in ctx:
-            tm = fontdata[nf].resources
-            if tm then
-                tm = tm.marks
-            end
-        end
-        if tm and tm[getchar(n)] then
-            nofmarks = nofmarks + 1
-            marks[nofmarks] = n
-            marki[nofmarks] = "injections"
-        else
-            nofglyphs = nofglyphs + 1
-            glyphs[nofglyphs] = n
-            glyphi[nofglyphs] = what
-        end
-        if offsets then
-            -- yoffsets can influence curs steps
-            local p = rawget(properties,n)
-            if p then
-                local i = rawget(p,what)
-                if i then
-                    local yoffset = i.yoffset
-                    if yoffset and yoffset ~= 0 then
-                        setfield(n,"yoffset",yoffset)
-                    end
-                end
-            end
-        end
-    end
-
-    while n do -- only needed for relevant fonts
-        local id = getid(n)
-        if id == glyph_code then
-            identify(n,"injections")
-        elseif id == disc_code then
-            local d = getfield(n,"pre")
-            if d then
-                for n in traverse_id(glyph_code,d) do
-                    if getsubtype(n) < 256 then
-                        identify(n,"preinjections")
-                    end
-                end
-			end
-            local d = getfield(n,"post")
-            if d then
-                for n in traverse_id(glyph_code,d) do
-                    if getsubtype(n) < 256 then
-                        identify(n,"postinjections")
-                    end
-                end
-			end
-            local d = getfield(n,"replace")
-            if d then
-                for n in traverse_id(glyph_code,d) do
-                    if getsubtype(n) < 256 then
-                        identify(n,"replaceinjections")
-                    end
-                end
-			end
-        end
-		n = getnext(n)
-    end
-
-    return glyphs, glyphi, nofglyphs, marks, marki, nofmarks
-end
-
-local function inject_marks(marks,marki,nofmarks)
-    for i=1,nofmarks do
-        local n  = marks[i]
-        local pn = rawget(properties,n)
-        if pn then
-            local ni = marki[i]
-            local pn = rawget(pn,ni)
-            if pn then
-                local p = pn.markbasenode
-                if p then
-                    local px = getfield(p,"xoffset")
-                    local ox = 0
-                    local rightkern = nil
-                    local pp = rawget(properties,p)
-                    if pp then
-                        pp = rawget(pp,ni)
-                        if pp then
-                            rightkern = pp.rightkern
-                        end
-                    end
-                    if rightkern then -- x and w ~= 0
-                        if pn.markdir < 0 then
-                            -- kern(w-x) glyph(p) kern(x) mark(n)
-                            ox = px - pn.markx - rightkern
-                         -- report_injections("r2l case 1: %p",ox)
-                        else
-                            -- kern(x) glyph(p) kern(w-x) mark(n)
-                         -- ox = px - getfield(p,"width") + pn.markx - pp.leftkern
-                            --
-							-- According to Kai we don't need to handle leftkern here but I'm
-                            -- pretty sure I've run into a case where it was needed so maybe
-	                        -- some day we need something more clever here.
-                            --
-							if false then
-                                -- a mark with kerning
-                                local leftkern = pp.leftkern
-                                if leftkern then
-                                    ox = px - pn.markx - leftkern
-                                else
-                                    ox = px - pn.markx
-                                end
-                            else
-                                ox = px - pn.markx
-                            end
-                        end
-                    else
-                        -- we need to deal with fonts that have marks with width
-                     -- if pn.markdir < 0 then
-                     --     ox = px - pn.markx
-                     --  -- report_injections("r2l case 3: %p",ox)
-                     -- else
-                     --  -- ox = px - getfield(p,"width") + pn.markx
-                            ox = px - pn.markx
-                         -- report_injections("l2r case 3: %p",ox)
-                     -- end
-                        local wn = getfield(n,"width") -- in arial marks have widths
-                        if wn ~= 0 then
-                            -- bad: we should center
-                         -- insert_node_before(head,n,newkern(-wn/2))
-                         -- insert_node_after(head,n,newkern(-wn/2))
-                            pn.leftkern  = -wn/2
-                            pn.rightkern = -wn/2
-                         -- wx[n] = { 0, -wn/2, 0, -wn }
-                        end
-                        -- so far
-                    end
-                    setfield(n,"xoffset",ox)
-                    --
-                    local py = getfield(p,"yoffset")
---                     local oy = 0
---                     if marks[p] then
---                         oy = py + pn.marky
---                     else
---                         oy = getfield(n,"yoffset") + py + pn.marky
---                     end
-                    local oy = getfield(n,"yoffset") + py + pn.marky
-                    setfield(n,"yoffset",oy)
-                else
-                    -- normally this can't happen (only when in trace mode which is a special case anyway)
-                 -- report_injections("missing mark anchor %i",pn.markbase or 0)
-                end
-            end
-        end
-    end
-end
-
-local function inject_cursives(glyphs,glyphi,nofglyphs)
-    local cursiveanchor, lastanchor = nil, nil
-    local minc, maxc, last = 0, 0, nil
-    for i=1,nofglyphs do
-        local n  = glyphs[i]
-        local pn = rawget(properties,n)
-        if pn then
-            pn = rawget(pn,glyphi[i])
-        end
-        if pn then
-            local cursivex = pn.cursivex
-            if cursivex then
-                if cursiveanchor then
-                    if cursivex ~= 0 then
-                        pn.leftkern = (pn.leftkern or 0) + cursivex
-                    end
-                    if lastanchor then
-                        if maxc == 0 then
-                            minc = lastanchor
-                        end
-                        maxc = lastanchor
-                        properties[cursiveanchor].cursivedy = pn.cursivey
-                    end
-                    last = n
-                else
-                    maxc = 0
-                end
-            elseif maxc > 0 then
-                local ny = getfield(n,"yoffset")
-                for i=maxc,minc,-1 do
-                    local ti = glyphs[i]
-                    ny = ny + properties[ti].cursivedy
-                    setfield(ti,"yoffset",ny) -- why not add ?
-                end
-                maxc = 0
-            end
-            if pn.cursiveanchor then
-                cursiveanchor = n
-                lastanchor = i
-            else
-                cursiveanchor = nil
-                lastanchor = nil
-                if maxc > 0 then
-                    local ny = getfield(n,"yoffset")
-                    for i=maxc,minc,-1 do
-                        local ti = glyphs[i]
-                        ny = ny + properties[ti].cursivedy
-                        setfield(ti,"yoffset",ny) -- why not add ?
-                    end
-                    maxc = 0
-                end
-            end
-        elseif maxc > 0 then
-            local ny = getfield(n,"yoffset")
-            for i=maxc,minc,-1 do
-                local ti = glyphs[i]
-                ny = ny + properties[ti].cursivedy
-                setfield(ti,"yoffset",getfield(ti,"yoffset") + ny) -- ?
-            end
-            maxc = 0
-            cursiveanchor = nil
-            lastanchor = nil
-        end
-     -- if maxc > 0 and not cursiveanchor then
-     --     local ny = getfield(n,"yoffset")
-     --     for i=maxc,minc,-1 do
-     --         local ti = glyphs[i][1]
-     --         ny = ny + properties[ti].cursivedy
-     --         setfield(ti,"yoffset",ny) -- why not add ?
-     --     end
-     --     maxc = 0
-     -- end
-    end
-    if last and maxc > 0 then
-        local ny = getfield(last,"yoffset")
-        for i=maxc,minc,-1 do
-            local ti = glyphs[i]
-            ny = ny + properties[ti].cursivedy
-            setfield(ti,"yoffset",ny) -- why not add ?
-        end
-    end
-end
-
--- G  +D-pre        G
---     D-post+
---    +D-replace+
---
--- G  +D-pre       +D-pre
---     D-post      +D-post
---    +D-replace   +D-replace
-
-local function inject_kerns(head,glist,ilist,length) -- not complete ! compare with inject_kerns_only (but unlikely disc here)
-    for i=1,length do
-        local n  = glist[i]
-        local pn = rawget(properties,n)
-        if pn then
-			local dp = nil
-			local dr = nil
-            local ni = ilist[i]
-            local p  = nil
-			if ni == "injections" then
-				p = getprev(n)
-				if p then
-					local id = getid(p)
-					if id == disc_code then
-						dp = getfield(p,"post")
-						dr = getfield(p,"replace")
-					end
-				end
-			end
-			if dp then
-				local i = rawget(pn,"postinjections")
-				if i then
-					local leftkern = i.leftkern
-					if leftkern and leftkern ~= 0 then
-						local t = find_tail(dp)
-						insert_node_after(dp,t,newkern(leftkern))
-                        setfield(p,"post",dp) -- currently we need to force a tail refresh
-					end
-				end
-			end
-			if dr then
-				local i = rawget(pn,"replaceinjections")
-				if i then
-					local leftkern = i.leftkern
-					if leftkern and leftkern ~= 0 then
-						local t = find_tail(dr)
-						insert_node_after(dr,t,newkern(leftkern))
-                        setfield(p,"replace",dr) -- currently we need to force a tail refresh
-					end
-				end
-			else
-				local i = rawget(pn,ni)
-				if i then
-					local leftkern = i.leftkern
-					if leftkern and leftkern ~= 0 then
-						insert_node_before(head,n,newkern(leftkern)) -- type 0/2
-					end
-					local rightkern = i.rightkern
-					if rightkern and rightkern ~= 0 then
-						insert_node_after(head,n,newkern(rightkern)) -- type 0/2
-					end
-				end
-			end
-        end
-    end
-end
-
-local function inject_everything(head,where)
-    head = tonut(head)
-    if trace_injections then
-        trace(head,"everything")
-    end
-    local glyphs, glyphi, nofglyphs, marks, marki, nofmarks = collect_glyphs(head,nofregisteredpairs > 0)
-    if nofglyphs > 0 then
-        if nofregisteredcursives > 0 then
-            inject_cursives(glyphs,glyphi,nofglyphs)
-        end
-        if nofregisteredmarks > 0 then -- and nofmarks > 0
-            inject_marks(marks,marki,nofmarks)
-        end
-        inject_kerns(head,glyphs,glyphi,nofglyphs)
-    end
-    if nofmarks > 0 then
-        inject_kerns(head,marks,marki,nofmarks)
-	end
-    if keepregisteredcounts then
-        keepregisteredcounts  = false
-    else
-        nofregisteredkerns    = 0
-        nofregisteredpairs    = 0
-        nofregisteredmarks    = 0
-        nofregisteredcursives = 0
-    end
-    return tonode(head), true
-end
-
--- G  +D-pre        G
---     D-post+
---    +D-replace+
---
--- G  +D-pre       +D-pre
---     D-post      +D-post
---    +D-replace   +D-replace
-
-local function inject_kerns_only(head,where)
-    head = tonut(head)
-    if trace_injections then
-        trace(head,"kerns")
-    end
-    local n = head
-    local p = nil -- disc node when non-nil
-    while n do
-        local id = getid(n)
-        if id == glyph_code then
-            if getsubtype(n) < 256 then
-                local pn = rawget(properties,n)
-                if pn then
-                    if p then
-                        local d = getfield(p,"post")
-                        if d then
-                            local i = rawget(pn,"postinjections")
-                            if i then
-                                local leftkern = i.leftkern
-                                if leftkern and leftkern ~= 0 then
-                                    local t = find_tail(d)
-                                    insert_node_after(d,t,newkern(leftkern))
-                                    setfield(p,"post",d) -- currently we need to force a tail refresh
-                                end
-                            end
-                        end
-                        local d = getfield(p,"replace")
-                        if d then
-                            local i = rawget(pn,"replaceinjections")
-                            if i then
-                                local leftkern = i.leftkern
-                                if leftkern and leftkern ~= 0 then
-                                    local t = find_tail(d)
-                                    insert_node_after(d,t,newkern(leftkern))
-                                    setfield(p,"replace",d) -- currently we need to force a tail refresh
-                                end
-                            end
-                        else
-                            local i = rawget(pn,"injections")
-                            if i then
-                                local leftkern = i.leftkern
-                                if leftkern and leftkern ~= 0 then
-                                    setfield(p,"replace",newkern(leftkern))
-                                end
-                            end
-                        end
-                    else
-                        -- this is the most common case
-                        local i = rawget(pn,"injections")
-                        if i then
-                            local leftkern = i.leftkern
-                            if leftkern and leftkern ~= 0 then
-                                head = insert_node_before(head,n,newkern(leftkern))
-                            end
-                        end
-                    end
-                end
-            end
-            p = nil
-        elseif id == disc_code then
-            local d = getfield(n,"pre")
-            if d then
-                local h = d
-                for n in traverse_id(glyph_code,d) do
-                    if getsubtype(n) < 256 then
-                        local pn = rawget(properties,n)
-                        if pn then
-                            local i = rawget(pn,"preinjections")
-                            if i then
-                                local leftkern = i.leftkern
-                                if leftkern and leftkern ~= 0 then
-                                    h = insert_node_before(h,n,newkern(leftkern))
-                                end
-                            end
-                        end
-                    else
-                        break
-                    end
-                end
-                if h ~= d then
-                    setfield(n,"pre",h)
-                end
-            end
-            local d = getfield(n,"post")
-            if d then
-                local h = d
-                for n in traverse_id(glyph_code,d) do
-                    if getsubtype(n) < 256 then
-                        local pn = rawget(properties,n)
-                        if pn then
-                            local i = rawget(pn,"postinjections")
-                            if i then
-                                local leftkern = i.leftkern
-                                if leftkern and leftkern ~= 0 then
-                                    h = insert_node_before(h,n,newkern(leftkern))
-                                end
-                            end
-                        end
-                    else
-                        break
-                    end
-                end
-                if h ~= d then
-                    setfield(n,"post",h)
-                end
-            end
-            local d = getfield(n,"replace")
-            if d then
-                local h = d
-                for n in traverse_id(glyph_code,d) do
-                    if getsubtype(n) < 256 then
-                        local pn = rawget(properties,n)
-                        if pn then
-                            local i = rawget(pn,"replaceinjections")
-                            if i then
-                                local leftkern = i.leftkern
-                                if leftkern and leftkern ~= 0 then
-                                    h = insert_node_before(h,n,newkern(leftkern))
-                                end
-                            end
-                        end
-                    else
-                        break
-                    end
-                end
-                if h ~= d then
-                    setfield(n,"replace",h)
-                end
-            end
-            p = n
-        else
-            p = nil
-        end
-        n = getnext(n)
-    end
-    --
-    if keepregisteredcounts then
-        keepregisteredcounts = false
-    else
-        nofregisteredkerns   = 0
-    end
-    return tonode(head), true
-end
-
-local function inject_pairs_only(head,where)
-    head = tonut(head)
-    if trace_injections then
-        trace(head,"pairs")
-    end
-    local n = head
-    local p = nil -- disc node when non-nil
-    while n do
-        local id = getid(n)
-        if id == glyph_code then
-            if getsubtype(n) < 256 then
-                local pn = rawget(properties,n)
-                if pn then
-                    if p then
-                        local d = getfield(p,"post")
-                        if d then
-                            local i = rawget(pn,"postinjections")
-                            if i then
-                                local leftkern = i.leftkern
-                                if leftkern and leftkern ~= 0 then
-                                    local t = find_tail(d)
-                                    insert_node_after(d,t,newkern(leftkern))
-                                    setfield(p,"post",d) -- currently we need to force a tail refresh
-                                end
-                             -- local rightkern = i.rightkern
-                             -- if rightkern and rightkern ~= 0 then
-                             --     insert_node_after(head,n,newkern(rightkern))
-                             --     n = getnext(n) -- to be checked
-                             -- end
-                            end
-                        end
-                        local d = getfield(p,"replace")
-                        if d then
-                            local i = rawget(pn,"replaceinjections")
-                            if i then
-                                local leftkern = i.leftkern
-                                if leftkern and leftkern ~= 0 then
-                                    local t = find_tail(d)
-                                    insert_node_after(d,t,newkern(leftkern))
-                                    setfield(p,"replace",d) -- currently we need to force a tail refresh
-                                end
-                             -- local rightkern = i.rightkern
-                             -- if rightkern and rightkern ~= 0 then
-                             --     insert_node_after(head,n,newkern(rightkern))
-                             --     n = getnext(n) -- to be checked
-                             -- end
-                            end
-                        else
-                            local i = rawget(pn,"injections")
-                            if i then
-                                local leftkern = i.leftkern
-                                if leftkern and leftkern ~= 0 then
-                                    setfield(p,"replace",newkern(leftkern))
-                                end
-                             -- local rightkern = i.rightkern
-                             -- if rightkern and rightkern ~= 0 then
-                             --     insert_node_after(head,n,newkern(rightkern))
-                             --     n = getnext(n) -- to be checked
-                             -- end
-                            end
-                        end
-                    else
-                        -- this is the most common case
-                        local i = rawget(pn,"injections")
-                        if i then
-                            local leftkern = i.leftkern
-                            if leftkern and leftkern ~= 0 then
-                                head = insert_node_before(head,n,newkern(leftkern))
-                            end
-                            local rightkern = i.rightkern
-                            if rightkern and rightkern ~= 0 then
-                                insert_node_after(head,n,newkern(rightkern))
-                                n = getnext(n) -- to be checked
-                            end
-                            local yoffset = i.yoffset
-                            if yoffset and yoffset ~= 0 then
-                                setfield(n,"yoffset",yoffset)
-                            end
-                        end
-                    end
-                end
-            end
-            p = nil
-        elseif id == disc_code then
-            local d = getfield(n,"pre")
-            if d then
-                local h = d
-                for n in traverse_id(glyph_code,d) do
-                    if getsubtype(n) < 256 then
-                        local pn = rawget(properties,n)
-                        if pn then
-                            local i = rawget(pn,"preinjections")
-                            if i then
-                                local leftkern = i.leftkern
-                                if leftkern and leftkern ~= 0 then
-                                    h = insert_node_before(h,n,newkern(leftkern))
-                                end
-                                local rightkern = i.rightkern
-                                if rightkern and rightkern ~= 0 then
-                                    insert_node_after(head,n,newkern(rightkern))
-                                    n = getnext(n) -- to be checked
-                                end
-                                local yoffset = i.yoffset
-                                if yoffset and yoffset ~= 0 then
-                                    setfield(n,"yoffset",yoffset)
-                                end
-                            end
-                        end
-                    else
-                        break
-                    end
-                end
-                if h ~= d then
-                    setfield(n,"pre",h)
-                end
-            end
-            local d = getfield(n,"post")
-            if d then
-                local h = d
-                for n in traverse_id(glyph_code,d) do
-                    if getsubtype(n) < 256 then
-                        local pn = rawget(properties,n)
-                        if pn then
-                            local i = rawget(pn,"postinjections")
-                            if i then
-                                local leftkern = i.leftkern
-                                if leftkern and leftkern ~= 0 then
-                                    h = insert_node_before(h,n,newkern(leftkern))
-                                end
-                                local rightkern = i.rightkern
-                                if rightkern and rightkern ~= 0 then
-                                    insert_node_after(head,n,newkern(rightkern))
-                                    n = getnext(n) -- to be checked
-                                end
-                                local yoffset = i.yoffset
-                                if yoffset and yoffset ~= 0 then
-                                    setfield(n,"yoffset",yoffset)
-                                end
-                            end
-                        end
-                    else
-                        break
-                    end
-                end
-                if h ~= d then
-                    setfield(n,"post",h)
-                end
-            end
-            local d = getfield(n,"replace")
-            if d then
-                local h = d
-                for n in traverse_id(glyph_code,d) do
-                    if getsubtype(n) < 256 then
-                        local pn = rawget(properties,n)
-                        if pn then
-                            local i = rawget(pn,"replaceinjections")
-                            if i then
-                                local leftkern = i.leftkern
-                                if leftkern and leftkern ~= 0 then
-                                    h = insert_node_before(h,n,newkern(leftkern))
-                                end
-                                local rightkern = i.rightkern
-                                if rightkern and rightkern ~= 0 then
-                                    insert_node_after(head,n,newkern(rightkern))
-                                    n = getnext(n) -- to be checked
-                                end
-                                local yoffset = i.yoffset
-                                if yoffset and yoffset ~= 0 then
-                                    setfield(n,"yoffset",yoffset)
-                                end
-                            end
-                        end
-                    else
-                        break
-                    end
-                end
-                if h ~= d then
-                    setfield(n,"replace",h)
-                end
-            end
-            p = n
-        else
-            p = nil
-        end
-        n = getnext(n)
-    end
-    --
-    if keepregisteredcounts then
-        keepregisteredcounts = false
-    else
-        nofregisteredpairs = 0
-        nofregisteredkerns = 0
-    end
-    return tonode(head), true
-end
-
-function injections.handler(head,where)
-    if nofregisteredmarks > 0 or nofregisteredcursives > 0 then
-        return inject_everything(head,where)
-    elseif nofregisteredpairs > 0 then
-        return inject_pairs_only(head,where)
-    elseif nofregisteredkerns > 0 then
-        return inject_kerns_only(head,where)
-    else
-        return head, false
-    end
-end
diff --git a/src/fontloader/misc/fontloader-fonts-otn.lua b/src/fontloader/misc/fontloader-fonts-otn.lua
deleted file mode 100644
index 7fafadb..0000000
--- a/src/fontloader/misc/fontloader-fonts-otn.lua
+++ /dev/null
@@ -1,3848 +0,0 @@
-if not modules then modules = { } end modules ['font-otn'] = {
-    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",
-}
-
--- this is a context version which can contain experimental code, but when we
--- have serious patches we also need to change the other two font-otn files
-
--- at some point i might decide to convert the whole list into a table and then
--- run over that instead (but it has some drawbacks as we also need to deal with
--- attributes and such so we need to keep a lot of track - which is why i rejected
--- that method - although it has become a bit easier in the meantime so it might
--- become an alternative (by that time i probably have gone completely lua) .. the
--- usual chicken-egg issues ... maybe mkix as it's no real tex any more then
-
--- preprocessors = { "nodes" }
-
--- anchor class : mark, mkmk, curs, mklg (todo)
--- anchor type  : mark, basechar, baselig, basemark, centry, cexit, max (todo)
-
--- this is still somewhat preliminary and it will get better in due time;
--- much functionality could only be implemented thanks to the husayni font
--- of Idris Samawi Hamid to who we dedicate this module.
-
--- in retrospect it always looks easy but believe it or not, it took a lot
--- of work to get proper open type support done: buggy fonts, fuzzy specs,
--- special made testfonts, many skype sessions between taco, idris and me,
--- torture tests etc etc ... unfortunately the code does not show how much
--- time it took ...
-
--- todo:
---
--- extension infrastructure (for usage out of context)
--- sorting features according to vendors/renderers
--- alternative loop quitters
--- check cursive and r2l
--- find out where ignore-mark-classes went
--- default features (per language, script)
--- handle positions (we need example fonts)
--- handle gpos_single (we might want an extra width field in glyph nodes because adding kerns might interfere)
--- mark (to mark) code is still not what it should be (too messy but we need some more extreem husayni tests)
--- remove some optimizations (when I have a faster machine)
---
--- beware:
---
--- we do some disc jugling where we need to keep in mind that the
--- pre, post and replace fields can have prev pointers to a nesting
--- node ... i wonder if that is still needed
---
--- not possible:
---
--- \discretionary {alpha-} {betagammadelta}
---   {\discretionary {alphabeta-} {gammadelta}
---      {\discretionary {alphabetagamma-} {delta}
---         {alphabetagammadelta}}}
-
---[[ldx--
-<p>This module is a bit more split up that I'd like but since we also want to test
-with plain <l n='tex'/> it has to be so. This module is part of <l n='context'/>
-and discussion about improvements and functionality mostly happens on the
-<l n='context'/> mailing list.</p>
-
-<p>The specification of OpenType is kind of vague. Apart from a lack of a proper
-free specifications there's also the problem that Microsoft and Adobe
-may have their own interpretation of how and in what order to apply features.
-In general the Microsoft website has more detailed specifications and is a
-better reference. There is also some information in the FontForge help files.</p>
-
-<p>Because there is so much possible, fonts might contain bugs and/or be made to
-work with certain rederers. These may evolve over time which may have the side
-effect that suddenly fonts behave differently.</p>
-
-<p>After a lot of experiments (mostly by Taco, me and Idris) we're now at yet another
-implementation. Of course all errors are mine and of course the code can be
-improved. There are quite some optimizations going on here and processing speed
-is currently acceptable. Not all functions are implemented yet, often because I
-lack the fonts for testing. Many scripts are not yet supported either, but I will
-look into them as soon as <l n='context'/> users ask for it.</p>
-
-<p>The specification leaves room for interpretation. In case of doubt the microsoft
-implementation is the reference as it is the most complete one. As they deal with
-lots of scripts and fonts, Kai and Ivo did a lot of testing of the generic code and
-their suggestions help improve the code. I'm aware that not all border cases can be
-taken care of, unless we accept excessive runtime, and even then the interference
-with other mechanisms (like hyphenation) are not trivial.</p>
-
-<p>Glyphs are indexed not by unicode but in their own way. This is because there is no
-relationship with unicode at all, apart from the fact that a font might cover certain
-ranges of characters. One character can have multiple shapes. However, at the
-<l n='tex'/> end we use unicode so and all extra glyphs are mapped into a private
-space. This is needed because we need to access them and <l n='tex'/> has to include
-then in the output eventually.</p>
-
-<p>The raw table as it coms from <l n='fontforge'/> gets reorganized in to fit out needs.
-In <l n='context'/> that table is packed (similar tables are shared) and cached on disk
-so that successive runs can use the optimized table (after loading the table is
-unpacked). The flattening code used later is a prelude to an even more compact table
-format (and as such it keeps evolving).</p>
-
-<p>This module is sparsely documented because it is a moving target. The table format
-of the reader changes and we experiment a lot with different methods for supporting
-features.</p>
-
-<p>As with the <l n='afm'/> code, we may decide to store more information in the
-<l n='otf'/> table.</p>
-
-<p>Incrementing the version number will force a re-cache. We jump the number by one
-when there's a fix in the <l n='fontforge'/> library or <l n='lua'/> code that
-results in different tables.</p>
---ldx]]--
-
--- action                    handler     chainproc
---
--- gsub_single               ok          ok
--- gsub_multiple             ok          ok
--- gsub_alternate            ok          ok
--- gsub_ligature             ok          ok
--- gsub_context              ok          --
--- gsub_contextchain         ok          --
--- gsub_reversecontextchain  ok          --
--- chainsub                  --          ok
--- reversesub                --          ok
--- gpos_mark2base            ok          ok
--- gpos_mark2ligature        ok          ok
--- gpos_mark2mark            ok          ok
--- gpos_cursive              ok          untested
--- gpos_single               ok          ok
--- gpos_pair                 ok          ok
--- gpos_context              ok          --
--- gpos_contextchain         ok          --
---
--- todo: contextpos and contextsub and class stuff
---
--- actions:
---
--- handler   : actions triggered by lookup
--- chainproc : actions triggered by contextual lookup
--- chainmore : multiple substitutions triggered by contextual lookup (e.g. fij -> f + ij)
---
--- remark: the 'not implemented yet' variants will be done when we have fonts that use them
-
--- We used to have independent hashes for lookups but as the tags are unique
--- we now use only one hash. If needed we can have multiple again but in that
--- case I will probably prefix (i.e. rename) the lookups in the cached font file.
-
--- Todo: make plugin feature that operates on char/glyphnode arrays
-
-local type, next, tonumber = type, next, tonumber
-local random = math.random
-local formatters = string.formatters
-
-local logs, trackers, nodes, attributes = logs, trackers, nodes, attributes
-
-local registertracker   = trackers.register
-local registerdirective = directives.register
-
-local fonts = fonts
-local otf   = fonts.handlers.otf
-
-local trace_lookups      = false  registertracker("otf.lookups",      function(v) trace_lookups      = v end)
-local trace_singles      = false  registertracker("otf.singles",      function(v) trace_singles      = v end)
-local trace_multiples    = false  registertracker("otf.multiples",    function(v) trace_multiples    = v end)
-local trace_alternatives = false  registertracker("otf.alternatives", function(v) trace_alternatives = v end)
-local trace_ligatures    = false  registertracker("otf.ligatures",    function(v) trace_ligatures    = v end)
-local trace_contexts     = false  registertracker("otf.contexts",     function(v) trace_contexts     = v end)
-local trace_marks        = false  registertracker("otf.marks",        function(v) trace_marks        = v end)
-local trace_kerns        = false  registertracker("otf.kerns",        function(v) trace_kerns        = v end)
-local trace_cursive      = false  registertracker("otf.cursive",      function(v) trace_cursive      = v end)
-local trace_preparing    = false  registertracker("otf.preparing",    function(v) trace_preparing    = v end)
-local trace_bugs         = false  registertracker("otf.bugs",         function(v) trace_bugs         = v end)
-local trace_details      = false  registertracker("otf.details",      function(v) trace_details      = v end)
-local trace_applied      = false  registertracker("otf.applied",      function(v) trace_applied      = v end)
-local trace_steps        = false  registertracker("otf.steps",        function(v) trace_steps        = v end)
-local trace_skips        = false  registertracker("otf.skips",        function(v) trace_skips        = v end)
-local trace_directions   = false  registertracker("otf.directions",   function(v) trace_directions   = v end)
-
-local trace_kernruns     = false  registertracker("otf.kernruns",     function(v) trace_kernruns     = v end)
-local trace_discruns     = false  registertracker("otf.discruns",     function(v) trace_discruns     = v end)
-local trace_compruns     = false  registertracker("otf.compruns",     function(v) trace_compruns     = v end)
-
-local quit_on_no_replacement = true  -- maybe per font
-local zwnjruns               = true
-
-registerdirective("otf.zwnjruns",                 function(v) zwnjruns = v end)
-registerdirective("otf.chain.quitonnoreplacement",function(value) quit_on_no_replacement = value end)
-
-local report_direct   = logs.reporter("fonts","otf direct")
-local report_subchain = logs.reporter("fonts","otf subchain")
-local report_chain    = logs.reporter("fonts","otf chain")
-local report_process  = logs.reporter("fonts","otf process")
-local report_prepare  = logs.reporter("fonts","otf prepare")
-local report_warning  = logs.reporter("fonts","otf warning")
-local report_run      = logs.reporter("fonts","otf run")
-
-registertracker("otf.verbose_chain", function(v) otf.setcontextchain(v and "verbose") end)
-registertracker("otf.normal_chain",  function(v) otf.setcontextchain(v and "normal")  end)
-
-registertracker("otf.replacements", "otf.singles,otf.multiples,otf.alternatives,otf.ligatures")
-registertracker("otf.positions","otf.marks,otf.kerns,otf.cursive")
-registertracker("otf.actions","otf.replacements,otf.positions")
-registertracker("otf.injections","nodes.injections")
-
-registertracker("*otf.sample","otf.steps,otf.actions,otf.analyzing")
-
-local nuts               = nodes.nuts
-local tonode             = nuts.tonode
-local tonut              = nuts.tonut
-
-local getfield           = nuts.getfield
-local setfield           = nuts.setfield
-local getnext            = nuts.getnext
-local getprev            = nuts.getprev
-local getid              = nuts.getid
-local getattr            = nuts.getattr
-local setattr            = nuts.setattr
-local getprop            = nuts.getprop
-local setprop            = nuts.setprop
-local getfont            = nuts.getfont
-local getsubtype         = nuts.getsubtype
-local getchar            = nuts.getchar
-
-local insert_node_before = nuts.insert_before
-local insert_node_after  = nuts.insert_after
-local delete_node        = nuts.delete
-local remove_node        = nuts.remove
-local copy_node          = nuts.copy
-local copy_node_list     = nuts.copy_list
-local find_node_tail     = nuts.tail
-local flush_node_list    = nuts.flush_list
-local free_node          = nuts.free
-local end_of_math        = nuts.end_of_math
-local traverse_nodes     = nuts.traverse
-local traverse_id        = nuts.traverse_id
-
-local setmetatableindex  = table.setmetatableindex
-
-local zwnj               = 0x200C
-local zwj                = 0x200D
-local wildcard           = "*"
-local default            = "dflt"
-
-local nodecodes          = nodes.nodecodes
-local whatcodes          = nodes.whatcodes
-local glyphcodes         = nodes.glyphcodes
-local disccodes          = nodes.disccodes
-
-local glyph_code         = nodecodes.glyph
-local glue_code          = nodecodes.glue
-local disc_code          = nodecodes.disc
-local math_code          = nodecodes.math
-
-local dir_code           = whatcodes.dir
-local localpar_code      = whatcodes.localpar
-local discretionary_code = disccodes.discretionary
-local ligature_code      = glyphcodes.ligature
-
-local privateattribute   = attributes.private
-
--- Something is messed up: we have two mark / ligature indices, one at the injection
--- end and one here ... this is based on KE's patches but there is something fishy
--- there as I'm pretty sure that for husayni we need some connection (as it's much
--- more complex than an average font) but I need proper examples of all cases, not
--- of only some.
-
-local a_state            = privateattribute('state')
-local a_cursbase         = privateattribute('cursbase') -- to be checked, probably can go
-
-local injections         = nodes.injections
-local setmark            = injections.setmark
-local setcursive         = injections.setcursive
-local setkern            = injections.setkern
-local setpair            = injections.setpair
-local resetinjection     = injections.reset
-local copyinjection      = injections.copy
-local setligaindex       = injections.setligaindex
-local getligaindex       = injections.getligaindex
-
-local cursonce           = true
-
-local fonthashes         = fonts.hashes
-local fontdata           = fonthashes.identifiers
-
-local otffeatures        = fonts.constructors.newfeatures("otf")
-local registerotffeature = otffeatures.register
-
-local onetimemessage     = fonts.loggers.onetimemessage or function() end
-
-otf.defaultnodealternate = "none" -- first last
-
--- we share some vars here, after all, we have no nested lookups and less code
-
-local tfmdata             = false
-local characters          = false
-local descriptions        = false
-local resources           = false
-local marks               = false
-local currentfont         = false
-local lookuptable         = false
-local anchorlookups       = false
-local lookuptypes         = false
-local lookuptags          = false
-local handlers            = { }
-local rlmode              = 0
-local featurevalue        = false
-
-local sweephead           = { }
-local sweepnode           = nil
-local sweepprev           = nil
-local sweepnext           = nil
-
-local notmatchpre         = { }
-local notmatchpost        = { }
-local notmatchreplace     = { }
-
--- we use this for special testing and documentation
-
-local checkstep       = (nodes and nodes.tracers and nodes.tracers.steppers.check)    or function() end
-local registerstep    = (nodes and nodes.tracers and nodes.tracers.steppers.register) or function() end
-local registermessage = (nodes and nodes.tracers and nodes.tracers.steppers.message)  or function() end
-
-local function logprocess(...)
-    if trace_steps then
-        registermessage(...)
-    end
-    report_direct(...)
-end
-
-local function logwarning(...)
-    report_direct(...)
-end
-
-local f_unicode = formatters["%U"]
-local f_uniname = formatters["%U (%s)"]
-local f_unilist = formatters["% t (% t)"]
-
-local function gref(n) -- currently the same as in font-otb
-    if type(n) == "number" then
-        local description = descriptions[n]
-        local name = description and description.name
-        if name then
-            return f_uniname(n,name)
-        else
-            return f_unicode(n)
-        end
-    elseif n then
-        local num, nam = { }, { }
-        for i=1,#n do
-            local ni = n[i]
-            if tonumber(ni) then -- later we will start at 2
-                local di = descriptions[ni]
-                num[i] = f_unicode(ni)
-                nam[i] = di and di.name or "-"
-            end
-        end
-        return f_unilist(num,nam)
-    else
-        return "<error in node mode tracing>"
-    end
-end
-
-local function cref(kind,chainname,chainlookupname,lookupname,index) -- not in the mood to alias f_
-    if index then
-        return formatters["feature %a, chain %a, sub %a, lookup %a, index %a"](kind,chainname,chainlookupname,lookuptags[lookupname],index)
-    elseif lookupname then
-        return formatters["feature %a, chain %a, sub %a, lookup %a"](kind,chainname,chainlookupname,lookuptags[lookupname])
-    elseif chainlookupname then
-        return formatters["feature %a, chain %a, sub %a"](kind,lookuptags[chainname],lookuptags[chainlookupname])
-    elseif chainname then
-        return formatters["feature %a, chain %a"](kind,lookuptags[chainname])
-    else
-        return formatters["feature %a"](kind)
-    end
-end
-
-local function pref(kind,lookupname)
-    return formatters["feature %a, lookup %a"](kind,lookuptags[lookupname])
-end
-
--- We can assume that languages that use marks are not hyphenated. We can also assume
--- that at most one discretionary is present.
-
--- We do need components in funny kerning mode but maybe I can better reconstruct then
--- as we do have the font components info available; removing components makes the
--- previous code much simpler. Also, later on copying and freeing becomes easier.
--- However, for arabic we need to keep them around for the sake of mark placement
--- and indices.
-
-local function copy_glyph(g) -- next and prev are untouched !
-    local components = getfield(g,"components")
-    if components then
-        setfield(g,"components",nil)
-        local n = copy_node(g)
-        copyinjection(n,g) -- we need to preserve the lig indices
-        setfield(g,"components",components)
-        return n
-    else
-        local n = copy_node(g)
-        copyinjection(n,g) -- we need to preserve the lig indices
-        return n
-    end
-end
-
-local function flattendisk(head,disc)
-    local replace = getfield(disc,"replace")
-    setfield(disc,"replace",nil)
-    free_node(disc)
-    if head == disc then
-        local next = getnext(disc)
-        if replace then
-            if next then
-                local tail = find_node_tail(replace)
-                setfield(tail,"next",next)
-                setfield(next,"prev",tail)
-            end
-            return replace, replace
-        elseif next then
-            return next, next
-        else
-            return -- maybe warning
-        end
-    else
-        local next = getnext(disc)
-        local prev = getprev(disc)
-        if replace then
-            local tail = find_node_tail(replace)
-            if next then
-                setfield(tail,"next",next)
-                setfield(next,"prev",tail)
-            end
-            setfield(prev,"next",replace)
-            setfield(replace,"prev",prev)
-            return head, replace
-        else
-            if next then
-                setfield(next,"prev",prev)
-            end
-            setfield(prev,"next",next)
-            return head, next
-        end
-    end
-end
-
-local function appenddisc(disc,list)
-    local post    = getfield(disc,"post")
-    local replace = getfield(disc,"replace")
-    local phead   = list
-    local rhead   = copy_node_list(list)
-    local ptail   = find_node_tail(post)
-    local rtail   = find_node_tail(replace)
-    if post then
-        setfield(ptail,"next",phead)
-        setfield(phead,"prev",ptail)
-    else
-        setfield(disc,"post",phead)
-    end
-    if replace then
-        setfield(rtail,"next",rhead)
-        setfield(rhead,"prev",rtail)
-    else
-        setfield(disc,"replace",rhead)
-    end
-end
-
--- start is a mark and we need to keep that one
-
-local function markstoligature(kind,lookupname,head,start,stop,char)
-    if start == stop and getchar(start) == char then
-        return head, start
-    else
-        local prev = getprev(start)
-        local next = getnext(stop)
-        setfield(start,"prev",nil)
-        setfield(stop,"next",nil)
-        local base = copy_glyph(start)
-        if head == start then
-            head = base
-        end
-        resetinjection(base)
-        setfield(base,"char",char)
-        setfield(base,"subtype",ligature_code)
-        setfield(base,"components",start)
-        if prev then
-            setfield(prev,"next",base)
-        end
-        if next then
-            setfield(next,"prev",base)
-        end
-        setfield(base,"next",next)
-        setfield(base,"prev",prev)
-        return head, base
-    end
-end
-
--- The next code is somewhat complicated by the fact that some fonts can have ligatures made
--- from ligatures that themselves have marks. This was identified by Kai in for instance
--- arabtype:  KAF LAM SHADDA ALEF FATHA (0x0643 0x0644 0x0651 0x0627 0x064E). This becomes
--- KAF LAM-ALEF with a SHADDA on the first and a FATHA op de second component. In a next
--- iteration this becomes a KAF-LAM-ALEF with a SHADDA on the second and a FATHA on the
--- third component.
-
-local function getcomponentindex(start) -- we could store this offset in the glyph (nofcomponents)
-    if getid(start) ~= glyph_code then  -- and then get rid of all components
-        return 0
-    elseif getsubtype(start) == ligature_code then
-        local i = 0
-        local components = getfield(start,"components")
-        while components do
-            i = i + getcomponentindex(components)
-            components = getnext(components)
-        end
-        return i
-    elseif not marks[getchar(start)] then
-        return 1
-    else
-        return 0
-    end
-end
-
-local a_noligature = attributes.private("noligature")
-
-local function toligature(kind,lookupname,head,start,stop,char,markflag,discfound) -- brr head
-    if getattr(start,a_noligature) == 1 then
-        -- so we can do: e\noligature{ff}e e\noligature{f}fie (we only look at the first)
-        return head, start
-    end
-    if start == stop and getchar(start) == char then
-        resetinjection(start)
-        setfield(start,"char",char)
-        return head, start
-    end
-    -- needs testing (side effects):
-    local components = getfield(start,"components")
-    if components then
-     -- we get a double free .. needs checking
-     -- flush_node_list(components)
-    end
-    --
-    local prev = getprev(start)
-    local next = getnext(stop)
-    local comp = start
-    setfield(start,"prev",nil)
-    setfield(stop,"next",nil)
-    local base = copy_glyph(start)
-    if start == head then
-        head = base
-    end
-    resetinjection(base)
-    setfield(base,"char",char)
-    setfield(base,"subtype",ligature_code)
-    setfield(base,"components",comp) -- start can have components ... do we need to flush?
-    if prev then
-        setfield(prev,"next",base)
-    end
-    if next then
-        setfield(next,"prev",base)
-    end
-    setfield(base,"prev",prev)
-    setfield(base,"next",next)
-    if not discfound then
-        local deletemarks = markflag ~= "mark"
-        local components = start
-        local baseindex = 0
-        local componentindex = 0
-        local head = base
-        local current = base
-        -- first we loop over the glyphs in start .. stop
-        while start do
-            local char = getchar(start)
-            if not marks[char] then
-                baseindex = baseindex + componentindex
-                componentindex = getcomponentindex(start)
-            elseif not deletemarks then -- quite fishy
-                setligaindex(start,baseindex + getligaindex(start,componentindex))
-                if trace_marks then
-                    logwarning("%s: keep mark %s, gets index %s",pref(kind,lookupname),gref(char),getligaindex(start))
-                end
-                local n = copy_node(start)
-                copyinjection(n,start)
-                head, current = insert_node_after(head,current,n) -- unlikely that mark has components
-            elseif trace_marks then
-                logwarning("%s: delete mark %s",pref(kind,lookupname),gref(char))
-            end
-            start = getnext(start)
-        end
-        -- we can have one accent as part of a lookup and another following
-     -- local start = components -- was wrong (component scanning was introduced when more complex ligs in devanagari was added)
-        local start = getnext(current)
-        while start and getid(start) == glyph_code do
-            local char = getchar(start)
-            if marks[char] then
-                setligaindex(start,baseindex + getligaindex(start,componentindex))
-                if trace_marks then
-                    logwarning("%s: set mark %s, gets index %s",pref(kind,lookupname),gref(char),getligaindex(start))
-                end
-            else
-                break
-            end
-            start = getnext(start)
-        end
-    else
-        -- discfound ... forget about marks .. probably no scripts that hyphenate and have marks
-        local discprev = getfield(discfound,"prev")
-        local discnext = getfield(discfound,"next")
-        if discprev and discnext then
-            -- we assume normalization in context, and don't care about generic ... especially
-            -- \- can give problems as there we can have a negative char but that won't match
-            -- anyway
-            local pre     = getfield(discfound,"pre")
-            local post    = getfield(discfound,"post")
-            local replace = getfield(discfound,"replace")
-            if not replace then -- todo: signal simple hyphen
-                local prev = getfield(base,"prev")
-                local copied = copy_node_list(comp)
-                setfield(discnext,"prev",nil) -- also blocks funny assignments
-                setfield(discprev,"next",nil) -- also blocks funny assignments
-                if pre then
-                    setfield(discprev,"next",pre)
-                    setfield(pre,"prev",discprev)
-                end
-                pre = comp
-                if post then
-                    local tail = find_node_tail(post)
-                    setfield(tail,"next",discnext)
-                    setfield(discnext,"prev",tail)
-                    setfield(post,"prev",nil)
-                else
-                    post = discnext
-                end
-                setfield(prev,"next",discfound)
-                setfield(discfound,"prev",prev)
-                setfield(discfound,"next",next)
-                setfield(next,"prev",discfound)
-                setfield(base,"next",nil)
-                setfield(base,"prev",nil)
-                setfield(base,"components",copied)
-                setfield(discfound,"pre",pre)
-                setfield(discfound,"post",post)
-                setfield(discfound,"replace",base)
-                setfield(discfound,"subtype",discretionary_code)
-                base = prev -- restart
-            end
-        end
-    end
-    return head, base
-end
-
-local function multiple_glyphs(head,start,multiple,ignoremarks)
-    local nofmultiples = #multiple
-    if nofmultiples > 0 then
-        resetinjection(start)
-        setfield(start,"char",multiple[1])
-        if nofmultiples > 1 then
-            local sn = getnext(start)
-            for k=2,nofmultiples do -- todo: use insert_node
--- untested:
---
--- while ignoremarks and marks[getchar(sn)] then
---     local sn = getnext(sn)
--- end
-                local n = copy_node(start) -- ignore components
-                resetinjection(n)
-                setfield(n,"char",multiple[k])
-                setfield(n,"prev",start)
-                setfield(n,"next",sn)
-                if sn then
-                    setfield(sn,"prev",n)
-                end
-                setfield(start,"next",n)
-                start = n
-            end
-        end
-        return head, start, true
-    else
-        if trace_multiples then
-            logprocess("no multiple for %s",gref(getchar(start)))
-        end
-        return head, start, false
-    end
-end
-
-local function get_alternative_glyph(start,alternatives,value,trace_alternatives)
-    local n = #alternatives
-    if value == "random" then
-        local r = random(1,n)
-        return alternatives[r], trace_alternatives and formatters["value %a, taking %a"](value,r)
-    elseif value == "first" then
-        return alternatives[1], trace_alternatives and formatters["value %a, taking %a"](value,1)
-    elseif value == "last" then
-        return alternatives[n], trace_alternatives and formatters["value %a, taking %a"](value,n)
-    else
-        value = tonumber(value)
-        if type(value) ~= "number" then
-            return alternatives[1], trace_alternatives and formatters["invalid value %s, taking %a"](value,1)
-        elseif value > n then
-            local defaultalt = otf.defaultnodealternate
-            if defaultalt == "first" then
-                return alternatives[n], trace_alternatives and formatters["invalid value %s, taking %a"](value,1)
-            elseif defaultalt == "last" then
-                return alternatives[1], trace_alternatives and formatters["invalid value %s, taking %a"](value,n)
-            else
-                return false, trace_alternatives and formatters["invalid value %a, %s"](value,"out of range")
-            end
-        elseif value == 0 then
-            return getchar(start), trace_alternatives and formatters["invalid value %a, %s"](value,"no change")
-        elseif value < 1 then
-            return alternatives[1], trace_alternatives and formatters["invalid value %a, taking %a"](value,1)
-        else
-            return alternatives[value], trace_alternatives and formatters["value %a, taking %a"](value,value)
-        end
-    end
-end
-
--- handlers
-
-function handlers.gsub_single(head,start,kind,lookupname,replacement)
-    if trace_singles then
-        logprocess("%s: replacing %s by single %s",pref(kind,lookupname),gref(getchar(start)),gref(replacement))
-    end
-    resetinjection(start)
-    setfield(start,"char",replacement)
-    return head, start, true
-end
-
-function handlers.gsub_alternate(head,start,kind,lookupname,alternative,sequence)
-    local value = featurevalue == true and tfmdata.shared.features[kind] or featurevalue
-    local choice, comment = get_alternative_glyph(start,alternative,value,trace_alternatives)
-    if choice then
-        if trace_alternatives then
-            logprocess("%s: replacing %s by alternative %a to %s, %s",pref(kind,lookupname),gref(getchar(start)),choice,gref(choice),comment)
-        end
-        resetinjection(start)
-        setfield(start,"char",choice)
-    else
-        if trace_alternatives then
-            logwarning("%s: no variant %a for %s, %s",pref(kind,lookupname),value,gref(getchar(start)),comment)
-        end
-    end
-    return head, start, true
-end
-
-function handlers.gsub_multiple(head,start,kind,lookupname,multiple,sequence)
-    if trace_multiples then
-        logprocess("%s: replacing %s by multiple %s",pref(kind,lookupname),gref(getchar(start)),gref(multiple))
-    end
-    return multiple_glyphs(head,start,multiple,sequence.flags[1])
-end
-
-function handlers.gsub_ligature(head,start,kind,lookupname,ligature,sequence)
-    local s, stop = getnext(start), nil
-    local startchar = getchar(start)
-    if marks[startchar] then
-        while s do
-            local id = getid(s)
-            if id == glyph_code and getfont(s) == currentfont and getsubtype(s)<256 then
-                local lg = ligature[getchar(s)]
-                if lg then
-                    stop = s
-                    ligature = lg
-                    s = getnext(s)
-                else
-                    break
-                end
-            else
-                break
-            end
-        end
-        if stop then
-            local lig = ligature.ligature
-            if lig then
-                if trace_ligatures then
-                    local stopchar = getchar(stop)
-                    head, start = markstoligature(kind,lookupname,head,start,stop,lig)
-                    logprocess("%s: replacing %s upto %s by ligature %s case 1",pref(kind,lookupname),gref(startchar),gref(stopchar),gref(getchar(start)))
-                else
-                    head, start = markstoligature(kind,lookupname,head,start,stop,lig)
-                end
-                return head, start, true, false
-            else
-                -- ok, goto next lookup
-            end
-        end
-    else
-        local skipmark  = sequence.flags[1]
-        local discfound = false
-        local lastdisc  = nil
-        while s do
-            local id = getid(s)
-            if id == glyph_code and getsubtype(s)<256 then -- not needed
-                if getfont(s) == currentfont then          -- also not needed only when mark
-                    local char = getchar(s)
-                    if skipmark and marks[char] then
-                        s = getnext(s)
-                    else -- ligature is a tree
-                        local lg = ligature[char] -- can there be multiple in a row? maybe in a bad font
-                        if lg then
-                            if not discfound and lastdisc then
-                                discfound = lastdisc
-                                lastdisc  = nil
-                            end
-                            stop = s -- needed for fake so outside then
-                            ligature = lg
-                            s = getnext(s)
-                        else
-                            break
-                        end
-                    end
-                else
-                    break
-                end
-            elseif id == disc_code then
-                lastdisc = s
-                s = getnext(s)
-            else
-                break
-            end
-        end
-        local lig = ligature.ligature -- can't we get rid of this .ligature?
-        if lig then
-            if stop then
-                if trace_ligatures then
-                    local stopchar = getchar(stop)
-                    head, start = toligature(kind,lookupname,head,start,stop,lig,skipmark,discfound)
-                    logprocess("%s: replacing %s upto %s by ligature %s case 2",pref(kind,lookupname),gref(startchar),gref(stopchar),gref(getchar(start)))
-                else
-                    head, start = toligature(kind,lookupname,head,start,stop,lig,skipmark,discfound)
-                end
-            else
-                -- weird but happens (in some arabic font)
-                resetinjection(start)
-                setfield(start,"char",lig)
-                if trace_ligatures then
-                    logprocess("%s: replacing %s by (no real) ligature %s case 3",pref(kind,lookupname),gref(startchar),gref(lig))
-                end
-            end
-            return head, start, true, discfound
-        else
-            -- weird but happens, pseudo ligatures ... just the components
-        end
-    end
-    return head, start, false, discfound
-end
-
-function handlers.gpos_single(head,start,kind,lookupname,kerns,sequence,injection)
-    local startchar = getchar(start)
-    local dx, dy, w, h = setpair(start,tfmdata.parameters.factor,rlmode,sequence.flags[4],kerns,injection) -- ,characters[startchar])
-    if trace_kerns then
-        logprocess("%s: shifting single %s by (%p,%p) and correction (%p,%p)",pref(kind,lookupname),gref(startchar),dx,dy,w,h)
-    end
-    return head, start, false
-end
-
-function handlers.gpos_pair(head,start,kind,lookupname,kerns,sequence,lookuphash,i,injection)
-    -- todo: kerns in disc nodes: pre, post, replace -> loop over disc too
-    -- todo: kerns in components of ligatures
-    local snext = getnext(start)
-    if not snext then
-        return head, start, false
-    else
-        local prev   = start
-        local done   = false
-        local factor = tfmdata.parameters.factor
-        local lookuptype = lookuptypes[lookupname]
-        while snext and getid(snext) == glyph_code and getfont(snext) == currentfont and getsubtype(snext)<256 do
-            local nextchar = getchar(snext)
-            local krn = kerns[nextchar]
-            if not krn and marks[nextchar] then
-                prev = snext
-                snext = getnext(snext)
-            else
-                if not krn then
-                    -- skip
-                elseif type(krn) == "table" then
-                    if lookuptype == "pair" then -- probably not needed
-                        local a, b = krn[2], krn[3]
-                        if a and #a > 0 then
-                            local x, y, w, h = setpair(start,factor,rlmode,sequence.flags[4],a,injection) -- characters[startchar])
-                            if trace_kerns then
-                                local startchar = getchar(start)
-                                logprocess("%s: shifting first of pair %s and %s by (%p,%p) and correction (%p,%p)",pref(kind,lookupname),gref(startchar),gref(nextchar),x,y,w,h)
-                            end
-                        end
-                        if b and #b > 0 then
-                            local x, y, w, h = setpair(snext,factor,rlmode,sequence.flags[4],b,injection) -- characters[nextchar])
-                            if trace_kerns then
-                                local startchar = getchar(start)
-                                logprocess("%s: shifting second of pair %s and %s by (%p,%p) and correction (%p,%p)",pref(kind,lookupname),gref(startchar),gref(nextchar),x,y,w,h)
-                            end
-                        end
-                    else -- wrong ... position has different entries
-                        report_process("%s: check this out (old kern stuff)",pref(kind,lookupname))
-                     -- local a, b = krn[2], krn[6]
-                     -- if a and a ~= 0 then
-                     --     local k = setkern(snext,factor,rlmode,a)
-                     --     if trace_kerns then
-                     --         logprocess("%s: inserting first kern %s between %s and %s",pref(kind,lookupname),k,gref(getchar(prev)),gref(nextchar))
-                     --     end
-                     -- end
-                     -- if b and b ~= 0 then
-                     --     logwarning("%s: ignoring second kern xoff %s",pref(kind,lookupname),b*factor)
-                     -- end
-                    end
-                    done = true
-                elseif krn ~= 0 then
-                    local k = setkern(snext,factor,rlmode,krn,injection)
-                    if trace_kerns then
-                        logprocess("%s: inserting kern %s between %s and %s",pref(kind,lookupname),k,gref(getchar(prev)),gref(nextchar)) -- prev?
-                    end
-                    done = true
-                end
-                break
-            end
-        end
-        return head, start, done
-    end
-end
-
---[[ldx--
-<p>We get hits on a mark, but we're not sure if the it has to be applied so
-we need to explicitly test for basechar, baselig and basemark entries.</p>
---ldx]]--
-
-function handlers.gpos_mark2base(head,start,kind,lookupname,markanchors,sequence)
-    local markchar = getchar(start)
-    if marks[markchar] then
-        local base = getprev(start) -- [glyph] [start=mark]
-        if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then
-            local basechar = getchar(base)
-            if marks[basechar] then
-                while true do
-                    base = getprev(base)
-                    if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then
-                        basechar = getchar(base)
-                        if not marks[basechar] then
-                            break
-                        end
-                    else
-                        if trace_bugs then
-                            logwarning("%s: no base for mark %s",pref(kind,lookupname),gref(markchar))
-                        end
-                        return head, start, false
-                    end
-                end
-            end
-            local baseanchors = descriptions[basechar]
-            if baseanchors then
-                baseanchors = baseanchors.anchors
-            end
-            if baseanchors then
-                local baseanchors = baseanchors['basechar']
-                if baseanchors then
-                    local al = anchorlookups[lookupname]
-                    for anchor,ba in next, baseanchors do
-                        if al[anchor] then
-                            local ma = markanchors[anchor]
-                            if ma then
-                                local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,characters[basechar])
-                                if trace_marks then
-                                    logprocess("%s, anchor %s, bound %s: anchoring mark %s to basechar %s => (%p,%p)",
-                                        pref(kind,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy)
-                                end
-                                return head, start, true
-                            end
-                        end
-                    end
-                    if trace_bugs then
-                        logwarning("%s, no matching anchors for mark %s and base %s",pref(kind,lookupname),gref(markchar),gref(basechar))
-                    end
-                end
-            elseif trace_bugs then
-            --  logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(basechar))
-                onetimemessage(currentfont,basechar,"no base anchors",report_fonts)
-            end
-        elseif trace_bugs then
-            logwarning("%s: prev node is no char",pref(kind,lookupname))
-        end
-    elseif trace_bugs then
-        logwarning("%s: mark %s is no mark",pref(kind,lookupname),gref(markchar))
-    end
-    return head, start, false
-end
-
-function handlers.gpos_mark2ligature(head,start,kind,lookupname,markanchors,sequence)
-    -- check chainpos variant
-    local markchar = getchar(start)
-    if marks[markchar] then
-        local base = getprev(start) -- [glyph] [optional marks] [start=mark]
-        if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then
-            local basechar = getchar(base)
-            if marks[basechar] then
-                while true do
-                    base = getprev(base)
-                    if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then
-                        basechar = getchar(base)
-                        if not marks[basechar] then
-                            break
-                        end
-                    else
-                        if trace_bugs then
-                            logwarning("%s: no base for mark %s",pref(kind,lookupname),gref(markchar))
-                        end
-                        return head, start, false
-                    end
-                end
-            end
-            local index = getligaindex(start)
-            local baseanchors = descriptions[basechar]
-            if baseanchors then
-                baseanchors = baseanchors.anchors
-                if baseanchors then
-                   local baseanchors = baseanchors['baselig']
-                   if baseanchors then
-                        local al = anchorlookups[lookupname]
-                        for anchor, ba in next, baseanchors do
-                            if al[anchor] then
-                                local ma = markanchors[anchor]
-                                if ma then
-                                    ba = ba[index]
-                                    if ba then
-                                        local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,characters[basechar]) -- index
-                                        if trace_marks then
-                                            logprocess("%s, anchor %s, index %s, bound %s: anchoring mark %s to baselig %s at index %s => (%p,%p)",
-                                                pref(kind,lookupname),anchor,index,bound,gref(markchar),gref(basechar),index,dx,dy)
-                                        end
-                                        return head, start, true
-                                    else
-                                        if trace_bugs then
-                                            logwarning("%s: no matching anchors for mark %s and baselig %s with index %a",pref(kind,lookupname),gref(markchar),gref(basechar),index)
-                                        end
-                                    end
-                                end
-                            end
-                        end
-                        if trace_bugs then
-                            logwarning("%s: no matching anchors for mark %s and baselig %s",pref(kind,lookupname),gref(markchar),gref(basechar))
-                        end
-                    end
-                end
-            elseif trace_bugs then
-            --  logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(basechar))
-                onetimemessage(currentfont,basechar,"no base anchors",report_fonts)
-            end
-        elseif trace_bugs then
-            logwarning("%s: prev node is no char",pref(kind,lookupname))
-        end
-    elseif trace_bugs then
-        logwarning("%s: mark %s is no mark",pref(kind,lookupname),gref(markchar))
-    end
-    return head, start, false
-end
-
-function handlers.gpos_mark2mark(head,start,kind,lookupname,markanchors,sequence)
-    local markchar = getchar(start)
-    if marks[markchar] then
-        local base = getprev(start) -- [glyph] [basemark] [start=mark]
-        local slc = getligaindex(start)
-        if slc then -- a rather messy loop ... needs checking with husayni
-            while base do
-                local blc = getligaindex(base)
-                if blc and blc ~= slc then
-                    base = getprev(base)
-                else
-                    break
-                end
-            end
-        end
-        if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then -- subtype test can go
-            local basechar = getchar(base)
-            local baseanchors = descriptions[basechar]
-            if baseanchors then
-                baseanchors = baseanchors.anchors
-                if baseanchors then
-                    baseanchors = baseanchors['basemark']
-                    if baseanchors then
-                        local al = anchorlookups[lookupname]
-                        for anchor,ba in next, baseanchors do
-                            if al[anchor] then
-                                local ma = markanchors[anchor]
-                                if ma then
-                                    local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,characters[basechar],true)
-                                    if trace_marks then
-                                        logprocess("%s, anchor %s, bound %s: anchoring mark %s to basemark %s => (%p,%p)",
-                                            pref(kind,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy)
-                                    end
-                                    return head, start, true
-                                end
-                            end
-                        end
-                        if trace_bugs then
-                            logwarning("%s: no matching anchors for mark %s and basemark %s",pref(kind,lookupname),gref(markchar),gref(basechar))
-                        end
-                    end
-                end
-            elseif trace_bugs then
-            --  logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(basechar))
-                onetimemessage(currentfont,basechar,"no base anchors",report_fonts)
-            end
-        elseif trace_bugs then
-            logwarning("%s: prev node is no mark",pref(kind,lookupname))
-        end
-    elseif trace_bugs then
-        logwarning("%s: mark %s is no mark",pref(kind,lookupname),gref(markchar))
-    end
-    return head, start, false
-end
-
-function handlers.gpos_cursive(head,start,kind,lookupname,exitanchors,sequence) -- to be checked
-    local alreadydone = cursonce and getprop(start,a_cursbase)
-    if not alreadydone then
-        local done = false
-        local startchar = getchar(start)
-        if marks[startchar] then
-            if trace_cursive then
-                logprocess("%s: ignoring cursive for mark %s",pref(kind,lookupname),gref(startchar))
-            end
-        else
-            local nxt = getnext(start)
-            while not done and nxt and getid(nxt) == glyph_code and getfont(nxt) == currentfont and getsubtype(nxt)<256 do
-                local nextchar = getchar(nxt)
-                if marks[nextchar] then
-                    -- should not happen (maybe warning)
-                    nxt = getnext(nxt)
-                else
-                    local entryanchors = descriptions[nextchar]
-                    if entryanchors then
-                        entryanchors = entryanchors.anchors
-                        if entryanchors then
-                            entryanchors = entryanchors['centry']
-                            if entryanchors then
-                                local al = anchorlookups[lookupname]
-                                for anchor, entry in next, entryanchors do
-                                    if al[anchor] then
-                                        local exit = exitanchors[anchor]
-                                        if exit then
-                                            local dx, dy, bound = setcursive(start,nxt,tfmdata.parameters.factor,rlmode,exit,entry,characters[startchar],characters[nextchar])
-                                            if trace_cursive then
-                                                logprocess("%s: moving %s to %s cursive (%p,%p) using anchor %s and bound %s in rlmode %s",pref(kind,lookupname),gref(startchar),gref(nextchar),dx,dy,anchor,bound,rlmode)
-                                            end
-                                            done = true
-                                            break
-                                        end
-                                    end
-                                end
-                            end
-                        end
-                    elseif trace_bugs then
-                    --  logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(startchar))
-                        onetimemessage(currentfont,startchar,"no entry anchors",report_fonts)
-                    end
-                    break
-                end
-            end
-        end
-        return head, start, done
-    else
-        if trace_cursive and trace_details then
-            logprocess("%s, cursive %s is already done",pref(kind,lookupname),gref(getchar(start)),alreadydone)
-        end
-        return head, start, false
-    end
-end
-
---[[ldx--
-<p>I will implement multiple chain replacements once I run into a font that uses
-it. It's not that complex to handle.</p>
---ldx]]--
-
-local chainprocs = { }
-
-local function logprocess(...)
-    if trace_steps then
-        registermessage(...)
-    end
-    report_subchain(...)
-end
-
-local logwarning = report_subchain
-
-local function logprocess(...)
-    if trace_steps then
-        registermessage(...)
-    end
-    report_chain(...)
-end
-
-local logwarning = report_chain
-
--- We could share functions but that would lead to extra function calls with many
--- arguments, redundant tests and confusing messages.
-
-function chainprocs.chainsub(head,start,stop,kind,chainname,currentcontext,lookuphash,lookuplist,chainlookupname)
-    logwarning("%s: a direct call to chainsub cannot happen",cref(kind,chainname,chainlookupname))
-    return head, start, false
-end
-
--- The reversesub is a special case, which is why we need to store the replacements
--- in a bit weird way. There is no lookup and the replacement comes from the lookup
--- itself. It is meant mostly for dealing with Urdu.
-
-function chainprocs.reversesub(head,start,stop,kind,chainname,currentcontext,lookuphash,replacements)
-    local char = getchar(start)
-    local replacement = replacements[char]
-    if replacement then
-        if trace_singles then
-            logprocess("%s: single reverse replacement of %s by %s",cref(kind,chainname),gref(char),gref(replacement))
-        end
-        resetinjection(start)
-        setfield(start,"char",replacement)
-        return head, start, true
-    else
-        return head, start, false
-    end
-end
-
---[[ldx--
-<p>This chain stuff is somewhat tricky since we can have a sequence of actions to be
-applied: single, alternate, multiple or ligature where ligature can be an invalid
-one in the sense that it will replace multiple by one but not neccessary one that
-looks like the combination (i.e. it is the counterpart of multiple then). For
-example, the following is valid:</p>
-
-<typing>
-<line>xxxabcdexxx [single a->A][multiple b->BCD][ligature cde->E] xxxABCDExxx</line>
-</typing>
-
-<p>Therefore we we don't really do the replacement here already unless we have the
-single lookup case. The efficiency of the replacements can be improved by deleting
-as less as needed but that would also make the code even more messy.</p>
---ldx]]--
-
--- local function delete_till_stop(head,start,stop,ignoremarks) -- keeps start
---     local n = 1
---     if start == stop then
---         -- done
---     elseif ignoremarks then
---         repeat -- start x x m x x stop => start m
---             local next = getnext(start)
---             if not marks[getchar(next)] then
---                 local components = getfield(next,"components")
---                 if components then -- probably not needed
---                     flush_node_list(components)
---                 end
---                 head = delete_node(head,next)
---             end
---             n = n + 1
---         until next == stop
---     else -- start x x x stop => start
---         repeat
---             local next = getnext(start)
---             local components = getfield(next,"components")
---             if components then -- probably not needed
---                 flush_node_list(components)
---             end
---             head = delete_node(head,next)
---             n = n + 1
---         until next == stop
---     end
---     return head, n
--- end
-
---[[ldx--
-<p>Here we replace start by a single variant.</p>
---ldx]]--
-
-function chainprocs.gsub_single(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex)
-    -- todo: marks ?
-    local current = start
-    local subtables = currentlookup.subtables
-    if #subtables > 1 then
-        logwarning("todo: check if we need to loop over the replacements: % t",subtables)
-    end
-    while current do
-        if getid(current) == glyph_code then
-            local currentchar = getchar(current)
-            local lookupname = subtables[1] -- only 1
-            local replacement = lookuphash[lookupname]
-            if not replacement then
-                if trace_bugs then
-                    logwarning("%s: no single hits",cref(kind,chainname,chainlookupname,lookupname,chainindex))
-                end
-            else
-                replacement = replacement[currentchar]
-                if not replacement or replacement == "" then
-                    if trace_bugs then
-                        logwarning("%s: no single for %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(currentchar))
-                    end
-                else
-                    if trace_singles then
-                        logprocess("%s: replacing single %s by %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(currentchar),gref(replacement))
-                    end
-                    resetinjection(current)
-                    setfield(current,"char",replacement)
-                end
-            end
-            return head, start, true
-        elseif current == stop then
-            break
-        else
-            current = getnext(current)
-        end
-    end
-    return head, start, false
-end
-
---[[ldx--
-<p>Here we replace start by a sequence of new glyphs.</p>
---ldx]]--
-
-function chainprocs.gsub_multiple(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname)
- -- local head, n = delete_till_stop(head,start,stop)
-    local startchar = getchar(start)
-    local subtables = currentlookup.subtables
-    local lookupname = subtables[1]
-    local replacements = lookuphash[lookupname]
-    if not replacements then
-        if trace_bugs then
-            logwarning("%s: no multiple hits",cref(kind,chainname,chainlookupname,lookupname))
-        end
-    else
-        replacements = replacements[startchar]
-        if not replacements or replacement == "" then
-            if trace_bugs then
-                logwarning("%s: no multiple for %s",cref(kind,chainname,chainlookupname,lookupname),gref(startchar))
-            end
-        else
-            if trace_multiples then
-                logprocess("%s: replacing %s by multiple characters %s",cref(kind,chainname,chainlookupname,lookupname),gref(startchar),gref(replacements))
-            end
-            return multiple_glyphs(head,start,replacements,currentlookup.flags[1])
-        end
-    end
-    return head, start, false
-end
-
---[[ldx--
-<p>Here we replace start by new glyph. First we delete the rest of the match.</p>
---ldx]]--
-
--- char_1 mark_1 -> char_x mark_1 (ignore marks)
--- char_1 mark_1 -> char_x
-
--- to be checked: do we always have just one glyph?
--- we can also have alternates for marks
--- marks come last anyway
--- are there cases where we need to delete the mark
-
-function chainprocs.gsub_alternate(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname)
-    local current = start
-    local subtables = currentlookup.subtables
-    local value  = featurevalue == true and tfmdata.shared.features[kind] or featurevalue
-    while current do
-        if getid(current) == glyph_code then -- is this check needed?
-            local currentchar = getchar(current)
-            local lookupname = subtables[1]
-            local alternatives = lookuphash[lookupname]
-            if not alternatives then
-                if trace_bugs then
-                    logwarning("%s: no alternative hit",cref(kind,chainname,chainlookupname,lookupname))
-                end
-            else
-                alternatives = alternatives[currentchar]
-                if alternatives then
-                    local choice, comment = get_alternative_glyph(current,alternatives,value,trace_alternatives)
-                    if choice then
-                        if trace_alternatives then
-                            logprocess("%s: replacing %s by alternative %a to %s, %s",cref(kind,chainname,chainlookupname,lookupname),gref(char),choice,gref(choice),comment)
-                        end
-                        resetinjection(start)
-                        setfield(start,"char",choice)
-                    else
-                        if trace_alternatives then
-                            logwarning("%s: no variant %a for %s, %s",cref(kind,chainname,chainlookupname,lookupname),value,gref(char),comment)
-                        end
-                    end
-                elseif trace_bugs then
-                    logwarning("%s: no alternative for %s, %s",cref(kind,chainname,chainlookupname,lookupname),gref(currentchar),comment)
-                end
-            end
-            return head, start, true
-        elseif current == stop then
-            break
-        else
-            current = getnext(current)
-        end
-    end
-    return head, start, false
-end
-
---[[ldx--
-<p>When we replace ligatures we use a helper that handles the marks. I might change
-this function (move code inline and handle the marks by a separate function). We
-assume rather stupid ligatures (no complex disc nodes).</p>
---ldx]]--
-
-function chainprocs.gsub_ligature(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex)
-    local startchar = getchar(start)
-    local subtables = currentlookup.subtables
-    local lookupname = subtables[1]
-    local ligatures = lookuphash[lookupname]
-    if not ligatures then
-        if trace_bugs then
-            logwarning("%s: no ligature hits",cref(kind,chainname,chainlookupname,lookupname,chainindex))
-        end
-    else
-        ligatures = ligatures[startchar]
-        if not ligatures then
-            if trace_bugs then
-                logwarning("%s: no ligatures starting with %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar))
-            end
-        else
-            local s = getnext(start)
-            local discfound = false
-            local last = stop
-            local nofreplacements = 1
-            local skipmark = currentlookup.flags[1]
-            while s do
-                local id = getid(s)
-                if id == disc_code then
-                    if not discfound then
-                        discfound = s
-                    end
-                    if s == stop then
-                        break -- okay? or before the disc
-                    else
-                        s = getnext(s)
-                    end
-                else
-                    local schar = getchar(s)
-                    if skipmark and marks[schar] then -- marks
-                        s = getnext(s)
-                    else
-                        local lg = ligatures[schar]
-                        if lg then
-                            ligatures, last, nofreplacements = lg, s, nofreplacements + 1
-                            if s == stop then
-                                break
-                            else
-                                s = getnext(s)
-                            end
-                        else
-                            break
-                        end
-                    end
-                end
-            end
-            local l2 = ligatures.ligature
-            if l2 then
-                if chainindex then
-                    stop = last
-                end
-                if trace_ligatures then
-                    if start == stop then
-                        logprocess("%s: replacing character %s by ligature %s case 3",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(l2))
-                    else
-                        logprocess("%s: replacing character %s upto %s by ligature %s case 4",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(getchar(stop)),gref(l2))
-                    end
-                end
-                head, start = toligature(kind,lookupname,head,start,stop,l2,currentlookup.flags[1],discfound)
-                return head, start, true, nofreplacements, discfound
-            elseif trace_bugs then
-                if start == stop then
-                    logwarning("%s: replacing character %s by ligature fails",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar))
-                else
-                    logwarning("%s: replacing character %s upto %s by ligature fails",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(getchar(stop)))
-                end
-            end
-        end
-    end
-    return head, start, false, 0, false
-end
-
-function chainprocs.gpos_single(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex,sequence)
-    -- untested .. needs checking for the new model
-    local startchar = getchar(start)
-    local subtables = currentlookup.subtables
-    local lookupname = subtables[1]
-    local kerns = lookuphash[lookupname]
-    if kerns then
-        kerns = kerns[startchar] -- needed ?
-        if kerns then
-            local dx, dy, w, h = setpair(start,tfmdata.parameters.factor,rlmode,sequence.flags[4],kerns) -- ,characters[startchar])
-            if trace_kerns then
-                logprocess("%s: shifting single %s by (%p,%p) and correction (%p,%p)",cref(kind,chainname,chainlookupname),gref(startchar),dx,dy,w,h)
-            end
-        end
-    end
-    return head, start, false
-end
-
-function chainprocs.gpos_pair(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex,sequence)
-    local snext = getnext(start)
-    if snext then
-        local startchar = getchar(start)
-        local subtables = currentlookup.subtables
-        local lookupname = subtables[1]
-        local kerns = lookuphash[lookupname]
-        if kerns then
-            kerns = kerns[startchar]
-            if kerns then
-                local lookuptype = lookuptypes[lookupname]
-                local prev, done = start, false
-                local factor = tfmdata.parameters.factor
-                while snext and getid(snext) == glyph_code and getfont(snext) == currentfont and getsubtype(snext)<256 do
-                    local nextchar = getchar(snext)
-                    local krn = kerns[nextchar]
-                    if not krn and marks[nextchar] then
-                        prev = snext
-                        snext = getnext(snext)
-                    else
-                        if not krn then
-                            -- skip
-                        elseif type(krn) == "table" then
-                            if lookuptype == "pair" then
-                                local a, b = krn[2], krn[3]
-                                if a and #a > 0 then
-                                    local startchar = getchar(start)
-                                    local x, y, w, h = setpair(start,factor,rlmode,sequence.flags[4],a) -- ,characters[startchar])
-                                    if trace_kerns then
-                                        logprocess("%s: shifting first of pair %s and %s by (%p,%p) and correction (%p,%p)",cref(kind,chainname,chainlookupname),gref(startchar),gref(nextchar),x,y,w,h)
-                                    end
-                                end
-                                if b and #b > 0 then
-                                    local startchar = getchar(start)
-                                    local x, y, w, h = setpair(snext,factor,rlmode,sequence.flags[4],b) -- ,characters[nextchar])
-                                    if trace_kerns then
-                                        logprocess("%s: shifting second of pair %s and %s by (%p,%p) and correction (%p,%p)",cref(kind,chainname,chainlookupname),gref(startchar),gref(nextchar),x,y,w,h)
-                                    end
-                                end
-                            else
-                                report_process("%s: check this out (old kern stuff)",cref(kind,chainname,chainlookupname))
-                             -- local a, b = krn[2], krn[6]
-                             -- if a and a ~= 0 then
-                             --     local k = setkern(snext,factor,rlmode,a)
-                             --     if trace_kerns then
-                             --         logprocess("%s: inserting first kern %s between %s and %s",cref(kind,chainname,chainlookupname),k,gref(getchar(prev)),gref(nextchar))
-                             --     end
-                             -- end
-                             -- if b and b ~= 0 then
-                             --     logwarning("%s: ignoring second kern xoff %s",cref(kind,chainname,chainlookupname),b*factor)
-                             -- end
-                            end
-                            done = true
-                        elseif krn ~= 0 then
-                            local k = setkern(snext,factor,rlmode,krn)
-                            if trace_kerns then
-                                logprocess("%s: inserting kern %s between %s and %s",cref(kind,chainname,chainlookupname),k,gref(getchar(prev)),gref(nextchar))
-                            end
-                            done = true
-                        end
-                        break
-                    end
-                end
-                return head, start, done
-            end
-        end
-    end
-    return head, start, false
-end
-
-function chainprocs.gpos_mark2base(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname)
-    local markchar = getchar(start)
-    if marks[markchar] then
-        local subtables = currentlookup.subtables
-        local lookupname = subtables[1]
-        local markanchors = lookuphash[lookupname]
-        if markanchors then
-            markanchors = markanchors[markchar]
-        end
-        if markanchors then
-            local base = getprev(start) -- [glyph] [start=mark]
-            if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then
-                local basechar = getchar(base)
-                if marks[basechar] then
-                    while true do
-                        base = getprev(base)
-                        if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then
-                            basechar = getchar(base)
-                            if not marks[basechar] then
-                                break
-                            end
-                        else
-                            if trace_bugs then
-                                logwarning("%s: no base for mark %s",pref(kind,lookupname),gref(markchar))
-                            end
-                            return head, start, false
-                        end
-                    end
-                end
-                local baseanchors = descriptions[basechar].anchors
-                if baseanchors then
-                    local baseanchors = baseanchors['basechar']
-                    if baseanchors then
-                        local al = anchorlookups[lookupname]
-                        for anchor,ba in next, baseanchors do
-                            if al[anchor] then
-                                local ma = markanchors[anchor]
-                                if ma then
-                                    local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,characters[basechar])
-                                    if trace_marks then
-                                        logprocess("%s, anchor %s, bound %s: anchoring mark %s to basechar %s => (%p,%p)",
-                                            cref(kind,chainname,chainlookupname,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy)
-                                    end
-                                    return head, start, true
-                                end
-                            end
-                        end
-                        if trace_bugs then
-                            logwarning("%s, no matching anchors for mark %s and base %s",cref(kind,chainname,chainlookupname,lookupname),gref(markchar),gref(basechar))
-                        end
-                    end
-                end
-            elseif trace_bugs then
-                logwarning("%s: prev node is no char",cref(kind,chainname,chainlookupname,lookupname))
-            end
-        elseif trace_bugs then
-            logwarning("%s: mark %s has no anchors",cref(kind,chainname,chainlookupname,lookupname),gref(markchar))
-        end
-    elseif trace_bugs then
-        logwarning("%s: mark %s is no mark",cref(kind,chainname,chainlookupname),gref(markchar))
-    end
-    return head, start, false
-end
-
-function chainprocs.gpos_mark2ligature(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname)
-    local markchar = getchar(start)
-    if marks[markchar] then
-        local subtables = currentlookup.subtables
-        local lookupname = subtables[1]
-        local markanchors = lookuphash[lookupname]
-        if markanchors then
-            markanchors = markanchors[markchar]
-        end
-        if markanchors then
-            local base = getprev(start) -- [glyph] [optional marks] [start=mark]
-            if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then
-                local basechar = getchar(base)
-                if marks[basechar] then
-                    while true do
-                        base = getprev(base)
-                        if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then
-                            basechar = getchar(base)
-                            if not marks[basechar] then
-                                break
-                            end
-                        else
-                            if trace_bugs then
-                                logwarning("%s: no base for mark %s",cref(kind,chainname,chainlookupname,lookupname),markchar)
-                            end
-                            return head, start, false
-                        end
-                    end
-                end
-                -- todo: like marks a ligatures hash
-                local index = getligaindex(start)
-                local baseanchors = descriptions[basechar].anchors
-                if baseanchors then
-                   local baseanchors = baseanchors['baselig']
-                   if baseanchors then
-                        local al = anchorlookups[lookupname]
-                        for anchor,ba in next, baseanchors do
-                            if al[anchor] then
-                                local ma = markanchors[anchor]
-                                if ma then
-                                    ba = ba[index]
-                                    if ba then
-                                        local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,characters[basechar])
-                                        if trace_marks then
-                                            logprocess("%s, anchor %s, bound %s: anchoring mark %s to baselig %s at index %s => (%p,%p)",
-                                                cref(kind,chainname,chainlookupname,lookupname),anchor,a or bound,gref(markchar),gref(basechar),index,dx,dy)
-                                        end
-                                        return head, start, true
-                                    end
-                                end
-                            end
-                        end
-                        if trace_bugs then
-                            logwarning("%s: no matching anchors for mark %s and baselig %s",cref(kind,chainname,chainlookupname,lookupname),gref(markchar),gref(basechar))
-                        end
-                    end
-                end
-            elseif trace_bugs then
-                logwarning("feature %s, lookup %s: prev node is no char",kind,lookupname)
-            end
-        elseif trace_bugs then
-            logwarning("%s: mark %s has no anchors",cref(kind,chainname,chainlookupname,lookupname),gref(markchar))
-        end
-    elseif trace_bugs then
-        logwarning("%s: mark %s is no mark",cref(kind,chainname,chainlookupname),gref(markchar))
-    end
-    return head, start, false
-end
-
-function chainprocs.gpos_mark2mark(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname)
-    local markchar = getchar(start)
-    if marks[markchar] then
-    --  local markanchors = descriptions[markchar].anchors markanchors = markanchors and markanchors.mark
-        local subtables = currentlookup.subtables
-        local lookupname = subtables[1]
-        local markanchors = lookuphash[lookupname]
-        if markanchors then
-            markanchors = markanchors[markchar]
-        end
-        if markanchors then
-            local base = getprev(start) -- [glyph] [basemark] [start=mark]
-            local slc = getligaindex(start)
-            if slc then -- a rather messy loop ... needs checking with husayni
-                while base do
-                    local blc = getligaindex(base)
-                    if blc and blc ~= slc then
-                        base = getprev(base)
-                    else
-                        break
-                    end
-                end
-            end
-            if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then -- subtype test can go
-                local basechar = getchar(base)
-                local baseanchors = descriptions[basechar].anchors
-                if baseanchors then
-                    baseanchors = baseanchors['basemark']
-                    if baseanchors then
-                        local al = anchorlookups[lookupname]
-                        for anchor,ba in next, baseanchors do
-                            if al[anchor] then
-                                local ma = markanchors[anchor]
-                                if ma then
-                                    local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,characters[basechar],true)
-                                    if trace_marks then
-                                        logprocess("%s, anchor %s, bound %s: anchoring mark %s to basemark %s => (%p,%p)",
-                                            cref(kind,chainname,chainlookupname,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy)
-                                    end
-                                    return head, start, true
-                                end
-                            end
-                        end
-                        if trace_bugs then
-                            logwarning("%s: no matching anchors for mark %s and basemark %s",gref(kind,chainname,chainlookupname,lookupname),gref(markchar),gref(basechar))
-                        end
-                    end
-                end
-            elseif trace_bugs then
-                logwarning("%s: prev node is no mark",cref(kind,chainname,chainlookupname,lookupname))
-            end
-        elseif trace_bugs then
-            logwarning("%s: mark %s has no anchors",cref(kind,chainname,chainlookupname,lookupname),gref(markchar))
-        end
-    elseif trace_bugs then
-        logwarning("%s: mark %s is no mark",cref(kind,chainname,chainlookupname),gref(markchar))
-    end
-    return head, start, false
-end
-
-function chainprocs.gpos_cursive(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname)
-    local alreadydone = cursonce and getprop(start,a_cursbase)
-    if not alreadydone then
-        local startchar = getchar(start)
-        local subtables = currentlookup.subtables
-        local lookupname = subtables[1]
-        local exitanchors = lookuphash[lookupname]
-        if exitanchors then
-            exitanchors = exitanchors[startchar]
-        end
-        if exitanchors then
-            local done = false
-            if marks[startchar] then
-                if trace_cursive then
-                    logprocess("%s: ignoring cursive for mark %s",pref(kind,lookupname),gref(startchar))
-                end
-            else
-                local nxt = getnext(start)
-                while not done and nxt and getid(nxt) == glyph_code and getfont(nxt) == currentfont and getsubtype(nxt)<256 do
-                    local nextchar = getchar(nxt)
-                    if marks[nextchar] then
-                        -- should not happen (maybe warning)
-                        nxt = getnext(nxt)
-                    else
-                        local entryanchors = descriptions[nextchar]
-                        if entryanchors then
-                            entryanchors = entryanchors.anchors
-                            if entryanchors then
-                                entryanchors = entryanchors['centry']
-                                if entryanchors then
-                                    local al = anchorlookups[lookupname]
-                                    for anchor, entry in next, entryanchors do
-                                        if al[anchor] then
-                                            local exit = exitanchors[anchor]
-                                            if exit then
-                                                local dx, dy, bound = setcursive(start,nxt,tfmdata.parameters.factor,rlmode,exit,entry,characters[startchar],characters[nextchar])
-                                                if trace_cursive then
-                                                    logprocess("%s: moving %s to %s cursive (%p,%p) using anchor %s and bound %s in rlmode %s",pref(kind,lookupname),gref(startchar),gref(nextchar),dx,dy,anchor,bound,rlmode)
-                                                end
-                                                done = true
-                                                break
-                                            end
-                                        end
-                                    end
-                                end
-                            end
-                        elseif trace_bugs then
-                        --  logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(startchar))
-                            onetimemessage(currentfont,startchar,"no entry anchors",report_fonts)
-                        end
-                        break
-                    end
-                end
-            end
-            return head, start, done
-        else
-            if trace_cursive and trace_details then
-                logprocess("%s, cursive %s is already done",pref(kind,lookupname),gref(getchar(start)),alreadydone)
-            end
-            return head, start, false
-        end
-    end
-    return head, start, false
-end
-
--- what pointer to return, spec says stop
--- to be discussed ... is bidi changer a space?
--- elseif char == zwnj and sequence[n][32] then -- brrr
-
--- somehow l or f is global
--- we don't need to pass the currentcontext, saves a bit
--- make a slow variant then can be activated but with more tracing
-
-local function show_skip(kind,chainname,char,ck,class)
-    if ck[9] then
-        logwarning("%s: skipping char %s, class %a, rule %a, lookuptype %a, %a => %a",cref(kind,chainname),gref(char),class,ck[1],ck[2],ck[9],ck[10])
-    else
-        logwarning("%s: skipping char %s, class %a, rule %a, lookuptype %a",cref(kind,chainname),gref(char),class,ck[1],ck[2])
-    end
-end
-
--- A previous version had disc collapsing code in the (single sub) handler plus some
--- checking in the main loop, but that left the pre/post sequences undone. The best
--- solution is to add some checking there and backtrack when a replace/post matches
--- but it takes a bit of work to figure out an efficient way (this is what the sweep*
--- names refer to). I might look into that variant one day again as it can replace
--- some other code too. In that approach we can have a special version for gub and pos
--- which gains some speed. This method does the test and passes info to the handlers
--- (sweepnode, sweepmode, sweepprev, sweepnext, etc). Here collapsing is handled in the
--- main loop which also makes code elsewhere simpler (i.e. no need for the other special
--- runners and disc code in ligature building). I also experimented with pushing preceding
--- glyphs sequences in the replace/pre fields beforehand which saves checking afterwards
--- but at the cost of duplicate glyphs (memory) but it's too much overhead (runtime).
---
--- In the meantime Kai had moved the code from the single chain into a more general handler
--- and this one (renamed to chaindisk) is used now. I optimized the code a bit and brought
--- it in sycn with the other code. Hopefully I didn't introduce errors. Note: this somewhat
--- complex approach is meant for fonts that implement (for instance) ligatures by character
--- replacement which to some extend is not that suitable for hyphenation. I also use some
--- helpers. This method passes some states but reparses the list. There is room for a bit of
--- speed up but that will be done in the context version. (In fact a partial rewrite of all
--- code can bring some more efficientry.)
---
--- I didn't test it with extremes but successive disc nodes still can give issues but in
--- order to handle that we need more complex code which also slows down even more. The main
--- loop variant could deal with that: test, collapse, backtrack.
-
-local function chaindisk(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,chainindex,sequence,chainproc)
-
-    if not start then
-        return head, start, false
-    end
-
-    local startishead   = start == head
-    local seq           = ck[3]
-    local f             = ck[4]
-    local l             = ck[5]
-    local s             = #seq
-    local done          = false
-    local sweepnode     = sweepnode
-    local sweeptype     = sweeptype
-    local sweepoverflow = false
-    local checkdisc     = getprev(head) -- hm bad name head
-    local keepdisc      = not sweepnode
-    local lookaheaddisc = nil
-    local backtrackdisc = nil
-    local current       = start
-    local last          = start
-    local prev          = getprev(start)
-
-    -- fishy: so we can overflow and then go on in the sweep?
-
-    local i = f
-    while i <= l do
-        local id = getid(current)
-        if id == glyph_code then
-            i       = i + 1
-            last    = current
-            current = getnext(current)
-        elseif id == disc_code then
-            if keepdisc then
-                keepdisc = false
-                if notmatchpre[current] ~= notmatchreplace[current] then
-                    lookaheaddisc = current
-                end
-                local replace = getfield(current,"replace")
-                while replace and i <= l do
-                    if getid(replace) == glyph_code then
-                        i = i + 1
-                    end
-                    replace = getnext(replace)
-                end
-                last    = current
-                current = getnext(c)
-            else
-                head, current = flattendisk(head,current)
-            end
-        else
-            last    = current
-            current = getnext(current)
-        end
-        if current then
-            -- go on
-        elseif sweepoverflow then
-            -- we already are folling up on sweepnode
-            break
-        elseif sweeptype == "post" or sweeptype == "replace" then
-            current = getnext(sweepnode)
-            if current then
-                sweeptype     = nil
-                sweepoverflow = true
-            else
-                break
-            end
-        end
-    end
-
-    if sweepoverflow then
-        local prev = current and getprev(current)
-        if not current or prev ~= sweepnode then
-            local head = getnext(sweepnode)
-            local tail = nil
-            if prev then
-                tail = prev
-                setfield(current,"prev",sweepnode)
-            else
-                tail = find_node_tail(head)
-            end
-            setfield(sweepnode,"next",current)
-            setfield(head,"prev",nil)
-            setfield(tail,"next",nil)
-            appenddisc(sweepnode,head)
-        end
-    end
-
-    if l < s then
-        local i = l
-        local t = sweeptype == "post" or sweeptype == "replace"
-        while current and i < s do
-            local id = getid(current)
-            if id == glyph_code then
-                i       = i + 1
-                current = getnext(current)
-            elseif id == disc_code then
-                if keepdisc then
-                    keepdisc = false
-                    if notmatchpre[current] ~= notmatchreplace[current] then
-                        lookaheaddisc = current
-                    end
-                    local replace = getfield(c,"replace")
-                    while replace and i < s do
-                        if getid(replace) == glyph_code then
-                            i = i + 1
-                        end
-                        replace = getnext(replace)
-                    end
-                    current = getnext(current)
-                elseif notmatchpre[current] ~= notmatchreplace[current] then
-                    head, current = flattendisk(head,current)
-                else
-                    current = getnext(current) -- HH
-                end
-            else
-                current = getnext(current)
-            end
-            if not current and t then
-                current = getnext(sweepnode)
-                if current then
-                    sweeptype = nil
-                end
-            end
-        end
-    end
-
-    if f > 1 then
-        local current = prev
-        local i       = f
-        local t       = sweeptype == "pre" or sweeptype == "replace"
-        if not current and t and current == checkdisk then
-            current = getprev(sweepnode)
-        end
-        while current and i > 1 do -- missing getprev added / moved outside
-            local id = getid(current)
-            if id == glyph_code then
-                i = i - 1
-            elseif id == disc_code then
-                if keepdisc then
-                    keepdisc = false
-                    if notmatchpost[current] ~= notmatchreplace[current] then
-                        backtrackdisc = current
-                    end
-                    local replace = getfield(current,"replace")
-                    while replace and i > 1 do
-                        if getid(replace) == glyph_code then
-                            i = i - 1
-                        end
-                        replace = getnext(replace)
-                    end
-                elseif notmatchpost[current] ~= notmatchreplace[current] then
-                    head, current = flattendisk(head,current)
-                end
-            end
-            current = getprev(current)
-            if t and current == checkdisk then
-                current = getprev(sweepnode)
-            end
-        end
-    end
-
-    local ok = false
-    if lookaheaddisc then
-
-        local cf            = start
-        local cl            = getprev(lookaheaddisc)
-        local cprev         = getprev(start)
-        local insertedmarks = 0
-
-        while cprev and getid(cf) == glyph_code and getfont(cf) == currentfont and getsubtype(cf) < 256 and marks[getchar(cf)] do
-            insertedmarks = insertedmarks + 1
-            cf            = cprev
-            startishead   = cf == head
-            cprev         = getprev(cprev)
-        end
-
-        setfield(lookaheaddisc,"prev",cprev)
-        if cprev then
-            setfield(cprev,"next",lookaheaddisc)
-        end
-        setfield(cf,"prev",nil)
-        setfield(cl,"next",nil)
-        if startishead then
-            head = lookaheaddisc
-        end
-
-        local replace = getfield(lookaheaddisc,"replace")
-        local pre     = getfield(lookaheaddisc,"pre")
-        local new     = copy_node_list(cf)
-        local cnew = new
-        for i=1,insertedmarks do
-            cnew = getnext(cnew)
-        end
-        local clast = cnew
-        for i=f,l do
-            clast = getnext(clast)
-        end
-        if not notmatchpre[lookaheaddisc] then
-            cf, start, ok = chainproc(cf,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence)
-        end
-        if not notmatchreplace[lookaheaddisc] then
-            new, cnew, ok = chainproc(new,cnew,clast,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence)
-        end
-        if pre then
-            setfield(cl,"next",pre)
-            setfield(pre,"prev",cl)
-        end
-        if replace then
-            local tail = find_node_tail(new)
-            setfield(tail,"next",replace)
-            setfield(replace,"prev",tail)
-        end
-        setfield(lookaheaddisc,"pre",cf)      -- also updates tail
-        setfield(lookaheaddisc,"replace",new) -- also updates tail
-
-        start          = getprev(lookaheaddisc)
-        sweephead[cf]  = getnext(clast)
-        sweephead[new] = getnext(last)
-
-    elseif backtrackdisc then
-
-        local cf            = getnext(backtrackdisc)
-        local cl            = start
-        local cnext         = getnext(start)
-        local insertedmarks = 0
-
-        while cnext and getid(cnext) == glyph_code and getfont(cnext) == currentfont and getsubtype(cnext) < 256 and marks[getchar(cnext)] do
-            insertedmarks = insertedmarks + 1
-            cl            = cnext
-            cnext         = getnext(cnext)
-        end
-        if cnext then
-            setfield(cnext,"prev",backtrackdisc)
-        end
-        setfield(backtrackdisc,"next",cnext)
-        setfield(cf,"prev",nil)
-        setfield(cl,"next",nil)
-        local replace = getfield(backtrackdisc,"replace")
-        local post    = getfield(backtrackdisc,"post")
-        local new     = copy_node_list(cf)
-        local cnew    = find_node_tail(new)
-        for i=1,insertedmarks do
-            cnew = getprev(cnew)
-        end
-        local clast = cnew
-        for i=f,l do
-            clast = getnext(clast)
-        end
-        if not notmatchpost[backtrackdisc] then
-            cf, start, ok = chainproc(cf,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence)
-        end
-        if not notmatchreplace[backtrackdisc] then
-            new, cnew, ok = chainproc(new,cnew,clast,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence)
-        end
-        if post then
-            local tail = find_node_tail(post)
-            setfield(tail,"next",cf)
-            setfield(cf,"prev",tail)
-        else
-            post = cf
-        end
-        if replace then
-            local tail = find_node_tail(replace)
-            setfield(tail,"next",new)
-            setfield(new,"prev",tail)
-        else
-            replace = new
-        end
-        setfield(backtrackdisc,"post",post)       -- also updates tail
-        setfield(backtrackdisc,"replace",replace) -- also updates tail
-        start              = getprev(backtrackdisc)
-        sweephead[post]    = getnext(clast)
-        sweephead[replace] = getnext(last)
-
-    else
-
-        head, start, ok = chainproc(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence)
-
-    end
-
-    return head, start, ok
-end
-
-local function normal_handle_contextchain(head,start,kind,chainname,contexts,sequence,lookuphash)
-    local sweepnode    = sweepnode
-    local sweeptype    = sweeptype
-    local diskseen     = false
-    local checkdisc    = getprev(head)
-    local flags        = sequence.flags
-    local done         = false
-    local skipmark     = flags[1]
-    local skipligature = flags[2]
-    local skipbase     = flags[3]
-    local markclass    = sequence.markclass
-    local skipped      = false
-
-    for k=1,#contexts do -- i've only seen ccmp having > 1 (e.g. dejavu)
-        local match   = true
-        local current = start
-        local last    = start
-        local ck      = contexts[k]
-        local seq     = ck[3]
-        local s       = #seq
-        -- f..l = mid string
-        if s == 1 then
-            -- never happens
-            match = getid(current) == glyph_code and getfont(current) == currentfont and getsubtype(current)<256 and seq[1][getchar(current)]
-        else
-            -- maybe we need a better space check (maybe check for glue or category or combination)
-            -- we cannot optimize for n=2 because there can be disc nodes
-            local f = ck[4]
-            local l = ck[5]
-            -- current match
-            if f == 1 and f == l then -- current only
-                -- already a hit
-             -- match = true
-            else -- before/current/after | before/current | current/after
-                -- no need to test first hit (to be optimized)
-                if f == l then -- new, else last out of sync (f is > 1)
-                 -- match = true
-                else
-                    local discfound = nil
-                    local n = f + 1
-                    last = getnext(last)
-                    while n <= l do
-                        if not last and (sweeptype == "post" or sweeptype == "replace") then
-                            last      = getnext(sweepnode)
-                            sweeptype = nil
-                        end
-                        if last then
-                            local id = getid(last)
-                            if id == glyph_code then
-                                if getfont(last) == currentfont and getsubtype(last)<256 then
-                                    local char = getchar(last)
-                                    local ccd = descriptions[char]
-                                    if ccd then
-                                        local class = ccd.class or "base"
-                                        if class == skipmark or class == skipligature or class == skipbase or (markclass and class == "mark" and not markclass[char]) then
-                                            skipped = true
-                                            if trace_skips then
-                                                show_skip(kind,chainname,char,ck,class)
-                                            end
-                                            last = getnext(last)
-                                        elseif seq[n][char] then
-                                            if n < l then
-                                                last = getnext(last)
-                                            end
-                                            n = n + 1
-                                        else
-                                            if discfound then
-                                                notmatchreplace[discfound] = true
-                                                match = not notmatchpre[discfound]
-                                            else
-                                                match = false
-                                            end
-                                            break
-                                        end
-                                    else
-                                        if discfound then
-                                            notmatchreplace[discfound] = true
-                                            match = not notmatchpre[discfound]
-                                        else
-                                            match = false
-                                        end
-                                        break
-                                    end
-                                else
-                                    if discfound then
-                                        notmatchreplace[discfound] = true
-                                        match = not notmatchpre[discfound]
-                                    else
-                                        match = false
-                                    end
-                                    break
-                                end
-                            elseif id == disc_code then
-                                diskseen              = true
-                                discfound             = last
-                                notmatchpre[last]     = nil
-                                notmatchpost[last]    = true
-                                notmatchreplace[last] = nil
-                                local pre     = getfield(last,"pre")
-                                local replace = getfield(last,"replace")
-                                if pre then
-                                    local n = n
-                                    while pre do
-                                        if seq[n][getchar(pre)] then
-                                            n = n + 1
-                                            pre = getnext(pre)
-                                            if n > l then
-                                                break
-                                            end
-                                        else
-                                            notmatchpre[last] = true
-                                            break
-                                        end
-                                    end
-                                    if n <= l then
-                                        notmatchpre[last] = true
-                                    end
-                                else
-                                    notmatchpre[last] = true
-                                end
-                                if replace then
-                                    -- so far we never entered this branch
-                                    while replace do
-                                        if seq[n][getchar(replace)] then
-                                            n = n + 1
-                                            replace = getnext(replace)
-                                            if n > l then
-                                                break
-                                            end
-                                        else
-                                            notmatchreplace[last] = true
-                                            match = not notmatchpre[last]
-                                            break
-                                        end
-                                    end
-                                    match = not notmatchpre[last]
-                                end
-                                last = getnext(last)
-                            else
-                                match = false
-                                break
-                            end
-                        else
-                            match = false
-                            break
-                        end
-                    end
-                end
-            end
-            -- before
-            if match and f > 1 then
-                local prev = getprev(start)
-                if prev then
-                    if prev == checkdisc and (sweeptype == "pre" or sweeptype == "replace") then
-                        prev      = getprev(sweepnode)
-                     -- sweeptype = nil
-                    end
-                    if prev then
-                        local discfound = nil
-                        local n = f - 1
-                        while n >= 1 do
-                            if prev then
-                                local id = getid(prev)
-                                if id == glyph_code then
-                                    if getfont(prev) == currentfont and getsubtype(prev)<256 then -- normal char
-                                        local char = getchar(prev)
-                                        local ccd = descriptions[char]
-                                        if ccd then
-                                            local class = ccd.class
-                                            if class == skipmark or class == skipligature or class == skipbase or (markclass and class == "mark" and not markclass[char]) then
-                                                skipped = true
-                                                if trace_skips then
-                                                    show_skip(kind,chainname,char,ck,class)
-                                                end
-                                            elseif seq[n][char] then
-                                                n = n -1
-                                            else
-                                                if discfound then
-                                                    notmatchreplace[discfound] = true
-                                                    match = not notmatchpost[discfound]
-                                                else
-                                                    match = false
-                                                end
-                                                break
-                                            end
-                                        else
-                                            if discfound then
-                                                notmatchreplace[discfound] = true
-                                                match = not notmatchpost[discfound]
-                                            else
-                                                match = false
-                                            end
-                                            break
-                                        end
-                                    else
-                                        if discfound then
-                                            notmatchreplace[discfound] = true
-                                            match = not notmatchpost[discfound]
-                                        else
-                                            match = false
-                                        end
-                                        break
-                                    end
-                                elseif id == disc_code then
-                                    -- the special case: f i where i becomes dottless i ..
-                                    diskseen              = true
-                                    discfound             = prev
-                                    notmatchpre[prev]     = true
-                                    notmatchpost[prev]    = nil
-                                    notmatchreplace[prev] = nil
-                                    local pre     = getfield(prev,"pre")
-                                    local post    = getfield(prev,"post")
-                                    local replace = getfield(prev,"replace")
-                                    if pre ~= start and post ~= start and replace ~= start then
-                                        if post then
-                                            local n = n
-                                            local posttail = find_node_tail(post)
-                                            while posttail do
-                                                if seq[n][getchar(posttail)] then
-                                                    n = n - 1
-                                                    if posttail == post then
-                                                        break
-                                                    else
-                                                        posttail = getprev(posttail)
-                                                        if n < 1 then
-                                                            break
-                                                        end
-                                                    end
-                                                else
-                                                    notmatchpost[prev] = true
-                                                    break
-                                                end
-                                            end
-                                            if n >= 1 then
-                                                notmatchpost[prev] = true
-                                            end
-                                        else
-                                            notmatchpost[prev] = true
-                                        end
-                                        if replace then
-                                            -- we seldom enter this branch (e.g. on brill efficient)
-                                            local replacetail = find_node_tail(replace)
-                                            while replacetail do
-                                                if seq[n][getchar(replacetail)] then
-                                                    n = n - 1
-                                                    if replacetail == replace then
-                                                        break
-                                                    else
-                                                        replacetail = getprev(replacetail)
-                                                        if n < 1 then
-                                                            break
-                                                        end
-                                                    end
-                                                else
-                                                    notmatchreplace[prev] = true
-                                                    match = not notmatchpost[prev]
-                                                    break
-                                                end
-                                            end
-                                            if not match then
-                                                break
-                                            end
-                                        else
-                                            -- skip 'm
-                                        end
-                                    else
-                                        -- skip 'm
-                                    end
-                                elseif seq[n][32] then
-                                    n = n -1
-                                else
-                                    match = false
-                                    break
-                                end
-                                prev = getprev(prev)
-                            elseif seq[n][32] then -- somewhat special, as zapfino can have many preceding spaces
-                                n = n - 1
-                            else
-                                match = false
-                                break
-                            end
-                        end
-                    else
-                        match = false
-                    end
-                else
-                    match = false
-                end
-            end
-            -- after
-            if match and s > l then
-                local current = last and getnext(last)
-                if not current then
-                    if sweeptype == "post" or sweeptype == "replace" then
-                        current   = getnext(sweepnode)
-                     -- sweeptype = nil
-                    end
-                end
-                if current then
-                    local discfound = nil
-                    -- removed optimization for s-l == 1, we have to deal with marks anyway
-                    local n = l + 1
-                    while n <= s do
-                        if current then
-                            local id = getid(current)
-                            if id == glyph_code then
-                                if getfont(current) == currentfont and getsubtype(current)<256 then -- normal char
-                                    local char = getchar(current)
-                                    local ccd = descriptions[char]
-                                    if ccd then
-                                        local class = ccd.class
-                                        if class == skipmark or class == skipligature or class == skipbase or (markclass and class == "mark" and not markclass[char]) then
-                                            skipped = true
-                                            if trace_skips then
-                                                show_skip(kind,chainname,char,ck,class)
-                                            end
-                                        elseif seq[n][char] then
-                                            n = n + 1
-                                        else
-                                            if discfound then
-                                                notmatchreplace[discfound] = true
-                                                match = not notmatchpre[discfound]
-                                            else
-                                                match = false
-                                            end
-                                            break
-                                        end
-                                    else
-                                        if discfound then
-                                            notmatchreplace[discfound] = true
-                                            match = not notmatchpre[discfound]
-                                        else
-                                            match = false
-                                        end
-                                        break
-                                    end
-                                else
-                                    if discfound then
-                                        notmatchreplace[discfound] = true
-                                        match = not notmatchpre[discfound]
-                                    else
-                                        match = false
-                                    end
-                                    break
-                                end
-                            elseif id == disc_code then
-                                diskseen                 = true
-                                discfound                = current
-                                notmatchpre[current]     = nil
-                                notmatchpost[current]    = true
-                                notmatchreplace[current] = nil
-                                local pre     = getfield(current,"pre")
-                                local replace = getfield(current,"replace")
-                                if pre then
-                                    local n = n
-                                    while pre do
-                                        if seq[n][getchar(pre)] then
-                                            n = n + 1
-                                            pre = getnext(pre)
-                                            if n > s then
-                                                break
-                                            end
-                                        else
-                                            notmatchpre[current] = true
-                                            break
-                                        end
-                                    end
-                                    if n <= s then
-                                        notmatchpre[current] = true
-                                    end
-                                else
-                                    notmatchpre[current] = true
-                                end
-                                if replace then
-                                    -- so far we never entered this branch
-                                    while replace do
-                                        if seq[n][getchar(replace)] then
-                                            n = n + 1
-                                            replace = getnext(replace)
-                                            if n > s then
-                                                break
-                                            end
-                                        else
-                                            notmatchreplace[current] = true
-                                            match = notmatchpre[current]
-                                            break
-                                        end
-                                    end
-                                    if not match then
-                                        break
-                                    end
-                                else
-                                    -- skip 'm
-                                end
-                            elseif seq[n][32] then -- brrr
-                                n = n + 1
-                            else
-                                match = false
-                                break
-                            end
-                            current = getnext(current)
-                        elseif seq[n][32] then
-                            n = n + 1
-                        else
-                            match = false
-                            break
-                        end
-                    end
-                else
-                    match = false
-                end
-            end
-        end
-        if match then
-            -- can lookups be of a different type ?
-            local diskchain = diskseen or sweepnode
-            if trace_contexts then
-                local rule, lookuptype, f, l = ck[1], ck[2], ck[4], ck[5]
-                local char = getchar(start)
-                if ck[9] then
-                    logwarning("%s: rule %s matches at char %s for (%s,%s,%s) chars, lookuptype %a, %a => %a",
-                        cref(kind,chainname),rule,gref(char),f-1,l-f+1,s-l,lookuptype,ck[9],ck[10])
-                else
-                    logwarning("%s: rule %s matches at char %s for (%s,%s,%s) chars, lookuptype %a",
-                        cref(kind,chainname),rule,gref(char),f-1,l-f+1,s-l,lookuptype)
-                end
-            end
-            local chainlookups = ck[6]
-            if chainlookups then
-                local nofchainlookups = #chainlookups
-                -- we can speed this up if needed
-                if nofchainlookups == 1 then
-                    local chainlookupname = chainlookups[1]
-                    local chainlookup = lookuptable[chainlookupname]
-                    if chainlookup then
-                        local chainproc = chainprocs[chainlookup.type]
-                        if chainproc then
-                            local ok
-                            if diskchain then
-                                head, start, ok = chaindisk(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence,chainproc)
-                            else
-                                head, start, ok = chainproc(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence)
-                            end
-                            if ok then
-                                done = true
-                            end
-                        else
-                            logprocess("%s: %s is not yet supported",cref(kind,chainname,chainlookupname),chainlookup.type)
-                        end
-                    else -- shouldn't happen
-                        logprocess("%s is not yet supported",cref(kind,chainname,chainlookupname))
-                    end
-                 else
-                    local i = 1
-                    while start and true do
-                        if skipped then
-                            while true do -- todo: use properties
-                                local char = getchar(start)
-                                local ccd = descriptions[char]
-                                if ccd then
-                                    local class = ccd.class or "base"
-                                    if class == skipmark or class == skipligature or class == skipbase or (markclass and class == "mark" and not markclass[char]) then
-                                        start = getnext(start)
-                                    else
-                                        break
-                                    end
-                                else
-                                    break
-                                end
-                            end
-                        end
-                        -- see remark in ms standard under : LookupType 5: Contextual Substitution Subtable
-                        local chainlookupname = chainlookups[i]
-                        local chainlookup = lookuptable[chainlookupname]
-                        if not chainlookup then
-                            -- we just advance
-                            i = i + 1
-                        else
-                            local chainproc = chainprocs[chainlookup.type]
-                            if not chainproc then
-                                -- actually an error
-                                logprocess("%s: %s is not yet supported",cref(kind,chainname,chainlookupname),chainlookup.type)
-                                i = i + 1
-                            else
-                                local ok, n
-                                if diskchain then
-                                    head, start, ok    = chaindisk(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence,chainproc)
-                                else
-                                    head, start, ok, n = chainproc(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,i,sequence)
-                                end
-                                -- messy since last can be changed !
-                                if ok then
-                                    done = true
-                                    if n and n > 1 then
-                                        -- we have a ligature (cf the spec we advance one but we really need to test it
-                                        -- as there are fonts out there that are fuzzy and have too many lookups:
-                                        --
-                                        -- U+1105 U+119E U+1105 U+119E : sourcehansansklight: script=hang ccmp=yes
-                                        --
-                                        if i + n > nofchainlookups then
-                                         -- if trace_contexts then
-                                         --     logprocess("%s: quitting lookups",cref(kind,chainname))
-                                         -- end
-                                            break
-                                        else
-                                            -- we need to carry one
-                                        end
-                                    end
-                                end
-                                i = i + 1
-                            end
-                        end
-                        if i > nofchainlookups or not start then
-                            break
-                        elseif start then
-                            start = getnext(start)
-                        end
-                    end
-                end
-            else
-                local replacements = ck[7]
-                if replacements then
-                    head, start, done = chainprocs.reversesub(head,start,last,kind,chainname,ck,lookuphash,replacements) -- sequence
-                else
-                    done = quit_on_no_replacement -- can be meant to be skipped / quite inconsistent in fonts
-                    if trace_contexts then
-                        logprocess("%s: skipping match",cref(kind,chainname))
-                    end
-                end
-            end
-            if done then
-                break -- out of contexts (new, needs checking)
-            end
-        end
-    end
-    if diskseen then -- maybe move up so that we can turn checking on/off
-        notmatchpre     = { }
-        notmatchpost    = { }
-        notmatchreplace = { }
-    end
-    return head, start, done
-end
-
--- Because we want to keep this elsewhere (an because speed is less an issue) we
--- pass the font id so that the verbose variant can access the relevant helper tables.
-
-local verbose_handle_contextchain = function(font,...)
-    logwarning("no verbose handler installed, reverting to 'normal'")
-    otf.setcontextchain()
-    return normal_handle_contextchain(...)
-end
-
-otf.chainhandlers = {
-    normal  = normal_handle_contextchain,
-    verbose = verbose_handle_contextchain,
-}
-
-function otf.setcontextchain(method)
-    if not method or method == "normal" or not otf.chainhandlers[method] then
-        if handlers.contextchain then -- no need for a message while making the format
-            logwarning("installing normal contextchain handler")
-        end
-        handlers.contextchain = normal_handle_contextchain
-    else
-        logwarning("installing contextchain handler %a",method)
-        local handler = otf.chainhandlers[method]
-        handlers.contextchain = function(...)
-            return handler(currentfont,...) -- hm, get rid of ...
-        end
-    end
-    handlers.gsub_context             = handlers.contextchain
-    handlers.gsub_contextchain        = handlers.contextchain
-    handlers.gsub_reversecontextchain = handlers.contextchain
-    handlers.gpos_contextchain        = handlers.contextchain
-    handlers.gpos_context             = handlers.contextchain
-end
-
-otf.setcontextchain()
-
-local missing = { } -- we only report once
-
-local function logprocess(...)
-    if trace_steps then
-        registermessage(...)
-    end
-    report_process(...)
-end
-
-local logwarning = report_process
-
-local function report_missing_cache(typ,lookup)
-    local f = missing[currentfont] if not f then f = { } missing[currentfont] = f end
-    local t = f[typ]               if not t then t = { } f[typ]               = t end
-    if not t[lookup] then
-        t[lookup] = true
-        logwarning("missing cache for lookup %a, type %a, font %a, name %a",lookup,typ,currentfont,tfmdata.properties.fullname)
-    end
-end
-
-local resolved = { } -- we only resolve a font,script,language pair once
-
--- todo: pass all these 'locals' in a table
-
-local lookuphashes = { }
-
-setmetatableindex(lookuphashes, function(t,font)
-    local lookuphash = fontdata[font].resources.lookuphash
-    if not lookuphash or not next(lookuphash) then
-        lookuphash = false
-    end
-    t[font] = lookuphash
-    return lookuphash
-end)
-
--- fonts.hashes.lookups = lookuphashes
-
-local autofeatures = fonts.analyzers.features -- was: constants
-
-local function initialize(sequence,script,language,enabled)
-    local features = sequence.features
-    if features then
-        local order = sequence.order
-        if order then
-            for i=1,#order do --
-                local kind  = order[i] --
-                local valid = enabled[kind]
-                if valid then
-                    local scripts = features[kind] --
-                    local languages = scripts[script] or scripts[wildcard]
-                    if languages and (languages[language] or languages[wildcard]) then
-                        return { valid, autofeatures[kind] or false, sequence, kind }
-                    end
-                end
-            end
-        else
-            -- can't happen
-        end
-    end
-    return false
-end
-
-function otf.dataset(tfmdata,font) -- generic variant, overloaded in context
-    local shared     = tfmdata.shared
-    local properties = tfmdata.properties
-    local language   = properties.language or "dflt"
-    local script     = properties.script   or "dflt"
-    local enabled    = shared.features
-    local res = resolved[font]
-    if not res then
-        res = { }
-        resolved[font] = res
-    end
-    local rs = res[script]
-    if not rs then
-        rs = { }
-        res[script] = rs
-    end
-    local rl = rs[language]
-    if not rl then
-        rl = {
-            -- indexed but we can also add specific data by key
-        }
-        rs[language] = rl
-        local sequences = tfmdata.resources.sequences
-        for s=1,#sequences do
-            local v = enabled and initialize(sequences[s],script,language,enabled)
-            if v then
-                rl[#rl+1] = v
-            end
-        end
-    end
-    return rl
-end
-
--- assumptions:
---
--- * languages that use complex disc nodes
-
-local function kernrun(disc,run)
-    --
-    -- we catch <font 1><disc font 2>
-    --
-    if trace_kernruns then
-        report_run("kern") -- will be more detailed
-    end
-    --
-    local prev      = getprev(disc) -- todo, keep these in the main loop
-    local next      = getnext(disc) -- todo, keep these in the main loop
-    --
-    local pre       = getfield(disc,"pre")
-    local post      = getfield(disc,"post")
-    local replace   = getfield(disc,"replace")
-    --
-    local prevmarks = prev
-    --
-    -- can be optional, because why on earth do we get a disc after a mark (okay, maybe when a ccmp
-    -- has happened but then it should be in the disc so basically this test indicates an error)
-    --
-    while prevmarks and getid(prevmarks) == glyph_code and marks[getchar(prevmarks)] and getfont(prevmarks) == currentfont and getsubtype(prevmarks) < 256 do
-        prevmarks = getprev(prevmarks)
-    end
-    --
-    if prev and (pre or replace) and not (getid(prev) == glyph_code and getfont(prev) == currentfont and getsubtype(prev)<256) then
-        prev = false
-    end
-    if next and (post or replace) and not (getid(next) == glyph_code and getfont(next) == currentfont and getsubtype(next)<256) then
-        next = false
-    end
-    --
-    if not pre then
-        -- go on
-    elseif prev then
-        local nest = getprev(pre)
-        setfield(pre,"prev",prev)
-        setfield(prev,"next",pre)
-        run(prevmarks,"preinjections")
-        setfield(pre,"prev",nest)
-        setfield(prev,"next",disc)
-    else
-        run(pre,"preinjections")
-    end
-    --
-    if not post then
-        -- go on
-    elseif next then
-        local tail = find_node_tail(post)
-        setfield(tail,"next",next)
-        setfield(next,"prev",tail)
-        run(post,"postinjections",next)
-        setfield(tail,"next",nil)
-        setfield(next,"prev",disc)
-    else
-        run(post,"postinjections")
-    end
-    --
-    if not replace and prev and next then
-        -- this should be already done by discfound
-        setfield(prev,"next",next)
-        setfield(next,"prev",prev)
-        run(prevmarks,"injections",next)
-        setfield(prev,"next",disc)
-        setfield(next,"prev",disc)
-    elseif prev and next then
-        local tail = find_node_tail(replace)
-        local nest = getprev(replace)
-        setfield(replace,"prev",prev)
-        setfield(prev,"next",replace)
-        setfield(tail,"next",next)
-        setfield(next,"prev",tail)
-        run(prevmarks,"replaceinjections",next)
-        setfield(replace,"prev",nest)
-        setfield(prev,"next",disc)
-        setfield(tail,"next",nil)
-        setfield(next,"prev",disc)
-    elseif prev then
-        local nest = getprev(replace)
-        setfield(replace,"prev",prev)
-        setfield(prev,"next",replace)
-        run(prevmarks,"replaceinjections")
-        setfield(replace,"prev",nest)
-        setfield(prev,"next",disc)
-    elseif next then
-        local tail = find_node_tail(replace)
-        setfield(tail,"next",next)
-        setfield(next,"prev",tail)
-        run(replace,"replaceinjections",next)
-        setfield(tail,"next",nil)
-        setfield(next,"prev",disc)
-    else
-        run(replace,"replaceinjections")
-    end
-end
-
--- the if new test might be dangerous as luatex will check / set some tail stuff
--- in a temp node
-
-local function comprun(disc,run)
-    if trace_compruns then
-        report_run("comp: %s",languages.serializediscretionary(disc))
-    end
-    --
-    local pre = getfield(disc,"pre")
-    if pre then
-        sweepnode = disc
-        sweeptype = "pre" -- in alternative code preinjections is used (also used then for proeprties, saves a variable)
-        local new, done = run(pre)
-        if done then
-            setfield(disc,"pre",new)
-        end
-    end
-    --
-    local post = getfield(disc,"post")
-    if post then
-        sweepnode = disc
-        sweeptype = "post"
-        local new, done = run(post)
-        if done then
-            setfield(disc,"post",new)
-        end
-    end
-    --
-    local replace = getfield(disc,"replace")
-    if replace then
-        sweepnode = disc
-        sweeptype = "replace"
-        local new, done = run(replace)
-        if done then
-            setfield(disc,"replace",new)
-        end
-    end
-    sweepnode = nil
-    sweeptype = nil
-end
-
-local function testrun(disc,trun,crun) -- use helper
-    local next = getnext(disc)
-    if next then
-        local replace = getfield(disc,"replace")
-        if replace then
-            local prev = getprev(disc)
-            if prev then
-                -- only look ahead
-                local tail = find_node_tail(replace)
-             -- local nest = getprev(replace)
-                setfield(tail,"next",next)
-                setfield(next,"prev",tail)
-                if trun(replace,next) then
-                    setfield(disc,"replace",nil) -- beware, side effects of nest so first
-                    setfield(prev,"next",replace)
-                    setfield(replace,"prev",prev)
-                    setfield(next,"prev",tail)
-                    setfield(tail,"next",next)
-                    setfield(disc,"prev",nil)
-                    setfield(disc,"next",nil)
-                    flush_node_list(disc)
-                    return replace -- restart
-                else
-                    setfield(tail,"next",nil)
-                    setfield(next,"prev",disc)
-                end
-            else
-                -- weird case
-            end
-        else
-            -- no need
-        end
-    else
-        -- weird case
-    end
-    comprun(disc,crun)
-    return next
-end
-
-local function discrun(disc,drun,krun)
-    local next = getnext(disc)
-    local prev = getprev(disc)
-    if trace_discruns then
-        report_run("disc") -- will be more detailed
-    end
-    if next and prev then
-        setfield(prev,"next",next)
-     -- setfield(next,"prev",prev)
-        drun(prev)
-        setfield(prev,"next",disc)
-     -- setfield(next,"prev",disc)
-    end
-    --
-    local pre = getfield(disc,"pre")
-    if not pre then
-        -- go on
-    elseif prev then
-        local nest = getprev(pre)
-        setfield(pre,"prev",prev)
-        setfield(prev,"next",pre)
-        krun(prev,"preinjections")
-        setfield(pre,"prev",nest)
-        setfield(prev,"next",disc)
-    else
-        krun(pre,"preinjections")
-    end
-    return next
-end
-
--- todo: maybe run lr and rl stretches
-
-local function featuresprocessor(head,font,attr)
-
-    local lookuphash = lookuphashes[font] -- we can also check sequences here
-
-    if not lookuphash then
-        return head, false
-    end
-
-    head = tonut(head)
-
-    if trace_steps then
-        checkstep(head)
-    end
-
-    tfmdata         = fontdata[font]
-    descriptions    = tfmdata.descriptions
-    characters      = tfmdata.characters
-    resources       = tfmdata.resources
-
-    marks           = resources.marks
-    anchorlookups   = resources.lookup_to_anchor
-    lookuptable     = resources.lookups
-    lookuptypes     = resources.lookuptypes
-    lookuptags      = resources.lookuptags
-
-    currentfont     = font
-    rlmode          = 0
-    sweephead       = { }
-
-    local sequences = resources.sequences
-    local done      = false
-    local datasets  = otf.dataset(tfmdata,font,attr)
-
-    local dirstack  = { } -- could move outside function
-
-    -- We could work on sub start-stop ranges instead but I wonder if there is that
-    -- much speed gain (experiments showed that it made not much sense) and we need
-    -- to keep track of directions anyway. Also at some point I want to play with
-    -- font interactions and then we do need the full sweeps.
-
-    -- Keeping track of the headnode is needed for devanagari (I generalized it a bit
-    -- so that multiple cases are also covered.)
-
-    -- We don't goto the next node of a disc node is created so that we can then treat
-    -- the pre, post and replace. It's abit of a hack but works out ok for most cases.
-
-    -- there can be less subtype and attr checking in the comprun etc helpers
-
-    for s=1,#datasets do
-        local dataset      = datasets[s]
-              featurevalue = dataset[1] -- todo: pass to function instead of using a global
-        local attribute    = dataset[2]
-        local sequence     = dataset[3] -- sequences[s] -- also dataset[5]
-        local kind         = dataset[4]
-        ----- chain        = dataset[5] -- sequence.chain or 0
-        local rlparmode    = 0
-        local topstack     = 0
-        local success      = false
-        local typ          = sequence.type
-        local gpossing     = typ == "gpos_single" or typ == "gpos_pair" -- maybe all of them
-        local subtables    = sequence.subtables
-        local handler      = handlers[typ]
-        if typ == "gsub_reversecontextchain" then -- chain < 0
-            -- this is a limited case, no special treatments like 'init' etc
-            -- we need to get rid of this slide! probably no longer needed in latest luatex
-            local start = find_node_tail(head) -- slow (we can store tail because there's always a skip at the end): todo
-            while start do
-                local id = getid(start)
-                if id == glyph_code then
-                    if getfont(start) == font and getsubtype(start) < 256 then
-                        local a = getattr(start,0)
-                        if a then
-                            a = a == attr
-                        else
-                            a = true
-                        end
-                        if a then
-                            local char = getchar(start)
-                            for i=1,#subtables do
-                                local lookupname = subtables[i]
-                                local lookupcache = lookuphash[lookupname]
-                                if lookupcache then
-                                    local lookupmatch = lookupcache[char]
-                                    if lookupmatch then
-                                        -- todo: disc?
-                                        head, start, success = handler(head,start,kind,lookupname,lookupmatch,sequence,lookuphash,i)
-                                        if success then
-                                            break
-                                        end
-                                    end
-                                else
-                                    report_missing_cache(typ,lookupname)
-                                end
-                            end
-                            if start then start = getprev(start) end
-                        else
-                            start = getprev(start)
-                        end
-                    else
-                        start = getprev(start)
-                    end
-                else
-                    start = getprev(start)
-                end
-            end
-        else
-            local ns = #subtables
-            local start = head -- local ?
-            rlmode = 0 -- to be checked ?
-            if ns == 1 then -- happens often
-                local lookupname  = subtables[1]
-                local lookupcache = lookuphash[lookupname]
-                if not lookupcache then -- also check for empty cache
-                    report_missing_cache(typ,lookupname)
-                else
-
-                    local function c_run(head) -- no need to check for 256 and attr probably also the same
-                        local done  = false
-                        local start = sweephead[head]
-                        if start then
-                            sweephead[head] = nil
-                        else
-                            start = head
-                        end
-                        while start do
-                            local id = getid(start)
-                            if id ~= glyph_code then
-                                -- very unlikely
-                                start = getnext(start)
-                            elseif getfont(start) == font and getsubtype(start) < 256 then
-                                local a = getattr(start,0)
-                                if a then
-                                    a = (a == attr) and (not attribute or getprop(start,a_state) == attribute)
-                                else
-                                    a = not attribute or getprop(start,a_state) == attribute
-                                end
-                                if a then
-                                    local lookupmatch = lookupcache[getchar(start)]
-                                    if lookupmatch then
-                                        -- sequence kan weg
-                                        local ok
-                                        head, start, ok = handler(head,start,kind,lookupname,lookupmatch,sequence,lookuphash,1)
-                                        if ok then
-                                            done = true
-                                        end
-                                    end
-                                    if start then start = getnext(start) end
-                                else
-                                    start = getnext(start)
-                                end
-                            else
-                                return head, false
-                            end
-                        end
-                        if done then
-                            success = true -- needed in this subrun?
-                        end
-                        return head, done
-                    end
-
-                    local function t_run(start,stop)
-                        while start ~= stop do
-                            local id = getid(start)
-                            if id == glyph_code and getfont(start) == font and getsubtype(start) < 256 then
-                                local a = getattr(start,0)
-                                if a then
-                                    a = (a == attr) and (not attribute or getprop(start,a_state) == attribute)
-                                else
-                                    a = not attribute or getprop(start,a_state) == attribute
-                                end
-                                if a then
-                                    local lookupmatch = lookupcache[getchar(start)]
-                                    if lookupmatch then -- hm, hyphens can match (tlig) so we need to really check
-                                        -- if we need more than ligatures we can outline the code and use functions
-                                        local s = getnext(start)
-                                        local l = nil
-                                        while s do
-                                            local lg = lookupmatch[getchar(s)]
-                                            if lg then
-                                                l = lg
-                                                s = getnext(s)
-                                            else
-                                                break
-                                            end
-                                        end
-                                        if l and l.ligature then
-                                            return true
-                                        end
-                                    end
-                                end
-                                start = getnext(start)
-                            else
-                                break
-                            end
-                        end
-                    end
-
-                    local function d_run(prev) -- we can assume that prev and next are glyphs
-                        local a = getattr(prev,0)
-                        if a then
-                            a = (a == attr) and (not attribute or getprop(prev,a_state) == attribute)
-                        else
-                            a = not attribute or getprop(prev,a_state) == attribute
-                        end
-                        if a then
-                            local lookupmatch = lookupcache[getchar(prev)]
-                            if lookupmatch then
-                                -- sequence kan weg
-                                local h, d, ok = handler(head,prev,kind,lookupname,lookupmatch,sequence,lookuphash,1)
-                                if ok then
-                                    done    = true
-                                    success = true
-                                end
-                            end
-                        end
-                    end
-
-                    local function k_run(sub,injection,last)
-                        local a = getattr(sub,0)
-                        if a then
-                            a = (a == attr) and (not attribute or getprop(sub,a_state) == attribute)
-                        else
-                            a = not attribute or getprop(sub,a_state) == attribute
-                        end
-                        if a then
-                            -- sequence kan weg
-                            for n in traverse_nodes(sub) do -- only gpos
-                                if n == last then
-                                    break
-                                end
-                                local id = getid(n)
-                                if id == glyph_code then
-                                    local lookupmatch = lookupcache[getchar(n)]
-                                    if lookupmatch then
-                                        local h, d, ok = handler(sub,n,kind,lookupname,lookupmatch,sequence,lookuphash,1,injection)
-                                        if ok then
-                                            done    = true
-                                            success = true
-                                        end
-                                    end
-                                else
-                                    -- message
-                                end
-                            end
-                        end
-                    end
-
-                    while start do
-                        local id = getid(start)
-                        if id == glyph_code then
-                            if getfont(start) == font and getsubtype(start) < 256 then -- why a 256 test ...
-                                local a = getattr(start,0)
-                                if a then
-                                    a = (a == attr) and (not attribute or getprop(start,a_state) == attribute)
-                                else
-                                    a = not attribute or getprop(start,a_state) == attribute
-                                end
-                                if a then
-                                    local char        = getchar(start)
-                                    local lookupmatch = lookupcache[char]
-                                    if lookupmatch then
-                                        -- sequence kan weg
-                                        local ok
-                                        head, start, ok = handler(head,start,kind,lookupname,lookupmatch,sequence,lookuphash,1)
-                                        if ok then
-                                            success = true
-                                        elseif gpossing and zwnjruns and char == zwnj then
-                                            discrun(start,d_run)
-                                        end
-                                    elseif gpossing and zwnjruns and char == zwnj then
-                                        discrun(start,d_run)
-                                    end
-                                    if start then start = getnext(start) end
-                                else
-                                   start = getnext(start)
-                                end
-                            else
-                                start = getnext(start)
-                            end
-                        elseif id == disc_code then
-                            if gpossing then
-                                kernrun(start,k_run)
-                                start = getnext(start)
-                            elseif typ == "gsub_ligature" then
-                                start = testrun(start,t_run,c_run)
-                            else
-                                comprun(start,c_run)
-                                start = getnext(start)
-                            end
-                        elseif id == math_code then
-                            start = getnext(end_of_math(start))
-                        else
-                            start = getnext(start)
-                        end
-                    end
-                end
-
-            else
-
-                local function c_run(head)
-                    local done  = false
-                    local start = sweephead[head]
-                    if start then
-                        sweephead[head] = nil
-                    else
-                        start = head
-                    end
-                    while start do
-                        local id = getid(start)
-                        if id ~= glyph_code then
-                            -- very unlikely
-                            start = getnext(start)
-                        elseif getfont(start) == font and getsubtype(start) < 256 then
-                            local a = getattr(start,0)
-                            if a then
-                                a = (a == attr) and (not attribute or getprop(start,a_state) == attribute)
-                            else
-                                a = not attribute or getprop(start,a_state) == attribute
-                            end
-                            if a then
-                                local char = getchar(start)
-                                for i=1,ns do
-                                    local lookupname = subtables[i]
-                                    local lookupcache = lookuphash[lookupname]
-                                    if lookupcache then
-                                        local lookupmatch = lookupcache[char]
-                                        if lookupmatch then
-                                            -- we could move all code inline but that makes things even more unreadable
-                                            local ok
-                                            head, start, ok = handler(head,start,kind,lookupname,lookupmatch,sequence,lookuphash,i)
-                                            if ok then
-                                                done = true
-                                                break
-                                            elseif not start then
-                                                -- don't ask why ... shouldn't happen
-                                                break
-                                            end
-                                        end
-                                    else
-                                        report_missing_cache(typ,lookupname)
-                                    end
-                                end
-                                if start then start = getnext(start) end
-                            else
-                                start = getnext(start)
-                            end
-                        else
-                            return head, false
-                        end
-                    end
-                    if done then
-                        success = true
-                    end
-                    return head, done
-                end
-
-                local function d_run(prev)
-                    local a = getattr(prev,0)
-                    if a then
-                        a = (a == attr) and (not attribute or getprop(prev,a_state) == attribute)
-                    else
-                        a = not attribute or getprop(prev,a_state) == attribute
-                    end
-                    if a then
-                        -- brr prev can be disc
-                        local char = getchar(prev)
-                        for i=1,ns do
-                            local lookupname  = subtables[i]
-                            local lookupcache = lookuphash[lookupname]
-                            if lookupcache then
-                                local lookupmatch = lookupcache[char]
-                                if lookupmatch then
-                                    -- we could move all code inline but that makes things even more unreadable
-                                    local h, d, ok = handler(head,prev,kind,lookupname,lookupmatch,sequence,lookuphash,i)
-                                    if ok then
-                                        done = true
-                                        break
-                                    end
-                                end
-                            else
-                                report_missing_cache(typ,lookupname)
-                            end
-                        end
-                    end
-                end
-
-               local function k_run(sub,injection,last)
-                    local a = getattr(sub,0)
-                    if a then
-                        a = (a == attr) and (not attribute or getprop(sub,a_state) == attribute)
-                    else
-                        a = not attribute or getprop(sub,a_state) == attribute
-                    end
-                    if a then
-                        for n in traverse_nodes(sub) do -- only gpos
-                            if n == last then
-                                break
-                            end
-                            local id = getid(n)
-                            if id == glyph_code then
-                                local char = getchar(n)
-                                for i=1,ns do
-                                    local lookupname  = subtables[i]
-                                    local lookupcache = lookuphash[lookupname]
-                                    if lookupcache then
-                                        local lookupmatch = lookupcache[char]
-                                        if lookupmatch then
-                                            local h, d, ok = handler(head,n,kind,lookupname,lookupmatch,sequence,lookuphash,i,injection)
-                                            if ok then
-                                                done = true
-                                                break
-                                            end
-                                        end
-                                    else
-                                        report_missing_cache(typ,lookupname)
-                                    end
-                                end
-                            else
-                                -- message
-                            end
-                        end
-                    end
-                end
-
-                local function t_run(start,stop)
-                    while start ~= stop do
-                        local id = getid(start)
-                        if id == glyph_code and getfont(start) == font and getsubtype(start) < 256 then
-                            local a = getattr(start,0)
-                            if a then
-                                a = (a == attr) and (not attribute or getprop(start,a_state) == attribute)
-                            else
-                                a = not attribute or getprop(start,a_state) == attribute
-                            end
-                            if a then
-                                local char = getchar(start)
-                                for i=1,ns do
-                                    local lookupname  = subtables[i]
-                                    local lookupcache = lookuphash[lookupname]
-                                    if lookupcache then
-                                        local lookupmatch = lookupcache[char]
-                                        if lookupmatch then
-                                            -- if we need more than ligatures we can outline the code and use functions
-                                            local s = getnext(start)
-                                            local l = nil
-                                            while s do
-                                                local lg = lookupmatch[getchar(s)]
-                                                if lg then
-                                                    l = lg
-                                                    s = getnext(s)
-                                                else
-                                                    break
-                                                end
-                                            end
-                                            if l and l.ligature then
-                                                return true
-                                            end
-                                        end
-                                    else
-                                        report_missing_cache(typ,lookupname)
-                                    end
-                                end
-                            end
-                            start = getnext(start)
-                        else
-                            break
-                        end
-                    end
-                end
-
-                while start do
-                    local id = getid(start)
-                    if id == glyph_code then
-                        if getfont(start) == font and getsubtype(start) < 256 then
-                            local a = getattr(start,0)
-                            if a then
-                                a = (a == attr) and (not attribute or getprop(start,a_state) == attribute)
-                            else
-                                a = not attribute or getprop(start,a_state) == attribute
-                            end
-                            if a then
-                                for i=1,ns do
-                                    local lookupname  = subtables[i]
-                                    local lookupcache = lookuphash[lookupname]
-                                    if lookupcache then
-                                        local char = getchar(start)
-                                        local lookupmatch = lookupcache[char]
-                                        if lookupmatch then
-                                            -- we could move all code inline but that makes things even more unreadable
-                                            local ok
-                                            head, start, ok = handler(head,start,kind,lookupname,lookupmatch,sequence,lookuphash,i)
-                                            if ok then
-                                                success = true
-                                                break
-                                            elseif not start then
-                                                -- don't ask why ... shouldn't happen
-                                                break
-                                            elseif gpossing and zwnjruns and char == zwnj then
-                                                discrun(start,d_run)
-                                            end
-                                        elseif gpossing and zwnjruns and char == zwnj then
-                                            discrun(start,d_run)
-                                        end
-                                    else
-                                        report_missing_cache(typ,lookupname)
-                                    end
-                                end
-                                if start then start = getnext(start) end
-                            else
-                                start = getnext(start)
-                            end
-                        else
-                            start = getnext(start)
-                        end
-                    elseif id == disc_code then
-                        if gpossing then
-                            kernrun(start,k_run)
-                            start = getnext(start)
-                        elseif typ == "gsub_ligature" then
-                            start = testrun(start,t_run,c_run)
-                        else
-                            comprun(start,c_run)
-                            start = getnext(start)
-                        end
-                    elseif id == math_code then
-                        start = getnext(end_of_math(start))
-                    else
-                        start = getnext(start)
-                    end
-                end
-            end
-        end
-        if success then
-            done = true
-        end
-        if trace_steps then -- ?
-            registerstep(head)
-        end
-
-    end
-
-    head = tonode(head)
-
-    return head, done
-end
-
--- this might move to the loader
-
-local function generic(lookupdata,lookupname,unicode,lookuphash)
-    local target = lookuphash[lookupname]
-    if target then
-        target[unicode] = lookupdata
-    else
-        lookuphash[lookupname] = { [unicode] = lookupdata }
-    end
-end
-
-local function ligature(lookupdata,lookupname,unicode,lookuphash)
-    local target = lookuphash[lookupname]
-    if not target then
-        target = { }
-        lookuphash[lookupname] = target
-    end
-    for i=1,#lookupdata do
-        local li = lookupdata[i]
-        local tu = target[li]
-        if not tu then
-            tu = { }
-            target[li] = tu
-        end
-        target = tu
-    end
-    target.ligature = unicode
-end
-
-local function pair(lookupdata,lookupname,unicode,lookuphash)
-    local target = lookuphash[lookupname]
-    if not target then
-        target = { }
-        lookuphash[lookupname] = target
-    end
-    local others = target[unicode]
-    local paired = lookupdata[1]
-    if others then
-        others[paired] = lookupdata
-    else
-        others = { [paired] = lookupdata }
-        target[unicode] = others
-    end
-end
-
-local action = {
-    substitution = generic,
-    multiple     = generic,
-    alternate    = generic,
-    position     = generic,
-    ligature     = ligature,
-    pair         = pair,
-    kern         = pair,
-}
-
-local function prepare_lookups(tfmdata)
-
-    local rawdata          = tfmdata.shared.rawdata
-    local resources        = rawdata.resources
-    local lookuphash       = resources.lookuphash
-    local anchor_to_lookup = resources.anchor_to_lookup
-    local lookup_to_anchor = resources.lookup_to_anchor
-    local lookuptypes      = resources.lookuptypes
-    local characters       = tfmdata.characters
-    local descriptions     = tfmdata.descriptions
-    local duplicates       = resources.duplicates
-
-    -- we cannot free the entries in the descriptions as sometimes we access
-    -- then directly (for instance anchors) ... selectively freeing does save
-    -- much memory as it's only a reference to a table and the slot in the
-    -- description hash is not freed anyway
-
-    -- we can delay this using metatables so that we don't make the hashes for
-    -- features we don't use but then we need to loop over the characters
-    -- many times so we gain nothing
-
-    for unicode, character in next, characters do -- we cannot loop over descriptions !
-
-        local description = descriptions[unicode]
-
-        if description then
-
-            local lookups = description.slookups
-            if lookups then
-                for lookupname, lookupdata in next, lookups do
-                    action[lookuptypes[lookupname]](lookupdata,lookupname,unicode,lookuphash,duplicates)
-                end
-            end
-
-            local lookups = description.mlookups
-            if lookups then
-                for lookupname, lookuplist in next, lookups do
-                    local lookuptype = lookuptypes[lookupname]
-                    for l=1,#lookuplist do
-                        local lookupdata = lookuplist[l]
-                        action[lookuptype](lookupdata,lookupname,unicode,lookuphash,duplicates)
-                    end
-                end
-            end
-
-            local list = description.kerns
-            if list then
-                for lookup, krn in next, list do  -- ref to glyph, saves lookup
-                    local target = lookuphash[lookup]
-                    if target then
-                        target[unicode] = krn
-                    else
-                        lookuphash[lookup] = { [unicode] = krn }
-                    end
-                end
-            end
-
-            local list = description.anchors
-            if list then
-                for typ, anchors in next, list do -- types
-                    if typ == "mark" or typ == "cexit" then -- or entry?
-                        for name, anchor in next, anchors do
-                            local lookups = anchor_to_lookup[name]
-                            if lookups then
-                                for lookup in next, lookups do
-                                    local target = lookuphash[lookup]
-                                    if target then
-                                        target[unicode] = anchors
-                                    else
-                                        lookuphash[lookup] = { [unicode] = anchors }
-                                    end
-                                end
-                            end
-                        end
-                    end
-                end
-            end
-
-        end
-
-    end
-
-end
-
--- so far
-
-local function split(replacement,original)
-    local result = { }
-    for i=1,#replacement do
-        result[original[i]] = replacement[i]
-    end
-    return result
-end
-
-local valid = {
-    coverage        = { chainsub = true, chainpos = true, contextsub = true },
-    reversecoverage = { reversesub = true },
-    glyphs          = { chainsub = true, chainpos = true },
-}
-
-local function prepare_contextchains(tfmdata)
-    local rawdata    = tfmdata.shared.rawdata
-    local resources  = rawdata.resources
-    local lookuphash = resources.lookuphash
-    local lookuptags = resources.lookuptags
-    local lookups    = rawdata.lookups
-    if lookups then
-        for lookupname, lookupdata in next, rawdata.lookups do
-            local lookuptype = lookupdata.type
-            if lookuptype then
-                local rules = lookupdata.rules
-                if rules then
-                    local format = lookupdata.format
-                    local validformat = valid[format]
-                    if not validformat then
-                        report_prepare("unsupported format %a",format)
-                    elseif not validformat[lookuptype] then
-                        -- todo: dejavu-serif has one (but i need to see what use it has)
-                        report_prepare("unsupported format %a, lookuptype %a, lookupname %a",format,lookuptype,lookuptags[lookupname])
-                    else
-                        local contexts = lookuphash[lookupname]
-                        if not contexts then
-                            contexts = { }
-                            lookuphash[lookupname] = contexts
-                        end
-                        local t, nt = { }, 0
-                        for nofrules=1,#rules do
-                            local rule         = rules[nofrules]
-                            local current      = rule.current
-                            local before       = rule.before
-                            local after        = rule.after
-                            local replacements = rule.replacements
-                            local sequence     = { }
-                            local nofsequences = 0
-                            -- Eventually we can store start, stop and sequence in the cached file
-                            -- but then less sharing takes place so best not do that without a lot
-                            -- of profiling so let's forget about it.
-                            if before then
-                                for n=1,#before do
-                                    nofsequences = nofsequences + 1
-                                    sequence[nofsequences] = before[n]
-                                end
-                            end
-                            local start = nofsequences + 1
-                            for n=1,#current do
-                                nofsequences = nofsequences + 1
-                                sequence[nofsequences] = current[n]
-                            end
-                            local stop = nofsequences
-                            if after then
-                                for n=1,#after do
-                                    nofsequences = nofsequences + 1
-                                    sequence[nofsequences] = after[n]
-                                end
-                            end
-                            if sequence[1] then
-                                -- Replacements only happen with reverse lookups as they are single only. We
-                                -- could pack them into current (replacement value instead of true) and then
-                                -- use sequence[start] instead but it's somewhat ugly.
-                                nt = nt + 1
-                                t[nt] = { nofrules, lookuptype, sequence, start, stop, rule.lookups, replacements }
-                                for unic in next, sequence[start] do
-                                    local cu = contexts[unic]
-                                    if not cu then
-                                        contexts[unic] = t
-                                    end
-                                end
-                            end
-                        end
-                    end
-                else
-                    -- no rules
-                end
-            else
-                report_prepare("missing lookuptype for lookupname %a",lookuptags[lookupname])
-            end
-        end
-    end
-end
-
--- we can consider lookuphash == false (initialized but empty) vs lookuphash == table
-
-local function featuresinitializer(tfmdata,value)
-    if true then -- value then
-        -- beware we need to use the topmost properties table
-        local rawdata    = tfmdata.shared.rawdata
-        local properties = rawdata.properties
-        if not properties.initialized then
-            local starttime = trace_preparing and os.clock()
-            local resources = rawdata.resources
-            resources.lookuphash = resources.lookuphash or { }
-            prepare_contextchains(tfmdata)
-            prepare_lookups(tfmdata)
-            properties.initialized = true
-            if trace_preparing then
-                report_prepare("preparation time is %0.3f seconds for %a",os.clock()-starttime,tfmdata.properties.fullname)
-            end
-        end
-    end
-end
-
-registerotffeature {
-    name         = "features",
-    description  = "features",
-    default      = true,
-    initializers = {
-        position = 1,
-        node     = featuresinitializer,
-    },
-    processors   = {
-        node     = featuresprocessor,
-    }
-}
-
--- This can be used for extra handlers, but should be used with care!
-
-otf.handlers = handlers
diff --git a/src/luaotfload-init.lua b/src/luaotfload-init.lua
index 895e32e..3568d21 100644
--- a/src/luaotfload-init.lua
+++ b/src/luaotfload-init.lua
@@ -65,6 +65,36 @@ local logreport  --- filled in after loading the log module
 
 --doc]]--
 
+local glyph_codes =
+  { [0] = "character"
+  , [1] = "glyph"
+  , [2] = "ligature"
+  , [3] = "ghost"
+  , [4] = "left"
+  , [5] = "right"
+  }
+
+local disc_codes  =
+  { [0] = "discretionary"
+  , [1] = "explicit"
+  , [2] = "automatic"
+  , [3] = "regular"
+  , [4] = "first"
+  , [5] = "second"
+  }
+
+local node_types = { disc = disc_codes, glyph = glyph_codes }
+
+local luatex_stubs = function ()
+  if not node.subtypes then
+    node.subtypes = function (t) return node_types [t] or { } end
+    local direct = node.direct
+    local getfield = direct.getfield
+    local setfield = direct.setfield
+    direct.setchar = direct.setchar or function (n, ...) setfield (n, "char", ...) end
+    direct.getchar = direct.getchar or function (n) getfield (n, "char")    end
+  end
+end
 
 local init_early = function ()
 
@@ -82,6 +112,8 @@ local init_early = function ()
   if not lualibs    then error "this module requires Luaotfload" end
   if not luaotfload then error "this module requires Luaotfload" end
 
+  luatex_stubs ()
+
   --[[doc--
 
     The logger needs to be in place prior to loading the fontloader due
@@ -214,9 +246,9 @@ local context_modules = {
   { ctx,   "font-oti"          },
   { ctx,   "font-otf"          },
   { ctx,   "font-otb"          },
-  { ltx,   "luatex-fonts-inj"  }, --> since 2014-01-07, replaces node-inj.lua
+  { ltx,   "font-inj"          },
   { ltx,   "luatex-fonts-ota"  },
-  { ltx,   "luatex-fonts-otn"  }, --> since 2014-01-07, replaces font-otn.lua
+  { ltx,   "font-otn"          },
   { ctx,   "font-otp"          }, --> since 2013-04-23
   { ltx,   "luatex-fonts-lua"  },
   { ctx,   "font-def"          },
@@ -338,9 +370,9 @@ local init_main = function ()
     load_fontloader_module "font-oti"
     load_fontloader_module "font-otf"
     load_fontloader_module "font-otb"
-    load_fontloader_module "fonts-inj"  --> since 2014-01-07, replaces node-inj.lua
+    load_fontloader_module "font-inj"
     load_fontloader_module "fonts-ota"
-    load_fontloader_module "fonts-otn"  --> since 2014-01-07, replaces font-otn.lua
+    load_fontloader_module "font-otn"
     load_fontloader_module "font-otp"   --> since 2013-04-23
     load_fontloader_module "fonts-lua"
     load_fontloader_module "font-def"
-- 
cgit v1.2.3


From 1de061c21e48dc546c62ff3e845cedcf8f2747ff Mon Sep 17 00:00:00 2001
From: Philipp Gesang <phg@phi-gamma.net>
Date: Mon, 21 Dec 2015 22:55:28 +0100
Subject: [fontloader] sync with Context as of 2015-12-21

---
 src/fontloader/misc/fontloader-basics-nod.lua   |   45 +-
 src/fontloader/misc/fontloader-font-con.lua     |    2 +
 src/fontloader/misc/fontloader-font-inj.lua     | 1196 +++++++
 src/fontloader/misc/fontloader-font-map.lua     |   66 +-
 src/fontloader/misc/fontloader-font-otf.lua     |    5 +-
 src/fontloader/misc/fontloader-font-oti.lua     |   69 +-
 src/fontloader/misc/fontloader-font-otn.lua     | 3958 +++++++++++++++++++++++
 src/fontloader/misc/fontloader-fonts-def.lua    |    2 +-
 src/fontloader/misc/fontloader-fonts-ota.lua    |   30 +-
 src/fontloader/misc/fontloader-fonts.lua        |    6 +-
 src/fontloader/misc/fontloader-languages.lua    |   15 +-
 src/fontloader/misc/fontloader-languages.tex    |    2 +
 src/fontloader/runtime/fontloader-reference.lua |  543 +++-
 13 files changed, 5724 insertions(+), 215 deletions(-)
 create mode 100644 src/fontloader/misc/fontloader-font-inj.lua
 create mode 100644 src/fontloader/misc/fontloader-font-otn.lua

diff --git a/src/fontloader/misc/fontloader-basics-nod.lua b/src/fontloader/misc/fontloader-basics-nod.lua
index 39400a3..78f1b17 100644
--- a/src/fontloader/misc/fontloader-basics-nod.lua
+++ b/src/fontloader/misc/fontloader-basics-nod.lua
@@ -51,17 +51,23 @@ nodes              = { }
 nodes.pool         = { }
 nodes.handlers     = { }
 
-local nodecodes    = { } for k,v in next, node.types   () do nodecodes[string.gsub(v,"_","")] = k end
-local whatcodes    = { } for k,v in next, node.whatsits() do whatcodes[string.gsub(v,"_","")] = k end
-local glyphcodes   = { [0] = "character", "glyph", "ligature", "ghost", "left", "right" }
-local disccodes    = { [0] = "discretionary", "explicit", "automatic", "regular", "first", "second" }
-
-for i=0,#glyphcodes do glyphcodes[glyphcodes[i]] = i end
-for i=0,#disccodes  do disccodes [disccodes [i]] = i end
+local nodecodes    = { }
+local glyphcodes   = node.subtypes("glyph")
+local disccodes    = node.subtypes("disc")
+
+for k, v in next, node.types() do
+    v = string.gsub(v,"_","")
+    nodecodes[k] = v
+    nodecodes[v] = k
+end
+for i=0,#glyphcodes do
+    glyphcodes[glyphcodes[i]] = i
+end
+for i=0,#disccodes do
+    disccodes[disccodes[i]] = i
+end
 
 nodes.nodecodes    = nodecodes
-nodes.whatcodes    = whatcodes
-nodes.whatsitcodes = whatcodes
 nodes.glyphcodes   = glyphcodes
 nodes.disccodes    = disccodes
 
@@ -140,7 +146,6 @@ nodes.slide                = node.slide
 nodes.vpack                = node.vpack
 
 nodes.first_glyph          = node.first_glyph
-nodes.first_character      = node.first_character
 nodes.has_glyph            = node.has_glyph or node.first_glyph
 
 nodes.current_attr         = node.current_attr
@@ -178,20 +183,33 @@ nodes.tonut              = tonut
 nuts.tonode              = tonode
 nuts.tonut               = tonut
 
-
 local getfield           = direct.getfield
 local setfield           = direct.setfield
 
 nuts.getfield            = getfield
 nuts.setfield            = setfield
 nuts.getnext             = direct.getnext
+nuts.setnext             = direct.setnext
 nuts.getprev             = direct.getprev
+nuts.setprev             = direct.setprev
+nuts.getboth             = direct.getboth
+nuts.setboth             = direct.setboth
 nuts.getid               = direct.getid
-nuts.getattr             = getfield
+nuts.getattr             = direct.get_attribute or direct.has_attribute or getfield
 nuts.setattr             = setfield
 nuts.getfont             = direct.getfont
+nuts.setfont             = direct.setfont
 nuts.getsubtype          = direct.getsubtype
+nuts.setsubtype          = direct.setsubtype or function(n,s) setfield(n,"subtype",s) end
 nuts.getchar             = direct.getchar
+nuts.setchar             = direct.setchar
+nuts.getdisc             = direct.getdisc
+nuts.setdisc             = direct.setdisc
+nuts.setlink             = direct.setlink
+nuts.getlist             = direct.getlist
+nuts.setlist             = direct.setlist    or function(n,l) setfield(n,"list",l) end
+nuts.getleader           = direct.getleader
+nuts.setleader           = direct.setleader  or function(n,l) setfield(n,"leader",l) end
 
 nuts.insert_before       = direct.insert_before
 nuts.insert_after        = direct.insert_after
@@ -206,6 +224,9 @@ nuts.is_node             = direct.is_node
 nuts.end_of_math         = direct.end_of_math
 nuts.traverse            = direct.traverse
 nuts.traverse_id         = direct.traverse_id
+nuts.traverse_char       = direct.traverse_char
+nuts.ligaturing          = direct.ligaturing
+nuts.kerning             = direct.kerning
 
 nuts.getprop             = nuts.getattr
 nuts.setprop             = nuts.setattr
diff --git a/src/fontloader/misc/fontloader-font-con.lua b/src/fontloader/misc/fontloader-font-con.lua
index 55d7793..e5bf9e9 100644
--- a/src/fontloader/misc/fontloader-font-con.lua
+++ b/src/fontloader/misc/fontloader-font-con.lua
@@ -682,6 +682,8 @@ function constructors.scale(tfmdata,specification)
         if isunicode then
             chr.unicode   = isunicode
             chr.tounicode = tounicode(isunicode)
+            -- in luatex > 0.85 we can do this:
+         -- chr.tounicode = isunicode
         end
         if hasquality then
             -- we could move these calculations elsewhere (saves calculations)
diff --git a/src/fontloader/misc/fontloader-font-inj.lua b/src/fontloader/misc/fontloader-font-inj.lua
new file mode 100644
index 0000000..8937021
--- /dev/null
+++ b/src/fontloader/misc/fontloader-font-inj.lua
@@ -0,0 +1,1196 @@
+if not modules then modules = { } end modules ['font-inj'] = {
+    version   = 1.001,
+    comment   = "companion to font-lib.mkiv",
+    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+    copyright = "PRAGMA ADE / ConTeXt Development Team",
+    license   = "see context related readme files",
+}
+
+-- This property based variant is not faster but looks nicer than the attribute one. We
+-- need to use rawget (which is apbout 4 times slower than a direct access but we cannot
+-- get/set that one for our purpose! This version does a bit more with discretionaries
+-- (and Kai has tested it with his collection of weird fonts.)
+
+-- There is some duplicate code here (especially in the the pre/post/replace branches) but
+-- we go for speed. We could store a list of glyph and mark nodes when registering but it's
+-- cleaner to have an identification pass here. Also, I need to keep tracing in mind so
+-- being too clever here is dangerous.
+
+-- The subtype test is not needed as there will be no (new) properties set, given that we
+-- reset the properties.
+
+if not nodes.properties then return end
+
+local next, rawget = next, rawget
+local utfchar = utf.char
+local fastcopy = table.fastcopy
+
+local trace_injections = false  trackers.register("fonts.injections", function(v) trace_injections = v end)
+
+local report_injections = logs.reporter("fonts","injections")
+
+local attributes, nodes, node = attributes, nodes, node
+
+fonts                    = fonts
+local fontdata           = fonts.hashes.identifiers
+
+nodes.injections         = nodes.injections or { }
+local injections         = nodes.injections
+
+local nodecodes          = nodes.nodecodes
+local glyph_code         = nodecodes.glyph
+local disc_code          = nodecodes.disc
+local kern_code          = nodecodes.kern
+
+local nuts               = nodes.nuts
+local nodepool           = nuts.pool
+
+local newkern            = nodepool.kern
+
+local tonode             = nuts.tonode
+local tonut              = nuts.tonut
+
+local getfield           = nuts.getfield
+local setfield           = nuts.setfield
+local getnext            = nuts.getnext
+local getprev            = nuts.getprev
+local getid              = nuts.getid
+local getfont            = nuts.getfont
+local getsubtype         = nuts.getsubtype
+local getchar            = nuts.getchar
+
+local traverse_id        = nuts.traverse_id
+local insert_node_before = nuts.insert_before
+local insert_node_after  = nuts.insert_after
+local find_tail          = nuts.tail
+
+local properties         = nodes.properties.data
+
+function injections.installnewkern(nk)
+    newkern = nk or newkern
+end
+
+local nofregisteredkerns    = 0
+local nofregisteredpairs    = 0
+local nofregisteredmarks    = 0
+local nofregisteredcursives = 0
+local keepregisteredcounts  = false
+
+function injections.keepcounts()
+    keepregisteredcounts = true
+end
+
+function injections.resetcounts()
+    nofregisteredkerns    = 0
+    nofregisteredpairs    = 0
+    nofregisteredmarks    = 0
+    nofregisteredcursives = 0
+    keepregisteredcounts  = false
+end
+
+-- We need to make sure that a possible metatable will not kick in unexpectedly.
+
+-- function injections.reset(n)
+--     local p = rawget(properties,n)
+--     if p and rawget(p,"injections") then
+--         p.injections = nil
+--     end
+-- end
+
+-- function injections.copy(target,source)
+--     local sp = rawget(properties,source)
+--     if sp then
+--         local tp = rawget(properties,target)
+--         local si = rawget(sp,"injections")
+--         if si then
+--             si = fastcopy(si)
+--             if tp then
+--                 tp.injections = si
+--             else
+--                 propertydata[target] = {
+--                     injections = si,
+--                 }
+--             end
+--         else
+--             if tp then
+--                 tp.injections = nil
+--             end
+--         end
+--     end
+-- end
+
+function injections.reset(n)
+    local p = rawget(properties,n)
+    if p then
+        p.injections = false -- { }
+    else
+        properties[n] = false -- { injections = { } }
+    end
+end
+
+function injections.copy(target,source)
+    local sp = rawget(properties,source)
+    if sp then
+        local tp = rawget(properties,target)
+        local si = rawget(sp,"injections")
+        if si then
+            si = fastcopy(si)
+            if tp then
+                tp.injections = si
+            else
+                propertydata[target] = {
+                    injections = si,
+                }
+            end
+        elseif tp then
+            tp.injections = false -- { }
+        else
+            properties[target] = { injections = { } }
+        end
+    else
+        local tp = rawget(properties,target)
+        if tp then
+            tp.injections = false -- { }
+        else
+            properties[target] = false -- { injections = { } }
+        end
+    end
+end
+
+function injections.setligaindex(n,index)
+    local p = rawget(properties,n)
+    if p then
+        local i = rawget(p,"injections")
+        if i then
+            i.ligaindex = index
+        else
+            p.injections = {
+                ligaindex = index
+            }
+        end
+    else
+        properties[n] = {
+            injections = {
+                ligaindex = index
+            }
+        }
+    end
+end
+
+function injections.getligaindex(n,default)
+    local p = rawget(properties,n)
+    if p then
+        local i = rawget(p,"injections")
+        if i then
+            return i.ligaindex or default
+        end
+    end
+    return default
+end
+
+function injections.setcursive(start,nxt,factor,rlmode,exit,entry,tfmstart,tfmnext) -- hm: nuts or nodes
+    local dx =  factor*(exit[1]-entry[1])
+    local dy = -factor*(exit[2]-entry[2])
+    local ws = tfmstart.width
+    local wn = tfmnext.width
+    nofregisteredcursives = nofregisteredcursives + 1
+    if rlmode < 0 then
+        dx = -(dx + wn)
+    else
+        dx = dx - ws
+    end
+    --
+    local p = rawget(properties,start)
+    if p then
+        local i = rawget(p,"injections")
+        if i then
+            i.cursiveanchor = true
+        else
+            p.injections = {
+                cursiveanchor = true,
+            }
+        end
+    else
+        properties[start] = {
+            injections = {
+                cursiveanchor = true,
+            },
+        }
+    end
+    local p = rawget(properties,nxt)
+    if p then
+        local i = rawget(p,"injections")
+        if i then
+            i.cursivex = dx
+            i.cursivey = dy
+        else
+            p.injections = {
+                cursivex = dx,
+                cursivey = dy,
+            }
+        end
+    else
+        properties[nxt] = {
+            injections = {
+                cursivex = dx,
+                cursivey = dy,
+            },
+        }
+    end
+    return dx, dy, nofregisteredcursives
+end
+
+function injections.setpair(current,factor,rlmode,r2lflag,spec,injection) -- r2lflag & tfmchr not used
+    local x = factor*spec[1]
+    local y = factor*spec[2]
+    local w = factor*spec[3]
+    local h = factor*spec[4]
+    if x ~= 0 or w ~= 0 or y ~= 0 or h ~= 0 then -- okay?
+        local yoffset   = y - h
+        local leftkern  = x      -- both kerns are set in a pair kern compared
+        local rightkern = w - x  -- to normal kerns where we set only leftkern
+        if leftkern ~= 0 or rightkern ~= 0 or yoffset ~= 0 then
+            nofregisteredpairs = nofregisteredpairs + 1
+            if rlmode and rlmode < 0 then
+                leftkern, rightkern = rightkern, leftkern
+            end
+            if not injection then
+                injection = "injections"
+            end
+            local p = rawget(properties,current)
+            if p then
+                local i = rawget(p,injection)
+                if i then
+                    if leftkern ~= 0 then
+                        i.leftkern  = (i.leftkern  or 0) + leftkern
+                    end
+                    if rightkern ~= 0 then
+                        i.rightkern = (i.rightkern or 0) + rightkern
+                    end
+                    if yoffset ~= 0 then
+                        i.yoffset = (i.yoffset or 0) + yoffset
+                    end
+                elseif leftkern ~= 0 or rightkern ~= 0 then
+                    p[injection] = {
+                        leftkern  = leftkern,
+                        rightkern = rightkern,
+                        yoffset   = yoffset,
+                    }
+                else
+                    p[injection] = {
+                        yoffset = yoffset,
+                    }
+                end
+            elseif leftkern ~= 0 or rightkern ~= 0 then
+                properties[current] = {
+                    [injection] = {
+                        leftkern  = leftkern,
+                        rightkern = rightkern,
+                        yoffset   = yoffset,
+                    },
+                }
+            else
+                properties[current] = {
+                    [injection] = {
+                        yoffset = yoffset,
+                    },
+                }
+            end
+            return x, y, w, h, nofregisteredpairs
+         end
+    end
+    return x, y, w, h -- no bound
+end
+
+-- This needs checking for rl < 0 but it is unlikely that a r2l script uses kernclasses between
+-- glyphs so we're probably safe (KE has a problematic font where marks interfere with rl < 0 in
+-- the previous case)
+
+function injections.setkern(current,factor,rlmode,x,injection)
+    local dx = factor * x
+    if dx ~= 0 then
+        nofregisteredkerns = nofregisteredkerns + 1
+        local p = rawget(properties,current)
+        if not injection then
+            injection = "injections"
+        end
+        if p then
+            local i = rawget(p,injection)
+            if i then
+                i.leftkern = dx + (i.leftkern or 0)
+            else
+                p[injection] = {
+                    leftkern = dx,
+                }
+            end
+        else
+            properties[current] = {
+                [injection] = {
+                    leftkern = dx,
+                },
+            }
+        end
+        return dx, nofregisteredkerns
+    else
+        return 0, 0
+    end
+end
+
+function injections.setmark(start,base,factor,rlmode,ba,ma,tfmbase,mkmk) -- ba=baseanchor, ma=markanchor
+    local dx, dy = factor*(ba[1]-ma[1]), factor*(ba[2]-ma[2])
+    nofregisteredmarks = nofregisteredmarks + 1
+ -- markanchors[nofregisteredmarks] = base
+    if rlmode >= 0 then
+        dx = tfmbase.width - dx -- see later commented ox
+    end
+    local p = rawget(properties,start)
+    -- hm, dejavu serif does a sloppy mark2mark before mark2base
+    if p then
+        local i = rawget(p,"injections")
+        if i then
+            if i.markmark then
+                -- out of order mkmk: yes or no or option
+            else
+                i.markx        = dx
+                i.marky        = dy
+                i.markdir      = rlmode or 0
+                i.markbase     = nofregisteredmarks
+                i.markbasenode = base
+                i.markmark     = mkmk
+            end
+        else
+            p.injections = {
+                markx        = dx,
+                marky        = dy,
+                markdir      = rlmode or 0,
+                markbase     = nofregisteredmarks,
+                markbasenode = base,
+                markmark     = mkmk,
+            }
+        end
+    else
+        properties[start] = {
+            injections = {
+                markx        = dx,
+                marky        = dy,
+                markdir      = rlmode or 0,
+                markbase     = nofregisteredmarks,
+                markbasenode = base,
+                markmark     = mkmk,
+            },
+        }
+    end
+    return dx, dy, nofregisteredmarks
+end
+
+local function dir(n)
+    return (n and n<0 and "r-to-l") or (n and n>0 and "l-to-r") or "unset"
+end
+
+local function showchar(n,nested)
+    local char = getchar(n)
+    report_injections("%wfont %s, char %U, glyph %c",nested and 2 or 0,getfont(n),char,char)
+end
+
+local function show(n,what,nested,symbol)
+    if n then
+        local p = rawget(properties,n)
+        if p then
+            local i = rawget(p,what)
+            if i then
+                local leftkern  = i.leftkern  or 0
+                local rightkern = i.rightkern or 0
+                local yoffset   = i.yoffset   or 0
+                local markx     = i.markx     or 0
+                local marky     = i.marky     or 0
+                local markdir   = i.markdir   or 0
+                local markbase  = i.markbase  or 0
+                local cursivex  = i.cursivex  or 0
+                local cursivey  = i.cursivey  or 0
+                local ligaindex = i.ligaindex or 0
+                local cursbase  = i.cursiveanchor
+                local margin    = nested and 4 or 2
+                --
+                if rightkern ~= 0 or yoffset ~= 0 then
+                    report_injections("%w%s pair: lx %p, rx %p, dy %p",margin,symbol,leftkern,rightkern,yoffset)
+                elseif leftkern ~= 0 then
+                    report_injections("%w%s kern: dx %p",margin,symbol,leftkern)
+                end
+                if markx ~= 0 or marky ~= 0 or markbase ~= 0 then
+                    report_injections("%w%s mark: dx %p, dy %p, dir %s, base %s",margin,symbol,markx,marky,markdir,markbase ~= 0 and "yes" or "no")
+                end
+                if cursivex ~= 0 or cursivey ~= 0 then
+                    if cursbase then
+                        report_injections("%w%s curs: base dx %p, dy %p",margin,symbol,cursivex,cursivey)
+                    else
+                        report_injections("%w%s curs: dx %p, dy %p",margin,symbol,cursivex,cursivey)
+                    end
+                elseif cursbase then
+                    report_injections("%w%s curs: base",margin,symbol)
+                end
+                if ligaindex ~= 0 then
+                    report_injections("%w%s liga: index %i",margin,symbol,ligaindex)
+                end
+            end
+        end
+    end
+end
+
+local function showsub(n,what,where)
+    report_injections("begin subrun: %s",where)
+    for n in traverse_id(glyph_code,n) do
+        showchar(n,where)
+        show(n,what,where," ")
+    end
+    report_injections("end subrun")
+end
+
+local function trace(head,where)
+    report_injections("begin run %s: %s kerns, %s pairs, %s marks and %s cursives registered",
+        where or "",nofregisteredkerns,nofregisteredpairs,nofregisteredmarks,nofregisteredcursives)
+    local n = head
+    while n do
+        local id = getid(n)
+        if id == glyph_code then
+            showchar(n)
+            show(n,"injections",false," ")
+            show(n,"preinjections",false,"<")
+            show(n,"postinjections",false,">")
+            show(n,"replaceinjections",false,"=")
+        elseif id == disc_code then
+            local pre     = getfield(n,"pre")
+            local post    = getfield(n,"post")
+            local replace = getfield(n,"replace")
+            if pre then
+                showsub(pre,"preinjections","pre")
+            end
+            if post then
+                showsub(post,"postinjections","post")
+            end
+            if replace then
+                showsub(replace,"replaceinjections","replace")
+            end
+        end
+        n = getnext(n)
+    end
+    report_injections("end run")
+end
+
+local function show_result(head)
+    local current  = head
+    local skipping = false
+    while current do
+        local id = getid(current)
+        if id == glyph_code then
+            report_injections("char: %C, width %p, xoffset %p, yoffset %p",
+                getchar(current),getfield(current,"width"),getfield(current,"xoffset"),getfield(current,"yoffset"))
+            skipping = false
+        elseif id == kern_code then
+            report_injections("kern: %p",getfield(current,"kern"))
+            skipping = false
+        elseif not skipping then
+            report_injections()
+            skipping = true
+        end
+        current = getnext(current)
+    end
+end
+
+local function collect_glyphs(head,offsets)
+    local glyphs, glyphi, nofglyphs = { }, { }, 0
+    local marks, marki, nofmarks = { }, { }, 0
+    local nf, tm = nil, nil
+    local n = head
+
+    local function identify(n,what)
+        local f = getfont(n)
+        if f ~= nf then
+            nf = f
+            -- other hash in ctx:
+            tm = fontdata[nf].resources
+            if tm then
+                tm = tm.marks
+            end
+        end
+        if tm and tm[getchar(n)] then
+            nofmarks = nofmarks + 1
+            marks[nofmarks] = n
+            marki[nofmarks] = "injections"
+        else
+            nofglyphs = nofglyphs + 1
+            glyphs[nofglyphs] = n
+            glyphi[nofglyphs] = what
+        end
+        if offsets then
+            -- yoffsets can influence curs steps
+            local p = rawget(properties,n)
+            if p then
+                local i = rawget(p,what)
+                if i then
+                    local yoffset = i.yoffset
+                    if yoffset and yoffset ~= 0 then
+                        setfield(n,"yoffset",yoffset)
+                    end
+                end
+            end
+        end
+    end
+
+    while n do -- only needed for relevant fonts
+        local id = getid(n)
+        if id == glyph_code then
+            identify(n,"injections")
+        elseif id == disc_code then
+            local d = getfield(n,"pre")
+            if d then
+                for n in traverse_id(glyph_code,d) do
+                    if getsubtype(n) < 256 then
+                        identify(n,"preinjections")
+                    end
+                end
+			end
+            local d = getfield(n,"post")
+            if d then
+                for n in traverse_id(glyph_code,d) do
+                    if getsubtype(n) < 256 then
+                        identify(n,"postinjections")
+                    end
+                end
+			end
+            local d = getfield(n,"replace")
+            if d then
+                for n in traverse_id(glyph_code,d) do
+                    if getsubtype(n) < 256 then
+                        identify(n,"replaceinjections")
+                    end
+                end
+			end
+        end
+		n = getnext(n)
+    end
+
+    return glyphs, glyphi, nofglyphs, marks, marki, nofmarks
+end
+
+local function inject_marks(marks,marki,nofmarks)
+    for i=1,nofmarks do
+        local n  = marks[i]
+        local pn = rawget(properties,n)
+        if pn then
+            local ni = marki[i]
+            local pn = rawget(pn,ni)
+            if pn then
+                local p = pn.markbasenode
+                if p then
+                    local px = getfield(p,"xoffset")
+                    local ox = 0
+                    local rightkern = nil
+                    local pp = rawget(properties,p)
+                    if pp then
+                        pp = rawget(pp,ni)
+                        if pp then
+                            rightkern = pp.rightkern
+                        end
+                    end
+                    if rightkern then -- x and w ~= 0
+                        if pn.markdir < 0 then
+                            -- kern(w-x) glyph(p) kern(x) mark(n)
+                            ox = px - pn.markx - rightkern
+                         -- report_injections("r2l case 1: %p",ox)
+                        else
+                            -- kern(x) glyph(p) kern(w-x) mark(n)
+                         -- ox = px - getfield(p,"width") + pn.markx - pp.leftkern
+                            --
+							-- According to Kai we don't need to handle leftkern here but I'm
+                            -- pretty sure I've run into a case where it was needed so maybe
+	                        -- some day we need something more clever here.
+                            --
+							if false then
+                                -- a mark with kerning
+                                local leftkern = pp.leftkern
+                                if leftkern then
+                                    ox = px - pn.markx - leftkern
+                                else
+                                    ox = px - pn.markx
+                                end
+                            else
+                                ox = px - pn.markx
+                            end
+                        end
+                    else
+                        -- we need to deal with fonts that have marks with width
+                     -- if pn.markdir < 0 then
+                     --     ox = px - pn.markx
+                     --  -- report_injections("r2l case 3: %p",ox)
+                     -- else
+                     --  -- ox = px - getfield(p,"width") + pn.markx
+                            ox = px - pn.markx
+                         -- report_injections("l2r case 3: %p",ox)
+                     -- end
+                        local wn = getfield(n,"width") -- in arial marks have widths
+                        if wn ~= 0 then
+                            -- bad: we should center
+                         -- insert_node_before(head,n,newkern(-wn/2))
+                         -- insert_node_after(head,n,newkern(-wn/2))
+                            pn.leftkern  = -wn/2
+                            pn.rightkern = -wn/2
+                         -- wx[n] = { 0, -wn/2, 0, -wn }
+                        end
+                        -- so far
+                    end
+                    setfield(n,"xoffset",ox)
+                    --
+                    local py = getfield(p,"yoffset")
+--                     local oy = 0
+--                     if marks[p] then
+--                         oy = py + pn.marky
+--                     else
+--                         oy = getfield(n,"yoffset") + py + pn.marky
+--                     end
+                    local oy = getfield(n,"yoffset") + py + pn.marky
+                    setfield(n,"yoffset",oy)
+                else
+                    -- normally this can't happen (only when in trace mode which is a special case anyway)
+                 -- report_injections("missing mark anchor %i",pn.markbase or 0)
+                end
+            end
+        end
+    end
+end
+
+local function inject_cursives(glyphs,glyphi,nofglyphs)
+    local cursiveanchor, lastanchor = nil, nil
+    local minc, maxc, last = 0, 0, nil
+    for i=1,nofglyphs do
+        local n  = glyphs[i]
+        local pn = rawget(properties,n)
+        if pn then
+            pn = rawget(pn,glyphi[i])
+        end
+        if pn then
+            local cursivex = pn.cursivex
+            if cursivex then
+                if cursiveanchor then
+                    if cursivex ~= 0 then
+                        pn.leftkern = (pn.leftkern or 0) + cursivex
+                    end
+                    if lastanchor then
+                        if maxc == 0 then
+                            minc = lastanchor
+                        end
+                        maxc = lastanchor
+                        properties[cursiveanchor].cursivedy = pn.cursivey
+                    end
+                    last = n
+                else
+                    maxc = 0
+                end
+            elseif maxc > 0 then
+                local ny = getfield(n,"yoffset")
+                for i=maxc,minc,-1 do
+                    local ti = glyphs[i]
+                    ny = ny + properties[ti].cursivedy
+                    setfield(ti,"yoffset",ny) -- why not add ?
+                end
+                maxc = 0
+            end
+            if pn.cursiveanchor then
+                cursiveanchor = n
+                lastanchor = i
+            else
+                cursiveanchor = nil
+                lastanchor = nil
+                if maxc > 0 then
+                    local ny = getfield(n,"yoffset")
+                    for i=maxc,minc,-1 do
+                        local ti = glyphs[i]
+                        ny = ny + properties[ti].cursivedy
+                        setfield(ti,"yoffset",ny) -- why not add ?
+                    end
+                    maxc = 0
+                end
+            end
+        elseif maxc > 0 then
+            local ny = getfield(n,"yoffset")
+            for i=maxc,minc,-1 do
+                local ti = glyphs[i]
+                ny = ny + properties[ti].cursivedy
+                setfield(ti,"yoffset",getfield(ti,"yoffset") + ny) -- ?
+            end
+            maxc = 0
+            cursiveanchor = nil
+            lastanchor = nil
+        end
+     -- if maxc > 0 and not cursiveanchor then
+     --     local ny = getfield(n,"yoffset")
+     --     for i=maxc,minc,-1 do
+     --         local ti = glyphs[i][1]
+     --         ny = ny + properties[ti].cursivedy
+     --         setfield(ti,"yoffset",ny) -- why not add ?
+     --     end
+     --     maxc = 0
+     -- end
+    end
+    if last and maxc > 0 then
+        local ny = getfield(last,"yoffset")
+        for i=maxc,minc,-1 do
+            local ti = glyphs[i]
+            ny = ny + properties[ti].cursivedy
+            setfield(ti,"yoffset",ny) -- why not add ?
+        end
+    end
+end
+
+-- G  +D-pre        G
+--     D-post+
+--    +D-replace+
+--
+-- G  +D-pre       +D-pre
+--     D-post      +D-post
+--    +D-replace   +D-replace
+
+local function inject_kerns(head,glist,ilist,length) -- not complete ! compare with inject_kerns_only (but unlikely disc here)
+    for i=1,length do
+        local n  = glist[i]
+        local pn = rawget(properties,n)
+        if pn then
+			local dp = nil
+			local dr = nil
+            local ni = ilist[i]
+            local p  = nil
+			if ni == "injections" then
+				p = getprev(n)
+				if p then
+					local id = getid(p)
+					if id == disc_code then
+						dp = getfield(p,"post")
+						dr = getfield(p,"replace")
+					end
+				end
+			end
+			if dp then
+				local i = rawget(pn,"postinjections")
+				if i then
+					local leftkern = i.leftkern
+					if leftkern and leftkern ~= 0 then
+						local t = find_tail(dp)
+						insert_node_after(dp,t,newkern(leftkern))
+                        setfield(p,"post",dp) -- currently we need to force a tail refresh
+					end
+				end
+			end
+			if dr then
+				local i = rawget(pn,"replaceinjections")
+				if i then
+					local leftkern = i.leftkern
+					if leftkern and leftkern ~= 0 then
+						local t = find_tail(dr)
+						insert_node_after(dr,t,newkern(leftkern))
+                        setfield(p,"replace",dr) -- currently we need to force a tail refresh
+					end
+				end
+			else
+				local i = rawget(pn,ni)
+				if i then
+					local leftkern = i.leftkern
+					if leftkern and leftkern ~= 0 then
+						insert_node_before(head,n,newkern(leftkern)) -- type 0/2
+					end
+					local rightkern = i.rightkern
+					if rightkern and rightkern ~= 0 then
+						insert_node_after(head,n,newkern(rightkern)) -- type 0/2
+					end
+				end
+			end
+        end
+    end
+end
+
+local function inject_everything(head,where)
+    head = tonut(head)
+    if trace_injections then
+        trace(head,"everything")
+    end
+    local glyphs, glyphi, nofglyphs, marks, marki, nofmarks = collect_glyphs(head,nofregisteredpairs > 0)
+    if nofglyphs > 0 then
+        if nofregisteredcursives > 0 then
+            inject_cursives(glyphs,glyphi,nofglyphs)
+        end
+        if nofregisteredmarks > 0 then -- and nofmarks > 0
+            inject_marks(marks,marki,nofmarks)
+        end
+        inject_kerns(head,glyphs,glyphi,nofglyphs)
+    end
+    if nofmarks > 0 then
+        inject_kerns(head,marks,marki,nofmarks)
+	end
+    if keepregisteredcounts then
+        keepregisteredcounts  = false
+    else
+        nofregisteredkerns    = 0
+        nofregisteredpairs    = 0
+        nofregisteredmarks    = 0
+        nofregisteredcursives = 0
+    end
+    return tonode(head), true
+end
+
+-- G  +D-pre        G
+--     D-post+
+--    +D-replace+
+--
+-- G  +D-pre       +D-pre
+--     D-post      +D-post
+--    +D-replace   +D-replace
+
+local function inject_kerns_only(head,where)
+    head = tonut(head)
+    if trace_injections then
+        trace(head,"kerns")
+    end
+    local n = head
+    local p = nil -- disc node when non-nil
+    while n do
+        local id = getid(n)
+        if id == glyph_code then
+            if getsubtype(n) < 256 then
+                local pn = rawget(properties,n)
+                if pn then
+                    if p then
+                        local d = getfield(p,"post")
+                        if d then
+                            local i = rawget(pn,"postinjections")
+                            if i then
+                                local leftkern = i.leftkern
+                                if leftkern and leftkern ~= 0 then
+                                    local t = find_tail(d)
+                                    insert_node_after(d,t,newkern(leftkern))
+                                    setfield(p,"post",d) -- currently we need to force a tail refresh
+                                end
+                            end
+                        end
+                        local d = getfield(p,"replace")
+                        if d then
+                            local i = rawget(pn,"replaceinjections")
+                            if i then
+                                local leftkern = i.leftkern
+                                if leftkern and leftkern ~= 0 then
+                                    local t = find_tail(d)
+                                    insert_node_after(d,t,newkern(leftkern))
+                                    setfield(p,"replace",d) -- currently we need to force a tail refresh
+                                end
+                            end
+                        else
+                            local i = rawget(pn,"injections")
+                            if i then
+                                local leftkern = i.leftkern
+                                if leftkern and leftkern ~= 0 then
+                                    setfield(p,"replace",newkern(leftkern))
+                                end
+                            end
+                        end
+                    else
+                        -- this is the most common case
+                        local i = rawget(pn,"injections")
+                        if i then
+                            local leftkern = i.leftkern
+                            if leftkern and leftkern ~= 0 then
+                                head = insert_node_before(head,n,newkern(leftkern))
+                            end
+                        end
+                    end
+                end
+            end
+            p = nil
+        elseif id == disc_code then
+            local d = getfield(n,"pre")
+            if d then
+                local h = d
+                for n in traverse_id(glyph_code,d) do
+                    if getsubtype(n) < 256 then
+                        local pn = rawget(properties,n)
+                        if pn then
+                            local i = rawget(pn,"preinjections")
+                            if i then
+                                local leftkern = i.leftkern
+                                if leftkern and leftkern ~= 0 then
+                                    h = insert_node_before(h,n,newkern(leftkern))
+                                end
+                            end
+                        end
+                    else
+                        break
+                    end
+                end
+                if h ~= d then
+                    setfield(n,"pre",h)
+                end
+            end
+            local d = getfield(n,"post")
+            if d then
+                local h = d
+                for n in traverse_id(glyph_code,d) do
+                    if getsubtype(n) < 256 then
+                        local pn = rawget(properties,n)
+                        if pn then
+                            local i = rawget(pn,"postinjections")
+                            if i then
+                                local leftkern = i.leftkern
+                                if leftkern and leftkern ~= 0 then
+                                    h = insert_node_before(h,n,newkern(leftkern))
+                                end
+                            end
+                        end
+                    else
+                        break
+                    end
+                end
+                if h ~= d then
+                    setfield(n,"post",h)
+                end
+            end
+            local d = getfield(n,"replace")
+            if d then
+                local h = d
+                for n in traverse_id(glyph_code,d) do
+                    if getsubtype(n) < 256 then
+                        local pn = rawget(properties,n)
+                        if pn then
+                            local i = rawget(pn,"replaceinjections")
+                            if i then
+                                local leftkern = i.leftkern
+                                if leftkern and leftkern ~= 0 then
+                                    h = insert_node_before(h,n,newkern(leftkern))
+                                end
+                            end
+                        end
+                    else
+                        break
+                    end
+                end
+                if h ~= d then
+                    setfield(n,"replace",h)
+                end
+            end
+            p = n
+        else
+            p = nil
+        end
+        n = getnext(n)
+    end
+    --
+    if keepregisteredcounts then
+        keepregisteredcounts = false
+    else
+        nofregisteredkerns   = 0
+    end
+    return tonode(head), true
+end
+
+local function inject_pairs_only(head,where)
+    head = tonut(head)
+    if trace_injections then
+        trace(head,"pairs")
+    end
+    local n = head
+    local p = nil -- disc node when non-nil
+    while n do
+        local id = getid(n)
+        if id == glyph_code then
+            if getsubtype(n) < 256 then
+                local pn = rawget(properties,n)
+                if pn then
+                    if p then
+                        local d = getfield(p,"post")
+                        if d then
+                            local i = rawget(pn,"postinjections")
+                            if i then
+                                local leftkern = i.leftkern
+                                if leftkern and leftkern ~= 0 then
+                                    local t = find_tail(d)
+                                    insert_node_after(d,t,newkern(leftkern))
+                                    setfield(p,"post",d) -- currently we need to force a tail refresh
+                                end
+                             -- local rightkern = i.rightkern
+                             -- if rightkern and rightkern ~= 0 then
+                             --     insert_node_after(head,n,newkern(rightkern))
+                             --     n = getnext(n) -- to be checked
+                             -- end
+                            end
+                        end
+                        local d = getfield(p,"replace")
+                        if d then
+                            local i = rawget(pn,"replaceinjections")
+                            if i then
+                                local leftkern = i.leftkern
+                                if leftkern and leftkern ~= 0 then
+                                    local t = find_tail(d)
+                                    insert_node_after(d,t,newkern(leftkern))
+                                    setfield(p,"replace",d) -- currently we need to force a tail refresh
+                                end
+                             -- local rightkern = i.rightkern
+                             -- if rightkern and rightkern ~= 0 then
+                             --     insert_node_after(head,n,newkern(rightkern))
+                             --     n = getnext(n) -- to be checked
+                             -- end
+                            end
+                        else
+                            local i = rawget(pn,"injections")
+                            if i then
+                                local leftkern = i.leftkern
+                                if leftkern and leftkern ~= 0 then
+                                    setfield(p,"replace",newkern(leftkern))
+                                end
+                             -- local rightkern = i.rightkern
+                             -- if rightkern and rightkern ~= 0 then
+                             --     insert_node_after(head,n,newkern(rightkern))
+                             --     n = getnext(n) -- to be checked
+                             -- end
+                            end
+                        end
+                    else
+                        -- this is the most common case
+                        local i = rawget(pn,"injections")
+                        if i then
+                            local leftkern = i.leftkern
+                            if leftkern and leftkern ~= 0 then
+                                head = insert_node_before(head,n,newkern(leftkern))
+                            end
+                            local rightkern = i.rightkern
+                            if rightkern and rightkern ~= 0 then
+                                insert_node_after(head,n,newkern(rightkern))
+                                n = getnext(n) -- to be checked
+                            end
+                            local yoffset = i.yoffset
+                            if yoffset and yoffset ~= 0 then
+                                setfield(n,"yoffset",yoffset)
+                            end
+                        end
+                    end
+                end
+            end
+            p = nil
+        elseif id == disc_code then
+            local d = getfield(n,"pre")
+            if d then
+                local h = d
+                for n in traverse_id(glyph_code,d) do
+                    if getsubtype(n) < 256 then
+                        local pn = rawget(properties,n)
+                        if pn then
+                            local i = rawget(pn,"preinjections")
+                            if i then
+                                local leftkern = i.leftkern
+                                if leftkern and leftkern ~= 0 then
+                                    h = insert_node_before(h,n,newkern(leftkern))
+                                end
+                                local rightkern = i.rightkern
+                                if rightkern and rightkern ~= 0 then
+                                    insert_node_after(head,n,newkern(rightkern))
+                                    n = getnext(n) -- to be checked
+                                end
+                                local yoffset = i.yoffset
+                                if yoffset and yoffset ~= 0 then
+                                    setfield(n,"yoffset",yoffset)
+                                end
+                            end
+                        end
+                    else
+                        break
+                    end
+                end
+                if h ~= d then
+                    setfield(n,"pre",h)
+                end
+            end
+            local d = getfield(n,"post")
+            if d then
+                local h = d
+                for n in traverse_id(glyph_code,d) do
+                    if getsubtype(n) < 256 then
+                        local pn = rawget(properties,n)
+                        if pn then
+                            local i = rawget(pn,"postinjections")
+                            if i then
+                                local leftkern = i.leftkern
+                                if leftkern and leftkern ~= 0 then
+                                    h = insert_node_before(h,n,newkern(leftkern))
+                                end
+                                local rightkern = i.rightkern
+                                if rightkern and rightkern ~= 0 then
+                                    insert_node_after(head,n,newkern(rightkern))
+                                    n = getnext(n) -- to be checked
+                                end
+                                local yoffset = i.yoffset
+                                if yoffset and yoffset ~= 0 then
+                                    setfield(n,"yoffset",yoffset)
+                                end
+                            end
+                        end
+                    else
+                        break
+                    end
+                end
+                if h ~= d then
+                    setfield(n,"post",h)
+                end
+            end
+            local d = getfield(n,"replace")
+            if d then
+                local h = d
+                for n in traverse_id(glyph_code,d) do
+                    if getsubtype(n) < 256 then
+                        local pn = rawget(properties,n)
+                        if pn then
+                            local i = rawget(pn,"replaceinjections")
+                            if i then
+                                local leftkern = i.leftkern
+                                if leftkern and leftkern ~= 0 then
+                                    h = insert_node_before(h,n,newkern(leftkern))
+                                end
+                                local rightkern = i.rightkern
+                                if rightkern and rightkern ~= 0 then
+                                    insert_node_after(head,n,newkern(rightkern))
+                                    n = getnext(n) -- to be checked
+                                end
+                                local yoffset = i.yoffset
+                                if yoffset and yoffset ~= 0 then
+                                    setfield(n,"yoffset",yoffset)
+                                end
+                            end
+                        end
+                    else
+                        break
+                    end
+                end
+                if h ~= d then
+                    setfield(n,"replace",h)
+                end
+            end
+            p = n
+        else
+            p = nil
+        end
+        n = getnext(n)
+    end
+    --
+    if keepregisteredcounts then
+        keepregisteredcounts = false
+    else
+        nofregisteredpairs = 0
+        nofregisteredkerns = 0
+    end
+    return tonode(head), true
+end
+
+function injections.handler(head,where)
+    if nofregisteredmarks > 0 or nofregisteredcursives > 0 then
+        return inject_everything(head,where)
+    elseif nofregisteredpairs > 0 then
+        return inject_pairs_only(head,where)
+    elseif nofregisteredkerns > 0 then
+        return inject_kerns_only(head,where)
+    else
+        return head, false
+    end
+end
diff --git a/src/fontloader/misc/fontloader-font-map.lua b/src/fontloader/misc/fontloader-font-map.lua
index b645d9a..dc3f499 100644
--- a/src/fontloader/misc/fontloader-font-map.lua
+++ b/src/fontloader/misc/fontloader-font-map.lua
@@ -74,6 +74,71 @@ end
 local f_single = formatters["%04X"]
 local f_double = formatters["%04X%04X"]
 
+-- 0.684 0.661 0,672 0.650 : cache at lua end (more mem)
+-- 0.682 0,672 0.698 0.657 : no cache (moderate mem i.e. lua strings)
+-- 0.644 0.647 0.655 0.645 : convert in c (less mem in theory)
+
+-- local tounicodes = table.setmetatableindex(function(t,unicode)
+--     local s
+--     if unicode < 0x10000 then
+--         s = f_single(unicode)
+--     elseif unicode < 0x1FFFFFFFFF then
+--         s = f_double(floor(unicode/1024),unicode%1024+0xDC00)
+--     else
+--         s = false
+--     end
+--     t[unicode] = s
+--     return s
+-- end)
+--
+-- local function tounicode16(unicode,name)
+--     local s = tounicodes[unicode]
+--     if s then
+--         return s
+--     else
+--         report_fonts("can't convert %a in %a into tounicode",unicode,name)
+--     end
+-- end
+--
+-- local function tounicode16sequence(unicodes,name)
+--     local t = { }
+--     for l=1,#unicodes do
+--         local u = unicodes[l]
+--         local s = tounicodes[u]
+--         if s then
+--             t[l] = s
+--         else
+--             report_fonts ("can't convert %a in %a into tounicode",u,name)
+--             return
+--         end
+--     end
+--     return concat(t)
+-- end
+--
+-- local function tounicode(unicode,name)
+--     if type(unicode) == "table" then
+--         local t = { }
+--         for l=1,#unicode do
+--             local u = unicode[l]
+--             local s = tounicodes[u]
+--             if s then
+--                 t[l] = s
+--             else
+--                 report_fonts ("can't convert %a in %a into tounicode",u,name)
+--                 return
+--             end
+--         end
+--         return concat(t)
+--     else
+--         local s = tounicodes[unicode]
+--         if s then
+--             return s
+--         else
+--             report_fonts("can't convert %a in %a into tounicode",unicode,name)
+--         end
+--     end
+-- end
+
 local function tounicode16(unicode,name)
     if unicode < 0x10000 then
         return f_single(unicode)
@@ -126,7 +191,6 @@ local function tounicode(unicode,name)
     end
 end
 
-
 local function fromunicode16(str)
     if #str == 4 then
         return tonumber(str,16)
diff --git a/src/fontloader/misc/fontloader-font-otf.lua b/src/fontloader/misc/fontloader-font-otf.lua
index f709e70..86b1313 100644
--- a/src/fontloader/misc/fontloader-font-otf.lua
+++ b/src/fontloader/misc/fontloader-font-otf.lua
@@ -58,7 +58,7 @@ local otf                = fonts.handlers.otf
 
 otf.glists               = { "gsub", "gpos" }
 
-otf.version              = 2.819 -- beware: also sync font-mis.lua and in mtx-fonts
+otf.version              = 2.820 -- beware: also sync font-mis.lua and in mtx-fonts
 otf.cache                = containers.define("fonts", "otf", otf.version, true)
 
 local hashes             = fonts.hashes
@@ -2959,7 +2959,7 @@ otf.coverup = {
         kern         = justset,
     },
     register = function(coverage,lookuptype,format,feature,n,descriptions,resources)
-        local name = formatters["ctx_%s_%s"](feature,n)
+        local name = formatters["ctx_%s_%s_%s"](feature,lookuptype,n) -- we can have a mix of types
         if lookuptype == "kern" then
             resources.lookuptypes[name] = "position"
         else
@@ -2973,7 +2973,6 @@ otf.coverup = {
             else
                 description.slookups = { [name] = c }
             end
--- inspect(feature,description)
         end
         return name
     end
diff --git a/src/fontloader/misc/fontloader-font-oti.lua b/src/fontloader/misc/fontloader-font-oti.lua
index 06c2a42..bacd001 100644
--- a/src/fontloader/misc/fontloader-font-oti.lua
+++ b/src/fontloader/misc/fontloader-font-oti.lua
@@ -13,9 +13,11 @@ local constructors       = fonts.constructors
 
 local otf                = constructors.newhandler("otf")
 local otffeatures        = constructors.newfeatures("otf")
-local otftables          = otf.tables
 local registerotffeature = otffeatures.register
 
+local otftables          = otf.tables or { }
+otf.tables               = otftables
+
 local allocate           = utilities.storage.allocate
 
 registerotffeature {
@@ -89,3 +91,68 @@ registerotffeature {
     }
 }
 
+-- here (as also in generic
+
+otftables.featuretypes = allocate {
+    gpos_single              = "position",
+    gpos_pair                = "position",
+    gpos_cursive             = "position",
+    gpos_mark2base           = "position",
+    gpos_mark2ligature       = "position",
+    gpos_mark2mark           = "position",
+    gpos_context             = "position",
+    gpos_contextchain        = "position",
+    gsub_single              = "substitution",
+    gsub_multiple            = "substitution",
+    gsub_alternate           = "substitution",
+    gsub_ligature            = "substitution",
+    gsub_context             = "substitution",
+    gsub_contextchain        = "substitution",
+    gsub_reversecontextchain = "substitution",
+    gsub_reversesub          = "substitution",
+}
+
+function otffeatures.checkeddefaultscript(featuretype,autoscript,scripts)
+    if featuretype == "position" then
+        local default = scripts.dflt
+        if default then
+            if autoscript == "position" or autoscript == true then
+                return default
+            else
+                report_otf("script feature %s not applied, enable default positioning")
+            end
+        else
+            -- no positioning at all
+        end
+    elseif featuretype == "substitution" then
+        local default = scripts.dflt
+        if default then
+            if autoscript == "substitution" or autoscript == true then
+                return default
+            end
+        end
+    end
+end
+
+function otffeatures.checkeddefaultlanguage(featuretype,autolanguage,languages)
+    if featuretype == "position" then
+        local default = languages.dflt
+        if default then
+            if autolanguage == "position" or autolanguage == true then
+                return default
+            else
+                report_otf("language feature %s not applied, enable default positioning")
+            end
+        else
+            -- no positioning at all
+        end
+    elseif featuretype == "substitution" then
+        local default = languages.dflt
+        if default then
+            if autolanguage == "substitution" or autolanguage == true then
+                return default
+            end
+        end
+    end
+end
+
diff --git a/src/fontloader/misc/fontloader-font-otn.lua b/src/fontloader/misc/fontloader-font-otn.lua
new file mode 100644
index 0000000..8df01bd
--- /dev/null
+++ b/src/fontloader/misc/fontloader-font-otn.lua
@@ -0,0 +1,3958 @@
+if not modules then modules = { } end modules ['font-otn'] = {
+    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",
+}
+
+-- this is a context version which can contain experimental code, but when we
+-- have serious patches we also need to change the other two font-otn files
+
+-- at some point i might decide to convert the whole list into a table and then
+-- run over that instead (but it has some drawbacks as we also need to deal with
+-- attributes and such so we need to keep a lot of track - which is why i rejected
+-- that method - although it has become a bit easier in the meantime so it might
+-- become an alternative (by that time i probably have gone completely lua) .. the
+-- usual chicken-egg issues ... maybe mkix as it's no real tex any more then
+
+-- preprocessors = { "nodes" }
+
+-- anchor class : mark, mkmk, curs, mklg (todo)
+-- anchor type  : mark, basechar, baselig, basemark, centry, cexit, max (todo)
+
+-- this is still somewhat preliminary and it will get better in due time;
+-- much functionality could only be implemented thanks to the husayni font
+-- of Idris Samawi Hamid to who we dedicate this module.
+
+-- in retrospect it always looks easy but believe it or not, it took a lot
+-- of work to get proper open type support done: buggy fonts, fuzzy specs,
+-- special made testfonts, many skype sessions between taco, idris and me,
+-- torture tests etc etc ... unfortunately the code does not show how much
+-- time it took ...
+
+-- todo:
+--
+-- extension infrastructure (for usage out of context)
+-- sorting features according to vendors/renderers
+-- alternative loop quitters
+-- check cursive and r2l
+-- find out where ignore-mark-classes went
+-- default features (per language, script)
+-- handle positions (we need example fonts)
+-- handle gpos_single (we might want an extra width field in glyph nodes because adding kerns might interfere)
+-- mark (to mark) code is still not what it should be (too messy but we need some more extreem husayni tests)
+-- remove some optimizations (when I have a faster machine)
+--
+-- beware:
+--
+-- we do some disc jugling where we need to keep in mind that the
+-- pre, post and replace fields can have prev pointers to a nesting
+-- node ... i wonder if that is still needed
+--
+-- not possible:
+--
+-- \discretionary {alpha-} {betagammadelta}
+--   {\discretionary {alphabeta-} {gammadelta}
+--      {\discretionary {alphabetagamma-} {delta}
+--         {alphabetagammadelta}}}
+
+--[[ldx--
+<p>This module is a bit more split up that I'd like but since we also want to test
+with plain <l n='tex'/> it has to be so. This module is part of <l n='context'/>
+and discussion about improvements and functionality mostly happens on the
+<l n='context'/> mailing list.</p>
+
+<p>The specification of OpenType is kind of vague. Apart from a lack of a proper
+free specifications there's also the problem that Microsoft and Adobe
+may have their own interpretation of how and in what order to apply features.
+In general the Microsoft website has more detailed specifications and is a
+better reference. There is also some information in the FontForge help files.</p>
+
+<p>Because there is so much possible, fonts might contain bugs and/or be made to
+work with certain rederers. These may evolve over time which may have the side
+effect that suddenly fonts behave differently.</p>
+
+<p>After a lot of experiments (mostly by Taco, me and Idris) we're now at yet another
+implementation. Of course all errors are mine and of course the code can be
+improved. There are quite some optimizations going on here and processing speed
+is currently acceptable. Not all functions are implemented yet, often because I
+lack the fonts for testing. Many scripts are not yet supported either, but I will
+look into them as soon as <l n='context'/> users ask for it.</p>
+
+<p>The specification leaves room for interpretation. In case of doubt the microsoft
+implementation is the reference as it is the most complete one. As they deal with
+lots of scripts and fonts, Kai and Ivo did a lot of testing of the generic code and
+their suggestions help improve the code. I'm aware that not all border cases can be
+taken care of, unless we accept excessive runtime, and even then the interference
+with other mechanisms (like hyphenation) are not trivial.</p>
+
+<p>Glyphs are indexed not by unicode but in their own way. This is because there is no
+relationship with unicode at all, apart from the fact that a font might cover certain
+ranges of characters. One character can have multiple shapes. However, at the
+<l n='tex'/> end we use unicode so and all extra glyphs are mapped into a private
+space. This is needed because we need to access them and <l n='tex'/> has to include
+then in the output eventually.</p>
+
+<p>The raw table as it coms from <l n='fontforge'/> gets reorganized in to fit out needs.
+In <l n='context'/> that table is packed (similar tables are shared) and cached on disk
+so that successive runs can use the optimized table (after loading the table is
+unpacked). The flattening code used later is a prelude to an even more compact table
+format (and as such it keeps evolving).</p>
+
+<p>This module is sparsely documented because it is a moving target. The table format
+of the reader changes and we experiment a lot with different methods for supporting
+features.</p>
+
+<p>As with the <l n='afm'/> code, we may decide to store more information in the
+<l n='otf'/> table.</p>
+
+<p>Incrementing the version number will force a re-cache. We jump the number by one
+when there's a fix in the <l n='fontforge'/> library or <l n='lua'/> code that
+results in different tables.</p>
+--ldx]]--
+
+-- action                    handler     chainproc
+--
+-- gsub_single               ok          ok
+-- gsub_multiple             ok          ok
+-- gsub_alternate            ok          ok
+-- gsub_ligature             ok          ok
+-- gsub_context              ok          --
+-- gsub_contextchain         ok          --
+-- gsub_reversecontextchain  ok          --
+-- chainsub                  --          ok
+-- reversesub                --          ok
+-- gpos_mark2base            ok          ok
+-- gpos_mark2ligature        ok          ok
+-- gpos_mark2mark            ok          ok
+-- gpos_cursive              ok          untested
+-- gpos_single               ok          ok
+-- gpos_pair                 ok          ok
+-- gpos_context              ok          --
+-- gpos_contextchain         ok          --
+--
+-- todo: contextpos
+--
+-- actions:
+--
+-- handler   : actions triggered by lookup
+-- chainproc : actions triggered by contextual lookup
+-- chainmore : multiple substitutions triggered by contextual lookup (e.g. fij -> f + ij)
+--
+-- remark: the 'not implemented yet' variants will be done when we have fonts that use them
+
+-- We used to have independent hashes for lookups but as the tags are unique
+-- we now use only one hash. If needed we can have multiple again but in that
+-- case I will probably prefix (i.e. rename) the lookups in the cached font file.
+
+-- Todo: make plugin feature that operates on char/glyphnode arrays
+
+local type, next, tonumber = type, next, tonumber
+local random = math.random
+local formatters = string.formatters
+
+local logs, trackers, nodes, attributes = logs, trackers, nodes, attributes
+
+local registertracker   = trackers.register
+local registerdirective = directives.register
+
+local fonts = fonts
+local otf   = fonts.handlers.otf
+
+local trace_lookups      = false  registertracker("otf.lookups",      function(v) trace_lookups      = v end)
+local trace_singles      = false  registertracker("otf.singles",      function(v) trace_singles      = v end)
+local trace_multiples    = false  registertracker("otf.multiples",    function(v) trace_multiples    = v end)
+local trace_alternatives = false  registertracker("otf.alternatives", function(v) trace_alternatives = v end)
+local trace_ligatures    = false  registertracker("otf.ligatures",    function(v) trace_ligatures    = v end)
+local trace_contexts     = false  registertracker("otf.contexts",     function(v) trace_contexts     = v end)
+local trace_marks        = false  registertracker("otf.marks",        function(v) trace_marks        = v end)
+local trace_kerns        = false  registertracker("otf.kerns",        function(v) trace_kerns        = v end)
+local trace_cursive      = false  registertracker("otf.cursive",      function(v) trace_cursive      = v end)
+local trace_preparing    = false  registertracker("otf.preparing",    function(v) trace_preparing    = v end)
+local trace_bugs         = false  registertracker("otf.bugs",         function(v) trace_bugs         = v end)
+local trace_details      = false  registertracker("otf.details",      function(v) trace_details      = v end)
+local trace_applied      = false  registertracker("otf.applied",      function(v) trace_applied      = v end)
+local trace_steps        = false  registertracker("otf.steps",        function(v) trace_steps        = v end)
+local trace_skips        = false  registertracker("otf.skips",        function(v) trace_skips        = v end)
+local trace_directions   = false  registertracker("otf.directions",   function(v) trace_directions   = v end)
+
+local trace_kernruns     = false  registertracker("otf.kernruns",     function(v) trace_kernruns     = v end)
+local trace_discruns     = false  registertracker("otf.discruns",     function(v) trace_discruns     = v end)
+local trace_compruns     = false  registertracker("otf.compruns",     function(v) trace_compruns     = v end)
+
+local quit_on_no_replacement = true  -- maybe per font
+local zwnjruns               = true
+
+registerdirective("otf.zwnjruns",                 function(v) zwnjruns = v end)
+registerdirective("otf.chain.quitonnoreplacement",function(value) quit_on_no_replacement = value end)
+
+local report_direct   = logs.reporter("fonts","otf direct")
+local report_subchain = logs.reporter("fonts","otf subchain")
+local report_chain    = logs.reporter("fonts","otf chain")
+local report_process  = logs.reporter("fonts","otf process")
+local report_prepare  = logs.reporter("fonts","otf prepare")
+local report_warning  = logs.reporter("fonts","otf warning")
+local report_run      = logs.reporter("fonts","otf run")
+
+registertracker("otf.verbose_chain", function(v) otf.setcontextchain(v and "verbose") end)
+registertracker("otf.normal_chain",  function(v) otf.setcontextchain(v and "normal")  end)
+
+registertracker("otf.replacements", "otf.singles,otf.multiples,otf.alternatives,otf.ligatures")
+registertracker("otf.positions","otf.marks,otf.kerns,otf.cursive")
+registertracker("otf.actions","otf.replacements,otf.positions")
+registertracker("otf.injections","nodes.injections")
+
+registertracker("*otf.sample","otf.steps,otf.actions,otf.analyzing")
+
+local nuts               = nodes.nuts
+local tonode             = nuts.tonode
+local tonut              = nuts.tonut
+
+local getfield           = nuts.getfield
+local setfield           = nuts.setfield
+local getnext            = nuts.getnext
+local setnext            = nuts.setnext
+local getprev            = nuts.getprev
+local setprev            = nuts.setprev
+local getid              = nuts.getid
+local getattr            = nuts.getattr
+local setattr            = nuts.setattr
+local getprop            = nuts.getprop
+local setprop            = nuts.setprop
+local getfont            = nuts.getfont
+local getsubtype         = nuts.getsubtype
+local setsubtype         = nuts.setsubtype
+local getchar            = nuts.getchar
+local setchar            = nuts.setchar
+
+local insert_node_before = nuts.insert_before
+local insert_node_after  = nuts.insert_after
+local delete_node        = nuts.delete
+local remove_node        = nuts.remove
+local copy_node          = nuts.copy
+local copy_node_list     = nuts.copy_list
+local find_node_tail     = nuts.tail
+local flush_node_list    = nuts.flush_list
+local free_node          = nuts.free
+local end_of_math        = nuts.end_of_math
+local traverse_nodes     = nuts.traverse
+local traverse_id        = nuts.traverse_id
+
+local setmetatableindex  = table.setmetatableindex
+
+local zwnj               = 0x200C
+local zwj                = 0x200D
+local wildcard           = "*"
+local default            = "dflt"
+
+local nodecodes          = nodes.nodecodes
+local glyphcodes         = nodes.glyphcodes
+local disccodes          = nodes.disccodes
+
+local glyph_code         = nodecodes.glyph
+local glue_code          = nodecodes.glue
+local disc_code          = nodecodes.disc
+local math_code          = nodecodes.math
+local dir_code           = nodecodes.dir
+local localpar_code      = nodecodes.localpar
+
+local discretionary_code = disccodes.discretionary
+local ligature_code      = glyphcodes.ligature
+
+local privateattribute   = attributes.private
+
+-- Something is messed up: we have two mark / ligature indices, one at the injection
+-- end and one here ... this is based on KE's patches but there is something fishy
+-- there as I'm pretty sure that for husayni we need some connection (as it's much
+-- more complex than an average font) but I need proper examples of all cases, not
+-- of only some.
+
+local a_state            = privateattribute('state')
+local a_cursbase         = privateattribute('cursbase') -- to be checked, probably can go
+
+local injections         = nodes.injections
+local setmark            = injections.setmark
+local setcursive         = injections.setcursive
+local setkern            = injections.setkern
+local setpair            = injections.setpair
+local resetinjection     = injections.reset
+local copyinjection      = injections.copy
+local setligaindex       = injections.setligaindex
+local getligaindex       = injections.getligaindex
+
+local cursonce           = true
+
+local fonthashes         = fonts.hashes
+local fontdata           = fonthashes.identifiers
+
+local otffeatures        = fonts.constructors.newfeatures("otf")
+local registerotffeature = otffeatures.register
+
+local onetimemessage     = fonts.loggers.onetimemessage or function() end
+
+otf.defaultnodealternate = "none" -- first last
+
+-- we share some vars here, after all, we have no nested lookups and less code
+
+local tfmdata             = false
+local characters          = false
+local descriptions        = false
+local resources           = false
+local marks               = false
+local currentfont         = false
+local lookuptable         = false
+local anchorlookups       = false
+local lookuptypes         = false
+local lookuptags          = false
+local handlers            = { }
+local rlmode              = 0
+local featurevalue        = false
+
+local sweephead           = { }
+local sweepnode           = nil
+local sweepprev           = nil
+local sweepnext           = nil
+
+local notmatchpre         = { }
+local notmatchpost        = { }
+local notmatchreplace     = { }
+
+-- we use this for special testing and documentation
+
+local checkstep       = (nodes and nodes.tracers and nodes.tracers.steppers.check)    or function() end
+local registerstep    = (nodes and nodes.tracers and nodes.tracers.steppers.register) or function() end
+local registermessage = (nodes and nodes.tracers and nodes.tracers.steppers.message)  or function() end
+
+local function logprocess(...)
+    if trace_steps then
+        registermessage(...)
+    end
+    report_direct(...)
+end
+
+local function logwarning(...)
+    report_direct(...)
+end
+
+local f_unicode = formatters["%U"]
+local f_uniname = formatters["%U (%s)"]
+local f_unilist = formatters["% t (% t)"]
+
+local function gref(n) -- currently the same as in font-otb
+    if type(n) == "number" then
+        local description = descriptions[n]
+        local name = description and description.name
+        if name then
+            return f_uniname(n,name)
+        else
+            return f_unicode(n)
+        end
+    elseif n then
+        local num, nam = { }, { }
+        for i=1,#n do
+            local ni = n[i]
+            if tonumber(ni) then -- later we will start at 2
+                local di = descriptions[ni]
+                num[i] = f_unicode(ni)
+                nam[i] = di and di.name or "-"
+            end
+        end
+        return f_unilist(num,nam)
+    else
+        return "<error in node mode tracing>"
+    end
+end
+
+local function cref(kind,chainname,chainlookupname,lookupname,index) -- not in the mood to alias f_
+    if index then
+        return formatters["feature %a, chain %a, sub %a, lookup %a, index %a"](kind,chainname,chainlookupname,lookuptags[lookupname],index)
+    elseif lookupname then
+        return formatters["feature %a, chain %a, sub %a, lookup %a"](kind,chainname,chainlookupname,lookuptags[lookupname])
+    elseif chainlookupname then
+        return formatters["feature %a, chain %a, sub %a"](kind,lookuptags[chainname],lookuptags[chainlookupname])
+    elseif chainname then
+        return formatters["feature %a, chain %a"](kind,lookuptags[chainname])
+    else
+        return formatters["feature %a"](kind)
+    end
+end
+
+local function pref(kind,lookupname)
+    return formatters["feature %a, lookup %a"](kind,lookuptags[lookupname])
+end
+
+-- We can assume that languages that use marks are not hyphenated. We can also assume
+-- that at most one discretionary is present.
+
+-- We do need components in funny kerning mode but maybe I can better reconstruct then
+-- as we do have the font components info available; removing components makes the
+-- previous code much simpler. Also, later on copying and freeing becomes easier.
+-- However, for arabic we need to keep them around for the sake of mark placement
+-- and indices.
+
+local function copy_glyph(g) -- next and prev are untouched !
+    local components = getfield(g,"components")
+    if components then
+        setfield(g,"components",nil)
+        local n = copy_node(g)
+        copyinjection(n,g) -- we need to preserve the lig indices
+        setfield(g,"components",components)
+        return n
+    else
+        local n = copy_node(g)
+        copyinjection(n,g) -- we need to preserve the lig indices
+        return n
+    end
+end
+
+local function flattendisk(head,disc)
+    local replace = getfield(disc,"replace")
+    setfield(disc,"replace",nil)
+    free_node(disc)
+    if head == disc then
+        local next = getnext(disc)
+        if replace then
+            if next then
+                local tail = find_node_tail(replace)
+                setnext(tail,next)
+                setprev(next,tail)
+            end
+            return replace, replace
+        elseif next then
+            return next, next
+        else
+            return -- maybe warning
+        end
+    else
+        local next = getnext(disc)
+        local prev = getprev(disc)
+        if replace then
+            local tail = find_node_tail(replace)
+            if next then
+                setnext(tail,next)
+                setprev(next,tail)
+            end
+            setnext(prev,replace)
+            setprev(replace,prev)
+            return head, replace
+        else
+            if next then
+                setprev(next,prev)
+            end
+            setnext(prev,next)
+            return head, next
+        end
+    end
+end
+
+local function appenddisc(disc,list)
+    local post    = getfield(disc,"post")
+    local replace = getfield(disc,"replace")
+    local phead   = list
+    local rhead   = copy_node_list(list)
+    local ptail   = find_node_tail(post)
+    local rtail   = find_node_tail(replace)
+    if post then
+        setnext(ptail,phead)
+        setprev(phead,ptail)
+    else
+        setfield(disc,"post",phead)
+    end
+    if replace then
+        setnext(rtail,rhead)
+        setprev(rhead,rtail)
+    else
+        setfield(disc,"replace",rhead)
+    end
+end
+
+-- start is a mark and we need to keep that one
+
+local function markstoligature(kind,lookupname,head,start,stop,char)
+    if start == stop and getchar(start) == char then
+        return head, start
+    else
+        local prev = getprev(start)
+        local next = getnext(stop)
+        setprev(start,nil)
+        setnext(stop,nil)
+        local base = copy_glyph(start)
+        if head == start then
+            head = base
+        end
+        resetinjection(base)
+        setchar(base,char)
+        setsubtype(base,ligature_code)
+        setfield(base,"components",start)
+        if prev then
+            setnext(prev,base)
+        end
+        if next then
+            setprev(next,base)
+        end
+        setnext(base,next)
+        setprev(base,prev)
+        return head, base
+    end
+end
+
+-- The next code is somewhat complicated by the fact that some fonts can have ligatures made
+-- from ligatures that themselves have marks. This was identified by Kai in for instance
+-- arabtype:  KAF LAM SHADDA ALEF FATHA (0x0643 0x0644 0x0651 0x0627 0x064E). This becomes
+-- KAF LAM-ALEF with a SHADDA on the first and a FATHA op de second component. In a next
+-- iteration this becomes a KAF-LAM-ALEF with a SHADDA on the second and a FATHA on the
+-- third component.
+
+local function getcomponentindex(start) -- we could store this offset in the glyph (nofcomponents)
+    if getid(start) ~= glyph_code then  -- and then get rid of all components
+        return 0
+    elseif getsubtype(start) == ligature_code then
+        local i = 0
+        local components = getfield(start,"components")
+        while components do
+            i = i + getcomponentindex(components)
+            components = getnext(components)
+        end
+        return i
+    elseif not marks[getchar(start)] then
+        return 1
+    else
+        return 0
+    end
+end
+
+local a_noligature = attributes.private("noligature")
+
+local function toligature(kind,lookupname,head,start,stop,char,markflag,discfound) -- brr head
+    if getattr(start,a_noligature) == 1 then
+        -- so we can do: e\noligature{ff}e e\noligature{f}fie (we only look at the first)
+        return head, start
+    end
+    if start == stop and getchar(start) == char then
+        resetinjection(start)
+        setchar(start,char)
+        return head, start
+    end
+    -- needs testing (side effects):
+    local components = getfield(start,"components")
+    if components then
+     -- we get a double free .. needs checking
+     -- flush_node_list(components)
+    end
+    --
+    local prev = getprev(start)
+    local next = getnext(stop)
+    local comp = start
+    setprev(start,nil)
+    setnext(stop,nil)
+    local base = copy_glyph(start)
+    if start == head then
+        head = base
+    end
+    resetinjection(base)
+    setchar(base,char)
+    setsubtype(base,ligature_code)
+    setfield(base,"components",comp) -- start can have components ... do we need to flush?
+    if prev then
+        setnext(prev,base)
+    end
+    if next then
+        setprev(next,base)
+    end
+    setprev(base,prev)
+    setnext(base,next)
+    if not discfound then
+        local deletemarks = markflag ~= "mark"
+        local components = start
+        local baseindex = 0
+        local componentindex = 0
+        local head = base
+        local current = base
+        -- first we loop over the glyphs in start .. stop
+        while start do
+            local char = getchar(start)
+            if not marks[char] then
+                baseindex = baseindex + componentindex
+                componentindex = getcomponentindex(start)
+            elseif not deletemarks then -- quite fishy
+                setligaindex(start,baseindex + getligaindex(start,componentindex))
+                if trace_marks then
+                    logwarning("%s: keep mark %s, gets index %s",pref(kind,lookupname),gref(char),getligaindex(start))
+                end
+                local n = copy_node(start)
+                copyinjection(n,start)
+                head, current = insert_node_after(head,current,n) -- unlikely that mark has components
+            elseif trace_marks then
+                logwarning("%s: delete mark %s",pref(kind,lookupname),gref(char))
+            end
+            start = getnext(start)
+        end
+        -- we can have one accent as part of a lookup and another following
+     -- local start = components -- was wrong (component scanning was introduced when more complex ligs in devanagari was added)
+        local start = getnext(current)
+        while start and getid(start) == glyph_code do
+            local char = getchar(start)
+            if marks[char] then
+                setligaindex(start,baseindex + getligaindex(start,componentindex))
+                if trace_marks then
+                    logwarning("%s: set mark %s, gets index %s",pref(kind,lookupname),gref(char),getligaindex(start))
+                end
+            else
+                break
+            end
+            start = getnext(start)
+        end
+    else
+        -- discfound ... forget about marks .. probably no scripts that hyphenate and have marks
+        local discprev = getprev(discfound)
+        local discnext = getnext(discfound)
+        if discprev and discnext then
+            -- we assume normalization in context, and don't care about generic ... especially
+            -- \- can give problems as there we can have a negative char but that won't match
+            -- anyway
+            local pre     = getfield(discfound,"pre")
+            local post    = getfield(discfound,"post")
+            local replace = getfield(discfound,"replace")
+            if not replace then -- todo: signal simple hyphen
+                local prev = getprev(base)
+                local copied = copy_node_list(comp)
+                setprev(discnext,nil) -- also blocks funny assignments
+                setnext(discprev,nil) -- also blocks funny assignments
+                if pre then
+                    setnext(discprev,pre)
+                    setprev(pre,discprev)
+                end
+                pre = comp
+                if post then
+                    local tail = find_node_tail(post)
+                    setnext(tail,discnext)
+                    setprev(discnext,tail)
+                    setprev(post,nil)
+                else
+                    post = discnext
+                end
+                setnext(prev,discfound)
+                setprev(discfound,prev)
+                setnext(discfound,next)
+                setprev(next,discfound)
+                setnext(base,nil)
+                setprev(base,nil)
+                setfield(base,"components",copied)
+                setfield(discfound,"pre",pre)
+                setfield(discfound,"post",post)
+                setfield(discfound,"replace",base)
+                setsubtype(discfound,discretionary_code)
+                base = prev -- restart
+            end
+        end
+    end
+    return head, base
+end
+
+local function multiple_glyphs(head,start,multiple,ignoremarks)
+    local nofmultiples = #multiple
+    if nofmultiples > 0 then
+        resetinjection(start)
+        setchar(start,multiple[1])
+        if nofmultiples > 1 then
+            local sn = getnext(start)
+            for k=2,nofmultiples do -- todo: use insert_node
+-- untested:
+--
+-- while ignoremarks and marks[getchar(sn)] then
+--     local sn = getnext(sn)
+-- end
+                local n = copy_node(start) -- ignore components
+                resetinjection(n)
+                setchar(n,multiple[k])
+                setprev(n,start)
+                setnext(n,sn)
+                if sn then
+                    setprev(sn,n)
+                end
+                setnext(start,n)
+                start = n
+            end
+        end
+        return head, start, true
+    else
+        if trace_multiples then
+            logprocess("no multiple for %s",gref(getchar(start)))
+        end
+        return head, start, false
+    end
+end
+
+local function get_alternative_glyph(start,alternatives,value,trace_alternatives)
+    local n = #alternatives
+    if value == "random" then
+        local r = random(1,n)
+        return alternatives[r], trace_alternatives and formatters["value %a, taking %a"](value,r)
+    elseif value == "first" then
+        return alternatives[1], trace_alternatives and formatters["value %a, taking %a"](value,1)
+    elseif value == "last" then
+        return alternatives[n], trace_alternatives and formatters["value %a, taking %a"](value,n)
+    else
+        value = tonumber(value)
+        if type(value) ~= "number" then
+            return alternatives[1], trace_alternatives and formatters["invalid value %s, taking %a"](value,1)
+        elseif value > n then
+            local defaultalt = otf.defaultnodealternate
+            if defaultalt == "first" then
+                return alternatives[n], trace_alternatives and formatters["invalid value %s, taking %a"](value,1)
+            elseif defaultalt == "last" then
+                return alternatives[1], trace_alternatives and formatters["invalid value %s, taking %a"](value,n)
+            else
+                return false, trace_alternatives and formatters["invalid value %a, %s"](value,"out of range")
+            end
+        elseif value == 0 then
+            return getchar(start), trace_alternatives and formatters["invalid value %a, %s"](value,"no change")
+        elseif value < 1 then
+            return alternatives[1], trace_alternatives and formatters["invalid value %a, taking %a"](value,1)
+        else
+            return alternatives[value], trace_alternatives and formatters["value %a, taking %a"](value,value)
+        end
+    end
+end
+
+-- handlers
+
+function handlers.gsub_single(head,start,kind,lookupname,replacement)
+    if trace_singles then
+        logprocess("%s: replacing %s by single %s",pref(kind,lookupname),gref(getchar(start)),gref(replacement))
+    end
+    resetinjection(start)
+    setchar(start,replacement)
+    return head, start, true
+end
+
+function handlers.gsub_alternate(head,start,kind,lookupname,alternative,sequence)
+    local value = featurevalue == true and tfmdata.shared.features[kind] or featurevalue
+    local choice, comment = get_alternative_glyph(start,alternative,value,trace_alternatives)
+    if choice then
+        if trace_alternatives then
+            logprocess("%s: replacing %s by alternative %a to %s, %s",pref(kind,lookupname),gref(getchar(start)),choice,gref(choice),comment)
+        end
+        resetinjection(start)
+        setchar(start,choice)
+    else
+        if trace_alternatives then
+            logwarning("%s: no variant %a for %s, %s",pref(kind,lookupname),value,gref(getchar(start)),comment)
+        end
+    end
+    return head, start, true
+end
+
+function handlers.gsub_multiple(head,start,kind,lookupname,multiple,sequence)
+    if trace_multiples then
+        logprocess("%s: replacing %s by multiple %s",pref(kind,lookupname),gref(getchar(start)),gref(multiple))
+    end
+    return multiple_glyphs(head,start,multiple,sequence.flags[1])
+end
+
+function handlers.gsub_ligature(head,start,kind,lookupname,ligature,sequence)
+    local s, stop = getnext(start), nil
+    local startchar = getchar(start)
+    if marks[startchar] then
+        while s do
+            local id = getid(s)
+            if id == glyph_code and getfont(s) == currentfont and getsubtype(s)<256 then
+                local lg = ligature[getchar(s)]
+                if lg then
+                    stop = s
+                    ligature = lg
+                    s = getnext(s)
+                else
+                    break
+                end
+            else
+                break
+            end
+        end
+        if stop then
+            local lig = ligature.ligature
+            if lig then
+                if trace_ligatures then
+                    local stopchar = getchar(stop)
+                    head, start = markstoligature(kind,lookupname,head,start,stop,lig)
+                    logprocess("%s: replacing %s upto %s by ligature %s case 1",pref(kind,lookupname),gref(startchar),gref(stopchar),gref(getchar(start)))
+                else
+                    head, start = markstoligature(kind,lookupname,head,start,stop,lig)
+                end
+                return head, start, true, false
+            else
+                -- ok, goto next lookup
+            end
+        end
+    else
+        local skipmark  = sequence.flags[1]
+        local discfound = false
+        local lastdisc  = nil
+        while s do
+            local id = getid(s)
+            if id == glyph_code and getsubtype(s)<256 then -- not needed
+                if getfont(s) == currentfont then          -- also not needed only when mark
+                    local char = getchar(s)
+                    if skipmark and marks[char] then
+                        s = getnext(s)
+                    else -- ligature is a tree
+                        local lg = ligature[char] -- can there be multiple in a row? maybe in a bad font
+                        if lg then
+                            if not discfound and lastdisc then
+                                discfound = lastdisc
+                                lastdisc  = nil
+                            end
+                            stop = s -- needed for fake so outside then
+                            ligature = lg
+                            s = getnext(s)
+                        else
+                            break
+                        end
+                    end
+                else
+                    break
+                end
+            elseif id == disc_code then
+                lastdisc = s
+                s = getnext(s)
+            else
+                break
+            end
+        end
+        local lig = ligature.ligature -- can't we get rid of this .ligature?
+        if lig then
+            if stop then
+                if trace_ligatures then
+                    local stopchar = getchar(stop)
+                    head, start = toligature(kind,lookupname,head,start,stop,lig,skipmark,discfound)
+                    logprocess("%s: replacing %s upto %s by ligature %s case 2",pref(kind,lookupname),gref(startchar),gref(stopchar),gref(getchar(start)))
+                else
+                    head, start = toligature(kind,lookupname,head,start,stop,lig,skipmark,discfound)
+                end
+            else
+                -- weird but happens (in some arabic font)
+                resetinjection(start)
+                setchar(start,lig)
+                if trace_ligatures then
+                    logprocess("%s: replacing %s by (no real) ligature %s case 3",pref(kind,lookupname),gref(startchar),gref(lig))
+                end
+            end
+            return head, start, true, discfound
+        else
+            -- weird but happens, pseudo ligatures ... just the components
+        end
+    end
+    return head, start, false, discfound
+end
+
+function handlers.gpos_single(head,start,kind,lookupname,kerns,sequence,injection)
+    local startchar = getchar(start)
+    local dx, dy, w, h = setpair(start,tfmdata.parameters.factor,rlmode,sequence.flags[4],kerns,injection) -- ,characters[startchar])
+    if trace_kerns then
+        logprocess("%s: shifting single %s by (%p,%p) and correction (%p,%p)",pref(kind,lookupname),gref(startchar),dx,dy,w,h)
+    end
+    return head, start, false
+end
+
+function handlers.gpos_pair(head,start,kind,lookupname,kerns,sequence,lookuphash,i,injection)
+    -- todo: kerns in disc nodes: pre, post, replace -> loop over disc too
+    -- todo: kerns in components of ligatures
+    local snext = getnext(start)
+    if not snext then
+        return head, start, false
+    else
+        local prev   = start
+        local done   = false
+        local factor = tfmdata.parameters.factor
+        local lookuptype = lookuptypes[lookupname]
+        while snext and getid(snext) == glyph_code and getfont(snext) == currentfont and getsubtype(snext)<256 do
+            local nextchar = getchar(snext)
+            local krn = kerns[nextchar]
+            if not krn and marks[nextchar] then
+                prev = snext
+                snext = getnext(snext)
+            else
+                if not krn then
+                    -- skip
+                elseif type(krn) == "table" then
+                    if lookuptype == "pair" then -- probably not needed
+                        local a, b = krn[2], krn[3]
+                        if a and #a > 0 then
+                            local x, y, w, h = setpair(start,factor,rlmode,sequence.flags[4],a,injection) -- characters[startchar])
+                            if trace_kerns then
+                                local startchar = getchar(start)
+                                logprocess("%s: shifting first of pair %s and %s by (%p,%p) and correction (%p,%p)",pref(kind,lookupname),gref(startchar),gref(nextchar),x,y,w,h)
+                            end
+                        end
+                        if b and #b > 0 then
+                            local x, y, w, h = setpair(snext,factor,rlmode,sequence.flags[4],b,injection) -- characters[nextchar])
+                            if trace_kerns then
+                                local startchar = getchar(start)
+                                logprocess("%s: shifting second of pair %s and %s by (%p,%p) and correction (%p,%p)",pref(kind,lookupname),gref(startchar),gref(nextchar),x,y,w,h)
+                            end
+                        end
+                    else -- wrong ... position has different entries
+                        report_process("%s: check this out (old kern stuff)",pref(kind,lookupname))
+                     -- local a, b = krn[2], krn[6]
+                     -- if a and a ~= 0 then
+                     --     local k = setkern(snext,factor,rlmode,a)
+                     --     if trace_kerns then
+                     --         logprocess("%s: inserting first kern %s between %s and %s",pref(kind,lookupname),k,gref(getchar(prev)),gref(nextchar))
+                     --     end
+                     -- end
+                     -- if b and b ~= 0 then
+                     --     logwarning("%s: ignoring second kern xoff %s",pref(kind,lookupname),b*factor)
+                     -- end
+                    end
+                    done = true
+                elseif krn ~= 0 then
+                    local k = setkern(snext,factor,rlmode,krn,injection)
+                    if trace_kerns then
+                        logprocess("%s: inserting kern %s between %s and %s",pref(kind,lookupname),k,gref(getchar(prev)),gref(nextchar)) -- prev?
+                    end
+                    done = true
+                end
+                break
+            end
+        end
+        return head, start, done
+    end
+end
+
+--[[ldx--
+<p>We get hits on a mark, but we're not sure if the it has to be applied so
+we need to explicitly test for basechar, baselig and basemark entries.</p>
+--ldx]]--
+
+function handlers.gpos_mark2base(head,start,kind,lookupname,markanchors,sequence)
+    local markchar = getchar(start)
+    if marks[markchar] then
+        local base = getprev(start) -- [glyph] [start=mark]
+        if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then
+            local basechar = getchar(base)
+            if marks[basechar] then
+                while true do
+                    base = getprev(base)
+                    if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then
+                        basechar = getchar(base)
+                        if not marks[basechar] then
+                            break
+                        end
+                    else
+                        if trace_bugs then
+                            logwarning("%s: no base for mark %s",pref(kind,lookupname),gref(markchar))
+                        end
+                        return head, start, false
+                    end
+                end
+            end
+            local baseanchors = descriptions[basechar]
+            if baseanchors then
+                baseanchors = baseanchors.anchors
+            end
+            if baseanchors then
+                local baseanchors = baseanchors['basechar']
+                if baseanchors then
+                    local al = anchorlookups[lookupname]
+                    for anchor,ba in next, baseanchors do
+                        if al[anchor] then
+                            local ma = markanchors[anchor]
+                            if ma then
+                                local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,characters[basechar])
+                                if trace_marks then
+                                    logprocess("%s, anchor %s, bound %s: anchoring mark %s to basechar %s => (%p,%p)",
+                                        pref(kind,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy)
+                                end
+                                return head, start, true
+                            end
+                        end
+                    end
+                    if trace_bugs then
+                        logwarning("%s, no matching anchors for mark %s and base %s",pref(kind,lookupname),gref(markchar),gref(basechar))
+                    end
+                end
+            elseif trace_bugs then
+            --  logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(basechar))
+                onetimemessage(currentfont,basechar,"no base anchors",report_fonts)
+            end
+        elseif trace_bugs then
+            logwarning("%s: prev node is no char",pref(kind,lookupname))
+        end
+    elseif trace_bugs then
+        logwarning("%s: mark %s is no mark",pref(kind,lookupname),gref(markchar))
+    end
+    return head, start, false
+end
+
+function handlers.gpos_mark2ligature(head,start,kind,lookupname,markanchors,sequence)
+    -- check chainpos variant
+    local markchar = getchar(start)
+    if marks[markchar] then
+        local base = getprev(start) -- [glyph] [optional marks] [start=mark]
+        if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then
+            local basechar = getchar(base)
+            if marks[basechar] then
+                while true do
+                    base = getprev(base)
+                    if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then
+                        basechar = getchar(base)
+                        if not marks[basechar] then
+                            break
+                        end
+                    else
+                        if trace_bugs then
+                            logwarning("%s: no base for mark %s",pref(kind,lookupname),gref(markchar))
+                        end
+                        return head, start, false
+                    end
+                end
+            end
+            local index = getligaindex(start)
+            local baseanchors = descriptions[basechar]
+            if baseanchors then
+                baseanchors = baseanchors.anchors
+                if baseanchors then
+                   local baseanchors = baseanchors['baselig']
+                   if baseanchors then
+                        local al = anchorlookups[lookupname]
+                        for anchor, ba in next, baseanchors do
+                            if al[anchor] then
+                                local ma = markanchors[anchor]
+                                if ma then
+                                    ba = ba[index]
+                                    if ba then
+                                        local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,characters[basechar]) -- index
+                                        if trace_marks then
+                                            logprocess("%s, anchor %s, index %s, bound %s: anchoring mark %s to baselig %s at index %s => (%p,%p)",
+                                                pref(kind,lookupname),anchor,index,bound,gref(markchar),gref(basechar),index,dx,dy)
+                                        end
+                                        return head, start, true
+                                    else
+                                        if trace_bugs then
+                                            logwarning("%s: no matching anchors for mark %s and baselig %s with index %a",pref(kind,lookupname),gref(markchar),gref(basechar),index)
+                                        end
+                                    end
+                                end
+                            end
+                        end
+                        if trace_bugs then
+                            logwarning("%s: no matching anchors for mark %s and baselig %s",pref(kind,lookupname),gref(markchar),gref(basechar))
+                        end
+                    end
+                end
+            elseif trace_bugs then
+            --  logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(basechar))
+                onetimemessage(currentfont,basechar,"no base anchors",report_fonts)
+            end
+        elseif trace_bugs then
+            logwarning("%s: prev node is no char",pref(kind,lookupname))
+        end
+    elseif trace_bugs then
+        logwarning("%s: mark %s is no mark",pref(kind,lookupname),gref(markchar))
+    end
+    return head, start, false
+end
+
+function handlers.gpos_mark2mark(head,start,kind,lookupname,markanchors,sequence)
+    local markchar = getchar(start)
+    if marks[markchar] then
+        local base = getprev(start) -- [glyph] [basemark] [start=mark]
+        local slc = getligaindex(start)
+        if slc then -- a rather messy loop ... needs checking with husayni
+            while base do
+                local blc = getligaindex(base)
+                if blc and blc ~= slc then
+                    base = getprev(base)
+                else
+                    break
+                end
+            end
+        end
+        if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then -- subtype test can go
+            local basechar = getchar(base)
+            local baseanchors = descriptions[basechar]
+            if baseanchors then
+                baseanchors = baseanchors.anchors
+                if baseanchors then
+                    baseanchors = baseanchors['basemark']
+                    if baseanchors then
+                        local al = anchorlookups[lookupname]
+                        for anchor,ba in next, baseanchors do
+                            if al[anchor] then
+                                local ma = markanchors[anchor]
+                                if ma then
+                                    local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,characters[basechar],true)
+                                    if trace_marks then
+                                        logprocess("%s, anchor %s, bound %s: anchoring mark %s to basemark %s => (%p,%p)",
+                                            pref(kind,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy)
+                                    end
+                                    return head, start, true
+                                end
+                            end
+                        end
+                        if trace_bugs then
+                            logwarning("%s: no matching anchors for mark %s and basemark %s",pref(kind,lookupname),gref(markchar),gref(basechar))
+                        end
+                    end
+                end
+            elseif trace_bugs then
+            --  logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(basechar))
+                onetimemessage(currentfont,basechar,"no base anchors",report_fonts)
+            end
+        elseif trace_bugs then
+            logwarning("%s: prev node is no mark",pref(kind,lookupname))
+        end
+    elseif trace_bugs then
+        logwarning("%s: mark %s is no mark",pref(kind,lookupname),gref(markchar))
+    end
+    return head, start, false
+end
+
+function handlers.gpos_cursive(head,start,kind,lookupname,exitanchors,sequence) -- to be checked
+    local alreadydone = cursonce and getprop(start,a_cursbase)
+    if not alreadydone then
+        local done = false
+        local startchar = getchar(start)
+        if marks[startchar] then
+            if trace_cursive then
+                logprocess("%s: ignoring cursive for mark %s",pref(kind,lookupname),gref(startchar))
+            end
+        else
+            local nxt = getnext(start)
+            while not done and nxt and getid(nxt) == glyph_code and getfont(nxt) == currentfont and getsubtype(nxt)<256 do
+                local nextchar = getchar(nxt)
+                if marks[nextchar] then
+                    -- should not happen (maybe warning)
+                    nxt = getnext(nxt)
+                else
+                    local entryanchors = descriptions[nextchar]
+                    if entryanchors then
+                        entryanchors = entryanchors.anchors
+                        if entryanchors then
+                            entryanchors = entryanchors['centry']
+                            if entryanchors then
+                                local al = anchorlookups[lookupname]
+                                for anchor, entry in next, entryanchors do
+                                    if al[anchor] then
+                                        local exit = exitanchors[anchor]
+                                        if exit then
+                                            local dx, dy, bound = setcursive(start,nxt,tfmdata.parameters.factor,rlmode,exit,entry,characters[startchar],characters[nextchar])
+                                            if trace_cursive then
+                                                logprocess("%s: moving %s to %s cursive (%p,%p) using anchor %s and bound %s in rlmode %s",pref(kind,lookupname),gref(startchar),gref(nextchar),dx,dy,anchor,bound,rlmode)
+                                            end
+                                            done = true
+                                            break
+                                        end
+                                    end
+                                end
+                            end
+                        end
+                    elseif trace_bugs then
+                    --  logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(startchar))
+                        onetimemessage(currentfont,startchar,"no entry anchors",report_fonts)
+                    end
+                    break
+                end
+            end
+        end
+        return head, start, done
+    else
+        if trace_cursive and trace_details then
+            logprocess("%s, cursive %s is already done",pref(kind,lookupname),gref(getchar(start)),alreadydone)
+        end
+        return head, start, false
+    end
+end
+
+--[[ldx--
+<p>I will implement multiple chain replacements once I run into a font that uses
+it. It's not that complex to handle.</p>
+--ldx]]--
+
+local chainprocs = { }
+
+local function logprocess(...)
+    if trace_steps then
+        registermessage(...)
+    end
+    report_subchain(...)
+end
+
+local logwarning = report_subchain
+
+local function logprocess(...)
+    if trace_steps then
+        registermessage(...)
+    end
+    report_chain(...)
+end
+
+local logwarning = report_chain
+
+-- We could share functions but that would lead to extra function calls with many
+-- arguments, redundant tests and confusing messages.
+
+function chainprocs.chainsub(head,start,stop,kind,chainname,currentcontext,lookuphash,lookuplist,chainlookupname)
+    logwarning("%s: a direct call to chainsub cannot happen",cref(kind,chainname,chainlookupname))
+    return head, start, false
+end
+
+-- The reversesub is a special case, which is why we need to store the replacements
+-- in a bit weird way. There is no lookup and the replacement comes from the lookup
+-- itself. It is meant mostly for dealing with Urdu.
+
+function chainprocs.reversesub(head,start,stop,kind,chainname,currentcontext,lookuphash,replacements)
+    local char = getchar(start)
+    local replacement = replacements[char]
+    if replacement then
+        if trace_singles then
+            logprocess("%s: single reverse replacement of %s by %s",cref(kind,chainname),gref(char),gref(replacement))
+        end
+        resetinjection(start)
+        setchar(start,replacement)
+        return head, start, true
+    else
+        return head, start, false
+    end
+end
+
+--[[ldx--
+<p>This chain stuff is somewhat tricky since we can have a sequence of actions to be
+applied: single, alternate, multiple or ligature where ligature can be an invalid
+one in the sense that it will replace multiple by one but not neccessary one that
+looks like the combination (i.e. it is the counterpart of multiple then). For
+example, the following is valid:</p>
+
+<typing>
+<line>xxxabcdexxx [single a->A][multiple b->BCD][ligature cde->E] xxxABCDExxx</line>
+</typing>
+
+<p>Therefore we we don't really do the replacement here already unless we have the
+single lookup case. The efficiency of the replacements can be improved by deleting
+as less as needed but that would also make the code even more messy.</p>
+--ldx]]--
+
+-- local function delete_till_stop(head,start,stop,ignoremarks) -- keeps start
+--     local n = 1
+--     if start == stop then
+--         -- done
+--     elseif ignoremarks then
+--         repeat -- start x x m x x stop => start m
+--             local next = getnext(start)
+--             if not marks[getchar(next)] then
+--                 local components = getnext(next,"components")
+--                 if components then -- probably not needed
+--                     flush_node_list(components)
+--                 end
+--                 head = delete_node(head,next)
+--             end
+--             n = n + 1
+--         until next == stop
+--     else -- start x x x stop => start
+--         repeat
+--             local next = getnext(start)
+--             local components = getfield(next,"components")
+--             if components then -- probably not needed
+--                 flush_node_list(components)
+--             end
+--             head = delete_node(head,next)
+--             n = n + 1
+--         until next == stop
+--     end
+--     return head, n
+-- end
+
+--[[ldx--
+<p>Here we replace start by a single variant.</p>
+--ldx]]--
+
+function chainprocs.gsub_single(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex)
+    -- todo: marks ?
+    local current = start
+    local subtables = currentlookup.subtables
+    if #subtables > 1 then
+        logwarning("todo: check if we need to loop over the replacements: % t",subtables)
+    end
+    while current do
+        if getid(current) == glyph_code then
+            local currentchar = getchar(current)
+            local lookupname = subtables[1] -- only 1
+            local replacement = lookuphash[lookupname]
+            if not replacement then
+                if trace_bugs then
+                    logwarning("%s: no single hits",cref(kind,chainname,chainlookupname,lookupname,chainindex))
+                end
+            else
+                replacement = replacement[currentchar]
+                if not replacement or replacement == "" then
+                    if trace_bugs then
+                        logwarning("%s: no single for %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(currentchar))
+                    end
+                else
+                    if trace_singles then
+                        logprocess("%s: replacing single %s by %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(currentchar),gref(replacement))
+                    end
+                    resetinjection(current)
+                    setchar(current,replacement)
+                end
+            end
+            return head, start, true
+        elseif current == stop then
+            break
+        else
+            current = getnext(current)
+        end
+    end
+    return head, start, false
+end
+
+--[[ldx--
+<p>Here we replace start by a sequence of new glyphs.</p>
+--ldx]]--
+
+function chainprocs.gsub_multiple(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname)
+ -- local head, n = delete_till_stop(head,start,stop)
+    local startchar = getchar(start)
+    local subtables = currentlookup.subtables
+    local lookupname = subtables[1]
+    local replacements = lookuphash[lookupname]
+    if not replacements then
+        if trace_bugs then
+            logwarning("%s: no multiple hits",cref(kind,chainname,chainlookupname,lookupname))
+        end
+    else
+        replacements = replacements[startchar]
+        if not replacements or replacement == "" then
+            if trace_bugs then
+                logwarning("%s: no multiple for %s",cref(kind,chainname,chainlookupname,lookupname),gref(startchar))
+            end
+        else
+            if trace_multiples then
+                logprocess("%s: replacing %s by multiple characters %s",cref(kind,chainname,chainlookupname,lookupname),gref(startchar),gref(replacements))
+            end
+            return multiple_glyphs(head,start,replacements,currentlookup.flags[1])
+        end
+    end
+    return head, start, false
+end
+
+--[[ldx--
+<p>Here we replace start by new glyph. First we delete the rest of the match.</p>
+--ldx]]--
+
+-- char_1 mark_1 -> char_x mark_1 (ignore marks)
+-- char_1 mark_1 -> char_x
+
+-- to be checked: do we always have just one glyph?
+-- we can also have alternates for marks
+-- marks come last anyway
+-- are there cases where we need to delete the mark
+
+function chainprocs.gsub_alternate(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname)
+    local current = start
+    local subtables = currentlookup.subtables
+    local value  = featurevalue == true and tfmdata.shared.features[kind] or featurevalue
+    while current do
+        if getid(current) == glyph_code then -- is this check needed?
+            local currentchar = getchar(current)
+            local lookupname = subtables[1]
+            local alternatives = lookuphash[lookupname]
+            if not alternatives then
+                if trace_bugs then
+                    logwarning("%s: no alternative hit",cref(kind,chainname,chainlookupname,lookupname))
+                end
+            else
+                alternatives = alternatives[currentchar]
+                if alternatives then
+                    local choice, comment = get_alternative_glyph(current,alternatives,value,trace_alternatives)
+                    if choice then
+                        if trace_alternatives then
+                            logprocess("%s: replacing %s by alternative %a to %s, %s",cref(kind,chainname,chainlookupname,lookupname),gref(char),choice,gref(choice),comment)
+                        end
+                        resetinjection(start)
+                        setchar(start,choice)
+                    else
+                        if trace_alternatives then
+                            logwarning("%s: no variant %a for %s, %s",cref(kind,chainname,chainlookupname,lookupname),value,gref(char),comment)
+                        end
+                    end
+                elseif trace_bugs then
+                    logwarning("%s: no alternative for %s, %s",cref(kind,chainname,chainlookupname,lookupname),gref(currentchar),comment)
+                end
+            end
+            return head, start, true
+        elseif current == stop then
+            break
+        else
+            current = getnext(current)
+        end
+    end
+    return head, start, false
+end
+
+--[[ldx--
+<p>When we replace ligatures we use a helper that handles the marks. I might change
+this function (move code inline and handle the marks by a separate function). We
+assume rather stupid ligatures (no complex disc nodes).</p>
+--ldx]]--
+
+function chainprocs.gsub_ligature(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex)
+    local startchar = getchar(start)
+    local subtables = currentlookup.subtables
+    local lookupname = subtables[1]
+    local ligatures = lookuphash[lookupname]
+    if not ligatures then
+        if trace_bugs then
+            logwarning("%s: no ligature hits",cref(kind,chainname,chainlookupname,lookupname,chainindex))
+        end
+    else
+        ligatures = ligatures[startchar]
+        if not ligatures then
+            if trace_bugs then
+                logwarning("%s: no ligatures starting with %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar))
+            end
+        else
+            local s = getnext(start)
+            local discfound = false
+            local last = stop
+            local nofreplacements = 1
+            local skipmark = currentlookup.flags[1]
+            while s do
+                local id = getid(s)
+                if id == disc_code then
+                    if not discfound then
+                        discfound = s
+                    end
+                    if s == stop then
+                        break -- okay? or before the disc
+                    else
+                        s = getnext(s)
+                    end
+                else
+                    local schar = getchar(s)
+                    if skipmark and marks[schar] then -- marks
+                        s = getnext(s)
+                    else
+                        local lg = ligatures[schar]
+                        if lg then
+                            ligatures, last, nofreplacements = lg, s, nofreplacements + 1
+                            if s == stop then
+                                break
+                            else
+                                s = getnext(s)
+                            end
+                        else
+                            break
+                        end
+                    end
+                end
+            end
+            local l2 = ligatures.ligature
+            if l2 then
+                if chainindex then
+                    stop = last
+                end
+                if trace_ligatures then
+                    if start == stop then
+                        logprocess("%s: replacing character %s by ligature %s case 3",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(l2))
+                    else
+                        logprocess("%s: replacing character %s upto %s by ligature %s case 4",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(getchar(stop)),gref(l2))
+                    end
+                end
+                head, start = toligature(kind,lookupname,head,start,stop,l2,currentlookup.flags[1],discfound)
+                return head, start, true, nofreplacements, discfound
+            elseif trace_bugs then
+                if start == stop then
+                    logwarning("%s: replacing character %s by ligature fails",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar))
+                else
+                    logwarning("%s: replacing character %s upto %s by ligature fails",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(getchar(stop)))
+                end
+            end
+        end
+    end
+    return head, start, false, 0, false
+end
+
+function chainprocs.gpos_single(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex,sequence)
+    -- untested .. needs checking for the new model
+    local startchar = getchar(start)
+    local subtables = currentlookup.subtables
+    local lookupname = subtables[1]
+    local kerns = lookuphash[lookupname]
+    if kerns then
+        kerns = kerns[startchar] -- needed ?
+        if kerns then
+            local dx, dy, w, h = setpair(start,tfmdata.parameters.factor,rlmode,sequence.flags[4],kerns) -- ,characters[startchar])
+            if trace_kerns then
+                logprocess("%s: shifting single %s by (%p,%p) and correction (%p,%p)",cref(kind,chainname,chainlookupname),gref(startchar),dx,dy,w,h)
+            end
+        end
+    end
+    return head, start, false
+end
+
+function chainprocs.gpos_pair(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex,sequence)
+    local snext = getnext(start)
+    if snext then
+        local startchar = getchar(start)
+        local subtables = currentlookup.subtables
+        local lookupname = subtables[1]
+        local kerns = lookuphash[lookupname]
+        if kerns then
+            kerns = kerns[startchar]
+            if kerns then
+                local lookuptype = lookuptypes[lookupname]
+                local prev, done = start, false
+                local factor = tfmdata.parameters.factor
+                while snext and getid(snext) == glyph_code and getfont(snext) == currentfont and getsubtype(snext)<256 do
+                    local nextchar = getchar(snext)
+                    local krn = kerns[nextchar]
+                    if not krn and marks[nextchar] then
+                        prev = snext
+                        snext = getnext(snext)
+                    else
+                        if not krn then
+                            -- skip
+                        elseif type(krn) == "table" then
+                            if lookuptype == "pair" then
+                                local a, b = krn[2], krn[3]
+                                if a and #a > 0 then
+                                    local startchar = getchar(start)
+                                    local x, y, w, h = setpair(start,factor,rlmode,sequence.flags[4],a) -- ,characters[startchar])
+                                    if trace_kerns then
+                                        logprocess("%s: shifting first of pair %s and %s by (%p,%p) and correction (%p,%p)",cref(kind,chainname,chainlookupname),gref(startchar),gref(nextchar),x,y,w,h)
+                                    end
+                                end
+                                if b and #b > 0 then
+                                    local startchar = getchar(start)
+                                    local x, y, w, h = setpair(snext,factor,rlmode,sequence.flags[4],b) -- ,characters[nextchar])
+                                    if trace_kerns then
+                                        logprocess("%s: shifting second of pair %s and %s by (%p,%p) and correction (%p,%p)",cref(kind,chainname,chainlookupname),gref(startchar),gref(nextchar),x,y,w,h)
+                                    end
+                                end
+                            else
+                                report_process("%s: check this out (old kern stuff)",cref(kind,chainname,chainlookupname))
+                             -- local a, b = krn[2], krn[6]
+                             -- if a and a ~= 0 then
+                             --     local k = setkern(snext,factor,rlmode,a)
+                             --     if trace_kerns then
+                             --         logprocess("%s: inserting first kern %s between %s and %s",cref(kind,chainname,chainlookupname),k,gref(getchar(prev)),gref(nextchar))
+                             --     end
+                             -- end
+                             -- if b and b ~= 0 then
+                             --     logwarning("%s: ignoring second kern xoff %s",cref(kind,chainname,chainlookupname),b*factor)
+                             -- end
+                            end
+                            done = true
+                        elseif krn ~= 0 then
+                            local k = setkern(snext,factor,rlmode,krn)
+                            if trace_kerns then
+                                logprocess("%s: inserting kern %s between %s and %s",cref(kind,chainname,chainlookupname),k,gref(getchar(prev)),gref(nextchar))
+                            end
+                            done = true
+                        end
+                        break
+                    end
+                end
+                return head, start, done
+            end
+        end
+    end
+    return head, start, false
+end
+
+function chainprocs.gpos_mark2base(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname)
+    local markchar = getchar(start)
+    if marks[markchar] then
+        local subtables = currentlookup.subtables
+        local lookupname = subtables[1]
+        local markanchors = lookuphash[lookupname]
+        if markanchors then
+            markanchors = markanchors[markchar]
+        end
+        if markanchors then
+            local base = getprev(start) -- [glyph] [start=mark]
+            if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then
+                local basechar = getchar(base)
+                if marks[basechar] then
+                    while true do
+                        base = getprev(base)
+                        if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then
+                            basechar = getchar(base)
+                            if not marks[basechar] then
+                                break
+                            end
+                        else
+                            if trace_bugs then
+                                logwarning("%s: no base for mark %s",pref(kind,lookupname),gref(markchar))
+                            end
+                            return head, start, false
+                        end
+                    end
+                end
+                local baseanchors = descriptions[basechar].anchors
+                if baseanchors then
+                    local baseanchors = baseanchors['basechar']
+                    if baseanchors then
+                        local al = anchorlookups[lookupname]
+                        for anchor,ba in next, baseanchors do
+                            if al[anchor] then
+                                local ma = markanchors[anchor]
+                                if ma then
+                                    local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,characters[basechar])
+                                    if trace_marks then
+                                        logprocess("%s, anchor %s, bound %s: anchoring mark %s to basechar %s => (%p,%p)",
+                                            cref(kind,chainname,chainlookupname,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy)
+                                    end
+                                    return head, start, true
+                                end
+                            end
+                        end
+                        if trace_bugs then
+                            logwarning("%s, no matching anchors for mark %s and base %s",cref(kind,chainname,chainlookupname,lookupname),gref(markchar),gref(basechar))
+                        end
+                    end
+                end
+            elseif trace_bugs then
+                logwarning("%s: prev node is no char",cref(kind,chainname,chainlookupname,lookupname))
+            end
+        elseif trace_bugs then
+            logwarning("%s: mark %s has no anchors",cref(kind,chainname,chainlookupname,lookupname),gref(markchar))
+        end
+    elseif trace_bugs then
+        logwarning("%s: mark %s is no mark",cref(kind,chainname,chainlookupname),gref(markchar))
+    end
+    return head, start, false
+end
+
+function chainprocs.gpos_mark2ligature(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname)
+    local markchar = getchar(start)
+    if marks[markchar] then
+        local subtables = currentlookup.subtables
+        local lookupname = subtables[1]
+        local markanchors = lookuphash[lookupname]
+        if markanchors then
+            markanchors = markanchors[markchar]
+        end
+        if markanchors then
+            local base = getprev(start) -- [glyph] [optional marks] [start=mark]
+            if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then
+                local basechar = getchar(base)
+                if marks[basechar] then
+                    while true do
+                        base = getprev(base)
+                        if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then
+                            basechar = getchar(base)
+                            if not marks[basechar] then
+                                break
+                            end
+                        else
+                            if trace_bugs then
+                                logwarning("%s: no base for mark %s",cref(kind,chainname,chainlookupname,lookupname),markchar)
+                            end
+                            return head, start, false
+                        end
+                    end
+                end
+                -- todo: like marks a ligatures hash
+                local index = getligaindex(start)
+                local baseanchors = descriptions[basechar].anchors
+                if baseanchors then
+                   local baseanchors = baseanchors['baselig']
+                   if baseanchors then
+                        local al = anchorlookups[lookupname]
+                        for anchor,ba in next, baseanchors do
+                            if al[anchor] then
+                                local ma = markanchors[anchor]
+                                if ma then
+                                    ba = ba[index]
+                                    if ba then
+                                        local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,characters[basechar])
+                                        if trace_marks then
+                                            logprocess("%s, anchor %s, bound %s: anchoring mark %s to baselig %s at index %s => (%p,%p)",
+                                                cref(kind,chainname,chainlookupname,lookupname),anchor,a or bound,gref(markchar),gref(basechar),index,dx,dy)
+                                        end
+                                        return head, start, true
+                                    end
+                                end
+                            end
+                        end
+                        if trace_bugs then
+                            logwarning("%s: no matching anchors for mark %s and baselig %s",cref(kind,chainname,chainlookupname,lookupname),gref(markchar),gref(basechar))
+                        end
+                    end
+                end
+            elseif trace_bugs then
+                logwarning("feature %s, lookup %s: prev node is no char",kind,lookupname)
+            end
+        elseif trace_bugs then
+            logwarning("%s: mark %s has no anchors",cref(kind,chainname,chainlookupname,lookupname),gref(markchar))
+        end
+    elseif trace_bugs then
+        logwarning("%s: mark %s is no mark",cref(kind,chainname,chainlookupname),gref(markchar))
+    end
+    return head, start, false
+end
+
+function chainprocs.gpos_mark2mark(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname)
+    local markchar = getchar(start)
+    if marks[markchar] then
+    --  local markanchors = descriptions[markchar].anchors markanchors = markanchors and markanchors.mark
+        local subtables = currentlookup.subtables
+        local lookupname = subtables[1]
+        local markanchors = lookuphash[lookupname]
+        if markanchors then
+            markanchors = markanchors[markchar]
+        end
+        if markanchors then
+            local base = getprev(start) -- [glyph] [basemark] [start=mark]
+            local slc = getligaindex(start)
+            if slc then -- a rather messy loop ... needs checking with husayni
+                while base do
+                    local blc = getligaindex(base)
+                    if blc and blc ~= slc then
+                        base = getprev(base)
+                    else
+                        break
+                    end
+                end
+            end
+            if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then -- subtype test can go
+                local basechar = getchar(base)
+                local baseanchors = descriptions[basechar].anchors
+                if baseanchors then
+                    baseanchors = baseanchors['basemark']
+                    if baseanchors then
+                        local al = anchorlookups[lookupname]
+                        for anchor,ba in next, baseanchors do
+                            if al[anchor] then
+                                local ma = markanchors[anchor]
+                                if ma then
+                                    local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,characters[basechar],true)
+                                    if trace_marks then
+                                        logprocess("%s, anchor %s, bound %s: anchoring mark %s to basemark %s => (%p,%p)",
+                                            cref(kind,chainname,chainlookupname,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy)
+                                    end
+                                    return head, start, true
+                                end
+                            end
+                        end
+                        if trace_bugs then
+                            logwarning("%s: no matching anchors for mark %s and basemark %s",gref(kind,chainname,chainlookupname,lookupname),gref(markchar),gref(basechar))
+                        end
+                    end
+                end
+            elseif trace_bugs then
+                logwarning("%s: prev node is no mark",cref(kind,chainname,chainlookupname,lookupname))
+            end
+        elseif trace_bugs then
+            logwarning("%s: mark %s has no anchors",cref(kind,chainname,chainlookupname,lookupname),gref(markchar))
+        end
+    elseif trace_bugs then
+        logwarning("%s: mark %s is no mark",cref(kind,chainname,chainlookupname),gref(markchar))
+    end
+    return head, start, false
+end
+
+function chainprocs.gpos_cursive(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname)
+    local alreadydone = cursonce and getprop(start,a_cursbase)
+    if not alreadydone then
+        local startchar = getchar(start)
+        local subtables = currentlookup.subtables
+        local lookupname = subtables[1]
+        local exitanchors = lookuphash[lookupname]
+        if exitanchors then
+            exitanchors = exitanchors[startchar]
+        end
+        if exitanchors then
+            local done = false
+            if marks[startchar] then
+                if trace_cursive then
+                    logprocess("%s: ignoring cursive for mark %s",pref(kind,lookupname),gref(startchar))
+                end
+            else
+                local nxt = getnext(start)
+                while not done and nxt and getid(nxt) == glyph_code and getfont(nxt) == currentfont and getsubtype(nxt)<256 do
+                    local nextchar = getchar(nxt)
+                    if marks[nextchar] then
+                        -- should not happen (maybe warning)
+                        nxt = getnext(nxt)
+                    else
+                        local entryanchors = descriptions[nextchar]
+                        if entryanchors then
+                            entryanchors = entryanchors.anchors
+                            if entryanchors then
+                                entryanchors = entryanchors['centry']
+                                if entryanchors then
+                                    local al = anchorlookups[lookupname]
+                                    for anchor, entry in next, entryanchors do
+                                        if al[anchor] then
+                                            local exit = exitanchors[anchor]
+                                            if exit then
+                                                local dx, dy, bound = setcursive(start,nxt,tfmdata.parameters.factor,rlmode,exit,entry,characters[startchar],characters[nextchar])
+                                                if trace_cursive then
+                                                    logprocess("%s: moving %s to %s cursive (%p,%p) using anchor %s and bound %s in rlmode %s",pref(kind,lookupname),gref(startchar),gref(nextchar),dx,dy,anchor,bound,rlmode)
+                                                end
+                                                done = true
+                                                break
+                                            end
+                                        end
+                                    end
+                                end
+                            end
+                        elseif trace_bugs then
+                        --  logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(startchar))
+                            onetimemessage(currentfont,startchar,"no entry anchors",report_fonts)
+                        end
+                        break
+                    end
+                end
+            end
+            return head, start, done
+        else
+            if trace_cursive and trace_details then
+                logprocess("%s, cursive %s is already done",pref(kind,lookupname),gref(getchar(start)),alreadydone)
+            end
+            return head, start, false
+        end
+    end
+    return head, start, false
+end
+
+-- what pointer to return, spec says stop
+-- to be discussed ... is bidi changer a space?
+-- elseif char == zwnj and sequence[n][32] then -- brrr
+
+-- somehow l or f is global
+-- we don't need to pass the currentcontext, saves a bit
+-- make a slow variant then can be activated but with more tracing
+
+local function show_skip(kind,chainname,char,ck,class)
+    if ck[9] then
+        logwarning("%s: skipping char %s, class %a, rule %a, lookuptype %a, %a => %a",cref(kind,chainname),gref(char),class,ck[1],ck[2],ck[9],ck[10])
+    else
+        logwarning("%s: skipping char %s, class %a, rule %a, lookuptype %a",cref(kind,chainname),gref(char),class,ck[1],ck[2])
+    end
+end
+
+-- A previous version had disc collapsing code in the (single sub) handler plus some
+-- checking in the main loop, but that left the pre/post sequences undone. The best
+-- solution is to add some checking there and backtrack when a replace/post matches
+-- but it takes a bit of work to figure out an efficient way (this is what the sweep*
+-- names refer to). I might look into that variant one day again as it can replace
+-- some other code too. In that approach we can have a special version for gub and pos
+-- which gains some speed. This method does the test and passes info to the handlers
+-- (sweepnode, sweepmode, sweepprev, sweepnext, etc). Here collapsing is handled in the
+-- main loop which also makes code elsewhere simpler (i.e. no need for the other special
+-- runners and disc code in ligature building). I also experimented with pushing preceding
+-- glyphs sequences in the replace/pre fields beforehand which saves checking afterwards
+-- but at the cost of duplicate glyphs (memory) but it's too much overhead (runtime).
+--
+-- In the meantime Kai had moved the code from the single chain into a more general handler
+-- and this one (renamed to chaindisk) is used now. I optimized the code a bit and brought
+-- it in sycn with the other code. Hopefully I didn't introduce errors. Note: this somewhat
+-- complex approach is meant for fonts that implement (for instance) ligatures by character
+-- replacement which to some extend is not that suitable for hyphenation. I also use some
+-- helpers. This method passes some states but reparses the list. There is room for a bit of
+-- speed up but that will be done in the context version. (In fact a partial rewrite of all
+-- code can bring some more efficientry.)
+--
+-- I didn't test it with extremes but successive disc nodes still can give issues but in
+-- order to handle that we need more complex code which also slows down even more. The main
+-- loop variant could deal with that: test, collapse, backtrack.
+
+local function chaindisk(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,chainindex,sequence,chainproc)
+
+    if not start then
+        return head, start, false
+    end
+
+    local startishead   = start == head
+    local seq           = ck[3]
+    local f             = ck[4]
+    local l             = ck[5]
+    local s             = #seq
+    local done          = false
+    local sweepnode     = sweepnode
+    local sweeptype     = sweeptype
+    local sweepoverflow = false
+    local checkdisc     = getprev(head) -- hm bad name head
+    local keepdisc      = not sweepnode
+    local lookaheaddisc = nil
+    local backtrackdisc = nil
+    local current       = start
+    local last          = start
+    local prev          = getprev(start)
+
+    -- fishy: so we can overflow and then go on in the sweep?
+
+    local i = f
+    while i <= l do
+        local id = getid(current)
+        if id == glyph_code then
+            i       = i + 1
+            last    = current
+            current = getnext(current)
+        elseif id == disc_code then
+            if keepdisc then
+                keepdisc = false
+                if notmatchpre[current] ~= notmatchreplace[current] then
+                    lookaheaddisc = current
+                end
+                local replace = getfield(current,"replace")
+                while replace and i <= l do
+                    if getid(replace) == glyph_code then
+                        i = i + 1
+                    end
+                    replace = getnext(replace)
+                end
+                last    = current
+                current = getnext(c)
+            else
+                head, current = flattendisk(head,current)
+            end
+        else
+            last    = current
+            current = getnext(current)
+        end
+        if current then
+            -- go on
+        elseif sweepoverflow then
+            -- we already are folling up on sweepnode
+            break
+        elseif sweeptype == "post" or sweeptype == "replace" then
+            current = getnext(sweepnode)
+            if current then
+                sweeptype     = nil
+                sweepoverflow = true
+            else
+                break
+            end
+        end
+    end
+
+    if sweepoverflow then
+        local prev = current and getprev(current)
+        if not current or prev ~= sweepnode then
+            local head = getnext(sweepnode)
+            local tail = nil
+            if prev then
+                tail = prev
+                setprev(current,sweepnode)
+            else
+                tail = find_node_tail(head)
+            end
+            setnext(sweepnode,current)
+            setprev(head,nil)
+            setnext(tail,nil)
+            appenddisc(sweepnode,head)
+        end
+    end
+
+    if l < s then
+        local i = l
+        local t = sweeptype == "post" or sweeptype == "replace"
+        while current and i < s do
+            local id = getid(current)
+            if id == glyph_code then
+                i       = i + 1
+                current = getnext(current)
+            elseif id == disc_code then
+                if keepdisc then
+                    keepdisc = false
+                    if notmatchpre[current] ~= notmatchreplace[current] then
+                        lookaheaddisc = current
+                    end
+                    local replace = getfield(c,"replace")
+                    while replace and i < s do
+                        if getid(replace) == glyph_code then
+                            i = i + 1
+                        end
+                        replace = getnext(replace)
+                    end
+                    current = getnext(current)
+                elseif notmatchpre[current] ~= notmatchreplace[current] then
+                    head, current = flattendisk(head,current)
+                else
+                    current = getnext(current) -- HH
+                end
+            else
+                current = getnext(current)
+            end
+            if not current and t then
+                current = getnext(sweepnode)
+                if current then
+                    sweeptype = nil
+                end
+            end
+        end
+    end
+
+    if f > 1 then
+        local current = prev
+        local i       = f
+        local t       = sweeptype == "pre" or sweeptype == "replace"
+        if not current and t and current == checkdisk then
+            current = getprev(sweepnode)
+        end
+        while current and i > 1 do -- missing getprev added / moved outside
+            local id = getid(current)
+            if id == glyph_code then
+                i = i - 1
+            elseif id == disc_code then
+                if keepdisc then
+                    keepdisc = false
+                    if notmatchpost[current] ~= notmatchreplace[current] then
+                        backtrackdisc = current
+                    end
+                    local replace = getfield(current,"replace")
+                    while replace and i > 1 do
+                        if getid(replace) == glyph_code then
+                            i = i - 1
+                        end
+                        replace = getnext(replace)
+                    end
+                elseif notmatchpost[current] ~= notmatchreplace[current] then
+                    head, current = flattendisk(head,current)
+                end
+            end
+            current = getprev(current)
+            if t and current == checkdisk then
+                current = getprev(sweepnode)
+            end
+        end
+    end
+
+    local ok = false
+    if lookaheaddisc then
+
+        local cf            = start
+        local cl            = getprev(lookaheaddisc)
+        local cprev         = getprev(start)
+        local insertedmarks = 0
+
+        while cprev and getid(cf) == glyph_code and getfont(cf) == currentfont and getsubtype(cf) < 256 and marks[getchar(cf)] do
+            insertedmarks = insertedmarks + 1
+            cf            = cprev
+            startishead   = cf == head
+            cprev         = getprev(cprev)
+        end
+
+        setprev(lookaheaddisc,cprev)
+        if cprev then
+            setnext(cprev,lookaheaddisc)
+        end
+        setprev(cf,nil)
+        setnext(cl,nil)
+        if startishead then
+            head = lookaheaddisc
+        end
+
+        local replace = getfield(lookaheaddisc,"replace")
+        local pre     = getfield(lookaheaddisc,"pre")
+        local new     = copy_node_list(cf)
+        local cnew = new
+        for i=1,insertedmarks do
+            cnew = getnext(cnew)
+        end
+        local clast = cnew
+        for i=f,l do
+            clast = getnext(clast)
+        end
+        if not notmatchpre[lookaheaddisc] then
+            cf, start, ok = chainproc(cf,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence)
+        end
+        if not notmatchreplace[lookaheaddisc] then
+            new, cnew, ok = chainproc(new,cnew,clast,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence)
+        end
+        if pre then
+            setnext(cl,pre)
+            setprev(pre,cl)
+        end
+        if replace then
+            local tail = find_node_tail(new)
+            setnext(tail,replace)
+            setprev(replace,tail)
+        end
+        setfield(lookaheaddisc,"pre",cf)      -- also updates tail
+        setfield(lookaheaddisc,"replace",new) -- also updates tail
+
+        start          = getprev(lookaheaddisc)
+        sweephead[cf]  = getnext(clast)
+        sweephead[new] = getnext(last)
+
+    elseif backtrackdisc then
+
+        local cf            = getnext(backtrackdisc)
+        local cl            = start
+        local cnext         = getnext(start)
+        local insertedmarks = 0
+
+        while cnext and getid(cnext) == glyph_code and getfont(cnext) == currentfont and getsubtype(cnext) < 256 and marks[getchar(cnext)] do
+            insertedmarks = insertedmarks + 1
+            cl            = cnext
+            cnext         = getnext(cnext)
+        end
+        if cnext then
+            setprev(cnext,backtrackdisc)
+        end
+        setnext(backtrackdisc,cnext)
+        setprev(cf,nil)
+        setnext(cl,nil)
+        local replace = getfield(backtrackdisc,"replace")
+        local post    = getfield(backtrackdisc,"post")
+        local new     = copy_node_list(cf)
+        local cnew    = find_node_tail(new)
+        for i=1,insertedmarks do
+            cnew = getprev(cnew)
+        end
+        local clast = cnew
+        for i=f,l do
+            clast = getnext(clast)
+        end
+        if not notmatchpost[backtrackdisc] then
+            cf, start, ok = chainproc(cf,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence)
+        end
+        if not notmatchreplace[backtrackdisc] then
+            new, cnew, ok = chainproc(new,cnew,clast,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence)
+        end
+        if post then
+            local tail = find_node_tail(post)
+            setnext(tail,cf)
+            setprev(cf,tail)
+        else
+            post = cf
+        end
+        if replace then
+            local tail = find_node_tail(replace)
+            setnext(tail,new)
+            setprev(new,tail)
+        else
+            replace = new
+        end
+        setfield(backtrackdisc,"post",post)       -- also updates tail
+        setfield(backtrackdisc,"replace",replace) -- also updates tail
+        start              = getprev(backtrackdisc)
+        sweephead[post]    = getnext(clast)
+        sweephead[replace] = getnext(last)
+
+    else
+
+        head, start, ok = chainproc(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence)
+
+    end
+
+    return head, start, ok
+end
+
+local function normal_handle_contextchain(head,start,kind,chainname,contexts,sequence,lookuphash)
+    local sweepnode    = sweepnode
+    local sweeptype    = sweeptype
+    local diskseen     = false
+    local checkdisc    = getprev(head)
+    local flags        = sequence.flags
+    local done         = false
+    local skipmark     = flags[1]
+    local skipligature = flags[2]
+    local skipbase     = flags[3]
+    local markclass    = sequence.markclass
+    local skipped      = false
+
+    for k=1,#contexts do -- i've only seen ccmp having > 1 (e.g. dejavu)
+        local match   = true
+        local current = start
+        local last    = start
+        local ck      = contexts[k]
+        local seq     = ck[3]
+        local s       = #seq
+        -- f..l = mid string
+        if s == 1 then
+            -- never happens
+            match = getid(current) == glyph_code and getfont(current) == currentfont and getsubtype(current)<256 and seq[1][getchar(current)]
+        else
+            -- maybe we need a better space check (maybe check for glue or category or combination)
+            -- we cannot optimize for n=2 because there can be disc nodes
+            local f = ck[4]
+            local l = ck[5]
+            -- current match
+            if f == 1 and f == l then -- current only
+                -- already a hit
+             -- match = true
+            else -- before/current/after | before/current | current/after
+                -- no need to test first hit (to be optimized)
+                if f == l then -- new, else last out of sync (f is > 1)
+                 -- match = true
+                else
+                    local discfound = nil
+                    local n = f + 1
+                    last = getnext(last)
+                    while n <= l do
+                        if not last and (sweeptype == "post" or sweeptype == "replace") then
+                            last      = getnext(sweepnode)
+                            sweeptype = nil
+                        end
+                        if last then
+                            local id = getid(last)
+                            if id == glyph_code then
+                                if getfont(last) == currentfont and getsubtype(last)<256 then
+                                    local char = getchar(last)
+                                    local ccd = descriptions[char]
+                                    if ccd then
+                                        local class = ccd.class or "base"
+                                        if class == skipmark or class == skipligature or class == skipbase or (markclass and class == "mark" and not markclass[char]) then
+                                            skipped = true
+                                            if trace_skips then
+                                                show_skip(kind,chainname,char,ck,class)
+                                            end
+                                            last = getnext(last)
+                                        elseif seq[n][char] then
+                                            if n < l then
+                                                last = getnext(last)
+                                            end
+                                            n = n + 1
+                                        else
+                                            if discfound then
+                                                notmatchreplace[discfound] = true
+                                                match = not notmatchpre[discfound]
+                                            else
+                                                match = false
+                                            end
+                                            break
+                                        end
+                                    else
+                                        if discfound then
+                                            notmatchreplace[discfound] = true
+                                            match = not notmatchpre[discfound]
+                                        else
+                                            match = false
+                                        end
+                                        break
+                                    end
+                                else
+                                    if discfound then
+                                        notmatchreplace[discfound] = true
+                                        match = not notmatchpre[discfound]
+                                    else
+                                        match = false
+                                    end
+                                    break
+                                end
+                            elseif id == disc_code then
+                                diskseen              = true
+                                discfound             = last
+                                notmatchpre[last]     = nil
+                                notmatchpost[last]    = true
+                                notmatchreplace[last] = nil
+                                local pre     = getfield(last,"pre")
+                                local replace = getfield(last,"replace")
+                                if pre then
+                                    local n = n
+                                    while pre do
+                                        if seq[n][getchar(pre)] then
+                                            n = n + 1
+                                            pre = getnext(pre)
+                                            if n > l then
+                                                break
+                                            end
+                                        else
+                                            notmatchpre[last] = true
+                                            break
+                                        end
+                                    end
+                                    if n <= l then
+                                        notmatchpre[last] = true
+                                    end
+                                else
+                                    notmatchpre[last] = true
+                                end
+                                if replace then
+                                    -- so far we never entered this branch
+                                    while replace do
+                                        if seq[n][getchar(replace)] then
+                                            n = n + 1
+                                            replace = getnext(replace)
+                                            if n > l then
+                                                break
+                                            end
+                                        else
+                                            notmatchreplace[last] = true
+                                            match = not notmatchpre[last]
+                                            break
+                                        end
+                                    end
+                                    match = not notmatchpre[last]
+                                end
+                                last = getnext(last)
+                            else
+                                match = false
+                                break
+                            end
+                        else
+                            match = false
+                            break
+                        end
+                    end
+                end
+            end
+            -- before
+            if match and f > 1 then
+                local prev = getprev(start)
+                if prev then
+                    if prev == checkdisc and (sweeptype == "pre" or sweeptype == "replace") then
+                        prev      = getprev(sweepnode)
+                     -- sweeptype = nil
+                    end
+                    if prev then
+                        local discfound = nil
+                        local n = f - 1
+                        while n >= 1 do
+                            if prev then
+                                local id = getid(prev)
+                                if id == glyph_code then
+                                    if getfont(prev) == currentfont and getsubtype(prev)<256 then -- normal char
+                                        local char = getchar(prev)
+                                        local ccd = descriptions[char]
+                                        if ccd then
+                                            local class = ccd.class
+                                            if class == skipmark or class == skipligature or class == skipbase or (markclass and class == "mark" and not markclass[char]) then
+                                                skipped = true
+                                                if trace_skips then
+                                                    show_skip(kind,chainname,char,ck,class)
+                                                end
+                                            elseif seq[n][char] then
+                                                n = n -1
+                                            else
+                                                if discfound then
+                                                    notmatchreplace[discfound] = true
+                                                    match = not notmatchpost[discfound]
+                                                else
+                                                    match = false
+                                                end
+                                                break
+                                            end
+                                        else
+                                            if discfound then
+                                                notmatchreplace[discfound] = true
+                                                match = not notmatchpost[discfound]
+                                            else
+                                                match = false
+                                            end
+                                            break
+                                        end
+                                    else
+                                        if discfound then
+                                            notmatchreplace[discfound] = true
+                                            match = not notmatchpost[discfound]
+                                        else
+                                            match = false
+                                        end
+                                        break
+                                    end
+                                elseif id == disc_code then
+                                    -- the special case: f i where i becomes dottless i ..
+                                    diskseen              = true
+                                    discfound             = prev
+                                    notmatchpre[prev]     = true
+                                    notmatchpost[prev]    = nil
+                                    notmatchreplace[prev] = nil
+                                    local pre     = getfield(prev,"pre")
+                                    local post    = getfield(prev,"post")
+                                    local replace = getfield(prev,"replace")
+                                    if pre ~= start and post ~= start and replace ~= start then
+                                        if post then
+                                            local n = n
+                                            local posttail = find_node_tail(post)
+                                            while posttail do
+                                                if seq[n][getchar(posttail)] then
+                                                    n = n - 1
+                                                    if posttail == post then
+                                                        break
+                                                    else
+                                                        posttail = getprev(posttail)
+                                                        if n < 1 then
+                                                            break
+                                                        end
+                                                    end
+                                                else
+                                                    notmatchpost[prev] = true
+                                                    break
+                                                end
+                                            end
+                                            if n >= 1 then
+                                                notmatchpost[prev] = true
+                                            end
+                                        else
+                                            notmatchpost[prev] = true
+                                        end
+                                        if replace then
+                                            -- we seldom enter this branch (e.g. on brill efficient)
+                                            local replacetail = find_node_tail(replace)
+                                            while replacetail do
+                                                if seq[n][getchar(replacetail)] then
+                                                    n = n - 1
+                                                    if replacetail == replace then
+                                                        break
+                                                    else
+                                                        replacetail = getprev(replacetail)
+                                                        if n < 1 then
+                                                            break
+                                                        end
+                                                    end
+                                                else
+                                                    notmatchreplace[prev] = true
+                                                    match = not notmatchpost[prev]
+                                                    break
+                                                end
+                                            end
+                                            if not match then
+                                                break
+                                            end
+                                        else
+                                            -- skip 'm
+                                        end
+                                    else
+                                        -- skip 'm
+                                    end
+                                elseif seq[n][32] then
+                                    n = n -1
+                                else
+                                    match = false
+                                    break
+                                end
+                                prev = getprev(prev)
+                            elseif seq[n][32] then -- somewhat special, as zapfino can have many preceding spaces
+                                n = n - 1
+                            else
+                                match = false
+                                break
+                            end
+                        end
+                    else
+                        match = false
+                    end
+                else
+                    match = false
+                end
+            end
+            -- after
+            if match and s > l then
+                local current = last and getnext(last)
+                if not current then
+                    if sweeptype == "post" or sweeptype == "replace" then
+                        current   = getnext(sweepnode)
+                     -- sweeptype = nil
+                    end
+                end
+                if current then
+                    local discfound = nil
+                    -- removed optimization for s-l == 1, we have to deal with marks anyway
+                    local n = l + 1
+                    while n <= s do
+                        if current then
+                            local id = getid(current)
+                            if id == glyph_code then
+                                if getfont(current) == currentfont and getsubtype(current)<256 then -- normal char
+                                    local char = getchar(current)
+                                    local ccd = descriptions[char]
+                                    if ccd then
+                                        local class = ccd.class
+                                        if class == skipmark or class == skipligature or class == skipbase or (markclass and class == "mark" and not markclass[char]) then
+                                            skipped = true
+                                            if trace_skips then
+                                                show_skip(kind,chainname,char,ck,class)
+                                            end
+                                        elseif seq[n][char] then
+                                            n = n + 1
+                                        else
+                                            if discfound then
+                                                notmatchreplace[discfound] = true
+                                                match = not notmatchpre[discfound]
+                                            else
+                                                match = false
+                                            end
+                                            break
+                                        end
+                                    else
+                                        if discfound then
+                                            notmatchreplace[discfound] = true
+                                            match = not notmatchpre[discfound]
+                                        else
+                                            match = false
+                                        end
+                                        break
+                                    end
+                                else
+                                    if discfound then
+                                        notmatchreplace[discfound] = true
+                                        match = not notmatchpre[discfound]
+                                    else
+                                        match = false
+                                    end
+                                    break
+                                end
+                            elseif id == disc_code then
+                                diskseen                 = true
+                                discfound                = current
+                                notmatchpre[current]     = nil
+                                notmatchpost[current]    = true
+                                notmatchreplace[current] = nil
+                                local pre     = getfield(current,"pre")
+                                local replace = getfield(current,"replace")
+                                if pre then
+                                    local n = n
+                                    while pre do
+                                        if seq[n][getchar(pre)] then
+                                            n = n + 1
+                                            pre = getnext(pre)
+                                            if n > s then
+                                                break
+                                            end
+                                        else
+                                            notmatchpre[current] = true
+                                            break
+                                        end
+                                    end
+                                    if n <= s then
+                                        notmatchpre[current] = true
+                                    end
+                                else
+                                    notmatchpre[current] = true
+                                end
+                                if replace then
+                                    -- so far we never entered this branch
+                                    while replace do
+                                        if seq[n][getchar(replace)] then
+                                            n = n + 1
+                                            replace = getnext(replace)
+                                            if n > s then
+                                                break
+                                            end
+                                        else
+                                            notmatchreplace[current] = true
+                                            match = notmatchpre[current]
+                                            break
+                                        end
+                                    end
+                                    if not match then
+                                        break
+                                    end
+                                else
+                                    -- skip 'm
+                                end
+                            elseif seq[n][32] then -- brrr
+                                n = n + 1
+                            else
+                                match = false
+                                break
+                            end
+                            current = getnext(current)
+                        elseif seq[n][32] then
+                            n = n + 1
+                        else
+                            match = false
+                            break
+                        end
+                    end
+                else
+                    match = false
+                end
+            end
+        end
+        if match then
+            -- can lookups be of a different type ?
+            local diskchain = diskseen or sweepnode
+            if trace_contexts then
+                local rule, lookuptype, f, l = ck[1], ck[2], ck[4], ck[5]
+                local char = getchar(start)
+                if ck[9] then
+                    logwarning("%s: rule %s matches at char %s for (%s,%s,%s) chars, lookuptype %a, %a => %a",
+                        cref(kind,chainname),rule,gref(char),f-1,l-f+1,s-l,lookuptype,ck[9],ck[10])
+                else
+                    logwarning("%s: rule %s matches at char %s for (%s,%s,%s) chars, lookuptype %a",
+                        cref(kind,chainname),rule,gref(char),f-1,l-f+1,s-l,lookuptype)
+                end
+            end
+            local chainlookups = ck[6]
+            if chainlookups then
+                local nofchainlookups = #chainlookups
+                -- we can speed this up if needed
+                if nofchainlookups == 1 then
+                    local chainlookupname = chainlookups[1]
+                    local chainlookup = lookuptable[chainlookupname]
+                    if chainlookup then
+                        local chainproc = chainprocs[chainlookup.type]
+                        if chainproc then
+                            local ok
+                            if diskchain then
+                                head, start, ok = chaindisk(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence,chainproc)
+                            else
+                                head, start, ok = chainproc(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence)
+                            end
+                            if ok then
+                                done = true
+                            end
+                        else
+                            logprocess("%s: %s is not yet supported",cref(kind,chainname,chainlookupname),chainlookup.type)
+                        end
+                    else -- shouldn't happen
+                        logprocess("%s is not yet supported",cref(kind,chainname,chainlookupname))
+                    end
+                 else
+                    local i = 1
+                    while start and true do
+                        if skipped then
+                            while true do -- todo: use properties
+                                local char = getchar(start)
+                                local ccd = descriptions[char]
+                                if ccd then
+                                    local class = ccd.class or "base"
+                                    if class == skipmark or class == skipligature or class == skipbase or (markclass and class == "mark" and not markclass[char]) then
+                                        start = getnext(start)
+                                    else
+                                        break
+                                    end
+                                else
+                                    break
+                                end
+                            end
+                        end
+                        -- see remark in ms standard under : LookupType 5: Contextual Substitution Subtable
+                        local chainlookupname = chainlookups[i]
+                        local chainlookup = lookuptable[chainlookupname]
+                        if not chainlookup then
+                            -- we just advance
+                            i = i + 1
+                        else
+                            local chainproc = chainprocs[chainlookup.type]
+                            if not chainproc then
+                                -- actually an error
+                                logprocess("%s: %s is not yet supported",cref(kind,chainname,chainlookupname),chainlookup.type)
+                                i = i + 1
+                            else
+                                local ok, n
+                                if diskchain then
+                                    head, start, ok    = chaindisk(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence,chainproc)
+                                else
+                                    head, start, ok, n = chainproc(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,i,sequence)
+                                end
+                                -- messy since last can be changed !
+                                if ok then
+                                    done = true
+                                    if n and n > 1 then
+                                        -- we have a ligature (cf the spec we advance one but we really need to test it
+                                        -- as there are fonts out there that are fuzzy and have too many lookups:
+                                        --
+                                        -- U+1105 U+119E U+1105 U+119E : sourcehansansklight: script=hang ccmp=yes
+                                        --
+                                        if i + n > nofchainlookups then
+                                         -- if trace_contexts then
+                                         --     logprocess("%s: quitting lookups",cref(kind,chainname))
+                                         -- end
+                                            break
+                                        else
+                                            -- we need to carry one
+                                        end
+                                    end
+                                end
+                                i = i + 1
+                            end
+                        end
+                        if i > nofchainlookups or not start then
+                            break
+                        elseif start then
+                            start = getnext(start)
+                        end
+                    end
+                end
+            else
+                local replacements = ck[7]
+                if replacements then
+                    head, start, done = chainprocs.reversesub(head,start,last,kind,chainname,ck,lookuphash,replacements) -- sequence
+                else
+                    done = quit_on_no_replacement -- can be meant to be skipped / quite inconsistent in fonts
+                    if trace_contexts then
+                        logprocess("%s: skipping match",cref(kind,chainname))
+                    end
+                end
+            end
+            if done then
+                break -- out of contexts (new, needs checking)
+            end
+        end
+    end
+    if diskseen then -- maybe move up so that we can turn checking on/off
+        notmatchpre     = { }
+        notmatchpost    = { }
+        notmatchreplace = { }
+    end
+    return head, start, done
+end
+
+-- Because we want to keep this elsewhere (an because speed is less an issue) we
+-- pass the font id so that the verbose variant can access the relevant helper tables.
+
+local verbose_handle_contextchain = function(font,...)
+    logwarning("no verbose handler installed, reverting to 'normal'")
+    otf.setcontextchain()
+    return normal_handle_contextchain(...)
+end
+
+otf.chainhandlers = {
+    normal  = normal_handle_contextchain,
+    verbose = verbose_handle_contextchain,
+}
+
+local handle_contextchain = nil
+
+-- normal_handle_contextchain(head,start,kind,chainname,contexts,sequence,lookuphash)
+
+function chained_contextchain(head,start,stop,...)
+    local steps    = currentlookup.steps
+    local nofsteps = currentlookup.nofsteps
+    if nofsteps > 1 then
+        reportmoresteps(dataset,sequence)
+    end
+    return handle_contextchain(head,start,...)
+end
+
+function otf.setcontextchain(method)
+    if not method or method == "normal" or not otf.chainhandlers[method] then
+        if handle_contextchain then -- no need for a message while making the format
+            logwarning("installing normal contextchain handler")
+        end
+        handle_contextchain = normal_handle_contextchain
+    else
+        logwarning("installing contextchain handler %a",method)
+        local handler = otf.chainhandlers[method]
+        handle_contextchain = function(...)
+            return handler(currentfont,...) -- hm, get rid of ...
+        end
+    end
+
+    handlers.gsub_context             = handle_contextchain
+    handlers.gsub_contextchain        = handle_contextchain
+    handlers.gsub_reversecontextchain = handle_contextchain
+    handlers.gpos_contextchain        = handle_contextchain
+    handlers.gpos_context             = handle_contextchain
+
+    handlers.contextchain = handle_contextchain
+
+end
+
+chainprocs.gsub_context             = chained_contextchain
+chainprocs.gsub_contextchain        = chained_contextchain
+chainprocs.gsub_reversecontextchain = chained_contextchain
+chainprocs.gpos_contextchain        = chained_contextchain
+chainprocs.gpos_context             = chained_contextchain
+
+otf.setcontextchain()
+
+local missing = { } -- we only report once
+
+local function logprocess(...)
+    if trace_steps then
+        registermessage(...)
+    end
+    report_process(...)
+end
+
+local logwarning = report_process
+
+local function report_missing_cache(typ,lookup)
+    local f = missing[currentfont] if not f then f = { } missing[currentfont] = f end
+    local t = f[typ]               if not t then t = { } f[typ]               = t end
+    if not t[lookup] then
+        t[lookup] = true
+        logwarning("missing cache for lookup %a, type %a, font %a, name %a",lookup,typ,currentfont,tfmdata.properties.fullname)
+    end
+end
+
+local resolved = { } -- we only resolve a font,script,language pair once
+
+-- todo: pass all these 'locals' in a table
+
+local lookuphashes = { }
+
+setmetatableindex(lookuphashes, function(t,font)
+    local lookuphash = fontdata[font].resources.lookuphash
+    if not lookuphash or not next(lookuphash) then
+        lookuphash = false
+    end
+    t[font] = lookuphash
+    return lookuphash
+end)
+
+-- fonts.hashes.lookups = lookuphashes
+
+local autofeatures    = fonts.analyzers.features
+local featuretypes    = otf.tables.featuretypes
+local defaultscript   = otf.features.checkeddefaultscript
+local defaultlanguage = otf.features.checkeddefaultlanguage
+
+local function initialize(sequence,script,language,enabled,autoscript,autolanguage)
+    local features = sequence.features
+    if features then
+        local order = sequence.order
+        if order then
+            local featuretype = featuretypes[sequence.type or "unknown"]
+            for i=1,#order do
+                local kind  = order[i]
+                local valid = enabled[kind]
+                if valid then
+                    local scripts   = features[kind]
+                    local languages = scripts and (
+                        scripts[script] or
+                        scripts[wildcard] or
+                        (autoscript and defaultscript(featuretype,autoscript,scripts))
+                    )
+                    local enabled = languages and (
+                        languages[language] or
+                        languages[wildcard] or
+                        (autolanguage and defaultlanguage(featuretype,autolanguage,languages))
+                    )
+                    if enabled then
+                        return { valid, autofeatures[kind] or false, sequence, kind }
+                    end
+                end
+            end
+        else
+            -- can't happen
+        end
+    end
+    return false
+end
+
+function otf.dataset(tfmdata,font) -- generic variant, overloaded in context
+    local shared       = tfmdata.shared
+    local properties   = tfmdata.properties
+    local language     = properties.language or "dflt"
+    local script       = properties.script   or "dflt"
+    local enabled      = shared.features
+    local autoscript   = enabled and enabled.autoscript
+    local autolanguage = enabled and enabled.autolanguage
+    local res = resolved[font]
+    if not res then
+        res = { }
+        resolved[font] = res
+    end
+    local rs = res[script]
+    if not rs then
+        rs = { }
+        res[script] = rs
+    end
+    local rl = rs[language]
+    if not rl then
+        rl = {
+            -- indexed but we can also add specific data by key
+        }
+        rs[language] = rl
+        local sequences = tfmdata.resources.sequences
+        for s=1,#sequences do
+            local v = enabled and initialize(sequences[s],script,language,enabled,autoscript,autolanguage)
+            if v then
+                rl[#rl+1] = v
+            end
+        end
+    end
+    return rl
+end
+
+-- assumptions:
+--
+-- * languages that use complex disc nodes
+
+local function kernrun(disc,run)
+    --
+    -- we catch <font 1><disc font 2>
+    --
+    if trace_kernruns then
+        report_run("kern") -- will be more detailed
+    end
+    --
+    local prev      = getprev(disc) -- todo, keep these in the main loop
+    local next      = getnext(disc) -- todo, keep these in the main loop
+    --
+    local pre       = getfield(disc,"pre")
+    local post      = getfield(disc,"post")
+    local replace   = getfield(disc,"replace")
+    --
+    local prevmarks = prev
+    --
+    -- can be optional, because why on earth do we get a disc after a mark (okay, maybe when a ccmp
+    -- has happened but then it should be in the disc so basically this test indicates an error)
+    --
+    while prevmarks and getid(prevmarks) == glyph_code and marks[getchar(prevmarks)] and getfont(prevmarks) == currentfont and getsubtype(prevmarks) < 256 do
+        prevmarks = getprev(prevmarks)
+    end
+    --
+    if prev and (pre or replace) and not (getid(prev) == glyph_code and getfont(prev) == currentfont and getsubtype(prev)<256) then
+        prev = false
+    end
+    if next and (post or replace) and not (getid(next) == glyph_code and getfont(next) == currentfont and getsubtype(next)<256) then
+        next = false
+    end
+    --
+    if not pre then
+        -- go on
+    elseif prev then
+        local nest = getprev(pre)
+        setprev(pre,prev)
+        setnext(prev,pre)
+        run(prevmarks,"preinjections")
+        setprev(pre,nest)
+        setnext(prev,disc)
+    else
+        run(pre,"preinjections")
+    end
+    --
+    if not post then
+        -- go on
+    elseif next then
+        local tail = find_node_tail(post)
+        setnext(tail,next)
+        setprev(next,tail)
+        run(post,"postinjections",next)
+        setnext(tail,nil)
+        setprev(next,disc)
+    else
+        run(post,"postinjections")
+    end
+    --
+    if not replace and prev and next then
+        -- this should be already done by discfound
+        setnext(prev,next)
+        setprev(next,prev)
+        run(prevmarks,"injections",next)
+        setnext(prev,disc)
+        setprev(next,disc)
+    elseif prev and next then
+        local tail = find_node_tail(replace)
+        local nest = getprev(replace)
+        setprev(replace,prev)
+        setnext(prev,replace)
+        setnext(tail,next)
+        setprev(next,tail)
+        run(prevmarks,"replaceinjections",next)
+        setprev(replace,nest)
+        setnext(prev,disc)
+        setnext(tail,nil)
+        setprev(next,disc)
+    elseif prev then
+        local nest = getprev(replace)
+        setprev(replace,prev)
+        setnext(prev,replace)
+        run(prevmarks,"replaceinjections")
+        setprev(replace,nest)
+        setnext(prev,disc)
+    elseif next then
+        local tail = find_node_tail(replace)
+        setnext(tail,next)
+        setprev(next,tail)
+        run(replace,"replaceinjections",next)
+        setnext(tail,nil)
+        setprev(next,disc)
+    else
+        run(replace,"replaceinjections")
+    end
+end
+
+-- the if new test might be dangerous as luatex will check / set some tail stuff
+-- in a temp node
+
+local function comprun(disc,run)
+    if trace_compruns then
+        report_run("comp: %s",languages.serializediscretionary(disc))
+    end
+    --
+    local pre = getfield(disc,"pre")
+    if pre then
+        sweepnode = disc
+        sweeptype = "pre" -- in alternative code preinjections is used (also used then for proeprties, saves a variable)
+        local new, done = run(pre)
+        if done then
+            setfield(disc,"pre",new)
+        end
+    end
+    --
+    local post = getfield(disc,"post")
+    if post then
+        sweepnode = disc
+        sweeptype = "post"
+        local new, done = run(post)
+        if done then
+            setfield(disc,"post",new)
+        end
+    end
+    --
+    local replace = getfield(disc,"replace")
+    if replace then
+        sweepnode = disc
+        sweeptype = "replace"
+        local new, done = run(replace)
+        if done then
+            setfield(disc,"replace",new)
+        end
+    end
+    sweepnode = nil
+    sweeptype = nil
+end
+
+local function testrun(disc,trun,crun) -- use helper
+    local next = getnext(disc)
+    if next then
+        local replace = getfield(disc,"replace")
+        if replace then
+            local prev = getprev(disc)
+            if prev then
+                -- only look ahead
+                local tail = find_node_tail(replace)
+             -- local nest = getprev(replace)
+                setnext(tail,next)
+                setprev(next,tail)
+                if trun(replace,next) then
+                    setfield(disc,"replace",nil) -- beware, side effects of nest so first
+                    setnext(prev,replace)
+                    setprev(replace,prev)
+                    setprev(next,tail)
+                    setnext(tail,next)
+                    setprev(disc,nil)
+                    setnext(disc,nil)
+                    flush_node_list(disc)
+                    return replace -- restart
+                else
+                    setnext(tail,nil)
+                    setprev(next,disc)
+                end
+            else
+                -- weird case
+            end
+        else
+            -- no need
+        end
+    else
+        -- weird case
+    end
+    comprun(disc,crun)
+    return next
+end
+
+local function discrun(disc,drun,krun)
+    local next = getnext(disc)
+    local prev = getprev(disc)
+    if trace_discruns then
+        report_run("disc") -- will be more detailed
+    end
+    if next and prev then
+        setnext(prev,next)
+     -- setprev(next,prev)
+        drun(prev)
+        setnext(prev,disc)
+     -- setprev(next,disc)
+    end
+    --
+    local pre = getfield(disc,"pre")
+    if not pre then
+        -- go on
+    elseif prev then
+        local nest = getprev(pre)
+        setprev(pre,prev)
+        setnext(prev,pre)
+        krun(prev,"preinjections")
+        setprev(pre,nest)
+        setnext(prev,disc)
+    else
+        krun(pre,"preinjections")
+    end
+    return next
+end
+
+-- todo: maybe run lr and rl stretches
+
+local function featuresprocessor(head,font,attr)
+
+    local lookuphash = lookuphashes[font] -- we can also check sequences here
+
+    if not lookuphash then
+        return head, false
+    end
+
+    head = tonut(head)
+
+    if trace_steps then
+        checkstep(head)
+    end
+
+    tfmdata         = fontdata[font]
+    descriptions    = tfmdata.descriptions
+    characters      = tfmdata.characters
+    resources       = tfmdata.resources
+
+    marks           = resources.marks
+    anchorlookups   = resources.lookup_to_anchor
+    lookuptable     = resources.lookups
+    lookuptypes     = resources.lookuptypes
+    lookuptags      = resources.lookuptags
+
+    currentfont     = font
+    rlmode          = 0
+    sweephead       = { }
+
+    local sequences = resources.sequences
+    local done      = false
+    local datasets  = otf.dataset(tfmdata,font,attr)
+
+    local dirstack  = { } -- could move outside function
+
+    -- We could work on sub start-stop ranges instead but I wonder if there is that
+    -- much speed gain (experiments showed that it made not much sense) and we need
+    -- to keep track of directions anyway. Also at some point I want to play with
+    -- font interactions and then we do need the full sweeps.
+
+    -- Keeping track of the headnode is needed for devanagari (I generalized it a bit
+    -- so that multiple cases are also covered.)
+
+    -- We don't goto the next node of a disc node is created so that we can then treat
+    -- the pre, post and replace. It's abit of a hack but works out ok for most cases.
+
+    -- there can be less subtype and attr checking in the comprun etc helpers
+
+    for s=1,#datasets do
+        local dataset      = datasets[s]
+              featurevalue = dataset[1] -- todo: pass to function instead of using a global
+        local attribute    = dataset[2]
+        local sequence     = dataset[3] -- sequences[s] -- also dataset[5]
+        local kind         = dataset[4]
+        ----- chain        = dataset[5] -- sequence.chain or 0
+        local rlparmode    = 0
+        local topstack     = 0
+        local success      = false
+        local typ          = sequence.type
+        local gpossing     = typ == "gpos_single" or typ == "gpos_pair" -- maybe all of them
+        local subtables    = sequence.subtables
+        local handler      = handlers[typ]
+        if typ == "gsub_reversecontextchain" then -- chain < 0
+            -- this is a limited case, no special treatments like 'init' etc
+            -- we need to get rid of this slide! probably no longer needed in latest luatex
+            local start = find_node_tail(head) -- slow (we can store tail because there's always a skip at the end): todo
+            while start do
+                local id = getid(start)
+                if id == glyph_code then
+                    if getfont(start) == font and getsubtype(start) < 256 then
+                        local a = getattr(start,0)
+                        if a then
+                            a = a == attr
+                        else
+                            a = true
+                        end
+                        if a then
+                            local char = getchar(start)
+                            for i=1,#subtables do
+                                local lookupname = subtables[i]
+                                local lookupcache = lookuphash[lookupname]
+                                if lookupcache then
+                                    local lookupmatch = lookupcache[char]
+                                    if lookupmatch then
+                                        -- todo: disc?
+                                        head, start, success = handler(head,start,kind,lookupname,lookupmatch,sequence,lookuphash,i)
+                                        if success then
+                                            break
+                                        end
+                                    end
+                                else
+                                    report_missing_cache(typ,lookupname)
+                                end
+                            end
+                            if start then start = getprev(start) end
+                        else
+                            start = getprev(start)
+                        end
+                    else
+                        start = getprev(start)
+                    end
+                else
+                    start = getprev(start)
+                end
+            end
+        else
+            local ns = #subtables
+            local start = head -- local ?
+            rlmode = 0 -- to be checked ?
+            if ns == 1 then -- happens often
+                local lookupname  = subtables[1]
+                local lookupcache = lookuphash[lookupname]
+                if not lookupcache then -- also check for empty cache
+                    report_missing_cache(typ,lookupname)
+                else
+
+                    local function c_run(head) -- no need to check for 256 and attr probably also the same
+                        local done  = false
+                        local start = sweephead[head]
+                        if start then
+                            sweephead[head] = nil
+                        else
+                            start = head
+                        end
+                        while start do
+                            local id = getid(start)
+                            if id ~= glyph_code then
+                                -- very unlikely
+                                start = getnext(start)
+                            elseif getfont(start) == font and getsubtype(start) < 256 then
+                                local a = getattr(start,0)
+                                if a then
+                                    a = (a == attr) and (not attribute or getprop(start,a_state) == attribute)
+                                else
+                                    a = not attribute or getprop(start,a_state) == attribute
+                                end
+                                if a then
+                                    local lookupmatch = lookupcache[getchar(start)]
+                                    if lookupmatch then
+                                        -- sequence kan weg
+                                        local ok
+                                        head, start, ok = handler(head,start,kind,lookupname,lookupmatch,sequence,lookuphash,1)
+                                        if ok then
+                                            done = true
+                                        end
+                                    end
+                                    if start then start = getnext(start) end
+                                else
+                                    start = getnext(start)
+                                end
+                            else
+                                return head, false
+                            end
+                        end
+                        if done then
+                            success = true -- needed in this subrun?
+                        end
+                        return head, done
+                    end
+
+                    local function t_run(start,stop)
+                        while start ~= stop do
+                            local id = getid(start)
+                            if id == glyph_code and getfont(start) == font and getsubtype(start) < 256 then
+                                local a = getattr(start,0)
+                                if a then
+                                    a = (a == attr) and (not attribute or getprop(start,a_state) == attribute)
+                                else
+                                    a = not attribute or getprop(start,a_state) == attribute
+                                end
+                                if a then
+                                    local lookupmatch = lookupcache[getchar(start)]
+                                    if lookupmatch then -- hm, hyphens can match (tlig) so we need to really check
+                                        -- if we need more than ligatures we can outline the code and use functions
+                                        local s = getnext(start)
+                                        local l = nil
+                                        while s do
+                                            local lg = lookupmatch[getchar(s)]
+                                            if lg then
+                                                l = lg
+                                                s = getnext(s)
+                                            else
+                                                break
+                                            end
+                                        end
+                                        if l and l.ligature then
+                                            return true
+                                        end
+                                    end
+                                end
+                                start = getnext(start)
+                            else
+                                break
+                            end
+                        end
+                    end
+
+                    local function d_run(prev) -- we can assume that prev and next are glyphs
+                        local a = getattr(prev,0)
+                        if a then
+                            a = (a == attr) and (not attribute or getprop(prev,a_state) == attribute)
+                        else
+                            a = not attribute or getprop(prev,a_state) == attribute
+                        end
+                        if a then
+                            local lookupmatch = lookupcache[getchar(prev)]
+                            if lookupmatch then
+                                -- sequence kan weg
+                                local h, d, ok = handler(head,prev,kind,lookupname,lookupmatch,sequence,lookuphash,1)
+                                if ok then
+                                    done    = true
+                                    success = true
+                                end
+                            end
+                        end
+                    end
+
+                    local function k_run(sub,injection,last)
+                        local a = getattr(sub,0)
+                        if a then
+                            a = (a == attr) and (not attribute or getprop(sub,a_state) == attribute)
+                        else
+                            a = not attribute or getprop(sub,a_state) == attribute
+                        end
+                        if a then
+                            -- sequence kan weg
+                            for n in traverse_nodes(sub) do -- only gpos
+                                if n == last then
+                                    break
+                                end
+                                local id = getid(n)
+                                if id == glyph_code then
+                                    local lookupmatch = lookupcache[getchar(n)]
+                                    if lookupmatch then
+                                        local h, d, ok = handler(sub,n,kind,lookupname,lookupmatch,sequence,lookuphash,1,injection)
+                                        if ok then
+                                            done    = true
+                                            success = true
+                                        end
+                                    end
+                                else
+                                    -- message
+                                end
+                            end
+                        end
+                    end
+
+                    while start do
+                        local id = getid(start)
+                        if id == glyph_code then
+                            if getfont(start) == font and getsubtype(start) < 256 then -- why a 256 test ...
+                                local a = getattr(start,0)
+                                if a then
+                                    a = (a == attr) and (not attribute or getprop(start,a_state) == attribute)
+                                else
+                                    a = not attribute or getprop(start,a_state) == attribute
+                                end
+                                if a then
+                                    local char        = getchar(start)
+                                    local lookupmatch = lookupcache[char]
+                                    if lookupmatch then
+                                        -- sequence kan weg
+                                        local ok
+                                        head, start, ok = handler(head,start,kind,lookupname,lookupmatch,sequence,lookuphash,1)
+                                        if ok then
+                                            success = true
+                                        elseif gpossing and zwnjruns and char == zwnj then
+                                            discrun(start,d_run)
+                                        end
+                                    elseif gpossing and zwnjruns and char == zwnj then
+                                        discrun(start,d_run)
+                                    end
+                                    if start then start = getnext(start) end
+                                else
+                                   start = getnext(start)
+                                end
+                            else
+                                start = getnext(start)
+                            end
+                        elseif id == disc_code then
+                            if gpossing then
+                                kernrun(start,k_run)
+                                start = getnext(start)
+                            elseif typ == "gsub_ligature" then
+                                start = testrun(start,t_run,c_run)
+                            else
+                                comprun(start,c_run)
+                                start = getnext(start)
+                            end
+                        elseif id == math_code then
+                            start = getnext(end_of_math(start))
+                        elseif id == dir_code then
+                            local dir = getfield(start,"dir")
+                            if dir == "+TLT" then
+                                topstack = topstack + 1
+                                dirstack[topstack] = dir
+                                rlmode = 1
+                            elseif dir == "+TRT" then
+                                topstack = topstack + 1
+                                dirstack[topstack] = dir
+                                rlmode = -1
+                            elseif dir == "-TLT" or dir == "-TRT" then
+                                topstack = topstack - 1
+                                rlmode = dirstack[topstack] == "+TRT" and -1 or 1
+                            else
+                                rlmode = rlparmode
+                            end
+                            if trace_directions then
+                                report_process("directions after txtdir %a: parmode %a, txtmode %a, # stack %a, new dir %a",dir,rlparmode,rlmode,topstack,newdir)
+                            end
+                            start = getnext(start)
+                        elseif id == localpar_code then
+                            local dir = getfield(start,"dir")
+                            if dir == "TRT" then
+                                rlparmode = -1
+                            elseif dir == "TLT" then
+                                rlparmode = 1
+                            else
+                                rlparmode = 0
+                            end
+                            -- one might wonder if the par dir should be looked at, so we might as well drop the next line
+                            rlmode = rlparmode
+                            if trace_directions then
+                                report_process("directions after pardir %a: parmode %a, txtmode %a",dir,rlparmode,rlmode)
+                            end
+                            start = getnext(start)
+                        else
+                            start = getnext(start)
+                        end
+                    end
+                end
+
+            else
+
+                local function c_run(head)
+                    local done  = false
+                    local start = sweephead[head]
+                    if start then
+                        sweephead[head] = nil
+                    else
+                        start = head
+                    end
+                    while start do
+                        local id = getid(start)
+                        if id ~= glyph_code then
+                            -- very unlikely
+                            start = getnext(start)
+                        elseif getfont(start) == font and getsubtype(start) < 256 then
+                            local a = getattr(start,0)
+                            if a then
+                                a = (a == attr) and (not attribute or getprop(start,a_state) == attribute)
+                            else
+                                a = not attribute or getprop(start,a_state) == attribute
+                            end
+                            if a then
+                                local char = getchar(start)
+                                for i=1,ns do
+                                    local lookupname = subtables[i]
+                                    local lookupcache = lookuphash[lookupname]
+                                    if lookupcache then
+                                        local lookupmatch = lookupcache[char]
+                                        if lookupmatch then
+                                            -- we could move all code inline but that makes things even more unreadable
+                                            local ok
+                                            head, start, ok = handler(head,start,kind,lookupname,lookupmatch,sequence,lookuphash,i)
+                                            if ok then
+                                                done = true
+                                                break
+                                            elseif not start then
+                                                -- don't ask why ... shouldn't happen
+                                                break
+                                            end
+                                        end
+                                    else
+                                        report_missing_cache(typ,lookupname)
+                                    end
+                                end
+                                if start then start = getnext(start) end
+                            else
+                                start = getnext(start)
+                            end
+                        else
+                            return head, false
+                        end
+                    end
+                    if done then
+                        success = true
+                    end
+                    return head, done
+                end
+
+                local function d_run(prev)
+                    local a = getattr(prev,0)
+                    if a then
+                        a = (a == attr) and (not attribute or getprop(prev,a_state) == attribute)
+                    else
+                        a = not attribute or getprop(prev,a_state) == attribute
+                    end
+                    if a then
+                        -- brr prev can be disc
+                        local char = getchar(prev)
+                        for i=1,ns do
+                            local lookupname  = subtables[i]
+                            local lookupcache = lookuphash[lookupname]
+                            if lookupcache then
+                                local lookupmatch = lookupcache[char]
+                                if lookupmatch then
+                                    -- we could move all code inline but that makes things even more unreadable
+                                    local h, d, ok = handler(head,prev,kind,lookupname,lookupmatch,sequence,lookuphash,i)
+                                    if ok then
+                                        done = true
+                                        break
+                                    end
+                                end
+                            else
+                                report_missing_cache(typ,lookupname)
+                            end
+                        end
+                    end
+                end
+
+               local function k_run(sub,injection,last)
+                    local a = getattr(sub,0)
+                    if a then
+                        a = (a == attr) and (not attribute or getprop(sub,a_state) == attribute)
+                    else
+                        a = not attribute or getprop(sub,a_state) == attribute
+                    end
+                    if a then
+                        for n in traverse_nodes(sub) do -- only gpos
+                            if n == last then
+                                break
+                            end
+                            local id = getid(n)
+                            if id == glyph_code then
+                                local char = getchar(n)
+                                for i=1,ns do
+                                    local lookupname  = subtables[i]
+                                    local lookupcache = lookuphash[lookupname]
+                                    if lookupcache then
+                                        local lookupmatch = lookupcache[char]
+                                        if lookupmatch then
+                                            local h, d, ok = handler(head,n,kind,lookupname,lookupmatch,sequence,lookuphash,i,injection)
+                                            if ok then
+                                                done = true
+                                                break
+                                            end
+                                        end
+                                    else
+                                        report_missing_cache(typ,lookupname)
+                                    end
+                                end
+                            else
+                                -- message
+                            end
+                        end
+                    end
+                end
+
+                local function t_run(start,stop)
+                    while start ~= stop do
+                        local id = getid(start)
+                        if id == glyph_code and getfont(start) == font and getsubtype(start) < 256 then
+                            local a = getattr(start,0)
+                            if a then
+                                a = (a == attr) and (not attribute or getprop(start,a_state) == attribute)
+                            else
+                                a = not attribute or getprop(start,a_state) == attribute
+                            end
+                            if a then
+                                local char = getchar(start)
+                                for i=1,ns do
+                                    local lookupname  = subtables[i]
+                                    local lookupcache = lookuphash[lookupname]
+                                    if lookupcache then
+                                        local lookupmatch = lookupcache[char]
+                                        if lookupmatch then
+                                            -- if we need more than ligatures we can outline the code and use functions
+                                            local s = getnext(start)
+                                            local l = nil
+                                            while s do
+                                                local lg = lookupmatch[getchar(s)]
+                                                if lg then
+                                                    l = lg
+                                                    s = getnext(s)
+                                                else
+                                                    break
+                                                end
+                                            end
+                                            if l and l.ligature then
+                                                return true
+                                            end
+                                        end
+                                    else
+                                        report_missing_cache(typ,lookupname)
+                                    end
+                                end
+                            end
+                            start = getnext(start)
+                        else
+                            break
+                        end
+                    end
+                end
+
+                while start do
+                    local id = getid(start)
+                    if id == glyph_code then
+                        if getfont(start) == font and getsubtype(start) < 256 then
+                            local a = getattr(start,0)
+                            if a then
+                                a = (a == attr) and (not attribute or getprop(start,a_state) == attribute)
+                            else
+                                a = not attribute or getprop(start,a_state) == attribute
+                            end
+                            if a then
+                                for i=1,ns do
+                                    local lookupname  = subtables[i]
+                                    local lookupcache = lookuphash[lookupname]
+                                    if lookupcache then
+                                        local char = getchar(start)
+                                        local lookupmatch = lookupcache[char]
+                                        if lookupmatch then
+                                            -- we could move all code inline but that makes things even more unreadable
+                                            local ok
+                                            head, start, ok = handler(head,start,kind,lookupname,lookupmatch,sequence,lookuphash,i)
+                                            if ok then
+                                                success = true
+                                                break
+                                            elseif not start then
+                                                -- don't ask why ... shouldn't happen
+                                                break
+                                            elseif gpossing and zwnjruns and char == zwnj then
+                                                discrun(start,d_run)
+                                            end
+                                        elseif gpossing and zwnjruns and char == zwnj then
+                                            discrun(start,d_run)
+                                        end
+                                    else
+                                        report_missing_cache(typ,lookupname)
+                                    end
+                                end
+                                if start then start = getnext(start) end
+                            else
+                                start = getnext(start)
+                            end
+                        else
+                            start = getnext(start)
+                        end
+                    elseif id == disc_code then
+                        if gpossing then
+                            kernrun(start,k_run)
+                            start = getnext(start)
+                        elseif typ == "gsub_ligature" then
+                            start = testrun(start,t_run,c_run)
+                        else
+                            comprun(start,c_run)
+                            start = getnext(start)
+                        end
+                    elseif id == math_code then
+                        start = getnext(end_of_math(start))
+                    elseif id == dir_code then
+                        local dir = getfield(start,"dir")
+                        if dir == "+TLT" then
+                            topstack = topstack + 1
+                            dirstack[topstack] = dir
+                            rlmode = 1
+                        elseif dir == "+TRT" then
+                            topstack = topstack + 1
+                            dirstack[topstack] = dir
+                            rlmode = -1
+                        elseif dir == "-TLT" or dir == "-TRT" then
+                            topstack = topstack - 1
+                            rlmode = dirstack[topstack] == "+TRT" and -1 or 1
+                        else
+                            rlmode = rlparmode
+                        end
+                        if trace_directions then
+                            report_process("directions after txtdir %a: parmode %a, txtmode %a, # stack %a, new dir %a",dir,rlparmode,rlmode,topstack,newdir)
+                        end
+                        start = getnext(start)
+                    elseif id == localpar_code then
+                        local dir = getfield(start,"dir")
+                        if dir == "TRT" then
+                            rlparmode = -1
+                        elseif dir == "TLT" then
+                            rlparmode = 1
+                        else
+                            rlparmode = 0
+                        end
+                        rlmode = rlparmode
+                        if trace_directions then
+                            report_process("directions after pardir %a: parmode %a, txtmode %a",dir,rlparmode,rlmode)
+                        end
+                        start = getnext(start)
+                    else
+                        start = getnext(start)
+                    end
+                end
+            end
+        end
+        if success then
+            done = true
+        end
+        if trace_steps then -- ?
+            registerstep(head)
+        end
+
+    end
+
+    head = tonode(head)
+
+    return head, done
+end
+
+-- this might move to the loader
+
+local function generic(lookupdata,lookupname,unicode,lookuphash)
+    local target = lookuphash[lookupname]
+    if target then
+        target[unicode] = lookupdata
+    else
+        lookuphash[lookupname] = { [unicode] = lookupdata }
+    end
+end
+
+local function ligature(lookupdata,lookupname,unicode,lookuphash)
+    local target = lookuphash[lookupname]
+    if not target then
+        target = { }
+        lookuphash[lookupname] = target
+    end
+    for i=1,#lookupdata do
+        local li = lookupdata[i]
+        local tu = target[li]
+        if not tu then
+            tu = { }
+            target[li] = tu
+        end
+        target = tu
+    end
+    target.ligature = unicode
+end
+
+local function pair(lookupdata,lookupname,unicode,lookuphash)
+    local target = lookuphash[lookupname]
+    if not target then
+        target = { }
+        lookuphash[lookupname] = target
+    end
+    local others = target[unicode]
+    local paired = lookupdata[1]
+    if others then
+        others[paired] = lookupdata
+    else
+        others = { [paired] = lookupdata }
+        target[unicode] = others
+    end
+end
+
+local action = {
+    substitution = generic,
+    multiple     = generic,
+    alternate    = generic,
+    position     = generic,
+    ligature     = ligature,
+    pair         = pair,
+    kern         = pair,
+}
+
+local function prepare_lookups(tfmdata)
+
+    local rawdata          = tfmdata.shared.rawdata
+    local resources        = rawdata.resources
+    local lookuphash       = resources.lookuphash
+    local anchor_to_lookup = resources.anchor_to_lookup
+    local lookup_to_anchor = resources.lookup_to_anchor
+    local lookuptypes      = resources.lookuptypes
+    local characters       = tfmdata.characters
+    local descriptions     = tfmdata.descriptions
+    local duplicates       = resources.duplicates
+
+    -- we cannot free the entries in the descriptions as sometimes we access
+    -- then directly (for instance anchors) ... selectively freeing does save
+    -- much memory as it's only a reference to a table and the slot in the
+    -- description hash is not freed anyway
+
+    -- we can delay this using metatables so that we don't make the hashes for
+    -- features we don't use but then we need to loop over the characters
+    -- many times so we gain nothing
+
+    for unicode, character in next, characters do -- we cannot loop over descriptions !
+
+        local description = descriptions[unicode]
+
+        if description then
+
+            local lookups = description.slookups
+            if lookups then
+                for lookupname, lookupdata in next, lookups do
+                    action[lookuptypes[lookupname]](lookupdata,lookupname,unicode,lookuphash,duplicates)
+                end
+            end
+
+            local lookups = description.mlookups
+            if lookups then
+                for lookupname, lookuplist in next, lookups do
+                    local lookuptype = lookuptypes[lookupname]
+                    for l=1,#lookuplist do
+                        local lookupdata = lookuplist[l]
+                        action[lookuptype](lookupdata,lookupname,unicode,lookuphash,duplicates)
+                    end
+                end
+            end
+
+            local list = description.kerns
+            if list then
+                for lookup, krn in next, list do  -- ref to glyph, saves lookup
+                    local target = lookuphash[lookup]
+                    if target then
+                        target[unicode] = krn
+                    else
+                        lookuphash[lookup] = { [unicode] = krn }
+                    end
+                end
+            end
+
+            local list = description.anchors
+            if list then
+                for typ, anchors in next, list do -- types
+                    if typ == "mark" or typ == "cexit" then -- or entry?
+                        for name, anchor in next, anchors do
+                            local lookups = anchor_to_lookup[name]
+                            if lookups then
+                                for lookup in next, lookups do
+                                    local target = lookuphash[lookup]
+                                    if target then
+                                        target[unicode] = anchors
+                                    else
+                                        lookuphash[lookup] = { [unicode] = anchors }
+                                    end
+                                end
+                            end
+                        end
+                    end
+                end
+            end
+
+        end
+
+    end
+
+end
+
+-- so far
+
+local function split(replacement,original)
+    local result = { }
+    for i=1,#replacement do
+        result[original[i]] = replacement[i]
+    end
+    return result
+end
+
+local valid = { -- does contextpos work?
+    coverage        = { chainsub = true, chainpos = true, contextsub = true, contextpos = true },
+    reversecoverage = { reversesub = true },
+    glyphs          = { chainsub = true, chainpos = true, contextsub = true, contextpos = true },
+}
+
+local function prepare_contextchains(tfmdata)
+    local rawdata    = tfmdata.shared.rawdata
+    local resources  = rawdata.resources
+    local lookuphash = resources.lookuphash
+    local lookuptags = resources.lookuptags
+    local lookups    = rawdata.lookups
+    if lookups then
+        for lookupname, lookupdata in next, rawdata.lookups do
+            local lookuptype = lookupdata.type
+            if lookuptype then
+                local rules = lookupdata.rules
+                if rules then
+                    local format = lookupdata.format
+                    local validformat = valid[format]
+                    if not validformat then
+                        report_prepare("unsupported format %a",format)
+                    elseif not validformat[lookuptype] then
+                        -- todo: dejavu-serif has one (but i need to see what use it has)
+                        report_prepare("unsupported format %a, lookuptype %a, lookupname %a",format,lookuptype,lookuptags[lookupname])
+                    else
+                        local contexts = lookuphash[lookupname]
+                        if not contexts then
+                            contexts = { }
+                            lookuphash[lookupname] = contexts
+                        end
+                        local t, nt = { }, 0
+                        for nofrules=1,#rules do
+                            local rule         = rules[nofrules]
+                            local current      = rule.current
+                            local before       = rule.before
+                            local after        = rule.after
+                            local replacements = rule.replacements
+                            local sequence     = { }
+                            local nofsequences = 0
+                            -- Eventually we can store start, stop and sequence in the cached file
+                            -- but then less sharing takes place so best not do that without a lot
+                            -- of profiling so let's forget about it.
+                            if before then
+                                for n=1,#before do
+                                    nofsequences = nofsequences + 1
+                                    sequence[nofsequences] = before[n]
+                                end
+                            end
+                            local start = nofsequences + 1
+                            for n=1,#current do
+                                nofsequences = nofsequences + 1
+                                sequence[nofsequences] = current[n]
+                            end
+                            local stop = nofsequences
+                            if after then
+                                for n=1,#after do
+                                    nofsequences = nofsequences + 1
+                                    sequence[nofsequences] = after[n]
+                                end
+                            end
+                            if sequence[1] then
+                                -- Replacements only happen with reverse lookups as they are single only. We
+                                -- could pack them into current (replacement value instead of true) and then
+                                -- use sequence[start] instead but it's somewhat ugly.
+                                nt = nt + 1
+                                t[nt] = { nofrules, lookuptype, sequence, start, stop, rule.lookups, replacements }
+                                for unic in next, sequence[start] do
+                                    local cu = contexts[unic]
+                                    if not cu then
+                                        contexts[unic] = t
+                                    end
+                                end
+                            end
+                        end
+                    end
+                else
+                    -- no rules
+                end
+            else
+                report_prepare("missing lookuptype for lookupname %a",lookuptags[lookupname])
+            end
+        end
+    end
+end
+
+-- we can consider lookuphash == false (initialized but empty) vs lookuphash == table
+
+local function featuresinitializer(tfmdata,value)
+    if true then -- value then
+        -- beware we need to use the topmost properties table
+        local rawdata    = tfmdata.shared.rawdata
+        local properties = rawdata.properties
+        if not properties.initialized then
+            local starttime = trace_preparing and os.clock()
+            local resources = rawdata.resources
+            resources.lookuphash = resources.lookuphash or { }
+            prepare_contextchains(tfmdata)
+            prepare_lookups(tfmdata)
+            properties.initialized = true
+            if trace_preparing then
+                report_prepare("preparation time is %0.3f seconds for %a",os.clock()-starttime,tfmdata.properties.fullname)
+            end
+        end
+    end
+end
+
+registerotffeature {
+    name         = "features",
+    description  = "features",
+    default      = true,
+    initializers = {
+        position = 1,
+        node     = featuresinitializer,
+    },
+    processors   = {
+        node     = featuresprocessor,
+    }
+}
+
+-- This can be used for extra handlers, but should be used with care!
+
+otf.handlers = handlers
diff --git a/src/fontloader/misc/fontloader-fonts-def.lua b/src/fontloader/misc/fontloader-fonts-def.lua
index 0c2f0db..f0941ec 100644
--- a/src/fontloader/misc/fontloader-fonts-def.lua
+++ b/src/fontloader/misc/fontloader-fonts-def.lua
@@ -1,4 +1,4 @@
-if not modules then modules = { } end modules ['luatex-font-def'] = {
+if not modules then modules = { } end modules ['luatex-fonts-def'] = {
     version   = 1.001,
     comment   = "companion to luatex-*.tex",
     author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
diff --git a/src/fontloader/misc/fontloader-fonts-ota.lua b/src/fontloader/misc/fontloader-fonts-ota.lua
index f083fe0..256ead5 100644
--- a/src/fontloader/misc/fontloader-fonts-ota.lua
+++ b/src/fontloader/misc/fontloader-fonts-ota.lua
@@ -1,4 +1,4 @@
-if not modules then modules = { } end modules ['font-otx'] = {
+if not modules then modules = { } end modules ['luatex-fonts-ota'] = {
     version   = 1.001,
     comment   = "companion to font-otf.lua (analysing)",
     author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
@@ -24,7 +24,6 @@ local methods             = allocate()
 
 analyzers.initializers    = initializers
 analyzers.methods         = methods
-analyzers.useunicodemarks = false
 
 local a_state             = attributes.private('state')
 
@@ -98,8 +97,9 @@ local features = {
     pstf = s_pstf,
 }
 
-analyzers.states   = states
-analyzers.features = features
+analyzers.states          = states
+analyzers.features        = features
+analyzers.useunicodemarks = false
 
 -- todo: analyzers per script/lang, cross font, so we need an font id hash -> script
 -- e.g. latin -> hyphenate, arab -> 1/2/3 analyze -- its own namespace
@@ -117,7 +117,10 @@ function analyzers.setstate(head,font)
             local char = getchar(current)
             local d = descriptions[char]
             if d then
-                if d.class == "mark" or (useunicodemarks and categories[char] == "mn") then
+                if d.class == "mark" then
+                    done = true
+                    setprop(current,a_state,s_mark)
+                elseif useunicodemarks and categories[char] == "mn" then
                     done = true
                     setprop(current,a_state,s_mark)
                 elseif n == 0 then
@@ -136,7 +139,9 @@ function analyzers.setstate(head,font)
                 first, last, n = nil, nil, 0
             end
         elseif id == disc_code then
-            -- always in the middle
+            -- always in the middle .. it doesn't make much sense to assign a property
+            -- here ... we might at some point decide to flag the components when present
+            -- but even then it's kind of bogus
             setprop(current,a_state,s_medi)
             last = current
         else -- finish
@@ -213,17 +218,6 @@ registerotffeature {
 
 methods.latn = analyzers.setstate
 
--- This info eventually can go into char-def and we will have a state
--- table for generic then (unicode recognized all states but in practice
--- only has only
---
--- isolated : isol
--- final    : isol_fina
--- medial   : isol_fina_medi_init
---
--- so in practice, without analyzer it's rather useless info which is
--- why having it in char-def makes only sense for special purposes (like)
--- like tracing cq. visualizing.
 
 local tatweel = 0x0640
 local zwnj    = 0x200C
@@ -344,8 +338,6 @@ local medial = { -- isol_fina_medi_init
 
 local arab_warned = { }
 
--- todo: gref
-
 local function warning(current,what)
     local char = getchar(current)
     if not arab_warned[char] then
diff --git a/src/fontloader/misc/fontloader-fonts.lua b/src/fontloader/misc/fontloader-fonts.lua
index f18ba35..2e34fb8 100644
--- a/src/fontloader/misc/fontloader-fonts.lua
+++ b/src/fontloader/misc/fontloader-fonts.lua
@@ -215,9 +215,11 @@ if non_generic_context.luatex_fonts.skip_loading ~= true then
         loadmodule('font-oti.lua')
         loadmodule('font-otf.lua')
         loadmodule('font-otb.lua')
-        loadmodule('luatex-fonts-inj.lua') -- normally the same as font-inj.lua
+        ----------('luatex-fonts-inj.lua') -- normally the same as font-inj.lua / beware loadmodule is parsed
+        loadmodule('font-inj.lua')
         loadmodule('luatex-fonts-ota.lua')
-        loadmodule('luatex-fonts-otn.lua') -- normally the same as font-otn.lua
+        ----------('luatex-fonts-otn.lua') -- normally the same as font-otn.lua / beware loadmodule is parsed
+        loadmodule('font-otn.lua')
         loadmodule('font-otp.lua')
         loadmodule('luatex-fonts-lua.lua')
         loadmodule('font-def.lua')         -- this code (stripped) might end up in luatex-fonts-def.lua
diff --git a/src/fontloader/misc/fontloader-languages.lua b/src/fontloader/misc/fontloader-languages.lua
index 1ea8c1f..cecd60c 100644
--- a/src/fontloader/misc/fontloader-languages.lua
+++ b/src/fontloader/misc/fontloader-languages.lua
@@ -16,28 +16,33 @@ function languages.loadpatterns(tag)
     if not loaded[tag] then
         loaded[tag] = 0
         local filename = kpse.find_file("lang-" .. tag .. ".lua")
-        if filename and filename == "" then
-            print("<unknown language file for: " .. tag .. ">")
+        if not filename or filename == "" then
+            texio.write("<unknown language file for: " .. tag .. ">")
         else
             local whatever = loadfile(filename)
             if type(whatever) == "function" then
                 whatever = whatever()
                 if type(whatever) == "table" then
+                    texio.write("<language file: " .. tag .. ">")
                     local characters = whatever.patterns.characters or ""
                     local patterns = whatever.patterns.data or ""
                     local exceptions = whatever.exceptions.data or ""
-                    local language = lang.new()
                     for b in string.utfvalues(characters) do
+                        -- what about uppercase
+-- lang.sethjcode(b,b)
                         tex.setlccode(b,b)
                     end
+                    local language = lang.new()
                     lang.patterns(language, patterns)
                     lang.hyphenation(language, exceptions)
                     loaded[tag] = lang.id(language)
                 else
-                    print("<invalid language table: " .. tag .. ">")
+                    texio.write("<invalid language table: " .. tag .. ">")
+                    os.exit()
                 end
             else
-                print("<invalid language file: " .. tag .. ">")
+                texio.write("<invalid language file: " .. tag .. ">")
+                os.exit()
             end
         end
     end
diff --git a/src/fontloader/misc/fontloader-languages.tex b/src/fontloader/misc/fontloader-languages.tex
index 9778da3..a087b30 100644
--- a/src/fontloader/misc/fontloader-languages.tex
+++ b/src/fontloader/misc/fontloader-languages.tex
@@ -8,6 +8,8 @@
 
 %D Cf. discussion on \CONTEXT\ list:
 
+% \savinghyphcodes1
+
 \directlua {
     dofile(kpse.find_file("luatex-languages.lua","tex"))
 }
diff --git a/src/fontloader/runtime/fontloader-reference.lua b/src/fontloader/runtime/fontloader-reference.lua
index ae36617..46de1d0 100644
--- a/src/fontloader/runtime/fontloader-reference.lua
+++ b/src/fontloader/runtime/fontloader-reference.lua
@@ -1,6 +1,6 @@
--- merged file : luatex-fonts-merged.lua
--- parent file : luatex-fonts.lua
--- merge date  : 11/19/15 19:13:15
+-- merged file : c:/data/develop/context/sources/luatex-fonts-merged.lua
+-- parent file : c:/data/develop/context/sources/luatex-fonts.lua
+-- merge date  : 12/21/15 16:29:15
 
 do -- begin closure to overcome local limits and interference
 
@@ -3901,15 +3901,21 @@ end
 nodes={}
 nodes.pool={}
 nodes.handlers={}
-local nodecodes={} for k,v in next,node.types  () do nodecodes[string.gsub(v,"_","")]=k end
-local whatcodes={} for k,v in next,node.whatsits() do whatcodes[string.gsub(v,"_","")]=k end
-local glyphcodes={ [0]="character","glyph","ligature","ghost","left","right" }
-local disccodes={ [0]="discretionary","explicit","automatic","regular","first","second" }
-for i=0,#glyphcodes do glyphcodes[glyphcodes[i]]=i end
-for i=0,#disccodes do disccodes [disccodes [i]]=i end
+local nodecodes={}
+local glyphcodes=node.subtypes("glyph")
+local disccodes=node.subtypes("disc")
+for k,v in next,node.types() do
+  v=string.gsub(v,"_","")
+  nodecodes[k]=v
+  nodecodes[v]=k
+end
+for i=0,#glyphcodes do
+  glyphcodes[glyphcodes[i]]=i
+end
+for i=0,#disccodes do
+  disccodes[disccodes[i]]=i
+end
 nodes.nodecodes=nodecodes
-nodes.whatcodes=whatcodes
-nodes.whatsitcodes=whatcodes
 nodes.glyphcodes=glyphcodes
 nodes.disccodes=disccodes
 local free_node=node.free
@@ -3973,7 +3979,6 @@ nodes.traverse_id=node.traverse_id
 nodes.slide=node.slide
 nodes.vpack=node.vpack
 nodes.first_glyph=node.first_glyph
-nodes.first_character=node.first_character
 nodes.has_glyph=node.has_glyph or node.first_glyph
 nodes.current_attr=node.current_attr
 nodes.do_ligature_n=node.do_ligature_n
@@ -4002,13 +4007,27 @@ local setfield=direct.setfield
 nuts.getfield=getfield
 nuts.setfield=setfield
 nuts.getnext=direct.getnext
+nuts.setnext=direct.setnext
 nuts.getprev=direct.getprev
+nuts.setprev=direct.setprev
+nuts.getboth=direct.getboth
+nuts.setboth=direct.setboth
 nuts.getid=direct.getid
-nuts.getattr=getfield
+nuts.getattr=direct.get_attribute or direct.has_attribute or getfield
 nuts.setattr=setfield
 nuts.getfont=direct.getfont
+nuts.setfont=direct.setfont
 nuts.getsubtype=direct.getsubtype
+nuts.setsubtype=direct.setsubtype or function(n,s) setfield(n,"subtype",s) end
 nuts.getchar=direct.getchar
+nuts.setchar=direct.setchar
+nuts.getdisc=direct.getdisc
+nuts.setdisc=direct.setdisc
+nuts.setlink=direct.setlink
+nuts.getlist=direct.getlist
+nuts.setlist=direct.setlist  or function(n,l) setfield(n,"list",l) end
+nuts.getleader=direct.getleader
+nuts.setleader=direct.setleader or function(n,l) setfield(n,"leader",l) end
 nuts.insert_before=direct.insert_before
 nuts.insert_after=direct.insert_after
 nuts.delete=direct.delete
@@ -4022,6 +4041,9 @@ nuts.is_node=direct.is_node
 nuts.end_of_math=direct.end_of_math
 nuts.traverse=direct.traverse
 nuts.traverse_id=direct.traverse_id
+nuts.traverse_char=direct.traverse_char
+nuts.ligaturing=direct.ligaturing
+nuts.kerning=direct.kerning
 nuts.getprop=nuts.getattr
 nuts.setprop=nuts.setattr
 local new_nut=direct.new
@@ -7048,8 +7070,9 @@ local fonts=fonts
 local constructors=fonts.constructors
 local otf=constructors.newhandler("otf")
 local otffeatures=constructors.newfeatures("otf")
-local otftables=otf.tables
 local registerotffeature=otffeatures.register
+local otftables=otf.tables or {}
+otf.tables=otftables
 local allocate=utilities.storage.allocate
 registerotffeature {
   name="features",
@@ -7113,6 +7136,64 @@ registerotffeature {
     node=setscript,
   }
 }
+otftables.featuretypes=allocate {
+  gpos_single="position",
+  gpos_pair="position",
+  gpos_cursive="position",
+  gpos_mark2base="position",
+  gpos_mark2ligature="position",
+  gpos_mark2mark="position",
+  gpos_context="position",
+  gpos_contextchain="position",
+  gsub_single="substitution",
+  gsub_multiple="substitution",
+  gsub_alternate="substitution",
+  gsub_ligature="substitution",
+  gsub_context="substitution",
+  gsub_contextchain="substitution",
+  gsub_reversecontextchain="substitution",
+  gsub_reversesub="substitution",
+}
+function otffeatures.checkeddefaultscript(featuretype,autoscript,scripts)
+  if featuretype=="position" then
+    local default=scripts.dflt
+    if default then
+      if autoscript=="position" or autoscript==true then
+        return default
+      else
+        report_otf("script feature %s not applied, enable default positioning")
+      end
+    else
+    end
+  elseif featuretype=="substitution" then
+    local default=scripts.dflt
+    if default then
+      if autoscript=="substitution" or autoscript==true then
+        return default
+      end
+    end
+  end
+end
+function otffeatures.checkeddefaultlanguage(featuretype,autolanguage,languages)
+  if featuretype=="position" then
+    local default=languages.dflt
+    if default then
+      if autolanguage=="position" or autolanguage==true then
+        return default
+      else
+        report_otf("language feature %s not applied, enable default positioning")
+      end
+    else
+    end
+  elseif featuretype=="substitution" then
+    local default=languages.dflt
+    if default then
+      if autolanguage=="substitution" or autolanguage==true then
+        return default
+      end
+    end
+  end
+end
 
 end -- closure
 
@@ -7156,7 +7237,7 @@ local report_otf=logs.reporter("fonts","otf loading")
 local fonts=fonts
 local otf=fonts.handlers.otf
 otf.glists={ "gsub","gpos" }
-otf.version=2.819 
+otf.version=2.820 
 otf.cache=containers.define("fonts","otf",otf.version,true)
 local hashes=fonts.hashes
 local definers=fonts.definers
@@ -9492,7 +9573,7 @@ otf.coverup={
     kern=justset,
   },
   register=function(coverage,lookuptype,format,feature,n,descriptions,resources)
-    local name=formatters["ctx_%s_%s"](feature,n)
+    local name=formatters["ctx_%s_%s_%s"](feature,lookuptype,n) 
     if lookuptype=="kern" then
       resources.lookuptypes[name]="position"
     else
@@ -10224,8 +10305,10 @@ function injections.resetcounts()
 end
 function injections.reset(n)
   local p=rawget(properties,n)
-  if p and rawget(p,"injections") then
-    p.injections=nil
+  if p then
+    p.injections=false 
+  else
+    properties[n]=false 
   end
 end
 function injections.copy(target,source)
@@ -10242,10 +10325,17 @@ function injections.copy(target,source)
           injections=si,
         }
       end
+    elseif tp then
+      tp.injections=false 
     else
-      if tp then
-        tp.injections=nil
-      end
+      properties[target]={ injections={} }
+    end
+  else
+    local tp=rawget(properties,target)
+    if tp then
+      tp.injections=false 
+    else
+      properties[target]=false 
     end
   end
 end
@@ -10480,10 +10570,11 @@ local function show(n,what,nested,symbol)
         local markx=i.markx   or 0
         local marky=i.marky   or 0
         local markdir=i.markdir  or 0
-        local markbase=i.markbase or 0 
+        local markbase=i.markbase or 0
         local cursivex=i.cursivex or 0
         local cursivey=i.cursivey or 0
         local ligaindex=i.ligaindex or 0
+        local cursbase=i.cursiveanchor
         local margin=nested and 4 or 2
         if rightkern~=0 or yoffset~=0 then
           report_injections("%w%s pair: lx %p, rx %p, dy %p",margin,symbol,leftkern,rightkern,yoffset)
@@ -10494,7 +10585,13 @@ local function show(n,what,nested,symbol)
           report_injections("%w%s mark: dx %p, dy %p, dir %s, base %s",margin,symbol,markx,marky,markdir,markbase~=0 and "yes" or "no")
         end
         if cursivex~=0 or cursivey~=0 then
-          report_injections("%w%s curs: dx %p, dy %p",margin,symbol,cursivex,cursivey)
+          if cursbase then
+            report_injections("%w%s curs: base dx %p, dy %p",margin,symbol,cursivex,cursivey)
+          else
+            report_injections("%w%s curs: dx %p, dy %p",margin,symbol,cursivex,cursivey)
+          end
+        elseif cursbase then
+          report_injections("%w%s curs: base",margin,symbol)
         end
         if ligaindex~=0 then
           report_injections("%w%s liga: index %i",margin,symbol,ligaindex)
@@ -11177,7 +11274,7 @@ end -- closure
 
 do -- begin closure to overcome local limits and interference
 
-if not modules then modules={} end modules ['font-otx']={
+if not modules then modules={} end modules ['luatex-fonts-ota']={
   version=1.001,
   comment="companion to font-otf.lua (analysing)",
   author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
@@ -11194,7 +11291,6 @@ local initializers=allocate()
 local methods=allocate()
 analyzers.initializers=initializers
 analyzers.methods=methods
-analyzers.useunicodemarks=false
 local a_state=attributes.private('state')
 local nuts=nodes.nuts
 local tonut=nuts.tonut
@@ -11250,6 +11346,7 @@ local features={
 }
 analyzers.states=states
 analyzers.features=features
+analyzers.useunicodemarks=false
 function analyzers.setstate(head,font)
   local useunicodemarks=analyzers.useunicodemarks
   local tfmdata=fontdata[font]
@@ -11263,7 +11360,10 @@ function analyzers.setstate(head,font)
       local char=getchar(current)
       local d=descriptions[char]
       if d then
-        if d.class=="mark" or (useunicodemarks and categories[char]=="mn") then
+        if d.class=="mark" then
+          done=true
+          setprop(current,a_state,s_mark)
+        elseif useunicodemarks and categories[char]=="mn" then
           done=true
           setprop(current,a_state,s_mark)
         elseif n==0 then
@@ -11612,7 +11712,9 @@ local tonut=nuts.tonut
 local getfield=nuts.getfield
 local setfield=nuts.setfield
 local getnext=nuts.getnext
+local setnext=nuts.setnext
 local getprev=nuts.getprev
+local setprev=nuts.setprev
 local getid=nuts.getid
 local getattr=nuts.getattr
 local setattr=nuts.setattr
@@ -11620,7 +11722,9 @@ local getprop=nuts.getprop
 local setprop=nuts.setprop
 local getfont=nuts.getfont
 local getsubtype=nuts.getsubtype
+local setsubtype=nuts.setsubtype
 local getchar=nuts.getchar
+local setchar=nuts.setchar
 local insert_node_before=nuts.insert_before
 local insert_node_after=nuts.insert_after
 local delete_node=nuts.delete
@@ -11639,15 +11743,14 @@ local zwj=0x200D
 local wildcard="*"
 local default="dflt"
 local nodecodes=nodes.nodecodes
-local whatcodes=nodes.whatcodes
 local glyphcodes=nodes.glyphcodes
 local disccodes=nodes.disccodes
 local glyph_code=nodecodes.glyph
 local glue_code=nodecodes.glue
 local disc_code=nodecodes.disc
 local math_code=nodecodes.math
-local dir_code=whatcodes.dir
-local localpar_code=whatcodes.localpar
+local dir_code=nodecodes.dir
+local localpar_code=nodecodes.localpar
 local discretionary_code=disccodes.discretionary
 local ligature_code=glyphcodes.ligature
 local privateattribute=attributes.private
@@ -11767,8 +11870,8 @@ local function flattendisk(head,disc)
     if replace then
       if next then
         local tail=find_node_tail(replace)
-        setfield(tail,"next",next)
-        setfield(next,"prev",tail)
+        setnext(tail,next)
+        setprev(next,tail)
       end
       return replace,replace
     elseif next then
@@ -11782,17 +11885,17 @@ local function flattendisk(head,disc)
     if replace then
       local tail=find_node_tail(replace)
       if next then
-        setfield(tail,"next",next)
-        setfield(next,"prev",tail)
+        setnext(tail,next)
+        setprev(next,tail)
       end
-      setfield(prev,"next",replace)
-      setfield(replace,"prev",prev)
+      setnext(prev,replace)
+      setprev(replace,prev)
       return head,replace
     else
       if next then
-        setfield(next,"prev",prev)
+        setprev(next,prev)
       end
-      setfield(prev,"next",next)
+      setnext(prev,next)
       return head,next
     end
   end
@@ -11805,14 +11908,14 @@ local function appenddisc(disc,list)
   local ptail=find_node_tail(post)
   local rtail=find_node_tail(replace)
   if post then
-    setfield(ptail,"next",phead)
-    setfield(phead,"prev",ptail)
+    setnext(ptail,phead)
+    setprev(phead,ptail)
   else
     setfield(disc,"post",phead)
   end
   if replace then
-    setfield(rtail,"next",rhead)
-    setfield(rhead,"prev",rtail)
+    setnext(rtail,rhead)
+    setprev(rhead,rtail)
   else
     setfield(disc,"replace",rhead)
   end
@@ -11823,24 +11926,24 @@ local function markstoligature(kind,lookupname,head,start,stop,char)
   else
     local prev=getprev(start)
     local next=getnext(stop)
-    setfield(start,"prev",nil)
-    setfield(stop,"next",nil)
+    setprev(start,nil)
+    setnext(stop,nil)
     local base=copy_glyph(start)
     if head==start then
       head=base
     end
     resetinjection(base)
-    setfield(base,"char",char)
-    setfield(base,"subtype",ligature_code)
+    setchar(base,char)
+    setsubtype(base,ligature_code)
     setfield(base,"components",start)
     if prev then
-      setfield(prev,"next",base)
+      setnext(prev,base)
     end
     if next then
-      setfield(next,"prev",base)
+      setprev(next,base)
     end
-    setfield(base,"next",next)
-    setfield(base,"prev",prev)
+    setnext(base,next)
+    setprev(base,prev)
     return head,base
   end
 end
@@ -11868,7 +11971,7 @@ local function toligature(kind,lookupname,head,start,stop,char,markflag,discfoun
   end
   if start==stop and getchar(start)==char then
     resetinjection(start)
-    setfield(start,"char",char)
+    setchar(start,char)
     return head,start
   end
   local components=getfield(start,"components")
@@ -11877,24 +11980,24 @@ local function toligature(kind,lookupname,head,start,stop,char,markflag,discfoun
   local prev=getprev(start)
   local next=getnext(stop)
   local comp=start
-  setfield(start,"prev",nil)
-  setfield(stop,"next",nil)
+  setprev(start,nil)
+  setnext(stop,nil)
   local base=copy_glyph(start)
   if start==head then
     head=base
   end
   resetinjection(base)
-  setfield(base,"char",char)
-  setfield(base,"subtype",ligature_code)
+  setchar(base,char)
+  setsubtype(base,ligature_code)
   setfield(base,"components",comp) 
   if prev then
-    setfield(prev,"next",base)
+    setnext(prev,base)
   end
   if next then
-    setfield(next,"prev",base)
+    setprev(next,base)
   end
-  setfield(base,"prev",prev)
-  setfield(base,"next",next)
+  setprev(base,prev)
+  setnext(base,next)
   if not discfound then
     local deletemarks=markflag~="mark"
     local components=start
@@ -11934,41 +12037,41 @@ local function toligature(kind,lookupname,head,start,stop,char,markflag,discfoun
       start=getnext(start)
     end
   else
-    local discprev=getfield(discfound,"prev")
-    local discnext=getfield(discfound,"next")
+    local discprev=getprev(discfound)
+    local discnext=getnext(discfound)
     if discprev and discnext then
       local pre=getfield(discfound,"pre")
       local post=getfield(discfound,"post")
       local replace=getfield(discfound,"replace")
       if not replace then 
-        local prev=getfield(base,"prev")
+        local prev=getprev(base)
         local copied=copy_node_list(comp)
-        setfield(discnext,"prev",nil) 
-        setfield(discprev,"next",nil) 
+        setprev(discnext,nil) 
+        setnext(discprev,nil) 
         if pre then
-          setfield(discprev,"next",pre)
-          setfield(pre,"prev",discprev)
+          setnext(discprev,pre)
+          setprev(pre,discprev)
         end
         pre=comp
         if post then
           local tail=find_node_tail(post)
-          setfield(tail,"next",discnext)
-          setfield(discnext,"prev",tail)
-          setfield(post,"prev",nil)
+          setnext(tail,discnext)
+          setprev(discnext,tail)
+          setprev(post,nil)
         else
           post=discnext
         end
-        setfield(prev,"next",discfound)
-        setfield(discfound,"prev",prev)
-        setfield(discfound,"next",next)
-        setfield(next,"prev",discfound)
-        setfield(base,"next",nil)
-        setfield(base,"prev",nil)
+        setnext(prev,discfound)
+        setprev(discfound,prev)
+        setnext(discfound,next)
+        setprev(next,discfound)
+        setnext(base,nil)
+        setprev(base,nil)
         setfield(base,"components",copied)
         setfield(discfound,"pre",pre)
         setfield(discfound,"post",post)
         setfield(discfound,"replace",base)
-        setfield(discfound,"subtype",discretionary_code)
+        setsubtype(discfound,discretionary_code)
         base=prev 
       end
     end
@@ -11979,19 +12082,19 @@ local function multiple_glyphs(head,start,multiple,ignoremarks)
   local nofmultiples=#multiple
   if nofmultiples>0 then
     resetinjection(start)
-    setfield(start,"char",multiple[1])
+    setchar(start,multiple[1])
     if nofmultiples>1 then
       local sn=getnext(start)
       for k=2,nofmultiples do
         local n=copy_node(start) 
         resetinjection(n)
-        setfield(n,"char",multiple[k])
-        setfield(n,"prev",start)
-        setfield(n,"next",sn)
+        setchar(n,multiple[k])
+        setprev(n,start)
+        setnext(n,sn)
         if sn then
-          setfield(sn,"prev",n)
+          setprev(sn,n)
         end
-        setfield(start,"next",n)
+        setnext(start,n)
         start=n
       end
     end
@@ -12039,7 +12142,7 @@ function handlers.gsub_single(head,start,kind,lookupname,replacement)
     logprocess("%s: replacing %s by single %s",pref(kind,lookupname),gref(getchar(start)),gref(replacement))
   end
   resetinjection(start)
-  setfield(start,"char",replacement)
+  setchar(start,replacement)
   return head,start,true
 end
 function handlers.gsub_alternate(head,start,kind,lookupname,alternative,sequence)
@@ -12050,7 +12153,7 @@ function handlers.gsub_alternate(head,start,kind,lookupname,alternative,sequence
       logprocess("%s: replacing %s by alternative %a to %s, %s",pref(kind,lookupname),gref(getchar(start)),choice,gref(choice),comment)
     end
     resetinjection(start)
-    setfield(start,"char",choice)
+    setchar(start,choice)
   else
     if trace_alternatives then
       logwarning("%s: no variant %a for %s, %s",pref(kind,lookupname),value,gref(getchar(start)),comment)
@@ -12144,7 +12247,7 @@ function handlers.gsub_ligature(head,start,kind,lookupname,ligature,sequence)
         end
       else
         resetinjection(start)
-        setfield(start,"char",lig)
+        setchar(start,lig)
         if trace_ligatures then
           logprocess("%s: replacing %s by (no real) ligature %s case 3",pref(kind,lookupname),gref(startchar),gref(lig))
         end
@@ -12471,7 +12574,7 @@ function chainprocs.reversesub(head,start,stop,kind,chainname,currentcontext,loo
       logprocess("%s: single reverse replacement of %s by %s",cref(kind,chainname),gref(char),gref(replacement))
     end
     resetinjection(start)
-    setfield(start,"char",replacement)
+    setchar(start,replacement)
     return head,start,true
   else
     return head,start,false
@@ -12503,7 +12606,7 @@ function chainprocs.gsub_single(head,start,stop,kind,chainname,currentcontext,lo
             logprocess("%s: replacing single %s by %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(currentchar),gref(replacement))
           end
           resetinjection(current)
-          setfield(current,"char",replacement)
+          setchar(current,replacement)
         end
       end
       return head,start,true
@@ -12561,7 +12664,7 @@ function chainprocs.gsub_alternate(head,start,stop,kind,chainname,currentcontext
               logprocess("%s: replacing %s by alternative %a to %s, %s",cref(kind,chainname,chainlookupname,lookupname),gref(char),choice,gref(choice),comment)
             end
             resetinjection(start)
-            setfield(start,"char",choice)
+            setchar(start,choice)
           else
             if trace_alternatives then
               logwarning("%s: no variant %a for %s, %s",cref(kind,chainname,chainlookupname,lookupname),value,gref(char),comment)
@@ -13056,13 +13159,13 @@ local function chaindisk(head,start,last,kind,chainname,ck,lookuphash,chainlooku
       local tail=nil
       if prev then
         tail=prev
-        setfield(current,"prev",sweepnode)
+        setprev(current,sweepnode)
       else
         tail=find_node_tail(head)
       end
-      setfield(sweepnode,"next",current)
-      setfield(head,"prev",nil)
-      setfield(tail,"next",nil)
+      setnext(sweepnode,current)
+      setprev(head,nil)
+      setnext(tail,nil)
       appenddisc(sweepnode,head)
     end
   end
@@ -13150,12 +13253,12 @@ local function chaindisk(head,start,last,kind,chainname,ck,lookuphash,chainlooku
       startishead=cf==head
       cprev=getprev(cprev)
     end
-    setfield(lookaheaddisc,"prev",cprev)
+    setprev(lookaheaddisc,cprev)
     if cprev then
-      setfield(cprev,"next",lookaheaddisc)
+      setnext(cprev,lookaheaddisc)
     end
-    setfield(cf,"prev",nil)
-    setfield(cl,"next",nil)
+    setprev(cf,nil)
+    setnext(cl,nil)
     if startishead then
       head=lookaheaddisc
     end
@@ -13177,13 +13280,13 @@ local function chaindisk(head,start,last,kind,chainname,ck,lookuphash,chainlooku
       new,cnew,ok=chainproc(new,cnew,clast,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence)
     end
     if pre then
-      setfield(cl,"next",pre)
-      setfield(pre,"prev",cl)
+      setnext(cl,pre)
+      setprev(pre,cl)
     end
     if replace then
       local tail=find_node_tail(new)
-      setfield(tail,"next",replace)
-      setfield(replace,"prev",tail)
+      setnext(tail,replace)
+      setprev(replace,tail)
     end
     setfield(lookaheaddisc,"pre",cf)   
     setfield(lookaheaddisc,"replace",new) 
@@ -13201,11 +13304,11 @@ local function chaindisk(head,start,last,kind,chainname,ck,lookuphash,chainlooku
       cnext=getnext(cnext)
     end
     if cnext then
-      setfield(cnext,"prev",backtrackdisc)
+      setprev(cnext,backtrackdisc)
     end
-    setfield(backtrackdisc,"next",cnext)
-    setfield(cf,"prev",nil)
-    setfield(cl,"next",nil)
+    setnext(backtrackdisc,cnext)
+    setprev(cf,nil)
+    setnext(cl,nil)
     local replace=getfield(backtrackdisc,"replace")
     local post=getfield(backtrackdisc,"post")
     local new=copy_node_list(cf)
@@ -13225,15 +13328,15 @@ local function chaindisk(head,start,last,kind,chainname,ck,lookuphash,chainlooku
     end
     if post then
       local tail=find_node_tail(post)
-      setfield(tail,"next",cf)
-      setfield(cf,"prev",tail)
+      setnext(tail,cf)
+      setprev(cf,tail)
     else
       post=cf
     end
     if replace then
       local tail=find_node_tail(replace)
-      setfield(tail,"next",new)
-      setfield(new,"prev",tail)
+      setnext(tail,new)
+      setprev(new,tail)
     else
       replace=new
     end
@@ -13761,25 +13864,40 @@ otf.chainhandlers={
   normal=normal_handle_contextchain,
   verbose=verbose_handle_contextchain,
 }
+local handle_contextchain=nil
+function chained_contextchain(head,start,stop,...)
+  local steps=currentlookup.steps
+  local nofsteps=currentlookup.nofsteps
+  if nofsteps>1 then
+    reportmoresteps(dataset,sequence)
+  end
+  return handle_contextchain(head,start,...)
+end
 function otf.setcontextchain(method)
   if not method or method=="normal" or not otf.chainhandlers[method] then
-    if handlers.contextchain then 
+    if handle_contextchain then 
       logwarning("installing normal contextchain handler")
     end
-    handlers.contextchain=normal_handle_contextchain
+    handle_contextchain=normal_handle_contextchain
   else
     logwarning("installing contextchain handler %a",method)
     local handler=otf.chainhandlers[method]
-    handlers.contextchain=function(...)
+    handle_contextchain=function(...)
       return handler(currentfont,...) 
     end
   end
-  handlers.gsub_context=handlers.contextchain
-  handlers.gsub_contextchain=handlers.contextchain
-  handlers.gsub_reversecontextchain=handlers.contextchain
-  handlers.gpos_contextchain=handlers.contextchain
-  handlers.gpos_context=handlers.contextchain
+  handlers.gsub_context=handle_contextchain
+  handlers.gsub_contextchain=handle_contextchain
+  handlers.gsub_reversecontextchain=handle_contextchain
+  handlers.gpos_contextchain=handle_contextchain
+  handlers.gpos_context=handle_contextchain
+  handlers.contextchain=handle_contextchain
 end
+chainprocs.gsub_context=chained_contextchain
+chainprocs.gsub_contextchain=chained_contextchain
+chainprocs.gsub_reversecontextchain=chained_contextchain
+chainprocs.gpos_contextchain=chained_contextchain
+chainprocs.gpos_context=chained_contextchain
 otf.setcontextchain()
 local missing={} 
 local function logprocess(...)
@@ -13807,19 +13925,32 @@ setmetatableindex(lookuphashes,function(t,font)
   t[font]=lookuphash
   return lookuphash
 end)
-local autofeatures=fonts.analyzers.features 
-local function initialize(sequence,script,language,enabled)
+local autofeatures=fonts.analyzers.features
+local featuretypes=otf.tables.featuretypes
+local defaultscript=otf.features.checkeddefaultscript
+local defaultlanguage=otf.features.checkeddefaultlanguage
+local function initialize(sequence,script,language,enabled,autoscript,autolanguage)
   local features=sequence.features
   if features then
     local order=sequence.order
     if order then
-      for i=1,#order do 
-        local kind=order[i] 
+      local featuretype=featuretypes[sequence.type or "unknown"]
+      for i=1,#order do
+        local kind=order[i]
         local valid=enabled[kind]
         if valid then
-          local scripts=features[kind] 
-          local languages=scripts[script] or scripts[wildcard]
-          if languages and (languages[language] or languages[wildcard]) then
+          local scripts=features[kind]
+          local languages=scripts and (
+            scripts[script] or
+            scripts[wildcard] or
+            (autoscript and defaultscript(featuretype,autoscript,scripts))
+          )
+          local enabled=languages and (
+            languages[language] or
+            languages[wildcard] or
+            (autolanguage and defaultlanguage(featuretype,autolanguage,languages))
+          )
+          if enabled then
             return { valid,autofeatures[kind] or false,sequence,kind }
           end
         end
@@ -13835,6 +13966,8 @@ function otf.dataset(tfmdata,font)
   local language=properties.language or "dflt"
   local script=properties.script  or "dflt"
   local enabled=shared.features
+  local autoscript=enabled and enabled.autoscript
+  local autolanguage=enabled and enabled.autolanguage
   local res=resolved[font]
   if not res then
     res={}
@@ -13852,7 +13985,7 @@ function otf.dataset(tfmdata,font)
     rs[language]=rl
     local sequences=tfmdata.resources.sequences
     for s=1,#sequences do
-      local v=enabled and initialize(sequences[s],script,language,enabled)
+      local v=enabled and initialize(sequences[s],script,language,enabled,autoscript,autolanguage)
       if v then
         rl[#rl+1]=v
       end
@@ -13882,57 +14015,57 @@ local function kernrun(disc,run)
   if not pre then
   elseif prev then
     local nest=getprev(pre)
-    setfield(pre,"prev",prev)
-    setfield(prev,"next",pre)
+    setprev(pre,prev)
+    setnext(prev,pre)
     run(prevmarks,"preinjections")
-    setfield(pre,"prev",nest)
-    setfield(prev,"next",disc)
+    setprev(pre,nest)
+    setnext(prev,disc)
   else
     run(pre,"preinjections")
   end
   if not post then
   elseif next then
     local tail=find_node_tail(post)
-    setfield(tail,"next",next)
-    setfield(next,"prev",tail)
+    setnext(tail,next)
+    setprev(next,tail)
     run(post,"postinjections",next)
-    setfield(tail,"next",nil)
-    setfield(next,"prev",disc)
+    setnext(tail,nil)
+    setprev(next,disc)
   else
     run(post,"postinjections")
   end
   if not replace and prev and next then
-    setfield(prev,"next",next)
-    setfield(next,"prev",prev)
+    setnext(prev,next)
+    setprev(next,prev)
     run(prevmarks,"injections",next)
-    setfield(prev,"next",disc)
-    setfield(next,"prev",disc)
+    setnext(prev,disc)
+    setprev(next,disc)
   elseif prev and next then
     local tail=find_node_tail(replace)
     local nest=getprev(replace)
-    setfield(replace,"prev",prev)
-    setfield(prev,"next",replace)
-    setfield(tail,"next",next)
-    setfield(next,"prev",tail)
+    setprev(replace,prev)
+    setnext(prev,replace)
+    setnext(tail,next)
+    setprev(next,tail)
     run(prevmarks,"replaceinjections",next)
-    setfield(replace,"prev",nest)
-    setfield(prev,"next",disc)
-    setfield(tail,"next",nil)
-    setfield(next,"prev",disc)
+    setprev(replace,nest)
+    setnext(prev,disc)
+    setnext(tail,nil)
+    setprev(next,disc)
   elseif prev then
     local nest=getprev(replace)
-    setfield(replace,"prev",prev)
-    setfield(prev,"next",replace)
+    setprev(replace,prev)
+    setnext(prev,replace)
     run(prevmarks,"replaceinjections")
-    setfield(replace,"prev",nest)
-    setfield(prev,"next",disc)
+    setprev(replace,nest)
+    setnext(prev,disc)
   elseif next then
     local tail=find_node_tail(replace)
-    setfield(tail,"next",next)
-    setfield(next,"prev",tail)
+    setnext(tail,next)
+    setprev(next,tail)
     run(replace,"replaceinjections",next)
-    setfield(tail,"next",nil)
-    setfield(next,"prev",disc)
+    setnext(tail,nil)
+    setprev(next,disc)
   else
     run(replace,"replaceinjections")
   end
@@ -13979,21 +14112,21 @@ local function testrun(disc,trun,crun)
       local prev=getprev(disc)
       if prev then
         local tail=find_node_tail(replace)
-        setfield(tail,"next",next)
-        setfield(next,"prev",tail)
+        setnext(tail,next)
+        setprev(next,tail)
         if trun(replace,next) then
           setfield(disc,"replace",nil) 
-          setfield(prev,"next",replace)
-          setfield(replace,"prev",prev)
-          setfield(next,"prev",tail)
-          setfield(tail,"next",next)
-          setfield(disc,"prev",nil)
-          setfield(disc,"next",nil)
+          setnext(prev,replace)
+          setprev(replace,prev)
+          setprev(next,tail)
+          setnext(tail,next)
+          setprev(disc,nil)
+          setnext(disc,nil)
           flush_node_list(disc)
           return replace 
         else
-          setfield(tail,"next",nil)
-          setfield(next,"prev",disc)
+          setnext(tail,nil)
+          setprev(next,disc)
         end
       else
       end
@@ -14011,19 +14144,19 @@ local function discrun(disc,drun,krun)
     report_run("disc") 
   end
   if next and prev then
-    setfield(prev,"next",next)
+    setnext(prev,next)
     drun(prev)
-    setfield(prev,"next",disc)
+    setnext(prev,disc)
   end
   local pre=getfield(disc,"pre")
   if not pre then
   elseif prev then
     local nest=getprev(pre)
-    setfield(pre,"prev",prev)
-    setfield(prev,"next",pre)
+    setprev(pre,prev)
+    setnext(prev,pre)
     krun(prev,"preinjections")
-    setfield(pre,"prev",nest)
-    setfield(prev,"next",disc)
+    setprev(pre,nest)
+    setnext(prev,disc)
   else
     krun(pre,"preinjections")
   end
@@ -14281,6 +14414,40 @@ local function featuresprocessor(head,font,attr)
               end
             elseif id==math_code then
               start=getnext(end_of_math(start))
+            elseif id==dir_code then
+              local dir=getfield(start,"dir")
+              if dir=="+TLT" then
+                topstack=topstack+1
+                dirstack[topstack]=dir
+                rlmode=1
+              elseif dir=="+TRT" then
+                topstack=topstack+1
+                dirstack[topstack]=dir
+                rlmode=-1
+              elseif dir=="-TLT" or dir=="-TRT" then
+                topstack=topstack-1
+                rlmode=dirstack[topstack]=="+TRT" and -1 or 1
+              else
+                rlmode=rlparmode
+              end
+              if trace_directions then
+                report_process("directions after txtdir %a: parmode %a, txtmode %a, # stack %a, new dir %a",dir,rlparmode,rlmode,topstack,newdir)
+              end
+              start=getnext(start)
+            elseif id==localpar_code then
+              local dir=getfield(start,"dir")
+              if dir=="TRT" then
+                rlparmode=-1
+              elseif dir=="TLT" then
+                rlparmode=1
+              else
+                rlparmode=0
+              end
+              rlmode=rlparmode
+              if trace_directions then
+                report_process("directions after pardir %a: parmode %a, txtmode %a",dir,rlparmode,rlmode)
+              end
+              start=getnext(start)
             else
               start=getnext(start)
             end
@@ -14501,6 +14668,40 @@ local function featuresprocessor(head,font,attr)
             end
           elseif id==math_code then
             start=getnext(end_of_math(start))
+          elseif id==dir_code then
+            local dir=getfield(start,"dir")
+            if dir=="+TLT" then
+              topstack=topstack+1
+              dirstack[topstack]=dir
+              rlmode=1
+            elseif dir=="+TRT" then
+              topstack=topstack+1
+              dirstack[topstack]=dir
+              rlmode=-1
+            elseif dir=="-TLT" or dir=="-TRT" then
+              topstack=topstack-1
+              rlmode=dirstack[topstack]=="+TRT" and -1 or 1
+            else
+              rlmode=rlparmode
+            end
+            if trace_directions then
+              report_process("directions after txtdir %a: parmode %a, txtmode %a, # stack %a, new dir %a",dir,rlparmode,rlmode,topstack,newdir)
+            end
+            start=getnext(start)
+          elseif id==localpar_code then
+            local dir=getfield(start,"dir")
+            if dir=="TRT" then
+              rlparmode=-1
+            elseif dir=="TLT" then
+              rlparmode=1
+            else
+              rlparmode=0
+            end
+            rlmode=rlparmode
+            if trace_directions then
+              report_process("directions after pardir %a: parmode %a, txtmode %a",dir,rlparmode,rlmode)
+            end
+            start=getnext(start)
           else
             start=getnext(start)
           end
@@ -14636,10 +14837,10 @@ local function split(replacement,original)
   end
   return result
 end
-local valid={
-  coverage={ chainsub=true,chainpos=true,contextsub=true },
+local valid={ 
+  coverage={ chainsub=true,chainpos=true,contextsub=true,contextpos=true },
   reversecoverage={ reversesub=true },
-  glyphs={ chainsub=true,chainpos=true },
+  glyphs={ chainsub=true,chainpos=true,contextsub=true,contextpos=true },
 }
 local function prepare_contextchains(tfmdata)
   local rawdata=tfmdata.shared.rawdata
@@ -15952,7 +16153,7 @@ end -- closure
 
 do -- begin closure to overcome local limits and interference
 
-if not modules then modules={} end modules ['luatex-font-def']={
+if not modules then modules={} end modules ['luatex-fonts-def']={
   version=1.001,
   comment="companion to luatex-*.tex",
   author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
-- 
cgit v1.2.3


From a55edde26344366c385e433824d3e78fec618158 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <phg@phi-gamma.net>
Date: Tue, 22 Dec 2015 00:01:05 +0100
Subject: [features] improve font feature injection

---
 src/luaotfload-features.lua | 114 ++++++++++++++++++++++++++++++--------------
 1 file changed, 77 insertions(+), 37 deletions(-)

diff --git a/src/luaotfload-features.lua b/src/luaotfload-features.lua
index fc04bce..3f9d67a 100644
--- a/src/luaotfload-features.lua
+++ b/src/luaotfload-features.lua
@@ -941,6 +941,7 @@ local report_otf          = logs.reporter("fonts","otf loading")
 --- start locals for addfeature()
 
 local utfbyte = unicode.utf8.byte
+local utfchar = unicode.utf8.char
 
 local otf = handlers and handlers.otf --- filled in later during initialization
 
@@ -1110,7 +1111,7 @@ local function ancient_addfeature (data, feature, specifications)
     end
 end
 
-local function addfeature(data,feature,specifications)
+local function current_addfeature(data,feature,specifications)
     local descriptions = data.descriptions
     local resources    = data.resources
     local features     = resources.features
@@ -1356,8 +1357,6 @@ end
 
 ---[[ end snippet from font-otc.lua ]]
 
-local tlig_order = { "tlig" }
-
 local tlig_specification = {
     {
         type      = "substitution",
@@ -1368,7 +1367,7 @@ local tlig_specification = {
             [0x0060] = 0x2018,                   -- quoteright
         },
         flags     = noflags,
-        order     = tlig_order,
+        order     = { "tlig" },
         prepend   = true,
     },
     {
@@ -1388,7 +1387,7 @@ local tlig_specification = {
             [0x00BB] = {0x003E, 0x003E},         -- RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
         },
         flags    = noflags,
-        order    = tlig_order,
+        order    = { "tlig" },
         prepend  = true,
     },
     {
@@ -1401,11 +1400,24 @@ local tlig_specification = {
             [0x00BF] = {0x003F, 0x0060},         -- questiondown
         },
         flags    = noflags,
-        order    = tlig_order,
+        order    = { "tlig" },
         prepend  = true,
     },
 }
 
+local tlig_specification = {
+    type      = "substitution",
+    features  = everywhere,
+    data      = {
+        [0x0022] = 0x201D,                   -- quotedblright
+        [0x0027] = 0x2019,                   -- quoteleft
+        [0x0060] = 0x2018,                   -- quoteright
+    },
+    flags     = noflags,
+    order     = { "tlig" },
+    prepend   = true,
+}
+
 local anum_arabic = { --- these are the same as in font-otc
     [0x0030] = 0x0660,
     [0x0031] = 0x0661,
@@ -1464,9 +1476,65 @@ local anum_specification = {
     },
 }
 
+local rot13_specification = {
+    type      = "substitution",
+    features  = everywhere,
+    data      = {
+        [65] = 78, [ 97] = 110, [78] = 65, [110] =  97,
+        [66] = 79, [ 98] = 111, [79] = 66, [111] =  98,
+        [67] = 80, [ 99] = 112, [80] = 67, [112] =  99,
+        [68] = 81, [100] = 113, [81] = 68, [113] = 100,
+        [69] = 82, [101] = 114, [82] = 69, [114] = 101,
+        [70] = 83, [102] = 115, [83] = 70, [115] = 102,
+        [71] = 84, [103] = 116, [84] = 71, [116] = 103,
+        [72] = 85, [104] = 117, [85] = 72, [117] = 104,
+        [73] = 86, [105] = 118, [86] = 73, [118] = 105,
+        [74] = 87, [106] = 119, [87] = 74, [119] = 106,
+        [75] = 88, [107] = 120, [88] = 75, [120] = 107,
+        [76] = 89, [108] = 121, [89] = 76, [121] = 108,
+        [77] = 90, [109] = 122, [90] = 77, [122] = 109,
+    },
+    flags     = noflags,
+    order     = { "rot13" },
+    prepend   = true,
+}
+
+local extrafeatures = {
+    tlig  = { tlig_specification,  "tex ligatures and substitutions" },
+    anum  = { anum_specification,  "arabic numerals"                 },
+    rot13 = { rot13_specification, "rot13"                           },
+}
+
+function add_otf_feature (name, specification)
+    if type (name) == "table" then
+        specification = name
+        name = specification.name
+    end
+    if type (name) == "string" then
+        extrafeatures[name] = specification
+    end
+end
+
+otf.addfeature           = add_otf_feature
+
+local install_extra_features = function (data, filename, raw)
+    for feature, specification in next, extrafeatures do
+        logreport ("both", 3, "features",
+                   "register synthetic feature “%s” for %s font “%s”(%d)",
+                   feature,
+                   data.format,
+                   tostring (data.metadata and data.metadata.fontname or "<unknown>"),
+                   data.subfont or -1)
+        otf.features.register { name = feature, description = specification[2] }
+        otf.enhancers.addfeature (data, feature, specification[1])
+    end
+end
+
 return {
     init = function ()
 
+        logreport = luaotfload.log.report
+
         if not fonts and fonts.handlers then
             logreport ("log", 0, "features",
                        "OTF mechanisms missing -- did you forget to \z
@@ -1476,41 +1544,13 @@ return {
 
         otf = fonts.handlers.otf
 
-        local extrafeatures = {
-            tlig = tlig_specification,
-            trep = { },
-            anum = anum_specification,
-        }
-
         --- hack for backwards compat with TL2014 loader
-        local addfeature = otf.version < 2.8 and current_addfeature
-                                              or ancient_addfeature
+        otf.enhancers.addfeature = otf.version < 2.8 and ancient_addfeature
+                                                      or current_addfeature
 
         otf.enhancers.register ("check extra features",
-                                function (data, filename, raw)
-                                    for feature, specification in next, extrafeatures do
-                                        logreport ("both", 3, "features",
-                                                   "register synthetic feature “%s” for %s font “%s”(%d)",
-                                                   feature,
-                                                   data.format,
-                                                   tostring (data.metadata and data.metadata.fontname or "<unknown>"),
-                                                   data.subfont or -1)
-                                        addfeature (data, feature, specification)
-                                    end
-                                end)
-
-        logreport = luaotfload.log.report
-        if not fonts then
-            logreport ("log", 0, "features",
-                       "OTF mechanisms missing -- did you forget to \z
-                       load a font loader?")
-            return false
-        end
+                                install_extra_features)
 
-        otf.features.register {
-            name        = "anum",
-            description = "arabic digits",
-        }
         return true
     end
 }
-- 
cgit v1.2.3


From 57482bf62a2819450161e6255c14b7f950db331a Mon Sep 17 00:00:00 2001
From: Philipp Gesang <phg@phi-gamma.net>
Date: Tue, 22 Dec 2015 08:17:44 +0100
Subject: [init] install some more accessors required by the fontloader

---
 src/fontloader/misc/fontloader-font-otf.lua | 5 ++++-
 src/luaotfload-init.lua                     | 9 ++++++++-
 2 files changed, 12 insertions(+), 2 deletions(-)

diff --git a/src/fontloader/misc/fontloader-font-otf.lua b/src/fontloader/misc/fontloader-font-otf.lua
index 86b1313..0471c17 100644
--- a/src/fontloader/misc/fontloader-font-otf.lua
+++ b/src/fontloader/misc/fontloader-font-otf.lua
@@ -296,7 +296,8 @@ local ordered_enhancers = {
 
     "expand lookups", -- a temp hack awaiting the lua loader
 
---     "check extra features", -- after metadata and duplicates
+--[[phg-- PATCH: Next line restores font features --phg]]--
+    "check extra features", -- after metadata and duplicates
 
     "cleanup tables",
 
@@ -600,7 +601,9 @@ function otf.load(filename,sub,featurefile) -- second argument (format) is gone
             applyruntimefixes(filename,data)
         end
         enhance("add dimensions",data,filename,nil,false)
+--[[phg-- This was hand-patched to restore the fontloader
 enhance("check extra features",data,filename)
+--phg]]--
         if trace_sequences then
             showfeatureorder(data,filename)
         end
diff --git a/src/luaotfload-init.lua b/src/luaotfload-init.lua
index 3568d21..d471152 100644
--- a/src/luaotfload-init.lua
+++ b/src/luaotfload-init.lua
@@ -89,10 +89,17 @@ local luatex_stubs = function ()
   if not node.subtypes then
     node.subtypes = function (t) return node_types [t] or { } end
     local direct = node.direct
+
     local getfield = direct.getfield
     local setfield = direct.setfield
+
     direct.setchar = direct.setchar or function (n, ...) setfield (n, "char", ...) end
-    direct.getchar = direct.getchar or function (n) getfield (n, "char")    end
+    direct.setprev = direct.setprev or function (n, ...) setfield (n, "prev", ...) end
+    direct.setnext = direct.setnext or function (n, ...) setfield (n, "next", ...) end
+
+    direct.getchar = direct.getchar or function (n) getfield (n, "char") end
+    direct.getprev = direct.getprev or function (n) getfield (n, "prev") end
+    direct.getnext = direct.getnext or function (n) getfield (n, "next") end
   end
 end
 
-- 
cgit v1.2.3


From 44eadadf3c6425f121136a0fcf736671a31f82d9 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <phg@phi-gamma.net>
Date: Tue, 22 Dec 2015 08:30:07 +0100
Subject: [fontloader] patch font-otn to preven calt crash

---
 src/fontloader/misc/fontloader-font-otn.lua | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/fontloader/misc/fontloader-font-otn.lua b/src/fontloader/misc/fontloader-font-otn.lua
index 8df01bd..b48aea7 100644
--- a/src/fontloader/misc/fontloader-font-otn.lua
+++ b/src/fontloader/misc/fontloader-font-otn.lua
@@ -3894,7 +3894,10 @@ local function prepare_contextchains(tfmdata)
                                     sequence[nofsequences] = after[n]
                                 end
                             end
+--[[phg-- Hard patch: This crashes, see https://github.com/lualatex/luaotfload/issues/303
                             if sequence[1] then
+--phg]]--
+                            if sequence[start] then
                                 -- Replacements only happen with reverse lookups as they are single only. We
                                 -- could pack them into current (replacement value instead of true) and then
                                 -- use sequence[start] instead but it's somewhat ugly.
-- 
cgit v1.2.3


From c7e64e04f85ab6386fcbc0bc631db808d0d6109f Mon Sep 17 00:00:00 2001
From: Philipp Gesang <phg@phi-gamma.net>
Date: Tue, 22 Dec 2015 23:32:39 +0100
Subject: [aux] assimilate logger to convention

---
 src/luaotfload-auxiliary.lua | 64 ++++++++++++++++++++++----------------------
 1 file changed, 32 insertions(+), 32 deletions(-)

diff --git a/src/luaotfload-auxiliary.lua b/src/luaotfload-auxiliary.lua
index c50e0cd..11d3101 100644
--- a/src/luaotfload-auxiliary.lua
+++ b/src/luaotfload-auxiliary.lua
@@ -15,7 +15,7 @@ luaotfload.aux              = luaotfload.aux or { }
 
 local aux                   = luaotfload.aux
 local log                   = luaotfload.log
-local report                = log.report
+local logreport             = log.report
 local fonthashes            = fonts.hashes
 local encodings             = fonts.encodings
 local identifiers           = fonthashes.identifiers
@@ -54,8 +54,8 @@ local start_rewrite_fontname = function ()
       rewrite_fontname,
       "luaotfload.rewrite_fontname")
     rewriting = true
-    report ("log", 1, "aux",
-            "start rewriting tfmdata.name field")
+    logreport ("log", 1, "aux",
+               "start rewriting tfmdata.name field")
   end
 end
 
@@ -66,8 +66,8 @@ local stop_rewrite_fontname = function ()
     luatexbase.remove_from_callback
       ("luaotfload.patch_font", "luaotfload.rewrite_fontname")
     rewriting = false
-    report ("log", 1, "aux",
-            "stop rewriting tfmdata.name field")
+    logreport ("log", 1, "aux",
+               "stop rewriting tfmdata.name field")
   end
 end
 
@@ -393,7 +393,7 @@ do
 
   local load_chardef = function ()
 
-    report ("both", 1, "aux", "Loading character metadata from %s.", chardef)
+    logreport ("both", 1, "aux", "Loading character metadata from %s.", chardef)
     chardata = dofile (kpse.find_file (chardef, "lua"))
 
     if chardata == nil then
@@ -452,18 +452,18 @@ local provides_script = function (font_id, asked_script)
       --- where method: "gpos" | "gsub"
       for feature, data in next, featuredata do
         if data[asked_script] then
-          report ("log", 1, "aux",
-                  "font no %d (%s) defines feature %s for script %s",
-                  font_id, fontname, feature, asked_script)
+          logreport ("log", 1, "aux",
+                     "font no %d (%s) defines feature %s for script %s",
+                     font_id, fontname, feature, asked_script)
           return true
         end
       end
     end
-    report ("log", 0, "aux",
-            "font no %d (%s) defines no feature for script %s",
-            font_id, fontname, asked_script)
+    logreport ("log", 0, "aux",
+               "font no %d (%s) defines no feature for script %s",
+               font_id, fontname, asked_script)
   end
-  report ("log", 0, "aux", "no font with id %d", font_id)
+  logreport ("log", 0, "aux", "no font with id %d", font_id)
   return false
 end
 
@@ -491,21 +491,21 @@ local provides_language = function (font_id, asked_script, asked_language)
       for feature, data in next, featuredata do
         local scriptdata = data[asked_script]
         if scriptdata and scriptdata[asked_language] then
-          report ("log", 1, "aux",
-                  "font no %d (%s) defines feature %s "
-                  .. "for script %s with language %s",
-                  font_id, fontname, feature,
-                  asked_script, asked_language)
+          logreport ("log", 1, "aux",
+                     "font no %d (%s) defines feature %s "
+                     .. "for script %s with language %s",
+                     font_id, fontname, feature,
+                     asked_script, asked_language)
           return true
         end
       end
     end
-    report ("log", 0, "aux",
-            "font no %d (%s) defines no feature "
-            .. "for script %s with language %s",
-            font_id, fontname, asked_script, asked_language)
+    logreport ("log", 0, "aux",
+               "font no %d (%s) defines no feature "
+               .. "for script %s with language %s",
+               font_id, fontname, asked_script, asked_language)
   end
-  report ("log", 0, "aux", "no font with id %d", font_id)
+  logreport ("log", 0, "aux", "no font with id %d", font_id)
   return false
 end
 
@@ -564,20 +564,20 @@ local provides_feature = function (font_id,        asked_script,
       if feature then
         local scriptdata = feature[asked_script]
         if scriptdata and scriptdata[asked_language] then
-          report ("log", 1, "aux",
-                  "font no %d (%s) defines feature %s "
-                  .. "for script %s with language %s",
-                  font_id, fontname, asked_feature,
-                  asked_script, asked_language)
+          logreport ("log", 1, "aux",
+                     "font no %d (%s) defines feature %s "
+                     .. "for script %s with language %s",
+                     font_id, fontname, asked_feature,
+                     asked_script, asked_language)
           return true
         end
       end
     end
-    report ("log", 0, "aux",
-            "font no %d (%s) does not define feature %s for script %s with language %s",
-            font_id, fontname, asked_feature, asked_script, asked_language)
+    logreport ("log", 0, "aux",
+               "font no %d (%s) does not define feature %s for script %s with language %s",
+               font_id, fontname, asked_feature, asked_script, asked_language)
   end
-  report ("log", 0, "aux", "no font with id %d", font_id)
+  logreport ("log", 0, "aux", "no font with id %d", font_id)
   return false
 end
 
-- 
cgit v1.2.3


From 34a02eaaaf87f6608fc7cb13d60f1217f36f6502 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <phg@phi-gamma.net>
Date: Wed, 23 Dec 2015 00:04:34 +0100
Subject: [mkstatus] safer parsing of git-describe(1) output

---
 scripts/mkstatus | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/scripts/mkstatus b/scripts/mkstatus
index 62161a8..c5ded0d 100755
--- a/scripts/mkstatus
+++ b/scripts/mkstatus
@@ -164,7 +164,9 @@ local git_info = function ()
   --io.write "\n"
   local desc = readpipe (describecmd)
   if not desc then die "cannot parse git information" end
-  local desc = string.explode (string.fullstrip (desc), "/")[2]
+  desc = string.fullstrip (desc)
+  local desc = string.explode (desc, "/")[2] or desc
+  if not desc then die "cannot parse sanitized git information" end
   local data = readpipe (logcmd)
   if data and type (data) == "string" and data ~= "" then
     data = load (data)
-- 
cgit v1.2.3