summaryrefslogtreecommitdiff
path: root/tex/context/base/mkiv/font-oup.lua
diff options
context:
space:
mode:
Diffstat (limited to 'tex/context/base/mkiv/font-oup.lua')
-rw-r--r--tex/context/base/mkiv/font-oup.lua727
1 files changed, 570 insertions, 157 deletions
diff --git a/tex/context/base/mkiv/font-oup.lua b/tex/context/base/mkiv/font-oup.lua
index 75ae08526..79ac76abe 100644
--- a/tex/context/base/mkiv/font-oup.lua
+++ b/tex/context/base/mkiv/font-oup.lua
@@ -15,6 +15,7 @@ local formatters = string.formatters
local sortedkeys = table.sortedkeys
local sortedhash = table.sortedhash
local tohash = table.tohash
+local setmetatableindex = table.setmetatableindex
local report = logs.reporter("otf reader")
@@ -29,8 +30,8 @@ local f_index = formatters["I%05X"]
local f_character_y = formatters["%C"]
local f_character_n = formatters["[ %C ]"]
-local check_duplicates = true -- can become an option (pseudo feature) / aways needed anyway
-local check_soft_hyphen = false -- can become an option (pseudo feature) / needed for tagging
+local check_duplicates = true -- can become an option (pseudo feature) / aways needed anyway
+local check_soft_hyphen = true -- can become an option (pseudo feature) / needed for tagging
directives.register("otf.checksofthyphen",function(v)
check_soft_hyphen = v
@@ -370,6 +371,7 @@ local function copyduplicates(fontdata)
local duplicates = resources.duplicates
if check_soft_hyphen then
-- ebgaramond has a zero width empty soft hyphen
+ -- antykwatorunsks lacks a soft hyphen
local ds = descriptions[0xAD]
if not ds or ds.width == 0 then
if ds then
@@ -621,7 +623,7 @@ local function checklookups(fontdata,missing,nofmissing)
end
end
if next(done) then
- report("not unicoded: % t",table.sortedkeys(done))
+ report("not unicoded: % t",sortedkeys(done))
end
end
end
@@ -632,7 +634,6 @@ local function unifymissing(fontdata)
require("font-agl")
end
local unicodes = { }
- local private = fontdata.private
local resources = fontdata.resources
resources.unicodes = unicodes
for unicode, d in next, fontdata.descriptions do
@@ -1066,13 +1067,14 @@ function readers.pack(data)
end
end
- local function pack_flat(v)
- local tag = tabstr_flat(v)
+ local function pack_normal_cc(v)
+ local tag = tabstr_normal(v)
local ht = h[tag]
if ht then
c[ht] = c[ht] + 1
return ht
else
+ v[1] = 0
nt = nt + 1
t[nt] = v
h[tag] = nt
@@ -1081,8 +1083,8 @@ function readers.pack(data)
end
end
- local function pack_boolean(v)
- local tag = tabstr_boolean(v)
+ local function pack_flat(v)
+ local tag = tabstr_flat(v)
local ht = h[tag]
if ht then
c[ht] = c[ht] + 1
@@ -1126,6 +1128,84 @@ function readers.pack(data)
end
end
+ -- saves a lot on noto sans
+
+ -- can be made more clever
+
+ local function pack_boolean(v)
+ local tag = tabstr_boolean(v)
+ local ht = h[tag]
+ if ht then
+ c[ht] = c[ht] + 1
+ return ht
+ else
+ nt = nt + 1
+ t[nt] = v
+ h[tag] = nt
+ c[nt] = 1
+ return nt
+ end
+ end
+
+ -- -- This was an experiment to see if we can bypass the luajit limits but loading is
+ -- -- still an issue due to other limits so we don't use this ... actually it can
+ -- -- prevent a luajittex crash but i don't care too much about that as we can't use
+ -- -- that engine anyway then.
+ --
+ -- local function check(t)
+ -- if type(t) == "table" then
+ -- local s = sortedkeys(t)
+ -- local n = #s
+ -- if n <= 10 then
+ -- return
+ -- end
+ -- local ranges = { }
+ -- local first, last
+ -- for i=1,#s do
+ -- local ti = s[i]
+ -- if not first then
+ -- first = ti
+ -- last = ti
+ -- elseif ti == last + 1 then
+ -- last = ti
+ -- elseif last - first < 10 then
+ -- -- we could permits a few exceptions
+ -- return
+ -- else
+ -- ranges[#ranges+1] = { first, last }
+ -- first, last = nil, nil
+ -- end
+ -- end
+ -- if #ranges > 0 then
+ -- return {
+ -- ranges = ranges
+ -- }
+ -- end
+ -- end
+ -- end
+ --
+ -- local function pack_boolean(v)
+ -- local tag
+ -- local r = check(v)
+ -- if r then
+ -- v = r
+ -- tag = tabstr_normal(v)
+ -- else
+ -- tag = tabstr_boolean(v)
+ -- end
+ -- local ht = h[tag]
+ -- if ht then
+ -- c[ht] = c[ht] + 1
+ -- return ht
+ -- else
+ -- nt = nt + 1
+ -- t[nt] = v
+ -- h[tag] = nt
+ -- c[nt] = 1
+ -- return nt
+ -- end
+ -- end
+
local function pack_final(v)
-- v == number
if c[v] <= criterium then
@@ -1145,6 +1225,25 @@ function readers.pack(data)
end
end
+ local function pack_final_cc(v)
+ -- v == number
+ if c[v] <= criterium then
+ return t[v]
+ else
+ -- compact hash
+ local hv = hh[v]
+ if hv then
+ return hv
+ else
+ ntt = ntt + 1
+ tt[ntt] = t[v]
+ hh[v] = ntt
+ cc[ntt] = c[v]
+ return ntt
+ end
+ end
+ end
+
local function success(stage,pass)
if nt == 0 then
if trace_loading or trace_packing then
@@ -1191,9 +1290,9 @@ function readers.pack(data)
local function packers(pass)
if pass == 1 then
- return pack_normal, pack_indexed, pack_flat, pack_boolean, pack_mixed
+ return pack_normal, pack_indexed, pack_flat, pack_boolean, pack_mixed, pack_normal_cc
else
- return pack_final, pack_final, pack_final, pack_final, pack_final
+ return pack_final, pack_final, pack_final, pack_final, pack_final, pack_final_cc
end
end
@@ -1211,15 +1310,13 @@ function readers.pack(data)
return
end
- --
-
for pass=1,2 do
if trace_packing then
report_otf("start packing: stage 1, pass %s",pass)
end
- local pack_normal, pack_indexed, pack_flat, pack_boolean, pack_mixed = packers(pass)
+ local pack_normal, pack_indexed, pack_flat, pack_boolean, pack_mixed, pack_normal_cc = packers(pass)
for unicode, description in next, descriptions do
local boundingbox = description.boundingbox
@@ -1259,28 +1356,30 @@ function readers.pack(data)
if kind == "gpos_pair" then
local c = step.coverage
if c then
- if step.format == "kern" then
+ if step.format == "pair" then
for g1, d1 in next, c do
- c[g1] = pack_normal(d1)
+ for g2, d2 in next, d1 do
+ local f = d2[1] if f and f ~= true then d2[1] = pack_indexed(f) end
+ local s = d2[2] if s and s ~= true then d2[2] = pack_indexed(s) end
+ end
end
else
for g1, d1 in next, c do
- for g2, d2 in next, d1 do
- local f = d2[1] if f then d2[1] = pack_indexed(f) end
- local s = d2[2] if s then d2[2] = pack_indexed(s) end
- end
+ c[g1] = pack_normal(d1)
end
end
end
elseif kind == "gpos_single" then
local c = step.coverage
if c then
- if step.format == "kern" then
- step.coverage = pack_normal(c)
- else
+ if step.format == "single" then
for g1, d1 in next, c do
- c[g1] = pack_indexed(d1)
+ if d1 and d1 ~= true then
+ c[g1] = pack_indexed(d1)
+ end
end
+ else
+ step.coverage = pack_normal(c)
end
end
elseif kind == "gpos_cursive" then
@@ -1399,7 +1498,7 @@ function readers.pack(data)
for i=1,#deltas do
local di = deltas[i]
local d = di.deltas
- local r = di.regions
+ -- local r = di.regions
for j=1,#d do
d[j] = pack_indexed(d[j])
end
@@ -1439,7 +1538,7 @@ function readers.pack(data)
report_otf("start packing: stage 2, pass %s",pass)
end
- local pack_normal, pack_indexed, pack_flat, pack_boolean, pack_mixed = packers(pass)
+ local pack_normal, pack_indexed, pack_flat, pack_boolean, pack_mixed, pack_normal_cc = packers(pass)
for unicode, description in next, descriptions do
local math = description.math
@@ -1463,9 +1562,7 @@ function readers.pack(data)
if kind == "gpos_pair" then
local c = step.coverage
if c then
- if step.format == "kern" then
- -- todo !
- else
+ if step.format == "pair" then
for g1, d1 in next, c do
for g2, d2 in next, d1 do
d1[g2] = pack_normal(d2)
@@ -1473,11 +1570,22 @@ function readers.pack(data)
end
end
end
--- elseif kind == "gpos_mark2base" or kind == "gpos_mark2mark" or kind == "gpos_mark2ligature" then
--- local c = step.baseclasses
--- for k, v in next, c do
--- c[k] = pack_normal(v)
--- end
+ -- elseif kind == "gpos_cursive" then
+ -- local c = step.coverage -- new
+ -- if c then
+ -- for g1, d1 in next, c do
+ -- c[g1] = pack_normal_cc(d1)
+ -- end
+ -- end
+ elseif kind == "gpos_mark2ligature" then
+ local c = step.baseclasses -- new
+ if c then
+ for g1, d1 in next, c do
+ for g2, d2 in next, d1 do
+ d1[g2] = pack_normal(d2)
+ end
+ end
+ end
end
local rules = step.rules
if rules then
@@ -1525,7 +1633,7 @@ function readers.pack(data)
report_otf("start packing: stage 3, pass %s",pass)
end
- local pack_normal, pack_indexed, pack_flat, pack_boolean, pack_mixed = packers(pass)
+ local pack_normal, pack_indexed, pack_flat, pack_boolean, pack_mixed, pack_normal_cc = packers(pass)
local function packthem(sequences)
for i=1,#sequences do
@@ -1539,18 +1647,23 @@ function readers.pack(data)
if kind == "gpos_pair" then
local c = step.coverage
if c then
- if step.format == "kern" then
- -- todo !
- else
+ if step.format == "pair" then
for g1, d1 in next, c do
c[g1] = pack_normal(d1)
end
end
end
+ elseif kind == "gpos_cursive" then
+ local c = step.coverage
+ if c then
+ for g1, d1 in next, c do
+ c[g1] = pack_normal_cc(d1)
+ end
+ end
end
end
end
- end
+ end
end
if sequences then
@@ -1626,6 +1739,15 @@ function readers.unpack(data)
-- end
end
+ -- local function expandranges(t,ranges)
+ -- for i=1,#ranges do
+ -- local r = ranges[i]
+ -- for k=r[1],r[2] do
+ -- t[k] = true
+ -- end
+ -- end
+ -- end
+
local function unpackthem(sequences)
for i=1,#sequences do
local sequence = sequences[i]
@@ -1635,20 +1757,26 @@ function readers.unpack(data)
local features = sequence.features
local flags = sequence.flags
local markclass = sequence.markclass
+ if features then
+ local tv = tables[features]
+ if tv then
+ sequence.features = tv
+ features = tv
+ end
+ for script, feature in next, features do
+ local tv = tables[feature]
+ if tv then
+ features[script] = tv
+ end
+ end
+ end
if steps then
for i=1,#steps do
local step = steps[i]
if kind == "gpos_pair" then
local c = step.coverage
if c then
- if step.format == "kern" then
- for g1, d1 in next, c do
- local tv = tables[d1]
- if tv then
- c[g1] = tv
- end
- end
- else
+ if step.format == "pair" then
for g1, d1 in next, c do
local tv = tables[d1]
if tv then
@@ -1665,29 +1793,41 @@ function readers.unpack(data)
local s = tables[d2[2]] if s then d2[2] = s end
end
end
+ else
+ for g1, d1 in next, c do
+ local tv = tables[d1]
+ if tv then
+ c[g1] = tv
+ end
+ end
end
end
elseif kind == "gpos_single" then
local c = step.coverage
if c then
- if step.format == "kern" then
- local tv = tables[c]
- if tv then
- step.coverage = tv
- end
- else
+ if step.format == "single" then
for g1, d1 in next, c do
local tv = tables[d1]
if tv then
c[g1] = tv
end
end
+ else
+ local tv = tables[c]
+ if tv then
+ step.coverage = tv
+ end
end
end
elseif kind == "gpos_cursive" then
local c = step.coverage
if c then
for g1, d1 in next, c do
+ local tv = tables[d1]
+ if tv then
+ d1 = tv
+ c[g1] = d1
+ end
local f = tables[d1[2]] if f then d1[2] = f end
local s = tables[d1[3]] if s then d1[3] = s end
end
@@ -1695,12 +1835,6 @@ function readers.unpack(data)
elseif kind == "gpos_mark2base" or kind == "gpos_mark2mark" then
local c = step.baseclasses
if c then
--- for k, v in next, c do
--- local tv = tables[v]
--- if tv then
--- c[k] = tv
--- end
--- end
for g1, d1 in next, c do
for g2, d2 in next, d1 do
local tv = tables[d2]
@@ -1722,14 +1856,13 @@ function readers.unpack(data)
elseif kind == "gpos_mark2ligature" then
local c = step.baseclasses
if c then
--- for k, v in next, c do
--- local tv = tables[v]
--- if tv then
--- c[k] = tv
--- end
--- end
for g1, d1 in next, c do
for g2, d2 in next, d1 do
+ local tv = tables[d2] -- new
+ if tv then
+ d2 = tv
+ d1[g2] = d2
+ end
for g3, d3 in next, d2 do
local tv = tables[d2[g3]]
if tv then
@@ -1766,6 +1899,18 @@ function readers.unpack(data)
before[i] = tv
end
end
+ -- for i=1,#before do
+ -- local bi = before[i]
+ -- local tv = tables[bi]
+ -- if tv then
+ -- bi = tv
+ -- before[i] = bi
+ -- end
+ -- local ranges = bi.ranges
+ -- if ranges then
+ -- expandranges(bi,ranges)
+ -- end
+ -- end
end
local after = rule.after
if after then
@@ -1780,6 +1925,18 @@ function readers.unpack(data)
after[i] = tv
end
end
+ -- for i=1,#after do
+ -- local ai = after[i]
+ -- local tv = tables[ai]
+ -- if tv then
+ -- ai = tv
+ -- after[i] = ai
+ -- end
+ -- local ranges = ai.ranges
+ -- if ranges then
+ -- expandranges(ai,ranges)
+ -- end
+ -- end
end
local current = rule.current
if current then
@@ -1794,6 +1951,18 @@ function readers.unpack(data)
current[i] = tv
end
end
+ -- for i=1,#current do
+ -- local ci = current[i]
+ -- local tv = tables[ci]
+ -- if tv then
+ -- ci = tv
+ -- current[i] = ci
+ -- end
+ -- local ranges = ci.ranges
+ -- if ranges then
+ -- expandranges(ci,ranges)
+ -- end
+ -- end
end
-- local lookups = rule.lookups
-- if lookups then
@@ -1813,19 +1982,6 @@ function readers.unpack(data)
end
end
end
- if features then
- local tv = tables[features]
- if tv then
- sequence.features = tv
- features = tv
- end
- for script, feature in next, features do
- local tv = tables[feature]
- if tv then
- features[script] = tv
- end
- end
- end
if order then
local tv = tables[order]
if tv then
@@ -1989,8 +2145,10 @@ local function mergesteps_1(lookup,strict)
return nofsteps - 1
end
-
-local function mergesteps_2(lookup,strict) -- pairs
+local function mergesteps_2(lookup) -- pairs
+ -- this can be tricky as we can have a match on a mark with no marks skip flag
+ -- in which case with multiple steps a hit can prevent a next step while in the
+ -- merged case we can hit differently (a messy font then anyway)
local steps = lookup.steps
local nofsteps = lookup.nofsteps
local first = steps[1]
@@ -2009,9 +2167,9 @@ local function mergesteps_2(lookup,strict) -- pairs
for k, v in next, steps[i].coverage do
local tk = target[k]
if tk then
- for k, v in next, v do
- if not tk[k] then
- tk[k] = v
+ for kk, vv in next, v do
+ if tk[kk] == nil then
+ tk[kk] = vv
end
end
else
@@ -2020,57 +2178,48 @@ local function mergesteps_2(lookup,strict) -- pairs
end
end
lookup.nofsteps = 1
- lookup.steps = { first }
+ lookup.merged = true
+ lookup.steps = { first }
return nofsteps - 1
end
+-- we could have a coverage[first][second] = { } already here (because eventually
+-- we also have something like that after loading)
local function mergesteps_3(lookup,strict) -- marks
local steps = lookup.steps
local nofsteps = lookup.nofsteps
- local first = steps[1]
report("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
- local baseclasses = { }
- local coverage = { }
- local used = { }
+ -- check first
+ local coverage = { }
for i=1,nofsteps do
- local offset = i*10
- local step = steps[i]
- for k, v in sortedhash(step.baseclasses) do
- baseclasses[offset+k] = v
- end
- for k, v in next, step.coverage do
- local tk = coverage[k]
+ for k, v in next, steps[i].coverage do
+ local tk = coverage[k] -- { class, { x, y } }
if tk then
- for k, v in next, v do
- if not tk[k] then
- tk[k] = v
- local c = offset + v[1]
- v[1] = c
- if not used[c] then
- used[c] = true
- end
- end
- end
+ report("quitting merge due to multiple checks")
+ return nofsteps
else
coverage[k] = v
- local c = offset + v[1]
- v[1] = c
- if not used[c] then
- used[c] = true
- end
end
end
end
- for k, v in next, baseclasses do
- if not used[k] then
- baseclasses[k] = nil
- report("discarding not used baseclass %i",k)
+ -- merge indeed
+ local first = steps[1]
+ local baseclasses = { } -- let's assume sparse step.baseclasses
+ for i=1,nofsteps do
+ local offset = i*10 -- we assume max 10 classes per step
+ local step = steps[i]
+ for k, v in sortedhash(step.baseclasses) do
+ baseclasses[offset+k] = v
+ end
+ for k, v in next, step.coverage do
+ v[1] = offset + v[1]
end
end
first.baseclasses = baseclasses
first.coverage = coverage
lookup.nofsteps = 1
+ lookup.merged = true
lookup.steps = { first }
return nofsteps - 1
end
@@ -2113,62 +2262,137 @@ local function mergesteps_4(lookup) -- ligatures
return nofsteps - 1
end
+-- so we assume only one cursive entry and exit and even then the first one seems
+-- to win anyway: no exit or entry quite the lookup match and then we take the
+-- next step; this means that we can as well merge them
+
+local function mergesteps_5(lookup) -- cursive
+ local steps = lookup.steps
+ local nofsteps = lookup.nofsteps
+ local first = steps[1]
+ report("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
+ local target = first.coverage
+ local hash = nil
+ for k, v in next, target do
+ hash = v[1]
+ break
+ end
+ for i=2,nofsteps do
+ for k, v in next, steps[i].coverage do
+ local tk = target[k]
+ if tk then
+ if not tk[2] then
+ tk[2] = v[2]
+ end
+ if not tk[3] then
+ tk[3] = v[3]
+ end
+ else
+ target[k] = v
+ v[1] = hash
+ end
+ end
+ end
+ lookup.nofsteps = 1
+ lookup.merged = true
+ lookup.steps = { first }
+ return nofsteps - 1
+end
+
local function checkkerns(lookup)
local steps = lookup.steps
local nofsteps = lookup.nofsteps
+ local kerned = 0
for i=1,nofsteps do
local step = steps[i]
if step.format == "pair" then
local coverage = step.coverage
local kerns = true
for g1, d1 in next, coverage do
- if d1[1] ~= 0 or d1[2] ~= 0 or d1[4] ~= 0 then
+ if d1 == true then
+ -- all zero
+ elseif not d1 then
+ -- null
+ elseif d1[1] ~= 0 or d1[2] ~= 0 or d1[4] ~= 0 then
kerns = false
break
end
end
if kerns then
report("turning pairs of step %a of %a lookup %a into kerns",i,lookup.type,lookup.name)
+ local c = { }
for g1, d1 in next, coverage do
- coverage[g1] = d1[3]
+ if d1 and d1 ~= true then
+ c[g1] = d1[3]
+ end
end
- step.format = "kern"
+ step.coverage = c
+ step.format = "move"
+ kerned = kerned + 1
end
end
end
+ return kerned
end
+-- There are several options to optimize but we have this somewhat fuzzy aspect of
+-- advancing (depending on the second of a pair) so we need to retain that information.
+--
+-- We can have:
+--
+-- true, nil|false
+--
+-- which effectively means: nothing to be done and advance to next (so not next of
+-- next) and because coverage should be not overlapping we can wipe these. However,
+-- checking for (true,nil) (false,nil) and omitting them doesn't gain much.
+
+-- Because we pack we cannot mix tables and numbers so we can only turn a whole set in
+-- format kern instead of pair.
+
local function checkpairs(lookup)
local steps = lookup.steps
local nofsteps = lookup.nofsteps
local kerned = 0
- for i=1,nofsteps do
- local step = steps[i]
- if step.format == "pair" then
- local coverage = step.coverage
- local kerns = true
- for g1, d1 in next, coverage do
- for g2, d2 in next, d1 do
- if d2[2] then
- kerns = false
- break
- else
- local v = d2[1]
- if v[1] ~= 0 or v[2] ~= 0 or v[4] ~= 0 then
- kerns = false
- break
- end
+
+ local function onlykerns(step)
+ local coverage = step.coverage
+ for g1, d1 in next, coverage do
+ for g2, d2 in next, d1 do
+ if d2[2] then
+ --- true or { a, b, c, d }
+ return false
+ else
+ local v = d2[1]
+ if v == true then
+ -- all zero
+ elseif v and (v[1] ~= 0 or v[2] ~= 0 or v[4] ~= 0) then
+ return false
end
end
end
- if kerns then
+ end
+ return coverage
+ end
+
+ for i=1,nofsteps do
+ local step = steps[i]
+ if step.format == "pair" then
+ local coverage = onlykerns(step)
+ if coverage then
report("turning pairs of step %a of %a lookup %a into kerns",i,lookup.type,lookup.name)
for g1, d1 in next, coverage do
+ local d = { }
for g2, d2 in next, d1 do
- d1[g2] = d2[1][3]
+ local v = d2[1]
+ if v == true then
+ -- ignore -- d1[g2] = nil
+ elseif v then
+ d[g2] = v[3] -- d1[g2] = v[3]
+ end
end
+ coverage[g1] = d
end
- step.format = "kern"
+ step.format = "move"
kerned = kerned + 1
end
end
@@ -2176,6 +2400,30 @@ local function checkpairs(lookup)
return kerned
end
+local compact_pairs = true
+local compact_singles = true
+
+local merge_pairs = true
+local merge_singles = true
+local merge_substitutions = true
+local merge_alternates = true
+local merge_multiples = true
+local merge_ligatures = true
+local merge_cursives = true
+local merge_marks = true
+
+directives.register("otf.compact.pairs", function(v) compact_pairs = v end)
+directives.register("otf.compact.singles", function(v) compact_singles = v end)
+
+directives.register("otf.merge.pairs", function(v) merge_pairs = v end)
+directives.register("otf.merge.singles", function(v) merge_singles = v end)
+directives.register("otf.merge.substitutions", function(v) merge_substitutions = v end)
+directives.register("otf.merge.alternates", function(v) merge_alternates = v end)
+directives.register("otf.merge.multiples", function(v) merge_multiples = v end)
+directives.register("otf.merge.ligatures", function(v) merge_ligatures = v end)
+directives.register("otf.merge.cursives", function(v) merge_cursives = v end)
+directives.register("otf.merge.marks", function(v) merge_marks = v end)
+
function readers.compact(data)
if not data or data.compacted then
return
@@ -2192,23 +2440,65 @@ function readers.compact(data)
for i=1,#lookups do
local lookup = lookups[i]
local nofsteps = lookup.nofsteps
+ local kind = lookup.type
allsteps = allsteps + nofsteps
if nofsteps > 1 then
- local kind = lookup.type
- if kind == "gsub_single" or kind == "gsub_alternate" or kind == "gsub_multiple" then
- merged = merged + mergesteps_1(lookup)
+ local merg = merged
+ if kind == "gsub_single" then
+ if merge_substitutions then
+ merged = merged + mergesteps_1(lookup)
+ end
+ elseif kind == "gsub_alternate" then
+ if merge_alternates then
+ merged = merged + mergesteps_1(lookup)
+ end
+ elseif kind == "gsub_multiple" then
+ if merge_multiples then
+ merged = merged + mergesteps_1(lookup)
+ end
elseif kind == "gsub_ligature" then
- merged = merged + mergesteps_4(lookup)
+ if merge_ligatures then
+ merged = merged + mergesteps_4(lookup)
+ end
elseif kind == "gpos_single" then
- merged = merged + mergesteps_1(lookup,true)
- checkkerns(lookup)
+ if merge_singles then
+ merged = merged + mergesteps_1(lookup,true)
+ end
+ if compact_singles then
+ kerned = kerned + checkkerns(lookup)
+ end
elseif kind == "gpos_pair" then
- merged = merged + mergesteps_2(lookup,true)
- kerned = kerned + checkpairs(lookup)
+ if merge_pairs then
+ merged = merged + mergesteps_2(lookup)
+ end
+ if compact_pairs then
+ kerned = kerned + checkpairs(lookup)
+ end
elseif kind == "gpos_cursive" then
- merged = merged + mergesteps_2(lookup)
+ if merge_cursives then
+ merged = merged + mergesteps_5(lookup)
+ end
elseif kind == "gpos_mark2mark" or kind == "gpos_mark2base" or kind == "gpos_mark2ligature" then
- merged = merged + mergesteps_3(lookup)
+ if merge_marks then
+ merged = merged + mergesteps_3(lookup)
+ end
+ end
+ if merg ~= merged then
+ lookup.merged = true
+ end
+ elseif nofsteps == 1 then
+ local kern = kerned
+ if kind == "gpos_single" then
+ if compact_singles then
+ kerned = kerned + checkkerns(lookup)
+ end
+ elseif kind == "gpos_pair" then
+ if compact_pairs then
+ kerned = kerned + checkpairs(lookup)
+ end
+ end
+ if kern ~= kerned then
+ -- lookup.kerned = true
end
end
end
@@ -2226,6 +2516,79 @@ function readers.compact(data)
end
end
+local function mergesteps(t,k)
+ if k == "merged" then
+ local merged = { }
+ for i=1,#t do
+ local step = t[i]
+ local coverage = step.coverage
+ for k in next, coverage do
+ local m = merged[k]
+ if m then
+ m[2] = i
+ -- m[#m+1] = step
+ else
+ merged[k] = { i, i }
+ -- merged[k] = { step }
+ end
+ end
+ end
+ t.merged = merged
+ return merged
+ end
+end
+
+local function checkmerge(sequence)
+ local steps = sequence.steps
+ if steps then
+ setmetatableindex(steps,mergesteps)
+ end
+end
+
+local function checkflags(sequence,resources)
+ if not sequence.skiphash then
+ local flags = sequence.flags
+ if flags then
+ local skipmark = flags[1]
+ local skipligature = flags[2]
+ local skipbase = flags[3]
+ local markclass = sequence.markclass
+ local skipsome = skipmark or skipligature or skipbase or markclass or false
+ if skipsome then
+ sequence.skiphash = setmetatableindex(function(t,k)
+ local c = resources.classes[k] -- delayed table
+ local v = c == skipmark
+ or (markclass and c == "mark" and not markclass[k])
+ or c == skipligature
+ or c == skipbase
+ or false
+ t[k] = v
+ return v
+ end)
+ else
+ sequence.skiphash = false
+ end
+ else
+ sequence.skiphash = false
+ end
+ end
+end
+
+local function checksteps(sequence)
+ local steps = sequence.steps
+ if steps then
+ for i=1,#steps do
+ steps[i].index = i
+ end
+ end
+end
+
+if fonts.helpers then
+ fonts.helpers.checkmerge = checkmerge
+ fonts.helpers.checkflags = checkflags
+ fonts.helpers.checksteps = checksteps -- has to happen last
+end
+
function readers.expand(data)
if not data or data.expanded then
return
@@ -2267,6 +2630,11 @@ function readers.expand(data)
end
end
end
+
+ -- using a merged combined hash as first test saves some 30% on ebgaramond and
+ -- about 15% on arabtype .. then moving the a test also saves a bit (even when
+ -- often a is not set at all so that one is a bit debatable
+
local function expandlookups(sequences)
if sequences then
-- we also need to do sublookups
@@ -2274,6 +2642,8 @@ function readers.expand(data)
local sequence = sequences[i]
local steps = sequence.steps
if steps then
+ local nofsteps = sequence.nofsteps
+
local kind = sequence.type
local markclass = sequence.markclass
if markclass then
@@ -2284,7 +2654,8 @@ function readers.expand(data)
sequence.markclass = markclasses[markclass]
end
end
- for i=1,sequence.nofsteps do
+
+ for i=1,nofsteps do
local step = steps[i]
local baseclasses = step.baseclasses
if baseclasses then
@@ -2300,13 +2671,14 @@ function readers.expand(data)
end
local rules = step.rules
if rules then
- local rulehash = { }
+ local rulehash = { n = 0 } -- is contexts in font-ots
local rulesize = 0
local coverage = { }
local lookuptype = sequence.type
+ local nofrules = #rules
step.coverage = coverage -- combined hits
- for nofrules=1,#rules do
- local rule = rules[nofrules]
+ for currentrule=1,nofrules do
+ local rule = rules[currentrule]
local current = rule.current
local before = rule.before
local after = rule.after
@@ -2337,7 +2709,7 @@ function readers.expand(data)
for i=1,#lookups do
local lookups = lookups[i]
if lookups then
- for k, v in next, lookups do
+ for k, v in next, lookups do -- actually this one is indexed
local lookup = sublookups[v]
if lookup then
lookups[k] = lookup
@@ -2352,9 +2724,9 @@ function readers.expand(data)
end
end
if sequence[1] then -- we merge coverage into one
- rulesize = rulesize + 1
- rulehash[rulesize] = {
- nofrules, -- 1
+ sequence.n = #sequence -- tiny speedup
+ local ruledata = {
+ currentrule, -- 1 -- original rule number, only use this for tracing!
lookuptype, -- 2
sequence, -- 3
start, -- 4
@@ -2363,20 +2735,61 @@ function readers.expand(data)
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
+ --
+ -- possible optimization: per [unic] a rulehash, but beware:
+ -- contexts have unique coverage and chains can have multiple
+ -- hits (rules) per coverage entry
+ --
+ -- so: we can combine multiple steps as well as multiple rules
+ -- but that takes careful checking, in which case we can go the
+ -- step list approach and turn contexts into steps .. in fact,
+ -- if we turn multiple contexts into steps we're already ok as
+ -- steps gets a coverage hash by metatable
+ --
+ rulesize = rulesize + 1
+ rulehash[rulesize] = ruledata
+ rulehash.n = rulesize -- tiny speedup
+ --
+ if true then -- nofrules > 1
+
+ for unic in next, sequence[start] do
+ local cu = coverage[unic]
+ if cu then
+ local n = #cu+1
+ cu[n] = ruledata
+ cu.n = n
+ else
+ coverage[unic] = { ruledata, n = 1 }
+ end
end
+
+ else
+
+ for unic in next, sequence[start] do
+ local cu = coverage[unic]
+ if cu then
+ -- we can have a contextchains with many matches which we
+ -- can actually optimize
+ else
+ coverage[unic] = rulehash
+ end
+ end
+
end
end
end
end
end
+
+ checkmerge(sequence)
+ checkflags(sequence,resources)
+ checksteps(sequence)
+
end
end
end
end
+
expandlookups(sequences)
expandlookups(sublookups)
end