From 499160de78a15bc9d9d24c4d1962fbd17b080f6e Mon Sep 17 00:00:00 2001 From: Philipp Gesang Date: Thu, 12 May 2016 23:55:19 +0200 Subject: [features] update addfeature() --- src/luaotfload-features.lua | 525 ++++++++++++++++++++++++++++++++------------ 1 file changed, 379 insertions(+), 146 deletions(-) diff --git a/src/luaotfload-features.lua b/src/luaotfload-features.lua index 8263b51..d6531d3 100644 --- a/src/luaotfload-features.lua +++ b/src/luaotfload-features.lua @@ -1340,15 +1340,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 } @@ -1357,11 +1357,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 @@ -1369,9 +1382,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 @@ -1379,185 +1664,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 -- cgit v1.2.3