diff options
| author | Philipp Gesang <phg42.2a@gmail.com> | 2013-11-05 00:27:31 +0100 | 
|---|---|---|
| committer | Philipp Gesang <phg42.2a@gmail.com> | 2013-11-05 00:27:31 +0100 | 
| commit | 3da77f501c148c757ca9cb570666ea409aff1ff3 (patch) | |
| tree | c9e02888fde12e44df76ddb9fcdb65ed8b91beca | |
| parent | 8e65e677bb678de920ea55097b8889b4245c0e15 (diff) | |
| download | luaotfload-3da77f501c148c757ca9cb570666ea409aff1ff3.tar.gz | |
[db] add first draft font family query
| -rw-r--r-- | luaotfload-database.lua | 218 | ||||
| -rw-r--r-- | luaotfload-features.lua | 6 | ||||
| -rw-r--r-- | luaotfload-merged.lua | 374 | 
3 files changed, 475 insertions, 123 deletions
diff --git a/luaotfload-database.lua b/luaotfload-database.lua index cba2f26..390c3ea 100644 --- a/luaotfload-database.lua +++ b/luaotfload-database.lua @@ -648,16 +648,21 @@ Existence of the resolved file name is verified differently depending  on whether the index entry has a texmf flag set.  --doc]]-- -local get_font_file = function (fullnames, entry) -    local basename = entry.basename -    if entry.location == "texmf" then +local get_font_file = function (index) +    local entry = name_index.mappings [index] +    if not entry then +        return false +    end +    local filedata = entry.file +    local basename = filedata.base +    if filedata.location == "texmf" then          if kpselookup(basename) then -            return true, basename, entry.subfont +            return true, basename, filedata.subfont          end -    else -        local fullname = fullnames[entry.index] -        if lfsisfile(fullname) then -            return true, basename, entry.subfont +    else --- system, local +        local fullname = name_index.files.full [index] +        if lfsisfile (fullname) then +            return true, basename, filedata.subfont          end      end      return false @@ -1096,17 +1101,206 @@ resolve = function (_, _, specification) -- the 1st two parameters are used by C  end --- resolve()  ]===] +local choose_closest = function (distances) +    local closest = 2^51 +    local match +    for i = 1, #distances do +        local d, index = unpack (distances [i]) +        if d < closest then +            closest = d +            match   = index +        end +    end +    return match +end + +--[[doc-- + +    choose_size -- Pick a font face of appropriate size from the list +    of family members with matching style. There are three categories: + +        1. exact matches: if there is a face whose design size equals +           the asked size, it is returned immediately and no further +           candidates are inspected. + +        2. range matches: of all faces in whose design range the +           requested size falls the one whose center the requested +           size is closest to is returned. + +        3. out-of-range matches: of all other faces (i. e. whose range +           is above or below the asked size) the one is chosen whose +           boundary (upper or lower) is closest to the requested size. + +        4. default matches: if no design size or a design size of zero +           is requested, the face with the default size is returned. + +--doc]]-- + +--- int * int * int * int list -> int -> int +local choose_size = function (sizes, askedsize) +    local mappings = name_index.mappings +    local match    = sizes.default +    local exact +    local inrange  = { } --- distance * index list +    local norange  = { } --- distance * index list +    local fontname, subfont +    if askedsize ~= 0 then +        --- firstly, look for an exactly matching design size or +        --- matching range +        for i = 1, #sizes do +            local dsnsize, high, low, index = unpack (sizes [i]) +            if dsnsize == askedsize then +                --- exact match, this is what we were looking for +                exact = index +                goto skip +            elseif askedsize < low then +                --- below range, add to the norange table +                local d = low - askedsize +                norange [#norange + 1] = { d, index } +            elseif askedsize > high then +                --- beyond range, add to the norange table +                local d = askedsize - high +                norange [#norange + 1] = { d, index } +            else +                --- range match +                local d = ((low + high) / 2) - askedsize +                if d < 0 then +                    d = -d +                end +                inrange [#inrange + 1] = { d, index } +            end +        end +    end +::skip:: +    if exact then +        match = exact +    elseif #inrange > 0 then +        match = choose_closest (inrange) +    elseif #norange > 0 then +        match = choose_closest (norange) +    end +    return match +end + +local format_precedence = { +    "otf",   "ttc", "ttf", +    "dfont", "afm", "pfb", +    "pfa", +} + +local location_precedence = { +    "local", "system", "texmf", +} + +--[[doc-- + +    resolve_familyname -- Query the families table for an entry +    matching the specification. +    The parameters “name” and “style” are pre-sanitized. + +--doc]]-- +--- spec -> string -> string -> int -> string * int +local resolve_familyname = function (specification, name, style, askedsize) +    local families   = name_index.families +    local mappings   = name_index.mappings +    local candidates = nil +    --- arrow code alert +    for i = 1, #location_precedence do +        local location = location_precedence [i] +        local locgroup = families [location] +        for j = 1, #format_precedence do +            local format       = format_precedence [j] +            local fmtgroup     = locgroup [format] +            if fmtgroup then +                local familygroup  = fmtgroup [name] +                if familygroup then +                    local stylegroup = familygroup [style] +                    if stylegroup then --- suitable match +                        candidates = stylegroup +                        goto done +                    end +                end +            end +        end +    end +    if true then +        return nil, nil +    end +::done:: +    index = choose_size (candidates, askedsize) +    local success, resolved, subfont = get_font_file (index) +    if not success then +        return nil, nil +    end +    report ("info", 2, "db", "Match found: %s(%d)", +            resolved, subfont or 0) +    return resolved, subfont +end + +local resolve_fontname = function (specification, name, style, askedsize) +    local resolved, subfont +    --[[ TODO ]] +    return resolved, subfont +end + +--[[doc-- + +    resolve_name -- Perform a name: lookup. This first queries the +    font families table and, if there is no match for the spec, the +    font names table. +    The return value is a pair consisting of the file name and the +    subfont index if appropriate.. + +--doc]]-- + +--- table -> string * (int | bool)  resolve_name = function (specification) +    local resolved, subfont      if not name_index then name_index = load_names () end +    local name      = sanitize_fontname (specification.name) +    local style     = sanitize_fontname (specification.style) or "r" +    local askedsize = specification.optsize + +    if askedsize then +        askedsize = tonumber (askedsize) +    else +        askedsize = specification.size +        if askedsize and askedsize >= 0 then +            askedsize = askedsize / 65536 +        else +            askedsize = 0 +        end +    end + +    resolved, subfont = resolve_familyname (specification, name, style, askedsize) +    if not resolved then +        resolved, subfont = resolve_fontname (specification, name, style, askedsize) +    end +    if not resolved then +        resolved = specification.name, false +    end +    return resolved, subfont  end  resolve_fullpath = function (fontname, ext) --- getfilename()      if not name_index then name_index = load_names () end      local files = name_index.files -    local idx = files.base[fontname] -             or files.bare[fontname] -    if idx then -        return files.full[idx] +    local basedata = files.base +    local baredata = files.bare +    for i = 1, #location_precedence do +        local location = location_precedence [i] +        local basenames = basedata [location] +        local barenames = baredata [location] [ext] +        local idx +        if basenames ~= nil then +            idx = basenames [fontname] +        end +        if not idx and barenames ~= nil then +            idx = barenames [fontname] +        end +        if idx then +            return files.full [idx] +        end      end      return ""  end diff --git a/luaotfload-features.lua b/luaotfload-features.lua index db0b1d0..f324be6 100644 --- a/luaotfload-features.lua +++ b/luaotfload-features.lua @@ -1097,9 +1097,9 @@ local select_lookup = function (request)  end  local supported = { -    b    = "bold", -    i    = "italic", -    bi   = "bolditalic", +    b    = "b", +    i    = "i", +    bi   = "bi",      aat  = false,      icu  = false,      gr   = false, diff --git a/luaotfload-merged.lua b/luaotfload-merged.lua index 3c63ba0..3e89856 100644 --- a/luaotfload-merged.lua +++ b/luaotfload-merged.lua @@ -1,6 +1,6 @@  -- merged file : luatex-fonts-merged.lua  -- parent file : luatex-fonts.lua --- merge date  : 09/13/13 10:59:07 +-- merge date  : 11/04/13 11:52:02  do -- begin closure to overcome local limits and interference @@ -134,6 +134,7 @@ local utfbom_16_le=P('\255\254')  local utfbom_8=P('\239\187\191')     local utfbom=utfbom_32_be+utfbom_32_le+utfbom_16_be+utfbom_16_le+utfbom_8  local utftype=utfbom_32_be*Cc("utf-32-be")+utfbom_32_le*Cc("utf-32-le")+utfbom_16_be*Cc("utf-16-be")+utfbom_16_le*Cc("utf-16-le")+utfbom_8*Cc("utf-8")+alwaysmatched*Cc("utf-8")  +local utfstricttype=utfbom_32_be*Cc("utf-32-be")+utfbom_32_le*Cc("utf-32-le")+utfbom_16_be*Cc("utf-16-be")+utfbom_16_le*Cc("utf-16-le")+utfbom_8*Cc("utf-8")  local utfoffset=utfbom_32_be*Cc(4)+utfbom_32_le*Cc(4)+utfbom_16_be*Cc(2)+utfbom_16_le*Cc(2)+utfbom_8*Cc(3)+Cc(0)  local utf8next=R("\128\191")  patterns.utfbom_32_be=utfbom_32_be @@ -149,6 +150,7 @@ patterns.utf8three=R("\224\239")*utf8next*utf8next  patterns.utf8four=R("\240\244")*utf8next*utf8next*utf8next  patterns.utfbom=utfbom  patterns.utftype=utftype +patterns.utfstricttype=utfstricttype  patterns.utfoffset=utfoffset  local utf8char=patterns.utf8one+patterns.utf8two+patterns.utf8three+patterns.utf8four  local validutf8char=utf8char^0*endofstring*Cc(true)+Cc(false) @@ -2611,6 +2613,31 @@ function number.signed(i)      return "-",-i    end  end +local zero=P("0")^1/"" +local plus=P("+")/"" +local minus=P("-") +local separator=S(".") +local digit=R("09") +local trailing=zero^1*#S("eE") +local exponent=(S("eE")*(plus+Cs((minus*zero^0*P(-1))/"")+minus)*zero^0*(P(-1)*Cc("0")+P(1)^1)) +local pattern_a=Cs(minus^0*digit^1*(separator/""*trailing+separator*(trailing+digit)^0)*exponent) +local pattern_b=Cs((exponent+P(1))^0) +function number.sparseexponent(f,n) +  if not n then +    n=f +    f="%e" +  end +  local tn=type(n) +  if tn=="string" then  +    local m=tonumber(n) +    if m then +      return lpegmatch((f=="%e" or f=="%E") and pattern_a or pattern_b,format(f,m)) +    end +  elseif tn=="number" then +    return lpegmatch((f=="%e" or f=="%E") and pattern_a or pattern_b,format(f,n)) +  end +  return tostring(n) +end  local preamble=[[  local type = type  local tostring = tostring @@ -2629,6 +2656,7 @@ local autosingle = string.autosingle  local autodouble = string.autodouble  local sequenced = table.sequenced  local formattednumber = number.formatted +local sparseexponent = number.sparseexponent  ]]  local template=[[  %s @@ -2701,6 +2729,14 @@ local format_E=function(f)    n=n+1    return format("format('%%%sE',a%s)",f,n)  end +local format_j=function(f) +  n=n+1 +  return format("sparseexponent('%%%se',a%s)",f,n) +end +local format_J=function(f) +  n=n+1 +  return format("sparseexponent('%%%sE',a%s)",f,n) +end  local format_x=function(f)    n=n+1    return format("format('%%%sx',a%s)",f,n) @@ -2858,6 +2894,10 @@ local format_M=function(f)    end    return format([[formattednumber(a%s,%q,",")]],n,f)  end +local format_z=function(f) +  n=n+(tonumber(f) or 1) +  return "''"  +end  local format_rest=function(s)    return format("%q",s)   end @@ -2891,12 +2931,13 @@ local builder=Cs { "start",  +V("c")+V("C")+V("S")   +V("Q")   +V("N") -+V("r")+V("h")+V("H")+V("u")+V("U")+V("p")+V("b")+V("t")+V("T")+V("l")+V("L")+V("I")+V("h")  -+V("w")  ++V("r")+V("h")+V("H")+V("u")+V("U")+V("p")+V("b")+V("t")+V("T")+V("l")+V("L")+V("I")+V("w")   +V("W")   +V("a")   +V("A")  -+V("m")+V("M") ++V("j")+V("J")  ++V("m")+V("M")  ++V("z")  +V("*")         )+V("*")      )*(P(-1)+Carg(1)) @@ -2932,8 +2973,11 @@ local builder=Cs { "start",    ["I"]=(prefix_any*P("I"))/format_I,    ["w"]=(prefix_any*P("w"))/format_w,    ["W"]=(prefix_any*P("W"))/format_W, +  ["j"]=(prefix_any*P("j"))/format_j, +  ["J"]=(prefix_any*P("J"))/format_J,    ["m"]=(prefix_tab*P("m"))/format_m,    ["M"]=(prefix_tab*P("M"))/format_M, +  ["z"]=(prefix_any*P("z"))/format_z,    ["a"]=(prefix_any*P("a"))/format_a,    ["A"]=(prefix_any*P("A"))/format_A,    ["*"]=Cs(((1-P("%"))^1+P("%%")/"%%")^1)/format_rest, @@ -3398,10 +3442,12 @@ nodes.handlers={}  local nodecodes={} for k,v in next,node.types  () do nodecodes[string.gsub(v,"_","")]=k end  local whatcodes={} for k,v in next,node.whatsits() do whatcodes[string.gsub(v,"_","")]=k end  local glyphcodes={ [0]="character","glyph","ligature","ghost","left","right" } +local disccodes={ [0]="discretionary","explicit","automatic","regular","first","second" }  nodes.nodecodes=nodecodes  nodes.whatcodes=whatcodes  nodes.whatsitcodes=whatcodes  nodes.glyphcodes=glyphcodes +nodes.disccodes=disccodes  local free_node=node.free  local remove_node=node.remove  local new_node=node.new @@ -5072,6 +5118,9 @@ fonts.names.resolvespec=fonts.names.resolve  function fonts.names.getfilename(askedname,suffix)     return ""  end +function fonts.names.ignoredfile(filename)  +  return false  +end  end -- closure @@ -5236,6 +5285,7 @@ afm.syncspace=true  afm.addligatures=true   afm.addtexligatures=true   afm.addkerns=true  +local applyruntimefixes=fonts.treatments and fonts.treatments.applyfixes  local function setmode(tfmdata,value)    if value then      tfmdata.properties.mode=lower(value) @@ -5425,7 +5475,7 @@ end  local addkerns,addligatures,addtexligatures,unify,normalize   function afm.load(filename)    filename=resolvers.findfile(filename,'afm') or "" -  if filename~="" then +  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) @@ -5475,6 +5525,9 @@ function afm.load(filename)          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 @@ -6325,7 +6378,7 @@ local report_otf=logs.reporter("fonts","otf loading")  local fonts=fonts  local otf=fonts.handlers.otf  otf.glists={ "gsub","gpos" } -otf.version=2.745  +otf.version=2.747   otf.cache=containers.define("fonts","otf",otf.version,true)  local fontdata=fonts.hashes.identifiers  local chardata=characters and characters.data  @@ -6346,11 +6399,17 @@ local syncspace=true  local forcenotdef=false  local includesubfonts=false  local overloadkerns=false  +local applyruntimefixes=fonts.treatments and fonts.treatments.applyfixes  local wildcard="*"  local default="dflt"  local fontloaderfields=fontloader.fields  local mainfields=nil  local glyphfields=nil  +local formats=fonts.formats +formats.otf="opentype" +formats.ttf="truetype" +formats.ttc="truetype" +formats.dfont="truetype"  registerdirective("fonts.otf.loader.cleanup",function(v) cleanup=tonumber(v) or (v and 1) or 0 end)  registerdirective("fonts.otf.loader.force",function(v) forceload=v end)  registerdirective("fonts.otf.loader.usemetatables",function(v) usemetatables=v end) @@ -6358,6 +6417,9 @@ registerdirective("fonts.otf.loader.pack",function(v) packdata=v end)  registerdirective("fonts.otf.loader.syncspace",function(v) syncspace=v end)  registerdirective("fonts.otf.loader.forcenotdef",function(v) forcenotdef=v end)  registerdirective("fonts.otf.loader.overloadkerns",function(v) overloadkerns=v end) +local function otf_format(filename) +  return formats[lower(file.suffix(filename))] +end  local function load_featurefile(raw,featurefile)    if featurefile and featurefile~="" then      if trace_loading then @@ -6544,7 +6606,7 @@ end  function enhancers.register(what,action)     actions[what]=action  end -function otf.load(filename,format,sub,featurefile) +function otf.load(filename,sub,featurefile)     local base=file.basename(file.removesuffix(filename))    local name=file.removesuffix(base)    local attr=lfs.attributes(filename) @@ -6642,7 +6704,7 @@ function otf.load(filename,format,sub,featurefile)        data={          size=size,          time=time, -        format=format, +        format=otf_format(filename),          featuredata=featurefiles,          resources={            filename=resolvers.unresolve(filename), @@ -6708,6 +6770,9 @@ function otf.load(filename,format,sub,featurefile)        report_otf("loading from cache using hash %a",hash)      end      enhance("unpack",data,filename,nil,false) +    if applyruntimefixes then +      applyruntimefixes(filename,data) +    end      enhance("add dimensions",data,filename,nil,false)      if trace_sequences then        showfeatureorder(data,filename) @@ -7183,14 +7248,6 @@ local g_directions={    gsub_reversecontextchain=-1,    gpos_reversecontextchain=-1,  } -local function supported(features) -  for i=1,#features do -    if features[i].ismac then -      return false -    end -  end -  return true -end  actions["reorganize subtables"]=function(data,filename,raw)    local resources=data.resources    local sequences={} @@ -7204,7 +7261,6 @@ actions["reorganize subtables"]=function(data,filename,raw)        for k=1,#dw do          local gk=dw[k]          local features=gk.features -        if not features or supported(features) then             local typ=gk.type            local chain=g_directions[typ] or 0            local subtables=gk.subtables @@ -7267,7 +7323,6 @@ actions["reorganize subtables"]=function(data,filename,raw)                markclass=markclass,              }            end -        end        end      end    end @@ -7954,10 +8009,19 @@ local function copytotfm(data,cache_id)          end        end      end +    local filename=constructors.checkedfilename(resources) +    local fontname=metadata.fontname +    local fullname=metadata.fullname or fontname +    local units=metadata.units_per_em or 1000 +    if units==0 then  +      units=1000  +      metadata.units_per_em=1000 +      report_otf("changing %a units to %a",0,units) +    end      local monospaced=metadata.isfixedpitch or (pfminfo.panose and pfminfo.panose.proportion=="Monospaced")      local charwidth=pfminfo.avgwidth  -    local italicangle=metadata.italicangle      local charxheight=pfminfo.os2_xheight and pfminfo.os2_xheight>0 and pfminfo.os2_xheight +    local italicangle=metadata.italicangle      properties.monospaced=monospaced      parameters.italicangle=italicangle      parameters.charwidth=charwidth @@ -7986,15 +8050,6 @@ local function copytotfm(data,cache_id)        end      end      spaceunits=tonumber(spaceunits) or 500 -    local filename=constructors.checkedfilename(resources) -    local fontname=metadata.fontname -    local fullname=metadata.fullname or fontname -    local units=metadata.units_per_em or 1000 -    if units==0 then  -      units=1000  -      metadata.units_per_em=1000 -      report_otf("changing %a units to %a",0,units) -    end      parameters.slant=0      parameters.space=spaceunits           parameters.space_stretch=units/2   @@ -8033,7 +8088,7 @@ local function copytotfm(data,cache_id)      parameters.units=units      properties.space=spacer      properties.encodingbytes=2 -    properties.format=data.format or fonts.formats[filename] or "opentype" +    properties.format=data.format or otf_format(filename) or formats.otf      properties.noglyphnames=true      properties.filename=filename      properties.fontname=fontname @@ -8058,9 +8113,8 @@ local function otftotfm(specification)      local name=specification.name      local sub=specification.sub      local filename=specification.filename -    local format=specification.format      local features=specification.features.normal -    local rawdata=otf.load(filename,format,sub,features and features.featurefile) +    local rawdata=otf.load(filename,sub,features and features.featurefile)      if rawdata and next(rawdata) then        rawdata.lookuphash={}        tfmdata=copytotfm(rawdata,cache_id) @@ -8142,41 +8196,33 @@ function otf.collectlookups(rawdata,kind,script,language)    end    return nil,nil  end -local function check_otf(forced,specification,suffix,what) +local function check_otf(forced,specification,suffix)    local name=specification.name    if forced then -    name=file.addsuffix(name,suffix,true) +    name=specification.forcedname     end    local fullname=findbinfile(name,suffix) or ""    if fullname=="" then      fullname=fonts.names.getfilename(name,suffix) or ""    end -  if fullname~="" then +  if fullname~="" and not fonts.names.ignoredfile(fullname) then      specification.filename=fullname -    specification.format=what      return read_from_otf(specification)    end  end -local function opentypereader(specification,suffix,what) +local function opentypereader(specification,suffix)    local forced=specification.forced or "" -  if forced=="otf" then -    return check_otf(true,specification,forced,"opentype") -  elseif forced=="ttf" or forced=="ttc" or forced=="dfont" then -    return check_otf(true,specification,forced,"truetype") +  if formats[forced] then +    return check_otf(true,specification,forced)    else -    return check_otf(false,specification,suffix,what) +    return check_otf(false,specification,suffix)    end  end -readers.opentype=opentypereader -local formats=fonts.formats -formats.otf="opentype" -formats.ttf="truetype" -formats.ttc="truetype" -formats.dfont="truetype" -function readers.otf (specification) return opentypereader(specification,"otf",formats.otf ) end -function readers.ttf (specification) return opentypereader(specification,"ttf",formats.ttf ) end -function readers.ttc (specification) return opentypereader(specification,"ttf",formats.ttc ) end -function readers.dfont(specification) return opentypereader(specification,"ttf",formats.dfont) end +readers.opentype=opentypereader  +function readers.otf (specification) return opentypereader(specification,"otf") end +function readers.ttf (specification) return opentypereader(specification,"ttf") end +function readers.ttc (specification) return opentypereader(specification,"ttf") end +function readers.dfont(specification) return opentypereader(specification,"ttf") end  function otf.scriptandlanguage(tfmdata,attr)    local properties=tfmdata.properties    return properties.script or "dflt",properties.language or "dflt" @@ -9283,6 +9329,11 @@ local features={    medi=s_medi,    fina=s_fina,    isol=s_isol, +  rphf=s_rphf, +  half=s_half, +  pref=s_pref, +  blwf=s_blwf, +  pstf=s_pstf,  }  analyzers.states=states  analyzers.features=features @@ -9659,6 +9710,7 @@ local default="dflt"  local nodecodes=nodes.nodecodes  local whatcodes=nodes.whatcodes  local glyphcodes=nodes.glyphcodes +local disccodes=nodes.disccodes  local glyph_code=nodecodes.glyph  local glue_code=nodecodes.glue  local disc_code=nodecodes.disc @@ -9666,6 +9718,7 @@ local whatsit_code=nodecodes.whatsit  local math_code=nodecodes.math  local dir_code=whatcodes.dir  local localpar_code=whatcodes.localpar +local discretionary_code=disccodes.discretionary  local ligature_code=glyphcodes.ligature  local privateattribute=attributes.private  local a_state=privateattribute('state') @@ -9913,13 +9966,13 @@ local function get_alternative_glyph(start,alternatives,value,trace_alternatives      end    end  end -local function multiple_glyphs(head,start,multiple)  +local function multiple_glyphs(head,start,multiple,ignoremarks)    local nofmultiples=#multiple    if nofmultiples>0 then      setfield(start,"char",multiple[1])      if nofmultiples>1 then        local sn=getnext(start) -      for k=2,nofmultiples do  +      for k=2,nofmultiples do          local n=copy_node(start)           setfield(n,"char",multiple[k])          setfield(n,"next",sn) @@ -9954,11 +10007,11 @@ function handlers.gsub_alternate(head,start,kind,lookupname,alternative,sequence    end    return head,start,true  end -function handlers.gsub_multiple(head,start,kind,lookupname,multiple) +function handlers.gsub_multiple(head,start,kind,lookupname,multiple,sequence)    if trace_multiples then      logprocess("%s: replacing %s by multiple %s",pref(kind,lookupname),gref(getchar(start)),gref(multiple))    end -  return multiple_glyphs(head,start,multiple) +  return multiple_glyphs(head,start,multiple,sequence.flags[1])  end  function handlers.gsub_ligature(head,start,kind,lookupname,ligature,sequence)    local s,stop,discfound=getnext(start),nil,false @@ -10292,7 +10345,6 @@ function handlers.gpos_pair(head,start,kind,lookupname,kerns,sequence)          prev=snext          snext=getnext(snext)        else -        local krn=kerns[nextchar]          if not krn then          elseif type(krn)=="table" then            if lookuptype=="pair" then  @@ -10365,34 +10417,6 @@ function chainprocs.reversesub(head,start,stop,kind,chainname,currentcontext,loo      return head,start,false    end  end -local function delete_till_stop(start,stop,ignoremarks)  -  local n=1 -  if start==stop then -  elseif ignoremarks then -    repeat  -      local next=getnext(start) -      if not marks[getchar(next)] then -        local components=getfield(next,"components") -        if components then  -          flush_node_list(components) -        end -        delete_node(start,next) -      end -      n=n+1 -    until next==stop -  else  -    repeat -      local next=getnext(start) -      local components=getfield(next,"components") -      if components then  -        flush_node_list(components) -      end -      delete_node(start,next) -      n=n+1 -    until next==stop -  end -  return n -end  function chainprocs.gsub_single(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex)    local current=start    local subtables=currentlookup.subtables @@ -10432,7 +10456,6 @@ function chainprocs.gsub_single(head,start,stop,kind,chainname,currentcontext,lo  end  chainmores.gsub_single=chainprocs.gsub_single  function chainprocs.gsub_multiple(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) -  delete_till_stop(start,stop)     local startchar=getchar(start)    local subtables=currentlookup.subtables    local lookupname=subtables[1] @@ -10451,7 +10474,7 @@ function chainprocs.gsub_multiple(head,start,stop,kind,chainname,currentcontext,        if trace_multiples then          logprocess("%s: replacing %s by multiple characters %s",cref(kind,chainname,chainlookupname,lookupname),gref(startchar),gref(replacements))        end -      return multiple_glyphs(head,start,replacements) +      return multiple_glyphs(head,start,replacements,currentlookup.flags[1])      end    end    return head,start,false @@ -10835,6 +10858,7 @@ function chainprocs.gpos_single(head,start,stop,kind,chainname,currentcontext,lo    end    return head,start,false  end +chainmores.gpos_single=chainprocs.gpos_single  function chainprocs.gpos_pair(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex,sequence)    local snext=getnext(start)    if snext then @@ -10903,6 +10927,7 @@ function chainprocs.gpos_pair(head,start,stop,kind,chainname,currentcontext,look    end    return head,start,false  end +chainmores.gpos_pair=chainprocs.gpos_pair  local function show_skip(kind,chainname,char,ck,class)    if ck[9] then      logwarning("%s: skipping char %s, class %a, rule %a, lookuptype %a, %a => %a",cref(kind,chainname),gref(char),class,ck[1],ck[2],ck[9],ck[10]) @@ -10910,6 +10935,10 @@ local function show_skip(kind,chainname,char,ck,class)      logwarning("%s: skipping char %s, class %a, rule %a, lookuptype %a",cref(kind,chainname),gref(char),class,ck[1],ck[2])    end  end +local quit_on_no_replacement=true +directives.register("otf.chain.quitonnoreplacement",function(value)  +  quit_on_no_replacement=value +end)  local function normal_handle_contextchain(head,start,kind,chainname,contexts,sequence,lookuphash)    local flags=sequence.flags    local done=false @@ -11119,7 +11148,11 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq            if chainlookup then              local cp=chainprocs[chainlookup.type]              if cp then -              head,start,done=cp(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence) +              local ok +              head,start,ok=cp(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence) +              if ok then +                done=true +              end              else                logprocess("%s: %s is not yet supported",cref(kind,chainname,chainlookupname),chainlookup.type)              end @@ -11146,19 +11179,24 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq                end              end              local chainlookupname=chainlookups[i] -            local chainlookup=lookuptable[chainlookupname]  -            local cp=chainlookup and chainmores[chainlookup.type] -            if cp then -              local ok,n -              head,start,ok,n=cp(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,i,sequence) -              if ok then -                done=true -                i=i+(n or 1) -              else +            local chainlookup=lookuptable[chainlookupname] +            if not chainlookup then +              i=i+1 +            else +              local cp=chainmores[chainlookup.type] +              if not cp then +                logprocess("%s: %s is not yet supported",cref(kind,chainname,chainlookupname),chainlookup.type)                  i=i+1 +              else +                local ok,n +                head,start,ok,n=cp(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,i,sequence) +                if ok then +                  done=true +                  i=i+(n or 1) +                else +                  i=i+1 +                end                end -            else -              i=i+1              end              if start then                start=getnext(start) @@ -11171,7 +11209,7 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq          if replacements then            head,start,done=chainprocs.reversesub(head,start,last,kind,chainname,ck,lookuphash,replacements)           else -          done=true  +          done=quit_on_no_replacement             if trace_contexts then              logprocess("%s: skipping match",cref(kind,chainname))            end @@ -11368,6 +11406,40 @@ local function featuresprocessor(head,font,attr)          if not lookupcache then             report_missing_cache(typ,lookupname)          else +          local function subrun(start) +            local head=start +            local done=false +            while start do +              local id=getid(start) +              if id==glyph_code and getfont(start)==font and getsubtype(start)<256 then +                local a=getattr(start,0) +                if a then +                  a=(a==attr) and (not attribute or getattr(start,a_state)==attribute) +                else +                  a=not attribute or getattr(start,a_state)==attribute +                end +                if a then +                  local lookupmatch=lookupcache[getchar(start)] +                  if lookupmatch then +                    local ok +                    head,start,ok=handler(head,start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,1) +                    if ok then +                      done=true +                    end +                  end +                  if start then start=getnext(start) end +                else +                  start=getnext(start) +                end +              else +                start=getnext(start) +              end +            end +            if done then +              success=true +              return head +            end +          end            while start do              local id=getid(start)              if id==glyph_code then @@ -11394,6 +11466,25 @@ local function featuresprocessor(head,font,attr)                else                  start=getnext(start)                end +            elseif id==disc_code then +              if getsubtype(start)==discretionary_code then +                local pre=getfield(start,"pre") +                if pre then +                  local new=subrun(pre) +                  if new then setfield(start,"pre",new) end +                end +                local post=getfield(start,"post") +                if post then +                  local new=subrun(post) +                  if new then setfield(start,"post",new) end +                end +                local replace=getfield(start,"replace") +                if replace then +                  local new=subrun(replace) +                  if new then setfield(start,"replace",new) end +                end +              end +              start=getnext(start)              elseif id==whatsit_code then                 local subtype=getsubtype(start)                if subtype==dir_code then @@ -11438,6 +11529,51 @@ local function featuresprocessor(head,font,attr)            end          end        else +        local function subrun(start) +          local head=start +          local done=false +          while start do +            local id=getid(start) +            if id==glyph_code and getfont(start)==font and getsubtype(start)<256 then +              local a=getattr(start,0) +              if a then +                a=(a==attr) and (not attribute or getattr(start,a_state)==attribute) +              else +                a=not attribute or getattr(start,a_state)==attribute +              end +              if a then +                for i=1,ns do +                  local lookupname=subtables[i] +                  local lookupcache=lookuphash[lookupname] +                  if lookupcache then +                    local lookupmatch=lookupcache[getchar(start)] +                    if lookupmatch then +                      local ok +                      head,start,ok=handler(head,start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,i) +                      if ok then +                        done=true +                        break +                      elseif not start then +                        break +                      end +                    end +                  else +                    report_missing_cache(typ,lookupname) +                  end +                end +                if start then start=getnext(start) end +              else +                start=getnext(start) +              end +            else +              start=getnext(start) +            end +          end +          if done then +            success=true +            return head +          end +        end          while start do            local id=getid(start)            if id==glyph_code then @@ -11475,6 +11611,25 @@ local function featuresprocessor(head,font,attr)              else                start=getnext(start)              end +          elseif id==disc_code then +            if getsubtype(start)==discretionary_code then +              local pre=getfield(start,"pre") +              if pre then +                local new=subrun(pre) +                if new then setfield(start,"pre",new) end +              end +              local post=getfield(start,"post") +              if post then +                local new=subrun(post) +                if new then setfield(start,"post",new) end +              end +              local replace=getfield(start,"replace") +              if replace then +                local new=subrun(replace) +                if new then setfield(start,"replace",new) end +              end +            end +            start=getnext(start)            elseif id==whatsit_code then              local subtype=getsubtype(start)              if subtype==dir_code then @@ -12635,6 +12790,7 @@ if not modules then modules={} end modules ['font-def']={  local format,gmatch,match,find,lower,gsub=string.format,string.gmatch,string.match,string.find,string.lower,string.gsub  local tostring,next=tostring,next  local lpegmatch=lpeg.match +local suffixonly,removesuffix=file.suffix,file.removesuffix  local allocate=utilities.storage.allocate  local trace_defining=false trackers .register("fonts.defining",function(v) trace_defining=v end)  local directive_embedall=false directives.register("fonts.embedall",function(v) directive_embedall=v end) @@ -12724,10 +12880,11 @@ definers.resolvers=definers.resolvers or {}  local resolvers=definers.resolvers  function resolvers.file(specification)    local name=resolvefile(specification.name)  -  local suffix=file.suffix(name) +  local suffix=lower(suffixonly(name))    if fonts.formats[suffix] then      specification.forced=suffix -    specification.name=file.removesuffix(name) +    specification.forcedname=name +    specification.name=removesuffix(name)    else      specification.name=name     end @@ -12739,10 +12896,11 @@ function resolvers.name(specification)      if resolved then        specification.resolved=resolved        specification.sub=sub -      local suffix=file.suffix(resolved) +      local suffix=lower(suffixonly(resolved))        if fonts.formats[suffix] then          specification.forced=suffix -        specification.name=file.removesuffix(resolved) +        specification.forcedname=resolved +        specification.name=removesuffix(resolved)        else          specification.name=resolved        end @@ -12758,8 +12916,9 @@ function resolvers.spec(specification)      if resolved then        specification.resolved=resolved        specification.sub=sub -      specification.forced=file.suffix(resolved) -      specification.name=file.removesuffix(resolved) +      specification.forced=lower(suffixonly(resolved)) +      specification.forcedname=resolved +      specification.name=removesuffix(resolved)      end    else      resolvers.name(specification) @@ -12774,8 +12933,7 @@ function definers.resolve(specification)    end    if specification.forced=="" then      specification.forced=nil -  else -    specification.forced=specification.forced +    specification.forcedname=nil    end    specification.hash=lower(specification.name..' @ '..constructors.hashfeatures(specification))    if specification.sub and specification.sub~="" then @@ -12820,7 +12978,7 @@ function definers.loadfont(specification)    if not tfmdata then      local forced=specification.forced or ""      if forced~="" then -      local reader=readers[lower(forced)] +      local reader=readers[lower(forced)]         tfmdata=reader and reader(specification)        if not tfmdata then          report_defining("forced type %a of %a not found",forced,specification.name)  | 
