diff options
35 files changed, 4790 insertions, 2685 deletions
diff --git a/doc/filegraph.dot b/doc/filegraph.dot index b274461..34c462a 100644 --- a/doc/filegraph.dot +++ b/doc/filegraph.dot @@ -356,7 +356,7 @@ strict digraph luaotfload_files { //looks weird with circo ...                      <td>font-tfm.lua</td>                  </tr>                  <tr> -                    <td>font-afm.lua</td> +                    <td>font-one.lua</td>                      <td>font-afk.lua</td>                      <td>font-oti.lua</td>                  </tr> @@ -385,6 +385,9 @@ strict digraph luaotfload_files { //looks weird with circo ...                      <td>font-xtx.lua</td>                      <td>font-gbn.lua</td>                  </tr> +                <tr> +                    <td>font-ocl.lua</td> +                </tr>              </table>          >,      ] diff --git a/doc/luaotfload-main.tex b/doc/luaotfload-main.tex index 05c3670..e238901 100644 --- a/doc/luaotfload-main.tex +++ b/doc/luaotfload-main.tex @@ -1279,7 +1279,7 @@ grouped twofold as below:          \beginaltitem{font-cid.lua} \endaltitem          \beginaltitem{font-map.lua} \endaltitem          \beginaltitem{font-tfm.lua} \endaltitem -        \beginaltitem{font-afm.lua} \endaltitem +        \beginaltitem{font-one.lua} \endaltitem          \beginaltitem{font-afk.lua} \endaltitem          \beginaltitem{font-oti.lua} \endaltitem          \beginaltitem{font-otr.lua} \endaltitem @@ -1293,6 +1293,7 @@ grouped twofold as below:          \beginaltitem{font-ota.lua} \endaltitem          \beginaltitem{font-ots.lua} \endaltitem          \beginaltitem{font-osd.lua} \endaltitem +        \beginaltitem{font-ocl.lua} \endaltitem          \beginaltitem{font-lua.lua} \endaltitem          \beginaltitem{font-def.lua} \endaltitem          \beginaltitem{font-xtx.lua} \endaltitem diff --git a/doc/luaotfload.conf.rst b/doc/luaotfload.conf.rst index 7b7f342..16f055b 100644 --- a/doc/luaotfload.conf.rst +++ b/doc/luaotfload.conf.rst @@ -188,10 +188,8 @@ If ``update-live`` is set, Luaotfload will reload the database if it  cannot find a requested font. Those who prefer to update manually using  **luaotfload-tool** should unset this flag. -The option ``use-fontforge`` switches name scanning back to the old -method that relies on the builtin ``fontloader`` library. This is -interesting for reference until the Fontforge code will be removed -eventually. +The option ``use-fontforge`` had a meaning during the transition to the +Lua-only Opentype reader. At present it is ignored.  Section ``default-features``  ----------------------------------------------------------------------- diff --git a/scripts/mkimport b/scripts/mkimport index 25d7560..1dc0a29 100755 --- a/scripts/mkimport +++ b/scripts/mkimport @@ -228,7 +228,6 @@ local imports = {    context = { --=> all merged      { name = "data-con"          , ours = "data-con"          , kind = kind_merged    },      { name = "font-afk"          , ours = "font-afk"          , kind = kind_merged    }, -    { name = "font-afm"          , ours = "font-afm"          , kind = kind_merged    },      { name = "font-cff"          , ours = "font-cff"          , kind = kind_merged    },      { name = "font-cid"          , ours = "font-cid"          , kind = kind_merged    },      { name = "font-con"          , ours = "font-con"          , kind = kind_merged    }, @@ -238,6 +237,9 @@ local imports = {      { name = "font-ini"          , ours = "font-ini"          , kind = kind_merged    },      { name = "font-lua"          , ours = "font-lua"          , kind = kind_merged    },      { name = "font-map"          , ours = "font-map"          , kind = kind_merged    }, +    { name = "font-ocl"          , ours = "font-ocl"          , kind = kind_merged    }, +    { name = "font-onr"          , ours = "font-onr"          , kind = kind_merged    }, +    { name = "font-one"          , ours = "font-one"          , kind = kind_merged    },      { name = "font-osd"          , ours = "font-osd"          , kind = kind_merged    },      { name = "font-ota"          , ours = "font-ota"          , kind = kind_merged    },      { name = "font-otd"          , ours = "font-otd"          , kind = kind_merged    }, @@ -294,25 +296,27 @@ local package = {  ---   [20] font-map.lua  ---   [21] luatex-fonts-syn.lua  ---   [22] font-tfm.lua ----   [23] font-afm.lua ----   [24] font-afk.lua ----   [25] font-oti.lua ----   [26] font-otr.lua ----   [27] font-cff.lua ----   [28] font-ttf.lua ----   [29] font-dsp.lua ----   [30] font-oup.lua ----   [31] font-otl.lua ----   [32] font-oto.lua ----   [33] font-otj.lua ----   [34] font-ota.lua ----   [35] font-ots.lua ----   [36] font-osd.lua ----   [37] font-lua.lua ----   [38] font-def.lua ----   [39] font-xtx.lua ----   [40] luatex-fonts-ext.lua ----   [41] font-gbn.lua +---   [23] font-oti.lua +---   [24] font-otr.lua +---   [25] font-cff.lua +---   [26] font-ttf.lua +---   [27] font-dsp.lua +---   [28] font-oup.lua +---   [29] font-otl.lua +---   [30] font-oto.lua +---   [31] font-otj.lua +---   [32] font-ota.lua +---   [33] font-ots.lua +---   [34] font-osd.lua +---   [35] font-ocl.lua +---   [36] font-onr.lua +---   [37] font-one.lua +---   [38] font-afk.lua +---   [39] font-lua.lua +---   [40] font-def.lua +---   [41] font-xtx.lua +---   [42] luatex-fonts-ext.lua +---   [43] font-gbn.lua  ---  --- Of these, nos. 01--11 are provided by the Lualibs. Keeping them  --- around in the Luaotfload fontloader is therefore unnecessary. @@ -358,8 +362,6 @@ local package = {      "font-map",      "fonts-syn",      "font-tfm", -    "font-afm", -    "font-afk",      "font-oti",      "font-otr",      "font-cff", @@ -372,6 +374,10 @@ local package = {      "font-ota",      "font-ots",      "font-osd", +    "font-ocl", +    "font-onr", +    "font-one", +    "font-afk",      "font-lua",      "font-def",      "fonts-ext", diff --git a/scripts/mkstatus b/scripts/mkstatus index f9ff7eb..10f6680 100755 --- a/scripts/mkstatus +++ b/scripts/mkstatus @@ -84,7 +84,8 @@ local names = {    { miscdir,      "fontloader-basics-nod.lua",      },    { miscdir,      "fontloader-data-con.lua",        },    { miscdir,      "fontloader-font-afk.lua",        }, -  { miscdir,      "fontloader-font-afm.lua",        }, +  { miscdir,      "fontloader-font-onr.lua",        }, +  { miscdir,      "fontloader-font-one.lua",        },    { miscdir,      "fontloader-font-cid.lua",        },    { miscdir,      "fontloader-font-con.lua",        },    { miscdir,      "fontloader-font-def.lua",        }, @@ -112,6 +113,7 @@ local names = {    { miscdir,      "fontloader-font-ota.lua",        },    { miscdir,      "fontloader-font-ots.lua",        },    { miscdir,      "fontloader-font-osd.lua",        }, +  { miscdir,      "fontloader-font-ocl.lua",        },    --- lua libraries    { miscdir,      "fontloader-languages.lua",       }, diff --git a/src/fontloader/misc/fontloader-font-con.lua b/src/fontloader/misc/fontloader-font-con.lua index b118535..1a0daff 100644 --- a/src/fontloader/misc/fontloader-font-con.lua +++ b/src/fontloader/misc/fontloader-font-con.lua @@ -10,7 +10,6 @@ if not modules then modules = { } end modules ['font-con'] = {  local next, tostring, rawget = next, tostring, rawget  local format, match, lower, gsub = string.format, string.match, string.lower, string.gsub -local utfbyte = utf.byte  local sort, insert, concat, sortedkeys, serialize, fastcopy = table.sort, table.insert, table.concat, table.sortedkeys, table.serialize, table.fastcopy  local derivetable = table.derive @@ -338,6 +337,20 @@ function constructors.enhanceparameters(parameters)      }  end +local function mathkerns(v,vdelta) +    local k = { } +    for i=1,#v do +        local entry  = v[i] +        local height = entry.height +        local kern   = entry.kern +        k[i] = { +            height = height and vdelta*height or 0, +            kern   = kern   and vdelta*kern   or 0, +        } +    end +    return k +end +  function constructors.scale(tfmdata,specification)      local target         = { } -- the new table      -- @@ -749,22 +762,15 @@ function constructors.scale(tfmdata,specification)                  chr.top_accent = vdelta*va              end              if stackmath then -                local mk = character.mathkerns -- not in math ? +                local mk = character.mathkerns                  if mk then -                    local kerns = { } -                    local v = mk.top_right    if v then local k = { } for i=1,#v do local vi = v[i] -                        k[i] = { height = vdelta*vi.height, kern = vdelta*vi.kern } -                    end     kerns.top_right    = k end -                    local v = mk.top_left     if v then local k = { } for i=1,#v do local vi = v[i] -                        k[i] = { height = vdelta*vi.height, kern = vdelta*vi.kern } -                    end     kerns.top_left     = k end -                    local v = mk.bottom_left  if v then local k = { } for i=1,#v do local vi = v[i] -                        k[i] = { height = vdelta*vi.height, kern = vdelta*vi.kern } -                    end     kerns.bottom_left  = k end -                    local v = mk.bottom_right if v then local k = { } for i=1,#v do local vi = v[i] -                        k[i] = { height = vdelta*vi.height, kern = vdelta*vi.kern } -                    end     kerns.bottom_right = k end -                    chr.mathkern = kerns -- singular -> should be patched in luatex ! +                    local tr, tl, br, bl = mk.topright, mk.topleft, mk.bottomright, mk.bottomleft +                    chr.mathkern = { -- singular -> should be patched in luatex ! +                        top_right    = tr and mathkerns(tr,vdelta) or nil, +                        top_left     = tl and mathkerns(tl,vdelta) or nil, +                        bottom_right = br and mathkerns(br,vdelta) or nil, +                        bottom_left  = bl and mathkerns(bl,vdelta) or nil, +                    }                  end              end              if hasitalics then @@ -1043,6 +1049,29 @@ function constructors.hashfeatures(specification) -- will be overloaded      return "unknown"  end +-- hashmethods.normal = function(list) +--     local s = { } +--     local n = 0 +--     for k, v in next, list do +--         if not k then +--             -- no need to add to hash +--         elseif k == "number" or k == "features" then +--             -- no need to add to hash (maybe we need a skip list) +--         else +--             n = n + 1 +--             s[n] = k +--         end +--     end +--     if n > 0 then +--         sort(s) +--         for i=1,n do +--             local k = s[i] +--             s[i] = k .. '=' .. tostring(list[k]) +--         end +--         return concat(s,"+") +--     end +-- end +  hashmethods.normal = function(list)      local s = { }      local n = 0 @@ -1053,15 +1082,11 @@ hashmethods.normal = function(list)              -- no need to add to hash (maybe we need a skip list)          else              n = n + 1 -            s[n] = k +            s[n] = k .. '=' .. tostring(v)          end      end      if n > 0 then          sort(s) -        for i=1,n do -            local k = s[i] -            s[i] = k .. '=' .. tostring(list[k]) -        end          return concat(s,"+")      end  end @@ -1230,7 +1255,11 @@ function constructors.getfeatureaction(what,where,mode,name)      end  end -function constructors.newhandler(what) -- could be a metatable newindex +local newhandler        = { } +constructors.handlers   = newhandler -- downward compatible +constructors.newhandler = newhandler + +local function setnewhandler(what) -- could be a metatable newindex      local handler = handlers[what]      if not handler then          handler = { } @@ -1239,7 +1268,16 @@ function constructors.newhandler(what) -- could be a metatable newindex      return handler  end -function constructors.newfeatures(what) -- could be a metatable newindex +setmetatable(newhandler, { +    __call  = function(t,k) local v = t[k] return v end, +    __index = function(t,k) local v = setnewhandler(k) t[k] = v return v end, +}) + +local newfeatures        = { } +constructors.newfeatures = newfeatures -- downward compatible +constructors.features    = newfeatures + +local function setnewfeatures(what)      local handler = handlers[what]      local features = handler.features      if not features then @@ -1259,6 +1297,11 @@ function constructors.newfeatures(what) -- could be a metatable newindex      return features  end +setmetatable(newfeatures, { +    __call  = function(t,k) local v = t[k] return v end, +    __index = function(t,k) local v = setnewfeatures(k) t[k] = v return v end, +}) +  --[[ldx--  <p>We need to check for default features. For this we provide  a helper function.</p> @@ -1286,7 +1329,6 @@ function constructors.initializefeatures(what,tfmdata,features,trace,report)          local properties       = tfmdata.properties or { } -- brrr          local whathandler      = handlers[what]          local whatfeatures     = whathandler.features -        local whatinitializers = whatfeatures.initializers          local whatmodechecker  = whatfeatures.modechecker          -- properties.mode can be enforces (for instance in font-otd)          local mode             = properties.mode or (whatmodechecker and whatmodechecker(tfmdata,features,features.mode)) or features.mode or "base" diff --git a/src/fontloader/misc/fontloader-font-dsp.lua b/src/fontloader/misc/fontloader-font-dsp.lua index 330a940..a1ae17f 100644 --- a/src/fontloader/misc/fontloader-font-dsp.lua +++ b/src/fontloader/misc/fontloader-font-dsp.lua @@ -69,7 +69,6 @@ local readers           = fonts.handlers.otf.readers  local streamreader      = readers.streamreader  local setposition       = streamreader.setposition -local skipbytes         = streamreader.skip  local skipshort         = streamreader.skipshort  local readushort        = streamreader.readcardinal2  -- 16-bit unsigned integer  local readulong         = streamreader.readcardinal4  -- 24-bit unsigned integer @@ -77,6 +76,7 @@ local readshort         = streamreader.readinteger2   -- 16-bit   signed integer  local readfword         = readshort  local readstring        = streamreader.readstring  local readtag           = streamreader.readtag +local readbytes         = streamreader.readbytes  local gsubhandlers      = { }  local gposhandlers      = { } @@ -716,6 +716,8 @@ function gsubhandlers.single(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofg      end  end +-- we see coverage format 0x300 in some old ms fonts +  local function sethandler(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,what)      local tableoffset = lookupoffset + offset      setposition(f,tableoffset) @@ -1599,6 +1601,14 @@ do          local reported = { } +        local function report_issue(i,what,sequence,kind) +            local name = sequence.name +            if not reported[name] then +                report("rule %i in %s lookup %a has %s lookups",i,what,name,kind) +                reported[name] = true +            end +        end +          for i=lastsequence+1,nofsequences do              local sequence = sequences[i]              local steps    = sequence.steps @@ -1610,18 +1620,10 @@ do                          local rule     = rules[i]                          local rlookups = rule.lookups                          if not rlookups then -                            local name = sequence.name -                            if not reported[name] then -                                report("rule %i in %s lookup %a has %s lookups",i,what,name,"no") -                                reported[name] = true -                            end +                            report_issue(i,what,sequence,"no")                          elseif not next(rlookups) then -                            local name = sequence.name -                            if not reported[name] then -                                -- can be ok as it aborts a chain sequence -                                report("rule %i in %s lookup %a has %s lookups",i,what,name,"empty") -                                reported[name] = true -                            end +                            -- can be ok as it aborts a chain sequence +                            report_issue(i,what,sequence,"empty")                              rule.lookups = nil                          else                              for index, lookupid in sortedhash(rlookups) do -- nicer @@ -1629,23 +1631,36 @@ do                                  if not h then                                      -- here we have a lookup that is used independent as well                                      -- as in another one -                                    nofsublookups = nofsublookups + 1 -                                 -- report("registering %i as sublookup %i",lookupid,nofsublookups) -                                    local d = lookups[lookupid].done -                                    h = { -                                        index     = nofsublookups, -- handy for tracing -                                        name      = f_lookupname(lookupprefix,"d",lookupid+lookupidoffset), -                                        derived   = true,          -- handy for tracing -                                        steps     = d.steps, -                                        nofsteps  = d.nofsteps, -                                        type      = d.lookuptype, -                                        markclass = d.markclass or nil, -                                        flags     = d.flags, -                                     -- chain     = d.chain, -                                    } -                                    sublookuplist[nofsublookups] = h -                                    sublookuphash[lookupid] = nofsublookups -                                    sublookupcheck[lookupid] = 1 +                                    local lookup = lookups[lookupid] +                                    if lookup then +                                        local d = lookup.done +                                        if d then +                                            nofsublookups = nofsublookups + 1 +                                         -- report("registering %i as sublookup %i",lookupid,nofsublookups) +                                            h = { +                                                index     = nofsublookups, -- handy for tracing +                                                name      = f_lookupname(lookupprefix,"d",lookupid+lookupidoffset), +                                                derived   = true,          -- handy for tracing +                                                steps     = d.steps, +                                                nofsteps  = d.nofsteps, +                                                type      = d.lookuptype, +                                                markclass = d.markclass or nil, +                                                flags     = d.flags, +                                             -- chain     = d.chain, +                                            } +                                            sublookuplist[nofsublookups] = h +                                            sublookuphash[lookupid] = nofsublookups +                                            sublookupcheck[lookupid] = 1 +                                        else +                                            report_issue(i,what,sequence,"missing") +                                            rule.lookups = nil +                                            break +                                        end +                                    else +                                        report_issue(i,what,sequence,"bad") +                                        rule.lookups = nil +                                        break +                                    end                                  else                                      sublookupcheck[lookupid] = sublookupcheck[lookupid] + 1                                  end @@ -2015,16 +2030,15 @@ local function readmathglyphinfo(f,fontdata,offset)              local function get(offset)                  setposition(f,kernoffset+offset)                  local n = readushort(f) -                if n > 0 then +                if n == 0 then +                    local k = readmathvalue(f) +                    if k == 0 then +                        -- no need for it (happens sometimes) +                    else +                        return { { kern = k } } +                    end +                else                      local l = { } -                 -- for i=1,n do -                 --     l[i] = { readushort(f), 0 } -- height, kern -                 --     skipshort(f) -                 -- end -                 -- for i=1,n do -                 --     l[i][2] = readushort(f) -                 --     skipshort(f) -                 -- end                      for i=1,n do                          l[i] = { height = readmathvalue(f) }                      end @@ -2059,10 +2073,10 @@ local function readmathglyphinfo(f,fontdata,offset)                      if next(kernset) then                          local glyph = glyphs[coverage[i]]                          local math  = glyph.math -                        if not math then -                            glyph.math = { kerns = kernset } -                        else +                        if math then                              math.kerns = kernset +                        else +                            glyph.math = { kerns = kernset }                          end                      end                  end @@ -2178,7 +2192,7 @@ function readers.math(f,fontdata,specification)              setposition(f,tableoffset)              local version = readulong(f)              if version ~= 0x00010000 then -                report("table version %a of %a is not supported (yet), maybe font %s is bad",version,what,fontdata.filename) +                report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"math",fontdata.filename)                  return              end              local constants = readushort(f) @@ -2198,3 +2212,146 @@ function readers.math(f,fontdata,specification)          end      end  end + +function readers.colr(f,fontdata,specification) +    local datatable = fontdata.tables.colr +    if datatable then +        if specification.glyphs then +            local tableoffset = datatable.offset +            setposition(f,tableoffset) +            local version = readushort(f) +            if version ~= 0 then +                report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"colr",fontdata.filename) +                return +            end +            if not fontdata.tables.cpal then +                report("color table %a in font %a has no mandate %a table","colr",fontdata.filename,"cpal") +                fontdata.colorpalettes = { } +            end +            local glyphs       = fontdata.glyphs +            local nofglyphs    = readushort(f) +            local baseoffset   = readulong(f) +            local layeroffset  = readulong(f) +            local noflayers    = readushort(f) +            local layerrecords = { } +            local maxclass     = 0 +            -- The special value 0xFFFF is foreground (but we index from 1). It +            -- more looks like indices into a palette so 'class' is a better name +            -- than 'palette'. +            setposition(f,tableoffset + layeroffset) +            for i=1,noflayers do +                local slot    = readushort(f) +                local class = readushort(f) +                if class < 0xFFFF then +                    class = class + 1 +                    if class > maxclass then +                        maxclass = class +                    end +                end +                layerrecords[i] = { +                    slot  = slot, +                    class = class, +                } +            end +            fontdata.maxcolorclass = maxclass +            setposition(f,tableoffset + baseoffset) +            for i=0,nofglyphs-1 do +                local glyphindex = readushort(f) +                local firstlayer = readushort(f) +                local noflayers  = readushort(f) +                local t = { } +                for i=1,noflayers do +                    t[i] = layerrecords[firstlayer+i] +                end +                glyphs[glyphindex].colors = t +            end +        end +        fontdata.hascolor = true +    end +end + +function readers.cpal(f,fontdata,specification) +    if specification.glyphs then +        local datatable = fontdata.tables.cpal +        if datatable then +            local tableoffset = datatable.offset +            setposition(f,tableoffset) +            local version = readushort(f) +            if version > 1 then +                report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"cpal",fontdata.filename) +                return +            end +            local nofpaletteentries  = readushort(f) +            local nofpalettes        = readushort(f) +            local nofcolorrecords    = readushort(f) +            local firstcoloroffset   = readulong(f) +            local colorrecords       = { } +            local palettes           = { } +            for i=1,nofpalettes do +                palettes[i] = readushort(f) +            end +            if version == 1 then +                -- used for guis +                local palettettypesoffset = readulong(f) +                local palettelabelsoffset = readulong(f) +                local paletteentryoffset  = readulong(f) +            end +            setposition(f,tableoffset+firstcoloroffset) +            for i=1,nofcolorrecords do +                local b, g, r, a = readbytes(f,4) +                colorrecords[i] = { +                    r, g, b, a ~= 255 and a or nil, +                } +            end +            for i=1,nofpalettes do +                local p = { } +                local o = palettes[i] +                for j=1,nofpaletteentries do +                    p[j] = colorrecords[o+j] +                end +                palettes[i] = p +            end +            fontdata.colorpalettes = palettes +        end +    end +end + +function readers.svg(f,fontdata,specification) +    local datatable = fontdata.tables.svg +    if datatable then +        if specification.glyphs then +            local tableoffset = datatable.offset +            setposition(f,tableoffset) +            local version = readushort(f) +            if version ~= 0 then +                report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"svg",fontdata.filename) +                return +            end +            local glyphs      = fontdata.glyphs +            local indexoffset = tableoffset + readulong(f) +            local reserved    = readulong(f) +            setposition(f,indexoffset) +            local nofentries  = readushort(f) +            local entries     = { } +            for i=1,nofentries do +                entries[i] = { +                    first  = readushort(f), +                    last   = readushort(f), +                    offset = indexoffset + readulong(f), +                    length = readulong(f), +                } +            end +            for i=1,nofentries do +                local entry = entries[i] +                setposition(f,entry.offset) +                entries[i] = { +                    first = entry.first, +                    last  = entry.last, +                    data  = readstring(f,entry.length) +                } +            end +            fontdata.svgshapes = entries +        end +        fontdata.hascolor = true +    end +end diff --git a/src/fontloader/misc/fontloader-font-gbn.lua b/src/fontloader/misc/fontloader-font-gbn.lua index daa072b..1ae817d 100644 --- a/src/fontloader/misc/fontloader-font-gbn.lua +++ b/src/fontloader/misc/fontloader-font-gbn.lua @@ -19,7 +19,6 @@ local nodes = nodes  local nuts        = nodes.nuts -- context abstraction of direct nodes  local traverse_id = nuts.traverse_id -local remove_node = nuts.remove  local free_node   = nuts.free  local glyph_code  = nodes.nodecodes.glyph @@ -126,17 +125,19 @@ function nodes.handlers.nodepass(head)                              local variant = hash[getchar(p)]                              if variant then                                  setchar(p,variant) -                                if not redundant then -                                    redundant = { n } -                                else -                                    redundant[#redundant+1] = n -                                end                              end                          end                      end +                    -- per generic user request we always remove selectors +                    if not redundant then +                        redundant = { n } +                    else +                        redundant[#redundant+1] = n +                    end                  end              end          end +        local nofbasefonts = #basefonts          if redundant then              for i=1,#redundant do                  local r = redundant[i] @@ -147,8 +148,8 @@ function nodes.handlers.nodepass(head)                  else                      setlink(p,n)                  end -                if b > 0 then -                    for i=1,b do +                if nofbasefonts > 0 then +                    for i=1,nofbasefonts do                          local bi = basefonts[i]                          if r == bi[1] then                              bi[1] = n @@ -192,8 +193,8 @@ function nodes.handlers.nodepass(head)                  end              end          end -        if basemodepass and #basefonts > 0 then -            for i=1,#basefonts do +        if basemodepass and nofbasefonts > 0 then +            for i=1,nofbasefonts do                  local range = basefonts[i]                  local start = range[1]                  local stop  = range[2] diff --git a/src/fontloader/misc/fontloader-font-ini.lua b/src/fontloader/misc/fontloader-font-ini.lua index c547f89..abc3194 100644 --- a/src/fontloader/misc/fontloader-font-ini.lua +++ b/src/fontloader/misc/fontloader-font-ini.lua @@ -12,8 +12,6 @@ if not modules then modules = { } end modules ['font-ini'] = {  local allocate = utilities.storage.allocate -local report_defining = logs.reporter("fonts","defining") -  fonts               = fonts or { }  local fonts         = fonts diff --git a/src/fontloader/misc/fontloader-font-map.lua b/src/fontloader/misc/fontloader-font-map.lua index 509e751..6151b37 100644 --- a/src/fontloader/misc/fontloader-font-map.lua +++ b/src/fontloader/misc/fontloader-font-map.lua @@ -10,7 +10,6 @@ local tonumber, next, type = tonumber, next, type  local match, format, find, concat, gsub, lower = string.match, string.format, string.find, table.concat, string.gsub, string.lower  local P, R, S, C, Ct, Cc, lpegmatch = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Ct, lpeg.Cc, lpeg.match -local utfbyte = utf.byte  local floor = math.floor  local formatters = string.formatters @@ -45,7 +44,7 @@ of obsolete. Some code may move to runtime or auxiliary modules.</p>  --     end  -- end -local hex     = R("AF","09") +local hex     = R("AF","af","09")  ----- 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 @@ -140,7 +139,7 @@ local f_double = formatters["%04X%04X"]  --     end  -- end -local function tounicode16(unicode,name) +local function tounicode16(unicode)      if unicode < 0xD7FF or (unicode > 0xDFFF and unicode <= 0xFFFF) then          return f_single(unicode)      else @@ -149,7 +148,7 @@ local function tounicode16(unicode,name)      end  end -local function tounicode16sequence(unicodes,name) +local function tounicode16sequence(unicodes)      local t = { }      for l=1,#unicodes do          local u = unicodes[l] @@ -295,145 +294,145 @@ function mappings.addtounicode(data,filename,checklookups)      local ns            = 0      local nl            = 0      -- -    for unic, glyph in next, descriptions do +    for du, glyph in next, descriptions do          local name = glyph.name          if name then -            local index = glyph.index -            local r = overloads[name] -            if r then +            local overload = overloads[name] +            if overload 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 +             -- glyph.name    = overload.name +                glyph.unicode = overload.unicode +            else +                local gu = glyph.unicode -- can already be set (number or table) +                if not gu or gu == -1 or du >= private or (du >= 0xE000 and du <= 0xF8FF) or du == 0xFFFE or du == 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. -                -- -                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 +                    -- 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 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 -                    else -                        local t, n = { }, 0 -                        for l=1,nsplit do -                            local base = split[l] +                        elseif nsplit == 1 then +                            local base = split[1]                              local u = unicodes[base] or unicodevector[base] or contextvector[name]                              if not u then -                                break +                                -- skip                              elseif type(u) == "table" then -                                if u[1] >= private then -                                    break +                                -- unlikely +                                if u[1] < private then +                                    unicode = u +                                    glyph.unicode = unicode                                  end -                                n = n + 1 -                                t[n] = u[1] -                            else -                                if u >= private then +                            elseif u < private then +                                unicode = u +                                glyph.unicode = unicode +                            end +                        else +                            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 +                            if n > 0 then +                                if n == 1 then +                                    unicode = t[1] +                                else +                                    unicode = t                                  end -                                n = n + 1 -                                t[n] = u +                                glyph.unicode = unicode                              end                          end -                        if n > 0 then -                            if n == 1 then -                                unicode = t[1] +                        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 -                                unicode = t +                                ns      = ns + 1 +                                unicode = foundcodes                              end -                            glyph.unicode = unicode                          end                      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 +                    -- check using substitutes and alternates +                    local r = overloads[unicode] +                    if r then +                        unicode = r.unicode +                        glyph.unicode = unicode +                    end +                    -- +                    if not unicode then +                        missing[du] = true +                        nofmissing  = nofmissing + 1                      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[unic] = true -                    nofmissing    = nofmissing + 1                  end              end -        else -            -- no name          end      end      if type(checklookups) == "function" then diff --git a/src/fontloader/misc/fontloader-font-ocl.lua b/src/fontloader/misc/fontloader-font-ocl.lua new file mode 100644 index 0000000..9661083 --- /dev/null +++ b/src/fontloader/misc/fontloader-font-ocl.lua @@ -0,0 +1,297 @@ +if not modules then modules = { } end modules ['font-ocl'] = { +    version   = 1.001, +    comment   = "companion to font-otf.lua (context)", +    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL", +    copyright = "PRAGMA ADE / ConTeXt Development Team", +    license   = "see context related readme files" +} + +-- todo : user list of colors + +local formatters = string.formatters + +local otf = fonts.handlers.otf + +local f_color_start = formatters["pdf:direct: %f %f %f rg"] +local s_color_stop  = "pdf:direct:" + +if context then + +    local startactualtext = nil +    local stopactualtext  = nil + +    function otf.getactualtext(n) +        if not startactualtext then +            startactualtext = backends.codeinjections.startunicodetoactualtext +            stopactualtext  = backends.codeinjections.stopunicodetoactualtext +        end +        return startactualtext(n), stopactualtext() +    end + +else + +    local tounicode = fonts.mappings.tounicode16 + +    function otf.getactualtext(n) +        return "/Span << /ActualText <feff" .. tounicode(n) .. "> >> BDC", "EMC" +    end + +end + +local function initializecolr(tfmdata,kind,value) -- hm, always value +    if value then +        local palettes = tfmdata.resources.colorpalettes +        if palettes then +            -- +            local palette = palettes[tonumber(value) or 1] or palettes[1] or { } +            local classes = #palette +            if classes == 0 then +                return +            end +            -- +            local characters   = tfmdata.characters +            local descriptions = tfmdata.descriptions +            local properties   = tfmdata.properties +            local colorvalues  = { } +            -- +            properties.virtualized = true +            tfmdata.fonts = { +                { id = 0 } +            } +            -- +            for i=1,classes do +                local p = palette[i] +                colorvalues[i] = { "special", f_color_start(p[1]/255,p[2]/255,p[3]/255) } +            end +            -- +            local getactualtext = otf.getactualtext +            -- +            for unicode, character in next, characters do +                local description = descriptions[unicode] +                if description then +                    local colorlist = description.colors +                    if colorlist then +                        local b, e = getactualtext(unicode) +                        local w = character.width or 0 +                        local s = #colorlist +                        local n = 1 +                        local t = { +                            { "special", "pdf:direct: q " .. b } +                        } +                        for i=1,s do +                            local entry = colorlist[i] +                            n = n + 1 t[n] = colorvalues[entry.class] +                            n = n + 1 t[n] = { "char", entry.slot } +                            if s > 1 and i < s and w ~= 0 then +                                n = n + 1 t[n] = { "right", -w } +                            end +                        end +                        n = n + 1 t[n] = { "special", "pdf:direct:" .. e .. " Q" } +                        character.commands = t +                    end +                end +            end +        end +    end +end + +fonts.handlers.otf.features.register { +    name         = "colr", +    description  = "color glyphs", +    manipulators = { +        base = initializecolr, +        node = initializecolr, +    } +} + +local otfsvg   = otf.svg or { } +otf.svg        = otfsvg +otf.svgenabled = true + +do + +    local nofstreams = 0 + + -- local f_setstream = formatters[ [[io.savedata("svg-glyph-%05i",%q)]] ] + -- local f_getstream = formatters[ [[svg-glyph-%05i]] ] + + -- function otfsvg.storepdfdata(pdf) + --     nofstreams = nofstreams + 1 + --     storepdfdata = function(pdf) + --         nofstreams = nofstreams + 1 + --         return f_setstream(nofstreams,pdf), f_getstream(nofstreams) + --     end + -- end + +    local f_name = formatters[ [[svg-glyph-%05i]] ] +    local f_used = context and formatters[ [[original:///%s]] ] or formatters[ [[%s]] ] + +    local cache = { } + +    function otfsvg.storepdfdata(pdf) +        nofstreams = nofstreams + 1 +        local o, n = epdf.openMemStream(pdf,#pdf,f_name(nofstreams)) +        cache[n] = o -- we need to keep in mem +        return nil, f_used(n), nil +    end + +    if context then + +        local storepdfdata = otfsvg.storepdfdata +        local initialized  = false + +        function otfsvg.storepdfdata(pdf) +            if not initialized then +                if resolvers.setmemstream then +                    local f_setstream = formatters[ [[resolvers.setmemstream("svg-glyph-%05i",%q,true)]] ] +                    local f_getstream = formatters[ [[memstream:///svg-glyph-%05i]] ] +                    local f_nilstream = formatters[ [[resolvers.resetmemstream("svg-glyph-%05i",true)]] ] +                    storepdfdata = function(pdf) +                        nofstreams = nofstreams + 1 +                        return +                            f_setstream(nofstreams,pdf), +                            f_getstream(nofstreams), +                            f_nilstream(nofstreams) +                    end +                    otfsvg.storepdfdata = storepdfdata +                end +                initialized = true +            end +            return storepdfdata(pdf) +        end + +    end + +end + +if context and xml.convert then + +    local report_svg = logs.reporter("fonts","svg conversion") + +    function otfsvg.topdf(svgshapes) +        local svgfile   = "temp-otf-svg-shape.svg" +        local pdffile   = "temp-otf-svg-shape.pdf" +        local command   = "inkscape " .. svgfile .. " --export-pdf=" .. pdffile +     -- local command   = [[python "c:\Users\Hans Hagen\AppData\Roaming\Python\Scripts\cairosvg" -f pdf ]] .. svgfile .. " -o " .. pdffile +        local testrun   = false +        local pdfshapes = { } +        local nofshapes = #svgshapes +        report_svg("processing %i svg containers",nofshapes) +        for i=1,nofshapes do +            local entry = svgshapes[i] +            for j=entry.first,entry.last do +                local svg  = xml.convert(entry.data) +                local data = xml.first(svg,"/svg[@id='glyph"..j.."']") +                io.savedata(svgfile,tostring(data)) +                report_svg("processing svg shape of glyph %i in container %i",j,i) +                os.execute(command) +                pdfshapes[j] = io.loaddata(pdffile) +            end +            if testrun and i > testrun then +                report_svg("quiting test run") +                break +            end +        end +        os.remove(svgfile) +        return pdfshapes +    end + +else + +    function otfsvg.topdf(svgshapes) +        local svgfile   = "temp-otf-svg-shape.svg" +        local pdffile   = "temp-otf-svg-shape.pdf" +        local command   = "inkscape " .. svgfile .. " --export-pdf=" .. pdffile +        local pdfshapes = { } +        local nofshapes = #svgshapes +        texio.write(formatters["[converting %i svg glyphs to pdf using command %q : "](nofshapes,command)) +        for i=1,nofshapes do +            local entry = svgshapes[i] +            for j=entry.first,entry.last do +                -- cross our fingers .. some, day i will filter +                texio.write(formatters["%i "](j)) +                io.savedata(svgfile,tostring(entry.data)) +                os.execute(command) +                pdfshapes[j] = io.loaddata(pdffile) +            end +        end +        os.remove(svgfile) +        texio.write("done]") +        return pdfshapes +    end + +end + +local function initializesvg(tfmdata,kind,value) -- hm, always value +    if value and otf.svgenabled then +        local characters   = tfmdata.characters +        local descriptions = tfmdata.descriptions +        local properties   = tfmdata.properties +        -- +        local svg       = properties.svg +        local hash      = svg and svg.hash +        local timestamp = svg and svg.timestamp +        if not hash then +            return +        end +        -- +        local pdffile   = containers.read(otf.pdfcache,hash) +        local pdfshapes = pdffile and pdffile.pdfshapes +        if not pdfshapes or pdffile.timestamp ~= timestamp then +            local svgfile   = containers.read(otf.svgcache,hash) +            local svgshapes = svgfile and svgfile.svgshapes +            pdfshapes = svgshapes and otfsvg.topdf(svgshapes) or { } +            containers.write(otf.pdfcache, hash, { +                pdfshapes = pdfshapes, +                timestamp = timestamp, +            }) +        end +        if not pdfshapes or not next(pdfshapes) then +            return +        end +        -- +        properties.virtualized = true +        tfmdata.fonts = { +            { id = 0 } +        } +        -- +        local getactualtext = otf.getactualtext +        local storepdfdata  = otfsvg.storepdfdata +        -- +        local nop = { "nop" } +        -- +        for unicode, character in next, characters do +            local index = character.index +            if index then +                local pdf = pdfshapes[index] +                if pdf then +                    local setcode, name, nilcode = storepdfdata(pdf) +                    if name then +                        local bt, et = getactualtext(unicode) +                        local wd = character.width  or 0 +                        local ht = character.height or 0 +                        local dp = character.depth  or 0 +                        character.commands = { +                            { "special", "pdf:direct:" .. bt }, +                            { "down", dp }, +                            setcode and { "lua", setcode } or nop, +                            { "image", { filename = name, width = wd, height = ht, depth = dp } }, +                            nilcode and { "lua", nilcode } or nop, +                            { "special", "pdf:direct:" .. et }, +                        } +                        character.svg = true +                    end +                end +            end +        end +    end +end + +fonts.handlers.otf.features.register { +    name         = "svg", +    description  = "svg glyphs", +    manipulators = { +        base = initializesvg, +        node = initializesvg, +    } +} diff --git a/src/fontloader/misc/fontloader-font-afm.lua b/src/fontloader/misc/fontloader-font-one.lua index 0d6b7cb..a6f47e8 100644 --- a/src/fontloader/misc/fontloader-font-afm.lua +++ b/src/fontloader/misc/fontloader-font-one.lua @@ -1,4 +1,4 @@ -if not modules then modules = { } end modules ['font-afm'] = { +if not modules then modules = { } end modules ['font-one'] = {      version   = 1.001,      comment   = "companion to font-ini.mkiv",      author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL", @@ -7,33 +7,27 @@ if not modules then modules = { } end modules ['font-afm'] = {  }  --[[ldx-- -<p>Some code may look a bit obscure but this has to do with the -fact that we also use this code for testing and much code evolved -in the transition from <l n='tfm'/> to <l n='afm'/> to <l -n='otf'/>.</p> +<p>Some code may look a bit obscure but this has to do with the fact that we also use +this code for testing and much code evolved in the transition from <l n='tfm'/> to +<l n='afm'/> to <l n='otf'/>.</p> -<p>The following code still has traces of intermediate font support -where we handles font encodings. Eventually font encoding goes -away.</p> - -<p>The embedding of a font involves creating temporary files and -depending on your system setup that can fail. It took more than a -day to figure out why sometimes embedding failed in mingw luatex -where running on a real path like c:\... failed while running on -say e:\... being a link worked well. The native windows binaries -don't have this issue.</p> +<p>The following code still has traces of intermediate font support where we handles +font encodings. Eventually font encoding went away but we kept some code around in +other modules.</p> +<p>This version implements a node mode approach so that users can also more easily +add features.</p>  --ldx]]--  local fonts, logs, trackers, containers, resolvers = fonts, logs, trackers, containers, resolvers -local next, type, tonumber = next, type, tonumber +local next, type, tonumber, rawget = next, type, tonumber, rawget  local match, gmatch, lower, gsub, strip, find = string.match, string.gmatch, string.lower, string.gsub, string.strip, string.find  local char, byte, sub = string.char, string.byte, string.sub  local abs = math.abs  local bxor, rshift = bit32.bxor, bit32.rshift -local P, S, R, Cmt, C, Ct, Cs, lpegmatch, patterns = lpeg.P, lpeg.S, lpeg.R, lpeg.Cmt, lpeg.C, lpeg.Ct, lpeg.Cs, lpeg.match, lpeg.patterns -local derivetable = table.derive +local P, S, R, Cmt, C, Ct, Cs, Carg = lpeg.P, lpeg.S, lpeg.R, lpeg.Cmt, lpeg.C, lpeg.Ct, lpeg.Cs, lpeg.Carg +local lpegmatch, patterns = lpeg.match, lpeg.patterns  local trace_features     = false  trackers.register("afm.features",   function(v) trace_features = v end)  local trace_indexing     = false  trackers.register("afm.indexing",   function(v) trace_indexing = v end) @@ -43,6 +37,7 @@ local trace_defining     = false  trackers.register("fonts.defining", function(v  local report_afm         = logs.reporter("fonts","afm loading")  local setmetatableindex  = table.setmetatableindex +local derivetable        = table.derive  local findbinfile        = resolvers.findbinfile @@ -50,434 +45,67 @@ local definers           = fonts.definers  local readers            = fonts.readers  local constructors       = fonts.constructors -local afm                = constructors.newhandler("afm") -local pfb                = constructors.newhandler("pfb") +local afm                = constructors.handlers.afm +local pfb                = constructors.handlers.pfb +local otf                = fonts.handlers.otf + +local otfreaders         = otf.readers +local otfenhancers       = otf.enhancers -local afmfeatures        = constructors.newfeatures("afm") +local afmfeatures        = constructors.features.afm  local registerafmfeature = afmfeatures.register -afm.version              = 1.501 -- incrementing this number one up will force a re-cache +afm.version              = 1.512 -- incrementing this number one up will force a re-cache  afm.cache                = containers.define("fonts", "afm", afm.version, true)  afm.autoprefixed         = true -- this will become false some day (catches texnansi-blabla.*)  afm.helpdata             = { }  -- set later on so no local for this  afm.syncspace            = true -- when true, nicer stretch values -afm.addligatures         = true -- best leave this set to true -afm.addtexligatures      = true -- best leave this set to true -afm.addkerns             = true -- best leave this set to true  local overloads          = fonts.mappings.overloads  local applyruntimefixes  = fonts.treatments and fonts.treatments.applyfixes -local function setmode(tfmdata,value) -    if value then -        tfmdata.properties.mode = lower(value) -    end -end - -registerafmfeature { -    name         = "mode", -    description  = "mode", -    initializers = { -        base = setmode, -        node = setmode, -    } -} -  --[[ldx-- -<p>We start with the basic reader which we give a name similar to the -built in <l n='tfm'/> and <l n='otf'/> reader.</p> ---ldx]]-- - ---~ Comment FONTIDENTIFIER LMMATHSYMBOLS10 ---~ Comment CODINGSCHEME TEX MATH SYMBOLS ---~ Comment DESIGNSIZE 10.0 pt ---~ Comment CHECKSUM O 4261307036 ---~ Comment SPACE 0 plus 0 minus 0 ---~ Comment QUAD 1000 ---~ Comment EXTRASPACE 0 ---~ Comment NUM 676.508 393.732 443.731 ---~ Comment DENOM 685.951 344.841 ---~ Comment SUP 412.892 362.892 288.889 ---~ Comment SUB 150 247.217 ---~ Comment SUPDROP 386.108 ---~ Comment SUBDROP 50 ---~ Comment DELIM 2390 1010 ---~ Comment AXISHEIGHT 250 - -local comment = P("Comment") -local spacing = patterns.spacer  -- S(" \t")^1 -local lineend = patterns.newline -- S("\n\r") -local words   = C((1 - lineend)^1) -local number  = C((R("09") + S("."))^1) / tonumber * spacing^0 -local data    = lpeg.Carg(1) - -local pattern = ( -- needs testing ... not used anyway as we no longer need math afm's -    comment * spacing * -        ( -            data * ( -                ("CODINGSCHEME" * spacing * words                                      ) / function(fd,a)                                      end + -                ("DESIGNSIZE"   * spacing * number * words                             ) / function(fd,a)     fd[ 1]                 = a       end + -                ("CHECKSUM"     * spacing * number * words                             ) / function(fd,a)     fd[ 2]                 = a       end + -                ("SPACE"        * spacing * number * "plus" * number * "minus" * number) / function(fd,a,b,c) fd[ 3], fd[ 4], fd[ 5] = a, b, c end + -                ("QUAD"         * spacing * number                                     ) / function(fd,a)     fd[ 6]                 = a       end + -                ("EXTRASPACE"   * spacing * number                                     ) / function(fd,a)     fd[ 7]                 = a       end + -                ("NUM"          * spacing * number * number * number                   ) / function(fd,a,b,c) fd[ 8], fd[ 9], fd[10] = a, b, c end + -                ("DENOM"        * spacing * number * number                            ) / function(fd,a,b  ) fd[11], fd[12]         = a, b    end + -                ("SUP"          * spacing * number * number * number                   ) / function(fd,a,b,c) fd[13], fd[14], fd[15] = a, b, c end + -                ("SUB"          * spacing * number * number                            ) / function(fd,a,b)   fd[16], fd[17]         = a, b    end + -                ("SUPDROP"      * spacing * number                                     ) / function(fd,a)     fd[18]                 = a       end + -                ("SUBDROP"      * spacing * number                                     ) / function(fd,a)     fd[19]                 = a       end + -                ("DELIM"        * spacing * number * number                            ) / function(fd,a,b)   fd[20], fd[21]         = a, b    end + -                ("AXISHEIGHT"   * spacing * number                                     ) / function(fd,a)     fd[22]                 = a       end -            ) -          + (1-lineend)^0 -        ) -  + (1-comment)^1 -)^0 - -local function scan_comment(str) -    local fd = { } -    lpegmatch(pattern,str,1,fd) -    return fd -end - --- On a rainy day I will rewrite this in lpeg ... or we can use the (slower) fontloader --- as in now supports afm/pfb loading but it's not too bad to have different methods --- for testing approaches. - -local keys = { } - -function keys.FontName    (data,line) data.metadata.fontname    = strip    (line) -- get rid of spaces -                                      data.metadata.fullname    = strip    (line) end -function keys.ItalicAngle (data,line) data.metadata.italicangle = tonumber (line) end -function keys.IsFixedPitch(data,line) data.metadata.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) -    line = lower(line) -    local designsize = match(line,"designsize[^%d]*(%d+)") -    if designsize then data.metadata.designsize = tonumber(designsize) end -end - -local function get_charmetrics(data,charmetrics,vector) -    local characters = data.characters -    local chr, ind = { }, 0 -    for k, v in gmatch(charmetrics,"([%a]+) +(.-) *;") do -        if k == 'C'  then -            v = tonumber(v) -            if v < 0 then -                ind = ind + 1 -- ? -            else -                ind = v -            end -            chr = { -                index = ind -            } -        elseif k == 'WX' then -            chr.width = tonumber(v) -        elseif k == 'N'  then -            characters[v] = chr -        elseif k == 'B'  then -            local llx, lly, urx, ury = match(v,"^ *(.-) +(.-) +(.-) +(.-)$") -            chr.boundingbox = { tonumber(llx), tonumber(lly), tonumber(urx), tonumber(ury) } -        elseif k == 'L'  then -            local plus, becomes = match(v,"^(.-) +(.-)$") -            local ligatures = chr.ligatures -            if ligatures then -                ligatures[plus] = becomes -            else -                chr.ligatures = { [plus] = becomes } -            end -        end -    end -end - -local function get_kernpairs(data,kernpairs) -    local characters = data.characters -    for one, two, value in gmatch(kernpairs,"KPX +(.-) +(.-) +(.-)\n") do -        local chr = characters[one] -        if chr then -            local kerns = chr.kerns -            if kerns then -                kerns[two] = tonumber(value) -            else -                chr.kerns = { [two] = tonumber(value) } -            end -        end -    end -end - -local function get_variables(data,fontmetrics) -    for key, rest in gmatch(fontmetrics,"(%a+) *(.-)[\n\r]") do -        local keyhandler = keys[key] -        if keyhandler then -            keyhandler(data,rest) -        end -    end -end - -local get_indexes - -do - -    -- old font loader - -    local fontloader      = fontloader -    local get_indexes_old = false - -    if fontloader then - -        local font_to_table = fontloader.to_table -        local open_font     = fontloader.open -        local close_font    = fontloader.close - -        get_indexes_old = function(data,pfbname) -            local pfbblob = open_font(pfbname) -            if pfbblob then -                local characters = data.characters -                local pfbdata = font_to_table(pfbblob) -                if pfbdata then -                    local glyphs = pfbdata.glyphs -                    if glyphs then -                        if trace_loading then -                            report_afm("getting index data from %a",pfbname) -                        end -                        for index, glyph in next, glyphs do -                            local name = glyph.name -                            if name then -                                local char = characters[name] -                                if char then -                                    if trace_indexing then -                                        report_afm("glyph %a has index %a",name,index) -                                    end -                                    char.index = index -                                end -                            end -                        end -                    elseif trace_loading then -                        report_afm("no glyph data in pfb file %a",pfbname) -                    end -                elseif trace_loading then -                    report_afm("no data in pfb file %a",pfbname) -                end -                close_font(pfbblob) -            elseif trace_loading then -                report_afm("invalid pfb file %a",pfbname) -            end -        end - -    end - -    -- new (unfinished) font loader but i see no differences between -    -- old and new (one bad vector with old) - -    local n, m - -    local progress = function(str,position,name,size) -        local forward = position + tonumber(size) + 3 + 2 -        n = n + 1 -        if n >= m then -            return #str, name -        elseif forward < #str then -            return forward, name -        else -            return #str, name -        end -    end - -    local initialize = function(str,position,size) -        n = 0 -        m = tonumber(size) -        return position + 1 -    end - -    local charstrings = P("/CharStrings") -    local name        = P("/") * C((R("az")+R("AZ")+R("09")+S("-_."))^1) -    local size        = C(R("09")^1) -    local spaces      = P(" ")^1 - -    local p_filternames = Ct ( -        (1-charstrings)^0 * charstrings * spaces * Cmt(size,initialize) -      * (Cmt(name * P(" ")^1 * C(R("09")^1), progress) + P(1))^1 -    ) - -    -- if one of first 4 not 0-9A-F then binary else hex - -    local decrypt - -    do - -        local r, c1, c2, n = 0, 0, 0, 0 - -        local function step(c) -            local cipher = byte(c) -            local plain  = bxor(cipher,rshift(r,8)) -            r = ((cipher + r) * c1 + c2) % 65536 -            return char(plain) -        end - -        decrypt = function(binary) -            r, c1, c2, n = 55665, 52845, 22719, 4 -            binary       = gsub(binary,".",step) -            return sub(binary,n+1) -        end - -     -- local pattern = Cs((P(1) / step)^1) -     -- -     -- decrypt = function(binary) -     --     r, c1, c2, n = 55665, 52845, 22719, 4 -     --     binary = lpegmatch(pattern,binary) -     --     return sub(binary,n+1) -     -- end - -    end - -    local function loadpfbvector(filename) -        -- for the moment limited to encoding only - -        local data = io.loaddata(resolvers.findfile(filename)) - -        if not find(data,"!PS%-AdobeFont%-") then -            print("no font",filename) -            return -        end - -        if not data then -            print("no data",filename) -            return -        end - -        local ascii, binary = match(data,"(.*)eexec%s+......(.*)") - -        if not binary then -            print("no binary",filename) -            return -        end - -        binary = decrypt(binary,4) - -        local vector = lpegmatch(p_filternames,binary) - -        vector[0] = table.remove(vector,1) - -        if not vector then -            print("no vector",filename) -            return -        end - -        return vector - -    end - -    get_indexes = function(data,pfbname) -        local vector = loadpfbvector(pfbname) -        if vector then -            local characters = data.characters -            if trace_loading then -                report_afm("getting index data from %a",pfbname) -            end -            for index=1,#vector do -                local name = vector[index] -                local char = characters[name] -                if char then -                    if trace_indexing then -                        report_afm("glyph %a has index %a",name,index) -                    end -                    char.index = index -                end -            end -        end -    end +<p>We cache files. Caching is taken care of in the loader. We cheat a bit by adding +ligatures and kern information to the afm derived data. That way we can set them faster +when defining a font.</p> -    if get_indexes_old then - -        afm.use_new_indexer = true -        get_indexes_new     = get_indexes - -        get_indexes = function(data,pfbname) -            if afm.use_new_indexer then -                return get_indexes_new(data,pfbname) -            else -                return get_indexes_old(data,pfbname) -            end -        end +<p>We still keep the loading two phased: first we load the data in a traditional +fashion and later we transform it to sequences. Then we apply some methods also +used in opentype fonts (like <t>tlig</t>).</p> +--ldx]]-- -    end +local enhancers = { +    -- It's cleaner to implement them after we've seen what we are +    -- dealing with. +} -end +local steps     = { +    "unify names", +    "add ligatures", +    "add extra kerns", +    "normalize features", +    "fix names", +--  "add tounicode data", +} -local function readafm(filename) -    local ok, afmblob, size = resolvers.loadbinfile(filename) -- has logging -    if ok and afmblob then -        local data = { -            resources = { -                filename = resolvers.unresolve(filename), -                version  = afm.version, -                creator  = "context mkiv", -            }, -            properties = { -                hasitalics = false, -            }, -            goodies = { -            }, -            metadata   = { -                filename = file.removesuffix(file.basename(filename)) -            }, -            characters = { -                -- a temporary store -            }, -            descriptions = { -                -- the final store -            }, -        } -        afmblob = gsub(afmblob,"StartCharMetrics(.-)EndCharMetrics", function(charmetrics) +local function applyenhancers(data,filename) +    for i=1,#steps do +        local step     = steps[i] +        local enhancer = enhancers[step] +        if enhancer then              if trace_loading then -                report_afm("loading char metrics") +                report_afm("applying enhancer %a",step)              end -            get_charmetrics(data,charmetrics,vector) -            return "" -        end) -        afmblob = gsub(afmblob,"StartKernPairs(.-)EndKernPairs", function(kernpairs) -            if trace_loading then -                report_afm("loading kern pairs") -            end -            get_kernpairs(data,kernpairs) -            return "" -        end) -        afmblob = gsub(afmblob,"StartFontMetrics%s+([%d%.]+)(.-)EndFontMetrics", function(version,fontmetrics) -            if trace_loading then -                report_afm("loading variables") -            end -            data.afmversion = version -            get_variables(data,fontmetrics) -            data.fontdimens = scan_comment(fontmetrics) -- todo: all lpeg, no time now -            return "" -        end) -        return data -    else -        if trace_loading then -            report_afm("no valid afm file %a",filename) +            enhancer(data,filename) +        else +            report_afm("invalid enhancer %a",step)          end -        return nil      end  end ---[[ldx-- -<p>We cache files. Caching is taken care of in the loader. We cheat a bit -by adding ligatures and kern information to the afm derived data. That -way we can set them faster when defining a font.</p> ---ldx]]-- - -local addkerns, addligatures, addtexligatures, unify, normalize, fixnames -- we will implement these later -  function afm.load(filename) -    -- hm, for some reasons not resolved yet      filename = resolvers.findfile(filename,'afm') or ""      if filename ~= "" and not fonts.names.ignoredfile(filename) then          local name = file.removesuffix(file.basename(filename)) @@ -498,55 +126,42 @@ function afm.load(filename)          end          if not data or data.size ~= size or data.time ~= time or data.pfbsize ~= pfbsize or data.pfbtime ~= pfbtime then              report_afm("reading %a",filename) -            data = readafm(filename) +            data = afm.readers.loadfont(filename,pfbname)              if data then -                if pfbname ~= "" then -                    data.resources.filename = resolvers.unresolve(pfbname) -                    get_indexes(data,pfbname) -               elseif trace_loading then -                    report_afm("no pfb file for %a",filename) -                 -- data.resources.filename = "unset" -- better than loading the afm file -                end -                report_afm("unifying %a",filename) -                unify(data,filename) -                if afm.addligatures then -                    report_afm("add ligatures") -                    addligatures(data) -                end -                if afm.addtexligatures then -                    report_afm("add tex ligatures") -                    addtexligatures(data) -                end -                if afm.addkerns then -                    report_afm("add extra kerns") -                    addkerns(data) -                end -                normalize(data) -                fixnames(data) -                report_afm("add tounicode data") +                applyenhancers(data,filename) +             -- otfreaders.addunicodetable(data) -- only when not done yet                  fonts.mappings.addtounicode(data,filename) +             -- otfreaders.extend(data) +                otfreaders.pack(data)                  data.size = size                  data.time = time                  data.pfbsize = pfbsize                  data.pfbtime = pfbtime                  report_afm("saving %a in cache",name) -                data.resources.unicodes = nil -- consistent with otf but here we save not much +             -- data.resources.unicodes = nil -- consistent with otf but here we save not much                  data = containers.write(afm.cache, name, data)                  data = containers.read(afm.cache,name)              end -            if applyruntimefixes and data then +        end +        if data then +         -- constructors.addcoreunicodes(unicodes) +            otfreaders.unpack(data) +            otfreaders.expand(data) -- inline tables +            otfreaders.addunicodetable(data) -- only when not done yet +            otfenhancers.apply(data,filename,data) +            if applyruntimefixes then                  applyruntimefixes(filename,data)              end          end          return data -    else -        return nil      end  end -local uparser = fonts.mappings.makenameparser() +-- we run a more advanced analyzer later on anyway + +local uparser = fonts.mappings.makenameparser() -- each time -unify = function(data, filename) +enhancers["unify names"] = function(data, filename)      local unicodevector = fonts.encodings.agl.unicodes -- loaded runtime in context      local unicodes      = { }      local names         = { } @@ -556,7 +171,7 @@ unify = function(data, filename)          local code = unicodevector[name] -- or characters.name_to_unicode[name]          if not code then              code = lpegmatch(uparser,name) -            if not code then +            if type(code) ~= "number" then                  code = private                  private = private + 1                  report_afm("assigning private slot %U for unknown glyph name %a",code,name) @@ -602,15 +217,108 @@ end  local everywhere = { ["*"] = { ["*"] = true } } -- or: { ["*"] = { "*" } }  local noflags    = { false, false, false, false } -afm.experimental_normalize = false - -normalize = function(data) -    if type(afm.experimental_normalize) == "function" then -        afm.experimental_normalize(data) +enhancers["normalize features"] = function(data) +    local ligatures  = setmetatableindex("table") +    local kerns      = setmetatableindex("table") +    local extrakerns = setmetatableindex("table") +    for u, c in next, data.descriptions do +        local l = c.ligatures +        local k = c.kerns +        local e = c.extrakerns +        if l then +            ligatures[u] = l +            for u, v in next, l do +                l[u] = { ligature = v } +            end +            c.ligatures = nil +        end +        if k then +            kerns[u] = k +            for u, v in next, k do +                k[u] = v -- { v, 0 } +            end +            c.kerns = nil +        end +        if e then +            extrakerns[u] = e +            for u, v in next, e do +                e[u] = v -- { v, 0 } +            end +            c.extrakerns = nil +        end      end +    local features = { +        gpos = { }, +        gsub = { }, +    } +    local sequences = { +        -- only filled ones +    } +    if next(ligatures) then +        features.gsub.liga = everywhere +        data.properties.hasligatures = true +        sequences[#sequences+1] = { +            features = { +                liga = everywhere, +            }, +            flags    = noflags, +            name     = "s_s_0", +            nofsteps = 1, +            order    = { "liga" }, +            type     = "gsub_ligature", +            steps    = { +                { +                    coverage = ligatures, +                }, +            }, +        } +    end +    if next(kerns) then +        features.gpos.kern = everywhere +        data.properties.haskerns = true +        sequences[#sequences+1] = { +            features = { +                kern = everywhere, +            }, +            flags    = noflags, +            name     = "p_s_0", +            nofsteps = 1, +            order    = { "kern" }, +            type     = "gpos_pair", +            steps    = { +                { +                    format   = "kern", +                    coverage = kerns, +                }, +            }, +        } +    end +    if next(extrakerns) then +        features.gpos.extrakerns = everywhere +        data.properties.haskerns = true +        sequences[#sequences+1] = { +            features = { +                extrakerns = everywhere, +            }, +            flags    = noflags, +            name     = "p_s_1", +            nofsteps = 1, +            order    = { "extrakerns" }, +            type     = "gpos_pair", +            steps    = { +                { +                    format   = "kern", +                    coverage = extrakerns, +                }, +            }, +        } +    end +    -- todo: compress kerns +    data.resources.features  = features +    data.resources.sequences = sequences  end -fixnames = function(data) +enhancers["fix names"] = function(data)      for k, v in next, data.descriptions do          local n = v.name          local r = overloads[n] @@ -657,8 +365,13 @@ local addthem = function(rawdata,ligatures)      end  end -addligatures    = function(rawdata) addthem(rawdata,afm.helpdata.ligatures   ) end -addtexligatures = function(rawdata) addthem(rawdata,afm.helpdata.texligatures) end +enhancers["add ligatures"] = function(rawdata) +    addthem(rawdata,afm.helpdata.ligatures) +end + +-- enhancers["add tex ligatures"] = function(rawdata) +--     addthem(rawdata,afm.helpdata.texligatures) +-- end  --[[ldx--  <p>We keep the extra kerns in separate kerning tables so that we can use @@ -672,7 +385,7 @@ them selectively.</p>  -- we don't use the character database. (Ok, we can have a context specific  -- variant). -addkerns = function(rawdata) -- using shcodes is not robust here +enhancers["add extra kerns"] = function(rawdata) -- using shcodes is not robust here      local descriptions = rawdata.descriptions      local resources    = rawdata.resources      local unicodes     = resources.unicodes @@ -864,11 +577,31 @@ local function copytotfm(data)              end              --          end -        local fd = data.fontdimens -        if fd and fd[8] and fd[9] and fd[10] then -- math -            for k,v in next, fd do -                parameters[k] = v -            end +        -- +        if metadata.sup then +            local dummy    = { 0, 0, 0 } +            parameters[ 1] = metadata.designsize        or 0 +            parameters[ 2] = metadata.checksum          or 0 +            parameters[ 3], +            parameters[ 4], +            parameters[ 5] = unpack(metadata.space      or dummy) +            parameters[ 6] =        metadata.quad       or 0 +            parameters[ 7] =        metadata.extraspace or 0 +            parameters[ 8], +            parameters[ 9], +            parameters[10] = unpack(metadata.num        or dummy) +            parameters[11], +            parameters[12] = unpack(metadata.denom      or dummy) +            parameters[13], +            parameters[14], +            parameters[15] = unpack(metadata.sup        or dummy) +            parameters[16], +            parameters[17] = unpack(metadata.sub        or dummy) +            parameters[18] =        metadata.supdrop    or 0 +            parameters[19] =        metadata.subdrop    or 0 +            parameters[20], +            parameters[21] = unpack(metadata.delim      or dummy) +            parameters[22] =        metadata.axisheight or 0          end          --          parameters.designsize = (metadata.designsize or 10)*65536 @@ -900,10 +633,9 @@ local function copytotfm(data)  end  --[[ldx-- -<p>Originally we had features kind of hard coded for <l n='afm'/> -files but since I expect to support more font formats, I decided -to treat this fontformat like any other and handle features in a -more configurable way.</p> +<p>Originally we had features kind of hard coded for <l n='afm'/> files but since I +expect to support more font formats, I decided to treat this fontformat like any +other and handle features in a more configurable way.</p>  --ldx]]--  function afm.setfeatures(tfmdata,features) @@ -982,6 +714,8 @@ local function afmtotfm(specification)                          tfmdata.shared = shared                      end                      shared.rawdata   = rawdata +                    shared.dynamics  = { } +                    tfmdata.changed  = { }                      shared.features  = features                      shared.processes = afm.setfeatures(tfmdata,features)                  end @@ -1015,127 +749,34 @@ local function read_from_afm(specification)  end  --[[ldx-- -<p>Here comes the implementation of a few features. We only implement -those that make sense for this format.</p> +<p>We have the usual two modes and related features initializers and processors.</p>  --ldx]]-- -local function prepareligatures(tfmdata,ligatures,value) -    if value then -        local descriptions = tfmdata.descriptions -        local hasligatures = false -        for unicode, character in next, tfmdata.characters do -            local description = descriptions[unicode] -            local dligatures = description.ligatures -            if dligatures then -                local cligatures = character.ligatures -                if not cligatures then -                    cligatures = { } -                    character.ligatures = cligatures -                end -                for unicode, ligature in next, dligatures do -                    cligatures[unicode] = { -                        char = ligature, -                        type = 0 -                    } -                end -                hasligatures = true -            end -        end -        tfmdata.properties.hasligatures = hasligatures -    end -end - -local function preparekerns(tfmdata,kerns,value) +local function setmode(tfmdata,value)      if value then -        local rawdata      = tfmdata.shared.rawdata -        local resources    = rawdata.resources -        local unicodes     = resources.unicodes -        local descriptions = tfmdata.descriptions -        local haskerns     = false -        for u, chr in next, tfmdata.characters do -            local d = descriptions[u] -            local newkerns = d[kerns] -            if newkerns then -                local kerns = chr.kerns -                if not kerns then -                    kerns = { } -                    chr.kerns = kerns -                end -                for k,v in next, newkerns do -                    local uk = unicodes[k] -                    if uk then -                        kerns[uk] = v -                    end -                end -                haskerns = true -            end -        end -        tfmdata.properties.haskerns = haskerns -    end -end - -local list = { - -- [0x0022] = 0x201D, -    [0x0027] = 0x2019, - -- [0x0060] = 0x2018, -} - -local function texreplacements(tfmdata,value) -    local descriptions = tfmdata.descriptions -    local characters   = tfmdata.characters -    for k, v in next, list do -        characters  [k] = characters  [v] -- we forget about kerns -        descriptions[k] = descriptions[v] -- we forget about kerns +        tfmdata.properties.mode = lower(value)      end  end -local function ligatures   (tfmdata,value) prepareligatures(tfmdata,'ligatures',   value) end -local function texligatures(tfmdata,value) prepareligatures(tfmdata,'texligatures',value) end -local function kerns       (tfmdata,value) preparekerns    (tfmdata,'kerns',       value) end -local function extrakerns  (tfmdata,value) preparekerns    (tfmdata,'extrakerns',  value) end - -registerafmfeature { -    name         = "liga", -    description  = "traditional ligatures", -    initializers = { -        base = ligatures, -        node = ligatures, -    } -} - -registerafmfeature { -    name         = "kern", -    description  = "intercharacter kerning", -    initializers = { -        base = kerns, -        node = kerns, -    } -} - -registerafmfeature { -    name         = "extrakerns", -    description  = "additional intercharacter kerning", -    initializers = { -        base = extrakerns, -        node = extrakerns, -    } -} -  registerafmfeature { -    name         = 'tlig', -    description  = 'tex ligatures', +    name         = "mode", +    description  = "mode",      initializers = { -        base = texligatures, -        node = texligatures, +        base = setmode, +        node = setmode,      }  }  registerafmfeature { -    name         = 'trep', -    description  = 'tex replacements', +    name         = "features", +    description  = "features", +    default      = true,      initializers = { -        base = texreplacements, -        node = texreplacements, +        node     = otf.nodemodeinitializer, +        base     = otf.basemodeinitializer, +    }, +    processors   = { +        node     = otf.featuresprocessor,      }  } @@ -1171,7 +812,8 @@ local function check_afm(specification,fullname)  end  function readers.afm(specification,method) -    local fullname, tfmdata = specification.filename or "", nil +    local fullname = specification.filename or "" +    local tfmdata  = nil      if fullname == "" then          local forced = specification.forced or ""          if forced ~= "" then @@ -1200,7 +842,16 @@ function readers.pfb(specification,method) -- only called when forced      if trace_defining then          report_afm("using afm reader for %a",original)      end -    specification.specification = gsub(original,"%.pfb",".afm")      specification.forced = "afm" +    local function swap(name) +        local value = specification[swap] +        if value then +            specification[swap] = gsub("%.pfb",".afm",1) +        end +    end +    swap("filename") +    swap("fullname") +    swap("forcedname") +    swap("specification")      return readers.afm(specification,method)  end diff --git a/src/fontloader/misc/fontloader-font-onr.lua b/src/fontloader/misc/fontloader-font-onr.lua new file mode 100644 index 0000000..a4969ad --- /dev/null +++ b/src/fontloader/misc/fontloader-font-onr.lua @@ -0,0 +1,411 @@ +if not modules then modules = { } end modules ['font-onr'] = { +    version   = 1.001, +    comment   = "companion to font-ini.mkiv", +    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL", +    copyright = "PRAGMA ADE / ConTeXt Development Team", +    license   = "see context related readme files" +} + +--[[ldx-- +<p>Some code may look a bit obscure but this has to do with the fact that we also use +this code for testing and much code evolved in the transition from <l n='tfm'/> to +<l n='afm'/> to <l n='otf'/>.</p> + +<p>The following code still has traces of intermediate font support where we handles +font encodings. Eventually font encoding went away but we kept some code around in +other modules.</p> + +<p>This version implements a node mode approach so that users can also more easily +add features.</p> +--ldx]]-- + +local fonts, logs, trackers, resolvers = fonts, logs, trackers, resolvers + +local next, type, tonumber, rawget = next, type, tonumber, rawget +local match, lower, gsub, strip, find = string.match, string.lower, string.gsub, string.strip, string.find +local char, byte, sub = string.char, string.byte, string.sub +local abs = math.abs +local bxor, rshift = bit32.bxor, bit32.rshift +local P, S, R, Cmt, C, Ct, Cs, Carg = lpeg.P, lpeg.S, lpeg.R, lpeg.Cmt, lpeg.C, lpeg.Ct, lpeg.Cs, lpeg.Carg +local lpegmatch, patterns = lpeg.match, lpeg.patterns + +local trace_indexing     = false  trackers.register("afm.indexing",   function(v) trace_indexing = v end) +local trace_loading      = false  trackers.register("afm.loading",    function(v) trace_loading  = v end) + +local report_afm         = logs.reporter("fonts","afm loading") +local report_afm         = logs.reporter("fonts","pfb loading") + +fonts                    = fonts or { } +local handlers           = fonts.handlers or { } +fonts.handlers           = handlers +local afm                = handlers.afm or { } +handlers.afm             = afm +local readers            = afm.readers or { } +afm.readers              = readers + +afm.version              = 1.512 -- incrementing this number one up will force a re-cache + +--[[ldx-- +<p>We start with the basic reader which we give a name similar to the built in <l n='tfm'/> +and <l n='otf'/> reader.</p> +<p>We use a new (unfinished) pfb loader but I see no differences between the old +and new vectors (we actually had one bad vector with the old loader).</p> +--ldx]]-- + +local get_indexes + +do + +    local n, m + +    local progress = function(str,position,name,size) +        local forward = position + tonumber(size) + 3 + 2 +        n = n + 1 +        if n >= m then +            return #str, name +        elseif forward < #str then +            return forward, name +        else +            return #str, name +        end +    end + +    local initialize = function(str,position,size) +        n = 0 +        m = tonumber(size) +        return position + 1 +    end + +    local charstrings = P("/CharStrings") +    local name        = P("/") * C((R("az")+R("AZ")+R("09")+S("-_."))^1) +    local size        = C(R("09")^1) +    local spaces      = P(" ")^1 + +    local p_filternames = Ct ( +        (1-charstrings)^0 * charstrings * spaces * Cmt(size,initialize) +      * (Cmt(name * P(" ")^1 * C(R("09")^1), progress) + P(1))^1 +    ) + +    -- if one of first 4 not 0-9A-F then binary else hex + +    local decrypt + +    do + +        local r, c1, c2, n = 0, 0, 0, 0 + +        local function step(c) +            local cipher = byte(c) +            local plain  = bxor(cipher,rshift(r,8)) +            r = ((cipher + r) * c1 + c2) % 65536 +            return char(plain) +        end + +        decrypt = function(binary) +            r, c1, c2, n = 55665, 52845, 22719, 4 +            binary       = gsub(binary,".",step) +            return sub(binary,n+1) +        end + +     -- local pattern = Cs((P(1) / step)^1) +     -- +     -- decrypt = function(binary) +     --     r, c1, c2, n = 55665, 52845, 22719, 4 +     --     binary = lpegmatch(pattern,binary) +     --     return sub(binary,n+1) +     -- end + +    end + +    local function loadpfbvector(filename) +        -- for the moment limited to encoding only + +        local data = io.loaddata(resolvers.findfile(filename)) + +        if not data then +            report_pfb("no data in %a",filename) +            return +        end + +        if not (find(data,"!PS%-AdobeFont%-") or find(data,"%%!FontType1")) then +            report_pfb("no font in %a",filename) +            return +        end + +        local ascii, binary = match(data,"(.*)eexec%s+......(.*)") + +        if not binary then +            report_pfb("no binary data in %a",filename) +            return +        end + +        binary = decrypt(binary,4) + +        local vector = lpegmatch(p_filternames,binary) + +        if vector[1] == ".notdef" then +            -- tricky +            vector[0] = table.remove(vector,1) +        end + +        if not vector then +            report_pfb("no vector in %a",filename) +            return +        end + +        return vector + +    end + +    get_indexes = function(data,pfbname) +        local vector = loadpfbvector(pfbname) +        if vector then +            local characters = data.characters +            if trace_loading then +                report_afm("getting index data from %a",pfbname) +            end +            for index=1,#vector do +                local name = vector[index] +                local char = characters[name] +                if char then +                    if trace_indexing then +                        report_afm("glyph %a has index %a",name,index) +                    end +                    char.index = index +                end +            end +        end +    end + +end + +--[[ldx-- +<p>We start with the basic reader which we give a name similar to the built in <l n='tfm'/> +and <l n='otf'/> reader. We only need data that is relevant for our use. We don't support +more complex arrangements like multiple master (obsolete), direction specific kerning, etc.</p> +--ldx]]-- + +local spacer     = patterns.spacer +local whitespace = patterns.whitespace +local lineend    = patterns.newline +local spacing    = spacer^0 +local number     = spacing * S("+-")^-1 * (R("09") + S("."))^1 / tonumber +local name       = spacing * C((1 - whitespace)^1) +local words      = spacing * ((1 - lineend)^1 / strip) +local rest       = (1 - lineend)^0 +local fontdata   = Carg(1) +local semicolon  = spacing * P(";") +local plus       = spacing * P("plus") * number +local minus      = spacing * P("minus") * number + +-- kern pairs + +local function addkernpair(data,one,two,value) +    local chr = data.characters[one] +    if chr then +        local kerns = chr.kerns +        if kerns then +            kerns[two] = tonumber(value) +        else +            chr.kerns = { [two] = tonumber(value) } +        end +    end +end + +local p_kernpair = (fontdata * P("KPX") * name * name * number) / addkernpair + +-- char metrics + +local chr = false +local ind = 0 + +local function start(data,version) +    data.metadata.afmversion = version +    ind = 0 +    chr = { } +end + +local function stop() +    ind = 0 +    chr = false +end + +local function setindex(i) +    if i < 0 then +        ind = ind + 1 -- ? +    else +        ind = i +    end +    chr = { +        index = ind +    } +end + +local function setwidth(width) +    chr.width = width +end + +local function setname(data,name) +    data.characters[name] = chr +end + +local function setboundingbox(boundingbox) +    chr.boundingbox = boundingbox +end + +local function setligature(plus,becomes) +    local ligatures = chr.ligatures +    if ligatures then +        ligatures[plus] = becomes +    else +        chr.ligatures = { [plus] = becomes } +    end +end + +local p_charmetric = ( ( +    P("C")  * number          / setindex +  + P("WX") * number          / setwidth +  + P("N")  * fontdata * name / setname +  + P("B")  * Ct((number)^4)  / setboundingbox +  + P("L")  * (name)^2        / setligature +  ) * semicolon )^1 + +local p_charmetrics = P("StartCharMetrics") * number * (p_charmetric + (1-P("EndCharMetrics")))^0 * P("EndCharMetrics") +local p_kernpairs   = P("StartKernPairs")   * number * (p_kernpair   + (1-P("EndKernPairs"  )))^0 * P("EndKernPairs"  ) + +local function set_1(data,key,a)     data.metadata[lower(key)] = a           end +local function set_2(data,key,a,b)   data.metadata[lower(key)] = { a, b }    end +local function set_3(data,key,a,b,c) data.metadata[lower(key)] = { a, b, c } end + +-- Notice         string +-- EncodingScheme string +-- MappingScheme  integer +-- EscChar        integer +-- CharacterSet   string +-- Characters     integer +-- IsBaseFont     boolean +-- VVector        number number +-- IsFixedV       boolean + +local p_parameters = P(false) +  + fontdata +  * ((P("FontName") + P("FullName") + P("FamilyName"))/lower) +  * words / function(data,key,value) +        data.metadata[key] = value +    end +  + fontdata +  * ((P("Weight") + P("Version"))/lower) +  * name / function(data,key,value) +        data.metadata[key] = value +    end +  + fontdata +  * P("IsFixedPitch") +  * name / function(data,pitch) +        data.metadata.monospaced = toboolean(pitch,true) +    end +  + fontdata +  * P("FontBBox") +  * Ct(number^4) / function(data,boundingbox) +        data.metadata.boundingbox = boundingbox +  end +  + fontdata +  * ((P("CharWidth") + P("CapHeight") + P("XHeight") + P("Descender") + P("Ascender") + P("ItalicAngle"))/lower) +  * number / function(data,key,value) +        data.metadata[key] = value +    end +  + P("Comment") * spacing * ( P(false) +      + (fontdata * C("DESIGNSIZE")     * number                   * rest) / set_1 -- 1 +      + (fontdata * C("TFM designsize") * number                   * rest) / set_1 +      + (fontdata * C("DesignSize")     * number                   * rest) / set_1 +      + (fontdata * C("CODINGSCHEME")   * words                    * rest) / set_1 -- +      + (fontdata * C("CHECKSUM")       * number * words           * rest) / set_1 -- 2 +      + (fontdata * C("SPACE")          * number * plus * minus    * rest) / set_3 -- 3 4 5 +      + (fontdata * C("QUAD")           * number                   * rest) / set_1 -- 6 +      + (fontdata * C("EXTRASPACE")     * number                   * rest) / set_1 -- 7 +      + (fontdata * C("NUM")            * number * number * number * rest) / set_3 -- 8 9 10 +      + (fontdata * C("DENOM")          * number * number          * rest) / set_2 -- 11 12 +      + (fontdata * C("SUP")            * number * number * number * rest) / set_3 -- 13 14 15 +      + (fontdata * C("SUB")            * number * number          * rest) / set_2 -- 16 17 +      + (fontdata * C("SUPDROP")        * number                   * rest) / set_1 -- 18 +      + (fontdata * C("SUBDROP")        * number                   * rest) / set_1 -- 19 +      + (fontdata * C("DELIM")          * number * number          * rest) / set_2 -- 20 21 +      + (fontdata * C("AXISHEIGHT")     * number                   * rest) / set_1 -- 22 +    ) + +local fullparser = ( P("StartFontMetrics") * fontdata * name / start ) +                 * ( p_charmetrics + p_kernpairs + p_parameters + (1-P("EndFontMetrics")) )^0 +                 * ( P("EndFontMetrics") / stop ) + +local fullparser = ( P("StartFontMetrics") * fontdata * name / start ) +                 * ( p_charmetrics + p_kernpairs + p_parameters + (1-P("EndFontMetrics")) )^0 +                 * ( P("EndFontMetrics") / stop ) + +local infoparser = ( P("StartFontMetrics") * fontdata * name / start ) +                 * ( p_parameters + (1-P("EndFontMetrics")) )^0 +                 * ( P("EndFontMetrics") / stop ) + +--    infoparser = ( P("StartFontMetrics") * fontdata * name / start ) +--               * ( p_parameters + (1-P("EndFontMetrics") - P("StartCharMetrics")) )^0 +--               * ( (P("EndFontMetrics") + P("StartCharMetrics")) / stop ) + +local function read(filename,parser) +    local afmblob = io.loaddata(filename) +    if afmblob then +        local data = { +            resources = { +                filename = resolvers.unresolve(filename), +                version  = afm.version, +                creator  = "context mkiv", +            }, +            properties = { +                hasitalics = false, +            }, +            goodies = { +            }, +            metadata   = { +                filename = file.removesuffix(file.basename(filename)) +            }, +            characters = { +                -- a temporary store +            }, +            descriptions = { +                -- the final store +            }, +        } +        if trace_loading then +            report_afm("parsing afm file %a",filename) +        end +        lpegmatch(parser,afmblob,1,data) +        return data +    else +        if trace_loading then +            report_afm("no valid afm file %a",filename) +        end +        return nil +    end +end + +function readers.loadfont(afmname,pfbname) +    local data = read(resolvers.findfile(afmname),fullparser) +    if data then +        if not pfbname or pfbname == "" then +            pfbname = file.replacesuffix(file.nameonly(afmname),"pfb") +            pfbname = resolvers.findfile(pfbname) +        end +        if pfbname and pfbname ~= "" then +            data.resources.filename = resolvers.unresolve(pfbname) +            get_indexes(data,pfbname) +        elseif trace_loading then +            report_afm("no pfb file for %a",afmname) +         -- data.resources.filename = "unset" -- better than loading the afm file +        end +        return data +    end +end + +function readers.getinfo(filename) +    local data = read(resolvers.findfile(filename),infoparser) +    if data then +        return data.metadata +    end +end diff --git a/src/fontloader/misc/fontloader-font-osd.lua b/src/fontloader/misc/fontloader-font-osd.lua index 6ff2e38..a3dda67 100644 --- a/src/fontloader/misc/fontloader-font-osd.lua +++ b/src/fontloader/misc/fontloader-font-osd.lua @@ -79,13 +79,10 @@ fonts.analyzers.methods  = fonts.analyzers.methods or { node = { otf = { } } }  local otf                = fonts.handlers.otf -local nodecodes          = nodes.nodecodes -local glyph_code         = nodecodes.glyph -  local handlers           = otf.handlers  local methods            = fonts.analyzers.methods -local otffeatures        = fonts.constructors.newfeatures("otf") +local otffeatures        = fonts.constructors.features.otf  local registerotffeature = otffeatures.register  local nuts               = nodes.nuts diff --git a/src/fontloader/misc/fontloader-font-ota.lua b/src/fontloader/misc/fontloader-font-ota.lua index 6a3804a..4ddb831 100644 --- a/src/fontloader/misc/fontloader-font-ota.lua +++ b/src/fontloader/misc/fontloader-font-ota.lua @@ -44,7 +44,6 @@ local getchar             = nuts.getchar  local ischar              = nuts.is_char  local traverse_id         = nuts.traverse_id -local traverse_node_list  = nuts.traverse  local end_of_math         = nuts.end_of_math  local nodecodes           = nodes.nodecodes @@ -56,7 +55,7 @@ local fontdata            = fonts.hashes.identifiers  local categories          = characters and characters.categories or { } -- sorry, only in context  local chardata            = characters and characters.data -local otffeatures         = fonts.constructors.newfeatures("otf") +local otffeatures         = fonts.constructors.features.otf  local registerotffeature  = otffeatures.register  --[[ldx-- diff --git a/src/fontloader/misc/fontloader-font-otd.lua b/src/fontloader/misc/fontloader-font-otd.lua index 2257caa..64cb1bc 100644 --- a/src/fontloader/misc/fontloader-font-otd.lua +++ b/src/fontloader/misc/fontloader-font-otd.lua @@ -36,9 +36,6 @@ local contextmerged      = specifiers.contextmerged  local setmetatableindex  = table.setmetatableindex -local otffeatures        = fonts.constructors.newfeatures("otf") -local registerotffeature = otffeatures.register -  local a_to_script        = { }  local a_to_language      = { } @@ -135,6 +132,10 @@ local wildcard = "*"  -- needs checking: some added features can pass twice +local P, C, Cc, lpegmatch = lpeg.P, lpeg.C, lpeg.Cc, lpeg.match + +local pattern = P("always") * (P(-1) * Cc(true) + P(":") * C((1-P(-1))^1)) +  local function initialize(sequence,script,language,s_enabled,a_enabled,font,attr,dynamic,ra,autoscript,autolanguage)      local features = sequence.features      if features then @@ -151,21 +152,34 @@ local function initialize(sequence,script,language,s_enabled,a_enabled,font,attr                      e_e = s_enabled and s_enabled[kind] -- the value (font)                  end                  if e_e then -                    local scripts = features[kind] -- -                    local languages = scripts[script] or scripts[wildcard] -                    if not languages and autoscript then -                        langages = defaultscript(featuretype,autoscript,scripts) -                    end -                    if languages then -                        -- we need detailed control over default becase we want to trace -                        -- only first attribute match check, so we assume simple fina's -                        local valid = false -                        if languages[language] then -                            valid = e_e -                        elseif languages[wildcard] then -                            valid = e_e -                        elseif autolanguage and defaultlanguage(featuretype,autolanguage,languages) then -                            valid = e_e +                    local valid = type(e_e) == "string" and lpegmatch(pattern,e_e) +                    if valid then +                        -- we have hit always +                        local attribute = autofeatures[kind] or false +                        if trace_applied then +                            report_process( +                                "font %s, dynamic %a (%a), feature %a, script %a, language %a, lookup %a, value %a", +                                    font,attr or 0,dynamic,kind,"*","*",sequence.name,valid) +                        end +                        ra[#ra+1] = { valid, attribute, sequence, kind } +                    else +                        -- we already checked for e_e +                        local scripts   = features[kind] -- +                        local languages = scripts[script] or scripts[wildcard] +                        if not languages and autoscript then +                            langages = defaultscript(featuretype,autoscript,scripts) +                        end +                        if languages then +                            -- we need detailed control over default becase we want to trace +                            -- only first attribute match check, so we assume simple fina's +                         -- local valid = false +                            if languages[language] then +                                valid = e_e +                            elseif languages[wildcard] then +                                valid = e_e +                            elseif autolanguage and defaultlanguage(featuretype,autolanguage,languages) then +                                valid = e_e +                            end                          end                          if valid then                              local attribute = autofeatures[kind] or false @@ -244,6 +258,7 @@ function otf.dataset(tfmdata,font,attr) -- attr only when explicit (as in specia              local autoscript   = (s_enabled and s_enabled.autoscript  ) or (a_enabled and a_enabled.autoscript  )              local autolanguage = (s_enabled and s_enabled.autolanguage) or (a_enabled and a_enabled.autolanguage)              for s=1,#sequences do +                -- just return nil or ra step                  initialize(sequences[s],script,language,s_enabled,a_enabled,font,attr,dynamic,ra,autoscript,autolanguage)              end          end diff --git a/src/fontloader/misc/fontloader-font-oti.lua b/src/fontloader/misc/fontloader-font-oti.lua index bacd001..d74d2d5 100644 --- a/src/fontloader/misc/fontloader-font-oti.lua +++ b/src/fontloader/misc/fontloader-font-oti.lua @@ -11,8 +11,8 @@ local lower = string.lower  local fonts              = fonts  local constructors       = fonts.constructors -local otf                = constructors.newhandler("otf") -local otffeatures        = constructors.newfeatures("otf") +local otf                = constructors.handlers.otf +local otffeatures        = constructors.features.otf  local registerotffeature = otffeatures.register  local otftables          = otf.tables or { } diff --git a/src/fontloader/misc/fontloader-font-otj.lua b/src/fontloader/misc/fontloader-font-otj.lua index 6ff80d8..d1408fd 100644 --- a/src/fontloader/misc/fontloader-font-otj.lua +++ b/src/fontloader/misc/fontloader-font-otj.lua @@ -24,10 +24,11 @@ if not modules then modules = { } end modules ['font-otj'] = {  -- The use_advance code is just a test and is meant for testing and manuals. There is no  -- performance (or whatever) gain and using kerns is somewhat cleaner (at least for now). +-- Maybe: subtype fontkern when pure kerns. +  if not nodes.properties then return end  local next, rawget = next, rawget -local utfchar = utf.char  local fastcopy = table.fastcopy  local registertracker = trackers.register @@ -92,7 +93,6 @@ local traverse_id        = nuts.traverse_id  local traverse_char      = nuts.traverse_char  local insert_node_before = nuts.insert_before  local insert_node_after  = nuts.insert_after -local find_tail          = nuts.tail  local properties         = nodes.properties.data @@ -755,7 +755,7 @@ local function inject_pairs_only(head,where)                          end                          local leftkern = i.leftkern                          if leftkern and leftkern ~= 0 then -                            insert_node_before(head,current,newkern(leftkern)) +                            head = insert_node_before(head,current,newkern(leftkern))                          end                          local rightkern = i.rightkern                          if rightkern and rightkern ~= 0 then @@ -1255,11 +1255,11 @@ local function inject_everything(head,where)                                  insert_node_after(pre,n,newkern(rightkern))                                  done = true                              end -                        end -                        if hasmarks then -                            local pm = i.markbasenode -                            if pm then -                                processmark(pm,current,i) +                            if hasmarks then +                                local pm = i.markbasenode +                                if pm then +                                    processmark(pm,current,i) +                                end                              end                          end                      end @@ -1287,11 +1287,11 @@ local function inject_everything(head,where)                                  insert_node_after(post,n,newkern(rightkern))                                  done = true                              end -                        end -                        if hasmarks then -                            local pm = i.markbasenode -                            if pm then -                                processmark(pm,current,i) +                            if hasmarks then +                                local pm = i.markbasenode +                                if pm then +                                    processmark(pm,current,i) +                                end                              end                          end                      end @@ -1319,11 +1319,11 @@ local function inject_everything(head,where)                                  insert_node_after(replace,n,newkern(rightkern))                                  done = true                              end -                        end -                        if hasmarks then -                            local pm = i.markbasenode -                            if pm then -                                processmark(pm,current,i) +                            if hasmarks then +                                local pm = i.markbasenode +                                if pm then +                                    processmark(pm,current,i) +                                end                              end                          end                      end @@ -1523,10 +1523,19 @@ function injections.handler(head,where)          head = injectspaces(head)      end      if nofregisteredmarks > 0 or nofregisteredcursives > 0 then +        if trace_injections then +            report_injections("injection variant %a","everything") +        end          return inject_everything(head,where)      elseif nofregisteredpairs > 0 then +        if trace_injections then +            report_injections("injection variant %a","pairs") +        end          return inject_pairs_only(head,where)      elseif nofregisteredkerns > 0 then +        if trace_injections then +            report_injections("injection variant %a","kerns") +        end          return inject_kerns_only(head,where)      else          return head, false diff --git a/src/fontloader/misc/fontloader-font-otl.lua b/src/fontloader/misc/fontloader-font-otl.lua index f7b6eb5..c7c278a 100644 --- a/src/fontloader/misc/fontloader-font-otl.lua +++ b/src/fontloader/misc/fontloader-font-otl.lua @@ -53,8 +53,12 @@ local report_otf         = logs.reporter("fonts","otf loading")  local fonts              = fonts  local otf                = fonts.handlers.otf -otf.version              = 3.019 -- beware: also sync font-mis.lua and in mtx-fonts +otf.version              = 3.022 -- beware: also sync font-mis.lua and in mtx-fonts  otf.cache                = containers.define("fonts", "otl", otf.version, true) +otf.svgcache             = containers.define("fonts", "svg", otf.version, true) +otf.pdfcache             = containers.define("fonts", "pdf", otf.version, true) + +otf.svgenabled           = false  local otfreaders         = otf.readers @@ -63,7 +67,7 @@ local definers           = fonts.definers  local readers            = fonts.readers  local constructors       = fonts.constructors -local otffeatures        = constructors.newfeatures("otf") +local otffeatures        = constructors.features.otf  local registerotffeature = otffeatures.register  local enhancers          = allocate() @@ -101,6 +105,12 @@ registerdirective("fonts.otf.loader.forcenotdef",   function(v) forcenotdef   =  --     end  -- end +-- Enhancers are used to apply fixes and extensions to fonts. For instance, we use them +-- to implement tlig and trep features. They are not neccessarily bound to opentype +-- fonts but can also apply to type one fonts, given that they obey the structure of an +-- opentype font. They are not to be confused with format specific features but maybe +-- some are so generic that they might eventually move to this mechanism. +  local ordered_enhancers = {      "check extra features",  } @@ -264,6 +274,25 @@ function otf.load(filename,sub,featurefile) -- second argument (format) is gone          --          --          if data then +            -- +            local resources = data.resources +            local svgshapes = resources.svgshapes +            if svgshapes then +                resources.svgshapes = nil +                if otf.svgenabled then +                    local timestamp = os.date() +                    -- work in progress ... a bit boring to do +                    containers.write(otf.svgcache,hash, { +                        svgshapes = svgshapes, +                        timestamp = timestamp, +                    }) +                    data.properties.svg = { +                        hash      = hash, +                        timestamp = timestamp, +                    } +                end +            end +            --              otfreaders.compact(data)              otfreaders.rehash(data,"unicodes")              otfreaders.addunicodetable(data) @@ -302,7 +331,7 @@ function otf.load(filename,sub,featurefile) -- second argument (format) is gone          --          enhancers.apply(data,filename,data)          -- -        constructors.addcoreunicodes(unicodes) +     -- constructors.addcoreunicodes(data.resources.unicodes) -- still needed ?          --          if applyruntimefixes then              applyruntimefixes(filename,data) @@ -340,7 +369,6 @@ end  local function copytotfm(data,cache_id)      if data then          local metadata       = data.metadata -        local resources      = data.resources          local properties     = derivetable(data.properties)          local descriptions   = derivetable(data.descriptions)          local goodies        = derivetable(data.goodies) diff --git a/src/fontloader/misc/fontloader-font-oto.lua b/src/fontloader/misc/fontloader-font-oto.lua index b7ee717..1199778 100644 --- a/src/fontloader/misc/fontloader-font-oto.lua +++ b/src/fontloader/misc/fontloader-font-oto.lua @@ -14,8 +14,6 @@ local concat, unpack = table.concat, table.unpack  local insert, remove = table.insert, table.remove  local format, gmatch, gsub, find, match, lower, strip = string.format, string.gmatch, string.gsub, string.find, string.match, string.lower, string.strip  local type, next, tonumber, tostring, rawget = type, next, tonumber, tostring, rawget -local lpegmatch = lpeg.match -local utfchar = utf.char  local trace_baseinit         = false  trackers.register("otf.baseinit",         function(v) trace_baseinit         = v end)  local trace_singles          = false  trackers.register("otf.singles",          function(v) trace_singles          = v end) @@ -450,3 +448,5 @@ registerotffeature {          base     = featuresinitializer,      }  } + +otf.basemodeinitializer = featuresinitializer diff --git a/src/fontloader/misc/fontloader-font-otr.lua b/src/fontloader/misc/fontloader-font-otr.lua index 24f6854..7d0bf04 100644 --- a/src/fontloader/misc/fontloader-font-otr.lua +++ b/src/fontloader/misc/fontloader-font-otr.lua @@ -99,7 +99,7 @@ readers.streamreader    = streamreader  local openfile          = streamreader.open  local closefile         = streamreader.close -local skipbytes         = streamreader.skip +----- skipbytes         = streamreader.skip  local setposition       = streamreader.setposition  local skipshort         = streamreader.skipshort  local readbytes         = streamreader.readbytes @@ -108,7 +108,7 @@ local readbyte          = streamreader.readcardinal1  --  8-bit unsigned integer  local readushort        = streamreader.readcardinal2  -- 16-bit unsigned integer  local readuint          = streamreader.readcardinal3  -- 24-bit unsigned integer  local readulong         = streamreader.readcardinal4  -- 24-bit unsigned integer -local readchar          = streamreader.readinteger1   --  8-bit   signed integer +----- readchar          = streamreader.readinteger1   --  8-bit   signed integer  local readshort         = streamreader.readinteger2   -- 16-bit   signed integer  local readlong          = streamreader.readinteger4   -- 24-bit unsigned integer  local readfixed         = streamreader.readfixed4 @@ -129,12 +129,11 @@ local function readlongdatetime(f)      return 0x100000000 * d + 0x1000000 * e + 0x10000 * f + 0x100 * g + h  end -local tableversion      = 0.004 -local privateoffset     = fonts.constructors and fonts.constructors.privateoffset or 0xF0000 -- 0x10FFFF +local tableversion    = 0.004 +readers.tableversion  = tableversion +local privateoffset   = fonts.constructors and fonts.constructors.privateoffset or 0xF0000 -- 0x10FFFF +local reportedskipped = { } -readers.tableversion    = tableversion - -local reportedskipped   = { }  local function reportskippedtable(tag)      if not reportedskipped[tag] then @@ -1657,6 +1656,26 @@ function readers.glyf(f,fontdata,specification) -- part goes to cff module      end  end +-- Experimental (we need fonts). + +function readers.colr(f,fontdata,specification) +    if specification.glyphs then +        reportskippedtable("colr") +    end +end + +function readers.cpal(f,fontdata,specification) +    if specification.glyphs then +        reportskippedtable("cpal") +    end +end + +function readers.svg(f,fontdata,specification) +    if specification.glyphs then +        reportskippedtable("svg") +    end +end +  -- Here we have a table that we really need for later processing although a more advanced gpos table  -- can also be available. Todo: we need a 'fake' lookup for this (analogue to ff). @@ -1874,6 +1893,7 @@ local function getinfo(maindata,sub,platformnames,rawfamilynames)           -- format         = fontdata.format,              fontname       = fontname,              fullname       = fullname, +         -- cfffullname    = cff.fullname,              family         = family,              subfamily      = subfamily,              familyname     = familyname, @@ -1996,6 +2016,9 @@ local function readdata(f,offset,specification)      readers["cmap"](f,fontdata,specification)      readers["loca"](f,fontdata,specification)      readers["glyf"](f,fontdata,specification) +    readers["colr"](f,fontdata,specification) +    readers["cpal"](f,fontdata,specification) +    readers["svg" ](f,fontdata,specification)      readers["kern"](f,fontdata,specification)      readers["gdef"](f,fontdata,specification)      readers["gsub"](f,fontdata,specification) @@ -2164,7 +2187,9 @@ function readers.loadfont(filename,n)              goodies       = { },              metadata      = getinfo(fontdata,n), -- no platformnames here !              properties    = { -                hasitalics = fontdata.hasitalics or false, +                hasitalics    = fontdata.hasitalics or false, +                maxcolorclass = fontdata.maxcolorclass, +                hascolor      = fontdata.hascolor or false,              },              resources     = {               -- filename      = fontdata.filename, @@ -2181,6 +2206,8 @@ function readers.loadfont(filename,n)                  version       = getname(fontdata,"version"),                  cidinfo       = fontdata.cidinfo,                  mathconstants = fontdata.mathconstants, +                colorpalettes = fontdata.colorpalettes, +                svgshapes     = fontdata.svgshapes,              },          }      end diff --git a/src/fontloader/misc/fontloader-font-ots.lua b/src/fontloader/misc/fontloader-font-ots.lua index 21225c2..51704bf 100644 --- a/src/fontloader/misc/fontloader-font-ots.lua +++ b/src/fontloader/misc/fontloader-font-ots.lua @@ -124,7 +124,7 @@ local trace_cursive      = false  registertracker("otf.cursive",      function(v  local trace_preparing    = false  registertracker("otf.preparing",    function(v) trace_preparing    = v end)  local trace_bugs         = false  registertracker("otf.bugs",         function(v) trace_bugs         = v end)  local trace_details      = false  registertracker("otf.details",      function(v) trace_details      = v end) -local trace_applied      = false  registertracker("otf.applied",      function(v) trace_applied      = v end) +----- trace_applied      = false  registertracker("otf.applied",      function(v) trace_applied      = v end)  local trace_steps        = false  registertracker("otf.steps",        function(v) trace_steps        = v end)  local trace_skips        = false  registertracker("otf.skips",        function(v) trace_skips        = v end)  local trace_directions   = false  registertracker("otf.directions",   function(v) trace_directions   = v end) @@ -145,10 +145,8 @@ local report_direct   = logs.reporter("fonts","otf direct")  local report_subchain = logs.reporter("fonts","otf subchain")  local report_chain    = logs.reporter("fonts","otf chain")  local report_process  = logs.reporter("fonts","otf process") ------ report_prepare  = logs.reporter("fonts","otf prepare")  local report_warning  = logs.reporter("fonts","otf warning")  local report_run      = logs.reporter("fonts","otf run") -local report_check    = logs.reporter("fonts","otf check")  registertracker("otf.replacements", "otf.singles,otf.multiples,otf.alternatives,otf.ligatures")  registertracker("otf.positions","otf.marks,otf.kerns,otf.cursive") @@ -185,10 +183,7 @@ local setlink            = nuts.setlink  local ischar             = nuts.is_char -local insert_node_before = nuts.insert_before  local insert_node_after  = nuts.insert_after -local delete_node        = nuts.delete -local remove_node        = nuts.remove  local copy_node          = nuts.copy  local copy_node_list     = nuts.copy_list  local find_node_tail     = nuts.tail @@ -244,7 +239,7 @@ local cursonce           = true  local fonthashes         = fonts.hashes  local fontdata           = fonthashes.identifiers -local otffeatures        = fonts.constructors.newfeatures("otf") +local otffeatures        = fonts.constructors.features.otf  local registerotffeature = otffeatures.register  local onetimemessage     = fonts.loggers.onetimemessage or function() end @@ -3064,7 +3059,7 @@ local function c_run_single(head,font,attr,lookupcache,step,dataset,sequence,rlm      while start do          local char = ischar(start,font)          if char then -            local a = getattr(start,0) +            local a = attr and getattr(start,0)              if not a or (a == attr) then                  local lookupmatch = lookupcache[char]                  if lookupmatch then @@ -3097,7 +3092,7 @@ local function t_run_single(start,stop,font,attr,lookupcache)      while start ~= stop do          local char = ischar(start,font)          if char then -            local a = getattr(start,0) +            local a = attr and getattr(start,0)              if not a or (a == attr) then                  local lookupmatch = lookupcache[char]                  if lookupmatch then -- hm, hyphens can match (tlig) so we need to really check @@ -3132,7 +3127,7 @@ local function t_run_single(start,stop,font,attr,lookupcache)  end  -- local function d_run_single(prev,font,attr,lookupcache,step,dataset,sequence,rlmode,handler) ---     local a = getattr(prev,0) +--     local a = attr and getattr(prev,0)  --     if not a or (a == attr) then  --         local char = ischar(prev) -- can be disc  --         if char then @@ -3149,7 +3144,7 @@ end  -- end  local function k_run_single(sub,injection,last,font,attr,lookupcache,step,dataset,sequence,rlmode,handler) -    local a = getattr(sub,0) +    local a = attr and getattr(sub,0)      if not a or (a == attr) then          for n in traverse_nodes(sub) do -- only gpos              if n == last then @@ -3181,7 +3176,7 @@ local function c_run_multiple(head,font,attr,steps,nofsteps,dataset,sequence,rlm      while start do          local char = ischar(start,font)          if char then -            local a = getattr(start,0) +            local a = attr and getattr(start,0)              if not a or (a == attr) then                  for i=1,nofsteps do                      local step        = steps[i] @@ -3228,7 +3223,7 @@ local function t_run_multiple(start,stop,font,attr,steps,nofsteps)      while start ~= stop do          local char = ischar(start,font)          if char then -            local a = getattr(start,0) +            local a = attr and getattr(start,0)              if not a or (a == attr) then                  for i=1,nofsteps do                      local step = steps[i] @@ -3271,7 +3266,7 @@ local function t_run_multiple(start,stop,font,attr,steps,nofsteps)  end  -- local function d_run_multiple(prev,attr,steps,nofsteps,dataset,sequence,rlmode,handler) ---     local a = getattr(prev,0) +--     local a = attr and getattr(prev,0)  --     if not a or (a == attr) then  --         local char = ischar(prev) -- can be disc  --         if char then @@ -3297,7 +3292,7 @@ end  -- end  local function k_run_multiple(sub,injection,last,font,attr,steps,nofsteps,dataset,sequence,rlmode,handler) -    local a = getattr(sub,0) +    local a = attr and getattr(sub,0)      if not a or (a == attr) then          for n in traverse_nodes(sub) do -- only gpos              if n == last then @@ -3394,6 +3389,14 @@ local function featuresprocessor(head,font,attr)      end +    -- some 10% faster when no dynamics but hardly measureable on real runs .. but: it only +    -- works when we have no other dynamics as otherwise the zero run will be applied to the +    -- whole stream for which we then need to pass another variable which we won't + +    -- if attr == 0 then +    --     attr = false +    -- end +      head = tonut(head)      if trace_steps then @@ -3405,7 +3408,7 @@ local function featuresprocessor(head,font,attr)      local done      = false      local datasets  = otf.dataset(tfmdata,font,attr) -    local dirstack  = { } -- could move outside function btu we can have local runss +    local dirstack  = { } -- could move outside function but we can have local runs      sweephead       = { } @@ -3451,7 +3454,7 @@ local function featuresprocessor(head,font,attr)              while start do                  local char = ischar(start,font)                  if char then -                    local a = getattr(start,0) +                    local a = attr and getattr(start,0)                      if not a or (a == attr) then                          for i=1,nofsteps do                              local step = steps[i] @@ -3485,18 +3488,15 @@ local function featuresprocessor(head,font,attr)              local start = head -- local ?              rlmode = 0 -- to be checked ?              if nofsteps == 1 then -- happens often -                  local step = steps[1]                  local lookupcache = step.coverage                  if not lookupcache then -                 -- can't happen, no check in loop either                      report_missing_coverage(dataset,sequence)                  else -                      while start do                          local char, id = ischar(start,font)                          if char then -                            local a = getattr(start,0) +                            local a = attr and getattr(start,0)                              if a then                                  a = (a == attr) and (not attribute or getprop(start,a_state) == attribute)                              else @@ -3553,7 +3553,7 @@ local function featuresprocessor(head,font,attr)                  while start do                      local char, id = ischar(start,font)                      if char then -                        local a = getattr(start,0) +                        local a = attr and getattr(start,0)                          if a then                              a = (a == attr) and (not attribute or getprop(start,a_state) == attribute)                          else @@ -3652,6 +3652,9 @@ registerotffeature {      }  } +otf.nodemodeinitializer = featuresinitializer +otf.featuresprocessor   = featuresprocessor +  -- This can be used for extra handlers, but should be used with care!  otf.handlers = handlers -- used in devanagari diff --git a/src/fontloader/misc/fontloader-font-oup.lua b/src/fontloader/misc/fontloader-font-oup.lua index 3b6d8ea..e2d209a 100644 --- a/src/fontloader/misc/fontloader-font-oup.lua +++ b/src/fontloader/misc/fontloader-font-oup.lua @@ -586,7 +586,8 @@ local function checklookups(fontdata,missing,nofmissing)          local done = { }          for i, r in next, missing do              if r then -                local name = descriptions[i].name or f_index(i) +                local data = descriptions[i] +                local name = data and data.name or f_index(i)                  if not ignore[name] then                      done[name] = true                  end @@ -706,6 +707,19 @@ local function unifyglyphs(fontdata,usenames)          end      end      -- +    local colorpalettes = resources.colorpalettes +    if colorpalettes then +        for index=1,#glyphs do +            local colors = glyphs[index].colors +            if colors then +                for i=1,#colors do +                    local c = colors[i] +                    c.slot = indices[c.slot] +                end +            end +        end +    end +    --      fontdata.private      = private      fontdata.glyphs       = nil      fontdata.names        = names @@ -1158,6 +1172,7 @@ function readers.pack(data)          local sequences  = resources.sequences          local sublookups = resources.sublookups          local features   = resources.features +        local palettes   = resources.colorpalettes          local chardata     = characters and characters.data          local descriptions = data.descriptions or data.glyphs @@ -1190,6 +1205,14 @@ function readers.pack(data)                          end                      end                  end +             -- if palettes then +             --     local color = description.color +             --     if color then +             --         for i=1,#color do +             --             color[i] = pack_normal(color[i]) +             --         end +             --     end +             -- end              end              local function packthem(sequences) @@ -1314,6 +1337,16 @@ function readers.pack(data)                  end              end +            if palettes then +                for i=1,#palettes do +                    local p = palettes[i] +                    for j=1,#p do +                        p[j] = pack_indexed(p[j]) +                    end +                end + +            end +              if not success(1,pass) then                  return              end @@ -1461,6 +1494,7 @@ function readers.unpack(data)              local sequences    = resources.sequences              local sublookups   = resources.sublookups              local features     = resources.features +            local palettes     = resources.colorpalettes              local unpacked     = { }              setmetatable(unpacked,unpacked_mt)              for unicode, description in next, descriptions do @@ -1487,6 +1521,17 @@ function readers.unpack(data)                          end                      end                  end +             -- if palettes then +             --     local color = description.color +             --     if color then +             --         for i=1,#color do +             --             local tv = tables[color[i]] +             --             if tv then +             --                 color[i] = tv +             --             end +             --         end +             --     end +             -- end              end              local function unpackthem(sequences) @@ -1716,6 +1761,18 @@ function readers.unpack(data)                  end              end +            if palettes then +                for i=1,#palettes do +                    local p = palettes[i] +                    for j=1,#p do +                        local tv = tables[p[j]] +                        if tv then +                            p[j] = tv +                        end +                    end +                end +            end +              data.tables = nil          end      end diff --git a/src/fontloader/misc/fontloader-font-tfm.lua b/src/fontloader/misc/fontloader-font-tfm.lua index 8e92c48..ab6d795 100644 --- a/src/fontloader/misc/fontloader-font-tfm.lua +++ b/src/fontloader/misc/fontloader-font-tfm.lua @@ -23,13 +23,13 @@ local readers                  = fonts.readers  local constructors             = fonts.constructors  local encodings                = fonts.encodings -local tfm                      = constructors.newhandler("tfm") +local tfm                      = constructors.handlers.tfm  tfm.version                    = 1.000  tfm.maxnestingdepth            = 5  tfm.maxnestingsize             = 65536*1024 -local tfmfeatures              = constructors.newfeatures("tfm") -local registertfmfeature       = tfmfeatures.register +local tfmfeatures              = constructors.features.tfm +----- registertfmfeature       = tfmfeatures.register  constructors.resolvevirtualtoo = false -- wil be set in font-ctx.lua diff --git a/src/fontloader/misc/fontloader-fonts-ext.lua b/src/fontloader/misc/fontloader-fonts-ext.lua index b60d045..9d8d307 100644 --- a/src/fontloader/misc/fontloader-fonts-ext.lua +++ b/src/fontloader/misc/fontloader-fonts-ext.lua @@ -12,7 +12,7 @@ if context then  end  local fonts       = fonts -local otffeatures = fonts.constructors.newfeatures("otf") +local otffeatures = fonts.constructors.features.otf  -- A few generic extensions. diff --git a/src/fontloader/misc/fontloader-fonts.lua b/src/fontloader/misc/fontloader-fonts.lua index e1ec376..83d52d9 100644 --- a/src/fontloader/misc/fontloader-fonts.lua +++ b/src/fontloader/misc/fontloader-fonts.lua @@ -186,6 +186,7 @@ if non_generic_context.luatex_fonts.skip_loading ~= true then          loadmodule("l-file.lua")          loadmodule("l-boolean.lua")          loadmodule("l-math.lua") +        loadmodule("l-unicode.lua")          -- A few slightly higher level support modules: @@ -230,8 +231,6 @@ if non_generic_context.luatex_fonts.skip_loading ~= true then          loadmodule('luatex-fonts-syn.lua')          loadmodule('font-tfm.lua') -        loadmodule('font-afm.lua') -        loadmodule('font-afk.lua')          loadmodule('font-oti.lua')          -- These are the old loader and processing modules. These use the built-in font loader and @@ -259,6 +258,13 @@ if non_generic_context.luatex_fonts.skip_loading ~= true then          loadmodule('font-ota.lua')          loadmodule('font-ots.lua')          loadmodule('font-osd.lua') +        loadmodule('font-ocl.lua') -- svg needs 0.97 (for fix in memstreams) + +        -- type one code + +        loadmodule('font-onr.lua') -- was font-afm.lua +        loadmodule('font-one.lua') -- was font-afm.lua +        loadmodule('font-afk.lua')          -- common code diff --git a/src/fontloader/misc/fontloader-math.tex b/src/fontloader/misc/fontloader-math.tex index 604b4a1..b249021 100644 --- a/src/fontloader/misc/fontloader-math.tex +++ b/src/fontloader/misc/fontloader-math.tex @@ -40,9 +40,9 @@     \font\tenbf   = file:lmroman10-bold.otf:+liga;+kern;+tlig;+trep         at 10pt     \font\tenbi   = file:lmroman10-bolditalic.otf:+liga;+kern;+tlig;+trep   at 10pt     % -   \font\mathfonttextupright         = file:latinmodern-math.otf:ssty=0;fixmath=yes at 10pt -   \font\mathfontscriptupright       = file:latinmodern-math.otf:ssty=1;fixmath=yes at  7pt -   \font\mathfontscriptscriptupright = file:latinmodern-math.otf:ssty=2;fixmath=yes at  5pt +   \font\mathfonttextupright         = file:latinmodern-math.otf:script=math;ssty=0;mathsize=yes at 10pt +   \font\mathfontscriptupright       = file:latinmodern-math.otf:script=math;ssty=1;mathsize=yes at  7pt +   \font\mathfontscriptscriptupright = file:latinmodern-math.otf:script=math;ssty=2;mathsize=yes at  5pt     %     \textfont         0 = \mathfonttextupright     \scriptfont       0 = \mathfontscriptupright @@ -61,9 +61,9 @@     \font\tenbf   = file:lucidabrightot-demi.otf:+liga;+kern;+tlig;+trep       at 10pt     \font\tenbi   = file:lucidabrightot-demiitalic.otf:+liga;+kern;+tlig;+trep at 10pt     % -   \font\mathfonttextupright         = file:lucidabrightmathot.otf:ssty=0;fixmath=yes at 10pt -   \font\mathfontscriptupright       = file:lucidabrightmathot.otf:ssty=1;fixmath=yes at  7pt -   \font\mathfontscriptscriptupright = file:lucidabrightmathot.otf:ssty=2;fixmath=yes at  5pt +   \font\mathfonttextupright         = file:lucidabrightmathot.otf:script=math;ssty=0;mathsize=yes at 10pt +   \font\mathfontscriptupright       = file:lucidabrightmathot.otf:script=math;ssty=1;mathsize=yes at  7pt +   \font\mathfontscriptscriptupright = file:lucidabrightmathot.otf:script=math;ssty=2;mathsize=yes at  5pt     %     \textfont         0 = \mathfonttextupright     \scriptfont       0 = \mathfontscriptupright diff --git a/src/fontloader/runtime/fontloader-reference.lua b/src/fontloader/runtime/fontloader-reference.lua index 5f35ded..6f96d97 100644 --- a/src/fontloader/runtime/fontloader-reference.lua +++ b/src/fontloader/runtime/fontloader-reference.lua @@ -1,6 +1,6 @@  -- merged file : c:/data/develop/context/sources/luatex-fonts-merged.lua  -- parent file : c:/data/develop/context/sources/luatex-fonts.lua --- merge date  : 05/01/16 09:52:32 +-- merge date  : 06/13/16 17:00:29  do -- begin closure to overcome local limits and interference @@ -2785,6 +2785,620 @@ end -- closure  do -- begin closure to overcome local limits and interference +if not modules then modules={} end modules ['l-unicode']={ +  version=1.001, +  comment="companion to luat-lib.mkiv", +  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", +  copyright="PRAGMA ADE / ConTeXt Development Team", +  license="see context related readme files" +} +utf=utf or (unicode and unicode.utf8) or {} +utf.characters=utf.characters or string.utfcharacters +utf.values=utf.values   or string.utfvalues +local type=type +local char,byte,format,sub,gmatch=string.char,string.byte,string.format,string.sub,string.gmatch +local concat=table.concat +local P,C,R,Cs,Ct,Cmt,Cc,Carg,Cp=lpeg.P,lpeg.C,lpeg.R,lpeg.Cs,lpeg.Ct,lpeg.Cmt,lpeg.Cc,lpeg.Carg,lpeg.Cp +local lpegmatch=lpeg.match +local patterns=lpeg.patterns +local tabletopattern=lpeg.utfchartabletopattern +local bytepairs=string.bytepairs +local finder=lpeg.finder +local replacer=lpeg.replacer +local utfvalues=utf.values +local utfgmatch=utf.gmatch  +local p_utftype=patterns.utftype +local p_utfstricttype=patterns.utfstricttype +local p_utfoffset=patterns.utfoffset +local p_utf8char=patterns.utf8character +local p_utf8byte=patterns.utf8byte +local p_utfbom=patterns.utfbom +local p_newline=patterns.newline +local p_whitespace=patterns.whitespace +if not unicode then +  unicode={ utf=utf }  +end +if not utf.char then +  local floor,char=math.floor,string.char +  function utf.char(n) +    if n<0x80 then +      return char(n) +    elseif n<0x800 then +      return char( +        0xC0+floor(n/0x40), +        0x80+(n%0x40) +      ) +    elseif n<0x10000 then +      return char( +        0xE0+floor(n/0x1000), +        0x80+(floor(n/0x40)%0x40), +        0x80+(n%0x40) +      ) +    elseif n<0x200000 then +      return char( +        0xF0+floor(n/0x40000), +        0x80+(floor(n/0x1000)%0x40), +        0x80+(floor(n/0x40)%0x40), +        0x80+(n%0x40) +      ) +    else +      return "" +    end +  end +end +if not utf.byte then +  local utf8byte=patterns.utf8byte +  function utf.byte(c) +    return lpegmatch(utf8byte,c) +  end +end +local utfchar,utfbyte=utf.char,utf.byte +function utf.filetype(data) +  return data and lpegmatch(p_utftype,data) or "unknown" +end +local toentities=Cs ( +  ( +    patterns.utf8one+( +        patterns.utf8two+patterns.utf8three+patterns.utf8four +      )/function(s) local b=utfbyte(s) if b<127 then return s else return format("&#%X;",b) end end +  )^0 +) +patterns.toentities=toentities +function utf.toentities(str) +  return lpegmatch(toentities,str) +end +local one=P(1) +local two=C(1)*C(1) +local four=C(R(utfchar(0xD8),utfchar(0xFF)))*C(1)*C(1)*C(1) +local pattern=P("\254\255")*Cs(( +          four/function(a,b,c,d) +                local ab=0xFF*byte(a)+byte(b) +                local cd=0xFF*byte(c)+byte(d) +                return utfchar((ab-0xD800)*0x400+(cd-0xDC00)+0x10000) +              end+two/function(a,b) +                return utfchar(byte(a)*256+byte(b)) +              end+one +        )^1 )+P("\255\254")*Cs(( +          four/function(b,a,d,c) +                local ab=0xFF*byte(a)+byte(b) +                local cd=0xFF*byte(c)+byte(d) +                return utfchar((ab-0xD800)*0x400+(cd-0xDC00)+0x10000) +              end+two/function(b,a) +                return utfchar(byte(a)*256+byte(b)) +              end+one +        )^1 ) +function string.toutf(s)  +  return lpegmatch(pattern,s) or s  +end +local validatedutf=Cs ( +  ( +    patterns.utf8one+patterns.utf8two+patterns.utf8three+patterns.utf8four+P(1)/"�" +  )^0 +) +patterns.validatedutf=validatedutf +function utf.is_valid(str) +  return type(str)=="string" and lpegmatch(validatedutf,str) or false +end +if not utf.len then +  local n,f=0,1 +  local utfcharcounter=patterns.utfbom^-1*Cmt ( +    Cc(1)*patterns.utf8one^1+Cc(2)*patterns.utf8two^1+Cc(3)*patterns.utf8three^1+Cc(4)*patterns.utf8four^1, +    function(_,t,d)  +      n=n+(t-f)/d +      f=t +      return true +    end +  )^0 +  function utf.len(str) +    n,f=0,1 +    lpegmatch(utfcharcounter,str or "") +    return n +  end +end +utf.length=utf.len +if not utf.sub then +  local utflength=utf.length +  local b,e,n,first,last=0,0,0,0,0 +  local function slide_zero(s,p) +    n=n+1 +    if n>=last then +      e=p-1 +    else +      return p +    end +  end +  local function slide_one(s,p) +    n=n+1 +    if n==first then +      b=p +    end +    if n>=last then +      e=p-1 +    else +      return p +    end +  end +  local function slide_two(s,p) +    n=n+1 +    if n==first then +      b=p +    else +      return true +    end +  end +  local pattern_zero=Cmt(p_utf8char,slide_zero)^0 +  local pattern_one=Cmt(p_utf8char,slide_one )^0 +  local pattern_two=Cmt(p_utf8char,slide_two )^0 +  local pattern_first=C(patterns.utf8character) +  function utf.sub(str,start,stop) +    if not start then +      return str +    end +    if start==0 then +      start=1 +    end +    if not stop then +      if start<0 then +        local l=utflength(str)  +        start=l+start +      else +        start=start-1 +      end +      b,n,first=0,0,start +      lpegmatch(pattern_two,str) +      if n>=first then +        return sub(str,b) +      else +        return "" +      end +    end +    if start<0 or stop<0 then +      local l=utf.length(str) +      if start<0 then +        start=l+start +        if start<=0 then +          start=1 +        else +          start=start+1 +        end +      end +      if stop<0 then +        stop=l+stop +        if stop==0 then +          stop=1 +        else +          stop=stop+1 +        end +      end +    end +    if start==1 and stop==1 then +      return lpegmatch(pattern_first,str) or "" +    elseif start>stop then +      return "" +    elseif start>1 then +      b,e,n,first,last=0,0,0,start-1,stop +      lpegmatch(pattern_one,str) +      if n>=first and e==0 then +        e=#str +      end +      return sub(str,b,e) +    else +      b,e,n,last=1,0,0,stop +      lpegmatch(pattern_zero,str) +      if e==0 then +        e=#str +      end +      return sub(str,b,e) +    end +  end +end +function utf.remapper(mapping,option,action)  +  local variant=type(mapping) +  if variant=="table" then +    action=action or mapping +    if option=="dynamic" then +      local pattern=false +      table.setmetatablenewindex(mapping,function(t,k,v) rawset(t,k,v) pattern=false end) +      return function(str) +        if not str or str=="" then +          return "" +        else +          if not pattern then +            pattern=Cs((tabletopattern(mapping)/action+p_utf8char)^0) +          end +          return lpegmatch(pattern,str) +        end +      end +    elseif option=="pattern" then +      return Cs((tabletopattern(mapping)/action+p_utf8char)^0) +    else +      local pattern=Cs((tabletopattern(mapping)/action+p_utf8char)^0) +      return function(str) +        if not str or str=="" then +          return "" +        else +          return lpegmatch(pattern,str) +        end +      end,pattern +    end +  elseif variant=="function" then +    if option=="pattern" then +      return Cs((p_utf8char/mapping+p_utf8char)^0) +    else +      local pattern=Cs((p_utf8char/mapping+p_utf8char)^0) +      return function(str) +        if not str or str=="" then +          return "" +        else +          return lpegmatch(pattern,str) +        end +      end,pattern +    end +  else +    return function(str) +      return str or "" +    end +  end +end +function utf.replacer(t)  +  local r=replacer(t,false,false,true) +  return function(str) +    return lpegmatch(r,str) +  end +end +function utf.subtituter(t)  +  local f=finder (t) +  local r=replacer(t,false,false,true) +  return function(str) +    local i=lpegmatch(f,str) +    if not i then +      return str +    elseif i>#str then +      return str +    else +      return lpegmatch(r,str) +    end +  end +end +local utflinesplitter=p_utfbom^-1*lpeg.tsplitat(p_newline) +local utfcharsplitter_ows=p_utfbom^-1*Ct(C(p_utf8char)^0) +local utfcharsplitter_iws=p_utfbom^-1*Ct((p_whitespace^1+C(p_utf8char))^0) +local utfcharsplitter_raw=Ct(C(p_utf8char)^0) +patterns.utflinesplitter=utflinesplitter +function utf.splitlines(str) +  return lpegmatch(utflinesplitter,str or "") +end +function utf.split(str,ignorewhitespace)  +  if ignorewhitespace then +    return lpegmatch(utfcharsplitter_iws,str or "") +  else +    return lpegmatch(utfcharsplitter_ows,str or "") +  end +end +function utf.totable(str)  +  return lpegmatch(utfcharsplitter_raw,str) +end +function utf.magic(f)  +  local str=f:read(4) or "" +  local off=lpegmatch(p_utfoffset,str) +  if off<4 then +    f:seek('set',off) +  end +  return lpegmatch(p_utftype,str) +end +local utf16_to_utf8_be,utf16_to_utf8_le +local utf32_to_utf8_be,utf32_to_utf8_le +local utf_16_be_getbom=patterns.utfbom_16_be^-1 +local utf_16_le_getbom=patterns.utfbom_16_le^-1 +local utf_32_be_getbom=patterns.utfbom_32_be^-1 +local utf_32_le_getbom=patterns.utfbom_32_le^-1 +local utf_16_be_linesplitter=utf_16_be_getbom*lpeg.tsplitat(patterns.utf_16_be_nl) +local utf_16_le_linesplitter=utf_16_le_getbom*lpeg.tsplitat(patterns.utf_16_le_nl) +local utf_32_be_linesplitter=utf_32_be_getbom*lpeg.tsplitat(patterns.utf_32_be_nl) +local utf_32_le_linesplitter=utf_32_le_getbom*lpeg.tsplitat(patterns.utf_32_le_nl) +local more=0 +local p_utf16_to_utf8_be=C(1)*C(1)/function(left,right) +  local now=256*byte(left)+byte(right) +  if more>0 then +    now=(more-0xD800)*0x400+(now-0xDC00)+0x10000  +    more=0 +    return utfchar(now) +  elseif now>=0xD800 and now<=0xDBFF then +    more=now +    return ""  +  else +    return utfchar(now) +  end +end +local p_utf16_to_utf8_le=C(1)*C(1)/function(right,left) +  local now=256*byte(left)+byte(right) +  if more>0 then +    now=(more-0xD800)*0x400+(now-0xDC00)+0x10000  +    more=0 +    return utfchar(now) +  elseif now>=0xD800 and now<=0xDBFF then +    more=now +    return ""  +  else +    return utfchar(now) +  end +end +local p_utf32_to_utf8_be=C(1)*C(1)*C(1)*C(1)/function(a,b,c,d) +  return utfchar(256*256*256*byte(a)+256*256*byte(b)+256*byte(c)+byte(d)) +end +local p_utf32_to_utf8_le=C(1)*C(1)*C(1)*C(1)/function(a,b,c,d) +  return utfchar(256*256*256*byte(d)+256*256*byte(c)+256*byte(b)+byte(a)) +end +p_utf16_to_utf8_be=P(true)/function() more=0 end*utf_16_be_getbom*Cs(p_utf16_to_utf8_be^0) +p_utf16_to_utf8_le=P(true)/function() more=0 end*utf_16_le_getbom*Cs(p_utf16_to_utf8_le^0) +p_utf32_to_utf8_be=P(true)/function() more=0 end*utf_32_be_getbom*Cs(p_utf32_to_utf8_be^0) +p_utf32_to_utf8_le=P(true)/function() more=0 end*utf_32_le_getbom*Cs(p_utf32_to_utf8_le^0) +patterns.utf16_to_utf8_be=p_utf16_to_utf8_be +patterns.utf16_to_utf8_le=p_utf16_to_utf8_le +patterns.utf32_to_utf8_be=p_utf32_to_utf8_be +patterns.utf32_to_utf8_le=p_utf32_to_utf8_le +utf16_to_utf8_be=function(s) +  if s and s~="" then +    return lpegmatch(p_utf16_to_utf8_be,s) +  else +    return s +  end +end +local utf16_to_utf8_be_t=function(t) +  if not t then +    return nil +  elseif type(t)=="string" then +    t=lpegmatch(utf_16_be_linesplitter,t) +  end +  for i=1,#t do +    local s=t[i] +    if s~="" then +      t[i]=lpegmatch(p_utf16_to_utf8_be,s) +    end +  end +  return t +end +utf16_to_utf8_le=function(s) +  if s and s~="" then +    return lpegmatch(p_utf16_to_utf8_le,s) +  else +    return s +  end +end +local utf16_to_utf8_le_t=function(t) +  if not t then +    return nil +  elseif type(t)=="string" then +    t=lpegmatch(utf_16_le_linesplitter,t) +  end +  for i=1,#t do +    local s=t[i] +    if s~="" then +      t[i]=lpegmatch(p_utf16_to_utf8_le,s) +    end +  end +  return t +end +utf32_to_utf8_be=function(s) +  if s and s~="" then +    return lpegmatch(p_utf32_to_utf8_be,s) +  else +    return s +  end +end +local utf32_to_utf8_be_t=function(t) +  if not t then +    return nil +  elseif type(t)=="string" then +    t=lpegmatch(utf_32_be_linesplitter,t) +  end +  for i=1,#t do +    local s=t[i] +    if s~="" then +      t[i]=lpegmatch(p_utf32_to_utf8_be,s) +    end +  end +  return t +end +utf32_to_utf8_le=function(s) +  if s and s~="" then +    return lpegmatch(p_utf32_to_utf8_le,s) +  else +    return s +  end +end +local utf32_to_utf8_le_t=function(t) +  if not t then +    return nil +  elseif type(t)=="string" then +    t=lpegmatch(utf_32_le_linesplitter,t) +  end +  for i=1,#t do +    local s=t[i] +    if s~="" then +      t[i]=lpegmatch(p_utf32_to_utf8_le,s) +    end +  end +  return t +end +utf.utf16_to_utf8_le_t=utf16_to_utf8_le_t +utf.utf16_to_utf8_be_t=utf16_to_utf8_be_t +utf.utf32_to_utf8_le_t=utf32_to_utf8_le_t +utf.utf32_to_utf8_be_t=utf32_to_utf8_be_t +utf.utf16_to_utf8_le=utf16_to_utf8_le +utf.utf16_to_utf8_be=utf16_to_utf8_be +utf.utf32_to_utf8_le=utf32_to_utf8_le +utf.utf32_to_utf8_be=utf32_to_utf8_be +function utf.utf8_to_utf8_t(t) +  return type(t)=="string" and lpegmatch(utflinesplitter,t) or t +end +function utf.utf16_to_utf8_t(t,endian) +  return endian and utf16_to_utf8_be_t(t) or utf16_to_utf8_le_t(t) or t +end +function utf.utf32_to_utf8_t(t,endian) +  return endian and utf32_to_utf8_be_t(t) or utf32_to_utf8_le_t(t) or t +end +local function little(b) +  if b<0x10000 then +    return char(b%256,b/256) +  else +    b=b-0x10000 +    local b1,b2=b/1024+0xD800,b%1024+0xDC00 +    return char(b1%256,b1/256,b2%256,b2/256) +  end +end +local function big(b) +  if b<0x10000 then +    return char(b/256,b%256) +  else +    b=b-0x10000 +    local b1,b2=b/1024+0xD800,b%1024+0xDC00 +    return char(b1/256,b1%256,b2/256,b2%256) +  end +end +local l_remap=Cs((p_utf8byte/little+P(1)/"")^0) +local b_remap=Cs((p_utf8byte/big+P(1)/"")^0) +local function utf8_to_utf16_be(str,nobom) +  if nobom then +    return lpegmatch(b_remap,str) +  else +    return char(254,255)..lpegmatch(b_remap,str) +  end +end +local function utf8_to_utf16_le(str,nobom) +  if nobom then +    return lpegmatch(l_remap,str) +  else +    return char(255,254)..lpegmatch(l_remap,str) +  end +end +utf.utf8_to_utf16_be=utf8_to_utf16_be +utf.utf8_to_utf16_le=utf8_to_utf16_le +function utf.utf8_to_utf16(str,littleendian,nobom) +  if littleendian then +    return utf8_to_utf16_le(str,nobom) +  else +    return utf8_to_utf16_be(str,nobom) +  end +end +local pattern=Cs ( +  (p_utf8byte/function(unicode     ) return format("0x%04X",unicode) end)*(p_utf8byte*Carg(1)/function(unicode,separator) return format("%s0x%04X",separator,unicode) end)^0 +) +function utf.tocodes(str,separator) +  return lpegmatch(pattern,str,1,separator or " ") +end +function utf.ustring(s) +  return format("U+%05X",type(s)=="number" and s or utfbyte(s)) +end +function utf.xstring(s) +  return format("0x%05X",type(s)=="number" and s or utfbyte(s)) +end +function utf.toeight(str) +  if not str or str=="" then +    return nil +  end +  local utftype=lpegmatch(p_utfstricttype,str) +  if utftype=="utf-8" then +    return sub(str,4)         +  elseif utftype=="utf-16-be" then +    return utf16_to_utf8_be(str)   +  elseif utftype=="utf-16-le" then +    return utf16_to_utf8_le(str)   +  else +    return str +  end +end +local p_nany=p_utf8char/"" +if utfgmatch then +  function utf.count(str,what) +    if type(what)=="string" then +      local n=0 +      for _ in utfgmatch(str,what) do +        n=n+1 +      end +      return n +    else  +      return #lpegmatch(Cs((P(what)/" "+p_nany)^0),str) +    end +  end +else +  local cache={} +  function utf.count(str,what) +    if type(what)=="string" then +      local p=cache[what] +      if not p then +        p=Cs((P(what)/" "+p_nany)^0) +        cache[p]=p +      end +      return #lpegmatch(p,str) +    else  +      return #lpegmatch(Cs((P(what)/" "+p_nany)^0),str) +    end +  end +end +if not utf.characters then +  function utf.characters(str) +    return gmatch(str,".[\128-\191]*") +  end +  string.utfcharacters=utf.characters +end +if not utf.values then +  local find=string.find +  local dummy=function() +  end +  function utf.values(str) +    local n=#str +    if n==0 then +      return dummy +    elseif n==1 then +      return function() return utfbyte(str) end +    else +      local p=1 +      return function() +          local b,e=find(str,".[\128-\191]*",p) +          if b then +            p=e+1 +            return utfbyte(sub(str,b,e)) +          end +      end +    end +  end +  string.utfvalues=utf.values +end +function utf.chrlen(u)  +  return +    (u<0x80 and 1) or +    (u<0xE0 and 2) or +    (u<0xF0 and 3) or +    (u<0xF8 and 4) or +    (u<0xFC and 5) or +    (u<0xFE and 6) or 0 +end + +end -- closure + +do -- begin closure to overcome local limits and interference +  if not modules then modules={} end modules ['util-str']={    version=1.001,    comment="companion to luat-lib.mkiv", @@ -5034,7 +5648,6 @@ if not modules then modules={} end modules ['font-ini']={    license="see context related readme files"  }  local allocate=utilities.storage.allocate -local report_defining=logs.reporter("fonts","defining")  fonts=fonts or {}  local fonts=fonts  fonts.hashes={ identifiers=allocate() } @@ -5061,7 +5674,6 @@ if not modules then modules={} end modules ['font-con']={  }  local next,tostring,rawget=next,tostring,rawget  local format,match,lower,gsub=string.format,string.match,string.lower,string.gsub -local utfbyte=utf.byte  local sort,insert,concat,sortedkeys,serialize,fastcopy=table.sort,table.insert,table.concat,table.sortedkeys,table.serialize,table.fastcopy  local derivetable=table.derive  local trace_defining=false trackers.register("fonts.defining",function(v) trace_defining=v end) @@ -5288,6 +5900,19 @@ function constructors.enhanceparameters(parameters)      extra=extra,    }  end +local function mathkerns(v,vdelta) +  local k={} +  for i=1,#v do +    local entry=v[i] +    local height=entry.height +    local kern=entry.kern +    k[i]={ +      height=height and vdelta*height or 0, +      kern=kern  and vdelta*kern  or 0, +    } +  end +  return k +end  function constructors.scale(tfmdata,specification)    local target={}    if tonumber(specification) then @@ -5626,22 +6251,15 @@ function constructors.scale(tfmdata,specification)          chr.top_accent=vdelta*va        end        if stackmath then -        local mk=character.mathkerns  +        local mk=character.mathkerns          if mk then -          local kerns={} -          local v=mk.top_right  if v then local k={} for i=1,#v do local vi=v[i] -            k[i]={ height=vdelta*vi.height,kern=vdelta*vi.kern } -          end   kerns.top_right=k end -          local v=mk.top_left   if v then local k={} for i=1,#v do local vi=v[i] -            k[i]={ height=vdelta*vi.height,kern=vdelta*vi.kern } -          end   kerns.top_left=k end -          local v=mk.bottom_left if v then local k={} for i=1,#v do local vi=v[i] -            k[i]={ height=vdelta*vi.height,kern=vdelta*vi.kern } -          end   kerns.bottom_left=k end -          local v=mk.bottom_right if v then local k={} for i=1,#v do local vi=v[i] -            k[i]={ height=vdelta*vi.height,kern=vdelta*vi.kern } -          end   kerns.bottom_right=k end -          chr.mathkern=kerns  +          local tr,tl,br,bl=mk.topright,mk.topleft,mk.bottomright,mk.bottomleft +          chr.mathkern={  +            top_right=tr and mathkerns(tr,vdelta) or nil, +            top_left=tl and mathkerns(tl,vdelta) or nil, +            bottom_right=br and mathkerns(br,vdelta) or nil, +            bottom_left=bl and mathkerns(bl,vdelta) or nil, +          }          end        end        if hasitalics then @@ -5886,15 +6504,11 @@ hashmethods.normal=function(list)      elseif k=="number" or k=="features" then      else        n=n+1 -      s[n]=k +      s[n]=k..'='..tostring(v)      end    end    if n>0 then      sort(s) -    for i=1,n do -      local k=s[i] -      s[i]=k..'='..tostring(list[k]) -    end      return concat(s,"+")    end  end @@ -6041,7 +6655,10 @@ function constructors.getfeatureaction(what,where,mode,name)      end    end  end -function constructors.newhandler(what)  +local newhandler={} +constructors.handlers=newhandler  +constructors.newhandler=newhandler +local function setnewhandler(what)     local handler=handlers[what]    if not handler then      handler={} @@ -6049,7 +6666,14 @@ function constructors.newhandler(what)    end    return handler  end -function constructors.newfeatures(what)  +setmetatable(newhandler,{ +  __call=function(t,k) local v=t[k] return v end, +  __index=function(t,k) local v=setnewhandler(k) t[k]=v return v end, +}) +local newfeatures={} +constructors.newfeatures=newfeatures  +constructors.features=newfeatures +local function setnewfeatures(what)    local handler=handlers[what]    local features=handler.features    if not features then @@ -6068,6 +6692,10 @@ function constructors.newfeatures(what)    end    return features  end +setmetatable(newfeatures,{ +  __call=function(t,k) local v=t[k] return v end, +  __index=function(t,k) local v=setnewfeatures(k) t[k]=v return v end, +})  function constructors.checkedfeatures(what,features)    local defaults=handlers[what].features.defaults    if features and next(features) then @@ -6087,7 +6715,6 @@ function constructors.initializefeatures(what,tfmdata,features,trace,report)      local properties=tfmdata.properties or {}       local whathandler=handlers[what]      local whatfeatures=whathandler.features -    local whatinitializers=whatfeatures.initializers      local whatmodechecker=whatfeatures.modechecker      local mode=properties.mode or (whatmodechecker and whatmodechecker(tfmdata,features,features.mode)) or features.mode or "base"      properties.mode=mode  @@ -6403,7 +7030,6 @@ if not modules then modules={} end modules ['font-map']={  local tonumber,next,type=tonumber,next,type  local match,format,find,concat,gsub,lower=string.match,string.format,string.find,table.concat,string.gsub,string.lower  local P,R,S,C,Ct,Cc,lpegmatch=lpeg.P,lpeg.R,lpeg.S,lpeg.C,lpeg.Ct,lpeg.Cc,lpeg.match -local utfbyte=utf.byte  local floor=math.floor  local formatters=string.formatters  local trace_loading=false trackers.register("fonts.loading",function(v) trace_loading=v end) @@ -6414,7 +7040,7 @@ local fonts=fonts or {}  local mappings=fonts.mappings or {}  fonts.mappings=mappings  local allocate=utilities.storage.allocate -local hex=R("AF","09") +local hex=R("AF","af","09")  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 @@ -6438,7 +7064,7 @@ local function makenameparser(str)  end  local f_single=formatters["%04X"]  local f_double=formatters["%04X%04X"] -local function tounicode16(unicode,name) +local function tounicode16(unicode)    if unicode<0xD7FF or (unicode>0xDFFF and unicode<=0xFFFF) then      return f_single(unicode)    else @@ -6446,7 +7072,7 @@ local function tounicode16(unicode,name)      return f_double(floor(unicode/1024)+0xD800,unicode%1024+0xDC00)    end  end -local function tounicode16sequence(unicodes,name) +local function tounicode16sequence(unicodes)    local t={}    for l=1,#unicodes do      local u=unicodes[l] @@ -6549,127 +7175,128 @@ function mappings.addtounicode(data,filename,checklookups)    end    local ns=0    local nl=0 -  for unic,glyph in next,descriptions do +  for du,glyph in next,descriptions do      local name=glyph.name      if name then -      local index=glyph.index -      local r=overloads[name] -      if r then -        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 -        if (not unicode) and usedmap then -          local foundindex=lpegmatch(oparser,name) -          if foundindex then -            unicode=cidcodes[foundindex]  -            if unicode then -              glyph.unicode=unicode -              ns=ns+1 -            else -              local reference=cidnames[foundindex]  -              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 -                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 +      local overload=overloads[name] +      if overload then +        glyph.unicode=overload.unicode +      else +        local gu=glyph.unicode  +        if not gu or gu==-1 or du>=private or (du>=0xE000 and du<=0xF8FF) or du==0xFFFE or du==0xFFFF then +          local unicode=unicodevector[name] or contextvector[name] +          if unicode then +            glyph.unicode=unicode +            ns=ns+1 +          end +          if (not unicode) and usedmap then +            local foundindex=lpegmatch(oparser,name) +            if foundindex then +              unicode=cidcodes[foundindex]  +              if unicode then +                glyph.unicode=unicode +                ns=ns+1 +              else +                local reference=cidnames[foundindex]  +                if reference then +                  local foundindex=lpegmatch(oparser,reference) +                  if foundindex then +                    unicode=cidcodes[foundindex] +                    if unicode then +                      glyph.unicode=unicode                        ns=ns+1 -                      unicode=foundcodes +                    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 +                      end                      end                    end                  end                end              end            end -        end -        if not unicode or unicode=="" then -          local split=lpegmatch(namesplitter,name) -          local nsplit=split and #split or 0  -          if nsplit==0 then -          elseif nsplit==1 then -            local base=split[1] -            local u=unicodes[base] or unicodevector[base] or contextvector[name] -            if not u then -            elseif type(u)=="table" then -              if u[1]<private then -                unicode=u -                glyph.unicode=unicode -              end -            elseif u<private then -              unicode=u -              glyph.unicode=unicode -            end -          else -            local t,n={},0 -            for l=1,nsplit do -              local base=split[l] +          if not unicode or unicode=="" then +            local split=lpegmatch(namesplitter,name) +            local nsplit=split and #split or 0  +            if nsplit==0 then +            elseif nsplit==1 then +              local base=split[1]                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 +                if u[1]<private then +                  unicode=u +                  glyph.unicode=unicode                  end -                n=n+1 -                t[n]=u[1] -              else -                if u>=private then +              elseif u<private then +                unicode=u +                glyph.unicode=unicode +              end +            else +              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 -                n=n+1 -                t[n]=u +              end +              if n>0 then +                if n==1 then +                  unicode=t[1] +                else +                  unicode=t +                end +                glyph.unicode=unicode                end              end -            if n>0 then -              if n==1 then -                unicode=t[1] +            nl=nl+1 +          end +          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 -                unicode=t +                ns=ns+1 +                unicode=foundcodes                end -              glyph.unicode=unicode              end            end -          nl=nl+1 -        end -        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 +          local r=overloads[unicode] +          if r then +            unicode=r.unicode +            glyph.unicode=unicode +          end +          if not unicode then +            missing[du]=true +            nofmissing=nofmissing+1            end -        end -        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      end    end    if type(checklookups)=="function" then @@ -6833,12 +7460,11 @@ local handlers=fonts.handlers  local readers=fonts.readers  local constructors=fonts.constructors  local encodings=fonts.encodings -local tfm=constructors.newhandler("tfm") +local tfm=constructors.handlers.tfm  tfm.version=1.000  tfm.maxnestingdepth=5  tfm.maxnestingsize=65536*1024 -local tfmfeatures=constructors.newfeatures("tfm") -local registertfmfeature=tfmfeatures.register +local tfmfeatures=constructors.features.tfm  constructors.resolvevirtualtoo=false   fonts.formats.tfm="type1"   fonts.formats.ofm="type1" @@ -6973,1137 +7599,6 @@ end -- closure  do -- begin closure to overcome local limits and interference -if not modules then modules={} end modules ['font-afm']={ -  version=1.001, -  comment="companion to font-ini.mkiv", -  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", -  copyright="PRAGMA ADE / ConTeXt Development Team", -  license="see context related readme files" -} -local fonts,logs,trackers,containers,resolvers=fonts,logs,trackers,containers,resolvers -local next,type,tonumber=next,type,tonumber -local match,gmatch,lower,gsub,strip,find=string.match,string.gmatch,string.lower,string.gsub,string.strip,string.find -local char,byte,sub=string.char,string.byte,string.sub -local abs=math.abs -local bxor,rshift=bit32.bxor,bit32.rshift -local P,S,R,Cmt,C,Ct,Cs,lpegmatch,patterns=lpeg.P,lpeg.S,lpeg.R,lpeg.Cmt,lpeg.C,lpeg.Ct,lpeg.Cs,lpeg.match,lpeg.patterns -local derivetable=table.derive -local trace_features=false trackers.register("afm.features",function(v) trace_features=v end) -local trace_indexing=false trackers.register("afm.indexing",function(v) trace_indexing=v end) -local trace_loading=false trackers.register("afm.loading",function(v) trace_loading=v end) -local trace_defining=false trackers.register("fonts.defining",function(v) trace_defining=v end) -local report_afm=logs.reporter("fonts","afm loading") -local setmetatableindex=table.setmetatableindex -local findbinfile=resolvers.findbinfile -local definers=fonts.definers -local readers=fonts.readers -local constructors=fonts.constructors -local afm=constructors.newhandler("afm") -local pfb=constructors.newhandler("pfb") -local afmfeatures=constructors.newfeatures("afm") -local registerafmfeature=afmfeatures.register -afm.version=1.501  -afm.cache=containers.define("fonts","afm",afm.version,true) -afm.autoprefixed=true  -afm.helpdata={}  -afm.syncspace=true  -afm.addligatures=true  -afm.addtexligatures=true  -afm.addkerns=true  -local overloads=fonts.mappings.overloads -local applyruntimefixes=fonts.treatments and fonts.treatments.applyfixes -local function setmode(tfmdata,value) -  if value then -    tfmdata.properties.mode=lower(value) -  end -end -registerafmfeature { -  name="mode", -  description="mode", -  initializers={ -    base=setmode, -    node=setmode, -  } -} -local comment=P("Comment") -local spacing=patterns.spacer  -local lineend=patterns.newline  -local words=C((1-lineend)^1) -local number=C((R("09")+S("."))^1)/tonumber*spacing^0 -local data=lpeg.Carg(1) -local pattern=( -  comment*spacing*( -      data*( -        ("CODINGSCHEME"*spacing*words                   )/function(fd,a)                   end+("DESIGNSIZE"*spacing*number*words               )/function(fd,a)   fd[ 1]=a    end+("CHECKSUM"*spacing*number*words               )/function(fd,a)   fd[ 2]=a    end+("SPACE"*spacing*number*"plus"*number*"minus"*number)/function(fd,a,b,c) fd[ 3],fd[ 4],fd[ 5]=a,b,c end+("QUAD"*spacing*number                   )/function(fd,a)   fd[ 6]=a    end+("EXTRASPACE"*spacing*number                   )/function(fd,a)   fd[ 7]=a    end+("NUM"*spacing*number*number*number          )/function(fd,a,b,c) fd[ 8],fd[ 9],fd[10]=a,b,c end+("DENOM"*spacing*number*number              )/function(fd,a,b ) fd[11],fd[12]=a,b  end+("SUP"*spacing*number*number*number          )/function(fd,a,b,c) fd[13],fd[14],fd[15]=a,b,c end+("SUB"*spacing*number*number              )/function(fd,a,b)  fd[16],fd[17]=a,b  end+("SUPDROP"*spacing*number                   )/function(fd,a)   fd[18]=a    end+("SUBDROP"*spacing*number                   )/function(fd,a)   fd[19]=a    end+("DELIM"*spacing*number*number              )/function(fd,a,b)  fd[20],fd[21]=a,b  end+("AXISHEIGHT"*spacing*number                   )/function(fd,a)   fd[22]=a    end -      )+(1-lineend)^0 -    )+(1-comment)^1 -)^0 -local function scan_comment(str) -  local fd={} -  lpegmatch(pattern,str,1,fd) -  return fd -end -local keys={} -function keys.FontName  (data,line) data.metadata.fontname=strip  (line)  -                   data.metadata.fullname=strip  (line) end -function keys.ItalicAngle (data,line) data.metadata.italicangle=tonumber (line) end -function keys.IsFixedPitch(data,line) data.metadata.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) -  line=lower(line) -  local designsize=match(line,"designsize[^%d]*(%d+)") -  if designsize then data.metadata.designsize=tonumber(designsize) end -end -local function get_charmetrics(data,charmetrics,vector) -  local characters=data.characters -  local chr,ind={},0 -  for k,v in gmatch(charmetrics,"([%a]+) +(.-) *;") do -    if k=='C' then -      v=tonumber(v) -      if v<0 then -        ind=ind+1  -      else -        ind=v -      end -      chr={ -        index=ind -      } -    elseif k=='WX' then -      chr.width=tonumber(v) -    elseif k=='N' then -      characters[v]=chr -    elseif k=='B' then -      local llx,lly,urx,ury=match(v,"^ *(.-) +(.-) +(.-) +(.-)$") -      chr.boundingbox={ tonumber(llx),tonumber(lly),tonumber(urx),tonumber(ury) } -    elseif k=='L' then -      local plus,becomes=match(v,"^(.-) +(.-)$") -      local ligatures=chr.ligatures -      if ligatures then -        ligatures[plus]=becomes -      else -        chr.ligatures={ [plus]=becomes } -      end -    end -  end -end -local function get_kernpairs(data,kernpairs) -  local characters=data.characters -  for one,two,value in gmatch(kernpairs,"KPX +(.-) +(.-) +(.-)\n") do -    local chr=characters[one] -    if chr then -      local kerns=chr.kerns -      if kerns then -        kerns[two]=tonumber(value) -      else -        chr.kerns={ [two]=tonumber(value) } -      end -    end -  end -end -local function get_variables(data,fontmetrics) -  for key,rest in gmatch(fontmetrics,"(%a+) *(.-)[\n\r]") do -    local keyhandler=keys[key] -    if keyhandler then -      keyhandler(data,rest) -    end -  end -end -local get_indexes -do -  local fontloader=fontloader -  local get_indexes_old=false -  if fontloader then -    local font_to_table=fontloader.to_table -    local open_font=fontloader.open -    local close_font=fontloader.close -    get_indexes_old=function(data,pfbname) -      local pfbblob=open_font(pfbname) -      if pfbblob then -        local characters=data.characters -        local pfbdata=font_to_table(pfbblob) -        if pfbdata then -          local glyphs=pfbdata.glyphs -          if glyphs then -            if trace_loading then -              report_afm("getting index data from %a",pfbname) -            end -            for index,glyph in next,glyphs do -              local name=glyph.name -              if name then -                local char=characters[name] -                if char then -                  if trace_indexing then -                    report_afm("glyph %a has index %a",name,index) -                  end -                  char.index=index -                end -              end -            end -          elseif trace_loading then -            report_afm("no glyph data in pfb file %a",pfbname) -          end -        elseif trace_loading then -          report_afm("no data in pfb file %a",pfbname) -        end -        close_font(pfbblob) -      elseif trace_loading then -        report_afm("invalid pfb file %a",pfbname) -      end -    end -  end -  local n,m -  local progress=function(str,position,name,size) -    local forward=position+tonumber(size)+3+2 -    n=n+1 -    if n>=m then -      return #str,name -    elseif forward<#str then -      return forward,name -    else -      return #str,name -    end -  end -  local initialize=function(str,position,size) -    n=0 -    m=tonumber(size) -    return position+1 -  end -  local charstrings=P("/CharStrings") -  local name=P("/")*C((R("az")+R("AZ")+R("09")+S("-_."))^1) -  local size=C(R("09")^1) -  local spaces=P(" ")^1 -  local p_filternames=Ct ( -    (1-charstrings)^0*charstrings*spaces*Cmt(size,initialize)*(Cmt(name*P(" ")^1*C(R("09")^1),progress)+P(1))^1 -  ) -  local decrypt -  do -    local r,c1,c2,n=0,0,0,0 -    local function step(c) -      local cipher=byte(c) -      local plain=bxor(cipher,rshift(r,8)) -      r=((cipher+r)*c1+c2)%65536 -      return char(plain) -    end -    decrypt=function(binary) -      r,c1,c2,n=55665,52845,22719,4 -      binary=gsub(binary,".",step) -      return sub(binary,n+1) -    end -  end -  local function loadpfbvector(filename) -    local data=io.loaddata(resolvers.findfile(filename)) -    if not find(data,"!PS%-AdobeFont%-") then -      print("no font",filename) -      return -    end -    if not data then -      print("no data",filename) -      return -    end -    local ascii,binary=match(data,"(.*)eexec%s+......(.*)") -    if not binary then -      print("no binary",filename) -      return -    end -    binary=decrypt(binary,4) -    local vector=lpegmatch(p_filternames,binary) -    vector[0]=table.remove(vector,1) -    if not vector then -      print("no vector",filename) -      return -    end -    return vector -  end -  get_indexes=function(data,pfbname) -    local vector=loadpfbvector(pfbname) -    if vector then -      local characters=data.characters -      if trace_loading then -        report_afm("getting index data from %a",pfbname) -      end -      for index=1,#vector do -        local name=vector[index] -        local char=characters[name] -        if char then -          if trace_indexing then -            report_afm("glyph %a has index %a",name,index) -          end -          char.index=index -        end -      end -    end -  end -  if get_indexes_old then -    afm.use_new_indexer=true -    get_indexes_new=get_indexes -    get_indexes=function(data,pfbname) -      if afm.use_new_indexer then -        return get_indexes_new(data,pfbname) -      else -        return get_indexes_old(data,pfbname) -      end -    end -  end -end -local function readafm(filename) -  local ok,afmblob,size=resolvers.loadbinfile(filename)  -  if ok and afmblob then -    local data={ -      resources={ -        filename=resolvers.unresolve(filename), -        version=afm.version, -        creator="context mkiv", -      }, -      properties={ -        hasitalics=false, -      }, -      goodies={}, -      metadata={ -        filename=file.removesuffix(file.basename(filename)) -      }, -      characters={ -      }, -      descriptions={ -      }, -    } -    afmblob=gsub(afmblob,"StartCharMetrics(.-)EndCharMetrics",function(charmetrics) -      if trace_loading then -        report_afm("loading char metrics") -      end -      get_charmetrics(data,charmetrics,vector) -      return "" -    end) -    afmblob=gsub(afmblob,"StartKernPairs(.-)EndKernPairs",function(kernpairs) -      if trace_loading then -        report_afm("loading kern pairs") -      end -      get_kernpairs(data,kernpairs) -      return "" -    end) -    afmblob=gsub(afmblob,"StartFontMetrics%s+([%d%.]+)(.-)EndFontMetrics",function(version,fontmetrics) -      if trace_loading then -        report_afm("loading variables") -      end -      data.afmversion=version -      get_variables(data,fontmetrics) -      data.fontdimens=scan_comment(fontmetrics)  -      return "" -    end) -    return data -  else -    if trace_loading then -      report_afm("no valid afm file %a",filename) -    end -    return nil -  end -end -local addkerns,addligatures,addtexligatures,unify,normalize,fixnames  -function afm.load(filename) -  filename=resolvers.findfile(filename,'afm') or "" -  if filename~="" and not fonts.names.ignoredfile(filename) then -    local name=file.removesuffix(file.basename(filename)) -    local data=containers.read(afm.cache,name) -    local attr=lfs.attributes(filename) -    local size,time=attr.size or 0,attr.modification or 0 -    local pfbfile=file.replacesuffix(name,"pfb") -    local pfbname=resolvers.findfile(pfbfile,"pfb") or "" -    if pfbname=="" then -      pfbname=resolvers.findfile(file.basename(pfbfile),"pfb") or "" -    end -    local pfbsize,pfbtime=0,0 -    if pfbname~="" then -      local attr=lfs.attributes(pfbname) -      pfbsize=attr.size or 0 -      pfbtime=attr.modification or 0 -    end -    if not data or data.size~=size or data.time~=time or data.pfbsize~=pfbsize or data.pfbtime~=pfbtime then -      report_afm("reading %a",filename) -      data=readafm(filename) -      if data then -        if pfbname~="" then -          data.resources.filename=resolvers.unresolve(pfbname) -          get_indexes(data,pfbname) -        elseif trace_loading then -          report_afm("no pfb file for %a",filename) -        end -        report_afm("unifying %a",filename) -        unify(data,filename) -        if afm.addligatures then -          report_afm("add ligatures") -          addligatures(data) -        end -        if afm.addtexligatures then -          report_afm("add tex ligatures") -          addtexligatures(data) -        end -        if afm.addkerns then -          report_afm("add extra kerns") -          addkerns(data) -        end -        normalize(data) -        fixnames(data) -        report_afm("add tounicode data") -        fonts.mappings.addtounicode(data,filename) -        data.size=size -        data.time=time -        data.pfbsize=pfbsize -        data.pfbtime=pfbtime -        report_afm("saving %a in cache",name) -        data.resources.unicodes=nil  -        data=containers.write(afm.cache,name,data) -        data=containers.read(afm.cache,name) -      end -      if applyruntimefixes and data then -        applyruntimefixes(filename,data) -      end -    end -    return data -  else -    return nil -  end -end -local uparser=fonts.mappings.makenameparser() -unify=function(data,filename) -  local unicodevector=fonts.encodings.agl.unicodes  -  local unicodes={} -  local names={} -  local private=constructors.privateoffset -  local descriptions=data.descriptions -  for name,blob in next,data.characters do -    local code=unicodevector[name]  -    if not code then -      code=lpegmatch(uparser,name) -      if not code then -        code=private -        private=private+1 -        report_afm("assigning private slot %U for unknown glyph name %a",code,name) -      end -    end -    local index=blob.index -    unicodes[name]=code -    names[name]=index -    blob.name=name -    descriptions[code]={ -      boundingbox=blob.boundingbox, -      width=blob.width, -      kerns=blob.kerns, -      index=index, -      name=name, -    } -  end -  for unicode,description in next,descriptions do -    local kerns=description.kerns -    if kerns then -      local krn={} -      for name,kern in next,kerns do -        local unicode=unicodes[name] -        if unicode then -          krn[unicode]=kern -        else -        end -      end -      description.kerns=krn -    end -  end -  data.characters=nil -  local resources=data.resources -  local filename=resources.filename or file.removesuffix(file.basename(filename)) -  resources.filename=resolvers.unresolve(filename)  -  resources.unicodes=unicodes  -  resources.marks={} -  resources.private=private -end -local everywhere={ ["*"]={ ["*"]=true } }  -local noflags={ false,false,false,false } -afm.experimental_normalize=false -normalize=function(data) -  if type(afm.experimental_normalize)=="function" then -    afm.experimental_normalize(data) -  end -end -fixnames=function(data) -  for k,v in next,data.descriptions do -    local n=v.name -    local r=overloads[n] -    if r then -      local name=r.name -      if trace_indexing then -        report_afm("renaming characters %a to %a",n,name) -      end -      v.name=name -      v.unicode=r.unicode -    end -  end -end -local addthem=function(rawdata,ligatures) -  if ligatures then -    local descriptions=rawdata.descriptions -    local resources=rawdata.resources -    local unicodes=resources.unicodes -    for ligname,ligdata in next,ligatures do -      local one=descriptions[unicodes[ligname]] -      if one then -        for _,pair in next,ligdata do -          local two,three=unicodes[pair[1]],unicodes[pair[2]] -          if two and three then -            local ol=one.ligatures -            if ol then -              if not ol[two] then -                ol[two]=three -              end -            else -              one.ligatures={ [two]=three } -            end -          end -        end -      end -    end -  end -end -addligatures=function(rawdata) addthem(rawdata,afm.helpdata.ligatures  ) end -addtexligatures=function(rawdata) addthem(rawdata,afm.helpdata.texligatures) end -addkerns=function(rawdata)  -  local descriptions=rawdata.descriptions -  local resources=rawdata.resources -  local unicodes=resources.unicodes -  local function do_it_left(what) -    if what then -      for unicode,description in next,descriptions do -        local kerns=description.kerns -        if kerns then -          local extrakerns -          for complex,simple in next,what do -            complex=unicodes[complex] -            simple=unicodes[simple] -            if complex and simple then -              local ks=kerns[simple] -              if ks and not kerns[complex] then -                if extrakerns then -                  extrakerns[complex]=ks -                else -                  extrakerns={ [complex]=ks } -                end -              end -            end -          end -          if extrakerns then -            description.extrakerns=extrakerns -          end -        end -      end -    end -  end -  local function do_it_copy(what) -    if what then -      for complex,simple in next,what do -        complex=unicodes[complex] -        simple=unicodes[simple] -        if complex and simple then -          local complexdescription=descriptions[complex] -          if complexdescription then  -            local simpledescription=descriptions[complex] -            if simpledescription then -              local extrakerns -              local kerns=simpledescription.kerns -              if kerns then -                for unicode,kern in next,kerns do -                  if extrakerns then -                    extrakerns[unicode]=kern -                  else -                    extrakerns={ [unicode]=kern } -                  end -                end -              end -              local extrakerns=simpledescription.extrakerns -              if extrakerns then -                for unicode,kern in next,extrakerns do -                  if extrakerns then -                    extrakerns[unicode]=kern -                  else -                    extrakerns={ [unicode]=kern } -                  end -                end -              end -              if extrakerns then -                complexdescription.extrakerns=extrakerns -              end -            end -          end -        end -      end -    end -  end -  do_it_left(afm.helpdata.leftkerned) -  do_it_left(afm.helpdata.bothkerned) -  do_it_copy(afm.helpdata.bothkerned) -  do_it_copy(afm.helpdata.rightkerned) -end -local function adddimensions(data)  -  if data then -    for unicode,description in next,data.descriptions do -      local bb=description.boundingbox -      if bb then -        local ht,dp=bb[4],-bb[2] -        if ht==0 or ht<0 then -        else -          description.height=ht -        end -        if dp==0 or dp<0 then -        else -          description.depth=dp -        end -      end -    end -  end -end -local function copytotfm(data) -  if data and data.descriptions then -    local metadata=data.metadata -    local resources=data.resources -    local properties=derivetable(data.properties) -    local descriptions=derivetable(data.descriptions) -    local goodies=derivetable(data.goodies) -    local characters={} -    local parameters={} -    local unicodes=resources.unicodes -    for unicode,description in next,data.descriptions do  -      characters[unicode]={} -    end -    local filename=constructors.checkedfilename(resources) -    local fontname=metadata.fontname or metadata.fullname -    local fullname=metadata.fullname or metadata.fontname -    local endash=0x0020  -    local emdash=0x2014 -    local spacer="space" -    local spaceunits=500 -    local monospaced=metadata.monospaced -    local charwidth=metadata.charwidth -    local italicangle=metadata.italicangle -    local charxheight=metadata.xheight and metadata.xheight>0 and metadata.xheight -    properties.monospaced=monospaced -    parameters.italicangle=italicangle -    parameters.charwidth=charwidth -    parameters.charxheight=charxheight -    if properties.monospaced then -      if descriptions[endash] then -        spaceunits,spacer=descriptions[endash].width,"space" -      end -      if not spaceunits and descriptions[emdash] then -        spaceunits,spacer=descriptions[emdash].width,"emdash" -      end -      if not spaceunits and charwidth then -        spaceunits,spacer=charwidth,"charwidth" -      end -    else -      if descriptions[endash] then -        spaceunits,spacer=descriptions[endash].width,"space" -      end -      if not spaceunits and charwidth then -        spaceunits,spacer=charwidth,"charwidth" -      end -    end -    spaceunits=tonumber(spaceunits) -    if spaceunits<200 then -    end -    parameters.slant=0 -    parameters.space=spaceunits -    parameters.space_stretch=500 -    parameters.space_shrink=333 -    parameters.x_height=400 -    parameters.quad=1000 -    if italicangle and italicangle~=0 then -      parameters.italicangle=italicangle -      parameters.italicfactor=math.cos(math.rad(90+italicangle)) -      parameters.slant=- math.tan(italicangle*math.pi/180) -    end -    if monospaced then -      parameters.space_stretch=0 -      parameters.space_shrink=0 -    elseif afm.syncspace then -      parameters.space_stretch=spaceunits/2 -      parameters.space_shrink=spaceunits/3 -    end -    parameters.extra_space=parameters.space_shrink -    if charxheight then -      parameters.x_height=charxheight -    else -      local x=0x0078  -      if x then -        local x=descriptions[x] -        if x then -          parameters.x_height=x.height -        end -      end -    end -    local fd=data.fontdimens -    if fd and fd[8] and fd[9] and fd[10] then  -      for k,v in next,fd do -        parameters[k]=v -      end -    end -    parameters.designsize=(metadata.designsize or 10)*65536 -    parameters.ascender=abs(metadata.ascender or 0) -    parameters.descender=abs(metadata.descender or 0) -    parameters.units=1000 -    properties.spacer=spacer -    properties.encodingbytes=2 -    properties.format=fonts.formats[filename] or "type1" -    properties.filename=filename -    properties.fontname=fontname -    properties.fullname=fullname -    properties.psname=fullname -    properties.name=filename or fullname or fontname -    if next(characters) then -      return { -        characters=characters, -        descriptions=descriptions, -        parameters=parameters, -        resources=resources, -        properties=properties, -        goodies=goodies, -      } -    end -  end -  return nil -end -function afm.setfeatures(tfmdata,features) -  local okay=constructors.initializefeatures("afm",tfmdata,features,trace_features,report_afm) -  if okay then -    return constructors.collectprocessors("afm",tfmdata,features,trace_features,report_afm) -  else -    return {}  -  end -end -local function addtables(data) -  local resources=data.resources -  local lookuptags=resources.lookuptags -  local unicodes=resources.unicodes -  if not lookuptags then -    lookuptags={} -    resources.lookuptags=lookuptags -  end -  setmetatableindex(lookuptags,function(t,k) -    local v=type(k)=="number" and ("lookup "..k) or k -    t[k]=v -    return v -  end) -  if not unicodes then -    unicodes={} -    resources.unicodes=unicodes -    setmetatableindex(unicodes,function(t,k) -      setmetatableindex(unicodes,nil) -      for u,d in next,data.descriptions do -        local n=d.name -        if n then -          t[n]=u -        end -      end -      return rawget(t,k) -    end) -  end -  constructors.addcoreunicodes(unicodes)  -end -local function afmtotfm(specification) -  local afmname=specification.filename or specification.name -  if specification.forced=="afm" or specification.format=="afm" then  -    if trace_loading then -      report_afm("forcing afm format for %a",afmname) -    end -  else -    local tfmname=findbinfile(afmname,"ofm") or "" -    if tfmname~="" then -      if trace_loading then -        report_afm("fallback from afm to tfm for %a",afmname) -      end -      return  -    end -  end -  if afmname~="" then -    local features=constructors.checkedfeatures("afm",specification.features.normal) -    specification.features.normal=features -    constructors.hashinstance(specification,true) -    specification=definers.resolve(specification)  -    local cache_id=specification.hash -    local tfmdata=containers.read(constructors.cache,cache_id)  -    if not tfmdata then -      local rawdata=afm.load(afmname) -      if rawdata and next(rawdata) then -        addtables(rawdata) -        adddimensions(rawdata) -        tfmdata=copytotfm(rawdata) -        if tfmdata and next(tfmdata) then -          local shared=tfmdata.shared -          if not shared then -            shared={} -            tfmdata.shared=shared -          end -          shared.rawdata=rawdata -          shared.features=features -          shared.processes=afm.setfeatures(tfmdata,features) -        end -      elseif trace_loading then -        report_afm("no (valid) afm file found with name %a",afmname) -      end -      tfmdata=containers.write(constructors.cache,cache_id,tfmdata) -    end -    return tfmdata -  end -end -local function read_from_afm(specification) -  local tfmdata=afmtotfm(specification) -  if tfmdata then -    tfmdata.properties.name=specification.name -    tfmdata=constructors.scale(tfmdata,specification) -    local allfeatures=tfmdata.shared.features or specification.features.normal -    constructors.applymanipulators("afm",tfmdata,allfeatures,trace_features,report_afm) -    fonts.loggers.register(tfmdata,'afm',specification) -  end -  return tfmdata -end -local function prepareligatures(tfmdata,ligatures,value) -  if value then -    local descriptions=tfmdata.descriptions -    local hasligatures=false -    for unicode,character in next,tfmdata.characters do -      local description=descriptions[unicode] -      local dligatures=description.ligatures -      if dligatures then -        local cligatures=character.ligatures -        if not cligatures then -          cligatures={} -          character.ligatures=cligatures -        end -        for unicode,ligature in next,dligatures do -          cligatures[unicode]={ -            char=ligature, -            type=0 -          } -        end -        hasligatures=true -      end -    end -    tfmdata.properties.hasligatures=hasligatures -  end -end -local function preparekerns(tfmdata,kerns,value) -  if value then -    local rawdata=tfmdata.shared.rawdata -    local resources=rawdata.resources -    local unicodes=resources.unicodes -    local descriptions=tfmdata.descriptions -    local haskerns=false -    for u,chr in next,tfmdata.characters do -      local d=descriptions[u] -      local newkerns=d[kerns] -      if newkerns then -        local kerns=chr.kerns -        if not kerns then -          kerns={} -          chr.kerns=kerns -        end -        for k,v in next,newkerns do -          local uk=unicodes[k] -          if uk then -            kerns[uk]=v -          end -        end -        haskerns=true -      end -    end -    tfmdata.properties.haskerns=haskerns -  end -end -local list={ -  [0x0027]=0x2019, -} -local function texreplacements(tfmdata,value) -  local descriptions=tfmdata.descriptions -  local characters=tfmdata.characters -  for k,v in next,list do -    characters [k]=characters [v]  -    descriptions[k]=descriptions[v]  -  end -end -local function ligatures  (tfmdata,value) prepareligatures(tfmdata,'ligatures',value) end -local function texligatures(tfmdata,value) prepareligatures(tfmdata,'texligatures',value) end -local function kerns    (tfmdata,value) preparekerns  (tfmdata,'kerns',value) end -local function extrakerns (tfmdata,value) preparekerns  (tfmdata,'extrakerns',value) end -registerafmfeature { -  name="liga", -  description="traditional ligatures", -  initializers={ -    base=ligatures, -    node=ligatures, -  } -} -registerafmfeature { -  name="kern", -  description="intercharacter kerning", -  initializers={ -    base=kerns, -    node=kerns, -  } -} -registerafmfeature { -  name="extrakerns", -  description="additional intercharacter kerning", -  initializers={ -    base=extrakerns, -    node=extrakerns, -  } -} -registerafmfeature { -  name='tlig', -  description='tex ligatures', -  initializers={ -    base=texligatures, -    node=texligatures, -  } -} -registerafmfeature { -  name='trep', -  description='tex replacements', -  initializers={ -    base=texreplacements, -    node=texreplacements, -  } -} -local check_tfm=readers.check_tfm -fonts.formats.afm="type1" -fonts.formats.pfb="type1" -local function check_afm(specification,fullname) -  local foundname=findbinfile(fullname,'afm') or ""  -  if foundname=="" then -    foundname=fonts.names.getfilename(fullname,"afm") or "" -  end -  if foundname=="" and afm.autoprefixed then -    local encoding,shortname=match(fullname,"^(.-)%-(.*)$")  -    if encoding and shortname and fonts.encodings.known[encoding] then -      shortname=findbinfile(shortname,'afm') or ""  -      if shortname~="" then -        foundname=shortname -        if trace_defining then -          report_afm("stripping encoding prefix from filename %a",afmname) -        end -      end -    end -  end -  if foundname~="" then -    specification.filename=foundname -    specification.format="afm" -    return read_from_afm(specification) -  end -end -function readers.afm(specification,method) -  local fullname,tfmdata=specification.filename or "",nil -  if fullname=="" then -    local forced=specification.forced or "" -    if forced~="" then -      tfmdata=check_afm(specification,specification.name.."."..forced) -    end -    if not tfmdata then -      method=method or definers.method or "afm or tfm" -      if method=="tfm" then -        tfmdata=check_tfm(specification,specification.name) -      elseif method=="afm" then -        tfmdata=check_afm(specification,specification.name) -      elseif method=="tfm or afm" then -        tfmdata=check_tfm(specification,specification.name) or check_afm(specification,specification.name) -      else  -        tfmdata=check_afm(specification,specification.name) or check_tfm(specification,specification.name) -      end -    end -  else -    tfmdata=check_afm(specification,fullname) -  end -  return tfmdata -end -function readers.pfb(specification,method)  -  local original=specification.specification -  if trace_defining then -    report_afm("using afm reader for %a",original) -  end -  specification.specification=gsub(original,"%.pfb",".afm") -  specification.forced="afm" -  return readers.afm(specification,method) -end - -end -- closure - -do -- begin closure to overcome local limits and interference - -if not modules then modules={} end modules ['font-afk']={ -  version=1.001, -  comment="companion to font-afm.lua", -  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", -  copyright="PRAGMA ADE / ConTeXt Development Team", -  license="see context related readme files", -  dataonly=true, -} -local allocate=utilities.storage.allocate -fonts.handlers.afm.helpdata={ -  ligatures=allocate {  -    ['f']={  -      { 'f','ff' }, -      { 'i','fi' }, -      { 'l','fl' }, -    }, -    ['ff']={ -      { 'i','ffi' } -    }, -    ['fi']={ -      { 'i','fii' } -    }, -    ['fl']={ -      { 'i','fli' } -    }, -    ['s']={ -      { 't','st' } -    }, -    ['i']={ -      { 'j','ij' } -    }, -  }, -  texligatures=allocate { -    ['quoteleft']={ -      { 'quoteleft','quotedblleft' } -    }, -    ['quoteright']={ -      { 'quoteright','quotedblright' } -    }, -    ['hyphen']={ -      { 'hyphen','endash' } -    }, -    ['endash']={ -      { 'hyphen','emdash' } -    } -  }, -  leftkerned=allocate { -    AEligature="A",aeligature="a", -    OEligature="O",oeligature="o", -    IJligature="I",ijligature="i", -    AE="A",ae="a", -    OE="O",oe="o", -    IJ="I",ij="i", -    Ssharp="S",ssharp="s", -  }, -  rightkerned=allocate { -    AEligature="E",aeligature="e", -    OEligature="E",oeligature="e", -    IJligature="J",ijligature="j", -    AE="E",ae="e", -    OE="E",oe="e", -    IJ="J",ij="j", -    Ssharp="S",ssharp="s", -  }, -  bothkerned=allocate { -    Acircumflex="A",acircumflex="a", -    Ccircumflex="C",ccircumflex="c", -    Ecircumflex="E",ecircumflex="e", -    Gcircumflex="G",gcircumflex="g", -    Hcircumflex="H",hcircumflex="h", -    Icircumflex="I",icircumflex="i", -    Jcircumflex="J",jcircumflex="j", -    Ocircumflex="O",ocircumflex="o", -    Scircumflex="S",scircumflex="s", -    Ucircumflex="U",ucircumflex="u", -    Wcircumflex="W",wcircumflex="w", -    Ycircumflex="Y",ycircumflex="y", -    Agrave="A",agrave="a", -    Egrave="E",egrave="e", -    Igrave="I",igrave="i", -    Ograve="O",ograve="o", -    Ugrave="U",ugrave="u", -    Ygrave="Y",ygrave="y", -    Atilde="A",atilde="a", -    Itilde="I",itilde="i", -    Otilde="O",otilde="o", -    Utilde="U",utilde="u", -    Ntilde="N",ntilde="n", -    Adiaeresis="A",adiaeresis="a",Adieresis="A",adieresis="a", -    Ediaeresis="E",ediaeresis="e",Edieresis="E",edieresis="e", -    Idiaeresis="I",idiaeresis="i",Idieresis="I",idieresis="i", -    Odiaeresis="O",odiaeresis="o",Odieresis="O",odieresis="o", -    Udiaeresis="U",udiaeresis="u",Udieresis="U",udieresis="u", -    Ydiaeresis="Y",ydiaeresis="y",Ydieresis="Y",ydieresis="y", -    Aacute="A",aacute="a", -    Cacute="C",cacute="c", -    Eacute="E",eacute="e", -    Iacute="I",iacute="i", -    Lacute="L",lacute="l", -    Nacute="N",nacute="n", -    Oacute="O",oacute="o", -    Racute="R",racute="r", -    Sacute="S",sacute="s", -    Uacute="U",uacute="u", -    Yacute="Y",yacute="y", -    Zacute="Z",zacute="z", -    Dstroke="D",dstroke="d", -    Hstroke="H",hstroke="h", -    Tstroke="T",tstroke="t", -    Cdotaccent="C",cdotaccent="c", -    Edotaccent="E",edotaccent="e", -    Gdotaccent="G",gdotaccent="g", -    Idotaccent="I",idotaccent="i", -    Zdotaccent="Z",zdotaccent="z", -    Amacron="A",amacron="a", -    Emacron="E",emacron="e", -    Imacron="I",imacron="i", -    Omacron="O",omacron="o", -    Umacron="U",umacron="u", -    Ccedilla="C",ccedilla="c", -    Kcedilla="K",kcedilla="k", -    Lcedilla="L",lcedilla="l", -    Ncedilla="N",ncedilla="n", -    Rcedilla="R",rcedilla="r", -    Scedilla="S",scedilla="s", -    Tcedilla="T",tcedilla="t", -    Ohungarumlaut="O",ohungarumlaut="o", -    Uhungarumlaut="U",uhungarumlaut="u", -    Aogonek="A",aogonek="a", -    Eogonek="E",eogonek="e", -    Iogonek="I",iogonek="i", -    Uogonek="U",uogonek="u", -    Aring="A",aring="a", -    Uring="U",uring="u", -    Abreve="A",abreve="a", -    Ebreve="E",ebreve="e", -    Gbreve="G",gbreve="g", -    Ibreve="I",ibreve="i", -    Obreve="O",obreve="o", -    Ubreve="U",ubreve="u", -    Ccaron="C",ccaron="c", -    Dcaron="D",dcaron="d", -    Ecaron="E",ecaron="e", -    Lcaron="L",lcaron="l", -    Ncaron="N",ncaron="n", -    Rcaron="R",rcaron="r", -    Scaron="S",scaron="s", -    Tcaron="T",tcaron="t", -    Zcaron="Z",zcaron="z", -    dotlessI="I",dotlessi="i", -    dotlessJ="J",dotlessj="j", -    AEligature="AE",aeligature="ae",AE="AE",ae="ae", -    OEligature="OE",oeligature="oe",OE="OE",oe="oe", -    IJligature="IJ",ijligature="ij",IJ="IJ",ij="ij", -    Lstroke="L",lstroke="l",Lslash="L",lslash="l", -    Ostroke="O",ostroke="o",Oslash="O",oslash="o", -    Ssharp="SS",ssharp="ss", -    Aumlaut="A",aumlaut="a", -    Eumlaut="E",eumlaut="e", -    Iumlaut="I",iumlaut="i", -    Oumlaut="O",oumlaut="o", -    Uumlaut="U",uumlaut="u", -  } -} - -end -- closure - -do -- begin closure to overcome local limits and interference -  if not modules then modules={} end modules ['font-oti']={    version=1.001,    comment="companion to font-ini.mkiv", @@ -8114,8 +7609,8 @@ if not modules then modules={} end modules ['font-oti']={  local lower=string.lower  local fonts=fonts  local constructors=fonts.constructors -local otf=constructors.newhandler("otf") -local otffeatures=constructors.newfeatures("otf") +local otf=constructors.handlers.otf +local otffeatures=constructors.features.otf  local registerotffeature=otffeatures.register  local otftables=otf.tables or {}  otf.tables=otftables @@ -8278,7 +7773,6 @@ local streamreader=utilities.files  readers.streamreader=streamreader  local openfile=streamreader.open  local closefile=streamreader.close -local skipbytes=streamreader.skip  local setposition=streamreader.setposition  local skipshort=streamreader.skipshort  local readbytes=streamreader.readbytes @@ -8286,8 +7780,7 @@ local readstring=streamreader.readstring  local readbyte=streamreader.readcardinal1   local readushort=streamreader.readcardinal2   local readuint=streamreader.readcardinal3  -local readulong=streamreader.readcardinal4  -local readchar=streamreader.readinteger1   +local readulong=streamreader.readcardinal4  local readshort=streamreader.readinteger2    local readlong=streamreader.readinteger4    local readfixed=streamreader.readfixed4 @@ -8303,8 +7796,8 @@ local function readlongdatetime(f)    return 0x100000000*d+0x1000000*e+0x10000*f+0x100*g+h  end  local tableversion=0.004 -local privateoffset=fonts.constructors and fonts.constructors.privateoffset or 0xF0000   readers.tableversion=tableversion +local privateoffset=fonts.constructors and fonts.constructors.privateoffset or 0xF0000   local reportedskipped={}  local function reportskippedtable(tag)    if not reportedskipped[tag] then @@ -9308,6 +8801,21 @@ function readers.glyf(f,fontdata,specification)      reportskippedtable("glyf")    end  end +function readers.colr(f,fontdata,specification) +  if specification.glyphs then +    reportskippedtable("colr") +  end +end +function readers.cpal(f,fontdata,specification) +  if specification.glyphs then +    reportskippedtable("cpal") +  end +end +function readers.svg(f,fontdata,specification) +  if specification.glyphs then +    reportskippedtable("svg") +  end +end  function readers.kern(f,fontdata,specification)    if specification.kerns then      local datatable=fontdata.tables.kern @@ -9616,6 +9124,9 @@ local function readdata(f,offset,specification)    readers["cmap"](f,fontdata,specification)    readers["loca"](f,fontdata,specification)    readers["glyf"](f,fontdata,specification) +  readers["colr"](f,fontdata,specification) +  readers["cpal"](f,fontdata,specification) +  readers["svg" ](f,fontdata,specification)    readers["kern"](f,fontdata,specification)    readers["gdef"](f,fontdata,specification)    readers["gsub"](f,fontdata,specification) @@ -9771,6 +9282,8 @@ function readers.loadfont(filename,n)        metadata=getinfo(fontdata,n),        properties={          hasitalics=fontdata.hasitalics or false, +        maxcolorclass=fontdata.maxcolorclass, +        hascolor=fontdata.hascolor or false,        },        resources={          filename=filename, @@ -9786,6 +9299,8 @@ function readers.loadfont(filename,n)          version=getname(fontdata,"version"),          cidinfo=fontdata.cidinfo,          mathconstants=fontdata.mathconstants, +        colorpalettes=fontdata.colorpalettes, +        svgshapes=fontdata.svgshapes,        },      }    end @@ -11727,7 +11242,6 @@ local report=logs.reporter("otf reader")  local readers=fonts.handlers.otf.readers  local streamreader=readers.streamreader  local setposition=streamreader.setposition -local skipbytes=streamreader.skip  local skipshort=streamreader.skipshort  local readushort=streamreader.readcardinal2   local readulong=streamreader.readcardinal4  @@ -11735,6 +11249,7 @@ local readshort=streamreader.readinteger2  local readfword=readshort  local readstring=streamreader.readstring  local readtag=streamreader.readtag +local readbytes=streamreader.readbytes  local gsubhandlers={}  local gposhandlers={}  local lookupidoffset=-1   @@ -13091,6 +12606,13 @@ do        end      end      local reported={} +    local function report_issue(i,what,sequence,kind) +      local name=sequence.name +      if not reported[name] then +        report("rule %i in %s lookup %a has %s lookups",i,what,name,kind) +        reported[name]=true +      end +    end      for i=lastsequence+1,nofsequences do        local sequence=sequences[i]        local steps=sequence.steps @@ -13102,37 +12624,42 @@ do              local rule=rules[i]              local rlookups=rule.lookups              if not rlookups then -              local name=sequence.name -              if not reported[name] then -                report("rule %i in %s lookup %a has %s lookups",i,what,name,"no") -                reported[name]=true -              end +              report_issue(i,what,sequence,"no")              elseif not next(rlookups) then -              local name=sequence.name -              if not reported[name] then -                report("rule %i in %s lookup %a has %s lookups",i,what,name,"empty") -                reported[name]=true -              end +              report_issue(i,what,sequence,"empty")                rule.lookups=nil              else                for index,lookupid in sortedhash(rlookups) do                   local h=sublookuphash[lookupid]                  if not h then -                  nofsublookups=nofsublookups+1 -                  local d=lookups[lookupid].done -                  h={ -                    index=nofsublookups, -                    name=f_lookupname(lookupprefix,"d",lookupid+lookupidoffset), -                    derived=true, -                    steps=d.steps, -                    nofsteps=d.nofsteps, -                    type=d.lookuptype, -                    markclass=d.markclass or nil, -                    flags=d.flags, -                  } -                  sublookuplist[nofsublookups]=h -                  sublookuphash[lookupid]=nofsublookups -                  sublookupcheck[lookupid]=1 +                  local lookup=lookups[lookupid] +                  if lookup then +                    local d=lookup.done +                    if d then +                      nofsublookups=nofsublookups+1 +                      h={ +                        index=nofsublookups, +                        name=f_lookupname(lookupprefix,"d",lookupid+lookupidoffset), +                        derived=true, +                        steps=d.steps, +                        nofsteps=d.nofsteps, +                        type=d.lookuptype, +                        markclass=d.markclass or nil, +                        flags=d.flags, +                      } +                      sublookuplist[nofsublookups]=h +                      sublookuphash[lookupid]=nofsublookups +                      sublookupcheck[lookupid]=1 +                    else +                      report_issue(i,what,sequence,"missing") +                      rule.lookups=nil +                      break +                    end +                  else +                    report_issue(i,what,sequence,"bad") +                    rule.lookups=nil +                    break +                  end                  else                    sublookupcheck[lookupid]=sublookupcheck[lookupid]+1                  end @@ -13473,7 +13000,13 @@ local function readmathglyphinfo(f,fontdata,offset)        local function get(offset)          setposition(f,kernoffset+offset)          local n=readushort(f) -        if n>0 then +        if n==0 then +          local k=readmathvalue(f) +          if k==0 then +          else +            return { { kern=k } } +          end +        else            local l={}            for i=1,n do              l[i]={ height=readmathvalue(f) } @@ -13509,10 +13042,10 @@ local function readmathglyphinfo(f,fontdata,offset)            if next(kernset) then              local glyph=glyphs[coverage[i]]              local math=glyph.math -            if not math then -              glyph.math={ kerns=kernset } -            else +            if math then                math.kerns=kernset +            else +              glyph.math={ kerns=kernset }              end            end          end @@ -13616,7 +13149,7 @@ function readers.math(f,fontdata,specification)        setposition(f,tableoffset)        local version=readulong(f)        if version~=0x00010000 then -        report("table version %a of %a is not supported (yet), maybe font %s is bad",version,what,fontdata.filename) +        report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"math",fontdata.filename)          return        end        local constants=readushort(f) @@ -13636,6 +13169,142 @@ function readers.math(f,fontdata,specification)      end    end  end +function readers.colr(f,fontdata,specification) +  local datatable=fontdata.tables.colr +  if datatable then +    if specification.glyphs then +      local tableoffset=datatable.offset +      setposition(f,tableoffset) +      local version=readushort(f) +      if version~=0 then +        report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"colr",fontdata.filename) +        return +      end +      if not fontdata.tables.cpal then +        report("color table %a in font %a has no mandate %a table","colr",fontdata.filename,"cpal") +        fontdata.colorpalettes={} +      end +      local glyphs=fontdata.glyphs +      local nofglyphs=readushort(f) +      local baseoffset=readulong(f) +      local layeroffset=readulong(f) +      local noflayers=readushort(f) +      local layerrecords={} +      local maxclass=0 +      setposition(f,tableoffset+layeroffset) +      for i=1,noflayers do +        local slot=readushort(f) +        local class=readushort(f) +        if class<0xFFFF then +          class=class+1 +          if class>maxclass then +            maxclass=class +          end +        end +        layerrecords[i]={ +          slot=slot, +          class=class, +        } +      end +      fontdata.maxcolorclass=maxclass +      setposition(f,tableoffset+baseoffset) +      for i=0,nofglyphs-1 do +        local glyphindex=readushort(f) +        local firstlayer=readushort(f) +        local noflayers=readushort(f) +        local t={} +        for i=1,noflayers do +          t[i]=layerrecords[firstlayer+i] +        end +        glyphs[glyphindex].colors=t +      end +    end +    fontdata.hascolor=true +  end +end +function readers.cpal(f,fontdata,specification) +  if specification.glyphs then +    local datatable=fontdata.tables.cpal +    if datatable then +      local tableoffset=datatable.offset +      setposition(f,tableoffset) +      local version=readushort(f) +      if version>1 then +        report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"cpal",fontdata.filename) +        return +      end +      local nofpaletteentries=readushort(f) +      local nofpalettes=readushort(f) +      local nofcolorrecords=readushort(f) +      local firstcoloroffset=readulong(f) +      local colorrecords={} +      local palettes={} +      for i=1,nofpalettes do +        palettes[i]=readushort(f) +      end +      if version==1 then +        local palettettypesoffset=readulong(f) +        local palettelabelsoffset=readulong(f) +        local paletteentryoffset=readulong(f) +      end +      setposition(f,tableoffset+firstcoloroffset) +      for i=1,nofcolorrecords do +        local b,g,r,a=readbytes(f,4) +        colorrecords[i]={ +          r,g,b,a~=255 and a or nil, +        } +      end +      for i=1,nofpalettes do +        local p={} +        local o=palettes[i] +        for j=1,nofpaletteentries do +          p[j]=colorrecords[o+j] +        end +        palettes[i]=p +      end +      fontdata.colorpalettes=palettes +    end +  end +end +function readers.svg(f,fontdata,specification) +  local datatable=fontdata.tables.svg +  if datatable then +    if specification.glyphs then +      local tableoffset=datatable.offset +      setposition(f,tableoffset) +      local version=readushort(f) +      if version~=0 then +        report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"svg",fontdata.filename) +        return +      end +      local glyphs=fontdata.glyphs +      local indexoffset=tableoffset+readulong(f) +      local reserved=readulong(f) +      setposition(f,indexoffset) +      local nofentries=readushort(f) +      local entries={} +      for i=1,nofentries do +        entries[i]={ +          first=readushort(f), +          last=readushort(f), +          offset=indexoffset+readulong(f), +          length=readulong(f), +        } +      end +      for i=1,nofentries do +        local entry=entries[i] +        setposition(f,entry.offset) +        entries[i]={ +          first=entry.first, +          last=entry.last, +          data=readstring(f,entry.length) +        } +      end +      fontdata.svgshapes=entries +    end +    fontdata.hascolor=true +  end +end  end -- closure @@ -14183,7 +13852,8 @@ local function checklookups(fontdata,missing,nofmissing)      local done={}      for i,r in next,missing do        if r then -        local name=descriptions[i].name or f_index(i) +        local data=descriptions[i] +        local name=data and data.name or f_index(i)          if not ignore[name] then            done[name]=true          end @@ -14292,6 +13962,18 @@ local function unifyglyphs(fontdata,usenames)        end      end    end +  local colorpalettes=resources.colorpalettes +  if colorpalettes then +    for index=1,#glyphs do +      local colors=glyphs[index].colors +      if colors then +        for i=1,#colors do +          local c=colors[i] +          c.slot=indices[c.slot] +        end +      end +    end +  end    fontdata.private=private    fontdata.glyphs=nil    fontdata.names=names @@ -14692,6 +14374,7 @@ function readers.pack(data)      local sequences=resources.sequences      local sublookups=resources.sublookups      local features=resources.features +    local palettes=resources.colorpalettes      local chardata=characters and characters.data      local descriptions=data.descriptions or data.glyphs      if not descriptions then @@ -14834,6 +14517,14 @@ function readers.pack(data)            end          end        end +      if palettes then +        for i=1,#palettes do +          local p=palettes[i] +          for j=1,#p do +            p[j]=pack_indexed(p[j]) +          end +        end +      end        if not success(1,pass) then          return        end @@ -14954,6 +14645,7 @@ function readers.unpack(data)        local sequences=resources.sequences        local sublookups=resources.sublookups        local features=resources.features +      local palettes=resources.colorpalettes        local unpacked={}        setmetatable(unpacked,unpacked_mt)        for unicode,description in next,descriptions do @@ -15192,6 +14884,17 @@ function readers.unpack(data)            end          end        end +      if palettes then +        for i=1,#palettes do +          local p=palettes[i] +          for j=1,#p do +            local tv=tables[p[j]] +            if tv then +              p[j]=tv +            end +          end +        end +      end        data.tables=nil      end    end @@ -15645,14 +15348,17 @@ local trace_defining=false registertracker("fonts.defining",function(v) trace_de  local report_otf=logs.reporter("fonts","otf loading")  local fonts=fonts  local otf=fonts.handlers.otf -otf.version=3.019  +otf.version=3.022   otf.cache=containers.define("fonts","otl",otf.version,true) +otf.svgcache=containers.define("fonts","svg",otf.version,true) +otf.pdfcache=containers.define("fonts","pdf",otf.version,true) +otf.svgenabled=false  local otfreaders=otf.readers  local hashes=fonts.hashes  local definers=fonts.definers  local readers=fonts.readers  local constructors=fonts.constructors -local otffeatures=constructors.newfeatures("otf") +local otffeatures=constructors.features.otf  local registerotffeature=otffeatures.register  local enhancers=allocate()  otf.enhancers=enhancers @@ -15792,6 +15498,22 @@ function otf.load(filename,sub,featurefile)      starttiming(otfreaders)      data=otfreaders.loadfont(filename,sub or 1)      if data then +      local resources=data.resources +      local svgshapes=resources.svgshapes +      if svgshapes then +        resources.svgshapes=nil +        if otf.svgenabled then +          local timestamp=os.date() +          containers.write(otf.svgcache,hash,{ +            svgshapes=svgshapes, +            timestamp=timestamp, +          }) +          data.properties.svg={ +            hash=hash, +            timestamp=timestamp, +          } +        end +      end        otfreaders.compact(data)        otfreaders.rehash(data,"unicodes")        otfreaders.addunicodetable(data) @@ -15827,7 +15549,6 @@ function otf.load(filename,sub,featurefile)      otfreaders.expand(data)       otfreaders.addunicodetable(data)      enhancers.apply(data,filename,data) -    constructors.addcoreunicodes(unicodes)      if applyruntimefixes then        applyruntimefixes(filename,data)      end @@ -15846,7 +15567,6 @@ end  local function copytotfm(data,cache_id)    if data then      local metadata=data.metadata -    local resources=data.resources      local properties=derivetable(data.properties)      local descriptions=derivetable(data.descriptions)      local goodies=derivetable(data.goodies) @@ -16300,8 +16020,6 @@ local concat,unpack=table.concat,table.unpack  local insert,remove=table.insert,table.remove  local format,gmatch,gsub,find,match,lower,strip=string.format,string.gmatch,string.gsub,string.find,string.match,string.lower,string.strip  local type,next,tonumber,tostring,rawget=type,next,tonumber,tostring,rawget -local lpegmatch=lpeg.match -local utfchar=utf.char  local trace_baseinit=false trackers.register("otf.baseinit",function(v) trace_baseinit=v end)  local trace_singles=false trackers.register("otf.singles",function(v) trace_singles=v end)  local trace_multiples=false trackers.register("otf.multiples",function(v) trace_multiples=v end) @@ -16658,6 +16376,7 @@ registerotffeature {      base=featuresinitializer,    }  } +otf.basemodeinitializer=featuresinitializer  end -- closure @@ -16672,7 +16391,6 @@ if not modules then modules={} end modules ['font-otj']={  }  if not nodes.properties then return end  local next,rawget=next,rawget -local utfchar=utf.char  local fastcopy=table.fastcopy  local registertracker=trackers.register  local trace_injections=false registertracker("fonts.injections",function(v) trace_injections=v end) @@ -16717,7 +16435,6 @@ local traverse_id=nuts.traverse_id  local traverse_char=nuts.traverse_char  local insert_node_before=nuts.insert_before  local insert_node_after=nuts.insert_after -local find_tail=nuts.tail  local properties=nodes.properties.data  function injections.installnewkern(nk)    newkern=nk or newkern @@ -17288,7 +17005,7 @@ local function inject_pairs_only(head,where)              end              local leftkern=i.leftkern              if leftkern and leftkern~=0 then -              insert_node_before(head,current,newkern(leftkern)) +              head=insert_node_before(head,current,newkern(leftkern))              end              local rightkern=i.rightkern              if rightkern and rightkern~=0 then @@ -17713,11 +17430,11 @@ local function inject_everything(head,where)                  insert_node_after(pre,n,newkern(rightkern))                  done=true                end -            end -            if hasmarks then -              local pm=i.markbasenode -              if pm then -                processmark(pm,current,i) +              if hasmarks then +                local pm=i.markbasenode +                if pm then +                  processmark(pm,current,i) +                end                end              end            end @@ -17743,11 +17460,11 @@ local function inject_everything(head,where)                  insert_node_after(post,n,newkern(rightkern))                  done=true                end -            end -            if hasmarks then -              local pm=i.markbasenode -              if pm then -                processmark(pm,current,i) +              if hasmarks then +                local pm=i.markbasenode +                if pm then +                  processmark(pm,current,i) +                end                end              end            end @@ -17773,11 +17490,11 @@ local function inject_everything(head,where)                  insert_node_after(replace,n,newkern(rightkern))                  done=true                end -            end -            if hasmarks then -              local pm=i.markbasenode -              if pm then -                processmark(pm,current,i) +              if hasmarks then +                local pm=i.markbasenode +                if pm then +                  processmark(pm,current,i) +                end                end              end            end @@ -17949,10 +17666,19 @@ function injections.handler(head,where)      head=injectspaces(head)    end    if nofregisteredmarks>0 or nofregisteredcursives>0 then +    if trace_injections then +      report_injections("injection variant %a","everything") +    end      return inject_everything(head,where)    elseif nofregisteredpairs>0 then +    if trace_injections then +      report_injections("injection variant %a","pairs") +    end      return inject_pairs_only(head,where)    elseif nofregisteredkerns>0 then +    if trace_injections then +      report_injections("injection variant %a","kerns") +    end      return inject_kerns_only(head,where)    else      return head,false @@ -17994,7 +17720,6 @@ local getsubtype=nuts.getsubtype  local getchar=nuts.getchar  local ischar=nuts.is_char  local traverse_id=nuts.traverse_id -local traverse_node_list=nuts.traverse  local end_of_math=nuts.end_of_math  local nodecodes=nodes.nodecodes  local disc_code=nodecodes.disc @@ -18002,7 +17727,7 @@ local math_code=nodecodes.math  local fontdata=fonts.hashes.identifiers  local categories=characters and characters.categories or {}   local chardata=characters and characters.data -local otffeatures=fonts.constructors.newfeatures("otf") +local otffeatures=fonts.constructors.features.otf  local registerotffeature=otffeatures.register  local s_init=1  local s_rphf=7  local s_medi=2  local s_half=8 @@ -18367,7 +18092,6 @@ local trace_cursive=false registertracker("otf.cursive",function(v) trace_cursiv  local trace_preparing=false registertracker("otf.preparing",function(v) trace_preparing=v end)  local trace_bugs=false registertracker("otf.bugs",function(v) trace_bugs=v end)  local trace_details=false registertracker("otf.details",function(v) trace_details=v end) -local trace_applied=false registertracker("otf.applied",function(v) trace_applied=v end)  local trace_steps=false registertracker("otf.steps",function(v) trace_steps=v end)  local trace_skips=false registertracker("otf.skips",function(v) trace_skips=v end)  local trace_directions=false registertracker("otf.directions",function(v) trace_directions=v end) @@ -18386,7 +18110,6 @@ local report_chain=logs.reporter("fonts","otf chain")  local report_process=logs.reporter("fonts","otf process")  local report_warning=logs.reporter("fonts","otf warning")  local report_run=logs.reporter("fonts","otf run") -local report_check=logs.reporter("fonts","otf check")  registertracker("otf.replacements","otf.singles,otf.multiples,otf.alternatives,otf.ligatures")  registertracker("otf.positions","otf.marks,otf.kerns,otf.cursive")  registertracker("otf.actions","otf.replacements,otf.positions") @@ -18417,10 +18140,7 @@ local getdisc=nuts.getdisc  local setdisc=nuts.setdisc  local setlink=nuts.setlink  local ischar=nuts.is_char -local insert_node_before=nuts.insert_before  local insert_node_after=nuts.insert_after -local delete_node=nuts.delete -local remove_node=nuts.remove  local copy_node=nuts.copy  local copy_node_list=nuts.copy_list  local find_node_tail=nuts.tail @@ -18459,7 +18179,7 @@ local getligaindex=injections.getligaindex  local cursonce=true  local fonthashes=fonts.hashes  local fontdata=fonthashes.identifiers -local otffeatures=fonts.constructors.newfeatures("otf") +local otffeatures=fonts.constructors.features.otf  local registerotffeature=otffeatures.register  local onetimemessage=fonts.loggers.onetimemessage or function() end  otf.defaultnodealternate="none" @@ -20815,7 +20535,7 @@ local function c_run_single(head,font,attr,lookupcache,step,dataset,sequence,rlm    while start do      local char=ischar(start,font)      if char then -      local a=getattr(start,0) +      local a=attr and getattr(start,0)        if not a or (a==attr) then          local lookupmatch=lookupcache[char]          if lookupmatch then @@ -20845,7 +20565,7 @@ local function t_run_single(start,stop,font,attr,lookupcache)    while start~=stop do      local char=ischar(start,font)      if char then -      local a=getattr(start,0) +      local a=attr and getattr(start,0)        if not a or (a==attr) then          local lookupmatch=lookupcache[char]          if lookupmatch then @@ -20878,7 +20598,7 @@ local function t_run_single(start,stop,font,attr,lookupcache)    end  end  local function k_run_single(sub,injection,last,font,attr,lookupcache,step,dataset,sequence,rlmode,handler) -  local a=getattr(sub,0) +  local a=attr and getattr(sub,0)    if not a or (a==attr) then      for n in traverse_nodes(sub) do         if n==last then @@ -20909,7 +20629,7 @@ local function c_run_multiple(head,font,attr,steps,nofsteps,dataset,sequence,rlm    while start do      local char=ischar(start,font)      if char then -      local a=getattr(start,0) +      local a=attr and getattr(start,0)        if not a or (a==attr) then          for i=1,nofsteps do            local step=steps[i] @@ -20950,7 +20670,7 @@ local function t_run_multiple(start,stop,font,attr,steps,nofsteps)    while start~=stop do      local char=ischar(start,font)      if char then -      local a=getattr(start,0) +      local a=attr and getattr(start,0)        if not a or (a==attr) then          for i=1,nofsteps do            local step=steps[i] @@ -20991,7 +20711,7 @@ local function t_run_multiple(start,stop,font,attr,steps,nofsteps)    end  end  local function k_run_multiple(sub,injection,last,font,attr,steps,nofsteps,dataset,sequence,rlmode,handler) -  local a=getattr(sub,0) +  local a=attr and getattr(sub,0)    if not a or (a==attr) then      for n in traverse_nodes(sub) do         if n==last then @@ -21110,7 +20830,7 @@ local function featuresprocessor(head,font,attr)        while start do          local char=ischar(start,font)          if char then -          local a=getattr(start,0) +          local a=attr and getattr(start,0)            if not a or (a==attr) then              for i=1,nofsteps do                local step=steps[i] @@ -21151,7 +20871,7 @@ local function featuresprocessor(head,font,attr)            while start do              local char,id=ischar(start,font)              if char then -              local a=getattr(start,0) +              local a=attr and getattr(start,0)                if a then                  a=(a==attr) and (not attribute or getprop(start,a_state)==attribute)                else @@ -21201,7 +20921,7 @@ local function featuresprocessor(head,font,attr)          while start do            local char,id=ischar(start,font)            if char then -            local a=getattr(start,0) +            local a=attr and getattr(start,0)              if a then                a=(a==attr) and (not attribute or getprop(start,a_state)==attribute)              else @@ -21284,6 +21004,8 @@ registerotffeature {      node=featuresprocessor,    }  } +otf.nodemodeinitializer=featuresinitializer +otf.featuresprocessor=featuresprocessor  otf.handlers=handlers  local setspacekerns=nodes.injections.setspacekerns if not setspacekerns then os.exit() end  function otf.handlers.trigger_space_kerns(head,start,dataset,sequence,_,_,_,_,font,attr) @@ -21424,11 +21146,9 @@ fonts=fonts          or {}  fonts.analyzers=fonts.analyzers     or {}  fonts.analyzers.methods=fonts.analyzers.methods or { node={ otf={} } }  local otf=fonts.handlers.otf -local nodecodes=nodes.nodecodes -local glyph_code=nodecodes.glyph  local handlers=otf.handlers  local methods=fonts.analyzers.methods -local otffeatures=fonts.constructors.newfeatures("otf") +local otffeatures=fonts.constructors.features.otf  local registerotffeature=otffeatures.register  local nuts=nodes.nuts  local tonode=nuts.tonode @@ -23381,6 +23101,1411 @@ end -- closure  do -- begin closure to overcome local limits and interference +if not modules then modules={} end modules ['font-ocl']={ +  version=1.001, +  comment="companion to font-otf.lua (context)", +  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", +  copyright="PRAGMA ADE / ConTeXt Development Team", +  license="see context related readme files" +} +local formatters=string.formatters +local otf=fonts.handlers.otf +local f_color_start=formatters["pdf:direct: %f %f %f rg"] +local s_color_stop="pdf:direct:" +if context then +  local startactualtext=nil +  local stopactualtext=nil +  function otf.getactualtext(n) +    if not startactualtext then +      startactualtext=backends.codeinjections.startunicodetoactualtext +      stopactualtext=backends.codeinjections.stopunicodetoactualtext +    end +    return startactualtext(n),stopactualtext() +  end +else +  local tounicode=fonts.mappings.tounicode16 +  function otf.getactualtext(n) +    return "/Span << /ActualText <feff"..tounicode(n).."> >> BDC","EMC" +  end +end +local function initializecolr(tfmdata,kind,value)  +  if value then +    local palettes=tfmdata.resources.colorpalettes +    if palettes then +      local palette=palettes[tonumber(value) or 1] or palettes[1] or {} +      local classes=#palette +      if classes==0 then +        return +      end +      local characters=tfmdata.characters +      local descriptions=tfmdata.descriptions +      local properties=tfmdata.properties +      local colorvalues={} +      properties.virtualized=true +      tfmdata.fonts={ +        { id=0 } +      } +      for i=1,classes do +        local p=palette[i] +        colorvalues[i]={ "special",f_color_start(p[1]/255,p[2]/255,p[3]/255) } +      end +      local getactualtext=otf.getactualtext +      for unicode,character in next,characters do +        local description=descriptions[unicode] +        if description then +          local colorlist=description.colors +          if colorlist then +            local b,e=getactualtext(unicode) +            local w=character.width or 0 +            local s=#colorlist +            local n=1 +            local t={ +              { "special","pdf:direct: q "..b } +            } +            for i=1,s do +              local entry=colorlist[i] +              n=n+1 t[n]=colorvalues[entry.class] +              n=n+1 t[n]={ "char",entry.slot } +              if s>1 and i<s and w~=0 then +                n=n+1 t[n]={ "right",-w } +              end +            end +            n=n+1 t[n]={ "special","pdf:direct:"..e.." Q" } +            character.commands=t +          end +        end +      end +    end +  end +end +fonts.handlers.otf.features.register { +  name="colr", +  description="color glyphs", +  manipulators={ +    base=initializecolr, +    node=initializecolr, +  } +} +local otfsvg=otf.svg or {} +otf.svg=otfsvg +otf.svgenabled=true +do +  local nofstreams=0 +  local f_name=formatters[ [[svg-glyph-%05i]] ] +  local f_used=context and formatters[ [[original:///%s]] ] or formatters[ [[%s]] ] +  local cache={} +  function otfsvg.storepdfdata(pdf) +    nofstreams=nofstreams+1 +    local o,n=epdf.openMemStream(pdf,#pdf,f_name(nofstreams)) +    cache[n]=o  +    return nil,f_used(n),nil +  end +  if context then +    local storepdfdata=otfsvg.storepdfdata +    local initialized=false +    function otfsvg.storepdfdata(pdf) +      if not initialized then +        if resolvers.setmemstream then +          local f_setstream=formatters[ [[resolvers.setmemstream("svg-glyph-%05i",%q,true)]] ] +          local f_getstream=formatters[ [[memstream:///svg-glyph-%05i]] ] +          local f_nilstream=formatters[ [[resolvers.resetmemstream("svg-glyph-%05i",true)]] ] +          storepdfdata=function(pdf) +            nofstreams=nofstreams+1 +            return +              f_setstream(nofstreams,pdf), +              f_getstream(nofstreams), +              f_nilstream(nofstreams) +          end +          otfsvg.storepdfdata=storepdfdata +        end +        initialized=true +      end +      return storepdfdata(pdf) +    end +  end +end +if context and xml.convert then +  local report_svg=logs.reporter("fonts","svg conversion") +  function otfsvg.topdf(svgshapes) +    local svgfile="temp-otf-svg-shape.svg" +    local pdffile="temp-otf-svg-shape.pdf" +    local command="inkscape "..svgfile.." --export-pdf="..pdffile +    local testrun=false +    local pdfshapes={} +    local nofshapes=#svgshapes +    report_svg("processing %i svg containers",nofshapes) +    for i=1,nofshapes do +      local entry=svgshapes[i] +      for j=entry.first,entry.last do +        local svg=xml.convert(entry.data) +        local data=xml.first(svg,"/svg[@id='glyph"..j.."']") +        io.savedata(svgfile,tostring(data)) +        report_svg("processing svg shape of glyph %i in container %i",j,i) +        os.execute(command) +        pdfshapes[j]=io.loaddata(pdffile) +      end +      if testrun and i>testrun then +        report_svg("quiting test run") +        break +      end +    end +    os.remove(svgfile) +    return pdfshapes +  end +else +  function otfsvg.topdf(svgshapes) +    local svgfile="temp-otf-svg-shape.svg" +    local pdffile="temp-otf-svg-shape.pdf" +    local command="inkscape "..svgfile.." --export-pdf="..pdffile +    local pdfshapes={} +    local nofshapes=#svgshapes +    texio.write(formatters["[converting %i svg glyphs to pdf using command %q : "](nofshapes,command)) +    for i=1,nofshapes do +      local entry=svgshapes[i] +      for j=entry.first,entry.last do +        texio.write(formatters["%i "](j)) +        io.savedata(svgfile,tostring(entry.data)) +        os.execute(command) +        pdfshapes[j]=io.loaddata(pdffile) +      end +    end +    os.remove(svgfile) +    texio.write("done]") +    return pdfshapes +  end +end +local function initializesvg(tfmdata,kind,value)  +  if value and otf.svgenabled then +    local characters=tfmdata.characters +    local descriptions=tfmdata.descriptions +    local properties=tfmdata.properties +    local svg=properties.svg +    local hash=svg and svg.hash +    local timestamp=svg and svg.timestamp +    if not hash then +      return +    end +    local pdffile=containers.read(otf.pdfcache,hash) +    local pdfshapes=pdffile and pdffile.pdfshapes +    if not pdfshapes or pdffile.timestamp~=timestamp then +      local svgfile=containers.read(otf.svgcache,hash) +      local svgshapes=svgfile and svgfile.svgshapes +      pdfshapes=svgshapes and otfsvg.topdf(svgshapes) or {} +      containers.write(otf.pdfcache,hash,{ +        pdfshapes=pdfshapes, +        timestamp=timestamp, +      }) +    end +    if not pdfshapes or not next(pdfshapes) then +      return +    end +    properties.virtualized=true +    tfmdata.fonts={ +      { id=0 } +    } +    local getactualtext=otf.getactualtext +    local storepdfdata=otfsvg.storepdfdata +    local nop={ "nop" } +    for unicode,character in next,characters do +      local index=character.index +      if index then +        local pdf=pdfshapes[index] +        if pdf then +          local setcode,name,nilcode=storepdfdata(pdf) +          if name then +            local bt,et=getactualtext(unicode) +            local wd=character.width or 0 +            local ht=character.height or 0 +            local dp=character.depth or 0 +            character.commands={ +              { "special","pdf:direct:"..bt }, +              { "down",dp }, +              setcode and { "lua",setcode } or nop, +              { "image",{ filename=name,width=wd,height=ht,depth=dp } }, +              nilcode and { "lua",nilcode } or nop, +              { "special","pdf:direct:"..et }, +            } +            character.svg=true +          end +        end +      end +    end +  end +end +fonts.handlers.otf.features.register { +  name="svg", +  description="svg glyphs", +  manipulators={ +    base=initializesvg, +    node=initializesvg, +  } +} + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-onr']={ +  version=1.001, +  comment="companion to font-ini.mkiv", +  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", +  copyright="PRAGMA ADE / ConTeXt Development Team", +  license="see context related readme files" +} +local fonts,logs,trackers,resolvers=fonts,logs,trackers,resolvers +local next,type,tonumber,rawget=next,type,tonumber,rawget +local match,lower,gsub,strip,find=string.match,string.lower,string.gsub,string.strip,string.find +local char,byte,sub=string.char,string.byte,string.sub +local abs=math.abs +local bxor,rshift=bit32.bxor,bit32.rshift +local P,S,R,Cmt,C,Ct,Cs,Carg=lpeg.P,lpeg.S,lpeg.R,lpeg.Cmt,lpeg.C,lpeg.Ct,lpeg.Cs,lpeg.Carg +local lpegmatch,patterns=lpeg.match,lpeg.patterns +local trace_indexing=false trackers.register("afm.indexing",function(v) trace_indexing=v end) +local trace_loading=false trackers.register("afm.loading",function(v) trace_loading=v end) +local report_afm=logs.reporter("fonts","afm loading") +local report_afm=logs.reporter("fonts","pfb loading") +fonts=fonts or {} +local handlers=fonts.handlers or {} +fonts.handlers=handlers +local afm=handlers.afm or {} +handlers.afm=afm +local readers=afm.readers or {} +afm.readers=readers +afm.version=1.512 +local get_indexes +do +  local n,m +  local progress=function(str,position,name,size) +    local forward=position+tonumber(size)+3+2 +    n=n+1 +    if n>=m then +      return #str,name +    elseif forward<#str then +      return forward,name +    else +      return #str,name +    end +  end +  local initialize=function(str,position,size) +    n=0 +    m=tonumber(size) +    return position+1 +  end +  local charstrings=P("/CharStrings") +  local name=P("/")*C((R("az")+R("AZ")+R("09")+S("-_."))^1) +  local size=C(R("09")^1) +  local spaces=P(" ")^1 +  local p_filternames=Ct ( +    (1-charstrings)^0*charstrings*spaces*Cmt(size,initialize)*(Cmt(name*P(" ")^1*C(R("09")^1),progress)+P(1))^1 +  ) +  local decrypt +  do +    local r,c1,c2,n=0,0,0,0 +    local function step(c) +      local cipher=byte(c) +      local plain=bxor(cipher,rshift(r,8)) +      r=((cipher+r)*c1+c2)%65536 +      return char(plain) +    end +    decrypt=function(binary) +      r,c1,c2,n=55665,52845,22719,4 +      binary=gsub(binary,".",step) +      return sub(binary,n+1) +    end +  end +  local function loadpfbvector(filename) +    local data=io.loaddata(resolvers.findfile(filename)) +    if not data then +      report_pfb("no data in %a",filename) +      return +    end +    if not (find(data,"!PS%-AdobeFont%-") or find(data,"%%!FontType1")) then +      report_pfb("no font in %a",filename) +      return +    end +    local ascii,binary=match(data,"(.*)eexec%s+......(.*)") +    if not binary then +      report_pfb("no binary data in %a",filename) +      return +    end +    binary=decrypt(binary,4) +    local vector=lpegmatch(p_filternames,binary) +    if vector[1]==".notdef" then +      vector[0]=table.remove(vector,1) +    end +    if not vector then +      report_pfb("no vector in %a",filename) +      return +    end +    return vector +  end +  get_indexes=function(data,pfbname) +    local vector=loadpfbvector(pfbname) +    if vector then +      local characters=data.characters +      if trace_loading then +        report_afm("getting index data from %a",pfbname) +      end +      for index=1,#vector do +        local name=vector[index] +        local char=characters[name] +        if char then +          if trace_indexing then +            report_afm("glyph %a has index %a",name,index) +          end +          char.index=index +        end +      end +    end +  end +end +local spacer=patterns.spacer +local whitespace=patterns.whitespace +local lineend=patterns.newline +local spacing=spacer^0 +local number=spacing*S("+-")^-1*(R("09")+S("."))^1/tonumber +local name=spacing*C((1-whitespace)^1) +local words=spacing*((1-lineend)^1/strip) +local rest=(1-lineend)^0 +local fontdata=Carg(1) +local semicolon=spacing*P(";") +local plus=spacing*P("plus")*number +local minus=spacing*P("minus")*number +local function addkernpair(data,one,two,value) +  local chr=data.characters[one] +  if chr then +    local kerns=chr.kerns +    if kerns then +      kerns[two]=tonumber(value) +    else +      chr.kerns={ [two]=tonumber(value) } +    end +  end +end +local p_kernpair=(fontdata*P("KPX")*name*name*number)/addkernpair +local chr=false +local ind=0 +local function start(data,version) +  data.metadata.afmversion=version +  ind=0 +  chr={} +end +local function stop() +  ind=0 +  chr=false +end +local function setindex(i) +  if i<0 then +    ind=ind+1  +  else +    ind=i +  end +  chr={ +    index=ind +  } +end +local function setwidth(width) +  chr.width=width +end +local function setname(data,name) +  data.characters[name]=chr +end +local function setboundingbox(boundingbox) +  chr.boundingbox=boundingbox +end +local function setligature(plus,becomes) +  local ligatures=chr.ligatures +  if ligatures then +    ligatures[plus]=becomes +  else +    chr.ligatures={ [plus]=becomes } +  end +end +local p_charmetric=(( +  P("C")*number/setindex+P("WX")*number/setwidth+P("N")*fontdata*name/setname+P("B")*Ct((number)^4)/setboundingbox+P("L")*(name)^2/setligature + )*semicolon )^1 +local p_charmetrics=P("StartCharMetrics")*number*(p_charmetric+(1-P("EndCharMetrics")))^0*P("EndCharMetrics") +local p_kernpairs=P("StartKernPairs")*number*(p_kernpair+(1-P("EndKernPairs" )))^0*P("EndKernPairs" ) +local function set_1(data,key,a)   data.metadata[lower(key)]=a      end +local function set_2(data,key,a,b)  data.metadata[lower(key)]={ a,b }  end +local function set_3(data,key,a,b,c) data.metadata[lower(key)]={ a,b,c } end +local p_parameters=P(false)+fontdata*((P("FontName")+P("FullName")+P("FamilyName"))/lower)*words/function(data,key,value) +    data.metadata[key]=value +  end+fontdata*((P("Weight")+P("Version"))/lower)*name/function(data,key,value) +    data.metadata[key]=value +  end+fontdata*P("IsFixedPitch")*name/function(data,pitch) +    data.metadata.monospaced=toboolean(pitch,true) +  end+fontdata*P("FontBBox")*Ct(number^4)/function(data,boundingbox) +    data.metadata.boundingbox=boundingbox + end+fontdata*((P("CharWidth")+P("CapHeight")+P("XHeight")+P("Descender")+P("Ascender")+P("ItalicAngle"))/lower)*number/function(data,key,value) +    data.metadata[key]=value +  end+P("Comment")*spacing*(P(false)+(fontdata*C("DESIGNSIZE")*number*rest)/set_1  ++(fontdata*C("TFM designsize")*number*rest)/set_1+(fontdata*C("DesignSize")*number*rest)/set_1+(fontdata*C("CODINGSCHEME")*words*rest)/set_1  ++(fontdata*C("CHECKSUM")*number*words*rest)/set_1  ++(fontdata*C("SPACE")*number*plus*minus*rest)/set_3  ++(fontdata*C("QUAD")*number*rest)/set_1  ++(fontdata*C("EXTRASPACE")*number*rest)/set_1  ++(fontdata*C("NUM")*number*number*number*rest)/set_3  ++(fontdata*C("DENOM")*number*number*rest)/set_2  ++(fontdata*C("SUP")*number*number*number*rest)/set_3  ++(fontdata*C("SUB")*number*number*rest)/set_2  ++(fontdata*C("SUPDROP")*number*rest)/set_1  ++(fontdata*C("SUBDROP")*number*rest)/set_1  ++(fontdata*C("DELIM")*number*number*rest)/set_2  ++(fontdata*C("AXISHEIGHT")*number*rest)/set_1  +  ) +local fullparser=(P("StartFontMetrics")*fontdata*name/start )*(p_charmetrics+p_kernpairs+p_parameters+(1-P("EndFontMetrics")) )^0*(P("EndFontMetrics")/stop ) +local fullparser=(P("StartFontMetrics")*fontdata*name/start )*(p_charmetrics+p_kernpairs+p_parameters+(1-P("EndFontMetrics")) )^0*(P("EndFontMetrics")/stop ) +local infoparser=(P("StartFontMetrics")*fontdata*name/start )*(p_parameters+(1-P("EndFontMetrics")) )^0*(P("EndFontMetrics")/stop ) +local function read(filename,parser) +  local afmblob=io.loaddata(filename) +  if afmblob then +    local data={ +      resources={ +        filename=resolvers.unresolve(filename), +        version=afm.version, +        creator="context mkiv", +      }, +      properties={ +        hasitalics=false, +      }, +      goodies={}, +      metadata={ +        filename=file.removesuffix(file.basename(filename)) +      }, +      characters={ +      }, +      descriptions={ +      }, +    } +    if trace_loading then +      report_afm("parsing afm file %a",filename) +    end +    lpegmatch(parser,afmblob,1,data) +    return data +  else +    if trace_loading then +      report_afm("no valid afm file %a",filename) +    end +    return nil +  end +end +function readers.loadfont(afmname,pfbname) +  local data=read(resolvers.findfile(afmname),fullparser) +  if data then +    if not pfbname or pfbname=="" then +      pfbname=file.replacesuffix(file.nameonly(afmname),"pfb") +      pfbname=resolvers.findfile(pfbname) +    end +    if pfbname and pfbname~="" then +      data.resources.filename=resolvers.unresolve(pfbname) +      get_indexes(data,pfbname) +    elseif trace_loading then +      report_afm("no pfb file for %a",afmname) +    end +    return data +  end +end +function readers.getinfo(filename) +  local data=read(resolvers.findfile(filename),infoparser) +  if data then +    return data.metadata +  end +end + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-one']={ +  version=1.001, +  comment="companion to font-ini.mkiv", +  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", +  copyright="PRAGMA ADE / ConTeXt Development Team", +  license="see context related readme files" +} +local fonts,logs,trackers,containers,resolvers=fonts,logs,trackers,containers,resolvers +local next,type,tonumber,rawget=next,type,tonumber,rawget +local match,gmatch,lower,gsub,strip,find=string.match,string.gmatch,string.lower,string.gsub,string.strip,string.find +local char,byte,sub=string.char,string.byte,string.sub +local abs=math.abs +local bxor,rshift=bit32.bxor,bit32.rshift +local P,S,R,Cmt,C,Ct,Cs,Carg=lpeg.P,lpeg.S,lpeg.R,lpeg.Cmt,lpeg.C,lpeg.Ct,lpeg.Cs,lpeg.Carg +local lpegmatch,patterns=lpeg.match,lpeg.patterns +local trace_features=false trackers.register("afm.features",function(v) trace_features=v end) +local trace_indexing=false trackers.register("afm.indexing",function(v) trace_indexing=v end) +local trace_loading=false trackers.register("afm.loading",function(v) trace_loading=v end) +local trace_defining=false trackers.register("fonts.defining",function(v) trace_defining=v end) +local report_afm=logs.reporter("fonts","afm loading") +local setmetatableindex=table.setmetatableindex +local derivetable=table.derive +local findbinfile=resolvers.findbinfile +local definers=fonts.definers +local readers=fonts.readers +local constructors=fonts.constructors +local afm=constructors.handlers.afm +local pfb=constructors.handlers.pfb +local otf=fonts.handlers.otf +local otfreaders=otf.readers +local otfenhancers=otf.enhancers +local afmfeatures=constructors.features.afm +local registerafmfeature=afmfeatures.register +afm.version=1.512  +afm.cache=containers.define("fonts","afm",afm.version,true) +afm.autoprefixed=true  +afm.helpdata={}  +afm.syncspace=true  +local overloads=fonts.mappings.overloads +local applyruntimefixes=fonts.treatments and fonts.treatments.applyfixes +local enhancers={ +} +local steps={ +  "unify names", +  "add ligatures", +  "add extra kerns", +  "normalize features", +  "fix names", +} +local function applyenhancers(data,filename) +  for i=1,#steps do +    local step=steps[i] +    local enhancer=enhancers[step] +    if enhancer then +      if trace_loading then +        report_afm("applying enhancer %a",step) +      end +      enhancer(data,filename) +    else +      report_afm("invalid enhancer %a",step) +    end +  end +end +function afm.load(filename) +  filename=resolvers.findfile(filename,'afm') or "" +  if filename~="" and not fonts.names.ignoredfile(filename) then +    local name=file.removesuffix(file.basename(filename)) +    local data=containers.read(afm.cache,name) +    local attr=lfs.attributes(filename) +    local size,time=attr.size or 0,attr.modification or 0 +    local pfbfile=file.replacesuffix(name,"pfb") +    local pfbname=resolvers.findfile(pfbfile,"pfb") or "" +    if pfbname=="" then +      pfbname=resolvers.findfile(file.basename(pfbfile),"pfb") or "" +    end +    local pfbsize,pfbtime=0,0 +    if pfbname~="" then +      local attr=lfs.attributes(pfbname) +      pfbsize=attr.size or 0 +      pfbtime=attr.modification or 0 +    end +    if not data or data.size~=size or data.time~=time or data.pfbsize~=pfbsize or data.pfbtime~=pfbtime then +      report_afm("reading %a",filename) +      data=afm.readers.loadfont(filename,pfbname) +      if data then +        applyenhancers(data,filename) +        fonts.mappings.addtounicode(data,filename) +        otfreaders.pack(data) +        data.size=size +        data.time=time +        data.pfbsize=pfbsize +        data.pfbtime=pfbtime +        report_afm("saving %a in cache",name) +        data=containers.write(afm.cache,name,data) +        data=containers.read(afm.cache,name) +      end +    end +    if data then +      otfreaders.unpack(data) +      otfreaders.expand(data)  +      otfreaders.addunicodetable(data)  +      otfenhancers.apply(data,filename,data) +      if applyruntimefixes then +        applyruntimefixes(filename,data) +      end +    end +    return data +  end +end +local uparser=fonts.mappings.makenameparser()  +enhancers["unify names"]=function(data,filename) +  local unicodevector=fonts.encodings.agl.unicodes  +  local unicodes={} +  local names={} +  local private=constructors.privateoffset +  local descriptions=data.descriptions +  for name,blob in next,data.characters do +    local code=unicodevector[name]  +    if not code then +      code=lpegmatch(uparser,name) +      if type(code)~="number" then +        code=private +        private=private+1 +        report_afm("assigning private slot %U for unknown glyph name %a",code,name) +      end +    end +    local index=blob.index +    unicodes[name]=code +    names[name]=index +    blob.name=name +    descriptions[code]={ +      boundingbox=blob.boundingbox, +      width=blob.width, +      kerns=blob.kerns, +      index=index, +      name=name, +    } +  end +  for unicode,description in next,descriptions do +    local kerns=description.kerns +    if kerns then +      local krn={} +      for name,kern in next,kerns do +        local unicode=unicodes[name] +        if unicode then +          krn[unicode]=kern +        else +        end +      end +      description.kerns=krn +    end +  end +  data.characters=nil +  local resources=data.resources +  local filename=resources.filename or file.removesuffix(file.basename(filename)) +  resources.filename=resolvers.unresolve(filename)  +  resources.unicodes=unicodes  +  resources.marks={} +  resources.private=private +end +local everywhere={ ["*"]={ ["*"]=true } }  +local noflags={ false,false,false,false } +enhancers["normalize features"]=function(data) +  local ligatures=setmetatableindex("table") +  local kerns=setmetatableindex("table") +  local extrakerns=setmetatableindex("table") +  for u,c in next,data.descriptions do +    local l=c.ligatures +    local k=c.kerns +    local e=c.extrakerns +    if l then +      ligatures[u]=l +      for u,v in next,l do +        l[u]={ ligature=v } +      end +      c.ligatures=nil +    end +    if k then +      kerns[u]=k +      for u,v in next,k do +        k[u]=v  +      end +      c.kerns=nil +    end +    if e then +      extrakerns[u]=e +      for u,v in next,e do +        e[u]=v  +      end +      c.extrakerns=nil +    end +  end +  local features={ +    gpos={}, +    gsub={}, +  } +  local sequences={ +  } +  if next(ligatures) then +    features.gsub.liga=everywhere +    data.properties.hasligatures=true +    sequences[#sequences+1]={ +      features={ +        liga=everywhere, +      }, +      flags=noflags, +      name="s_s_0", +      nofsteps=1, +      order={ "liga" }, +      type="gsub_ligature", +      steps={ +        { +          coverage=ligatures, +        }, +      }, +    } +  end +  if next(kerns) then +    features.gpos.kern=everywhere +    data.properties.haskerns=true +    sequences[#sequences+1]={ +      features={ +        kern=everywhere, +      }, +      flags=noflags, +      name="p_s_0", +      nofsteps=1, +      order={ "kern" }, +      type="gpos_pair", +      steps={ +        { +          format="kern", +          coverage=kerns, +        }, +      }, +    } +  end +  if next(extrakerns) then +    features.gpos.extrakerns=everywhere +    data.properties.haskerns=true +    sequences[#sequences+1]={ +      features={ +        extrakerns=everywhere, +      }, +      flags=noflags, +      name="p_s_1", +      nofsteps=1, +      order={ "extrakerns" }, +      type="gpos_pair", +      steps={ +        { +          format="kern", +          coverage=extrakerns, +        }, +      }, +    } +  end +  data.resources.features=features +  data.resources.sequences=sequences +end +enhancers["fix names"]=function(data) +  for k,v in next,data.descriptions do +    local n=v.name +    local r=overloads[n] +    if r then +      local name=r.name +      if trace_indexing then +        report_afm("renaming characters %a to %a",n,name) +      end +      v.name=name +      v.unicode=r.unicode +    end +  end +end +local addthem=function(rawdata,ligatures) +  if ligatures then +    local descriptions=rawdata.descriptions +    local resources=rawdata.resources +    local unicodes=resources.unicodes +    for ligname,ligdata in next,ligatures do +      local one=descriptions[unicodes[ligname]] +      if one then +        for _,pair in next,ligdata do +          local two,three=unicodes[pair[1]],unicodes[pair[2]] +          if two and three then +            local ol=one.ligatures +            if ol then +              if not ol[two] then +                ol[two]=three +              end +            else +              one.ligatures={ [two]=three } +            end +          end +        end +      end +    end +  end +end +enhancers["add ligatures"]=function(rawdata) +  addthem(rawdata,afm.helpdata.ligatures) +end +enhancers["add extra kerns"]=function(rawdata)  +  local descriptions=rawdata.descriptions +  local resources=rawdata.resources +  local unicodes=resources.unicodes +  local function do_it_left(what) +    if what then +      for unicode,description in next,descriptions do +        local kerns=description.kerns +        if kerns then +          local extrakerns +          for complex,simple in next,what do +            complex=unicodes[complex] +            simple=unicodes[simple] +            if complex and simple then +              local ks=kerns[simple] +              if ks and not kerns[complex] then +                if extrakerns then +                  extrakerns[complex]=ks +                else +                  extrakerns={ [complex]=ks } +                end +              end +            end +          end +          if extrakerns then +            description.extrakerns=extrakerns +          end +        end +      end +    end +  end +  local function do_it_copy(what) +    if what then +      for complex,simple in next,what do +        complex=unicodes[complex] +        simple=unicodes[simple] +        if complex and simple then +          local complexdescription=descriptions[complex] +          if complexdescription then  +            local simpledescription=descriptions[complex] +            if simpledescription then +              local extrakerns +              local kerns=simpledescription.kerns +              if kerns then +                for unicode,kern in next,kerns do +                  if extrakerns then +                    extrakerns[unicode]=kern +                  else +                    extrakerns={ [unicode]=kern } +                  end +                end +              end +              local extrakerns=simpledescription.extrakerns +              if extrakerns then +                for unicode,kern in next,extrakerns do +                  if extrakerns then +                    extrakerns[unicode]=kern +                  else +                    extrakerns={ [unicode]=kern } +                  end +                end +              end +              if extrakerns then +                complexdescription.extrakerns=extrakerns +              end +            end +          end +        end +      end +    end +  end +  do_it_left(afm.helpdata.leftkerned) +  do_it_left(afm.helpdata.bothkerned) +  do_it_copy(afm.helpdata.bothkerned) +  do_it_copy(afm.helpdata.rightkerned) +end +local function adddimensions(data)  +  if data then +    for unicode,description in next,data.descriptions do +      local bb=description.boundingbox +      if bb then +        local ht,dp=bb[4],-bb[2] +        if ht==0 or ht<0 then +        else +          description.height=ht +        end +        if dp==0 or dp<0 then +        else +          description.depth=dp +        end +      end +    end +  end +end +local function copytotfm(data) +  if data and data.descriptions then +    local metadata=data.metadata +    local resources=data.resources +    local properties=derivetable(data.properties) +    local descriptions=derivetable(data.descriptions) +    local goodies=derivetable(data.goodies) +    local characters={} +    local parameters={} +    local unicodes=resources.unicodes +    for unicode,description in next,data.descriptions do  +      characters[unicode]={} +    end +    local filename=constructors.checkedfilename(resources) +    local fontname=metadata.fontname or metadata.fullname +    local fullname=metadata.fullname or metadata.fontname +    local endash=0x0020  +    local emdash=0x2014 +    local spacer="space" +    local spaceunits=500 +    local monospaced=metadata.monospaced +    local charwidth=metadata.charwidth +    local italicangle=metadata.italicangle +    local charxheight=metadata.xheight and metadata.xheight>0 and metadata.xheight +    properties.monospaced=monospaced +    parameters.italicangle=italicangle +    parameters.charwidth=charwidth +    parameters.charxheight=charxheight +    if properties.monospaced then +      if descriptions[endash] then +        spaceunits,spacer=descriptions[endash].width,"space" +      end +      if not spaceunits and descriptions[emdash] then +        spaceunits,spacer=descriptions[emdash].width,"emdash" +      end +      if not spaceunits and charwidth then +        spaceunits,spacer=charwidth,"charwidth" +      end +    else +      if descriptions[endash] then +        spaceunits,spacer=descriptions[endash].width,"space" +      end +      if not spaceunits and charwidth then +        spaceunits,spacer=charwidth,"charwidth" +      end +    end +    spaceunits=tonumber(spaceunits) +    if spaceunits<200 then +    end +    parameters.slant=0 +    parameters.space=spaceunits +    parameters.space_stretch=500 +    parameters.space_shrink=333 +    parameters.x_height=400 +    parameters.quad=1000 +    if italicangle and italicangle~=0 then +      parameters.italicangle=italicangle +      parameters.italicfactor=math.cos(math.rad(90+italicangle)) +      parameters.slant=- math.tan(italicangle*math.pi/180) +    end +    if monospaced then +      parameters.space_stretch=0 +      parameters.space_shrink=0 +    elseif afm.syncspace then +      parameters.space_stretch=spaceunits/2 +      parameters.space_shrink=spaceunits/3 +    end +    parameters.extra_space=parameters.space_shrink +    if charxheight then +      parameters.x_height=charxheight +    else +      local x=0x0078  +      if x then +        local x=descriptions[x] +        if x then +          parameters.x_height=x.height +        end +      end +    end +    if metadata.sup then +      local dummy={ 0,0,0 } +      parameters[ 1]=metadata.designsize    or 0 +      parameters[ 2]=metadata.checksum     or 0 +      parameters[ 3], +      parameters[ 4], +      parameters[ 5]=unpack(metadata.space   or dummy) +      parameters[ 6]=metadata.quad    or 0 +      parameters[ 7]=metadata.extraspace or 0 +      parameters[ 8], +      parameters[ 9], +      parameters[10]=unpack(metadata.num    or dummy) +      parameters[11], +      parameters[12]=unpack(metadata.denom   or dummy) +      parameters[13], +      parameters[14], +      parameters[15]=unpack(metadata.sup    or dummy) +      parameters[16], +      parameters[17]=unpack(metadata.sub    or dummy) +      parameters[18]=metadata.supdrop  or 0 +      parameters[19]=metadata.subdrop  or 0 +      parameters[20], +      parameters[21]=unpack(metadata.delim   or dummy) +      parameters[22]=metadata.axisheight or 0 +    end +    parameters.designsize=(metadata.designsize or 10)*65536 +    parameters.ascender=abs(metadata.ascender or 0) +    parameters.descender=abs(metadata.descender or 0) +    parameters.units=1000 +    properties.spacer=spacer +    properties.encodingbytes=2 +    properties.format=fonts.formats[filename] or "type1" +    properties.filename=filename +    properties.fontname=fontname +    properties.fullname=fullname +    properties.psname=fullname +    properties.name=filename or fullname or fontname +    if next(characters) then +      return { +        characters=characters, +        descriptions=descriptions, +        parameters=parameters, +        resources=resources, +        properties=properties, +        goodies=goodies, +      } +    end +  end +  return nil +end +function afm.setfeatures(tfmdata,features) +  local okay=constructors.initializefeatures("afm",tfmdata,features,trace_features,report_afm) +  if okay then +    return constructors.collectprocessors("afm",tfmdata,features,trace_features,report_afm) +  else +    return {}  +  end +end +local function addtables(data) +  local resources=data.resources +  local lookuptags=resources.lookuptags +  local unicodes=resources.unicodes +  if not lookuptags then +    lookuptags={} +    resources.lookuptags=lookuptags +  end +  setmetatableindex(lookuptags,function(t,k) +    local v=type(k)=="number" and ("lookup "..k) or k +    t[k]=v +    return v +  end) +  if not unicodes then +    unicodes={} +    resources.unicodes=unicodes +    setmetatableindex(unicodes,function(t,k) +      setmetatableindex(unicodes,nil) +      for u,d in next,data.descriptions do +        local n=d.name +        if n then +          t[n]=u +        end +      end +      return rawget(t,k) +    end) +  end +  constructors.addcoreunicodes(unicodes)  +end +local function afmtotfm(specification) +  local afmname=specification.filename or specification.name +  if specification.forced=="afm" or specification.format=="afm" then  +    if trace_loading then +      report_afm("forcing afm format for %a",afmname) +    end +  else +    local tfmname=findbinfile(afmname,"ofm") or "" +    if tfmname~="" then +      if trace_loading then +        report_afm("fallback from afm to tfm for %a",afmname) +      end +      return  +    end +  end +  if afmname~="" then +    local features=constructors.checkedfeatures("afm",specification.features.normal) +    specification.features.normal=features +    constructors.hashinstance(specification,true) +    specification=definers.resolve(specification)  +    local cache_id=specification.hash +    local tfmdata=containers.read(constructors.cache,cache_id)  +    if not tfmdata then +      local rawdata=afm.load(afmname) +      if rawdata and next(rawdata) then +        addtables(rawdata) +        adddimensions(rawdata) +        tfmdata=copytotfm(rawdata) +        if tfmdata and next(tfmdata) then +          local shared=tfmdata.shared +          if not shared then +            shared={} +            tfmdata.shared=shared +          end +          shared.rawdata=rawdata +          shared.dynamics={} +          tfmdata.changed={} +          shared.features=features +          shared.processes=afm.setfeatures(tfmdata,features) +        end +      elseif trace_loading then +        report_afm("no (valid) afm file found with name %a",afmname) +      end +      tfmdata=containers.write(constructors.cache,cache_id,tfmdata) +    end +    return tfmdata +  end +end +local function read_from_afm(specification) +  local tfmdata=afmtotfm(specification) +  if tfmdata then +    tfmdata.properties.name=specification.name +    tfmdata=constructors.scale(tfmdata,specification) +    local allfeatures=tfmdata.shared.features or specification.features.normal +    constructors.applymanipulators("afm",tfmdata,allfeatures,trace_features,report_afm) +    fonts.loggers.register(tfmdata,'afm',specification) +  end +  return tfmdata +end +local function setmode(tfmdata,value) +  if value then +    tfmdata.properties.mode=lower(value) +  end +end +registerafmfeature { +  name="mode", +  description="mode", +  initializers={ +    base=setmode, +    node=setmode, +  } +} +registerafmfeature { +  name="features", +  description="features", +  default=true, +  initializers={ +    node=otf.nodemodeinitializer, +    base=otf.basemodeinitializer, +  }, +  processors={ +    node=otf.featuresprocessor, +  } +} +local check_tfm=readers.check_tfm +fonts.formats.afm="type1" +fonts.formats.pfb="type1" +local function check_afm(specification,fullname) +  local foundname=findbinfile(fullname,'afm') or ""  +  if foundname=="" then +    foundname=fonts.names.getfilename(fullname,"afm") or "" +  end +  if foundname=="" and afm.autoprefixed then +    local encoding,shortname=match(fullname,"^(.-)%-(.*)$")  +    if encoding and shortname and fonts.encodings.known[encoding] then +      shortname=findbinfile(shortname,'afm') or ""  +      if shortname~="" then +        foundname=shortname +        if trace_defining then +          report_afm("stripping encoding prefix from filename %a",afmname) +        end +      end +    end +  end +  if foundname~="" then +    specification.filename=foundname +    specification.format="afm" +    return read_from_afm(specification) +  end +end +function readers.afm(specification,method) +  local fullname=specification.filename or "" +  local tfmdata=nil +  if fullname=="" then +    local forced=specification.forced or "" +    if forced~="" then +      tfmdata=check_afm(specification,specification.name.."."..forced) +    end +    if not tfmdata then +      method=method or definers.method or "afm or tfm" +      if method=="tfm" then +        tfmdata=check_tfm(specification,specification.name) +      elseif method=="afm" then +        tfmdata=check_afm(specification,specification.name) +      elseif method=="tfm or afm" then +        tfmdata=check_tfm(specification,specification.name) or check_afm(specification,specification.name) +      else  +        tfmdata=check_afm(specification,specification.name) or check_tfm(specification,specification.name) +      end +    end +  else +    tfmdata=check_afm(specification,fullname) +  end +  return tfmdata +end +function readers.pfb(specification,method)  +  local original=specification.specification +  if trace_defining then +    report_afm("using afm reader for %a",original) +  end +  specification.forced="afm" +  local function swap(name) +    local value=specification[swap] +    if value then +      specification[swap]=gsub("%.pfb",".afm",1) +    end +  end +  swap("filename") +  swap("fullname") +  swap("forcedname") +  swap("specification") +  return readers.afm(specification,method) +end + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-afk']={ +  version=1.001, +  comment="companion to font-afm.lua", +  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", +  copyright="PRAGMA ADE / ConTeXt Development Team", +  license="see context related readme files", +  dataonly=true, +} +local allocate=utilities.storage.allocate +fonts.handlers.afm.helpdata={ +  ligatures=allocate {  +    ['f']={  +      { 'f','ff' }, +      { 'i','fi' }, +      { 'l','fl' }, +    }, +    ['ff']={ +      { 'i','ffi' } +    }, +    ['fi']={ +      { 'i','fii' } +    }, +    ['fl']={ +      { 'i','fli' } +    }, +    ['s']={ +      { 't','st' } +    }, +    ['i']={ +      { 'j','ij' } +    }, +  }, +  texligatures=allocate { +    ['quoteleft']={ +      { 'quoteleft','quotedblleft' } +    }, +    ['quoteright']={ +      { 'quoteright','quotedblright' } +    }, +    ['hyphen']={ +      { 'hyphen','endash' } +    }, +    ['endash']={ +      { 'hyphen','emdash' } +    } +  }, +  leftkerned=allocate { +    AEligature="A",aeligature="a", +    OEligature="O",oeligature="o", +    IJligature="I",ijligature="i", +    AE="A",ae="a", +    OE="O",oe="o", +    IJ="I",ij="i", +    Ssharp="S",ssharp="s", +  }, +  rightkerned=allocate { +    AEligature="E",aeligature="e", +    OEligature="E",oeligature="e", +    IJligature="J",ijligature="j", +    AE="E",ae="e", +    OE="E",oe="e", +    IJ="J",ij="j", +    Ssharp="S",ssharp="s", +  }, +  bothkerned=allocate { +    Acircumflex="A",acircumflex="a", +    Ccircumflex="C",ccircumflex="c", +    Ecircumflex="E",ecircumflex="e", +    Gcircumflex="G",gcircumflex="g", +    Hcircumflex="H",hcircumflex="h", +    Icircumflex="I",icircumflex="i", +    Jcircumflex="J",jcircumflex="j", +    Ocircumflex="O",ocircumflex="o", +    Scircumflex="S",scircumflex="s", +    Ucircumflex="U",ucircumflex="u", +    Wcircumflex="W",wcircumflex="w", +    Ycircumflex="Y",ycircumflex="y", +    Agrave="A",agrave="a", +    Egrave="E",egrave="e", +    Igrave="I",igrave="i", +    Ograve="O",ograve="o", +    Ugrave="U",ugrave="u", +    Ygrave="Y",ygrave="y", +    Atilde="A",atilde="a", +    Itilde="I",itilde="i", +    Otilde="O",otilde="o", +    Utilde="U",utilde="u", +    Ntilde="N",ntilde="n", +    Adiaeresis="A",adiaeresis="a",Adieresis="A",adieresis="a", +    Ediaeresis="E",ediaeresis="e",Edieresis="E",edieresis="e", +    Idiaeresis="I",idiaeresis="i",Idieresis="I",idieresis="i", +    Odiaeresis="O",odiaeresis="o",Odieresis="O",odieresis="o", +    Udiaeresis="U",udiaeresis="u",Udieresis="U",udieresis="u", +    Ydiaeresis="Y",ydiaeresis="y",Ydieresis="Y",ydieresis="y", +    Aacute="A",aacute="a", +    Cacute="C",cacute="c", +    Eacute="E",eacute="e", +    Iacute="I",iacute="i", +    Lacute="L",lacute="l", +    Nacute="N",nacute="n", +    Oacute="O",oacute="o", +    Racute="R",racute="r", +    Sacute="S",sacute="s", +    Uacute="U",uacute="u", +    Yacute="Y",yacute="y", +    Zacute="Z",zacute="z", +    Dstroke="D",dstroke="d", +    Hstroke="H",hstroke="h", +    Tstroke="T",tstroke="t", +    Cdotaccent="C",cdotaccent="c", +    Edotaccent="E",edotaccent="e", +    Gdotaccent="G",gdotaccent="g", +    Idotaccent="I",idotaccent="i", +    Zdotaccent="Z",zdotaccent="z", +    Amacron="A",amacron="a", +    Emacron="E",emacron="e", +    Imacron="I",imacron="i", +    Omacron="O",omacron="o", +    Umacron="U",umacron="u", +    Ccedilla="C",ccedilla="c", +    Kcedilla="K",kcedilla="k", +    Lcedilla="L",lcedilla="l", +    Ncedilla="N",ncedilla="n", +    Rcedilla="R",rcedilla="r", +    Scedilla="S",scedilla="s", +    Tcedilla="T",tcedilla="t", +    Ohungarumlaut="O",ohungarumlaut="o", +    Uhungarumlaut="U",uhungarumlaut="u", +    Aogonek="A",aogonek="a", +    Eogonek="E",eogonek="e", +    Iogonek="I",iogonek="i", +    Uogonek="U",uogonek="u", +    Aring="A",aring="a", +    Uring="U",uring="u", +    Abreve="A",abreve="a", +    Ebreve="E",ebreve="e", +    Gbreve="G",gbreve="g", +    Ibreve="I",ibreve="i", +    Obreve="O",obreve="o", +    Ubreve="U",ubreve="u", +    Ccaron="C",ccaron="c", +    Dcaron="D",dcaron="d", +    Ecaron="E",ecaron="e", +    Lcaron="L",lcaron="l", +    Ncaron="N",ncaron="n", +    Rcaron="R",rcaron="r", +    Scaron="S",scaron="s", +    Tcaron="T",tcaron="t", +    Zcaron="Z",zcaron="z", +    dotlessI="I",dotlessi="i", +    dotlessJ="J",dotlessj="j", +    AEligature="AE",aeligature="ae",AE="AE",ae="ae", +    OEligature="OE",oeligature="oe",OE="OE",oe="oe", +    IJligature="IJ",ijligature="ij",IJ="IJ",ij="ij", +    Lstroke="L",lstroke="l",Lslash="L",lslash="l", +    Ostroke="O",ostroke="o",Oslash="O",oslash="o", +    Ssharp="SS",ssharp="ss", +    Aumlaut="A",aumlaut="a", +    Eumlaut="E",eumlaut="e", +    Iumlaut="I",iumlaut="i", +    Oumlaut="O",oumlaut="o", +    Uumlaut="U",uumlaut="u", +  } +} + +end -- closure + +do -- begin closure to overcome local limits and interference +  if not modules then modules={} end modules ['font-lua']={    version=1.001,    comment="companion to font-ini.mkiv", @@ -23850,7 +24975,7 @@ if context then    os.exit()  end  local fonts=fonts -local otffeatures=fonts.constructors.newfeatures("otf") +local otffeatures=fonts.constructors.features.otf  local function initializeitlc(tfmdata,value)    if value then      local parameters=tfmdata.parameters @@ -24072,7 +25197,6 @@ local fonts=fonts  local nodes=nodes  local nuts=nodes.nuts   local traverse_id=nuts.traverse_id -local remove_node=nuts.remove  local free_node=nuts.free  local glyph_code=nodes.nodecodes.glyph  local disc_code=nodes.nodecodes.disc @@ -24164,17 +25288,18 @@ function nodes.handlers.nodepass(head)                local variant=hash[getchar(p)]                if variant then                  setchar(p,variant) -                if not redundant then -                  redundant={ n } -                else -                  redundant[#redundant+1]=n -                end                end              end            end +          if not redundant then +            redundant={ n } +          else +            redundant[#redundant+1]=n +          end          end        end      end +    local nofbasefonts=#basefonts      if redundant then        for i=1,#redundant do          local r=redundant[i] @@ -24185,8 +25310,8 @@ function nodes.handlers.nodepass(head)          else            setlink(p,n)          end -        if b>0 then -          for i=1,b do +        if nofbasefonts>0 then +          for i=1,nofbasefonts do              local bi=basefonts[i]              if r==bi[1] then                bi[1]=n @@ -24230,8 +25355,8 @@ function nodes.handlers.nodepass(head)          end        end      end -    if basemodepass and #basefonts>0 then -      for i=1,#basefonts do +    if basemodepass and nofbasefonts>0 then +      for i=1,nofbasefonts do          local range=basefonts[i]          local start=range[1]          local stop=range[2] diff --git a/src/luaotfload-auxiliary.lua b/src/luaotfload-auxiliary.lua index e482aba..3c43eb9 100644 --- a/src/luaotfload-auxiliary.lua +++ b/src/luaotfload-auxiliary.lua @@ -212,15 +212,19 @@ end  local query_ascender = function (fontdata)    local parameters = fontdata.parameters if not parameters then return false end +  local ascender   = parameters.ascender +  if ascender then +    return ascender --- pre-scaled +  end +    local shared     = fontdata.shared     if not shared     then return false end    local rawdata    = shared.rawdata      if not rawdata    then return false end    local metadata   = rawdata.metadata    if not metadata   then return false end -  local ascender   = parameters.ascender -                  or metadata.ascender   if not ascender   then return false end +  ascender         = metadata.ascender   if not ascender   then return false end    local size       = parameters.size     if not size       then return false end    local units = lookup_units (fontdata)    if not units or units == 0 then return false end -  return ascender * size / units +  return ascender * size / units --- scaled  end  local query_capheight = function (fontdata) diff --git a/src/luaotfload-configuration.lua b/src/luaotfload-configuration.lua index 8cdebe0..c707b63 100644 --- a/src/luaotfload-configuration.lua +++ b/src/luaotfload-configuration.lua @@ -387,17 +387,8 @@ local set_fontforge = function ()    end    local use_ff = config.luaotfload.db.use_fontforge    if use_ff == true then -    if not _G.fontloader then        logreport ("both", 0, "db", -                 "Fontforge loader was requested but the fontloader \z -                  library is missing.") -      return false -    end -    logreport ("log", 0, "db", "Loading font data with FontForge.") -    names.use_fontforge (true) -  else -    logreport ("log", 4, "db", "Loading font data with the Lua loader.") -    names.use_fontforge (false) +                 "Fontforge loader was requested but not supported anymore.")    end    return true  end diff --git a/src/luaotfload-database.lua b/src/luaotfload-database.lua index 437091f..7495ff2 100644 --- a/src/luaotfload-database.lua +++ b/src/luaotfload-database.lua @@ -132,11 +132,6 @@ local fontshandlers            = fonts.handlers     or { }  local otfhandler               = fonts.handlers.otf or { }  fonts.handlers                 = fontshandlers -local read_font_file           = otfhandler.readers.loadfont -local read_font_info           = read_font_file -local close_font_file          = function () end -local get_english_names -  local gzipload                 = gzip.load  local gzipsave                 = gzip.save  local iolines                  = io.lines @@ -207,8 +202,7 @@ local make_luanames = function (path)  end  local format_precedence = { -    "otf",  "ttc", "ttf", "afm", -    -- "pfb" --- may come back before Luatex 1.0 +    "otf",  "ttc", "ttf", "afm", "pfb"  }  local location_precedence = { @@ -355,25 +349,25 @@ This is a sketch of the luaotfload db:          full : (int, string) hash; // idx -> full path      }      and fontentry = { // finalized by collect_families() -        basename        : string;   // file name without path "foo.otf" -        conflicts       : { barename : int; basename : int }; // filename conflict with font at index; happens with subfonts -        familyname      : string;   // sanitized name of the font family the font belongs to, usually from the names table -        fontname        : string;   // sanitized name of the font -        format          : string;   // "otf" | "ttf" | "afm" (* | "pfb" *) -        fullname        : string;   // sanitized full name of the font including style modifiers -        fullpath        : string;   // path to font in filesystem -        index           : int;      // index in the mappings table -        italicangle     : float;    // italic angle; non-zero with oblique faces -        location        : string;   // "texmf" | "system" | "local" -        metafamily      : string;   // alternative family identifier if appropriate, sanitized -        plainname       : string;   // unsanitized font name -        prefmodifiers   : string;   // sanitized preferred subfamily (names table 14) -        psname          : string;   // PostScript name -        size            : (false | float * float * float);  // if available, size info from the size table converted from decipoints -        subfamily       : string;   // sanitized subfamily (names table 2) -        subfont         : (int | bool);     // integer if font is part of a TrueType collection ("ttc") -        version         : string;   // font version string -        weight          : int;      // usWeightClass +        basename             : string;   // file name without path "foo.otf" +        conflicts            : { barename : int; basename : int }; // filename conflict with font at index; happens with subfonts +        familyname           : string;   // sanitized name of the font family the font belongs to, usually from the names table +        fontname             : string;   // sanitized name of the font +        format               : string;   // "otf" | "ttf" | "afm" (* | "pfb" *) +        fullname             : string;   // sanitized full name of the font including style modifiers +        fullpath             : string;   // path to font in filesystem +        index                : int;      // index in the mappings table +        italicangle          : float;    // italic angle; non-zero with oblique faces +        location             : string;   // "texmf" | "system" | "local" +        metafamily           : string;   // alternative family identifier if appropriate, sanitized +        plainname            : string;   // unsanitized font name +        typographicsubfamily : string;   // sanitized preferred subfamily (names table 14) +        psname               : string;   // PostScript name +        size                 : (false | float * float * float);  // if available, size info from the size table converted from decipoints +        subfamily            : string;   // sanitized subfamily (names table 2) +        subfont              : (int | bool);     // integer if font is part of a TrueType collection ("ttc") +        version              : string;   // font version string +        weight               : int;      // usWeightClass      }      and filestatus = (string,       // fullname                        { index       : int list; // pointer into mappings @@ -1016,7 +1010,7 @@ local lookup_fontname = function (specification, name, style)      style = style_category [style]      for i = 1, #mappings do          local face = mappings [i] -        local prefmodifiers = face.prefmodifiers +        local typographicsubfamily = face.typographicsubfamily          local subfamily     = face.subfamily          if     face.fontname   == name              or face.fullname   == name @@ -1024,17 +1018,17 @@ local lookup_fontname = function (specification, name, style)          then              return face.basename, face.subfont          elseif face.familyname == name then -            if prefmodifiers == style +            if typographicsubfamily == style                  or subfamily == style              then                  fallback = face -            elseif regular_synonym [prefmodifiers] +            elseif regular_synonym [typographicsubfamily]                  or regular_synonym [subfamily]              then                  lastresort = face              end          elseif face.metafamily == name -            and (   regular_synonym [prefmodifiers] +            and (   regular_synonym [typographicsubfamily]                   or regular_synonym [subfamily])          then              lastresort = face @@ -1082,12 +1076,12 @@ end      size should that be? Xetex appears to pick the “normal” (unmarked)      size: with Adobe fonts this would be the one that is neither      “caption” nor “subhead” nor “display” &c ... For fonts by Adobe this -    seems to be the one that does not receive a “prefmodifiers” field. -    (IOW Adobe uses the “prefmodifiers” field to encode the design size -    in more or less human readable format.) However, this is not true -    of LM and EB Garamond. As this matters only where there are -    multiple design sizes to a given font/style combination, we put a -    workaround in place that chooses that unmarked version. +    seems to be the one that does not receive a “typographicsubfamily” +    field. (IOW Adobe uses the “typographicsubfamily” field to encode +    the design size in more or less human readable format.) However, +    this is not true of LM and EB Garamond. As this matters only where +    there are multiple design sizes to a given font/style combination, +    we put a workaround in place that chooses that unmarked version.      The first return value of “lookup_font_name” is the file name of the      requested font (string). It can be passed to the fullname resolver @@ -1304,13 +1298,29 @@ find_closest = function (name, limit)      return false  end --- find_closest() +--- string -> uint -> bool * (string | rawdata) +local read_font_file = function (filename, subfont) +    local fontdata = otfhandler.readers.getinfo (filename, +                                                 { subfont        = subfont +                                                 , details        = false +                                                 , platformnames  = true +                                                 , rawfamilynames = true +                                                 }) +    local msg = fontdata.comment +    if msg then +        return false, msg +    end +    return true, fontdata +end +  local load_font_file = function (filename, subfont) -    local rawfont, _msg = read_font_file (filename, subfont, true) -    if not rawfont then -        logreport ("log", 1, "db", "ERROR: failed to open %s.", filename) +    local err, ret = read_font_file (filename, subfont) +    if err == false then +        logreport ("both", 1, "db", "ERROR: failed to open %q: %q.", +                   tostring (filename), tostring (ret))          return      end -    return rawfont +    return ret  end  --- rawdata -> (int * int * int | bool) @@ -1337,46 +1347,13 @@ local get_size_info = function (rawinfo)  end  --[[doc-- -    get_english_names_from_ff -- For legacy Fontforge-style names -    tables. Extracted from the actual names table, not the font item -    itself. ---doc]]-- - -local get_english_names_from_ff = function (metadata) -    local names = metadata.names -    if names then -        for _, raw_namedata in next, names do -            if raw_namedata.lang == "English (US)" then -                return raw_namedata.names -            end -        end -    end - -    -- no (English) names table, probably a broken font -    logreport ("both", 3, "db", -               "%s: missing or broken English names table.", basename) -    return { fontname = metadata.fontname, -             fullname = metadata.fullname, } -end - ---[[doc--      map_enlish_names -- Names-table for Lua fontloader objects. This      may vanish eventually once we ditch Fontforge completely. Only      subset of entries of that table are actually relevant so we’ll      stick to that part.  --doc]]-- -local names_items = { -    compatfull       = "compatiblefullname", -    fullname         = "fullname", -    postscriptname   = "postscriptname", -    preffamily       = "typographicfamily", -    prefmodifiers    = "typographicsubfamily", -    family           = "family", -    subfamily        = "subfamily", -} - -local map_english_names = function (metadata) +local get_english_names = function (metadata)      local namesource      local platformnames = metadata.platformnames      --[[-- @@ -1412,16 +1389,9 @@ local map_english_names = function (metadata)          --namesource = platformnames.macintosh or platformnames.windows          namesource = platformnames.windows or platformnames.macintosh      end -    namesource = namesource or metadata -    local nameinfo = { } -    for ours, theirs in next, names_items do -        nameinfo [ours] = namesource [theirs] -    end -    return nameinfo +    return namesource or metadata  end -get_english_names = map_english_names -  --[[--      In case of broken PS names we set some dummies. @@ -1432,14 +1402,15 @@ local get_raw_info = function (metadata, basename)      local fontname = metadata.fontname      local fullname = metadata.fullname -    local validation_state = metadata.validation_state      if not fontname or not fullname then          --- Broken names table, e.g. avkv.ttf with UTF-16 strings;          --- we put some dummies in place like the fontloader          --- (font-otf.lua) does.          logreport ("both", 3, "db", -                   "invalid names table of font %s, using dummies.", -                   basename) +                   "Invalid names table of font %s, using dummies. \z +                    Reported: fontname=%q, fullname=%q.", +                   tostring (basename), tostring (fontname), +                   tostring (fullname))          fontname = "bad-fontname-" .. basename          fullname = "bad-fullname-" .. basename      end @@ -1462,46 +1433,46 @@ local organize_namedata = function (rawinfo,                                      nametable,                                      basename,                                      info) -    local default_name = nametable.compatfull +    local default_name = nametable.compatiblefullname                        or nametable.fullname                        or nametable.postscriptname                        or rawinfo.fullname                        or rawinfo.fontname                        or info.fullname                        or info.fontname -    local default_family = nametable.preffamily +    local default_family = nametable.typographicfamily                          or nametable.family                          or rawinfo.familyname                          or info.familyname ---    local default_modifier = nametable.prefmodifiers +--    local default_modifier = nametable.typographicsubfamily  --                          or nametable.subfamily      local fontnames = {          --- see          --- https://developer.apple.com/fonts/TTRefMan/RM06/Chap6name.html          --- http://www.microsoft.com/typography/OTSPEC/name.htm#NameIDs          english = { -            --- where a “compatfull” field is given, the value of “fullname” is -            --- either identical or differs by separating the style -            --- with a hyphen and omitting spaces. (According to the -            --- spec, “compatfull” is “Macintosh only”.) -            --- Of the three “fullname” fields, this one appears to be the one -            --- with the entire name given in a legible, -            --- non-abbreviated fashion, for most fonts at any rate. -            --- However, in some fonts (e.g. CMU) all three fields are -            --- identical. -            fullname      = --[[ 18 ]] nametable.compatfull +            --- where a “compatiblefullname” field is given, the value +            --- of “fullname” is either identical or differs by +            --- separating the style with a hyphen and omitting spaces. +            --- (According to the spec, “compatiblefullname” is +            --- “Macintosh only”.) Of the three “fullname” fields, this +            --- one appears to be the one with the entire name given in +            --- a legible, non-abbreviated fashion, for most fonts at +            --- any rate. However, in some fonts (e.g. CMU) all three +            --- fields are identical. +            fullname      = --[[ 18 ]] nametable.compatiblefullname                           or --[[  4 ]] nametable.fullname                           or default_name,              --- we keep both the “preferred family” and the “family”              --- values around since both are valid but can turn out              --- quite differently, e.g. with Latin Modern: -            ---     preffamily: “Latin Modern Sans”, +            ---     typographicfamily: “Latin Modern Sans”,              ---     family:     “LM Sans 10” -            preffamily    = --[[ 16 ]] nametable.preffamilyname, -            family        = --[[  1 ]] nametable.family or default_family, -            prefmodifiers = --[[ 17 ]] nametable.prefmodifiers, -            subfamily     = --[[  2 ]] nametable.subfamily or rawinfo.subfamilyname, -            psname        = --[[  6 ]] nametable.postscriptname, +            family               = --[[  1 ]] nametable.family or default_family, +            subfamily            = --[[  2 ]] nametable.subfamily or rawinfo.subfamilyname, +            psname               = --[[  6 ]] nametable.postscriptname, +            typographicfamily    = --[[ 16 ]] nametable.typographicfamily, +            typographicsubfamily = --[[ 17 ]] nametable.typographicsubfamily,          },          metadata = { @@ -1596,7 +1567,6 @@ ot_fullinfo = function (filename,          style           = style,          version         = rawinfo.version,      } -    close_font_file (metadata) --> FF only      return res  end @@ -1623,12 +1593,12 @@ t1_fullinfo = function (filename, _subfont, location, basename, format)      local weight      sanitized = sanitize_fontnames ({ -        fontname        = fontname, -        psname          = fullname, -        metafamily      = familyname, -        familyname      = familyname, -        weight          = metadata.weight, --- string identifier -        prefmodifiers   = style, +        fontname              = fontname, +        psname                = fullname, +        metafamily            = familyname, +        familyname            = familyname, +        weight                = metadata.weight, --- string identifier +        typographicsubfamily  = style,      })      weight = sanitized.weight @@ -1642,21 +1612,21 @@ t1_fullinfo = function (filename, _subfont, location, basename, format)      end      return { -        basename         = basename, -        fullpath         = filename, -        subfont          = false, -        location         = location or "system", -        format           = format, -        fullname         = sanitized.fullname, -        fontname         = sanitized.fontname, -        familyname       = sanitized.familyname, -        plainname        = fullname, -        psname           = sanitized.fontname, -        version          = metadata.version, -        size             = false, -        prefmodifiers    = style ~= "" and style or weight, -        weight           = metadata.pfminfo and pfminfo.weight or 400, -        italicangle      = italicangle, +        basename              = basename, +        fullpath              = filename, +        subfont               = false, +        location              = location or "system", +        format                = format, +        fullname              = sanitized.fullname, +        fontname              = sanitized.fontname, +        familyname            = sanitized.familyname, +        plainname             = fullname, +        psname                = sanitized.fontname, +        version               = metadata.version, +        size                  = false, +        typographicsubfamily  = style ~= "" and style or weight, +        weight                = metadata.pfminfo and pfminfo.weight or 400, +        italicangle           = italicangle,      }  end @@ -1664,7 +1634,8 @@ local loaders = {      otf     = ot_fullinfo,      ttc     = ot_fullinfo,      ttf     = ot_fullinfo, ---- pfb     = t1_fullinfo, +    afm     = t1_fullinfo, +    pfb     = t1_fullinfo,  }  --- not side-effect free! @@ -1808,11 +1779,12 @@ local read_font_names = function (fullname,      --- 4) get basic info, abort if fontloader can’t read it -    local info = read_font_file (fullname) +    local err, info = read_font_file (fullname) -    if not info then +    if err == false then          logreport ("log", 1, "db", -                   "Failed to read basic information from %q", basename) +                   "Failed to read basic information from %q: %q", +                    basename, tostring (info))          return false      end @@ -2057,8 +2029,18 @@ do      end  end +local locate_matching_pfb = function (afmfile, dir) +    local pfbname = filereplacesuffix (afmfile, "pfb") +    local pfbpath = dir .. "/" .. pfbname +    if lfsisfile (pfbpath) then +        return pfbpath +    end +    --- Check for match in texmf too +    return kpsefind_file (pfbname, "type1 fonts") +end +  local process_dir_tree -process_dir_tree = function (acc, dirs) +process_dir_tree = function (acc, dirs, done)      if not next (dirs) then --- done          return acc      end @@ -2067,48 +2049,56 @@ process_dir_tree = function (acc, dirs)      local dir   = dirs[#dirs]      dirs[#dirs] = nil -    if lfschdir (dir) then -        lfschdir (pwd) - -        local newfiles = { } -        local blacklist = names.blacklist -        for ent in lfsdir (dir) do -            --- filter right away -            if ent ~= "." and ent ~= ".." and not blacklist[ent] then -                local fullpath = dir .. "/" .. ent -                if lfsisdir (fullpath) -                and not lpegmatch (p_blacklist, fullpath) -                then -                    dirs[#dirs+1] = fullpath -                elseif lfsisfile (fullpath) then -                    ent = stringlower (ent) - -                    if lpegmatch (p_font_filter, ent) then -                        if filesuffix (ent) == "afm" then -                            --- fontloader.open() will load the afm -                            --- iff both files are in the same directory -                            local pfbpath = filereplacesuffix -                                                    (fullpath, "pfb") -                            if lfsisfile (pfbpath) then -                                newfiles[#newfiles+1] = pfbpath -                            end -                        else -                            newfiles[#newfiles+1] = fullpath +    if not lfschdir (dir) then +        --- Cannot cd; skip. +        return process_dir_tree (acc, dirs, done) +    end + +    dir = lfscurrentdir () --- resolve symlinks +    lfschdir (pwd) +    if tablecontains (done, dir) then +        --- Already traversed. Note that it’d be unsafe to rely on the +        --- hash part above due to Lua only processing up to 32 bytes +        --- of string data. The lookup shouldn’t impact performance too +        --- much but we could check the performance of alternative data +        --- structures at some point. +        return process_dir_tree (acc, dirs, done) +    end + +    local newfiles = { } +    local blacklist = names.blacklist +    for ent in lfsdir (dir) do +        --- filter right away +        if ent ~= "." and ent ~= ".." and not blacklist[ent] then +            local fullpath = dir .. "/" .. ent +            if lfsisdir (fullpath) +            and not lpegmatch (p_blacklist, fullpath) +            then +                dirs[#dirs+1] = fullpath +            elseif lfsisfile (fullpath) then +                ent = stringlower (ent) +                if lpegmatch (p_font_filter, ent) then +                    newfiles[#newfiles+1] = fullpath +                    if filesuffix (ent) == "afm" then +                        local pfbpath = locate_matching_pfb (ent, dir) +                        if pfbpath then +                            newfiles[#newfiles+1] = pfbpath                          end +                    else +                        newfiles[#newfiles+1] = fullpath                      end -                  end              end          end -        return process_dir_tree (tableappend (acc, newfiles), dirs)      end -    --- cannot cd; skip -    return process_dir_tree (acc, dirs) +    done [#done + 1] = dir +    return process_dir_tree (tableappend (acc, newfiles), dirs, done)  end  local process_dir = function (dir)      local pwd = lfscurrentdir ()      if lfschdir (dir) then +        dir = lfscurrentdir () --- resolve symlinks          lfschdir (pwd)          local files = { } @@ -2121,12 +2111,8 @@ local process_dir = function (dir)                      if lpegmatch (p_font_filter, ent)                      then                          if filesuffix (ent) == "afm" then -                            --- fontloader.open() will load the afm -                            --- iff both files are in the same -                            --- directory -                            local pfbpath = filereplacesuffix -                                                    (fullpath, "pfb") -                            if lfsisfile (pfbpath) then +                            local pfbpath = locate_matching_pfb (ent, dir) +                            if pfbpath then                                  files[#files+1] = pfbpath                              end                          else @@ -2145,7 +2131,7 @@ end  local find_font_files = function (root, recurse)      if lfsisdir (root) then          if recurse == true then -            return process_dir_tree ({}, { root }) +            return process_dir_tree ({}, { root }, {})          else --- kpathsea already delivered the necessary subdirs              return process_dir (root)          end @@ -2261,6 +2247,7 @@ local collect_font_filenames_texmf = function ()      fontdirs = kpseexpand_path "$OPENTYPEFONTS"      fontdirs = fontdirs .. path_separator .. kpseexpand_path "$TTFONTS"      fontdirs = fontdirs .. path_separator .. kpseexpand_path "$T1FONTS" +    fontdirs = fontdirs .. path_separator .. kpseexpand_path "$AFMFONTS"      if stringis_empty (fontdirs) then          return { } @@ -2568,6 +2555,7 @@ end  local bold_spectrum_low  = 501 --- 500 is medium, 900 heavy/black  local bold_weight        = 700 +local normal_width       = 5  local pick_style  local pick_fallback_style @@ -2596,18 +2584,19 @@ do          return false      end -    pick_style = function (prefmodifiers, -                           subfamily) +    pick_style = function (typographicsubfamily, subfamily)          local style -        if prefmodifiers then -            style = choose_exact (prefmodifiers) +        if typographicsubfamily then +            style = choose_exact (typographicsubfamily) +            if style then return style end          elseif subfamily then              style = choose_exact (subfamily) +            if style then return style end          end -        return style +        return false      end -    pick_fallback_style = function (italicangle, weight, pfmweight) +    pick_fallback_style = function (italicangle, pfmweight, width)          --[[--              More aggressive, but only to determine bold faces.              Note: Before you make this test more inclusive, ensure @@ -2618,7 +2607,8 @@ do              treating weights > 500 as bold or allowing synonyms like              “heavy”, “black”.          --]]--  -        if pfmweight == bold_weight then --- bold spectrum matches +        if width == normal_width and pfmweight == bold_weight then +            --- bold spectrum matches              if italicangle == 0 then                  return "b"              end @@ -2630,30 +2620,46 @@ do      --- we use only exact matches here since there are constructs      --- like “regularitalic” (Cabin, Bodoni Old Fashion) -    check_regular = function (prefmodifiers, +    check_regular = function (typographicsubfamily,                                subfamily,                                italicangle,                                weight, +                              width,                                pfmweight)          local plausible_weight = false          --[[--            This filters out undesirable candidates that specify their -          prefmodifiers or subfamily as “regular” but are actually of +          typographicsubfamily or subfamily as “regular” but are actually of            “semibold” or other weight—another drawback of the            oversimplifying classification into only three styles (r, i,            b, bi).          --]]-- -          if italicangle == 0 then -            if     pfmweight == 400                    then plausible_weight = true -            elseif weight and regular_synonym [weight] then plausible_weight = true end +            if pfmweight == 400 then +                --[[-- +                  Some fonts like Dejavu advertise an undistinguished +                  regular and a “condensed” version with the same +                  weight whilst also providing the style info in the +                  typographic subfamily instead of the subfamily (i. e. +                  the converse of what Adobe’s doing). The only way to +                  weed out the undesired pseudo-regular shape is to +                  peek at its advertised width (4 vs. 5). +                --]]-- +                if width then +                    plausible_weight = width == normal_width +                else +                    plausible_weight = true +                end +            elseif weight and regular_synonym [weight] then +                plausible_weight = true +            end          end          if plausible_weight then -            if prefmodifiers  and regular_synonym [prefmodifiers] -            or subfamily      and regular_synonym [subfamily] -            then -                return "r" +            if subfamily then +                if regular_synonym [subfamily] then return "r" end +            elseif typographicsubfamily then +                if regular_synonym [typographicsubfamily] then return "r" end              end          end          return false @@ -2676,18 +2682,19 @@ local pull_values = function (entry)      entry.subfont           = file.subfont      --- pull name info ... -    entry.psname            = english.psname -    entry.fontname          = info.fontname or metadata.fontname -    entry.fullname          = english.fullname or info.fullname -    entry.prefmodifiers     = english.prefmodifiers -    entry.familyname        = metadata.familyname or english.preffamily or english.family -    entry.plainname         = names.fullname -    entry.subfamily         = english.subfamily +    entry.psname               = english.psname +    entry.fontname             = info.fontname or metadata.fontname +    entry.fullname             = english.fullname or info.fullname +    entry.typographicsubfamily = english.typographicsubfamily +    entry.familyname           = metadata.familyname or english.typographicfamily or english.family +    entry.plainname            = names.fullname +    entry.subfamily            = english.subfamily      --- pull style info ...      entry.italicangle       = style.italicangle      entry.size              = style.size      entry.weight            = style.weight +    entry.width             = style.width      entry.pfmweight         = style.pfmweight      if config.luaotfload.db.strip == true then @@ -2742,28 +2749,27 @@ local collect_families = function (mappings)              pull_values (entry)          end -        local subtable          = get_subtable (families, entry) - -        local familyname        = entry.familyname -        local prefmodifiers     = entry.prefmodifiers -        local subfamily         = entry.subfamily - -        local weight            = entry.weight -        local pfmweight         = entry.pfmweight -        local italicangle       = entry.italicangle - -        local modifier          = pick_style (prefmodifiers, subfamily) +        local subtable             = get_subtable (families, entry) +        local familyname           = entry.familyname +        local typographicsubfamily = entry.typographicsubfamily +        local subfamily            = entry.subfamily +        local weight               = entry.weight +        local width                = entry.width +        local pfmweight            = entry.pfmweight +        local italicangle          = entry.italicangle +        local modifier             = pick_style (typographicsubfamily, subfamily)          if not modifier then --- regular, exact only -            modifier = check_regular (prefmodifiers, +            modifier = check_regular (typographicsubfamily,                                        subfamily,                                        italicangle,                                        weight, +                                      width,                                        pfmweight)          end          if not modifier then -            modifier = pick_fallback_style (italicangle, weight, pfmweight) +            modifier = pick_fallback_style (italicangle, pfmweight, width)          end          if modifier then @@ -2994,7 +3000,7 @@ local collect_statistics = function (mappings)      local sum_dsnsize, n_dsnsize = 0, 0      local fullname, family, families = { }, { }, { } -    local subfamily, prefmodifiers = { }, { } +    local subfamily, typographicsubfamily = { }, { }      local addtohash = function (hash, item)          if item then @@ -3050,10 +3056,10 @@ local collect_statistics = function (mappings)          local names        = entry.names.sanitized          local englishnames = names.english -        addtohash (fullname,        englishnames.fullname) -        addtohash (family,          englishnames.family) -        addtohash (subfamily,       englishnames.subfamily) -        addtohash (prefmodifiers,   englishnames.prefmodifiers) +        addtohash (fullname,             englishnames.fullname) +        addtohash (family,               englishnames.family) +        addtohash (subfamily,            englishnames.subfamily) +        addtohash (typographicsubfamily, englishnames.typographicsubfamily)          addtoset (families, englishnames.family, englishnames.fullname) @@ -3124,9 +3130,9 @@ local collect_statistics = function (mappings)          pprint_top (subfamily, 4)          logreport ("both", 0, "db", -                   "   · %d different “prefmodifiers” kinds.", -                   setsize (prefmodifiers)) -        pprint_top (prefmodifiers, 4) +                   "   · %d different “typographicsubfamily” kinds.", +                   setsize (typographicsubfamily)) +        pprint_top (typographicsubfamily, 4)      end @@ -3143,7 +3149,7 @@ local collect_statistics = function (mappings)          },  --        style = {  --            subfamily = subfamily, ---            prefmodifiers = prefmodifiers, +--            typographicsubfamily = typographicsubfamily,  --        },      }  end @@ -3500,29 +3506,6 @@ local show_cache = function ( )      return true  end -local use_fontforge = function (val) -    if val == true then -        local fontloader  = fontloader -        read_font_info    = fontloader.info -        read_font_file    = fontloader.open -        close_font_file   = fontloader.close -        get_english_names = get_english_names_from_ff -    else -        local wrapper = function (filename, subfont) -            return otfhandler.readers.getinfo (filename, -                                               { subfont        = subfont -                                               , details        = false -                                               , platformnames  = true -                                               , rawfamilynames = true -                                               }) -        end -        read_font_file    = wrapper -        read_font_info    = wrapper -        close_font_file   = function () end -        get_english_names = map_english_names -    end -end -  -----------------------------------------------------------------------  --- export functionality to the namespace “fonts.names”  ----------------------------------------------------------------------- @@ -3554,7 +3537,7 @@ local export = {      show_cache                  = show_cache,      find_closest                = find_closest,      --- transitionary -    use_fontforge               = use_fontforge, +    use_fontforge               = false,  }  return { @@ -3570,7 +3553,7 @@ return {          fonts.definers  = fonts.definers or { resolvers = { } }          names.blacklist = blacklist -        names.version   = 2.7 +        names.version   = 2.8          names.data      = nil      --- contains the loaded database          names.lookups   = nil      --- contains the lookup cache diff --git a/src/luaotfload-features.lua b/src/luaotfload-features.lua index 5152fab..b6e889e 100644 --- a/src/luaotfload-features.lua +++ b/src/luaotfload-features.lua @@ -1122,7 +1122,6 @@ local import_values = {      -- "style", "optsize",--> from slashed notation; handled otherwise      { "lookup", false },      { "sub",    false }, -    { "mode",   true },  }  local lookup_types = { "anon"  , "file", "kpse" @@ -1221,8 +1220,14 @@ local handle_request = function (specification)      request.features = apply_default_features(request.features)      if name then -        specification.name    = name -        specification.lookup  = lookup or specification.lookup +        if lookup == "file" then +            local suffix = file.suffix (name) +            specification.forcedname = name +            specification.forced     = suffix +            name                     = file.removesuffix (name) +        end +        specification.name     = name +        specification.lookup   = lookup or specification.lookup      end      if request.modifiers then @@ -1245,7 +1250,12 @@ local handle_request = function (specification)      --- The next line sets the “rand” feature to “random”; I haven’t      --- investigated it any further (luatex-fonts-ext), so it will      --- just stay here. -    specification.features.normal = normalize (request.features) +    local features = specification.features +    if not features then +        features = { } +        specification.features = features +    end +    features.normal = normalize (request.features)      local subfont = tonumber (specification.sub)      if subfont and subfont >= 0 then          specification.sub = subfont + 1 @@ -1253,8 +1263,9 @@ local handle_request = function (specification)      return specification  end +fonts.names.handle_request = handle_request +  if as_script == true then --- skip the remainder of the file -    fonts.names.handle_request = handle_request      report ("log", 5, "features",              "Exiting early from luaotfload-features.lua.")      return @@ -1285,8 +1296,9 @@ local report_otf          = logs.reporter("fonts","otf loading")  --- start locals for addfeature() -local utfbyte = unicode.utf8.byte -local utfchar = unicode.utf8.char +local utf8    = unicode.utf8 +local utfbyte = utf8.byte +local utflen  = utf8.len  local otf = handlers and handlers.otf --- filled in later during initialization @@ -1330,15 +1342,15 @@ local function current_addfeature(data,feature,specifications)      if not features or not sequences then          return      end -    local gsubfeatures = features.gsub -    if gsubfeatures and gsubfeatures[feature] then -        return -- already present -    end +    -- feature has to be unique but the name entry wins eventually +      local fontfeatures = resources.features or everywhere      local unicodes     = resources.unicodes      local splitter     = lpeg.splitter(" ",unicodes)      local done         = 0      local skip         = 0 +    local aglunicodes  = false +      if not specifications[1] then          -- so we accept a one entry specification          specifications = { specifications } @@ -1347,11 +1359,24 @@ local function current_addfeature(data,feature,specifications)      local function tounicode(code)          if not code then              return -        elseif type(code) == "number" then +        end +        if type(code) == "number" then              return code -        else -            return unicodes[code] or utfbyte(code)          end +        local u = unicodes[code] +        if u then +            return u +        end +        if utflen(code) == 1 then +            u = utfbyte(code) +            if u then +                return u +            end +        end +        if not aglunicodes then +            aglunicodes = fonts.encodings.agl.unicodes -- delayed +        end +        return aglunicodes[code]      end      local coverup      = otf.coverup @@ -1359,9 +1384,281 @@ local function current_addfeature(data,feature,specifications)      local stepkey      = coverup.stepkey      local register     = coverup.register +    local function prepare_substitution(list,featuretype) +        local coverage = { } +        local cover    = coveractions[featuretype] +        for code, replacement in next, list do +            local unicode     = tounicode(code) +            local description = descriptions[unicode] +            if description then +                if type(replacement) == "table" then +                    replacement = replacement[1] +                end +                replacement = tounicode(replacement) +                if replacement and descriptions[replacement] then +                    cover(coverage,unicode,replacement) +                    done = done + 1 +                else +                    skip = skip + 1 +                end +            else +                skip = skip + 1 +            end +        end +        return coverage +    end + +    local function prepare_alternate(list,featuretype) +        local coverage = { } +        local cover    = coveractions[featuretype] +        for code, replacement in next, list do +            local unicode     = tounicode(code) +            local description = descriptions[unicode] +            if not description then +                skip = skip + 1 +            elseif type(replacement) == "table" then +                local r = { } +                for i=1,#replacement do +                    local u = tounicode(replacement[i]) +                    r[i] = descriptions[u] and u or unicode +                end +                cover(coverage,unicode,r) +                done = done + 1 +            else +                local u = tounicode(replacement) +                if u then +                    cover(coverage,unicode,{ u }) +                    done = done + 1 +                else +                    skip = skip + 1 +                end +            end +        end +        return coverage +    end + +    local function prepare_multiple(list,featuretype) +        local coverage = { } +        local cover    = coveractions[featuretype] +        for code, replacement in next, list do +            local unicode     = tounicode(code) +            local description = descriptions[unicode] +            if not description then +                skip = skip + 1 +            elseif type(replacement) == "table" then +                local r, n = { }, 0 +                for i=1,#replacement do +                    local u = tounicode(replacement[i]) +                    if descriptions[u] then +                        n = n + 1 +                        r[n] = u +                    end +                end +                if n > 0 then +                    cover(coverage,unicode,r) +                    done = done + 1 +                else +                    skip = skip + 1 +                end +            else +                local u = tounicode(replacement) +                if u then +                    cover(coverage,unicode,{ u }) +                    done = done + 1 +                else +                    skip = skip + 1 +                end +            end +        end +        return coverage +    end + +    local function prepare_ligature(list,featuretype) +        local coverage = { } +        local cover    = coveractions[featuretype] +        for code, ligature in next, list do +            local unicode     = tounicode(code) +            local description = descriptions[unicode] +            if description then +                if type(ligature) == "string" then +                    ligature = { lpegmatch(splitter,ligature) } +                end +                local present = true +                for i=1,#ligature do +                    local l = ligature[i] +                    local u = tounicode(l) +                    if descriptions[u] then +                        ligature[i] = u +                    else +                        present = false +                        break +                    end +                end +                if present then +                    cover(coverage,unicode,ligature) +                    done = done + 1 +                else +                    skip = skip + 1 +                end +            else +                skip = skip + 1 +            end +        end +        return coverage +    end + +    local function prepare_kern(list,featuretype) +        local coverage = { } +        local cover    = coveractions[featuretype] +        for code, replacement in next, list do +            local unicode     = tounicode(code) +            local description = descriptions[unicode] +            if description and type(replacement) == "table" then +                local r = { } +                for k, v in next, replacement do +                    local u = tounicode(k) +                    if u then +                        r[u] = v +                    end +                end +                if next(r) then +                    cover(coverage,unicode,r) +                    done = done + 1 +                else +                    skip = skip + 1 +                end +            else +                skip = skip + 1 +            end +        end +        return coverage +    end + +    local function prepare_pair(list,featuretype) +        local coverage = { } +        local cover    = coveractions[featuretype] +        if cover then +            for code, replacement in next, list do +                local unicode     = tounicode(code) +                local description = descriptions[unicode] +                if description and type(replacement) == "table" then +                    local r = { } +                    for k, v in next, replacement do +                        local u = tounicode(k) +                        if u then +                            r[u] = v +                        end +                    end +                    if next(r) then +                        cover(coverage,unicode,r) +                        done = done + 1 +                    else +                        skip = skip + 1 +                    end +                else +                    skip = skip + 1 +                end +            end +        else +            report_otf("unknown cover type %a",featuretype) +        end +        return coverage +    end + +    local function prepare_chain(list,featuretype,sublookups) +        -- todo: coveractions +        local rules    = list.rules +        local coverage = { } +        if rules then +            local rulehash     = { } +            local rulesize     = 0 +            local sequence     = { } +            local nofsequences = 0 +            local lookuptype   = types[featuretype] +            for nofrules=1,#rules do +                local rule         = rules[nofrules] +                local current      = rule.current +                local before       = rule.before +                local after        = rule.after +                local replacements = rule.replacements or false +                local sequence     = { } +                local nofsequences = 0 +                if before then +                    for n=1,#before do +                        nofsequences = nofsequences + 1 +                        sequence[nofsequences] = before[n] +                    end +                end +                local start = nofsequences + 1 +                for n=1,#current do +                    nofsequences = nofsequences + 1 +                    sequence[nofsequences] = current[n] +                end +                local stop = nofsequences +                if after then +                    for n=1,#after do +                        nofsequences = nofsequences + 1 +                        sequence[nofsequences] = after[n] +                    end +                end +                local lookups = rule.lookups or false +                local subtype = nil +                if lookups and sublookups then +                    for k, v in next, lookups do +                        local lookup = sublookups[v] +                        if lookup then +                            lookups[k] = lookup +                            if not subtype then +                                subtype = lookup.type +                            end +                        else +                            -- already expanded +                        end +                    end +                end +                if nofsequences > 0 then -- we merge coverage into one +                    -- we copy as we can have different fonts +                    local hashed = { } +                    for i=1,nofsequences do +                        local t = { } +                        local s = sequence[i] +                        for i=1,#s do +                            local u = tounicode(s[i]) +                            if u then +                                t[u] = true +                            end +                        end +                        hashed[i] = t +                    end +                    sequence = hashed +                    -- now we create the rule +                    rulesize = rulesize + 1 +                    rulehash[rulesize] = { +                        nofrules,     -- 1 +                        lookuptype,   -- 2 +                        sequence,     -- 3 +                        start,        -- 4 +                        stop,         -- 5 +                        lookups,      -- 6 (6/7 also signal of what to do) +                        replacements, -- 7 +                        subtype,      -- 8 +                    } +                    for unic in next, sequence[start] do +                        local cu = coverage[unic] +                        if not cu then +                            coverage[unic] = rulehash -- can now be done cleaner i think +                        end +                    end +                end +            end +        end +        return coverage +    end +      for s=1,#specifications do          local specification = specifications[s]          local valid         = specification.valid +        local feature       = specification.name or feature          if not valid or valid(data,specification,feature) then              local initialize = specification.initialize              if initialize then @@ -1369,185 +1666,133 @@ local function current_addfeature(data,feature,specifications)                  specification.initialize = initialize(specification,data) and initialize or nil              end              local askedfeatures = specification.features or everywhere -            local askedsteps    = specifications.steps or specification.subtables or { specification.data } or { } +            local askedsteps    = specification.steps or specification.subtables or { specification.data } or { }              local featuretype   = normalized[specification.type or "substitution"] or "substitution"              local featureflags  = specification.flags or noflags              local featureorder  = specification.order or { feature } -            local added         = false +            local featurechain  = (featuretype == "chainsubstitution" or featuretype == "chainposition") and 1 or 0              local nofsteps      = 0              local steps         = { } +            local sublookups    = specification.lookups +            local category      = nil +            if sublookups then +                local s = { } +                for i=1,#sublookups do +                    local specification = sublookups[i] +                    local askedsteps    = specification.steps or specification.subtables or { specification.data } or { } +                    local featuretype   = normalized[specification.type or "substitution"] or "substitution" +                    local featureflags  = specification.flags or noflags +                    local nofsteps      = 0 +                    local steps         = { } +                    for i=1,#askedsteps do +                        local list     = askedsteps[i] +                        local coverage = nil +                        local format   = nil +                        if featuretype == "substitution" then +                            coverage = prepare_substitution(list,featuretype) +                        elseif featuretype == "ligature" then +                            coverage = prepare_ligature(list,featuretype) +                        elseif featuretype == "alternate" then +                            coverage = prepare_alternate(list,featuretype) +                        elseif featuretype == "multiple" then +                            coverage = prepare_multiple(list,featuretype) +                        elseif featuretype == "kern" then +                            format   = "kern" +                            coverage = prepare_kern(list,featuretype) +                        elseif featuretype == "pair" then +                            format   = "pair" +                            coverage = prepare_pair(list,featuretype) +                        end +                        if coverage and next(coverage) then +                            nofsteps = nofsteps + 1 +                            steps[nofsteps] = register(coverage,featuretype,format,feature,nofsteps,descriptions,resources) +                        end +                    end +                    s[i] = { +                        [stepkey] = steps, +                        nofsteps  = nofsteps, +                        type      = types[featuretype], +                    } +                end +                sublookups = s +            end              for i=1,#askedsteps do                  local list     = askedsteps[i] -                local coverage = { } -                local cover    = coveractions[featuretype] +                local coverage = nil                  local format   = nil -                if not cover then -                    -- unknown -                elseif featuretype == "substitution" then -                    for code, replacement in next, list do -                        local unicode     = tounicode(code) -                        local description = descriptions[unicode] -                        if description then -                            if type(replacement) == "table" then -                                replacement = replacement[1] -                            end -                            replacement = tounicode(replacement) -                            if replacement and descriptions[replacement] then -                                cover(coverage,unicode,replacement) -                                done = done + 1 -                            else -                                skip = skip + 1 -                            end -                        else -                            skip = skip + 1 -                        end -                    end +                if featuretype == "substitution" then +                    category = "gsub" +                    coverage = prepare_substitution(list,featuretype)                  elseif featuretype == "ligature" then -                    for code, ligature in next, list do -                        local unicode     = tounicode(code) -                        local description = descriptions[unicode] -                        if description then -                            if type(ligature) == "string" then -                                ligature = { lpegmatch(splitter,ligature) } -                            end -                            local present = true -                            for i=1,#ligature do -                                local l = ligature[i] -                                local u = tounicode(l) -                                if descriptions[u] then -                                    ligature[i] = u -                                else -                                    present = false -                                    break -                                end -                            end -                            if present then -                                cover(coverage,unicode,ligature) -                                done = done + 1 -                            else -                                skip = skip + 1 -                            end -                        else -                            skip = skip + 1 -                        end -                    end +                    category = "gsub" +                    coverage = prepare_ligature(list,featuretype)                  elseif featuretype == "alternate" then -                    for code, replacement in next, list do -                        local unicode     = tounicode(code) -                        local description = descriptions[unicode] -                        if not description then -                            skip = skip + 1 -                        elseif type(replacement) == "table" then -                            local r = { } -                            for i=1,#replacement do -                                local u = tounicode(replacement[i]) -                                r[i] = descriptions[u] and u or unicode -                            end -                            cover(coverage,unicode,r) -                            done = done + 1 -                        else -                            local u = tounicode(replacement) -                            if u then -                                cover(coverage,unicode,{ u }) -                                done = done + 1 -                            else -                                skip = skip + 1 -                            end -                        end -                    end -                elseif featuretype == "multiple" then -- todo: unicode can be table -                    for code, replacement in next, list do -                        local unicode     = tounicode(code) -                        local description = descriptions[unicode] -                        if not description then -                            skip = skip + 1 -                        elseif type(replacement) == "table" then -                            local r, n = { }, 0 -                            for i=1,#replacement do -                                local u = tounicode(replacement[i]) -                                if descriptions[u] then -                                    n = n + 1 -                                    r[n] = u -                                end -                            end -                            if n > 0 then -                                cover(coverage,unicode,r) -                                done = done + 1 -                            else -                                skip = skip + 1 -                            end -                        else -                            local u = tounicode(replacement) -                            if u then -                                cover(coverage,unicode,{ u }) -                                done = done + 1 -                            else -                                skip = skip + 1 -                            end -                        end -                    end +                    category = "gsub" +                    coverage = prepare_alternate(list,featuretype) +                elseif featuretype == "multiple" then +                    category = "gsub" +                    coverage = prepare_multiple(list,featuretype)                  elseif featuretype == "kern" then -                    for code, replacement in next, list do -                        local unicode     = tounicode(code) -                        local description = descriptions[unicode] -                        if description and type(replacement) == "table" then -                            local r = { } -                            for k, v in next, replacement do -                                local u = tounicode(k) -                                if u then -                                    r[u] = v -                                end -                            end -                            if next(r) then -                                cover(coverage,unicode,r) -                                done = done + 1 -                            else -                                skip = skip + 1 -                            end -                        else -                            skip = skip + 1 -                        end -                    end -                    format = "kern" +                    category = "gpos" +                    format   = "kern" +                    coverage = prepare_kern(list,featuretype) +                elseif featuretype == "pair" then +                    category = "gpos" +                    format   = "pair" +                    coverage = prepare_pair(list,featuretype) +                elseif featuretype == "chainsubstitution" then +                    category = "gsub" +                    coverage = prepare_chain(list,featuretype,sublookups) +                elseif featuretype == "chainposition" then +                    category = "gpos" +                    coverage = prepare_chain(list,featuretype,sublookups) +                else +                    report_otf("not registering feature %a, unknown category",feature) +                    return                  end -                if next(coverage) then -                    added = true +                if coverage and next(coverage) then                      nofsteps = nofsteps + 1                      steps[nofsteps] = register(coverage,featuretype,format,feature,nofsteps,descriptions,resources)                  end              end -            if added then +            if nofsteps > 0 then                  -- script = { lang1, lang2, lang3 } or script = { lang1 = true, ... }                  for k, v in next, askedfeatures do                      if v[1] then                          askedfeatures[k] = tohash(v)                      end                  end +                if featureflags[1] then featureflags[1] = "mark" end +                if featureflags[2] then featureflags[2] = "ligature" end +                if featureflags[3] then featureflags[3] = "base" end                  local sequence = { -                    chain     = 0, +                    chain     = featurechain,                      features  = { [feature] = askedfeatures },                      flags     = featureflags, -                    name      = feature, -- not needed +                    name      = feature, -- redundant                      order     = featureorder,                      [stepkey] = steps,                      nofsteps  = nofsteps,                      type      = types[featuretype],                  } +                -- todo : before|after|index                  if specification.prepend then                      insert(sequences,1,sequence)                  else                      insert(sequences,sequence)                  end                  -- register in metadata (merge as there can be a few) -                if not gsubfeatures then -                    gsubfeatures  = { } -                    fontfeatures.gsub = gsubfeatures +                local features = fontfeatures[category] +                if not features then +                    features  = { } +                    fontfeatures[category] = features                  end -                local k = gsubfeatures[feature] +                local k = features[feature]                  if not k then                      k = { } -                    gsubfeatures[feature] = k +                    features[feature] = k                  end +                --                  for script, languages in next, askedfeatures do                      local kk = k[script]                      if not kk then diff --git a/src/luaotfload-init.lua b/src/luaotfload-init.lua index bcc0e18..c977b49 100644 --- a/src/luaotfload-init.lua +++ b/src/luaotfload-init.lua @@ -9,6 +9,7 @@  local setmetatable = setmetatable  local kpselookup   = kpse.lookup +local lfsisdir     = lfs.isdir  --[[doc-- @@ -223,7 +224,7 @@ end  --- below paths are relative to the texmf-context  local ltx = "tex/generic/context/luatex" -local ctx = "tex/context/base" +local ctx = { "tex/context/base/mkiv", "tex/context/base" }  local context_modules = { @@ -251,8 +252,6 @@ local context_modules = {    { ctx,   "font-map"          },    { ltx,   "luatex-fonts-syn"  },    { ctx,   "font-tfm"          }, -  { ctx,   "font-afm"          }, -  { ctx,   "font-afk"          },    { ctx,   "font-oti"          },    { ctx,   "font-otr"          },    { ctx,   "font-cff"          }, @@ -265,6 +264,10 @@ local context_modules = {    { ctx,   "font-ota"          },    { ctx,   "font-ots"          },    { ctx,   "font-osd"          }, +  { ctx,   "font-ocl"          }, +  { ctx,   "font-onr"          }, +  { ctx,   "font-one"          }, +  { ctx,   "font-afk"          },    { ctx,   "font-lua"          },    { ctx,   "font-def"          },    { ltx,   "luatex-fonts-ext"  }, @@ -284,17 +287,37 @@ local load_context_modules = function (pth)      local sub, spec = unpack (context_modules [i])      if sub == false then        ignore_module (spec) -    elseif type (sub) == "string" then -      if pth then +    else +      local tsub = type (sub) +      if not pth then +        load_module (spec) +      elseif tsub == "string" then          load_module (spec, file.join (pth, sub)) +      elseif tsub == "table" then +        local pfx +        local nsub = #sub +        for j = 1, nsub do +          local full = file.join (pth, sub [j]) +          if lfsisdir (full) then --- pick the first real one +            pfx = full +            break +          end +        end +        if pfx then +          load_module (spec, pfx) +        else +          logreport ("both", 0, "init", +                     "None of the %d search paths for module %q exist; \z +                      falling back to default path.", +                     nsub, tostring (spec)) +          load_module (spec) --- maybe we’ll get by after all? +        end        else -        load_module (spec) +        logreport ("both", 0, "init", +                   "Internal error, please report. \z +                    This is not your fault.") +        os.exit (-1)        end -    else -      logreport ("both", 0, "init", -                 "Internal error, please report. \z -                  This is not your fault.") -      os.exit (-1)      end    end @@ -509,8 +532,6 @@ local init_main = function ()      load_fontloader_module "font-map"      load_fontloader_module "fonts-syn"      load_fontloader_module "font-tfm" -    load_fontloader_module "font-afm" -    load_fontloader_module "font-afk"      load_fontloader_module "font-oti"      load_fontloader_module "font-otr"      load_fontloader_module "font-cff" @@ -523,6 +544,10 @@ local init_main = function ()      load_fontloader_module "font-ota"      load_fontloader_module "font-ots"      load_fontloader_module "font-osd" +    load_fontloader_module "font-ocl" +    load_fontloader_module "font-onr" +    load_fontloader_module "font-one" +    load_fontloader_module "font-afk"      load_fontloader_module "font-lua"      load_fontloader_module "font-def"      load_fontloader_module "fonts-ext" @@ -533,7 +558,7 @@ local init_main = function ()                 "Loading Context modules in lookup path.")      load_context_modules () -  elseif lfs.isdir (fontloader) then +  elseif lfsisdir (fontloader) then      logreport ("log", 0, "init",                 "Loading Context files under prefix “%s”.",                 fontloader) diff --git a/src/luaotfload-loaders.lua b/src/luaotfload-loaders.lua index f6cb272..68cc50f 100644 --- a/src/luaotfload-loaders.lua +++ b/src/luaotfload-loaders.lua @@ -49,12 +49,22 @@ local unsupported_reader = function (format)    end  end -local afm_compat_message = function (specification, method) +local afm_reader = fonts.readers.afm + +local afm_loader = function (specification) +  specification.forced      = "afm" +  specification.sub         = false +  specification.forcedname  = file.addsuffix(specification.name, "afm") +  inspect(specification) +  return afm_reader (specification, "afm") +end + +local afm_compat_message = function (specification)    logreport ("both", 4, "loaders",               "PFB format only supported with matching \z -              AFM; redirecting (“%s”, “%s”).", -             tostring (specification.name), tostring (method)) -  return fonts.readers.afm (specification, method) +              AFM; redirecting (“%s”, “afm”).", +             tostring (specification.name)) +  return afm_loader (specification)  end  local install_formats = function () @@ -64,9 +74,8 @@ local install_formats = function ()    local readers   = fonts.readers    local sequence  = readers.sequence    local seqset    = table.tohash (sequence) -  local handlers  = fonts.handlers    local formats   = fonts.formats -  if not readers or not handlers or not formats then return false end +  if not readers or not formats then return false end    local aux = function (which, reader)      if   not which  or type (which) ~= "string" @@ -76,7 +85,6 @@ local install_formats = function ()      end      formats  [which] = "type1"      readers  [which] = reader -    handlers [which] = { }      if not seqset [which] then        logreport ("both", 3, "loaders",                   "Extending reader sequence for “%s”.", which) @@ -89,11 +97,28 @@ local install_formats = function ()    return aux ("evl", eval_reader)       and aux ("lua", lua_reader)       and aux ("pfa", unsupported_reader "pfa") +     and aux ("afm", afm_loader)       and aux ("pfb", afm_compat_message) --- pfb loader is incomplete       and aux ("ofm", readers.tfm)       and aux ("dfont", unsupported_reader "dfont")  end +local not_found_msg = function (specification, size, id) +  logreport ("both", 0, "loaders", "") +  logreport ("both", 0, "loaders", +             "--------------------------------------------------------") +  logreport ("both", 0, "loaders", "") +  logreport ("both", 0, "loaders", "Font definition failed for:") +  logreport ("both", 0, "loaders", "") +  logreport ("both", 0, "loaders", "   > id            : %d", id) +  logreport ("both", 0, "loaders", "   > specification : %q", specification) +  if size > 0 then +    logreport ("both", 0, "loaders", "   > size          : %.2f pt", size / 2^16) +  end +  logreport ("both", 0, "loaders", "") +  logreport ("both", 0, "loaders", +             "--------------------------------------------------------") +end  --[[doc--      \subsection{\CONTEXT override} @@ -110,6 +135,7 @@ do    local patch = function (specification, size, id)      local fontdata = read (specification, size, id) +----if not fontdata then not_found_msg (specification, size, id) end      if type (fontdata) == "table" and fontdata.shared then        --- We need to test for the “shared” field here        --- or else the fontspec capheight callback will @@ -129,10 +155,8 @@ do        logreport ("both", 0, "loaders", "   > spec %q", specification)        logreport ("both", 0, "loaders", "   > at size %.2f pt", size / 2^16)        local result = definer (specification, size, id) -      if not result then -        logreport ("both", 0, "loaders", "   > font definition failed") -        return -      elseif type (result) == "number" then +      if not result then return not_found_msg (specification, size, id) end +      if type (result) == "number" then          logreport ("both", 0, "loaders", "   > font definition yielded id %d", result)          return result        end diff --git a/src/luaotfload-tool.lua b/src/luaotfload-tool.lua index 35dc9b3..bc9e425 100755 --- a/src/luaotfload-tool.lua +++ b/src/luaotfload-tool.lua @@ -145,8 +145,6 @@ require "fontloader-fonts-enc"  require "fontloader-font-cid"  require "fontloader-font-map"  require "fontloader-font-tfm" -require "fontloader-font-afm" -require "fontloader-font-afk"  require "fontloader-font-oti"  require "fontloader-font-otr"  require "fontloader-font-cff" @@ -159,6 +157,9 @@ require "fontloader-font-oto"  ------- "fontloader-font-ota"  ------- "fontloader-font-ots"  ------- "fontloader-font-osd" +require "fontloader-font-onr" +require "fontloader-font-one" +require "fontloader-font-afk"  require "fontloader-font-lua"  require "fontloader-font-def"  require "fontloader-fonts-ext" @@ -797,7 +798,7 @@ actions.blacklist = function (job)  end  actions.generate = function (job) -    local _ = fonts.names.update (fontnames, job.force_reload, job.dry_run) +    local _ = fonts.names.update ({ }, job.force_reload, job.dry_run)      local namedata = fonts.names.data ()      if namedata then          logreport ("info", 2, "db",  | 
