diff options
| author | Philipp Gesang <phg@phi-gamma.net> | 2015-12-21 07:55:35 +0100 | 
|---|---|---|
| committer | Philipp Gesang <phg@phi-gamma.net> | 2015-12-21 07:55:35 +0100 | 
| commit | bd29de3d14bd6cfeb96111227f153cbccf0527a4 (patch) | |
| tree | c5ae30b5b63d27168e2d363e5ebb4e685a40470e | |
| parent | d12378937eb166900947717a588829ed247a3ba7 (diff) | |
| parent | fde58ab447fbfdbf0eb07c627d3f3b789cf36759 (diff) | |
| download | luaotfload-bd29de3d14bd6cfeb96111227f153cbccf0527a4.tar.gz | |
Merge pull request #312 from phi-gamma/master
improve letterspacing, synthetic features
| -rw-r--r-- | src/luaotfload-features.lua | 307 | ||||
| -rw-r--r-- | src/luaotfload-letterspace.lua | 86 | 
2 files changed, 344 insertions, 49 deletions
diff --git a/src/luaotfload-features.lua b/src/luaotfload-features.lua index 962806c..d212df5 100644 --- a/src/luaotfload-features.lua +++ b/src/luaotfload-features.lua @@ -938,18 +938,39 @@ local report_otf          = logs.reporter("fonts","otf loading")  --HH]]-- +--- start locals for addfeature() + +local utfbyte = unicode.utf8.byte + +local otf = handlers and handlers.otf --- filled in later during initialization + +local normalized = { +    substitution = "substitution", +    single       = "substitution", +    ligature     = "ligature", +    alternate    = "alternate", +    multiple     = "multiple", +    kern         = "kern", +} +  local types = {      substitution = "gsub_single",      ligature     = "gsub_ligature",      alternate    = "gsub_alternate", +    multiple     = "gsub_multiple", +    kern         = "gpos_pair",  }  setmetatableindex(types, function(t,k) t[k] = k return k end) -- "key" +--- stop locals for addfeature() +  local everywhere = { ["*"] = { ["*"] = true } } -- or: { ["*"] = { "*" } } -local noflags    = { } +local noflags = { } + +local tohash = table.tohash -local function addfeature (data, feature, specifications) +local function ancient_addfeature (data, feature, specifications)      local descriptions = data.descriptions      local resources    = data.resources      local lookups      = resources.lookups @@ -1089,8 +1110,256 @@ local function addfeature (data, feature, specifications)      end  end +local function current_addfeature(data,feature,specifications) +    local descriptions = data.descriptions +    local resources    = data.resources +    local features     = resources.features +    local sequences    = resources.sequences +    if not features or not sequences then +        return +    end +    local gsubfeatures = features.gsub +    if gsubfeatures and gsubfeatures[feature] then +        return -- already present +    end +    local fontfeatures = resources.features or everywhere +    local unicodes     = resources.unicodes +    local splitter     = lpeg.splitter(" ",unicodes) +    local done         = 0 +    local skip         = 0 +    if not specifications[1] then +        -- so we accept a one entry specification +        specifications = { specifications } +    end + +    local function tounicode(code) +        if not code then +            return +        elseif type(code) == "number" then +            return code +        else +            return unicodes[code] or utfbyte(code) +        end +    end + +    local coverup      = otf.coverup +    local coveractions = coverup.actions +    local stepkey      = coverup.stepkey +    local register     = coverup.register + +    for s=1,#specifications do +        local specification = specifications[s] +        local valid         = specification.valid +        if not valid or valid(data,specification,feature) then +            local initialize = specification.initialize +            if initialize then +                -- when false is returned we initialize only once +                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 defaulttype   = specification.type or "substitution" +            local featureflags  = specification.flags or noflags +            local featureorder  = specification.order or { feature } +            local added         = false +            local nofsteps      = 0 +            local steps         = { } +            for i=1,#askedsteps do +                local list        = askedsteps[i] +                local coverage    = { } +                local cover       = coveractions[featuretype] +                local format      = nil +                local featuretype = normalized[list.type or defaulttype] or "substitution" +                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 +                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 +                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 +                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" +                end +                if next(coverage) then +                    added = true +                    nofsteps = nofsteps + 1 +                    steps[nofsteps] = register(coverage,featuretype,format,feature,nofsteps,descriptions,resources) +                end +            end +            if added 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 +                local sequence = { +                    chain     = 0, +                    features  = { [feature] = askedfeatures }, +                    flags     = featureflags, +                    name      = feature, -- not needed +                    order     = featureorder, +                    [stepkey] = steps, +                    nofsteps  = nofsteps, +                    type      = types[featuretype], +                } +                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 +                end +                local k = gsubfeatures[feature] +                if not k then +                    k = { } +                    gsubfeatures[feature] = k +                end +                for script, languages in next, askedfeatures do +                    local kk = k[script] +                    if not kk then +                        kk = { } +                        k[script] = kk +                    end +                    for language, value in next, languages do +                        kk[language] = value +                    end +                end +            end +        end +    end +    if trace_loading then +        report_otf("registering feature %a, affected glyphs %a, skipped glyphs %a",feature,done,skip) +    end +end + +  ---[[ end snippet from font-otc.lua ]] +local tlig_order = { "tlig" } +  local tlig_specification = {      {          type      = "substitution", @@ -1100,8 +1369,8 @@ local tlig_specification = {              [0x0027] = 0x2019,                   -- quoteleft              [0x0060] = 0x2018,                   -- quoteright          }, -        flags     = { }, -        order     = { "tlig" }, +        flags     = noflags, +        order     = tlig_order,          prepend   = true,      },      { @@ -1120,8 +1389,8 @@ local tlig_specification = {              [0x00AB] = {0x003C, 0x003C},         -- LEFT-POINTING DOUBLE ANGLE QUOTATION MARK              [0x00BB] = {0x003E, 0x003E},         -- RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK          }, -        flags    = { }, -        order    = { "tlig" }, +        flags    = noflags, +        order    = tlig_order,          prepend  = true,      },      { @@ -1133,8 +1402,8 @@ local tlig_specification = {              [0x00A1] = {0x0021, 0x0060},         -- exclamdown              [0x00BF] = {0x003F, 0x0060},         -- questiondown          }, -        flags    = { }, -        order    = { "tlig" }, +        flags    = noflags, +        order    = tlig_order,          prepend  = true,      },  } @@ -1183,7 +1452,7 @@ local anum_specification = {          type     = "substitution",          features = { arab = { far = true, urd = true, snd = true } },          data     = anum_persian, -        flags    = { }, +        flags    = noflags,          order    = { "anum" },          valid    = valid,      }, @@ -1191,7 +1460,7 @@ local anum_specification = {          type     = "substitution",          features = { arab = { ["*"] = true } },          data     = anum_arabic, -        flags    = { }, +        flags    = noflags,          order    = { "anum" },          valid    = valid,      }, @@ -1201,13 +1470,13 @@ return {      init = function ()          if not fonts and fonts.handlers then -            logreport ("log", 0, "color", +            logreport ("log", 0, "features",                         "OTF mechanisms missing -- did you forget to \z                         load a font loader?")              return false          end -        local otf = fonts.handlers.otf +        otf = fonts.handlers.otf          local extrafeatures = {              tlig = tlig_specification, @@ -1215,16 +1484,26 @@ return {              anum = anum_specification,          } +        --- hack for backwards compat with TL2014 loader +        local addfeature = otf.version < 2.8 and current_addfeature +                                              or ancient_addfeature +          otf.enhancers.register ("check extra features", -                                function (data,filename, raw) +                                function (data, filename, raw)                                      for feature, specification in next, extrafeatures do +                                        logreport ("both", 3, "features", +                                                   "register synthetic feature ā%sā for %s font ā%sā(%d)", +                                                   feature, +                                                   data.format, +                                                   tostring (data.metadata and data.metadata.fontname or "<unknown>"), +                                                   data.subfont or -1)                                          addfeature (data, feature, specification)                                      end                                  end)          logreport = luaotfload.log.report          if not fonts then -            logreport ("log", 0, "color", +            logreport ("log", 0, "features",                         "OTF mechanisms missing -- did you forget to \z                         load a font loader?")              return false diff --git a/src/luaotfload-letterspace.lua b/src/luaotfload-letterspace.lua index 5fa25f9..0d6b8e8 100644 --- a/src/luaotfload-letterspace.lua +++ b/src/luaotfload-letterspace.lua @@ -20,17 +20,35 @@ local tonumber           = tonumber  local next               = next  local nodes, node, fonts = nodes, node, fonts +local nodedirect         = nodes.nuts + +local getfield           = nodedirect.getfield +local setfield           = nodedirect.setfield + +local field_setter = function (name) return function (n, ...) setfield (n, name, ...) end end +local field_getter = function (name) return function (n, ...) getfield (n, name, ...) end end +  --- As of December 2014 the faster ``node.direct.*`` interface is  --- preferred. -local nodedirect         = nodes.nuts -local getchar            = nodedirect.getchar +  local getfont            = nodedirect.getfont  local getid              = nodedirect.getid -local getnext            = nodedirect.getnext -local getprev            = nodedirect.getprev -local getfield           = nodedirect.getfield -local setfield           = nodedirect.setfield -local getsubtype         = nodedirect.getsubtype + +local getnext            = nodedirect.getnext or field_getter "next" +local setnext            = nodedirect.setnext or field_setter "next" + +local getprev            = nodedirect.getprev or field_getter "prev" +local setprev            = nodedirect.setprev or field_setter "prev" + +local getdisc            = nodedirect.getdisc or field_getter "disc" +local setdisc            = nodedirect.setdisc or field_setter "disc" + +local getsubtype         = nodedirect.getsubtype or field_getter "subtype" +local setsubtype         = nodedirect.setsubtype or field_setter "subtype" + +local getchar            = nodedirect.getchar or field_getter "subtype" +local setchar            = nodedirect.setchar or field_setter "subtype" +  local find_node_tail     = nodedirect.tail  local todirect           = nodedirect.tonut  local tonode             = nodedirect.tonode @@ -310,10 +328,11 @@ kerncharacters = function (head)          else            --- c = kerncharacters (c) --> taken care of after replacing            local s = start -          local p, n = getprev(s), s.next +          local p = getprev(s) +          local n = getnext(s)            local tail = find_node_tail(c)            if p then -            setfield(p, "next", c) +            setnext(p, c)              p = getprev(c)            else              head = c @@ -321,7 +340,7 @@ kerncharacters = function (head)            if n then              tail = getprev(n)            end -          setnext(tail, "next", n) +          setnext(tail, n)            start = c            setfield(s, "components", nil)            -- we now leak nodes ! @@ -368,9 +387,10 @@ kerncharacters = function (head)              then                -- keep              else -              prev_subtype = userkern_code +              setsubtype (prev, userkern_code)                local prev_kern = getfield(prev, "kern")                prev_kern = prev_kern + quaddata[lastfont] * krn +              setfield (prev, "kern", prev_kern)                done = true              end            end @@ -395,24 +415,20 @@ kerncharacters = function (head)            end          elseif pid == disc_code then -          -- a bit too complicated, we can best not copy and just calculate -          -- but we could have multiple glyphs involved so ...            local disc = prev -- disc -          local pre     = getfield(disc, "pre") -          local post    = getfield(disc, "post") -          local replace = getfield(disc, "replace") -          local prv     = getprev(disc) -          local nxt     = getnext(disc) +          local pre, post, replace = getdisc (disc) +          local prv = getprev(disc) +          local nxt = getnext(disc)            if pre and prv then -- must pair with start.prev              -- this one happens in most cases              local before = copy_node(prv) -            setfield(pre, "prev", before) -            setfield(before, "next", pre) -            setfield(before, "prev", nil) +            setprev(pre,    before) +            setnext(before, pre) +            setprev(before, nil)              pre = kerncharacters (before)              pre = getnext(pre) -            setfield(pre, "prev", nil) +            setprev(pre, nil)              setfield(disc, "pre", pre)              free_node(before)            end @@ -420,11 +436,11 @@ kerncharacters = function (head)            if post and nxt then  -- must pair with start              local after = copy_node(nxt)              local tail = find_node_tail(post) -            setfield(tail, "next", after) -            setfield(after, "prev", tail) -            setfield(after, "next", nil) +            setnext(tail,  after) +            setprev(after, tail) +            setnext(after, nil)              post = kerncharacters (post) -            setfield(tail, "next", nil) +            setnext(tail, nil)              setfield(disc, "post", post)              free_node(after)            end @@ -433,17 +449,17 @@ kerncharacters = function (head)              local before = copy_node(prv)              local after = copy_node(nxt)              local tail = find_node_tail(replace) -            setfield(replace, "prev", before) -            setfield(before,  "next", replace) -            setfield(before,  "prev", nil) -            setfield(tail,    "next", after) -            setfield(after,   "prev", tail) -            setfield(after,   "next", nil) +            setprev(replace, before) +            setnext(before,  replace) +            setprev(before,  nil) +            setnext(tail,    after) +            setprev(after,   tail) +            setnext(after,   nil)              replace = kerncharacters (before)              replace = getnext(replace) -            setfield(replace, "prev",      nil) -            setfield(after,   "prev.next", nil) -            setfield(disc,    "replace",   replace) +            setprev(replace, nil) +            setnext(getprev(after), nil) +            setfield(disc, "replace",   replace)              free_node(after)              free_node(before)  | 
