You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
883 lines
31 KiB
883 lines
31 KiB
if not modules then modules = { } end modules ['font-otc'] = {
|
|
version = 1.001,
|
|
comment = "companion to font-ini.mkiv",
|
|
author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
|
|
copyright = "PRAGMA ADE / ConTeXt Development Team",
|
|
license = "see context related readme files"
|
|
}
|
|
|
|
local insert, sortedkeys, sortedhash, tohash = table.insert, table.sortedkeys, table.sortedhash, table.tohash
|
|
local type, next, tonumber = type, next, tonumber
|
|
local lpegmatch = lpeg.match
|
|
local utfbyte, utflen = utf.byte, utf.len
|
|
local sortedhash = table.sortedhash
|
|
|
|
-- 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 fonthelpers = fonts.helpers
|
|
local checkmerge = fonthelpers.checkmerge
|
|
local checkflags = fonthelpers.checkflags
|
|
local checksteps = fonthelpers.checksteps
|
|
|
|
local normalized = {
|
|
substitution = "substitution",
|
|
single = "substitution",
|
|
ligature = "ligature",
|
|
alternate = "alternate",
|
|
multiple = "multiple",
|
|
kern = "kern",
|
|
pair = "pair",
|
|
single = "single",
|
|
chainsubstitution = "chainsubstitution",
|
|
chainposition = "chainposition",
|
|
}
|
|
|
|
local types = {
|
|
substitution = "gsub_single",
|
|
ligature = "gsub_ligature",
|
|
alternate = "gsub_alternate",
|
|
multiple = "gsub_multiple",
|
|
kern = "gpos_pair",
|
|
pair = "gpos_pair",
|
|
single = "gpos_single",
|
|
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 privateslot = fonthelpers.privateslot
|
|
|
|
local specifications = validspecification(specifications,feature)
|
|
if not specifications then
|
|
-- report_otf("invalid specification")
|
|
return
|
|
end
|
|
|
|
local p = lpeg.P("P")
|
|
* (lpeg.patterns.hexdigit^1/function(s) return tonumber(s,16) end)
|
|
* lpeg.P(-1)
|
|
|
|
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
|
|
-- unicodes[code] = u
|
|
return u
|
|
end
|
|
if utflen(code) == 1 then
|
|
u = utfbyte(code)
|
|
if u then
|
|
return u
|
|
end
|
|
end
|
|
if privateslot then
|
|
u = privateslot(code) -- no creation !
|
|
if u then
|
|
-- unicodes[code] = u
|
|
return u
|
|
end
|
|
end
|
|
local u = lpegmatch(p,code)
|
|
if u then
|
|
-- unicodes[code] = u
|
|
return u
|
|
end
|
|
if not aglunicodes then
|
|
aglunicodes = fonts.encodings.agl.unicodes -- delayed
|
|
end
|
|
local u = aglunicodes[code]
|
|
if u then
|
|
-- unicodes[code] = u
|
|
return u
|
|
end
|
|
end
|
|
|
|
local coverup = otf.coverup
|
|
local coveractions = coverup.actions
|
|
local stepkey = coverup.stepkey
|
|
local register = coverup.register
|
|
|
|
local function prepare_substitution(list,featuretype,nocheck)
|
|
local coverage = { }
|
|
local cover = coveractions[featuretype]
|
|
for code, replacement in next, list do
|
|
local unicode = tounicode(code)
|
|
local description = descriptions[unicode]
|
|
if not nocheck and not description then
|
|
skip = skip + 1
|
|
else
|
|
if type(replacement) == "table" then
|
|
replacement = replacement[1]
|
|
end
|
|
replacement = tounicode(replacement)
|
|
if replacement and (nocheck or descriptions[replacement]) then
|
|
cover(coverage,unicode,replacement)
|
|
done = done + 1
|
|
else
|
|
skip = skip + 1
|
|
end
|
|
end
|
|
end
|
|
return coverage
|
|
end
|
|
|
|
local function prepare_alternate(list,featuretype,nocheck)
|
|
local coverage = { }
|
|
local cover = coveractions[featuretype]
|
|
for code, replacement in next, list do
|
|
local unicode = tounicode(code)
|
|
local description = descriptions[unicode]
|
|
if not nocheck and 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] = (nocheck or 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,nocheck)
|
|
local coverage = { }
|
|
local cover = coveractions[featuretype]
|
|
for code, replacement in next, list do
|
|
local unicode = tounicode(code)
|
|
local description = descriptions[unicode]
|
|
if not nocheck and not description then
|
|
skip = skip + 1
|
|
elseif type(replacement) == "table" then
|
|
local r = { }
|
|
local n = 0
|
|
for i=1,#replacement do
|
|
local u = tounicode(replacement[i])
|
|
if nocheck or 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,nocheck)
|
|
local coverage = { }
|
|
local cover = coveractions[featuretype]
|
|
for code, ligature in next, list do
|
|
local unicode = tounicode(code)
|
|
local description = descriptions[unicode]
|
|
if not nocheck and not description then
|
|
skip = skip + 1
|
|
else
|
|
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 nocheck or 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
|
|
end
|
|
end
|
|
return coverage
|
|
end
|
|
|
|
local function resetspacekerns()
|
|
-- a bit of a hack, this nil setting but it forces a
|
|
-- rehash of the resources needed .. the feature itself
|
|
-- should be a kern (at least for now)
|
|
data.properties.hasspacekerns = true
|
|
data.resources .spacekerns = nil
|
|
end
|
|
|
|
local function prepare_kern(list,featuretype)
|
|
local coverage = { }
|
|
local cover = coveractions[featuretype]
|
|
local isspace = false
|
|
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
|
|
if u == 32 then
|
|
isspace = true
|
|
end
|
|
end
|
|
end
|
|
if next(r) then
|
|
cover(coverage,unicode,r)
|
|
done = done + 1
|
|
if unicode == 32 then
|
|
isspace = true
|
|
end
|
|
else
|
|
skip = skip + 1
|
|
end
|
|
else
|
|
skip = skip + 1
|
|
end
|
|
end
|
|
if isspace then
|
|
resetspacekerns()
|
|
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
|
|
if u == 32 then
|
|
isspace = true
|
|
end
|
|
end
|
|
end
|
|
if next(r) then
|
|
cover(coverage,unicode,r)
|
|
done = done + 1
|
|
if unicode == 32 then
|
|
isspace = true
|
|
end
|
|
else
|
|
skip = skip + 1
|
|
end
|
|
else
|
|
skip = skip + 1
|
|
end
|
|
end
|
|
if isspace then
|
|
resetspacekerns()
|
|
end
|
|
else
|
|
report_otf("unknown cover type %a",featuretype)
|
|
end
|
|
return coverage
|
|
end
|
|
|
|
local prepare_single = prepare_pair -- we could have a better test on the spec
|
|
|
|
local function prepare_chain(list,featuretype,sublookups)
|
|
-- todo: coveractions
|
|
local rules = list.rules
|
|
local coverage = { }
|
|
if rules then
|
|
local rulehash = { }
|
|
local rulesize = 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 sortedhash(lookups) do
|
|
local t = type(v)
|
|
if t == "table" then
|
|
-- already ok
|
|
for i=1,#v do
|
|
local vi = v[i]
|
|
if type(vi) ~= "table" then
|
|
v[i] = { vi }
|
|
end
|
|
end
|
|
elseif t == "number" then
|
|
local lookup = sublookups[v]
|
|
if lookup then
|
|
lookups[k] = { lookup }
|
|
if not subtype then
|
|
subtype = lookup.type
|
|
end
|
|
elseif v == 0 then
|
|
lookups[k] = { { type = "gsub_remove" } }
|
|
else
|
|
lookups[k] = false -- { false } -- new
|
|
end
|
|
else
|
|
lookups[k] = false -- { false } -- new
|
|
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
|
|
for unic in sortedhash(sequence[start]) do
|
|
local cu = coverage[unic]
|
|
if not cu then
|
|
coverage[unic] = rulehash -- can now be done cleaner i think
|
|
end
|
|
end
|
|
sequence.n = nofsequences
|
|
end
|
|
end
|
|
rulehash.n = rulesize
|
|
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 sortedhash(f) do -- 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 nocheck = specification.nocheck
|
|
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
|
|
--
|
|
checkflags(specification,resources)
|
|
--
|
|
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,nocheck)
|
|
elseif featuretype == "ligature" then
|
|
coverage = prepare_ligature(list,featuretype,nocheck)
|
|
elseif featuretype == "alternate" then
|
|
coverage = prepare_alternate(list,featuretype,nocheck)
|
|
elseif featuretype == "multiple" then
|
|
coverage = prepare_multiple(list,featuretype,nocheck)
|
|
elseif featuretype == "kern" or featuretype == "move" then
|
|
format = featuretype
|
|
coverage = prepare_kern(list,featuretype)
|
|
elseif featuretype == "pair" then
|
|
format = "pair"
|
|
coverage = prepare_pair(list,featuretype)
|
|
elseif featuretype == "single" then
|
|
format = "single"
|
|
coverage = prepare_single(list,featuretype)
|
|
end
|
|
if coverage and next(coverage) then
|
|
nofsteps = nofsteps + 1
|
|
steps[nofsteps] = register(coverage,featuretype,format,feature,nofsteps,descriptions,resources)
|
|
end
|
|
end
|
|
--
|
|
checkmerge(specification)
|
|
checksteps(specification)
|
|
--
|
|
s[i] = {
|
|
[stepkey] = steps,
|
|
nofsteps = nofsteps,
|
|
flags = featureflags,
|
|
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,nocheck)
|
|
elseif featuretype == "ligature" then
|
|
category = "gsub"
|
|
coverage = prepare_ligature(list,featuretype,nocheck)
|
|
elseif featuretype == "alternate" then
|
|
category = "gsub"
|
|
coverage = prepare_alternate(list,featuretype,nocheck)
|
|
elseif featuretype == "multiple" then
|
|
category = "gsub"
|
|
coverage = prepare_multiple(list,featuretype,nocheck)
|
|
elseif featuretype == "kern" or featuretype == "move" then
|
|
category = "gpos"
|
|
format = featuretype
|
|
coverage = prepare_kern(list,featuretype)
|
|
elseif featuretype == "pair" then
|
|
category = "gpos"
|
|
format = "pair"
|
|
coverage = prepare_pair(list,featuretype)
|
|
elseif featuretype == "single" then
|
|
category = "gpos"
|
|
format = "single"
|
|
coverage = prepare_single(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,
|
|
}
|
|
--
|
|
checkflags(sequence,resources)
|
|
checkmerge(sequence)
|
|
checksteps(sequence)
|
|
-- 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 not slot then
|
|
-- we have a new one
|
|
slot = #extrafeatures + 1
|
|
knownfeatures[name] = slot
|
|
elseif specification.overload == false then
|
|
-- we add an extre one
|
|
slot = #extrafeatures + 1
|
|
knownfeatures[name] = slot
|
|
else
|
|
-- we overload a previous one
|
|
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)
|
|
|
|
-- 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 },
|
|
-- }
|
|
-- }
|
|
|