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