diff options
author | Karl Berry <karl@freefriends.org> | 2018-09-26 20:50:34 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2018-09-26 20:50:34 +0000 |
commit | 78334b0d4607b242273f9d0cde94f6a6fba51832 (patch) | |
tree | 5b604ee30bb611eeeae95ed4218b4e7fd6eb00b8 /Master/texmf-dist/tex/luatex/luaotfload/fontloader-font-oup.lua | |
parent | 3a0140d05a7e481750ec23a30ed5cf3cdef61eb7 (diff) |
luaotfload (26sep18)
git-svn-id: svn://tug.org/texlive/trunk@48771 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/tex/luatex/luaotfload/fontloader-font-oup.lua')
-rw-r--r-- | Master/texmf-dist/tex/luatex/luaotfload/fontloader-font-oup.lua | 953 |
1 files changed, 756 insertions, 197 deletions
diff --git a/Master/texmf-dist/tex/luatex/luaotfload/fontloader-font-oup.lua b/Master/texmf-dist/tex/luatex/luaotfload/fontloader-font-oup.lua index cfa90c7944a..562d7561d94 100644 --- a/Master/texmf-dist/tex/luatex/luaotfload/fontloader-font-oup.lua +++ b/Master/texmf-dist/tex/luatex/luaotfload/fontloader-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 @@ -673,7 +674,8 @@ local function unifyglyphs(fontdata,usenames) for index=1,#glyphs do local glyph = glyphs[index] local unicode = glyph.unicode -- this is the primary one - if not unicode then + -- if not unicode then -- some fonts use the private space + if not unicode or unicode >= private or (unicode >= 0xE000 and unicode <= 0xF8FF) or unicode == 0xFFFE or unicode == 0xFFFF then -- report("assigning private unicode %U to glyph indexed %05X (%s)",private,index,"unset") unicode = private -- glyph.unicode = -1 @@ -812,9 +814,9 @@ function readers.getcomponents(fontdata) -- handy for resolving ligatures when n end for i=1,#steps do -- we actually had/have this in base mode - local coverage = steps[i].coverage - if coverage then - for k, v in next, coverage do + local c = steps[i].coverage + if c then + for k, v in next, c do traverse(k,k,v) end end @@ -891,7 +893,7 @@ function readers.rehash(fontdata,hashmethod) -- TODO: combine loops in one unifymissing(fontdata) -- stripredundant(fontdata) else - fontdata.hashmethod = "unicode" + fontdata.hashmethod = "unicodes" local indices = unifyglyphs(fontdata) unifyresources(fontdata,indices) copyduplicates(fontdata) @@ -908,10 +910,10 @@ function readers.checkhash(fontdata) elseif hashmethod == "names" and fontdata.names then unifyresources(fontdata,fontdata.names) copyduplicates(fontdata) - fontdata.hashmethod = "unicode" + fontdata.hashmethod = "unicodes" fontdata.names = nil -- no need for it else - readers.rehash(fontdata,"unicode") + readers.rehash(fontdata,"unicodes") end end @@ -1066,13 +1068,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 +1084,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 +1129,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 +1226,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 +1291,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 @@ -1202,6 +1302,7 @@ function readers.pack(data) local sublookups = resources.sublookups local features = resources.features local palettes = resources.colorpalettes + local variable = resources.variabledata local chardata = characters and characters.data local descriptions = data.descriptions or data.glyphs @@ -1210,15 +1311,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 @@ -1258,28 +1357,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 @@ -1331,7 +1432,7 @@ function readers.pack(data) local r = rule.before if r then for i=1,#r do r[i] = pack_boolean(r[i]) end end local r = rule.after if r then for i=1,#r do r[i] = pack_boolean(r[i]) end end local r = rule.current if r then for i=1,#r do r[i] = pack_boolean(r[i]) end end - local r = rule.lookups if r then rule.lookups = pack_mixed (r) end + -- local r = rule.lookups if r then rule.lookups = pack_mixed (r) end local r = rule.replacements if r then rule.replacements = pack_flat (r) end end end @@ -1377,6 +1478,53 @@ function readers.pack(data) end + if variable then + + -- todo: segments + + local instances = variable.instances + if instances then + for i=1,#instances do + local v = instances[i].values + for j=1,#v do + v[j] = pack_normal(v[j]) + end + end + end + + local function packdeltas(main) + if main then + local deltas = main.deltas + if deltas then + for i=1,#deltas do + local di = deltas[i] + local d = di.deltas + -- local r = di.regions + for j=1,#d do + d[j] = pack_indexed(d[j]) + end + di.regions = pack_indexed(di.regions) + end + end + local regions = main.regions + if regions then + for i=1,#regions do + local r = regions[i] + for j=1,#r do + r[j] = pack_normal(r[j]) + end + end + end + end + end + + packdeltas(variable.global) + packdeltas(variable.horizontal) + packdeltas(variable.vertical) + packdeltas(variable.metrics) + + end + if not success(1,pass) then return end @@ -1391,7 +1539,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 @@ -1415,9 +1563,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) @@ -1425,11 +1571,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 @@ -1453,10 +1610,23 @@ function readers.pack(data) if sublookups then packthem(sublookups) end - -- features - if not success(2,pass) then - -- return + if variable then + local function unpackdeltas(main) + if main then + local regions = main.regions + if regions then + main.regions = pack_normal(regions) + end + end + end + unpackdeltas(variable.global) + unpackdeltas(variable.horizontal) + unpackdeltas(variable.vertical) + unpackdeltas(variable.metrics) end + -- if not success(2,pass) then + -- -- return + -- end end for pass=1,2 do @@ -1464,7 +1634,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 @@ -1478,18 +1648,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 @@ -1525,6 +1700,7 @@ function readers.unpack(data) local sublookups = resources.sublookups local features = resources.features local palettes = resources.colorpalettes + local variable = resources.variabledata local unpacked = { } setmetatable(unpacked,unpacked_mt) for unicode, description in next, descriptions do @@ -1564,6 +1740,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] @@ -1573,20 +1758,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 @@ -1603,29 +1794,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 @@ -1633,12 +1836,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] @@ -1660,14 +1857,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 @@ -1704,6 +1900,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 @@ -1718,6 +1926,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 @@ -1732,14 +1952,26 @@ 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 - local tv = tables[lookups] - if tv then - rule.lookups = tv - end - end + -- local lookups = rule.lookups + -- if lookups then + -- local tv = tables[lookups] + -- if tv then + -- rule.lookups = tv + -- end + -- end local replacements = rule.replacements if replacements then local tv = tables[replacements] @@ -1751,19 +1983,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 @@ -1810,6 +2029,70 @@ function readers.unpack(data) end end + if variable then + + -- todo: segments + + local instances = variable.instances + if instances then + for i=1,#instances do + local v = instances[i].values + for j=1,#v do + local tv = tables[v[j]] + if tv then + v[j] = tv + end + end + end + end + + local function unpackdeltas(main) + if main then + local deltas = main.deltas + if deltas then + for i=1,#deltas do + local di = deltas[i] + local d = di.deltas + local r = di.regions + for j=1,#d do + local tv = tables[d[j]] + if tv then + d[j] = tv + end + end + local tv = di.regions + if tv then + di.regions = tv + end + end + end + local regions = main.regions + if regions then + local tv = tables[regions] + if tv then + main.regions = tv + regions = tv + end + for i=1,#regions do + local r = regions[i] + for j=1,#r do + local tv = tables[r[j]] + if tv then + r[j] = tv + end + end + end + end + end + end + + unpackdeltas(variable.global) + unpackdeltas(variable.horizontal) + unpackdeltas(variable.vertical) + unpackdeltas(variable.metrics) + + end + data.tables = nil end end @@ -1851,9 +2134,12 @@ local function mergesteps_1(lookup,strict) report("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name) local target = first.coverage for i=2,nofsteps do - for k, v in next, steps[i].coverage do - if not target[k] then - target[k] = v + local c = steps[i].coverage + if c then + for k, v in next, c do + if not target[k] then + target[k] = v + end end end end @@ -1863,8 +2149,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] @@ -1880,71 +2168,68 @@ local function mergesteps_2(lookup,strict) -- pairs report("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name) local target = first.coverage for i=2,nofsteps do - 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 + local c = steps[i].coverage + if c then + for k, v in next, c do + local tk = target[k] + if tk then + for kk, vv in next, v do + if tk[kk] == nil then + tk[kk] = vv + end end + else + target[k] = v end - else - target[k] = v end 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 c = steps[i].coverage + if c then + for k, v in next, c do + local tk = coverage[k] -- { class, { x, y } } + if tk then + report("quitting merge due to multiple checks") + return nofsteps + else + coverage[k] = v + end + end + end + end + -- 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 - local tk = coverage[k] - 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 - 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) + 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 @@ -1973,12 +2258,15 @@ local function mergesteps_4(lookup) -- ligatures report("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name) local target = first.coverage for i=2,nofsteps do - for k, v in next, steps[i].coverage do - local tk = target[k] - if tk then - nested(v,tk) - else - target[k] = v + local c = steps[i].coverage + if c then + for k, v in next, c do + local tk = target[k] + if tk then + nested(v,tk) + else + target[k] = v + end end end end @@ -1987,62 +2275,140 @@ 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 + local c = steps[i].coverage + if c then + for k, v in next, c 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 + 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 @@ -2050,6 +2416,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 @@ -2066,23 +2456,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 @@ -2100,6 +2532,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 @@ -2141,6 +2646,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 @@ -2148,6 +2658,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 @@ -2158,7 +2670,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 @@ -2174,13 +2687,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 @@ -2208,23 +2722,27 @@ function readers.expand(data) local lookups = rule.lookups or false local subtype = nil if lookups then - -- is now indexed - for k, v in next, lookups do - local lookup = sublookups[v] - if lookup then - lookups[k] = lookup - if not subtype then - subtype = lookup.type + for i=1,#lookups do + local lookups = lookups[i] + if lookups then + for k, v in next, lookups do -- actually this one is indexed + local lookup = sublookups[v] + if lookup then + lookups[k] = lookup + if not subtype then + subtype = lookup.type + end + else + -- already expanded + end end - else - -- already expanded end 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 @@ -2233,20 +2751,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 |