summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilipp Gesang <phg@phi-gamma.net>2016-08-15 23:22:09 +0200
committerGitHub <noreply@github.com>2016-08-15 23:22:09 +0200
commita25067d7f3095cc3a3a28994e48ba1e02bdd3045 (patch)
treec47c5486ff83cef4a165bf6ae29e4cb27fcf10ee
parent9e2db3ead715ceecf2afc879aefc020f84ea4f09 (diff)
parent371249323abe5c51bec06163f3835e09149b5bd8 (diff)
downloadluaotfload-a25067d7f3095cc3a3a28994e48ba1e02bdd3045.tar.gz
Merge pull request #376 from phi-gamma/master
font-otc now part of the loader
-rwxr-xr-xscripts/mkimport21
-rwxr-xr-xscripts/mkstatus1
-rw-r--r--src/fontloader/misc/fontloader-font-otc.lua952
-rw-r--r--src/fontloader/misc/fontloader-fonts.lua2
-rw-r--r--src/fontloader/runtime/fontloader-reference.lua773
-rw-r--r--src/luaotfload-features.lua786
-rw-r--r--src/luaotfload-init.lua1
7 files changed, 1748 insertions, 788 deletions
diff --git a/scripts/mkimport b/scripts/mkimport
index c283509..75b4117 100755
--- a/scripts/mkimport
+++ b/scripts/mkimport
@@ -238,6 +238,7 @@ local imports = {
{ name = "font-lua" , ours = "font-lua" , kind = kind_merged },
{ name = "font-map" , ours = "font-map" , kind = kind_merged },
{ name = "font-ocl" , ours = "font-ocl" , kind = kind_merged },
+ { name = "font-otc" , ours = "font-otc" , kind = kind_merged },
{ name = "font-onr" , ours = "font-onr" , kind = kind_merged },
{ name = "font-one" , ours = "font-one" , kind = kind_merged },
{ name = "font-osd" , ours = "font-osd" , kind = kind_merged },
@@ -307,15 +308,16 @@ local package = {
--- [33] font-ots.lua
--- [34] font-osd.lua
--- [35] font-ocl.lua
---- [36] font-onr.lua
---- [37] font-one.lua
---- [38] font-afk.lua
---- [22] font-tfm.lua
---- [39] font-lua.lua
---- [40] font-def.lua
---- [41] font-xtx.lua
---- [42] luatex-fonts-ext.lua
---- [43] font-gbn.lua
+--- [36] font-otc.lua
+--- [37] font-onr.lua
+--- [38] font-one.lua
+--- [39] font-afk.lua
+--- [40] font-tfm.lua
+--- [41] font-lua.lua
+--- [42] font-def.lua
+--- [43] font-xtx.lua
+--- [44] luatex-fonts-ext.lua
+--- [45] font-gbn.lua
---
--- Of these, nos. 01--11 are provided by the Lualibs. Keeping them
--- around in the Luaotfload fontloader is therefore unnecessary.
@@ -373,6 +375,7 @@ local package = {
"font-ots",
"font-osd",
"font-ocl",
+ "font-otc",
"font-onr",
"font-one",
"font-afk",
diff --git a/scripts/mkstatus b/scripts/mkstatus
index 84474fb..4fda6a7 100755
--- a/scripts/mkstatus
+++ b/scripts/mkstatus
@@ -113,6 +113,7 @@ local names = {
{ miscdir, "fontloader-font-ots.lua", },
{ miscdir, "fontloader-font-osd.lua", },
{ miscdir, "fontloader-font-ocl.lua", },
+ { miscdir, "fontloader-font-otc.lua", },
--- lua libraries
{ miscdir, "fontloader-languages.lua", },
diff --git a/src/fontloader/misc/fontloader-font-otc.lua b/src/fontloader/misc/fontloader-font-otc.lua
new file mode 100644
index 0000000..a553209
--- /dev/null
+++ b/src/fontloader/misc/fontloader-font-otc.lua
@@ -0,0 +1,952 @@
+if not modules then modules = { } end modules ['font-otc'] = {
+ version = 1.001,
+ comment = "companion to font-otf.lua (context)",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+local format, insert, sortedkeys, tohash = string.format, table.insert, table.sortedkeys, table.tohash
+local type, next = type, next
+local lpegmatch = lpeg.match
+local utfbyte, utflen = utf.byte, utf.len
+
+-- we assume that the other otf stuff is loaded already
+
+local trace_loading = false trackers.register("otf.loading", function(v) trace_loading = v end)
+local report_otf = logs.reporter("fonts","otf loading")
+
+local fonts = fonts
+local otf = fonts.handlers.otf
+local registerotffeature = otf.features.register
+local setmetatableindex = table.setmetatableindex
+
+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",
+}
+
+local names = {
+ gsub_single = "gsub",
+ gsub_multiple = "gsub",
+ gsub_alternate = "gsub",
+ gsub_ligature = "gsub",
+ gsub_context = "gsub",
+ gsub_contextchain = "gsub",
+ gsub_reversecontextchain = "gsub",
+ gpos_single = "gpos",
+ gpos_pair = "gpos",
+ gpos_cursive = "gpos",
+ gpos_mark2base = "gpos",
+ gpos_mark2ligature = "gpos",
+ gpos_mark2mark = "gpos",
+ gpos_context = "gpos",
+ gpos_contextchain = "gpos",
+}
+
+setmetatableindex(types, function(t,k) t[k] = k return k end) -- "key"
+
+local everywhere = { ["*"] = { ["*"] = true } } -- or: { ["*"] = { "*" } }
+local noflags = { false, false, false, false }
+
+-- beware: shared, maybe we should copy the sequence
+
+local function getrange(sequences,category)
+ local count = #sequences
+ local first = nil
+ local last = nil
+ for i=1,count do
+ local t = sequences[i].type
+ if t and names[t] == category then
+ if not first then
+ first = i
+ end
+ last = i
+ end
+ end
+ return first or 1, last or count
+end
+
+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
+
+ local function report(name,category,position,first,last,sequences)
+ report_otf("injecting name %a of category %a at position %i in [%i,%i] of [%i,%i]",
+ name,category,position,first,last,1,#sequences)
+ end
+
+ local function inject(specification,sequences,sequence,first,last,category,name)
+ local position = specification.position or false
+ if not position then
+ position = specification.prepend
+ if position == true then
+ if trace_loading then
+ report(name,category,first,first,last,sequences)
+ end
+ insert(sequences,first,sequence)
+ return
+ end
+ end
+ if not position then
+ position = specification.append
+ if position == true then
+ if trace_loading then
+ report(name,category,last+1,first,last,sequences)
+ end
+ insert(sequences,last+1,sequence)
+ return
+ end
+ end
+ local kind = type(position)
+ if kind == "string" then
+ local index = false
+ for i=first,last do
+ local s = sequences[i]
+ local f = s.features
+ if f then
+ for k in next, f do
+ if k == position then
+ index = i
+ break
+ end
+ end
+ if index then
+ break
+ end
+ end
+ end
+ if index then
+ position = index
+ else
+ position = last + 1
+ end
+ elseif kind == "number" then
+ if position < 0 then
+ position = last - position + 1
+ end
+ if position > last then
+ position = last + 1
+ elseif position < first then
+ position = first
+ end
+ else
+ position = last + 1
+ end
+ if trace_loading then
+ report(name,category,position,first,last,sequences)
+ end
+ insert(sequences,position,sequence)
+ end
+
+ 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 steptype = types[featuretype]
+ local sequence = {
+ chain = featurechain,
+ features = { [feature] = askedfeatures },
+ flags = featureflags,
+ name = feature, -- redundant
+ order = featureorder,
+ [stepkey] = steps,
+ nofsteps = nofsteps,
+ type = steptype,
+ }
+ -- position | prepend | append
+ local first, last = getrange(sequences,category)
+ inject(specification,sequences,sequence,first,last,category,feature)
+ -- 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
+
+otf.enhancers.addfeature = addfeature
+
+local extrafeatures = { }
+local knownfeatures = { }
+
+function otf.addfeature(name,specification)
+ if type(name) == "table" then
+ specification = name
+ end
+ if type(specification) ~= "table" then
+ report_otf("invalid feature specification, no valid table")
+ return
+ end
+ specification, name = validspecification(specification,name)
+ if name and specification then
+ local slot = knownfeatures[name]
+ if slot then
+ -- we overload one .. should be option
+ else
+ slot = #extrafeatures + 1
+ knownfeatures[name] = slot
+ end
+ specification.name = name -- to be sure
+ extrafeatures[slot] = specification
+ -- report_otf("adding feature %a @ %i",name,slot)
+ end
+end
+
+-- for feature, specification in next, extrafeatures do
+-- addfeature(data,feature,specification)
+-- end
+
+local function enhance(data,filename,raw)
+ for slot=1,#extrafeatures do
+ local specification = extrafeatures[slot]
+ addfeature(data,specification.name,specification)
+ end
+end
+
+otf.enhancers.enhance = enhance
+
+otf.enhancers.register("check extra features",enhance)
+
+-- tlig --
+
+local tlig = { -- we need numbers for some fonts so ...
+ -- endash = "hyphen hyphen",
+ -- emdash = "hyphen hyphen hyphen",
+ [0x2013] = { 0x002D, 0x002D },
+ [0x2014] = { 0x002D, 0x002D, 0x002D },
+ -- quotedblleft = "quoteleft quoteleft",
+ -- quotedblright = "quoteright quoteright",
+ -- quotedblleft = "grave grave",
+ -- quotedblright = "quotesingle quotesingle",
+ -- quotedblbase = "comma comma",
+}
+
+local tlig_specification = {
+ type = "ligature",
+ features = everywhere,
+ data = tlig,
+ order = { "tlig" },
+ flags = noflags,
+ prepend = true,
+}
+
+otf.addfeature("tlig",tlig_specification)
+
+registerotffeature {
+ -- this makes it a known feature (in tables)
+ name = 'tlig',
+ description = 'tex ligatures',
+}
+
+-- trep
+
+local trep = {
+ -- [0x0022] = 0x201D,
+ [0x0027] = 0x2019,
+ -- [0x0060] = 0x2018,
+}
+
+local trep_specification = {
+ type = "substitution",
+ features = everywhere,
+ data = trep,
+ order = { "trep" },
+ flags = noflags,
+ prepend = true,
+}
+
+otf.addfeature("trep",trep_specification)
+
+registerotffeature {
+ -- this makes it a known feature (in tables)
+ name = 'trep',
+ description = 'tex replacements',
+}
+
+-- -- tcom (obsolete, was already not set for a while)
+
+-- if characters.combined then
+--
+-- local tcom = { }
+--
+-- local function initialize()
+-- characters.initialize()
+-- for first, seconds in next, characters.combined do
+-- for second, combination in next, seconds do
+-- tcom[combination] = { first, second }
+-- end
+-- end
+-- -- return false
+-- end
+--
+-- local tcom_specification = {
+-- type = "ligature",
+-- features = everywhere,
+-- data = tcom,
+-- order = { "tcom" },
+-- flags = noflags,
+-- initialize = initialize,
+-- }
+--
+-- otf.addfeature("tcom",tcom_specification)
+--
+-- registerotffeature {
+-- name = 'tcom',
+-- description = 'tex combinations',
+-- }
+--
+-- end
+
+-- anum
+
+local anum_arabic = {
+ [0x0030] = 0x0660,
+ [0x0031] = 0x0661,
+ [0x0032] = 0x0662,
+ [0x0033] = 0x0663,
+ [0x0034] = 0x0664,
+ [0x0035] = 0x0665,
+ [0x0036] = 0x0666,
+ [0x0037] = 0x0667,
+ [0x0038] = 0x0668,
+ [0x0039] = 0x0669,
+}
+
+local anum_persian = {
+ [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 = { urd = true, dflt = true } },
+ order = { "anum" },
+ data = anum_arabic,
+ flags = noflags, -- { },
+ valid = valid,
+ },
+ {
+ type = "substitution",
+ features = { arab = { urd = true } },
+ order = { "anum" },
+ data = anum_persian,
+ flags = noflags, -- { },
+ valid = valid,
+ },
+}
+
+otf.addfeature("anum",anum_specification) -- todo: only when there is already an arab script feature
+
+registerotffeature {
+ -- this makes it a known feature (in tables)
+ name = 'anum',
+ description = 'arabic digits',
+}
+
+-- maybe:
+
+-- fonts.handlers.otf.addfeature("hangulfix",{
+-- type = "substitution",
+-- features = { ["hang"] = { ["*"] = true } },
+-- data = {
+-- [0x1160] = 0x119E,
+-- },
+-- order = { "hangulfix" },
+-- flags = { },
+-- prepend = true,
+-- })
+
+-- fonts.handlers.otf.features.register {
+-- name = 'hangulfix',
+-- description = 'fixes for hangul',
+-- }
+
+-- fonts.handlers.otf.addfeature {
+-- name = "stest",
+-- type = "substitution",
+-- data = {
+-- a = "X",
+-- b = "P",
+-- }
+-- }
+-- fonts.handlers.otf.addfeature {
+-- name = "atest",
+-- type = "alternate",
+-- data = {
+-- a = { "X", "Y" },
+-- b = { "P", "Q" },
+-- }
+-- }
+-- fonts.handlers.otf.addfeature {
+-- name = "mtest",
+-- type = "multiple",
+-- data = {
+-- a = { "X", "Y" },
+-- b = { "P", "Q" },
+-- }
+-- }
+-- fonts.handlers.otf.addfeature {
+-- name = "ltest",
+-- type = "ligature",
+-- data = {
+-- X = { "a", "b" },
+-- Y = { "d", "a" },
+-- }
+-- }
+-- fonts.handlers.otf.addfeature {
+-- name = "ktest",
+-- type = "kern",
+-- data = {
+-- a = { b = -500 },
+-- }
+-- }
diff --git a/src/fontloader/misc/fontloader-fonts.lua b/src/fontloader/misc/fontloader-fonts.lua
index 41b95d9..c21389c 100644
--- a/src/fontloader/misc/fontloader-fonts.lua
+++ b/src/fontloader/misc/fontloader-fonts.lua
@@ -259,6 +259,8 @@ if non_generic_context.luatex_fonts.skip_loading ~= true then
loadmodule('font-osd.lua')
loadmodule('font-ocl.lua') -- svg needs 0.97 (for fix in memstreams)
+ loadmodule('font-otc.lua')
+
-- type one code
loadmodule('font-onr.lua') -- was font-afm.lua
diff --git a/src/fontloader/runtime/fontloader-reference.lua b/src/fontloader/runtime/fontloader-reference.lua
index 4cefe9a..6ef9430 100644
--- a/src/fontloader/runtime/fontloader-reference.lua
+++ b/src/fontloader/runtime/fontloader-reference.lua
@@ -1,6 +1,6 @@
-- merged file : c:/data/develop/context/sources/luatex-fonts-merged.lua
-- parent file : c:/data/develop/context/sources/luatex-fonts.lua
--- merge date : 07/30/16 00:26:47
+-- merge date : 08/11/16 13:56:03
do -- begin closure to overcome local limits and interference
@@ -23430,6 +23430,777 @@ end -- closure
do -- begin closure to overcome local limits and interference
+if not modules then modules={} end modules ['font-otc']={
+ version=1.001,
+ comment="companion to font-otf.lua (context)",
+ author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright="PRAGMA ADE / ConTeXt Development Team",
+ license="see context related readme files"
+}
+local format,insert,sortedkeys,tohash=string.format,table.insert,table.sortedkeys,table.tohash
+local type,next=type,next
+local lpegmatch=lpeg.match
+local utfbyte,utflen=utf.byte,utf.len
+local trace_loading=false trackers.register("otf.loading",function(v) trace_loading=v end)
+local report_otf=logs.reporter("fonts","otf loading")
+local fonts=fonts
+local otf=fonts.handlers.otf
+local registerotffeature=otf.features.register
+local setmetatableindex=table.setmetatableindex
+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",
+}
+local names={
+ gsub_single="gsub",
+ gsub_multiple="gsub",
+ gsub_alternate="gsub",
+ gsub_ligature="gsub",
+ gsub_context="gsub",
+ gsub_contextchain="gsub",
+ gsub_reversecontextchain="gsub",
+ gpos_single="gpos",
+ gpos_pair="gpos",
+ gpos_cursive="gpos",
+ gpos_mark2base="gpos",
+ gpos_mark2ligature="gpos",
+ gpos_mark2mark="gpos",
+ gpos_context="gpos",
+ gpos_contextchain="gpos",
+}
+setmetatableindex(types,function(t,k) t[k]=k return k end)
+local everywhere={ ["*"]={ ["*"]=true } }
+local noflags={ false,false,false,false }
+local function getrange(sequences,category)
+ local count=#sequences
+ local first=nil
+ local last=nil
+ for i=1,count do
+ local t=sequences[i].type
+ if t and names[t]==category then
+ if not first then
+ first=i
+ end
+ last=i
+ end
+ end
+ return first or 1,last or count
+end
+local function validspecification(specification,name)
+ local dataset=specification.dataset
+ if dataset then
+ 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)
+ 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
+ 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
+ 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
+ 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)
+ 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
+ end
+ end
+ end
+ if nofsequences>0 then
+ 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
+ rulesize=rulesize+1
+ rulehash[rulesize]={
+ nofrules,
+ lookuptype,
+ sequence,
+ start,
+ stop,
+ lookups,
+ replacements,
+ subtype,
+ }
+ for unic in next,sequence[start] do
+ local cu=coverage[unic]
+ if not cu then
+ coverage[unic]=rulehash
+ end
+ end
+ end
+ end
+ end
+ return coverage
+ end
+ local dataset=specifications.dataset
+ local function report(name,category,position,first,last,sequences)
+ report_otf("injecting name %a of category %a at position %i in [%i,%i] of [%i,%i]",
+ name,category,position,first,last,1,#sequences)
+ end
+ local function inject(specification,sequences,sequence,first,last,category,name)
+ local position=specification.position or false
+ if not position then
+ position=specification.prepend
+ if position==true then
+ if trace_loading then
+ report(name,category,first,first,last,sequences)
+ end
+ insert(sequences,first,sequence)
+ return
+ end
+ end
+ if not position then
+ position=specification.append
+ if position==true then
+ if trace_loading then
+ report(name,category,last+1,first,last,sequences)
+ end
+ insert(sequences,last+1,sequence)
+ return
+ end
+ end
+ local kind=type(position)
+ if kind=="string" then
+ local index=false
+ for i=first,last do
+ local s=sequences[i]
+ local f=s.features
+ if f then
+ for k in next,f do
+ if k==position then
+ index=i
+ break
+ end
+ end
+ if index then
+ break
+ end
+ end
+ end
+ if index then
+ position=index
+ else
+ position=last+1
+ end
+ elseif kind=="number" then
+ if position<0 then
+ position=last-position+1
+ end
+ if position>last then
+ position=last+1
+ elseif position<first then
+ position=first
+ end
+ else
+ position=last+1
+ end
+ if trace_loading then
+ report(name,category,position,first,last,sequences)
+ end
+ insert(sequences,position,sequence)
+ end
+ for s=1,#dataset do
+ local specification=dataset[s]
+ local valid=specification.valid
+ 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
+ local initialize=specification.initialize
+ if initialize then
+ 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
+ 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 steptype=types[featuretype]
+ local sequence={
+ chain=featurechain,
+ features={ [feature]=askedfeatures },
+ flags=featureflags,
+ name=feature,
+ order=featureorder,
+ [stepkey]=steps,
+ nofsteps=nofsteps,
+ type=steptype,
+ }
+ local first,last=getrange(sequences,category)
+ inject(specification,sequences,sequence,first,last,category,feature)
+ 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
+otf.enhancers.addfeature=addfeature
+local extrafeatures={}
+local knownfeatures={}
+function otf.addfeature(name,specification)
+ if type(name)=="table" then
+ specification=name
+ end
+ if type(specification)~="table" then
+ report_otf("invalid feature specification, no valid table")
+ return
+ end
+ specification,name=validspecification(specification,name)
+ if name and specification then
+ local slot=knownfeatures[name]
+ if slot then
+ else
+ slot=#extrafeatures+1
+ knownfeatures[name]=slot
+ end
+ specification.name=name
+ extrafeatures[slot]=specification
+ end
+end
+local function enhance(data,filename,raw)
+ for slot=1,#extrafeatures do
+ local specification=extrafeatures[slot]
+ addfeature(data,specification.name,specification)
+ end
+end
+otf.enhancers.enhance=enhance
+otf.enhancers.register("check extra features",enhance)
+local tlig={
+ [0x2013]={ 0x002D,0x002D },
+ [0x2014]={ 0x002D,0x002D,0x002D },
+}
+local tlig_specification={
+ type="ligature",
+ features=everywhere,
+ data=tlig,
+ order={ "tlig" },
+ flags=noflags,
+ prepend=true,
+}
+otf.addfeature("tlig",tlig_specification)
+registerotffeature {
+ name='tlig',
+ description='tex ligatures',
+}
+local trep={
+ [0x0027]=0x2019,
+}
+local trep_specification={
+ type="substitution",
+ features=everywhere,
+ data=trep,
+ order={ "trep" },
+ flags=noflags,
+ prepend=true,
+}
+otf.addfeature("trep",trep_specification)
+registerotffeature {
+ name='trep',
+ description='tex replacements',
+}
+local anum_arabic={
+ [0x0030]=0x0660,
+ [0x0031]=0x0661,
+ [0x0032]=0x0662,
+ [0x0033]=0x0663,
+ [0x0034]=0x0664,
+ [0x0035]=0x0665,
+ [0x0036]=0x0666,
+ [0x0037]=0x0667,
+ [0x0038]=0x0668,
+ [0x0039]=0x0669,
+}
+local anum_persian={
+ [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={ urd=true,dflt=true } },
+ order={ "anum" },
+ data=anum_arabic,
+ flags=noflags,
+ valid=valid,
+ },
+ {
+ type="substitution",
+ features={ arab={ urd=true } },
+ order={ "anum" },
+ data=anum_persian,
+ flags=noflags,
+ valid=valid,
+ },
+}
+otf.addfeature("anum",anum_specification)
+registerotffeature {
+ name='anum',
+ description='arabic digits',
+}
+
+end -- closure
+
+do -- begin closure to overcome local limits and interference
+
if not modules then modules={} end modules ['font-onr']={
version=1.001,
comment="companion to font-ini.mkiv",
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
}
diff --git a/src/luaotfload-init.lua b/src/luaotfload-init.lua
index 25558be..ad18b32 100644
--- a/src/luaotfload-init.lua
+++ b/src/luaotfload-init.lua
@@ -264,6 +264,7 @@ local context_modules = {
{ ctx, "font-ots" },
{ ctx, "font-osd" },
{ ctx, "font-ocl" },
+ { ctx, "font-otc" },
{ ctx, "font-onr" },
{ ctx, "font-one" },
{ ctx, "font-afk" },