diff options
-rw-r--r-- | src/luaotfload-features.lua | 786 |
1 files changed, 8 insertions, 778 deletions
diff --git a/src/luaotfload-features.lua b/src/luaotfload-features.lua index 3f2c527..f56213d 100644 --- a/src/luaotfload-features.lua +++ b/src/luaotfload-features.lua @@ -1,5 +1,5 @@ if not modules then modules = { } end modules ["features"] = { - version = "2.7", + version = "2.8", comment = "companion to luaotfload-main.lua", author = "Hans Hagen, Khaled Hosny, Elie Roux, Philipp Gesang", copyright = "PRAGMA ADE / ConTeXt Development Team", @@ -9,7 +9,6 @@ if not modules then modules = { } end modules ["features"] = { local type = type local next = next local tonumber = tonumber -local tostring = tostring local lpeg = require "lpeg" local lpegmatch = lpeg.match @@ -20,20 +19,14 @@ local C = lpeg.C local table = table local tabletohash = table.tohash local tablesort = table.sort -local setmetatableindex = table.setmetatableindex -local insert = table.insert ----[[ begin included font-ltx.lua ]] --- this appears to be based in part on luatex-fonts-def.lua local fonts = fonts local definers = fonts.definers local handlers = fonts.handlers local fontidentifiers = fonts.hashes and fonts.hashes.identifiers - local otf = handlers.otf -local otfenhancers = otf.enhancers -local otffeatures = otf.features local config = config or { luaotfload = { run = { } } } @@ -41,41 +34,25 @@ local as_script = true local normalize = function () end if config.luaotfload.run.live ~= false then - normalize = handlers.otf.features.normalize + normalize = otf.features.normalize as_script = false end ---HH A bit of tuning for definitions. - -local constructors = fonts.constructors - -if constructors then - constructors.namemode = "specification" -- somehow latex needs this (changed name!) => will change into an overload -end - -local tfm = constructors.handlers.tfm -local tfmfeatures = constructors.features.tfm -local tfmenhancers = constructors.enhancers.tfm - ---[[HH-- +--[[HH (font-xtx) -- tricky: we sort of bypass the parser and directly feed all into the sub parser --HH]]-- -function fonts.definers.getspecification(str) +function definers.getspecification(str) return "", str, "", ":", str end local log = luaotfload.log local report = log.report -local stringfind = string.find -local stringlower = string.lower local stringgsub = string.gsub -local stringsub = string.sub local stringformat = string.format local stringis_empty = string.is_empty -local mathceil = math.ceil local cmp_by_idx = function (a, b) return a.idx < b.idx end @@ -1277,6 +1254,7 @@ end fonts.names.handle_request = handle_request + if as_script == true then --- skip the remainder of the file report ("log", 5, "features", "Exiting early from luaotfload-features.lua.") @@ -1287,634 +1265,9 @@ else registersplit ("", handle_request, "xetex path style") -- catches \font\text=[names] end ----[[ end included font-ltx.lua ]] - -- We assume that the other otf stuff is loaded already; though there’s -- another check below during the initialization phase. ----[[ begin snippet from font-otc.lua ]] -local trace_loading = false trackers.register("otf.loading", function(v) trace_loading = v end) -local report_otf = logs.reporter("fonts","otf loading") - ---[[HH-- - - In the userdata interface we can not longer tweak the loaded font as - conveniently as before. For instance, instead of pushing extra data in - in the table using the original structure, we now have to operate on - the mkiv representation. And as the fontloader interface is modelled - after fontforge we cannot change that one too much either. - ---HH]]-- - ---- start locals for addfeature() - -local utf8 = unicode.utf8 -local utfbyte = utf8.byte -local utflen = utf8.len - -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", - pair = "pair", - chainsubstitution = "chainsubstitution", - chainposition = "chainposition", -} - -local types = { - substitution = "gsub_single", - ligature = "gsub_ligature", - alternate = "gsub_alternate", - multiple = "gsub_multiple", - kern = "gpos_pair", - pair = "gpos_pair", - chainsubstitution = "gsub_contextchain", - chainposition = "gpos_contextchain", -} - -setmetatableindex(types, function(t,k) t[k] = k return k end) -- "key" - ---- stop locals for addfeature() - -local everywhere = { ["*"] = { ["*"] = true } } -- or: { ["*"] = { "*" } } -local noflags = { false, false, false, false } - -local tohash = table.tohash - -local function validspecification(specification,name) - local dataset = specification.dataset - if dataset then - -- okay - elseif specification[1] then - dataset = specification - specification = { dataset = dataset } - else - dataset = { { data = specification.data } } - specification.data = nil - specification.dataset = dataset - end - local first = dataset[1] - if first then - first = first.data - end - if not first then - report_otf("invalid feature specification, no dataset") - return - end - if type(name) ~= "string" then - name = specification.name or first.name - end - if type(name) ~= "string" then - report_otf("invalid feature specification, no name") - return - end - local n = #dataset - if n > 0 then - for i=1,n do - setmetatableindex(dataset[i],specification) - end - return specification, name - end -end - -local function addfeature(data,feature,specifications) - - -- todo: add some validator / check code so that we're more tolerant to - -- user errors - - if not specifications then - report_otf("missing specification") - return - end - - local descriptions = data.descriptions - local resources = data.resources - local features = resources.features - local sequences = resources.sequences - - if not features or not sequences then - report_otf("missing specification") - return - end - - local alreadydone = resources.alreadydone - if not alreadydone then - alreadydone = { } - resources.alreadydone = alreadydone - end - if alreadydone[specifications] then - return - else - alreadydone[specifications] = true - 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 - - local specifications = validspecification(specifications,feature) - if not specifications then - -- report_otf("invalid specification") - return - end - - local function tounicode(code) - if not code then - return - end - if type(code) == "number" then - return 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 - local coveractions = coverup.actions - 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 - - local dataset = specifications.dataset - - for s=1,#dataset do - local specification = dataset[s] - local valid = specification.valid -- nowhere used - local feature = specification.name or feature - if not feature or feature == "" then - report_otf("no valid name given for extra feature") - elseif not valid or valid(data,specification,feature) then -- anum uses this - 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 = 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 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 = nil - local format = nil - if featuretype == "substitution" then - category = "gsub" - coverage = prepare_substitution(list,featuretype) - elseif featuretype == "ligature" then - category = "gsub" - coverage = prepare_ligature(list,featuretype) - elseif featuretype == "alternate" then - category = "gsub" - coverage = prepare_alternate(list,featuretype) - elseif featuretype == "multiple" then - category = "gsub" - coverage = prepare_multiple(list,featuretype) - elseif featuretype == "kern" then - 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 coverage and next(coverage) then - nofsteps = nofsteps + 1 - steps[nofsteps] = register(coverage,featuretype,format,feature,nofsteps,descriptions,resources) - end - end - 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 = featurechain, - features = { [feature] = askedfeatures }, - flags = featureflags, - name = feature, -- redundant - order = featureorder, - [stepkey] = steps, - nofsteps = nofsteps, - type = types[featuretype], - } - -- todo : before|after|index - local prepend = specification.prepend - if prepend == true then - prepend = 1 - end - if type(prepend) == "number" then - -- okay - elseif type(prepend) == "string" then - local index = false - for i=1,#sequences do - local s = sequences[i] - local f = s.features - if f then - for k in next, f do - if k == prepend then - index = i - break - end - end - if index then - break - end - end - end - prepend = index - elseif prepend == true then - prepend = 1 - end - if not prepend or prepend <= 0 or prepend > #sequences then - insert(sequences,sequence) - else - insert(sequences,prepend,sequence) - end - -- register in metadata (merge as there can be a few) - local features = fontfeatures[category] - if not features then - features = { } - fontfeatures[category] = features - end - local k = features[feature] - if not k then - k = { } - features[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_specification = { { @@ -1964,64 +1317,6 @@ local tlig_specification = { }, } -local anum_arabic = { --- these are the same as in font-otc - [0x0030] = 0x0660, - [0x0031] = 0x0661, - [0x0032] = 0x0662, - [0x0033] = 0x0663, - [0x0034] = 0x0664, - [0x0035] = 0x0665, - [0x0036] = 0x0666, - [0x0037] = 0x0667, - [0x0038] = 0x0668, - [0x0039] = 0x0669, -} - -local anum_persian = {--- these are the same as in font-otc - [0x0030] = 0x06F0, - [0x0031] = 0x06F1, - [0x0032] = 0x06F2, - [0x0033] = 0x06F3, - [0x0034] = 0x06F4, - [0x0035] = 0x06F5, - [0x0036] = 0x06F6, - [0x0037] = 0x06F7, - [0x0038] = 0x06F8, - [0x0039] = 0x06F9, -} - -local function valid(data) - local features = data.resources.features - if features then - for k, v in next, features do - for k, v in next, v do - if v.arab then - return true - end - end - end - end -end - -local anum_specification = { - { - type = "substitution", - features = { arab = { far = true, urd = true, snd = true } }, - data = anum_persian, - flags = noflags, - order = { "anum" }, - valid = valid, - }, - { - type = "substitution", - features = { arab = { ["*"] = true } }, - data = anum_arabic, - flags = noflags, - order = { "anum" }, - valid = valid, - }, -} - local rot13_specification = { type = "substitution", features = everywhere, @@ -2045,38 +1340,10 @@ local rot13_specification = { prepend = true, } - -local extrafeatures = { } -local knownfeatures = { } - -function add_otf_feature (name, specification) - if type (name) == "table" then - specification = name - end - specification, name = validspecification (specification, name) - if type (specification) ~= "table" then - logreport ("both", 0, "features", - "invalid feature specification “%s”", tostring (name)) - return - end - specification.name = name - if name and specification then - if knownfeatures [name] then - logreport ("both", 0, "features", - "prevent redefinition of extra feature “%s”", name) - else - extrafeatures [#extrafeatures + 1] = specification - knownfeatures [name] = true - end - end -end - -otf.addfeature = add_otf_feature - local autofeatures = { - --- always present with Luaotfload + --- always present with Luaotfload; anum for Arabic and Persian is + --- predefined in font-otc. { "tlig" , tlig_specification , "tex ligatures and substitutions" }, - { "anum" , anum_specification , "arabic numerals" }, { "rot13", rot13_specification, "rot13" }, } @@ -2087,37 +1354,7 @@ local add_auto_features = function () for i = 1, nfeats do local name, spec, desc = unpack (autofeatures [i]) spec.description = desc - add_otf_feature (name, spec) - end -end - -local install_extra_features = function (data, filename, raw) - local format = data.format - if not format then - --- font not fully loaded, happens with TFM/PFB when loaded the - --- classical fashion - logreport ("both", 4, "features", - "no format info for font “%s”", filename) - elseif not data.metadata and format ~= "type1" then - logreport ("both", 4, "features", - "no metadata received from font “%s”; not \z - installing extra features.", filename) - return - end - for i = 1, #extrafeatures do - local specification = extrafeatures [i] - local feature = specification.name - local metadata = data.metadata - local fontname = metadata and tostring (metadata.fontname) - or data.name or "<unknown>" - local subfont = metadata and tonumber (metadata.subfontindex) - or -1 - if not fontname then fontname = filename end - if not subfont then subfont = -1 end - logreport ("both", 3, "features", - "register synthetic feature “%s” for %s font “%s”(%d)", - feature, format or "tfm", filename, subfont) - addfeature (data, feature, specification) --> for otf and type1 + otf.addfeature (name, spec) end end @@ -2135,13 +1372,6 @@ return { add_auto_features () - otf = fonts.handlers.otf - otfenhancers.addfeature = addfeature - tfmenhancers.register ("check extra features", - install_extra_features) - otfenhancers.register ("check extra features", - install_extra_features) - return true end } |