diff options
Diffstat (limited to 'src/fontloader/misc')
22 files changed, 2771 insertions, 1233 deletions
| diff --git a/src/fontloader/misc/fontloader-basics-nod.lua b/src/fontloader/misc/fontloader-basics-nod.lua index 1ec2895..39400a3 100644 --- a/src/fontloader/misc/fontloader-basics-nod.lua +++ b/src/fontloader/misc/fontloader-basics-nod.lua @@ -56,6 +56,9 @@ local whatcodes    = { } for k,v in next, node.whatsits() do whatcodes[string.gs  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 +  nodes.nodecodes    = nodecodes  nodes.whatcodes    = whatcodes  nodes.whatsitcodes = whatcodes diff --git a/src/fontloader/misc/fontloader-font-afm.lua b/src/fontloader/misc/fontloader-font-afm.lua index a96c668..329639b 100644 --- a/src/fontloader/misc/fontloader-font-afm.lua +++ b/src/fontloader/misc/fontloader-font-afm.lua @@ -152,14 +152,14 @@ end  local keys = { } -function keys.FontName    (data,line) data.metadata.fontname     = strip    (line) -- get rid of spaces -                                      data.metadata.fullname     = strip    (line) end -function keys.ItalicAngle (data,line) data.metadata.italicangle  = tonumber (line) end -function keys.IsFixedPitch(data,line) data.metadata.isfixedpitch = toboolean(line,true) end -function keys.CharWidth   (data,line) data.metadata.charwidth    = tonumber (line) end -function keys.XHeight     (data,line) data.metadata.xheight      = tonumber (line) end -function keys.Descender   (data,line) data.metadata.descender    = tonumber (line) end -function keys.Ascender    (data,line) data.metadata.ascender     = tonumber (line) end +function keys.FontName    (data,line) data.metadata.fontname    = strip    (line) -- get rid of spaces +                                      data.metadata.fullname    = strip    (line) end +function keys.ItalicAngle (data,line) data.metadata.italicangle = tonumber (line) end +function keys.IsFixedPitch(data,line) data.metadata.monospaced  = toboolean(line,true) end +function keys.CharWidth   (data,line) data.metadata.charwidth   = tonumber (line) end +function keys.XHeight     (data,line) data.metadata.xheight     = tonumber (line) end +function keys.Descender   (data,line) data.metadata.descender   = tonumber (line) end +function keys.Ascender    (data,line) data.metadata.ascender    = tonumber (line) end  function keys.Comment     (data,line)   -- Comment DesignSize 12 (pts)   -- Comment TFM designsize: 12 (in points) @@ -640,7 +640,7 @@ local function copytotfm(data)          local spacer     = "space"          local spaceunits = 500          -- -        local monospaced  = metadata.isfixedpitch +        local monospaced  = metadata.monospaced          local charwidth   = metadata.charwidth          local italicangle = metadata.italicangle          local charxheight = metadata.xheight and metadata.xheight > 0 and metadata.xheight diff --git a/src/fontloader/misc/fontloader-font-con.lua b/src/fontloader/misc/fontloader-font-con.lua index 72fbb5c..383a403 100644 --- a/src/fontloader/misc/fontloader-font-con.lua +++ b/src/fontloader/misc/fontloader-font-con.lua @@ -191,10 +191,9 @@ in the process; numbers are of course copies. Here 65536 equals 1pt. (Due to  excessive memory usage in CJK fonts, we no longer pass the boundingbox.)</p>  --ldx]]-- --- The scaler is only used for otf and afm and virtual fonts. If --- a virtual font has italic correction make sure to set the --- hasitalics flag. Some more flags will be added in --- the future. +-- The scaler is only used for otf and afm and virtual fonts. If a virtual font has italic +-- correction make sure to set the hasitalics flag. Some more flags will be added in the +-- future.  --[[ldx--  <p>The reason why the scaler was originally split, is that for a while we experimented @@ -426,6 +425,7 @@ function constructors.scale(tfmdata,specification)      local vdelta         = delta      --      target.designsize    = parameters.designsize -- not really needed so it might become obsolete +    target.units         = units      target.units_per_em  = units                 -- just a trigger for the backend      --      local direction      = properties.direction or tfmdata.direction or 0 -- pointless, as we don't use omf fonts at all @@ -562,26 +562,27 @@ function constructors.scale(tfmdata,specification)          target.mathparameters = nil -- nop      end      -- -    local italickey  = "italic" -    local useitalics = true -- something context -    -- -    -- some context specific trickery (this will move to a plugin) +    -- Here we support some context specific trickery (this might move to a plugin). During the +    -- transition to opentype the engine had troubles with italics so we had some additional code +    -- for fixing that. In node mode (text) we don't care much if italics gets passed because +    -- the engine does nothign with them then.      --      if hasmath then -     -- the latest luatex can deal with it itself so we now disable this -     -- mechanism here -     -- -     -- if properties.mathitalics then -     --     italickey = "italic_correction" -     --     if trace_defining then -     --         report_defining("math italics disabled for font %a, fullname %a, filename %a",name,fullname,filename) -     --     end -     -- end -        autoitalicamount = false -- new -    elseif properties.textitalics then -        italickey = "italic_correction" -        useitalics = false -        if properties.delaytextitalics then +        local mathitalics = properties.mathitalics +        if mathitalics == false then +            if trace_defining then +                report_defining("%s italics %s for font %a, fullname %a, filename %a","math",hasitalics and "ignored" or "disabled",name,fullname,filename) +            end +            hasitalics       = false +            autoitalicamount = false +        end +    else +        local textitalics = properties.textitalics +        if textitalics == false then +            if trace_defining then +                report_defining("%s italics %s for font %a, fullname %a, filename %a","text",hasitalics and "ignored" or "disabled",name,fullname,filename) +            end +            hasitalics       = false              autoitalicamount = false          end      end @@ -590,8 +591,7 @@ function constructors.scale(tfmdata,specification)      --      if trace_defining then          report_defining("defining tfm, name %a, fullname %a, filename %a, hscale %a, vscale %a, math %a, italics %a", -            name,fullname,filename,hdelta,vdelta, -            hasmath and "enabled" or "disabled",useitalics and "enabled" or "disabled") +            name,fullname,filename,hdelta,vdelta,hasmath and "enabled" or "disabled",hasitalics and "enabled" or "disabled")      end      --      constructors.beforecopyingcharacters(target,tfmdata) @@ -606,15 +606,15 @@ function constructors.scale(tfmdata,specification)              local c = changed[unicode]              if c then                  description = descriptions[c] or descriptions[unicode] or character -                character = characters[c] or character -                index = description.index or c +                character   = characters[c] or character +                index       = description.index or c              else                  description = descriptions[unicode] or character -                index = description.index or unicode +                index       = description.index or unicode              end          else              description = descriptions[unicode] or character -            index = description.index or unicode +            index       = description.index or unicode          end          local width  = description.width          local height = description.height @@ -699,23 +699,6 @@ function constructors.scale(tfmdata,specification)              end          end          -- -        if autoitalicamount then -            local vi = description.italic -            if not vi then -                local vi = description.boundingbox[3] - description.width + autoitalicamount -                if vi > 0 then -- < 0 indicates no overshoot or a very small auto italic -                    chr[italickey] = vi*hdelta -                end -            elseif vi ~= 0 then -                chr[italickey] = vi*hdelta -            end -        elseif hasitalics then -            local vi = description.italic -            if vi and vi ~= 0 then -                chr[italickey] = vi*hdelta -            end -        end -        -- to be tested          if hasmath then              -- todo, just operate on descriptions.math              local vn = character.next @@ -754,7 +737,7 @@ function constructors.scale(tfmdata,specification)                      end                  end              end -            local va = character.top_accent +            local va = character.accent              if va then                  chr.top_accent = vdelta*va              end @@ -777,6 +760,27 @@ function constructors.scale(tfmdata,specification)                      chr.mathkern = kerns -- singular -> should be patched in luatex !                  end              end +            if hasitalics then +                local vi = character.italic +                if vi and vi ~= 0 then +                    chr.italic = vi*hdelta +                end +            end +        elseif autoitalicamount then -- itlc feature +            local vi = description.italic +            if not vi then +                local vi = description.boundingbox[3] - description.width + autoitalicamount +                if vi > 0 then -- < 0 indicates no overshoot or a very small auto italic +                    chr.italic = vi*hdelta +                end +            elseif vi ~= 0 then +                chr.italic = vi*hdelta +            end +        elseif hasitalics then -- unlikely +            local vi = character.italic +            if vi and vi ~= 0 then +                chr.italic = vi*hdelta +            end          end          if haskerns then              local vk = character.kerns @@ -843,6 +847,8 @@ function constructors.scale(tfmdata,specification)          targetcharacters[unicode] = chr      end      -- +    properties.setitalics = hasitalics -- for postprocessing +    --      constructors.aftercopyingcharacters(target,tfmdata)      --      constructors.trytosharefont(target,tfmdata) @@ -895,12 +901,21 @@ function constructors.finalize(tfmdata)          parameters.slantfactor = tfmdata.slant or 0      end      -- -    if not parameters.designsize then -        parameters.designsize = tfmdata.designsize or (factors.pt * 10) +    local designsize = parameters.designsize +    if designsize then +        parameters.minsize = tfmdata.minsize or designsize +        parameters.maxsize = tfmdata.maxsize or designsize +    else +        designsize = factors.pt * 10 +        parameters.designsize = designsize +        parameters.minsize    = designsize +        parameters.maxsize    = designsize      end +    parameters.minsize = tfmdata.minsize or parameters.designsize +    parameters.maxsize = tfmdata.maxsize or parameters.designsize      --      if not parameters.units then -        parameters.units = tfmdata.units_per_em or 1000 +        parameters.units = tfmdata.units or tfmdata.units_per_em or 1000      end      --      if not tfmdata.descriptions then @@ -976,6 +991,7 @@ function constructors.finalize(tfmdata)      tfmdata.auto_protrude  = nil      tfmdata.extend         = nil      tfmdata.slant          = nil +    tfmdata.units          = nil      tfmdata.units_per_em   = nil      --      tfmdata.cache          = nil diff --git a/src/fontloader/misc/fontloader-font-def.lua b/src/fontloader/misc/fontloader-font-def.lua index fdded3c..add42ee 100644 --- a/src/fontloader/misc/fontloader-font-def.lua +++ b/src/fontloader/misc/fontloader-font-def.lua @@ -183,10 +183,11 @@ end  function resolvers.name(specification)      local resolve = fonts.names.resolve      if resolve then -        local resolved, sub = resolve(specification.name,specification.sub,specification) -- we pass specification for overloaded versions +        local resolved, sub, subindex = resolve(specification.name,specification.sub,specification) -- we pass specification for overloaded versions          if resolved then              specification.resolved = resolved              specification.sub      = sub +            specification.subindex = subindex              local suffix = lower(suffixonly(resolved))              if fonts.formats[suffix] then                  specification.forced     = suffix @@ -204,10 +205,11 @@ end  function resolvers.spec(specification)      local resolvespec = fonts.names.resolvespec      if resolvespec then -        local resolved, sub = resolvespec(specification.name,specification.sub,specification) -- we pass specification for overloaded versions +        local resolved, sub, subindex = resolvespec(specification.name,specification.sub,specification) -- we pass specification for overloaded versions          if resolved then              specification.resolved   = resolved              specification.sub        = sub +            specification.subindex   = subindex              specification.forced     = lower(suffixonly(resolved))              specification.forcedname = resolved              specification.name       = removesuffix(resolved) diff --git a/src/fontloader/misc/fontloader-font-map.lua b/src/fontloader/misc/fontloader-font-map.lua index 69474ba..b645d9a 100644 --- a/src/fontloader/misc/fontloader-font-map.lua +++ b/src/fontloader/misc/fontloader-font-map.lua @@ -31,25 +31,27 @@ of obsolete. Some code may move to runtime or auxiliary modules.</p>  <p>The name to unciode related code will stay of course.</p>  --ldx]]-- -local function loadlumtable(filename) -- will move to font goodies -    local lumname = file.replacesuffix(file.basename(filename),"lum") -    local lumfile = resolvers.findfile(lumname,"map") or "" -    if lumfile ~= "" and lfs.isfile(lumfile) then -        if trace_loading or trace_mapping then -            report_fonts("loading map table %a",lumfile) -        end -        lumunic = dofile(lumfile) -        return lumunic, lumfile -    end -end +-- local function loadlumtable(filename) -- will move to font goodies +--     local lumname = file.replacesuffix(file.basename(filename),"lum") +--     local lumfile = resolvers.findfile(lumname,"map") or "" +--     if lumfile ~= "" and lfs.isfile(lumfile) then +--         if trace_loading or trace_mapping then +--             report_fonts("loading map table %a",lumfile) +--         end +--         lumunic = dofile(lumfile) +--         return lumunic, lumfile +--     end +-- end  local hex     = R("AF","09") -local hexfour = (hex*hex*hex*hex)         / function(s) return tonumber(s,16) end -local hexsix  = (hex*hex*hex*hex*hex*hex) / function(s) return tonumber(s,16) end +----- hexfour = (hex*hex*hex*hex)         / function(s) return tonumber(s,16) end +----- hexsix  = (hex*hex*hex*hex*hex*hex) / function(s) return tonumber(s,16) end +local hexfour = (hex*hex*hex^-2) / function(s) return tonumber(s,16) end +local hexsix  = (hex*hex*hex^-4) / function(s) return tonumber(s,16) end  local dec     = (R("09")^1) / tonumber  local period  = P(".") -local unicode = P("uni")   * (hexfour * (period + P(-1)) * Cc(false) + Ct(hexfour^1) * Cc(true)) -local ucode   = P("u")     * (hexsix  * (period + P(-1)) * Cc(false) + Ct(hexsix ^1) * Cc(true)) +local unicode = (P("uni") + P("UNI")) * (hexfour * (period + P(-1)) * Cc(false) + Ct(hexfour^1) * Cc(true)) -- base planes +local ucode   = (P("u")   + P("U")  ) * (hexsix  * (period + P(-1)) * Cc(false) + Ct(hexsix ^1) * Cc(true)) -- extended  local index   = P("index") * dec * Cc(false)  local parser  = unicode + ucode + index @@ -168,7 +170,6 @@ end  --     return s  -- end -mappings.loadlumtable        = loadlumtable  mappings.makenameparser      = makenameparser  mappings.tounicode           = tounicode  mappings.tounicode16         = tounicode16 @@ -179,13 +180,13 @@ local ligseparator = P("_")  local varseparator = P(".")  local namesplitter = Ct(C((1 - ligseparator - varseparator)^1) * (ligseparator * C((1 - ligseparator - varseparator)^1))^0) +-- maybe: ff fi fl ffi ffl => f_f f_i f_l f_f_i f_f_l +  -- local function test(name)  --     local split = lpegmatch(namesplitter,name)  --     print(string.formatters["%s: [% t]"](name,split))  -- end --- maybe: ff fi fl ffi ffl => f_f f_i f_l f_f_i f_f_l -  -- test("i.f_")  -- test("this")  -- test("this.that") @@ -221,332 +222,184 @@ end  mappings.overloads = overloads -function mappings.addtounicode(data,filename) -    local resources    = data.resources -    local properties   = data.properties -    local descriptions = data.descriptions -    local unicodes     = resources.unicodes -    local lookuptypes  = resources.lookuptypes +function mappings.addtounicode(data,filename,checklookups) +    local resources = data.resources +    local unicodes  = resources.unicodes      if not unicodes then          return      end +    local properties    = data.properties +    local descriptions  = data.descriptions      -- we need to move this code      unicodes['space']   = unicodes['space']  or 32      unicodes['hyphen']  = unicodes['hyphen'] or 45      unicodes['zwj']     = unicodes['zwj']    or 0x200D      unicodes['zwnj']    = unicodes['zwnj']   or 0x200C -    local private       = fonts.constructors.privateoffset -    local unicodevector = fonts.encodings.agl.unicodes -- loaded runtime in context -    ----- namevector    = fonts.encodings.agl.names    -- loaded runtime in context -    local missing       = { } -    local lumunic, uparser, oparser -    local cidinfo, cidnames, cidcodes, usedmap -    -- -    cidinfo = properties.cidinfo -    usedmap = cidinfo and fonts.cid.getmap(cidinfo)      -- +    local private       = fonts.constructors and fonts.constructors.privateoffset or 0xF0000 -- 0x10FFFF +    local unicodevector = fonts.encodings.agl.unicodes or { } -- loaded runtime in context +    local contextvector = fonts.encodings.agl.ctxcodes or { } -- loaded runtime in context +    local missing       = { } +    local nofmissing    = 0 +    local oparser       = nil +    local cidnames      = nil +    local cidcodes      = nil +    local cidinfo       = properties.cidinfo +    local usedmap       = cidinfo and fonts.cid.getmap(cidinfo) +    local uparser       = makenameparser() -- hm, every time?      if usedmap then -        oparser  = usedmap and makenameparser(cidinfo.ordering) -        cidnames = usedmap.names -        cidcodes = usedmap.unicodes +          oparser       = usedmap and makenameparser(cidinfo.ordering) +          cidnames      = usedmap.names +          cidcodes      = usedmap.unicodes      end -    uparser = makenameparser() -    local ns, nl = 0, 0 +    local ns            = 0 +    local nl            = 0 +    --      for unic, glyph in next, descriptions do -        local index = glyph.index -        local name  = glyph.name -        local r = overloads[name] -        if r then -            -- get rid of weird ligatures -         -- glyph.name    = r.name -            glyph.unicode = r.unicode -        elseif unic == -1 or unic >= private or (unic >= 0xE000 and unic <= 0xF8FF) or unic == 0xFFFE or unic == 0xFFFF then -            local unicode = lumunic and lumunic[name] or unicodevector[name] -            if unicode then -                glyph.unicode = unicode -                ns            = ns + 1 -            end -            -- cidmap heuristics, beware, there is no guarantee for a match unless -            -- the chain resolves -            if (not unicode) and usedmap then -                local foundindex = lpegmatch(oparser,name) -                if foundindex then -                    unicode = cidcodes[foundindex] -- name to number -                    if unicode then -                        glyph.unicode = unicode -                        ns            = ns + 1 -                    else -                        local reference = cidnames[foundindex] -- number to name -                        if reference then -                            local foundindex = lpegmatch(oparser,reference) -                            if foundindex then -                                unicode = cidcodes[foundindex] -                                if unicode then -                                    glyph.unicode = unicode -                                    ns            = ns + 1 +        local name = glyph.name +        if name then +            local index = glyph.index +            local r = overloads[name] +            if r then +                -- get rid of weird ligatures +             -- glyph.name    = r.name +                glyph.unicode = r.unicode +            elseif not unic or unic == -1 or unic >= private or (unic >= 0xE000 and unic <= 0xF8FF) or unic == 0xFFFE or unic == 0xFFFF then +                local unicode = unicodevector[name] or contextvector[name] +                if unicode then +                    glyph.unicode = unicode +                    ns            = ns + 1 +                end +                -- cidmap heuristics, beware, there is no guarantee for a match unless +                -- the chain resolves +                if (not unicode) and usedmap then +                    local foundindex = lpegmatch(oparser,name) +                    if foundindex then +                        unicode = cidcodes[foundindex] -- name to number +                        if unicode then +                            glyph.unicode = unicode +                            ns            = ns + 1 +                        else +                            local reference = cidnames[foundindex] -- number to name +                            if reference then +                                local foundindex = lpegmatch(oparser,reference) +                                if foundindex then +                                    unicode = cidcodes[foundindex] +                                    if unicode then +                                        glyph.unicode = unicode +                                        ns            = ns + 1 +                                    end                                  end -                            end -                            if not unicode or unicode == "" then -                                local foundcodes, multiple = lpegmatch(uparser,reference) -                                if foundcodes then -                                    glyph.unicode = foundcodes -                                    if multiple then -                                        nl      = nl + 1 -                                        unicode = true -                                    else -                                        ns      = ns + 1 -                                        unicode = foundcodes +                                if not unicode or unicode == "" then +                                    local foundcodes, multiple = lpegmatch(uparser,reference) +                                    if foundcodes then +                                        glyph.unicode = foundcodes +                                        if multiple then +                                            nl      = nl + 1 +                                            unicode = true +                                        else +                                            ns      = ns + 1 +                                            unicode = foundcodes +                                        end                                      end                                  end                              end                          end                      end                  end -            end -            -- a.whatever or a_b_c.whatever or a_b_c (no numbers) a.b_ -            -- -            -- It is not trivial to find a solution that suits all fonts. We tried several alternatives -            -- and this one seems to work reasonable also with fonts that use less standardized naming -            -- schemes. The extra private test is tested by KE and seems to work okay with non-typical -            -- fonts as well. -            -- -            -- The next time I look into this, I'll add an extra analysis step to the otf loader (we can -            -- resolve some tounicodes by looking into the gsub data tables that are bound to glyphs. -            -- --- a real tricky last resort: --- ---             local lookups = glyph.lookups ---             if lookups then ---                 for _, lookup in next, lookups do -- assume consistency else we need to sort ---                     for i=1,#lookup do ---                         local l = lookup[i] ---                         if l.type == "ligature" then ---                             local s = l.specification ---                             if s.char == glyph.name then ---                                 local components = s.components ---                                 if components then ---                                     local t, n = { }, 0 ---                                     unicode = true ---                                     for l=1,#components do ---                                         local base = components[l] ---                                         local u = unicodes[base] or unicodevector[base] ---                                         if not u then ---                                             break ---                                         elseif type(u) == "table" then ---                                             if u[1] >= private then ---                                                 unicode = false ---                                                 break ---                                             end ---                                             n = n + 1 ---                                             t[n] = u[1] ---                                         else ---                                             if u >= private then ---                                                 unicode = false ---                                                 break ---                                             end ---                                             n = n + 1 ---                                             t[n] = u ---                                         end ---                                     end ---                                     if n == 0 then -- done then ---                                         -- nothing ---                                     elseif n == 1 then ---                                         glyph.unicode = t[1] ---                                     else ---                                         glyph.unicode = t ---                                     end ---                                     nl = nl + 1 ---                                     break ---                                 end ---                             end ---                         end ---                     end ---                     if unicode then ---                         break ---                     end ---                 end ---             end -            if not unicode or unicode == "" then -                local split = lpegmatch(namesplitter,name) -                local nsplit = split and #split or 0 -                local t, n = { }, 0 -                unicode = true -                for l=1,nsplit do -                    local base = split[l] -                    local u = unicodes[base] or unicodevector[base] -                    if not u then -                        break -                    elseif type(u) == "table" then -                        if u[1] >= private then -                            unicode = false -                            break +                -- a.whatever or a_b_c.whatever or a_b_c (no numbers) a.b_ +                -- +                -- It is not trivial to find a solution that suits all fonts. We tried several alternatives +                -- and this one seems to work reasonable also with fonts that use less standardized naming +                -- schemes. The extra private test is tested by KE and seems to work okay with non-typical +                -- fonts as well. +                -- +                if not unicode or unicode == "" then +                    local split  = lpegmatch(namesplitter,name) +                    local nsplit = split and #split or 0 -- add if +                    if nsplit == 0 then +                        -- skip +                    elseif nsplit == 1 then +                        local base = split[1] +                        local u = unicodes[base] or unicodevector[base] or contextvector[name] +                        if not u then +                            -- skip +                        elseif type(u) == "table" then +                            -- unlikely +                            if u[1] < private then +                                unicode = u +                                glyph.unicode = unicode +                            end +                        elseif u < private then +                            unicode = u +                            glyph.unicode = unicode                          end -                        n = n + 1 -                        t[n] = u[1]                      else -                        if u >= private then -                            unicode = false -                            break -                        end -                        n = n + 1 -                        t[n] = u -                    end -                end -                if n == 0 then -- done then -                    -- nothing -                elseif n == 1 then -                    glyph.unicode = t[1] -                else -                    glyph.unicode = t -                end -                nl = nl + 1 -            end -            -- last resort (we might need to catch private here as well) -            if not unicode or unicode == "" then -                local foundcodes, multiple = lpegmatch(uparser,name) -                if foundcodes then -                    glyph.unicode = foundcodes -                    if multiple then -                        nl      = nl + 1 -                        unicode = true -                    else -                        ns      = ns + 1 -                        unicode = foundcodes -                    end -                end -            end -            -- check using substitutes and alternates -            local r = overloads[unicode] -            if r then -                unicode = r.unicode -                glyph.unicode = unicode -            end -            -- -            if not unicode then -                missing[name] = true -            end -        end -    end -    if next(missing) then -        local guess  = { } -        -- helper -        local function check(gname,code,unicode) -            local description = descriptions[code] -            -- no need to add a self reference -            local variant = description.name -            if variant == gname then -                return -            end -            -- the variant already has a unicode (normally that resultrs in a default tounicode to self) -            local unic = unicodes[variant] -            if unic == -1 or unic >= private or (unic >= 0xE000 and unic <= 0xF8FF) or unic == 0xFFFE or unic == 0xFFFF then -                -- no default mapping and therefore maybe no tounicode yet -            else -                return -            end -            -- the variant already has a tounicode -            if descriptions[code].unicode then -                return -            end -            -- add to the list -            local g = guess[variant] -         -- local r = overloads[unicode] -         -- if r then -         --     unicode = r.unicode -         -- end -            if g then -                g[gname] = unicode -            else -                guess[variant] = { [gname] = unicode } -            end -        end -        -- -        for unicode, description in next, descriptions do -            local slookups = description.slookups -            if slookups then -                local gname = description.name -                for tag, data in next, slookups do -                    local lookuptype = lookuptypes[tag] -                    if lookuptype == "alternate" then -                        for i=1,#data do -                            check(gname,data[i],unicode) -                        end -                    elseif lookuptype == "substitution" then -                        check(gname,data,unicode) -                    end -                end -            end -            local mlookups = description.mlookups -            if mlookups then -                local gname = description.name -                for tag, list in next, mlookups do -                    local lookuptype = lookuptypes[tag] -                    if lookuptype == "alternate" then -                        for i=1,#list do -                            local data = list[i] -                            for i=1,#data do -                                check(gname,data[i],unicode) +                        local t, n = { }, 0 +                        for l=1,nsplit do +                            local base = split[l] +                            local u = unicodes[base] or unicodevector[base] or contextvector[name] +                            if not u then +                                break +                            elseif type(u) == "table" then +                                if u[1] >= private then +                                    break +                                end +                                n = n + 1 +                                t[n] = u[1] +                            else +                                if u >= private then +                                    break +                                end +                                n = n + 1 +                                t[n] = u                              end                          end -                    elseif lookuptype == "substitution" then -                        for i=1,#list do -                            check(gname,list[i],unicode) +                        if n > 0 then +                            if n == 1 then +                                unicode = t[1] +                            else +                                unicode = t +                            end +                            glyph.unicode = unicode                          end                      end +                    nl = nl + 1                  end -            end -        end -        -- resolve references -        local done = true -        while done do -            done = false -            for k, v in next, guess do -                if type(v) ~= "number" then -                    for kk, vv in next, v do -                        if vv == -1 or vv >= private or (vv >= 0xE000 and vv <= 0xF8FF) or vv == 0xFFFE or vv == 0xFFFF then -                            local uu = guess[kk] -                            if type(uu) == "number" then -                                guess[k] = uu -                                done = true -                            end +                -- last resort (we might need to catch private here as well) +                if not unicode or unicode == "" then +                    local foundcodes, multiple = lpegmatch(uparser,name) +                    if foundcodes then +                        glyph.unicode = foundcodes +                        if multiple then +                            nl      = nl + 1 +                            unicode = true                          else -                            guess[k] = vv -                            done = true +                            ns      = ns + 1 +                            unicode = foundcodes                          end                      end                  end -            end -        end -        -- wrap up -        local orphans = 0 -        local guessed = 0 -        for k, v in next, guess do -            if type(v) == "number" then -                descriptions[unicodes[k]].unicode = descriptions[v].unicode or v -- can also be a table -                guessed = guessed + 1 -            else -                local t = nil -                local l = lower(k) -                local u = unicodes[l] -                if not u then -                    orphans = orphans + 1 -                elseif u == -1 or u >= private or (u >= 0xE000 and u <= 0xF8FF) or u == 0xFFFE or u == 0xFFFF then -                    local unicode = descriptions[u].unicode -                    if unicode then -                        descriptions[unicodes[k]].unicode = unicode -                        guessed = guessed + 1 -                    else -                        orphans = orphans + 1 -                    end -                else -                    orphans = orphans + 1 +                -- check using substitutes and alternates +                local r = overloads[unicode] +                if r then +                    unicode = r.unicode +                    glyph.unicode = unicode +                end +                -- +                if not unicode then +                    missing[unic] = true +                    nofmissing    = nofmissing + 1                  end              end +        else +            -- no name          end -        if trace_loading and orphans > 0 or guessed > 0 then -            report_fonts("%s glyphs with no related unicode, %s guessed, %s orphans",guessed+orphans,guessed,orphans) -        end      end +    if type(checklookups) == "function" then +        checklookups(data,missing,nofmissing) +    end +    -- todo: go lowercase      if trace_mapping then          for unic, glyph in table.sortedhash(descriptions) do              local name    = glyph.name diff --git a/src/fontloader/misc/fontloader-font-otb.lua b/src/fontloader/misc/fontloader-font-otb.lua index 4e955a1..c9f5d4a 100644 --- a/src/fontloader/misc/fontloader-font-otb.lua +++ b/src/fontloader/misc/fontloader-font-otb.lua @@ -321,14 +321,14 @@ local function preparesubstitutions(tfmdata,feature,value,validlookups,lookuplis      for unicode, character in next, characters do          local description = descriptions[unicode] -        local lookups = description.slookups +        local lookups     = description.slookups          if lookups then              for l=1,#lookuplist do                  local lookupname = lookuplist[l]                  local lookupdata = lookups[lookupname]                  if lookupdata then                      local lookuptype = lookuptypes[lookupname] -                    local action = actions[lookuptype] +                    local action     = actions[lookuptype]                      if action then                          action(lookupdata,lookuptags,lookupname,description,unicode)                      end @@ -342,7 +342,7 @@ local function preparesubstitutions(tfmdata,feature,value,validlookups,lookuplis                  local lookuplist = lookups[lookupname]                  if lookuplist then                      local lookuptype = lookuptypes[lookupname] -                    local action = actions[lookuptype] +                    local action     = actions[lookuptype]                      if action then                          for i=1,#lookuplist do                              action(lookuplist[i],lookuptags,lookupname,description,unicode) @@ -614,8 +614,8 @@ local function featuresinitializer(tfmdata,value)              local collectlookups    = otf.collectlookups              local rawdata           = tfmdata.shared.rawdata              local properties        = tfmdata.properties -            local script            = properties.script -            local language          = properties.language +            local script            = properties.script   -- or "dflt" -- can be nil +            local language          = properties.language -- or "dflt" -- can be nil              local basesubstitutions = rawdata.resources.features.gsub              local basepositionings  = rawdata.resources.features.gpos              -- diff --git a/src/fontloader/misc/fontloader-font-otf.lua b/src/fontloader/misc/fontloader-font-otf.lua index e7a97c6..0ca1e98 100644 --- a/src/fontloader/misc/fontloader-font-otf.lua +++ b/src/fontloader/misc/fontloader-font-otf.lua @@ -12,15 +12,19 @@ if not modules then modules = { } end modules ['font-otf'] = {  -- to_table -> totable  -- ascent descent +-- to be checked: combinations like: +-- +-- current="ABCD" with [A]=nothing, [BC]=ligature, [D]=single (applied to result of BC so funny index) +-- +-- unlikely but possible +  -- more checking against low level calls of functions  local utfbyte = utf.byte -local format, gmatch, gsub, find, match, lower, strip = string.format, string.gmatch, string.gsub, string.find, string.match, string.lower, string.strip +local gmatch, gsub, find, match, lower, strip = string.gmatch, string.gsub, string.find, string.match, string.lower, string.strip  local type, next, tonumber, tostring = type, next, tonumber, tostring  local abs = math.abs -local insert = table.insert -local lpegmatch = lpeg.match -local reversed, concat, remove, sortedkeys = table.reversed, table.concat, table.remove, table.sortedkeys +local reversed, concat, insert, remove, sortedkeys = table.reversed, table.concat, table.insert, table.remove, table.sortedkeys  local ioflush = io.flush  local fastcopy, tohash, derivetable = table.fastcopy, table.tohash, table.derive  local formatters = string.formatters @@ -54,7 +58,7 @@ local otf                = fonts.handlers.otf  otf.glists               = { "gsub", "gpos" } -otf.version              = 2.812 -- beware: also sync font-mis.lua +otf.version              = 2.819 -- beware: also sync font-mis.lua and in mtx-fonts  otf.cache                = containers.define("fonts", "otf", otf.version, true)  local hashes             = fonts.hashes @@ -283,13 +287,17 @@ local ordered_enhancers = {      "check glyphs",      "check metadata", -    "check extra features", -- after metadata +--     "check extra features", -- after metadata      "prepare tounicode",      "check encoding", -- moved      "add duplicates", +    "expand lookups", -- a temp hack awaiting the lua loader + +--     "check extra features", -- after metadata and duplicates +      "cleanup tables",      "compact lookups", @@ -386,6 +394,7 @@ function otf.load(filename,sub,featurefile) -- second argument (format) is gone      if featurefile then          name = name .. "@" .. file.removesuffix(file.basename(featurefile))      end +    -- or: sub = tonumber(sub)      if sub == "" then          sub = false      end @@ -442,6 +451,7 @@ function otf.load(filename,sub,featurefile) -- second argument (format) is gone          end       end       if reload then +        starttiming("fontloader")          report_otf("loading %a, hash %a",filename,hash)          local fontdata, messages          if sub then @@ -476,6 +486,7 @@ function otf.load(filename,sub,featurefile) -- second argument (format) is gone              data = {                  size        = size,                  time        = time, +                subfont     = sub,                  format      = otf_format(filename),                  featuredata = featurefiles,                  resources   = { @@ -512,7 +523,6 @@ function otf.load(filename,sub,featurefile) -- second argument (format) is gone                      tounicodetable = Ct(splitter),                  },              } -            starttiming(data)              report_otf("file size: %s", size)              enhancers.apply(data,filename,fontdata)              local packtime = { } @@ -529,10 +539,10 @@ function otf.load(filename,sub,featurefile) -- second argument (format) is gone              if cleanup > 1 then                  collectgarbage("collect")              end -            stoptiming(data) +            stoptiming("fontloader")              if elapsedtime then -- not in generic -                report_otf("preprocessing and caching time %s, packtime %s", -                    elapsedtime(data),packdata and elapsedtime(packtime) or 0) +                report_otf("loading, optimizing, packing and caching time %s, pack time %s", +                    elapsedtime("fontloader"),packdata and elapsedtime(packtime) or 0)              end              close_font(fontdata) -- free memory              if cleanup > 3 then @@ -543,6 +553,7 @@ function otf.load(filename,sub,featurefile) -- second argument (format) is gone                  collectgarbage("collect")              end          else +            stoptiming("fontloader")              data = nil              report_otf("loading failed due to read error")          end @@ -589,6 +600,7 @@ function otf.load(filename,sub,featurefile) -- second argument (format) is gone              applyruntimefixes(filename,data)          end          enhance("add dimensions",data,filename,nil,false) +enhance("check extra features",data,filename)          if trace_sequences then              showfeatureorder(data,filename)          end @@ -697,7 +709,7 @@ local function somecopy(old) -- fast one      end  end --- not setting hasitalics and class (when nil) during table cronstruction can save some mem +-- not setting hasitalics and class (when nil) during table construction can save some mem  actions["prepare glyphs"] = function(data,filename,raw)      local tableversion = tonumber(raw.table_version) or 0 @@ -780,7 +792,7 @@ actions["prepare glyphs"] = function(data,filename,raw)                                  end                                  if not unicode or unicode == -1 then -- or unicode >= criterium then                                      if not name then -                                        name = format("u%06X.ctx",private) +                                        name = formatters["u%06X.ctx"](private)                                      end                                      unicode = private                                      unicodes[name] = private @@ -803,7 +815,7 @@ actions["prepare glyphs"] = function(data,filename,raw)                                   --     end                                   -- end                                      if not name then -                                        name = format("u%06X.ctx",unicode) +                                        name = formatters["u%06X.ctx"](unicode)                                      end                                      unicodes[name] = unicode                                      nofunicodes = nofunicodes + 1 @@ -819,35 +831,35 @@ actions["prepare glyphs"] = function(data,filename,raw)                                      glyph       = glyph,                                  }                                  descriptions[unicode] = description -local altuni = glyph.altuni -if altuni then - -- local d -    for i=1,#altuni do -        local a = altuni[i] -        local u = a.unicode -        if u ~= unicode then -            local v = a.variant -            if v then -                -- tricky: no addition to d? needs checking but in practice such dups are either very simple -                -- shapes or e.g cjk with not that many features -                local vv = variants[v] -                if vv then -                    vv[u] = unicode -                else -- xits-math has some: -                    vv = { [u] = unicode } -                    variants[v] = vv -                end -         -- elseif d then -         --     d[#d+1] = u -         -- else -         --     d = { u } -            end -        end -    end - -- if d then - --     duplicates[unicode] = d -- is this needed ? - -- end -end +                                local altuni = glyph.altuni +                                if altuni then +                                 -- local d +                                    for i=1,#altuni do +                                        local a = altuni[i] +                                        local u = a.unicode +                                        if u ~= unicode then +                                            local v = a.variant +                                            if v then +                                                -- tricky: no addition to d? needs checking but in practice such dups are either very simple +                                                -- shapes or e.g cjk with not that many features +                                                local vv = variants[v] +                                                if vv then +                                                    vv[u] = unicode +                                                else -- xits-math has some: +                                                    vv = { [u] = unicode } +                                                    variants[v] = vv +                                                end +                                         -- elseif d then +                                         --     d[#d+1] = u +                                         -- else +                                         --     d = { u } +                                            end +                                        end +                                    end +                                 -- if d then +                                 --     duplicates[unicode] = d -- is this needed ? +                                 -- end +                                end                              end                          end                      else @@ -916,7 +928,7 @@ end                      end                      indices[index] = unicode                   -- if not name then -                 --     name = format("u%06X",unicode) -- u%06X.ctx +                 --     name = formatters["u%06X"](unicode) -- u%06X.ctx                   -- end                      descriptions[unicode] = {                       -- width       = glyph.width, @@ -1089,7 +1101,7 @@ actions["add duplicates"] = function(data,filename,raw)                      end                      if u > 0 then -- and                          local duplicate = table.copy(description) -- else packing problem -                        duplicate.comment = format("copy of U+%05X", unicode) +                        duplicate.comment = formatters["copy of %U"](unicode)                          descriptions[u] = duplicate                       -- validduplicates[#validduplicates+1] = u                          if trace_loading then @@ -1107,16 +1119,16 @@ end  -- boundingbox: split into ht/dp takes more memory (larger tables and less sharing)  actions["analyze glyphs"] = function(data,filename,raw) -- maybe integrate this in the previous -    local descriptions      = data.descriptions -    local resources         = data.resources -    local metadata          = data.metadata -    local properties        = data.properties -    local hasitalics        = false -    local widths            = { } -    local marks             = { } -- always present (saves checking) +    local descriptions = data.descriptions +    local resources    = data.resources +    local metadata     = data.metadata +    local properties   = data.properties +    local hasitalics   = false +    local widths       = { } +    local marks        = { } -- always present (saves checking)      for unicode, description in next, descriptions do -        local glyph = description.glyph -        local italic = glyph.italic_correction +        local glyph  = description.glyph +        local italic = glyph.italic_correction -- only in a math font (we also have vert/horiz)          if not italic then              -- skip          elseif italic == 0 then @@ -1185,7 +1197,8 @@ end  actions["reorganize features"] = function(data,filename,raw) -- combine with other      local features = { }      data.resources.features = features -    for k, what in next, otf.glists do +    for k=1,#otf.glists do +        local what = otf.glists[k]          local dw = raw[what]          if dw then              local f = { } @@ -1254,6 +1267,140 @@ actions["reorganize anchor classes"] = function(data,filename,raw)      end  end +-- local function checklookups(data,missing,nofmissing) +--     local resources    = data.resources +--     local unicodes     = resources.unicodes +--     local lookuptypes  = resources.lookuptypes +--     if not unicodes or not lookuptypes then +--         return +--     elseif nofmissing <= 0 then +--         return +--     end +--     local descriptions = data.descriptions +--     local private      = fonts.constructors and fonts.constructors.privateoffset or 0xF0000 -- 0x10FFFF +--     -- +--     local ns, nl = 0, 0 + +--     local guess  = { } +--     -- helper +--     local function check(gname,code,unicode) +--         local description = descriptions[code] +--         -- no need to add a self reference +--         local variant = description.name +--         if variant == gname then +--             return +--         end +--         -- the variant already has a unicode (normally that results in a default tounicode to self) +--         local unic = unicodes[variant] +--         if unic == -1 or unic >= private or (unic >= 0xE000 and unic <= 0xF8FF) or unic == 0xFFFE or unic == 0xFFFF then +--             -- no default mapping and therefore maybe no tounicode yet +--         else +--             return +--         end +--         -- the variant already has a tounicode +--         if descriptions[code].unicode then +--             return +--         end +--         -- add to the list +--         local g = guess[variant] +--      -- local r = overloads[unicode] +--      -- if r then +--      --     unicode = r.unicode +--      -- end +--         if g then +--             g[gname] = unicode +--         else +--             guess[variant] = { [gname] = unicode } +--         end +--     end +--     -- +--     for unicode, description in next, descriptions do +--         local slookups = description.slookups +--         if slookups then +--             local gname = description.name +--             for tag, data in next, slookups do +--                 local lookuptype = lookuptypes[tag] +--                 if lookuptype == "alternate" then +--                     for i=1,#data do +--                         check(gname,data[i],unicode) +--                     end +--                 elseif lookuptype == "substitution" then +--                     check(gname,data,unicode) +--                 end +--             end +--         end +--         local mlookups = description.mlookups +--         if mlookups then +--             local gname = description.name +--             for tag, list in next, mlookups do +--                 local lookuptype = lookuptypes[tag] +--                 if lookuptype == "alternate" then +--                     for i=1,#list do +--                         local data = list[i] +--                         for i=1,#data do +--                             check(gname,data[i],unicode) +--                         end +--                     end +--                 elseif lookuptype == "substitution" then +--                     for i=1,#list do +--                         check(gname,list[i],unicode) +--                     end +--                 end +--             end +--         end +--     end +--     -- resolve references +--     local done = true +--     while done do +--         done = false +--         for k, v in next, guess do +--             if type(v) ~= "number" then +--                 for kk, vv in next, v do +--                     if vv == -1 or vv >= private or (vv >= 0xE000 and vv <= 0xF8FF) or vv == 0xFFFE or vv == 0xFFFF then +--                         local uu = guess[kk] +--                         if type(uu) == "number" then +--                             guess[k] = uu +--                             done = true +--                         end +--                     else +--                         guess[k] = vv +--                         done = true +--                     end +--                 end +--             end +--         end +--     end +--     -- wrap up +--     local orphans = 0 +--     local guessed = 0 +--     for k, v in next, guess do +--         if type(v) == "number" then +--             descriptions[unicodes[k]].unicode = descriptions[v].unicode or v -- can also be a table +--             guessed = guessed + 1 +--         else +--             local t = nil +--             local l = lower(k) +--             local u = unicodes[l] +--             if not u then +--                 orphans = orphans + 1 +--             elseif u == -1 or u >= private or (u >= 0xE000 and u <= 0xF8FF) or u == 0xFFFE or u == 0xFFFF then +--                 local unicode = descriptions[u].unicode +--                 if unicode then +--                     descriptions[unicodes[k]].unicode = unicode +--                     guessed = guessed + 1 +--                 else +--                     orphans = orphans + 1 +--                 end +--             else +--                 orphans = orphans + 1 +--             end +--         end +--     end +--     if trace_loading and orphans > 0 or guessed > 0 then +--         report_otf("%s glyphs with no related unicode, %s guessed, %s orphans",guessed+orphans,guessed,orphans) +--     end +-- end +  actions["prepare tounicode"] = function(data,filename,raw)      fonts.mappings.addtounicode(data,filename)  end @@ -1288,8 +1435,9 @@ actions["reorganize subtables"] = function(data,filename,raw)      local lookups         = { }      local chainedfeatures = { }      resources.sequences   = sequences -    resources.lookups     = lookups -    for _, what in next, otf.glists do +    resources.lookups     = lookups -- we also have lookups in data itself +    for k=1,#otf.glists do +        local what = otf.glists[k]          local dw = raw[what]          if dw then              for k=1,#dw do @@ -1375,12 +1523,6 @@ actions["reorganize subtables"] = function(data,filename,raw)      end  end --- test this: --- ---    for _, what in next, otf.glists do ---        raw[what] = nil ---    end -  actions["prepare lookups"] = function(data,filename,raw)      local lookups = raw.lookups      if lookups then @@ -1494,12 +1636,16 @@ end  actions["reorganize lookups"] = function(data,filename,raw) -- we could check for "" and n == 0      -- we prefer the before lookups in a normal order      if data.lookups then -        local splitter = data.helpers.tounicodetable -        local t_u_cache = { } -        local s_u_cache = t_u_cache -- string keys -        local t_h_cache = { } -        local s_h_cache = t_h_cache -- table keys (so we could use one cache) -        local r_u_cache = { } -- maybe shared +        local helpers      = data.helpers +        local duplicates   = data.resources.duplicates +        local splitter     = helpers.tounicodetable +        local t_u_cache    = { } +        local s_u_cache    = t_u_cache -- string keys +        local t_h_cache    = { } +        local s_h_cache    = t_h_cache -- table keys (so we could use one cache) +        local r_u_cache    = { } -- maybe shared +        helpers.matchcache = t_h_cache -- so that we can add duplicates +        --          for _, lookup in next, data.lookups do              local rules = lookup.rules              if rules then @@ -1653,6 +1799,50 @@ actions["reorganize lookups"] = function(data,filename,raw) -- we could check fo      end  end +actions["expand lookups"] = function(data,filename,raw) -- we could check for "" and n == 0 +    if data.lookups then +        local cache = data.helpers.matchcache +        if cache then +            local duplicates = data.resources.duplicates +            for key, hash in next, cache do +                local done = nil +                for key in next, hash do +                    local unicode = duplicates[key] +                    if not unicode then +                        -- no duplicate +                    elseif type(unicode) == "table" then +                        -- multiple duplicates +                        for i=1,#unicode do +                            local u = unicode[i] +                            if hash[u] then +                                -- already in set +                            elseif done then +                                done[u] = key +                            else +                                done = { [u] = key } +                            end +                        end +                    else +                        -- one duplicate +                        if hash[unicode] then +                            -- already in set +                        elseif done then +                            done[unicode] = key +                        else +                            done = { [unicode] = key } +                        end +                    end +                end +                if done then +                    for u in next, done do +                        hash[u] = true +                    end +                end +            end +        end +    end +end +  local function check_variants(unicode,the_variants,splitter,unicodes)      local variants = the_variants.variants      if variants then -- use splitter @@ -1693,11 +1883,11 @@ local function check_variants(unicode,the_variants,splitter,unicodes)              parts = nil          end      end -    local italic_correction = the_variants.italic_correction -    if italic_correction and italic_correction == 0 then -        italic_correction = nil +    local italic = the_variants.italic +    if italic and italic == 0 then +        italic = nil      end -    return variants, parts, italic_correction +    return variants, parts, italic  end  actions["analyze math"] = function(data,filename,raw) @@ -1706,15 +1896,16 @@ actions["analyze math"] = function(data,filename,raw)          local unicodes = data.resources.unicodes          local splitter = data.helpers.tounicodetable          for unicode, description in next, data.descriptions do -            local glyph          = description.glyph -            local mathkerns      = glyph.mathkern -- singular -            local horiz_variants = glyph.horiz_variants -            local vert_variants  = glyph.vert_variants -            local top_accent     = glyph.top_accent -            if mathkerns or horiz_variants or vert_variants or top_accent then +            local glyph        = description.glyph +            local mathkerns    = glyph.mathkern -- singular +            local hvariants    = glyph.horiz_variants +            local vvariants    = glyph.vert_variants +            local accent       = glyph.top_accent +            local italic       = glyph.italic_correction +            if mathkerns or hvariants or vvariants or accent or italic then                  local math = { } -                if top_accent then -                    math.top_accent = top_accent +                if accent then +                    math.accent = accent                  end                  if mathkerns then                      for k, v in next, mathkerns do @@ -1730,15 +1921,14 @@ actions["analyze math"] = function(data,filename,raw)                      end                      math.kerns = mathkerns                  end -                if horiz_variants then -                    math.horiz_variants, math.horiz_parts, math.horiz_italic_correction = check_variants(unicode,horiz_variants,splitter,unicodes) +                if hvariants then +                    math.hvariants, math.hparts, math.hitalic = check_variants(unicode,hvariants,splitter,unicodes)                  end -                if vert_variants then -                    math.vert_variants, math.vert_parts, math.vert_italic_correction = check_variants(unicode,vert_variants,splitter,unicodes) +                if vvariants then +                    math.vvariants, math.vparts, math.vitalic = check_variants(unicode,vvariants,splitter,unicodes)                  end -                local italic_correction = description.italic -                if italic_correction and italic_correction ~= 0 then -                    math.italic_correction = italic_correction +                if italic and italic ~= 0 then +                    math.italic = italic                  end                  description.math = math              end @@ -1903,7 +2093,7 @@ actions["merge kern classes"] = function(data,filename,raw)              report_otf("%s kern overloads ignored",ignored)          end          if blocked > 0 then -            report_otf("%s succesive kerns blocked",blocked) +            report_otf("%s successive kerns blocked",blocked)          end      end  end @@ -1940,18 +2130,21 @@ actions["check metadata"] = function(data,filename,raw)          end      end      -- +    local names = raw.names +    --      if metadata.validation_state and table.contains(metadata.validation_state,"bad_ps_fontname") then          -- the ff library does a bit too much (and wrong) checking ... so we need to catch this          -- at least for now          local function valid(what) -            local names = raw.names -            for i=1,#names do -                local list = names[i] -                local names = list.names -                if names then -                    local name = names[what] -                    if name and valid_ps_name(name) then -                        return name +            if names then +                for i=1,#names do +                    local list = names[i] +                    local names = list.names +                    if names then +                        local name = names[what] +                        if name and valid_ps_name(name) then +                            return name +                        end                      end                  end              end @@ -1975,6 +2168,33 @@ actions["check metadata"] = function(data,filename,raw)          check("fullname")      end      -- +    if names then +        local psname = metadata.psname +        if not psname or psname == "" then +            for i=1,#names do +                local name = names[i] +                -- Currently we use the same restricted search as in the new context (specific) font loader +                -- but we might add more lang checks (it worked ok in the new loaded so now we're in sync) +                -- This check here is also because there are (esp) cjk fonts out there with psnames different +                -- from fontnames (gives a bad lookup in backend). +                if lower(name.lang) == "english (us)" then +                    local specification = name.names +                    if specification then +                        local postscriptname = specification.postscriptname +                        if postscriptname then +                            psname = postscriptname +                        end +                    end +                end +                break +            end +        end +        if psname ~= metadata.fontname then +            report_otf("fontname %a, fullname %a, psname %a",metadata.fontname,metadata.fullname,psname) +        end +        metadata.psname = psname +    end +    --  end  actions["cleanup tables"] = function(data,filename,raw) @@ -2000,7 +2220,9 @@ end  -- we can share { } as it is never set ---- ligatures have an extra specification.char entry that we don't use +-- ligatures have an extra specification.char entry that we don't use + +-- mlookups only with pairs and ligatures  actions["reorganize glyph lookups"] = function(data,filename,raw)      local resources    = data.resources @@ -2081,14 +2303,14 @@ actions["reorganize glyph lookups"] = function(data,filename,raw)              if mlookups then                  description.mlookups = mlookups              end +         -- description.lookups = nil          end      end -  end  local zero = { 0, 0 } -actions["reorganize glyph anchors"] = function(data,filename,raw) -- when we replace inplace we safe entries +actions["reorganize glyph anchors"] = function(data,filename,raw)      local descriptions = data.descriptions      for unicode, description in next, descriptions do          local anchors = description.glyph.anchors @@ -2311,7 +2533,7 @@ end  -- we cannot share descriptions as virtual fonts might extend them (ok,  -- we could use a cache with a hash  -- --- we already assing an empty tabel to characters as we can add for +-- we already assign an empty tabel to characters as we can add for  -- instance protruding info and loop over characters; one is not supposed  -- to change descriptions and if one does so one should make a copy! @@ -2334,10 +2556,14 @@ local function copytotfm(data,cache_id)          local spaceunits     = 500          local spacer         = "space"          local designsize     = metadata.designsize or metadata.design_size or 100 +        local minsize        = metadata.minsize or metadata.design_range_bottom or designsize +        local maxsize        = metadata.maxsize or metadata.design_range_top    or designsize          local mathspecs      = metadata.math          --          if designsize == 0 then              designsize = 100 +            minsize    = 100 +            maxsize    = 100          end          if mathspecs then              for name, value in next, mathspecs do @@ -2355,8 +2581,11 @@ local function copytotfm(data,cache_id)                  local m = d.math                  if m then                      -- watch out: luatex uses horiz_variants for the parts -                    local variants = m.horiz_variants -                    local parts    = m.horiz_parts +                    -- +                    local italic   = m.italic +                    -- +                    local variants = m.hvariants +                    local parts    = m.hparts                   -- local done     = { [unicode] = true }                      if variants then                          local c = character @@ -2373,9 +2602,11 @@ local function copytotfm(data,cache_id)                          c.horiz_variants = parts                      elseif parts then                          character.horiz_variants = parts +                        italic = m.hitalic                      end -                    local variants = m.vert_variants -                    local parts    = m.vert_parts +                    -- +                    local variants = m.vvariants +                    local parts    = m.vparts                   -- local done     = { [unicode] = true }                      if variants then                          local c = character @@ -2392,15 +2623,18 @@ local function copytotfm(data,cache_id)                          c.vert_variants = parts                      elseif parts then                          character.vert_variants = parts +                        italic = m.vitalic                      end -                    local italic_correction = m.vert_italic_correction -                    if italic_correction then -                        character.vert_italic_correction = italic_correction -- was c. +                    -- +                    if italic and italic ~= 0 then +                        character.italic = italic -- overload                      end -                    local top_accent = m.top_accent -                    if top_accent then -                        character.top_accent = top_accent +                    -- +                    local accent = m.accent +                    if accent then +                        character.accent = accent                      end +                    --                      local kerns = m.kerns                      if kerns then                          character.mathkerns = kerns @@ -2413,16 +2647,16 @@ local function copytotfm(data,cache_id)          local filename = constructors.checkedfilename(resources)          local fontname = metadata.fontname          local fullname = metadata.fullname or fontname -        local psname   = fontname or fullname -        local units    = metadata.units_per_em or 1000 +        local psname   = metadata.psname or fontname or fullname +        local units    = metadata.units or metadata.units_per_em or 1000          --          if units == 0 then -- catch bugs in fonts              units = 1000 -- maybe 2000 when ttf -            metadata.units_per_em = 1000 +            metadata.units = 1000              report_otf("changing %a units to %a",0,units)          end          -- -        local monospaced  = metadata.isfixedpitch or (pfminfo.panose and pfminfo.panose.proportion == "Monospaced") +        local monospaced  = metadata.monospaced or metadata.isfixedpitch or (pfminfo.panose and pfminfo.panose.proportion == "Monospaced")          local charwidth   = pfminfo.avgwidth -- or unset          local charxheight = pfminfo.os2_xheight and pfminfo.os2_xheight > 0 and pfminfo.os2_xheight  -- charwidth = charwidth * units/1000 @@ -2492,17 +2726,16 @@ local function copytotfm(data,cache_id)              end          end          -- -        parameters.designsize = (designsize/10)*65536 -        parameters.ascender   = abs(metadata.ascent  or 0) -        parameters.descender  = abs(metadata.descent or 0) -        parameters.units      = units +        parameters.designsize    = (designsize/10)*65536 +        parameters.minsize       = (minsize   /10)*65536 +        parameters.maxsize       = (maxsize   /10)*65536 +        parameters.ascender      = abs(metadata.ascender  or metadata.ascent  or 0) +        parameters.descender     = abs(metadata.descender or metadata.descent or 0) +        parameters.units         = units          --          properties.space         = spacer          properties.encodingbytes = 2          properties.format        = data.format or otf_format(filename) or formats.otf --- if units ~= 1000 and format ~= "truetype" then ---     properties.format = "truetype" --- end          properties.noglyphnames  = true          properties.filename      = filename          properties.fontname      = fontname @@ -2703,3 +2936,111 @@ function otf.scriptandlanguage(tfmdata,attr)      local properties = tfmdata.properties      return properties.script or "dflt", properties.language or "dflt"  end + +-- a little bit of abstraction + +local function justset(coverage,unicode,replacement) +    coverage[unicode] = replacement +end + +otf.coverup = { +    stepkey = "subtables", +    actions = { +        substitution = justset, +        alternate    = justset, +        multiple     = justset, +        ligature     = justset, +        kern         = justset, +    }, +    register = function(coverage,lookuptype,format,feature,n,descriptions,resources) +        local name = formatters["ctx_%s_%s"](feature,n) +        if lookuptype == "kern" then +            resources.lookuptypes[name] = "position" +        else +            resources.lookuptypes[name] = lookuptype +        end +        for u, c in next, coverage do +            local description = descriptions[u] +            local slookups = description.slookups +            if slookups then +                slookups[name] = c +            else +                description.slookups = { [name] = c } +            end +-- inspect(feature,description) +        end +        return name +    end +} + +-- moved from font-oth.lua + +local function getgsub(tfmdata,k,kind) +    local description = tfmdata.descriptions[k] +    if description then +        local slookups = description.slookups -- we assume only slookups (we can always extend) +        if slookups then +            local shared = tfmdata.shared +            local rawdata = shared and shared.rawdata +            if rawdata then +                local lookuptypes = rawdata.resources.lookuptypes +                if lookuptypes then +                    local properties = tfmdata.properties +                    -- we could cache these +                    local validlookups, lookuplist = otf.collectlookups(rawdata,kind,properties.script,properties.language) +                    if validlookups then +                        for l=1,#lookuplist do +                            local lookup = lookuplist[l] +                            local found  = slookups[lookup] +                            if found then +                                return found, lookuptypes[lookup] +                            end +                        end +                    end +                end +            end +        end +    end +end + +otf.getgsub = getgsub -- returns value, gsub_kind + +function otf.getsubstitution(tfmdata,k,kind,value) +    local found, kind = getgsub(tfmdata,k,kind) +    if not found then +        -- +    elseif kind == "substitution" then +        return found +    elseif kind == "alternate" then +        local choice = tonumber(value) or 1 -- no random here (yet) +        return found[choice] or found[1] or k +    end +    return k +end + +otf.getalternate = otf.getsubstitution + +function otf.getmultiple(tfmdata,k,kind) +    local found, kind = getgsub(tfmdata,k,kind) +    if found and kind == "multiple" then +        return found +    end +    return { k } +end + +function otf.getkern(tfmdata,left,right,kind) +    local kerns = getgsub(tfmdata,left,kind or "kern",true) -- for now we use getsub +    if kerns then +        local found = kerns[right] +        local kind  = type(found) +        if kind == "table" then +            found = found[1][3] -- can be more clever +        elseif kind ~= "number" then +            found = false +        end +        if found then +            return found * tfmdata.parameters.factor +        end +    end +    return 0 +end diff --git a/src/fontloader/misc/fontloader-font-otp.lua b/src/fontloader/misc/fontloader-font-otp.lua index ebf36ed..91bd05b 100644 --- a/src/fontloader/misc/fontloader-font-otp.lua +++ b/src/fontloader/misc/fontloader-font-otp.lua @@ -12,9 +12,8 @@ if not modules then modules = { } end modules ['font-otp'] = {  --  -- unless we sort all hashes we can get a different pack order (no big deal but size can differ) -local next, type = next, type +local next, type, tostring = next, type, tostring  local sort, concat = table.sort, table.concat -local sortedhash = table.sortedhash  local trace_packing = false  trackers.register("otf.packing", function(v) trace_packing = v end)  local trace_loading = false  trackers.register("otf.loading", function(v) trace_loading = v end) @@ -148,6 +147,7 @@ end  -- we then need to sort more thanks to random hashing  local function packdata(data) +      if data then       -- stripdata(data)          local h, t, c = { }, { }, { } @@ -537,6 +537,7 @@ local unpacked_mt = {  }  local function unpackdata(data) +      if data then          local tables = data.tables          if tables then diff --git a/src/fontloader/misc/fontloader-font-tfm.lua b/src/fontloader/misc/fontloader-font-tfm.lua index ab03788..401dc83 100644 --- a/src/fontloader/misc/fontloader-font-tfm.lua +++ b/src/fontloader/misc/fontloader-font-tfm.lua @@ -24,6 +24,7 @@ local constructors             = fonts.constructors  local encodings                = fonts.encodings  local tfm                      = constructors.newhandler("tfm") +tfm.version                    = 1.000  local tfmfeatures              = constructors.newfeatures("tfm")  local registertfmfeature       = tfmfeatures.register diff --git a/src/fontloader/misc/fontloader-fonts-cbk.lua b/src/fontloader/misc/fontloader-fonts-cbk.lua index 81b5b6e..9da8151 100644 --- a/src/fontloader/misc/fontloader-fonts-cbk.lua +++ b/src/fontloader/misc/fontloader-fonts-cbk.lua @@ -160,12 +160,31 @@ function nodes.handlers.nodepass(head)                  local range = basefonts[i]                  local start = range[1]                  local stop  = range[2] -                if stop then -                    start, stop = ligaturing(start,stop) -                    start, stop = kerning(start,stop) -                elseif start then -                    start = ligaturing(start) -                    start = kerning(start) +                -- maybe even: if start and start ~= stop then +                if start or stop then +                    local prev  = nil +                    local next  = nil +                    local front = start == head +                    if stop then +                        next  = stop.next +                        start, stop = ligaturing(start,stop) +                        start, stop = kerning(start,stop) +                    elseif start then +                        prev  = start.prev +                        start = ligaturing(start) +                        start = kerning(start) +                    end +                    if prev then +                        start.prev = prev +                        prev.next  = start +                    end +                    if next then +                        stop.next  = next +                        next.prev  = stop +                    end +                    if front then +                        head = start +                    end                  end              end          end @@ -176,7 +195,7 @@ function nodes.handlers.nodepass(head)  end  function nodes.handlers.basepass(head) -    if not basepass then +    if basepass then          head = ligaturing(head)          head = kerning(head)      end diff --git a/src/fontloader/misc/fontloader-fonts-inj.lua b/src/fontloader/misc/fontloader-fonts-inj.lua index 332e920..36781f7 100644 --- a/src/fontloader/misc/fontloader-fonts-inj.lua +++ b/src/fontloader/misc/fontloader-fonts-inj.lua @@ -8,7 +8,16 @@ if not modules then modules = { } end modules ['font-inj'] = {  -- 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! +-- 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 @@ -80,8 +89,7 @@ function injections.resetcounts()      keepregisteredcounts  = false  end --- We need to make sure that a possible metatable will not kick in --- unexpectedly. +-- We need to make sure that a possible metatable will not kick in unexpectedly.  function injections.reset(n)      local p = rawget(properties,n) @@ -146,7 +154,8 @@ 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, wn = tfmstart.width, tfmnext.width +    local ws = tfmstart.width +    local wn = tfmnext.width      nofregisteredcursives = nofregisteredcursives + 1      if rlmode < 0 then          dx = -(dx + wn) @@ -195,7 +204,10 @@ function injections.setcursive(start,nxt,factor,rlmode,exit,entry,tfmstart,tfmne  end  function injections.setpair(current,factor,rlmode,r2lflag,spec,injection) -- r2lflag & tfmchr not used -    local x, y, w, h = factor*spec[1], factor*spec[2], factor*spec[3], factor*spec[4] +    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 @@ -205,9 +217,12 @@ function injections.setpair(current,factor,rlmode,r2lflag,spec,injection) -- r2l              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,"injections") +                local i = rawget(p,injection)                  if i then                      if leftkern ~= 0 then                          i.leftkern  = (i.leftkern  or 0) + leftkern @@ -219,19 +234,19 @@ function injections.setpair(current,factor,rlmode,r2lflag,spec,injection) -- r2l                          i.yoffset = (i.yoffset or 0) + yoffset                      end                  elseif leftkern ~= 0 or rightkern ~= 0 then -                    p.injections = { +                    p[injection] = {                          leftkern  = leftkern,                          rightkern = rightkern,                          yoffset   = yoffset,                      }                  else -                    p.injections = { +                    p[injection] = {                          yoffset = yoffset,                      }                  end              elseif leftkern ~= 0 or rightkern ~= 0 then                  properties[current] = { -                    injections = { +                    [injection] = {                          leftkern  = leftkern,                          rightkern = rightkern,                          yoffset   = yoffset, @@ -239,7 +254,7 @@ function injections.setpair(current,factor,rlmode,r2lflag,spec,injection) -- r2l                  }              else                  properties[current] = { -                    injections = { +                    [injection] = {                          yoffset = yoffset,                      },                  } @@ -250,10 +265,9 @@ function injections.setpair(current,factor,rlmode,r2lflag,spec,injection) -- r2l      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) +-- 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 @@ -285,7 +299,7 @@ function injections.setkern(current,factor,rlmode,x,injection)      end  end -function injections.setmark(start,base,factor,rlmode,ba,ma,tfmbase) -- ba=baseanchor, ma=markanchor +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 @@ -293,14 +307,20 @@ function injections.setmark(start,base,factor,rlmode,ba,ma,tfmbase) -- ba=basean          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 -            i.markx        = dx -            i.marky        = dy -            i.markdir      = rlmode or 0 -            i.markbase     = nofregisteredmarks -            i.markbasenode = base +            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, @@ -308,6 +328,7 @@ function injections.setmark(start,base,factor,rlmode,ba,ma,tfmbase) -- ba=basean                  markdir      = rlmode or 0,                  markbase     = nofregisteredmarks,                  markbasenode = base, +                markmark     = mkmk,              }          end      else @@ -318,6 +339,7 @@ function injections.setmark(start,base,factor,rlmode,ba,ma,tfmbase) -- ba=basean                  markdir      = rlmode or 0,                  markbase     = nofregisteredmarks,                  markbasenode = base, +                markmark     = mkmk,              },          }      end @@ -430,30 +452,36 @@ local function show_result(head)      end  end --- we could also check for marks here but maybe not all are registered (needs checking) - -local function collect_glyphs_1(head) -    local glyphs, nofglyphs = { }, 0 -    local marks, nofmarks = { }, 0 +local function collect_glyphs(head,offsets) +    local glyphs, glyphi, nofglyphs = { }, { }, 0 +    local marks, marki, nofmarks = { }, { }, 0      local nf, tm = nil, nil -    for n in traverse_id(glyph_code,head) do -- only needed for relevant fonts -        if getsubtype(n) < 256 then -            local f = getfont(n) -            if f ~= nf then -                nf = f -                tm = fontdata[nf].resources.marks -- other hash in ctx -            end -            if tm and tm[getchar(n)] then -                nofmarks = nofmarks + 1 -                marks[nofmarks] = n -            else -                nofglyphs = nofglyphs + 1 -                glyphs[nofglyphs] = n +    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,"injections") +                local i = rawget(p,what)                  if i then                      local yoffset = i.yoffset                      if yoffset and yoffset ~= 0 then @@ -463,38 +491,50 @@ local function collect_glyphs_1(head)              end          end      end -    return glyphs, nofglyphs, marks, nofmarks -end -local function collect_glyphs_2(head) -    local glyphs, nofglyphs = { }, 0 -    local marks, nofmarks = { }, 0 -    local nf, tm = nil, nil -    for n in traverse_id(glyph_code,head) do -        if getsubtype(n) < 256 then -            local f = getfont(n) -            if f ~= nf then -                nf = f -                tm = fontdata[nf].resources.marks -- other hash in ctx -            end -            if tm and tm[getchar(n)] then -                nofmarks = nofmarks + 1 -                marks[nofmarks] = n -            else -                nofglyphs = nofglyphs + 1 -                glyphs[nofglyphs] = n -            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, nofglyphs, marks, nofmarks + +    return glyphs, glyphi, nofglyphs, marks, marki, nofmarks  end -local function inject_marks(marks,nofmarks) +local function inject_marks(marks,marki,nofmarks)      for i=1,nofmarks do -        local n = marks[i] +        local n  = marks[i]          local pn = rawget(properties,n)          if pn then -            pn = rawget(pn,"injections") +            local ni = marki[i] +            local pn = rawget(pn,ni)              if pn then                  local p = pn.markbasenode                  if p then @@ -503,7 +543,7 @@ local function inject_marks(marks,nofmarks)                      local rightkern = nil                      local pp = rawget(properties,p)                      if pp then -                        pp = rawget(pp,"injections") +                        pp = rawget(pp,ni)                          if pp then                              rightkern = pp.rightkern                          end @@ -516,13 +556,22 @@ local function inject_marks(marks,nofmarks)                          else                              -- kern(x) glyph(p) kern(w-x) mark(n)                           -- ox = px - getfield(p,"width") + pn.markx - pp.leftkern -                            local leftkern = pp.leftkern -                            if leftkern then -                                ox = px - pn.markx +                            -- +							-- 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 - leftkern +                                ox = px - pn.markx                              end --- report_injections("l2r case 1: %p",ox)                          end                      else                          -- we need to deal with fonts that have marks with width @@ -548,12 +597,13 @@ local function inject_marks(marks,nofmarks)                      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 = 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) @@ -564,14 +614,14 @@ local function inject_marks(marks,nofmarks)      end  end -local function inject_cursives(glyphs,nofglyphs) +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 n  = glyphs[i]          local pn = rawget(properties,n)          if pn then -            pn = rawget(pn,"injections") +            pn = rawget(pn,glyphi[i])          end          if pn then              local cursivex = pn.cursivex @@ -630,7 +680,7 @@ local function inject_cursives(glyphs,nofglyphs)       -- if maxc > 0 and not cursiveanchor then       --     local ny = getfield(n,"yoffset")       --     for i=maxc,minc,-1 do -     --         local ti = glyphs[i] +     --         local ti = glyphs[i][1]       --         ny = ny + properties[ti].cursivedy       --         setfield(ti,"yoffset",ny) -- why not add ?       --     end @@ -647,23 +697,67 @@ local function inject_cursives(glyphs,nofglyphs)      end  end -local function inject_kerns(head,list,length) - -- todo: pre/post/replace +-- 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  = list[i] +        local n  = glist[i]          local pn = rawget(properties,n)          if pn then -            local i = rawget(pn,"injections") -            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 +			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 @@ -673,23 +767,18 @@ local function inject_everything(head,where)      if trace_injections then          trace(head,"everything")      end -    local glyphs, nofglyphs, marks, nofmarks -    if nofregisteredpairs > 0 then -        glyphs, nofglyphs, marks, nofmarks = collect_glyphs_1(head) -    else -        glyphs, nofglyphs, marks, nofmarks = collect_glyphs_2(head) -    end +    local glyphs, glyphi, nofglyphs, marks, marki, nofmarks = collect_glyphs(head,nofregisteredpairs > 0)      if nofglyphs > 0 then          if nofregisteredcursives > 0 then -            inject_cursives(glyphs,nofglyphs) +            inject_cursives(glyphs,glyphi,nofglyphs)          end          if nofregisteredmarks > 0 then -- and nofmarks > 0 -            inject_marks(marks,nofmarks) +            inject_marks(marks,marki,nofmarks)          end -        inject_kerns(head,glyphs,nofglyphs) +        inject_kerns(head,glyphs,glyphi,nofglyphs)      end      if nofmarks > 0 then -        inject_kerns(head,marks,nofmarks) +        inject_kerns(head,marks,marki,nofmarks)  	end      if keepregisteredcounts then          keepregisteredcounts  = false @@ -702,13 +791,21 @@ local function inject_everything(head,where)      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 +    local p = nil -- disc node when non-nil      while n do          local id = getid(n)          if id == glyph_code then @@ -724,6 +821,7 @@ local function inject_kerns_only(head,where)                                  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 @@ -735,6 +833,7 @@ local function inject_kerns_only(head,where)                                  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 @@ -747,6 +846,7 @@ local function inject_kerns_only(head,where)                              end                          end                      else +                        -- this is the most common case                          local i = rawget(pn,"injections")                          if i then                              local leftkern = i.leftkern @@ -756,8 +856,6 @@ local function inject_kerns_only(head,where)                          end                      end                  end -            else -                break              end              p = nil          elseif id == disc_code then @@ -812,7 +910,7 @@ local function inject_kerns_only(head,where)                  local h = d                  for n in traverse_id(glyph_code,d) do                      if getsubtype(n) < 256 then -                        local pn = rawget(properties,n) -- why can it be empty { } +                        local pn = rawget(properties,n)                          if pn then                              local i = rawget(pn,"replaceinjections")                              if i then @@ -850,9 +948,8 @@ local function inject_pairs_only(head,where)      if trace_injections then          trace(head,"pairs")      end -    --      local n = head -    local p = nil +    local p = nil -- disc node when non-nil      while n do          local id = getid(n)          if id == glyph_code then @@ -868,6 +965,7 @@ local function inject_pairs_only(head,where)                                  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 @@ -884,6 +982,7 @@ local function inject_pairs_only(head,where)                                  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 @@ -909,24 +1008,22 @@ local function inject_pairs_only(head,where)                          -- this is the most common case                          local i = rawget(pn,"injections")                          if i then -                            local yoffset = i.yoffset -                            if yoffset and yoffset ~= 0 then -                                setfield(n,"yoffset",yoffset) -                            end                              local leftkern = i.leftkern                              if leftkern and leftkern ~= 0 then -                                insert_node_before(head,n,newkern(leftkern)) +                                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 -            else -                break              end              p = nil          elseif id == disc_code then @@ -935,16 +1032,12 @@ local function inject_pairs_only(head,where)                  local h = d                  for n in traverse_id(glyph_code,d) do                      if getsubtype(n) < 256 then -                        local p = rawget(properties,n) -                        if p then -                            local i = rawget(p,"preinjections") +                        local pn = rawget(properties,n) +                        if pn then +                            local i = rawget(pn,"preinjections")                              if i then -                                local yoffset = i.yoffset -                                if yoffset and yoffset ~= 0 then -                                    setfield(n,"yoffset",yoffset) -                                end                                  local leftkern = i.leftkern -                                if leftkern ~= 0 then +                                if leftkern and leftkern ~= 0 then                                      h = insert_node_before(h,n,newkern(leftkern))                                  end                                  local rightkern = i.rightkern @@ -952,6 +1045,10 @@ local function inject_pairs_only(head,where)                                      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 @@ -967,14 +1064,10 @@ local function inject_pairs_only(head,where)                  local h = d                  for n in traverse_id(glyph_code,d) do                      if getsubtype(n) < 256 then -                        local p = rawget(properties,n) -                        if p then -                            local i = rawget(p,"postinjections") +                        local pn = rawget(properties,n) +                        if pn then +                            local i = rawget(pn,"postinjections")                              if i then -                                local yoffset = i.yoffset -                                if yoffset and yoffset ~= 0 then -                                    setfield(n,"yoffset",yoffset) -                                end                                  local leftkern = i.leftkern                                  if leftkern and leftkern ~= 0 then                                      h = insert_node_before(h,n,newkern(leftkern)) @@ -984,6 +1077,10 @@ local function inject_pairs_only(head,where)                                      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 @@ -999,14 +1096,10 @@ local function inject_pairs_only(head,where)                  local h = d                  for n in traverse_id(glyph_code,d) do                      if getsubtype(n) < 256 then -                        local p = rawget(properties,n) -                        if p then -                            local i = rawget(p,"replaceinjections") +                        local pn = rawget(properties,n) +                        if pn then +                            local i = rawget(pn,"replaceinjections")                              if i then -                                local yoffset = i.yoffset -                                if yoffset and yoffset ~= 0 then -                                    setfield(n,"yoffset",yoffset) -                                end                                  local leftkern = i.leftkern                                  if leftkern and leftkern ~= 0 then                                      h = insert_node_before(h,n,newkern(leftkern)) @@ -1016,6 +1109,10 @@ local function inject_pairs_only(head,where)                                      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 @@ -1042,7 +1139,7 @@ local function inject_pairs_only(head,where)      return tonode(head), true  end -function injections.handler(head,where) -- optimize for n=1 ? +function injections.handler(head,where)      if nofregisteredmarks > 0 or nofregisteredcursives > 0 then          return inject_everything(head,where)      elseif nofregisteredpairs > 0 then diff --git a/src/fontloader/misc/fontloader-fonts-otn.lua b/src/fontloader/misc/fontloader-fonts-otn.lua index dd3aa61..1b99c56 100644 --- a/src/fontloader/misc/fontloader-fonts-otn.lua +++ b/src/fontloader/misc/fontloader-fonts-otn.lua @@ -6,12 +6,16 @@ if not modules then modules = { } end modules ['font-otn'] = {      license   = "see context related readme files",  } --- todo: looks like we have a leak somewhere (probably in ligatures) --- todo: copy attributes to disc -  -- 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) @@ -40,7 +44,18 @@ if not modules then modules = { } end modules ['font-otn'] = {  -- 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)  -- --- maybe redo the lot some way (more context specific) +-- 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 @@ -65,9 +80,12 @@ 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>Because there are different interpretations possible, I will extend the code -with more (configureable) variants. I can also add hooks for users so that they can -write their own extensions.</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 @@ -94,12 +112,12 @@ 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             chainmore              comment +-- action                    handler     chainproc  -- --- gsub_single               ok          ok                    ok --- gsub_multiple             ok          ok                    not implemented yet --- gsub_alternate            ok          ok                    not implemented yet --- gsub_ligature             ok          ok                    ok +-- 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          -- @@ -123,7 +141,6 @@ results in different tables.</p>  -- 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 --- remark: we need to check what to do with discretionaries  -- 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 @@ -131,16 +148,14 @@ results in different tables.</p>  -- Todo: make plugin feature that operates on char/glyphnode arrays -local concat, insert, remove = table.concat, table.insert, table.remove -local gmatch, gsub, find, match, lower, strip = string.gmatch, string.gsub, string.find, string.match, string.lower, string.strip -local type, next, tonumber, tostring = type, next, tonumber, tostring -local lpegmatch = lpeg.match +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 registertracker   = trackers.register +local registerdirective = directives.register  local fonts = fonts  local otf   = fonts.handlers.otf @@ -162,6 +177,16 @@ local trace_steps        = false  registertracker("otf.steps",        function(v  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") @@ -230,11 +255,7 @@ local math_code          = nodecodes.math  local dir_code           = whatcodes.dir  local localpar_code      = whatcodes.localpar -  local discretionary_code = disccodes.discretionary -local regular_code       = disccodes.regular -local automatic_code     = disccodes.automatic -  local ligature_code      = glyphcodes.ligature  local privateattribute   = attributes.private @@ -286,6 +307,15 @@ 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     = { } +  -- head is always a whatsit so we can safely assume that head is not changed  -- we use this for special testing and documentation @@ -376,8 +406,66 @@ local function copy_glyph(g) -- next and prev are untouched !      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 @@ -416,8 +504,8 @@ end  -- iteration this becomes a KAF-LAM-ALEF with a SHADDA on the second and a FATHA on the  -- third component. -local function getcomponentindex(start) -    if getid(start) ~= glyph_code then +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 @@ -434,16 +522,28 @@ local function getcomponentindex(start)      end  end --- eventually we will do positioning in an other way (needs addional w/h/d fields) +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) @@ -453,15 +553,15 @@ local function toligature(kind,lookupname,head,start,stop,char,markflag,discfoun      resetinjection(base)      setfield(base,"char",char)      setfield(base,"subtype",ligature_code) -    setfield(base,"components",start) -- start can have components +    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,"next",next)      setfield(base,"prev",prev) +    setfield(base,"next",next)      if not discfound then          local deletemarks = markflag ~= "mark"          local components = start @@ -480,7 +580,9 @@ local function toligature(kind,lookupname,head,start,stop,char,markflag,discfoun                  if trace_marks then                      logwarning("%s: keep mark %s, gets index %s",pref(kind,lookupname),gref(char),getligaindex(start))                  end -                head, current = insert_node_after(head,current,copy_node(start)) -- unlikely that mark has components +                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 @@ -501,17 +603,85 @@ local function toligature(kind,lookupname,head,start,stop,char,markflag,discfoun              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 -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)) +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 -    resetinjection(start) -    setfield(start,"char",replacement) -    return head, start, true  end  local function get_alternative_glyph(start,alternatives,value,trace_alternatives) @@ -546,38 +716,15 @@ local function get_alternative_glyph(start,alternatives,value,trace_alternatives      end  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,"next",sn) -                setfield(n,"prev",start) -                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 +-- 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) @@ -605,7 +752,7 @@ function handlers.gsub_multiple(head,start,kind,lookupname,multiple,sequence)  end  function handlers.gsub_ligature(head,start,kind,lookupname,ligature,sequence) -    local s, stop, discfound = getnext(start), nil, false +    local s, stop = getnext(start), nil      local startchar = getchar(start)      if marks[startchar] then          while s do @@ -633,24 +780,30 @@ function handlers.gsub_ligature(head,start,kind,lookupname,ligature,sequence)                  else                      head, start = markstoligature(kind,lookupname,head,start,stop,lig)                  end -                return head, start, true +                return head, start, true, false              else                  -- ok, goto next lookup              end          end      else -        local skipmark = sequence.flags[1] +        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 -                if getfont(s) == currentfont then +            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 -                        local lg = ligature[char] +                    else -- ligature is a tree +                        local lg = ligature[char] -- can there be multiple in a row? maybe in a bad font                          if lg then -                            stop = s +                            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 @@ -661,13 +814,13 @@ function handlers.gsub_ligature(head,start,kind,lookupname,ligature,sequence)                      break                  end              elseif id == disc_code then -                discfound = true +                lastdisc = s                  s = getnext(s)              else                  break              end          end -        local lig = ligature.ligature +        local lig = ligature.ligature -- can't we get rid of this .ligature?          if lig then              if stop then                  if trace_ligatures then @@ -685,14 +838,88 @@ function handlers.gsub_ligature(head,start,kind,lookupname,ligature,sequence)                      logprocess("%s: replacing %s by (no real) ligature %s case 3",pref(kind,lookupname),gref(startchar),gref(lig))                  end              end -            return head, start, true +            return head, start, true, discfound          else -            -- weird but happens +            -- 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> @@ -855,7 +1082,7 @@ function handlers.gpos_mark2mark(head,start,kind,lookupname,markanchors,sequence                              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]) +                                    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) @@ -938,85 +1165,11 @@ function handlers.gpos_cursive(head,start,kind,lookupname,exitanchors,sequence)      end  end -function handlers.gpos_single(head,start,kind,lookupname,kerns,sequence) -    local startchar = getchar(start) -    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)",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) -    -- 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, done = start, 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 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)",pref(kind,lookupname),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)",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) -                    if trace_kerns then -                        logprocess("%s: inserting kern %s between %s and %s",pref(kind,lookupname),k,gref(getchar(prev)),gref(nextchar)) -                    end -                    done = true -                end -                break -            end -        end -        return head, start, done -    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 chainmores = { }  local chainprocs = { }  local function logprocess(...) @@ -1045,11 +1198,6 @@ function chainprocs.chainsub(head,start,stop,kind,chainname,currentcontext,looku      return head, start, false  end -function chainmores.chainsub(head,start,stop,kind,chainname,currentcontext,lookuphash,lookuplist,chainlookupname,n) -    logprocess("%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. @@ -1116,8 +1264,7 @@ as less as needed but that would also make the code even more messy.</p>  -- end  --[[ldx-- -<p>Here we replace start by a single variant, First we delete the rest of the -match.</p> +<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) @@ -1125,7 +1272,7 @@ function chainprocs.gsub_single(head,start,stop,kind,chainname,currentcontext,lo      local current = start      local subtables = currentlookup.subtables      if #subtables > 1 then -        logwarning("todo: check if we need to loop over the replacements: %s",concat(subtables," ")) +        logwarning("todo: check if we need to loop over the replacements: % t",subtables)      end      while current do          if getid(current) == glyph_code then @@ -1160,11 +1307,8 @@ function chainprocs.gsub_single(head,start,stop,kind,chainname,currentcontext,lo      return head, start, false  end -chainmores.gsub_single = chainprocs.gsub_single -  --[[ldx-- -<p>Here we replace start by a sequence of new glyphs. First we delete the rest of -the match.</p> +<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) @@ -1193,8 +1337,6 @@ function chainprocs.gsub_multiple(head,start,stop,kind,chainname,currentcontext,      return head, start, false  end -chainmores.gsub_multiple = chainprocs.gsub_multiple -  --[[ldx--  <p>Here we replace start by new glyph. First we delete the rest of the match.</p>  --ldx]]-- @@ -1249,8 +1391,6 @@ function chainprocs.gsub_alternate(head,start,stop,kind,chainname,currentcontext      return head, start, false  end -chainmores.gsub_alternate = chainprocs.gsub_alternate -  --[[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 @@ -1276,13 +1416,19 @@ function chainprocs.gsub_ligature(head,start,stop,kind,chainname,currentcontext,              local s = getnext(start)              local discfound = false              local last = stop -            local nofreplacements = 0 +            local nofreplacements = 1              local skipmark = currentlookup.flags[1]              while s do                  local id = getid(s)                  if id == disc_code then -                    s = getnext(s) -                    discfound = true +                    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 @@ -1315,7 +1461,7 @@ function chainprocs.gsub_ligature(head,start,stop,kind,chainname,currentcontext,                      end                  end                  head, start = toligature(kind,lookupname,head,start,stop,l2,currentlookup.flags[1],discfound) -                return head, start, true, nofreplacements +                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)) @@ -1325,10 +1471,96 @@ function chainprocs.gsub_ligature(head,start,stop,kind,chainname,currentcontext,              end          end      end -    return head, start, false, 0 +    return head, start, false, 0, false  end -chainmores.gsub_ligature = chainprocs.gsub_ligature +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) @@ -1497,7 +1729,7 @@ function chainprocs.gpos_mark2mark(head,start,stop,kind,chainname,currentcontext                              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]) +                                    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) @@ -1588,133 +1820,346 @@ function chainprocs.gpos_cursive(head,start,stop,kind,chainname,currentcontext,l      return head, start, 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) +-- 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 -    return head, start, false -end -chainmores.gpos_single = chainprocs.gpos_single -- okay? +    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 --- when machines become faster i will make a shared function +    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 -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 +    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 -                        break +                        replace = getnext(replace)                      end +                elseif notmatchpost[current] ~= notmatchreplace[current] then +                    head, current = flattendisk(head,current)                  end -                return head, start, done +            end +            current = getprev(current) +            if t and current == checkdisk then +                current = getprev(sweepnode)              end          end      end -    return head, start, false -end -chainmores.gpos_pair = chainprocs.gpos_pair -- okay? +    local ok = false +    if lookaheaddisc then --- what pointer to return, spec says stop --- to be discussed ... is bidi changer a space? --- elseif char == zwnj and sequence[n][32] then -- brrr +        local cf            = start +        local cl            = getprev(lookaheaddisc) +        local cprev         = getprev(start) +        local insertedmarks = 0 --- 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 +        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) -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 -local quit_on_no_replacement = true +        head, start, ok = chainproc(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence) -directives.register("otf.chain.quitonnoreplacement",function(value) -- maybe per font -    quit_on_no_replacement = value -end) +    end + +    return head, start, ok +end  local function normal_handle_contextchain(head,start,kind,chainname,contexts,sequence,lookuphash) -    --  local rule, lookuptype, sequence, f, l, lookups = ck[1], ck[2] ,ck[3], ck[4], ck[5], ck[6] +    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 someskip     = skipmark or skipligature or skipbase -- could be stored in flags for a fast test (hm, flags could be false !) -    local markclass    = sequence.markclass                   -- todo, first we need a proper test +    local markclass    = sequence.markclass      local skipped      = false -    for k=1,#contexts do + +    for k=1,#contexts do -- i've only seen ccmp having > 1 (e.g. dejavu)          local match   = true          local current = start          local last    = start @@ -1728,7 +2173,8 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq          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, l = ck[4], ck[5] +            local f = ck[4] +            local l = ck[5]              -- current match              if f == 1 and f == l then -- current only                  -- already a hit @@ -1738,9 +2184,14 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq                  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 @@ -1748,7 +2199,7 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq                                      local char = getchar(last)                                      local ccd = descriptions[char]                                      if ccd then -                                        local class = ccd.class +                                        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 @@ -1761,18 +2212,77 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq                                              end                                              n = n + 1                                          else -                                            match = false +                                            if discfound then +                                                notmatchreplace[discfound] = true +                                                match = not notmatchpre[discfound] +                                            else +                                                match = false +                                            end                                              break                                          end                                      else -                                        match = false +                                        if discfound then +                                            notmatchreplace[discfound] = true +                                            match = not notmatchpre[discfound] +                                        else +                                            match = false +                                        end                                          break                                      end                                  else -                                    match = false +                                    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 @@ -1789,50 +2299,137 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq              if match and f > 1 then                  local prev = getprev(start)                  if prev then -                    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) +                    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 -                                        elseif seq[n][char] then -                                            n = n -1                                          else -                                            match = false +                                            if discfound then +                                                notmatchreplace[discfound] = true +                                                match = not notmatchpost[discfound] +                                            else +                                                match = false +                                            end                                              break                                          end                                      else -                                        match = false +                                        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 -                            elseif id == disc_code then -                                -- skip 'm -                            elseif seq[n][32] then -                                n = n -1 +                                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 -                            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 +                    else +                        match = false                      end                  else                      match = false @@ -1841,7 +2438,14 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq              -- 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 @@ -1861,19 +2465,81 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq                                          elseif seq[n][char] then                                              n = n + 1                                          else -                                            match = false +                                            if discfound then +                                                notmatchreplace[discfound] = true +                                                match = not notmatchpre[discfound] +                                            else +                                                match = false +                                            end                                              break                                          end                                      else -                                        match = false +                                        if discfound then +                                            notmatchreplace[discfound] = true +                                            match = not notmatchpre[discfound] +                                        else +                                            match = false +                                        end                                          break                                      end                                  else -                                    match = false +                                    if discfound then +                                        notmatchreplace[discfound] = true +                                        match = not notmatchpre[discfound] +                                    else +                                        match = false +                                    end                                      break                                  end                              elseif id == disc_code then -                                -- skip 'm +                                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 @@ -1894,7 +2560,8 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq              end          end          if match then -            -- ck == currentcontext +            -- 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) @@ -1914,10 +2581,14 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq                      local chainlookupname = chainlookups[1]                      local chainlookup = lookuptable[chainlookupname]                      if chainlookup then -                        local cp = chainprocs[chainlookup.type] -                        if cp then +                        local chainproc = chainprocs[chainlookup.type] +                        if chainproc then                              local ok -                            head, start, ok = cp(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence) +                            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 @@ -1929,13 +2600,13 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq                      end                   else                      local i = 1 -                    while true do +                    while start and true do                          if skipped then -                            while true do +                            while true do -- todo: use properties                                  local char = getchar(start)                                  local ccd = descriptions[char]                                  if ccd then -                                    local class = ccd.class +                                    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 @@ -1946,36 +2617,51 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq                                  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 -                            -- okay, n matches, < n replacements +                            -- we just advance                              i = i + 1                          else -                            local cp = chainmores[chainlookup.type] -                            if not cp then +                            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 -                                head, start, ok, n = cp(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,i,sequence) +                                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 -                                    -- skip next one(s) if ligature -                                    i = i + (n or 1) -                                else -                                    i = i + 1 +                                    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 then +                        if i > nofchainlookups or not start then                              break                          elseif start then                              start = getnext(start) -                        else -                            -- weird                          end                      end                  end @@ -1990,8 +2676,16 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq                      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 @@ -2076,13 +2770,13 @@ local function initialize(sequence,script,language,enabled)          local order = sequence.order          if order then              for i=1,#order do -- -                local kind = order[i] -- +                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.chain or 0, kind, sequence } +                        return { valid, autofeatures[kind] or false, sequence, kind }                      end                  end              end @@ -2126,32 +2820,216 @@ function otf.dataset(tfmdata,font) -- generic variant, overloaded in context      return rl  end --- elseif id == glue_code then ---     if p[5] then -- chain ---         local pc = pp[32] ---         if pc then ---             start, ok = start, false -- p[1](start,kind,p[2],pc,p[3],p[4]) ---             if ok then ---                 done = true ---             end ---             if start then start = getnext(start) end ---         else ---             start = getnext(start) ---         end ---     else ---         start = getnext(start) ---     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 --- there will be a new direction parser (pre-parsed etc) +-- the if new test might be dangerous as luatex will check / set some tail stuff +-- in a temp node --- less bytecode: 290 -> 254 --- --- attr = attr or false --- --- local a = getattr(start,0) --- if (a == attr and (not attribute or getprop(start,a_state) == attribute)) or (not attribute or getprop(start,a_state) == attribute) then ---     -- the action --- end +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) @@ -2180,6 +3058,7 @@ local function featuresprocessor(head,font,attr)      currentfont     = font      rlmode          = 0 +    sweephead       = { }      local sequences = resources.sequences      local done      = false @@ -2195,23 +3074,27 @@ local function featuresprocessor(head,font,attr)      -- Keeping track of the headnode is needed for devanagari (I generalized it a bit      -- so that multiple cases are also covered.) -    -- todo: retain prev +    -- 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 sequence  = dataset[5] -- sequences[s] -- also dataset[5] -        local rlparmode = 0 -        local topstack  = 0 -        local success   = false -        local attribute = dataset[2] -        local chain     = dataset[3] -- sequence.chain or 0 -        local typ       = sequence.type -        local subtables = sequence.subtables -        if chain < 0 then +        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 -            local handler = handlers[typ]              -- 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 @@ -2225,13 +3108,15 @@ local function featuresprocessor(head,font,attr)                              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[getchar(start)] +                                    local lookupmatch = lookupcache[char]                                      if lookupmatch then -                                        head, start, success = handler(head,start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,i) +                                        -- todo: disc? +                                        head, start, success = handler(head,start,kind,lookupname,lookupmatch,sequence,lookuphash,i)                                          if success then                                              break                                          end @@ -2252,24 +3137,30 @@ local function featuresprocessor(head,font,attr)                  end              end          else -            local handler = handlers[typ]              local ns = #subtables              local start = head -- local ?              rlmode = 0 -- to be checked ?              if ns == 1 then -- happens often -                local lookupname = subtables[1] +                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 subrun(start) -                        -- mostly for gsub, gpos would demand a more clever approach -                        local head = start -                        local done = false +                    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 and getfont(start) == font and getsubtype(start) < 256 then +                            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) @@ -2281,7 +3172,7 @@ local function featuresprocessor(head,font,attr)                                      if lookupmatch then                                          -- sequence kan weg                                          local ok -                                        head, start, ok = handler(head,start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,1) +                                        head, start, ok = handler(head,start,kind,lookupname,lookupmatch,sequence,lookuphash,1)                                          if ok then                                              done = true                                          end @@ -2291,48 +3182,106 @@ local function featuresprocessor(head,font,attr)                                      start = getnext(start)                                  end                              else -                                start = getnext(start) +                                return head, false                              end                          end                          if done then -                            success = true -                            return head +                            success = true -- needed in this subrun?                          end +                        return head, done                      end -                    local function kerndisc(disc) -- we can assume that prev and next are glyphs -                        local prev = getprev(disc) -                        local next = getnext(disc) -                        if prev and next then -                            setfield(prev,"next",next) -                         -- setfield(next,"prev",prev) -                            local a = getattr(prev,0) -                            if a then -                                a = (a == attr) and (not attribute or getprop(prev,a_state) == attribute) +                    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 -                                a = not attribute or getprop(prev,a_state) == attribute +                                break                              end -                            if a then -                                local lookupmatch = lookupcache[getchar(prev)] -                                if lookupmatch then -                                    -- sequence kan weg -                                    local h, d, ok = handler(head,prev,dataset[4],lookupname,lookupmatch,sequence,lookuphash,1) -                                    if ok then -                                        done = true -                                        success = true +                        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 -                            setfield(prev,"next",disc) -                         -- setfield(next,"prev",disc)                          end -                        return next                      end                      while start do                          local id = getid(start)                          if id == glyph_code then -                            if getfont(start) == font and getsubtype(start) < 256 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) @@ -2340,59 +3289,52 @@ local function featuresprocessor(head,font,attr)                                      a = not attribute or getprop(start,a_state) == attribute                                  end                                  if a then -                                    local lookupmatch = lookupcache[getchar(start)] +                                    local char        = getchar(start) +                                    local lookupmatch = lookupcache[char]                                      if lookupmatch then                                          -- sequence kan weg                                          local ok -                                        head, start, ok = handler(head,start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,1) +                                        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) +                                   start = getnext(start)                                  end                              else                                  start = getnext(start)                              end                          elseif id == disc_code then -                            -- mostly for gsub -                            if getsubtype(start) == discretionary_code then -                                local pre = getfield(start,"pre") -                                if pre then -                                    local new = subrun(pre) -                                    if new then setfield(start,"pre",new) end -                                end -                                local post = getfield(start,"post") -                                if post then -                                    local new = subrun(post) -                                    if new then setfield(start,"post",new) end -                                end -                                local replace = getfield(start,"replace") -                                if replace then -                                    local new = subrun(replace) -                                    if new then setfield(start,"replace",new) end -                                end -elseif typ == "gpos_single" or typ == "gpos_pair" then -    kerndisc(start) +                            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 -                            start = getnext(start)                          elseif id == whatsit_code then -- will be function                              local subtype = getsubtype(start)                              if subtype == dir_code then                                  local dir = getfield(start,"dir") -                                if     dir == "+TRT" or dir == "+TLT" then +                                if dir == "+TLT" then                                      topstack = topstack + 1                                      dirstack[topstack] = dir -                                elseif dir == "-TRT" or dir == "-TLT" then -                                    topstack = topstack - 1 -                                end -                                local newdir = dirstack[topstack] -                                if newdir == "+TRT" then -                                    rlmode = -1 -                                elseif newdir == "+TLT" then                                      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 @@ -2422,15 +3364,23 @@ elseif typ == "gpos_single" or typ == "gpos_pair" then                          end                      end                  end +              else -                local function subrun(start) -                    -- mostly for gsub, gpos would demand a more clever approach -                    local head = start -                    local done = false +                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 and getfont(start) == font and getsubtype(start) < 256 then +                        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) @@ -2438,15 +3388,16 @@ elseif typ == "gpos_single" or typ == "gpos_pair" then                                  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[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,dataset[4],lookupname,lookupmatch,sequence,lookuphash,i) +                                            head, start, ok = handler(head,start,kind,lookupname,lookupmatch,sequence,lookuphash,i)                                              if ok then                                                  done = true                                                  break @@ -2464,50 +3415,127 @@ elseif typ == "gpos_single" or typ == "gpos_pair" then                                  start = getnext(start)                              end                          else -                            start = getnext(start) +                            return head, false                          end                      end                      if done then                          success = true -                        return head                      end +                    return head, done                  end -                local function kerndisc(disc) -- we can assume that prev and next are glyphs -                    local prev = getprev(disc) -                    local next = getnext(disc) -                    if prev and next then -                        setfield(prev,"next",next) -                     -- setfield(next,"prev",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 +                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 -                        if a then -                            for i=1,ns do -                                local lookupname = subtables[i] -                                local lookupcache = lookuphash[lookupname] -                                if lookupcache then -                                    local lookupmatch = lookupcache[getchar(prev)] -                                    if lookupmatch then -                                        -- we could move all code inline but that makes things even more unreadable -                                        local h, d, ok = handler(head,prev,dataset[4],lookupname,lookupmatch,sequence,lookuphash,i) -                                        if ok then -                                            done = true -                                            break +                    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 -                                else -                                    report_missing_cache(typ,lookupname)                                  end                              end +                            start = getnext(start) +                        else +                            break                          end -                        setfield(prev,"next",disc) -                     -- setfield(next,"prev",disc)                      end -                    return next                  end                  while start do @@ -2522,21 +3550,26 @@ elseif typ == "gpos_single" or typ == "gpos_pair" then                              end                              if a then                                  for i=1,ns do -                                    local lookupname = subtables[i] +                                    local lookupname  = subtables[i]                                      local lookupcache = lookuphash[lookupname]                                      if lookupcache then -                                        local lookupmatch = lookupcache[getchar(start)] +                                        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,dataset[4],lookupname,lookupmatch,sequence,lookuphash,i) +                                            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) @@ -2550,42 +3583,30 @@ elseif typ == "gpos_single" or typ == "gpos_pair" then                              start = getnext(start)                          end                      elseif id == disc_code then -                        -- mostly for gsub -                        if getsubtype(start) == discretionary_code then -                            local pre = getfield(start,"pre") -                            if pre then -                                local new = subrun(pre) -                                if new then setfield(start,"pre",new) end -                            end -                            local post = getfield(start,"post") -                            if post then -                                local new = subrun(post) -                                if new then setfield(start,"post",new) end -                            end -                            local replace = getfield(start,"replace") -                            if replace then -                                local new = subrun(replace) -                                if new then setfield(start,"replace",new) end -                            end -elseif typ == "gpos_single" or typ == "gpos_pair" then -    kerndisc(start) +                        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 -                        start = getnext(start)                      elseif id == whatsit_code then                          local subtype = getsubtype(start)                          if subtype == dir_code then                              local dir = getfield(start,"dir") -                            if     dir == "+TRT" or dir == "+TLT" then +                            if dir == "+TLT" then                                  topstack = topstack + 1                                  dirstack[topstack] = dir -                            elseif dir == "-TRT" or dir == "-TLT" then -                                topstack = topstack - 1 -                            end -                            local newdir = dirstack[topstack] -                            if newdir == "+TRT" then -                                rlmode = -1 -                            elseif newdir == "+TLT" then                                  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 @@ -2629,6 +3650,8 @@ elseif typ == "gpos_single" or typ == "gpos_pair" then      return head, done  end +-- this might move to the loader +  local function generic(lookupdata,lookupname,unicode,lookuphash)      local target = lookuphash[lookupname]      if target then @@ -2638,47 +3661,48 @@ local function generic(lookupdata,lookupname,unicode,lookuphash)      end  end -local action = { +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 = function(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, - -    pair = function(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, - +    ligature     = ligature, +    pair         = pair, +    kern         = pair,  }  local function prepare_lookups(tfmdata) @@ -2691,12 +3715,17 @@ local function prepare_lookups(tfmdata)      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] @@ -2706,7 +3735,7 @@ local function prepare_lookups(tfmdata)              local lookups = description.slookups              if lookups then                  for lookupname, lookupdata in next, lookups do -                    action[lookuptypes[lookupname]](lookupdata,lookupname,unicode,lookuphash) +                    action[lookuptypes[lookupname]](lookupdata,lookupname,unicode,lookuphash,duplicates)                  end              end @@ -2716,7 +3745,7 @@ local function prepare_lookups(tfmdata)                      local lookuptype = lookuptypes[lookupname]                      for l=1,#lookuplist do                          local lookupdata = lookuplist[l] -                        action[lookuptype](lookupdata,lookupname,unicode,lookuphash) +                        action[lookuptype](lookupdata,lookupname,unicode,lookuphash,duplicates)                      end                  end              end @@ -2740,7 +3769,7 @@ local function prepare_lookups(tfmdata)                          for name, anchor in next, anchors do                              local lookups = anchor_to_lookup[name]                              if lookups then -                                for lookup, _ in next, lookups do +                                for lookup in next, lookups do                                      local target = lookuphash[lookup]                                      if target then                                          target[unicode] = anchors @@ -2760,6 +3789,8 @@ local function prepare_lookups(tfmdata)  end +-- so far +  local function split(replacement,original)      local result = { }      for i=1,#replacement do @@ -2835,7 +3866,7 @@ local function prepare_contextchains(tfmdata)                                  -- 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 +                                for unic in next, sequence[start] do                                      local cu = contexts[unic]                                      if not cu then                                          contexts[unic] = t diff --git a/src/fontloader/misc/fontloader-fonts.lua b/src/fontloader/misc/fontloader-fonts.lua index c81e8cd..f18ba35 100644 --- a/src/fontloader/misc/fontloader-fonts.lua +++ b/src/fontloader/misc/fontloader-fonts.lua @@ -27,16 +27,10 @@ if not modules then modules = { } end modules ['luatex-fonts'] = {  -- also add more helper code here, but that depends to what extend metatex (sidetrack of context)  -- evolves into a low level layer (depends on time, as usual). -texio.write_nl("") -texio.write_nl("--------------------------------------------------------------------------------") -texio.write_nl("The font code has been brought in sync with the context version of 2014.12.21 so") -texio.write_nl("if things don't work out as expected the interfacing needs to be checked. When") -texio.write_nl("this works as expected a second upgrade will happen that gives a more complete") -texio.write_nl("support and another sync with the context code (that new code is currently being") -texio.write_nl("tested. The base pass is now integrated in the main pass. The results can differ") -texio.write_nl("from those in context because there we integrate some mechanisms differently.") -texio.write_nl("--------------------------------------------------------------------------------") -texio.write_nl("") +-- The code here is the same as in context version 2015.09.11 but the rendering in context can be +-- different from generic. This can be a side effect of additional callbacks, additional features +-- and interferences between mechanisms between macro packages. We use the rendering in context +-- and luatex-plain as reference for issues.  utf = utf or unicode.utf8 @@ -221,9 +215,9 @@ 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') +        loadmodule('luatex-fonts-inj.lua') -- normally the same as font-inj.lua          loadmodule('luatex-fonts-ota.lua') -        loadmodule('luatex-fonts-otn.lua') +        loadmodule('luatex-fonts-otn.lua') -- normally the same as 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-l-lpeg.lua b/src/fontloader/misc/fontloader-l-lpeg.lua index 55a0d89..5be1246 100644 --- a/src/fontloader/misc/fontloader-l-lpeg.lua +++ b/src/fontloader/misc/fontloader-l-lpeg.lua @@ -82,7 +82,7 @@ local lpegtype, lpegmatch, lpegprint = lpeg.type, lpeg.match, lpeg.print  -- let's start with an inspector:  if setinspector then -    setinspector(function(v) if lpegtype(v) then lpegprint(v) return true end end) +    setinspector("lpeg",function(v) if lpegtype(v) then lpegprint(v) return true end end)  end  -- Beware, we predefine a bunch of patterns here and one reason for doing so diff --git a/src/fontloader/misc/fontloader-l-lua.lua b/src/fontloader/misc/fontloader-l-lua.lua index 1a2a987..cb61829 100644 --- a/src/fontloader/misc/fontloader-l-lua.lua +++ b/src/fontloader/misc/fontloader-l-lua.lua @@ -129,22 +129,36 @@ local print, select, tostring = print, select, tostring  local inspectors = { } -function setinspector(inspector) -- global function -    inspectors[#inspectors+1] = inspector +function setinspector(kind,inspector) -- global function +    inspectors[kind] = inspector  end  function inspect(...) -- global function      for s=1,select("#",...) do          local value = select(s,...) -        local done = false -        for i=1,#inspectors do -            done = inspectors[i](value) -            if done then -                break +        if value == nil then +            print("nil") +        else +            local done  = false +            -- type driven (table) +            local kind      = type(value) +            local inspector = inspectors[kind] +            if inspector then +                done = inspector(value) +                if done then +                    break +                end +            end +            -- whatever driven (token, node, ...) +            for kind, inspector in next, inspectors do +                done = inspector(value) +                if done then +                    break +                end +            end +            if not done then +                print(tostring(value))              end -        end -        if not done then -            print(tostring(value))          end      end  end diff --git a/src/fontloader/misc/fontloader-l-string.lua b/src/fontloader/misc/fontloader-l-string.lua index 70c66f6..e9dc2bb 100644 --- a/src/fontloader/misc/fontloader-l-string.lua +++ b/src/fontloader/misc/fontloader-l-string.lua @@ -192,10 +192,11 @@ string.itself  = function(s) return s end  -- also handy (see utf variant) -local pattern = Ct(C(1)^0) -- string and not utf ! +local pattern_c = Ct( C(1)      ^0) -- string and not utf ! +local pattern_b = Ct((C(1)/byte)^0) -function string.totable(str) -    return lpegmatch(pattern,str) +function string.totable(str,bytes) +    return lpegmatch(bytes and pattern_b or pattern_c,str)  end  -- handy from within tex: diff --git a/src/fontloader/misc/fontloader-l-table.lua b/src/fontloader/misc/fontloader-l-table.lua index b02f210..552097e 100644 --- a/src/fontloader/misc/fontloader-l-table.lua +++ b/src/fontloader/misc/fontloader-l-table.lua @@ -1144,7 +1144,7 @@ function table.print(t,...)  end  if setinspector then -    setinspector(function(v) if type(v) == "table" then serialize(print,v,"table") return true end end) +    setinspector("table",function(v) if type(v) == "table" then serialize(print,v,"table") return true end end)  end  -- -- -- obsolete but we keep them for a while and might comment them later -- -- -- diff --git a/src/fontloader/misc/fontloader-mplib.lua b/src/fontloader/misc/fontloader-mplib.lua index c6628ac..fd6eb97 100644 --- a/src/fontloader/misc/fontloader-mplib.lua +++ b/src/fontloader/misc/fontloader-mplib.lua @@ -22,7 +22,9 @@ if metapost and metapost.version then  else -    local format, concat, abs, match = string.format, table.concat, math.abs, string.match +    local format, match, gsub = string.format, string.match, string.gsub +    local concat = table.concat +    local abs = math.abs      local mplib = require ('mplib')      local kpse  = require ('kpse') @@ -144,10 +146,101 @@ else          metapost.make = metapost.make or function()          end +        local template = [[ +            \pdfoutput=1 +            \pdfpkresolution600 +            \pdfcompresslevel=9 +            %s\relax +            \hsize=100in +            \vsize=\hsize +            \hoffset=-1in +            \voffset=\hoffset +            \topskip=0pt +            \setbox0=\hbox{%s}\relax +            \pageheight=\ht0 +            \pagewidth=\wd0 +            \box0 +            \bye +        ]] + +        metapost.texrunner = "mtxrun --script plain" + +        local texruns = 0   -- per document +        local texhash = { } -- per document + +        function metapost.maketext(mpd,str,what) +            -- inefficient but one can always use metafun .. it's more a test +            -- feature +            local verbatimtex = mpd.verbatimtex +            if not verbatimtex then +                verbatimtex = { } +                mpd.verbatimtex = verbatimtex +            end +            if what == 1 then +                table.insert(verbatimtex,str) +            else +                local texcode = format(template,concat(verbatimtex,"\n"),str) +                local texdone = texhash[texcode] +                local jobname = tex.jobname +                if not texdone then +                    texruns = texruns + 1 +                    texdone = texruns +                    texhash[texcode] = texdone +                    local texname = format("%s-mplib-%s.tmp",jobname,texdone) +                    local logname = format("%s-mplib-%s.log",jobname,texdone) +                    local pdfname = format("%s-mplib-%s.pdf",jobname,texdone) +                    io.savedata(texname,texcode) +                    os.execute(format("%s %s",metapost.texrunner,texname)) +                    os.remove(texname) +                    os.remove(logname) +                end +                return format('"image::%s-mplib-%s.pdf" infont defaultfont',jobname,texdone) +            end +        end + +        local function mpprint(buffer,...) +            for i=1,select("#",...) do +                local value = select(i,...) +                if value ~= nil then +                    local t = type(value) +                    if t == "number" then +                        buffer[#buffer+1] = format("%.16f",value) +                    elseif t == "string" then +                        buffer[#buffer+1] = value +                    elseif t == "table" then +                        buffer[#buffer+1] = "(" .. concat(value,",") .. ")" +                    else -- boolean or whatever +                        buffer[#buffer+1] = tostring(value) +                    end +                end +            end +        end + +        function metapost.runscript(mpd,code) +            local code = loadstring(code) +            if type(code) == "function" then +                local buffer = { } +                function metapost.print(...) +                    mpprint(buffer,...) +                end +                code() +             -- mpd.buffer = buffer -- for tracing +                return concat(buffer,"") +            end +            return "" +        end +          function metapost.load(name) +            local mpd = { +                buffer   = { }, +                verbatim = { } +            }              local mpx = mplib.new {                  ini_version = true, -                find_file = metapost.finder, +                find_file   = metapost.finder, +                make_text   = function(...) return metapost.maketext (mpd,...) end, +                run_script  = function(...) return metapost.runscript(mpd,...) end, +                extensions  = 1,              }              local result              if not mpx then @@ -217,8 +310,8 @@ else          return figure:objects()      end -    function metapost.convert(result, flusher) -        metapost.flush(result, flusher) +    function metapost.convert(result,flusher) +        metapost.flush(result,flusher)          return true -- done      end @@ -239,8 +332,13 @@ else      end      function pdf_textfigure(font,size,text,width,height,depth) -        text = text:gsub(".","\\hbox{%1}") -- kerning happens in metapost -        tex.sprint(format("\\MPLIBtextext{%s}{%s}{%s}{%s}{%s}",font,size,text,0,-( 7200/ 7227)/65536*depth)) +        local how, what = match(text,"^(.-)::(.+)$") +        if how == "image" then +            tex.sprint(format("\\MPLIBpdftext{%s}{%s}",what,depth)) +        else +            text = gsub(text,".","\\hbox{%1}") -- kerning happens in metapost +            tex.sprint(format("\\MPLIBtextext{%s}{%s}{%s}{%s}",font,size,text,depth)) +        end      end      local bend_tolerance = 131/65536 @@ -375,8 +473,10 @@ else                                      pdf_literalcode("Q")                                  else                                      local cs = object.color +                                    local cr = false                                      if cs and #cs > 0 then -                                        pdf_literalcode(metapost.colorconverter(cs)) +                                        cs, cr = metapost.colorconverter(cs) +                                        pdf_literalcode(cs)                                      end                                      local ml = object.miterlimit                                      if ml and ml ~= miterlimit then diff --git a/src/fontloader/misc/fontloader-mplib.tex b/src/fontloader/misc/fontloader-mplib.tex index 09dd179..f9de4b2 100644 --- a/src/fontloader/misc/fontloader-mplib.tex +++ b/src/fontloader/misc/fontloader-mplib.tex @@ -106,15 +106,14 @@  %D Text items have a special handler: -\def\MPLIBtextext#1#2#3#4#5% +\def\MPLIBtextext#1#2#3#4%    {\begingroup     \setbox\mplibscratchbox\hbox       {\font\temp=#1 at #2bp%        \temp        #3}%     \setbox\mplibscratchbox\hbox -     {\hskip#4 bp% -      \raise#5 bp% +     {\raise#4sp%        \box\mplibscratchbox}%     \wd\mplibscratchbox0pt%     \ht\mplibscratchbox0pt% @@ -122,4 +121,20 @@     \box\mplibscratchbox     \endgroup} +\def\MPLIBpdftext#1#2% +  {\ifcsname mplib::#1\endcsname +     % already done, forgotten outside convert group +     \message{<reusing mplib: #1>}% +   \else +     \message{<embedding mplib: #1>}% +     \immediate\pdfximage{#1}% we cannot remove the file as it is included last +     \expandafter\edef\csname mplib::#1\endcsname{\the\pdflastximage}% +   \fi +   \setbox\mplibscratchbox\hbox +     {\raise#2sp\hbox{\pdfrefximage\csname mplib::#1\endcsname}}% +   \wd\mplibscratchbox0pt% +   \ht\mplibscratchbox0pt% +   \dp\mplibscratchbox0pt% +   \box\mplibscratchbox} +  \endinput diff --git a/src/fontloader/misc/fontloader-plain.tex b/src/fontloader/misc/fontloader-plain.tex index c9a9e36..9902c49 100644 --- a/src/fontloader/misc/fontloader-plain.tex +++ b/src/fontloader/misc/fontloader-plain.tex @@ -11,7 +11,26 @@  \directlua {tex.enableprimitives('', tex.extraprimitives())} -\pdfoutput=1 +% We assume that pdf is used. + +\pdfoutput 1 + +% We set the page dimensions because otherwise the backend does weird things +% when we have for instance this on a line of its own: +% +%   \hbox to 100cm {\hss wide indeed\hss} +% +% The page dimension calculation is a fuzzy one as there are some compensations +% for the \hoffset and \voffset and such. I remember long discussions and much +% trial and error in figuring this out during pdftex development times. Where +% a dvi driver will project on a papersize (and thereby clip) the pdf backend +% has to deal with the lack of a page concept on tex by some guessing. Normally +% a macro package will set the dimensions to something reasonable anyway. + +\pagewidth   8.5in +\pageheight 11.0in + +% We load some code at runtime:  \everyjob \expandafter {%      \the\everyjob @@ -20,9 +39,11 @@      \input {luatex-math}%      \input {luatex-languages}%      \input {luatex-mplib}% -  % \input {luatex-gadgets}% +    \input {luatex-gadgets}%  } +% We also patch the version number: +  \edef\fmtversion{\fmtversion+luatex}  \dump diff --git a/src/fontloader/misc/fontloader-test.tex b/src/fontloader/misc/fontloader-test.tex index 6f48e0c..f851aab 100644 --- a/src/fontloader/misc/fontloader-test.tex +++ b/src/fontloader/misc/fontloader-test.tex @@ -1,3 +1,5 @@ +% texformat=luatex-plain +  %D \module  %D   [       file=luatex-test,  %D        version=2009.12.01, @@ -33,12 +35,12 @@  \font\mathtest=cambria(math) {\mathtest 123} -\font\gothic=msgothic(ms-gothic) {\gothic whatever} +% \font\gothic=msgothic(ms-gothic) {\gothic whatever} % no longer in windows 10  \bgroup -    \pdfprotrudechars2 -    \pdfadjustspacing2 +    \ifdefined\pdfprotrudechars \pdfprotrudechars \else \protrudechars \fi 2 \relax +    \ifdefined\pdfadjustspacing \pdfadjustspacing \else \adjustspacing \fi 2 \relax      \font\testb=file:lmroman12-regular:+liga;extend=1.5         at 12pt \testb \input tufte \par      \font\testb=file:lmroman12-regular:+liga;slant=0.8          at 12pt \testb \input tufte \par @@ -48,12 +50,30 @@  \setmplibformat{plain} +\directlua { +    function MpTest() +        metapost.print("fullcircle scaled 3cm") +    end +} +  \mplibcode      beginfig(1) ;          draw fullcircle              scaled 10cm              withcolor red              withpen pencircle xscaled 4mm yscaled 2mm rotated 30 ; +        draw "test" infont defaultfont scaled 4 ; +        verbatimtex \sl etex; +        draw btex some more test etex scaled 2 ; +        currentpicture := currentpicture shifted (0,1cm) ; +        verbatimtex \bf etex; +        draw btex another test etex scaled 2 ; +        currentpicture := currentpicture shifted (0,1cm) ; +        draw btex another test etex scaled 2 ; +        draw +            runscript("MpTest()") +            withcolor green +            withpen pencircle xscaled 2mm yscaled 1mm rotated 20 ;      endfig ;  \endmplibcode diff --git a/src/fontloader/misc/fontloader-util-str.lua b/src/fontloader/misc/fontloader-util-str.lua index c2139b1..95534c8 100644 --- a/src/fontloader/misc/fontloader-util-str.lua +++ b/src/fontloader/misc/fontloader-util-str.lua @@ -6,7 +6,7 @@ if not modules then modules = { } end modules ['util-str'] = {      license   = "see context related readme files"  } -utilities         = utilities or {} +utilities         = utilities or { }  utilities.strings = utilities.strings or { }  local strings     = utilities.strings @@ -354,7 +354,16 @@ function string.autosingle(s,sep)      return ("'" .. tostring(s) .. "'")  end -local tracedchars  = { } +local tracedchars  = { [0] = +    -- the regular bunch +    "[null]", "[soh]", "[stx]", "[etx]", "[eot]", "[enq]", "[ack]", "[bel]", +    "[bs]",   "[ht]",  "[lf]",  "[vt]",  "[ff]",  "[cr]",  "[so]",  "[si]", +    "[dle]",  "[dc1]", "[dc2]", "[dc3]", "[dc4]", "[nak]", "[syn]", "[etb]", +    "[can]",  "[em]",  "[sub]", "[esc]", "[fs]",  "[gs]",  "[rs]",  "[us]", +    -- plus space +    "[space]", -- 0x20 +} +  string.tracedchars = tracedchars  strings.tracers    = tracedchars | 
