summaryrefslogtreecommitdiff
path: root/macros/luatex/generic/luaotfload/fontloader-font-oup.lua
diff options
context:
space:
mode:
Diffstat (limited to 'macros/luatex/generic/luaotfload/fontloader-font-oup.lua')
-rw-r--r--macros/luatex/generic/luaotfload/fontloader-font-oup.lua3067
1 files changed, 3067 insertions, 0 deletions
diff --git a/macros/luatex/generic/luaotfload/fontloader-font-oup.lua b/macros/luatex/generic/luaotfload/fontloader-font-oup.lua
new file mode 100644
index 0000000000..51b1c248f8
--- /dev/null
+++ b/macros/luatex/generic/luaotfload/fontloader-font-oup.lua
@@ -0,0 +1,3067 @@
+if not modules then modules = { } end modules ['font-oup'] = {
+ 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 next, type = next, type
+local P, R, S = lpeg.P, lpeg.R, lpeg.S
+local lpegmatch = lpeg.match
+local insert, remove, copy, unpack = table.insert, table.remove, table.copy, table.unpack
+
+local formatters = string.formatters
+local sortedkeys = table.sortedkeys
+local sortedhash = table.sortedhash
+local tohash = table.tohash
+local setmetatableindex = table.setmetatableindex
+
+local report_error = logs.reporter("otf reader","error")
+local report_markwidth = logs.reporter("otf reader","markwidth")
+local report_cleanup = logs.reporter("otf reader","cleanup")
+local report_optimizations = logs.reporter("otf reader","merges")
+local report_unicodes = logs.reporter("otf reader","unicodes")
+
+local trace_markwidth = false trackers.register("otf.markwidth", function(v) trace_markwidth = v end)
+local trace_cleanup = false trackers.register("otf.cleanups", function(v) trace_cleanups = v end)
+local trace_optimizations = false trackers.register("otf.optimizations", function(v) trace_optimizations = v end)
+local trace_unicodes = false trackers.register("otf.unicodes", function(v) trace_unicodes = v end)
+
+local readers = fonts.handlers.otf.readers
+local privateoffset = fonts.constructors and fonts.constructors.privateoffset or 0xF0000 -- 0x10FFFF
+
+local f_private = formatters["P%05X"]
+local f_unicode = formatters["U%05X"]
+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 = true -- can become an option (pseudo feature) / needed for tagging
+
+directives.register("otf.checksofthyphen",function(v)
+ check_soft_hyphen = v
+end)
+
+local function replaced(list,index,replacement)
+ if type(list) == "number" then
+ return replacement
+ elseif type(replacement) == "table" then
+ local t = { }
+ local n = index-1
+ for i=1,n do
+ t[i] = list[i]
+ end
+ for i=1,#replacement do
+ n = n + 1
+ t[n] = replacement[i]
+ end
+ for i=index+1,#list do
+ n = n + 1
+ t[n] = list[i]
+ end
+ else
+ list[index] = replacement
+ return list
+ end
+end
+
+local function unifyresources(fontdata,indices)
+ local descriptions = fontdata.descriptions
+ local resources = fontdata.resources
+ if not descriptions or not resources then
+ return
+ end
+ --
+ local nofindices = #indices
+ --
+ local variants = fontdata.resources.variants
+ if variants then
+ for selector, unicodes in next, variants do
+ for unicode, index in next, unicodes do
+ unicodes[unicode] = indices[index]
+ end
+ end
+ end
+ --
+ local function remark(marks)
+ if marks then
+ local newmarks = { }
+ for k, v in next, marks do
+ local u = indices[k]
+ if u then
+ newmarks[u] = v
+ elseif trace_optimizations then
+ report_optimizations("discarding mark %i",k)
+ end
+ end
+ return newmarks
+ end
+ end
+ --
+ local marks = resources.marks
+ if marks then
+ resources.marks = remark(marks)
+ end
+ --
+ local markclasses = resources.markclasses
+ if markclasses then
+ for class, marks in next, markclasses do
+ markclasses[class] = remark(marks)
+ end
+ end
+ --
+ local marksets = resources.marksets
+ if marksets then
+ for class, marks in next, marksets do
+ marksets[class] = remark(marks)
+ end
+ end
+ --
+ local done = { } -- we need to deal with shared !
+ --
+ local duplicates = check_duplicates and resources.duplicates
+ if duplicates and not next(duplicates) then
+ duplicates = false
+ end
+ --
+ local function recover(cover) -- can be packed
+ for i=1,#cover do
+ local c = cover[i]
+ if not done[c] then
+ local t = { }
+ for k, v in next, c do
+ local ug = indices[k]
+ if ug then
+ t[ug] = v
+ else
+ report_error("case %i, bad index in unifying %s: %s of %s",1,"coverage",k,nofindices)
+ end
+ end
+ cover[i] = t
+ done[c] = d
+ end
+ end
+ end
+ --
+ local function recursed(c,kind) -- ligs are not packed
+ local t = { }
+ for g, d in next, c do
+ if type(d) == "table" then
+ local ug = indices[g]
+ if ug then
+ t[ug] = recursed(d,kind)
+ else
+ report_error("case %i, bad index in unifying %s: %s of %s",1,kind,g,nofindices)
+ end
+ else
+ t[g] = indices[d] -- ligature
+ end
+ end
+ return t
+ end
+ --
+ -- the duplicates need checking (probably only in cjk fonts): currently we only check
+ -- gsub_single, gsub_alternate, gsub_multiple, gpos_single and gpos_cursive
+ --
+ local function unifythem(sequences)
+ if not sequences then
+ return
+ end
+ for i=1,#sequences do
+ local sequence = sequences[i]
+ local kind = sequence.type
+ local steps = sequence.steps
+ local features = sequence.features
+ if steps then
+ for i=1,#steps do
+ local step = steps[i]
+ if kind == "gsub_single" then
+ local c = step.coverage
+ if c then
+ local t1 = done[c]
+ if not t1 then
+ t1 = { }
+ if duplicates then
+ for g1, d1 in next, c do
+ local ug1 = indices[g1]
+ if ug1 then
+ local ud1 = indices[d1]
+ if ud1 then
+ t1[ug1] = ud1
+ local dg1 = duplicates[ug1]
+ if dg1 then
+ for u in next, dg1 do
+ t1[u] = ud1
+ end
+ end
+ else
+ report_error("case %i, bad index in unifying %s: %s of %s",3,kind,d1,nofindices)
+ end
+ else
+ report_error("case %i, bad index in unifying %s: %s of %s",1,kind,g1,nofindices)
+ end
+ end
+ else
+ for g1, d1 in next, c do
+ local ug1 = indices[g1]
+ if ug1 then
+ t1[ug1] = indices[d1]
+ else
+ report_error("fuzzy case %i in unifying %s: %i",2,kind,g1)
+ end
+ end
+ end
+ done[c] = t1
+ end
+ step.coverage = t1
+ end
+ elseif kind == "gpos_pair" then
+ local c = step.coverage
+ if c then
+ local t1 = done[c]
+ if not t1 then
+ t1 = { }
+ for g1, d1 in next, c do
+ local ug1 = indices[g1]
+ if ug1 then
+ local t2 = done[d1]
+ if not t2 then
+ t2 = { }
+ for g2, d2 in next, d1 do
+ local ug2 = indices[g2]
+ if ug2 then
+ t2[ug2] = d2
+ else
+ report_error("case %i, bad index in unifying %s: %s of %s",1,kind,g2,nofindices,nofindices)
+ end
+ end
+ done[d1] = t2
+ end
+ t1[ug1] = t2
+ else
+ report_error("case %i, bad index in unifying %s: %s of %s",2,kind,g1,nofindices)
+ end
+ end
+ done[c] = t1
+ end
+ step.coverage = t1
+ end
+ elseif kind == "gsub_ligature" then
+ local c = step.coverage
+ if c then
+ step.coverage = recursed(c,kind)
+ end
+ elseif kind == "gsub_alternate" or kind == "gsub_multiple" then
+ local c = step.coverage
+ if c then
+ local t1 = done[c]
+ if not t1 then
+ t1 = { }
+ if duplicates then
+ for g1, d1 in next, c do
+ for i=1,#d1 do
+ local d1i = d1[i]
+ local d1u = indices[d1i]
+ if d1u then
+ d1[i] = d1u
+ else
+ report_error("case %i, bad index in unifying %s: %s of %s",1,kind,i,d1i,nofindices)
+ end
+ end
+ local ug1 = indices[g1]
+ if ug1 then
+ t1[ug1] = d1
+ local dg1 = duplicates[ug1]
+ if dg1 then
+ for u in next, dg1 do
+ t1[u] = copy(d1)
+ end
+ end
+ else
+ report_error("case %i, bad index in unifying %s: %s of %s",2,kind,g1,nofindices)
+ end
+ end
+ else
+ for g1, d1 in next, c do
+ for i=1,#d1 do
+ local d1i = d1[i]
+ local d1u = indices[d1i]
+ if d1u then
+ d1[i] = d1u
+ else
+ report_error("case %i, bad index in unifying %s: %s of %s",2,kind,d1i,nofindices)
+ end
+ end
+ t1[indices[g1]] = d1
+ end
+ end
+ done[c] = t1
+ end
+ step.coverage = t1
+ end
+ elseif kind == "gpos_single" then
+ local c = step.coverage
+ if c then
+ local t1 = done[c]
+ if not t1 then
+ t1 = { }
+ if duplicates then
+ for g1, d1 in next, c do
+ local ug1 = indices[g1]
+ if ug1 then
+ t1[ug1] = d1
+ local dg1 = duplicates[ug1]
+ if dg1 then
+ for u in next, dg1 do
+ t1[u] = d1
+ end
+ end
+ else
+ report_error("case %i, bad index in unifying %s: %s of %s",1,kind,g1,nofindices)
+ end
+ end
+ else
+ for g1, d1 in next, c do
+ local ug1 = indices[g1]
+ if ug1 then
+ t1[ug1] = d1
+ else
+ report_error("case %i, bad index in unifying %s: %s of %s",2,kind,g1,nofindices)
+ end
+ end
+ end
+ done[c] = t1
+ end
+ step.coverage = t1
+ end
+ elseif kind == "gpos_mark2base" or kind == "gpos_mark2mark" or kind == "gpos_mark2ligature" then
+ local c = step.coverage
+ if c then
+ local t1 = done[c]
+ if not t1 then
+ t1 = { }
+ for g1, d1 in next, c do
+ local ug1 = indices[g1]
+ if ug1 then
+ t1[ug1] = d1
+ else
+ report_error("case %i, bad index in unifying %s: %s of %s",1,kind,g1,nofindices)
+ end
+ end
+ done[c] = t1
+ end
+ step.coverage = t1
+ end
+ local c = step.baseclasses
+ if c then
+ local t1 = done[c]
+ if not t1 then
+ for g1, d1 in next, c do
+ local t2 = done[d1]
+ if not t2 then
+ t2 = { }
+ for g2, d2 in next, d1 do
+ local ug2 = indices[g2]
+ if ug2 then
+ t2[ug2] = d2
+ else
+ report_error("case %i, bad index in unifying %s: %s of %s",2,kind,g2,nofindices)
+ end
+ end
+ done[d1] = t2
+ end
+ c[g1] = t2
+ end
+ done[c] = c
+ end
+ end
+ elseif kind == "gpos_cursive" then
+ local c = step.coverage
+ if c then
+ local t1 = done[c]
+ if not t1 then
+ t1 = { }
+ if duplicates then
+ for g1, d1 in next, c do
+ local ug1 = indices[g1]
+ if ug1 then
+ t1[ug1] = d1
+ --
+ local dg1 = duplicates[ug1]
+ if dg1 then
+ -- probably needs a bit more
+ for u in next, dg1 do
+ t1[u] = copy(d1)
+ end
+ end
+ else
+ report_error("case %i, bad index in unifying %s: %s of %s",1,kind,g1,nofindices)
+ end
+ end
+ else
+ for g1, d1 in next, c do
+ local ug1 = indices[g1]
+ if ug1 then
+ t1[ug1] = d1
+ else
+ report_error("case %i, bad index in unifying %s: %s of %s",2,kind,g1,nofindices)
+ end
+ end
+ end
+ done[c] = t1
+ end
+ step.coverage = t1
+ end
+ end
+ --
+ local rules = step.rules
+ if rules then
+ for i=1,#rules do
+ local rule = rules[i]
+ --
+ local before = rule.before if before then recover(before) end
+ local after = rule.after if after then recover(after) end
+ local current = rule.current if current then recover(current) end
+ --
+ local replacements = rule.replacements
+ if replacements then
+ if not done[replacements] then
+ local r = { }
+ for k, v in next, replacements do
+ r[indices[k]] = indices[v]
+ end
+ rule.replacements = r
+ done[replacements] = r
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ --
+ unifythem(resources.sequences)
+ unifythem(resources.sublookups)
+end
+
+local function copyduplicates(fontdata)
+ if check_duplicates then
+ local descriptions = fontdata.descriptions
+ local resources = fontdata.resources
+ 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
+ descriptions[0xAD] = nil
+ if trace_unicodes then
+ report_unicodes("patching soft hyphen")
+ end
+ else
+ if trace_unicodes then
+ report_unicodes("adding soft hyphen")
+ end
+ end
+ if not duplicates then
+ duplicates = { }
+ resources.duplicates = duplicates
+ end
+ local dh = duplicates[0x2D]
+ if dh then
+ dh[#dh+1] = { [0xAD] = true }
+ else
+ duplicates[0x2D] = { [0xAD] = true }
+ end
+ end
+ end
+ if duplicates then
+ for u, d in next, duplicates do
+ local du = descriptions[u]
+ if du then
+ local t = { f_character_y(u), "@", f_index(du.index), "->" }
+ local n = 0
+ local m = 25
+ for u in next, d do
+ if descriptions[u] then
+ if n < m then
+ t[n+4] = f_character_n(u)
+ end
+ else
+ local c = copy(du)
+ c.unicode = u -- better this way
+ descriptions[u] = c
+ if n < m then
+ t[n+4] = f_character_y(u)
+ end
+ end
+ n = n + 1
+ end
+ if trace_unicodes then
+ if n <= m then
+ report_unicodes("%i : % t",n,t)
+ else
+ report_unicodes("%i : % t ...",n,t)
+ end
+ end
+ else
+ -- what a mess
+ end
+ end
+ end
+ end
+end
+
+local ignore = { -- should we fix them?
+ ["notdef"] = true,
+ [".notdef"] = true,
+ ["null"] = true,
+ [".null"] = true,
+ ["nonmarkingreturn"] = true,
+}
+
+
+local function checklookups(fontdata,missing,nofmissing)
+ local descriptions = fontdata.descriptions
+ local resources = fontdata.resources
+ if missing and nofmissing and nofmissing <= 0 then
+ return
+ end
+ --
+ local singles = { }
+ local alternates = { }
+ local ligatures = { }
+
+ if not missing then
+ missing = { }
+ nofmissing = 0
+ for u, d in next, descriptions do
+ if not d.unicode then
+ nofmissing = nofmissing + 1
+ missing[u] = true
+ end
+ end
+ end
+ local function collectthem(sequences)
+ if not sequences then
+ return
+ end
+ for i=1,#sequences do
+ local sequence = sequences[i]
+ local kind = sequence.type
+ local steps = sequence.steps
+ if steps then
+ for i=1,#steps do
+ local step = steps[i]
+ if kind == "gsub_single" then
+ local c = step.coverage
+ if c then
+ singles[#singles+1] = c
+ end
+ elseif kind == "gsub_alternate" then
+ local c = step.coverage
+ if c then
+ alternates[#alternates+1] = c
+ end
+ elseif kind == "gsub_ligature" then
+ local c = step.coverage
+ if c then
+ ligatures[#ligatures+1] = c
+ end
+ end
+ end
+ end
+ end
+ end
+
+ collectthem(resources.sequences)
+ collectthem(resources.sublookups)
+
+ local loops = 0
+ while true do
+ loops = loops + 1
+ local old = nofmissing
+ for i=1,#singles do
+ local c = singles[i]
+ for g1, g2 in next, c do
+ if missing[g1] then
+ local u2 = descriptions[g2].unicode
+ if u2 then
+ missing[g1] = false
+ descriptions[g1].unicode = u2
+ nofmissing = nofmissing - 1
+ end
+ end
+ if missing[g2] then
+ local u1 = descriptions[g1].unicode
+ if u1 then
+ missing[g2] = false
+ descriptions[g2].unicode = u1
+ nofmissing = nofmissing - 1
+ end
+ end
+ end
+ end
+ for i=1,#alternates do
+ local c = alternates[i]
+ -- maybe first a g1 loop and then a g2
+ for g1, d1 in next, c do
+ if missing[g1] then
+ for i=1,#d1 do
+ local g2 = d1[i]
+ local u2 = descriptions[g2].unicode
+ if u2 then
+ missing[g1] = false
+ descriptions[g1].unicode = u2
+ nofmissing = nofmissing - 1
+ end
+ end
+ end
+ if not missing[g1] then
+ for i=1,#d1 do
+ local g2 = d1[i]
+ if missing[g2] then
+ local u1 = descriptions[g1].unicode
+ if u1 then
+ missing[g2] = false
+ descriptions[g2].unicode = u1
+ nofmissing = nofmissing - 1
+ end
+ end
+ end
+ end
+ end
+ end
+ if nofmissing <= 0 then
+ if trace_unicodes then
+ report_unicodes("all missings done in %s loops",loops)
+ end
+ return
+ elseif old == nofmissing then
+ break
+ end
+ end
+
+ local t, n -- no need to insert/remove and allocate many times
+
+ local function recursed(c)
+ for g, d in next, c do
+ if g ~= "ligature" then
+ local u = descriptions[g].unicode
+ if u then
+ n = n + 1
+ t[n] = u
+ recursed(d)
+ n = n - 1
+ end
+ elseif missing[d] then
+ local l = { }
+ local m = 0
+ for i=1,n do
+ local u = t[i]
+ if type(u) == "table" then
+ for i=1,#u do
+ m = m + 1
+ l[m] = u[i]
+ end
+ else
+ m = m + 1
+ l[m] = u
+ end
+ end
+ missing[d] = false
+ descriptions[d].unicode = l
+ nofmissing = nofmissing - 1
+ end
+ end
+ end
+
+ if nofmissing > 0 then
+ t = { }
+ n = 0
+ local loops = 0
+ while true do
+ loops = loops + 1
+ local old = nofmissing
+ for i=1,#ligatures do
+ recursed(ligatures[i])
+ end
+ if nofmissing <= 0 then
+ if trace_unicodes then
+ report_unicodes("all missings done in %s loops",loops)
+ end
+ return
+ elseif old == nofmissing then
+ break
+ end
+ end
+ t = nil
+ n = 0
+ end
+
+ if trace_unicodes and nofmissing > 0 then
+ local done = { }
+ for i, r in next, missing do
+ if r then
+ local data = descriptions[i]
+ local name = data and data.name or f_index(i)
+ if not ignore[name] then
+ done[name] = true
+ end
+ end
+ end
+ if next(done) then
+ report_unicodes("not unicoded: % t",sortedkeys(done))
+ end
+ end
+end
+
+local function unifymissing(fontdata)
+ if not fonts.mappings then
+ require("font-map")
+ require("font-agl")
+ end
+ local unicodes = { }
+ local resources = fontdata.resources
+ resources.unicodes = unicodes
+ for unicode, d in next, fontdata.descriptions do
+ if unicode < privateoffset then
+ local name = d.name
+ if name then
+ unicodes[name] = unicode
+ end
+ end
+ end
+ fonts.mappings.addtounicode(fontdata,fontdata.filename,checklookups)
+ resources.unicodes = nil
+end
+
+local firstprivate = fonts.privateoffsets and fonts.privateoffsets.textbase or 0xF0000
+local puafirst = 0xE000
+local pualast = 0xF8FF
+
+local function unifyglyphs(fontdata,usenames)
+ local private = fontdata.private or privateoffset
+ local glyphs = fontdata.glyphs
+ local indices = { }
+ local descriptions = { }
+ local names = usenames and { }
+ local resources = fontdata.resources
+ local zero = glyphs[0]
+ local zerocode = zero.unicode
+ if not zerocode then
+ zerocode = private
+ zero.unicode = zerocode
+ private = private + 1
+ end
+ descriptions[zerocode] = zero
+ if names then
+ local name = glyphs[0].name or f_private(zerocode)
+ indices[0] = name
+ names[name] = zerocode
+ else
+ indices[0] = zerocode
+ end
+ --
+ if names then
+ -- seldom uses, we don't issue message ... this branch might even go away
+ for index=1,#glyphs do
+ local glyph = glyphs[index]
+ local unicode = glyph.unicode -- this is the primary one
+ if not unicode then
+ unicode = private
+ local name = glyph.name or f_private(unicode)
+ indices[index] = name
+ names[name] = unicode
+ private = private + 1
+ elseif unicode >= firstprivate then
+ unicode = private
+ local name = glyph.name or f_private(unicode)
+ indices[index] = name
+ names[name] = unicode
+ private = private + 1
+ elseif unicode >= puafirst and unicode <= pualast then
+ local name = glyph.name or f_private(unicode)
+ indices[index] = name
+ names[name] = unicode
+ elseif descriptions[unicode] then
+ unicode = private
+ local name = glyph.name or f_private(unicode)
+ indices[index] = name
+ names[name] = unicode
+ private = private + 1
+ else
+ local name = glyph.name or f_unicode(unicode)
+ indices[index] = name
+ names[name] = unicode
+ end
+ descriptions[unicode] = glyph
+ end
+ elseif trace_unicodes then
+ for index=1,#glyphs do
+ local glyph = glyphs[index]
+ local unicode = glyph.unicode -- this is the primary one
+ if not unicode then
+ unicode = private
+ indices[index] = unicode
+ private = private + 1
+ elseif unicode >= firstprivate then
+ local name = glyph.name
+ if name then
+ report_unicodes("moving glyph %a indexed %05X from private %U to %U ",name,index,unicode,private)
+ else
+ report_unicodes("moving glyph indexed %05X from private %U to %U ",index,unicode,private)
+ end
+ unicode = private
+ indices[index] = unicode
+ private = private + 1
+ elseif unicode >= puafirst and unicode <= pualast then
+ local name = glyph.name
+ if name then
+ report_unicodes("keeping private unicode %U for glyph %a indexed %05X",unicode,name,index)
+ else
+ report_unicodes("keeping private unicode %U for glyph indexed %05X",unicode,index)
+ end
+ indices[index] = unicode
+ elseif descriptions[unicode] then
+ local name = glyph.name
+ if name then
+ report_unicodes("assigning duplicate unicode %U to %U for glyph %a indexed %05X ",unicode,private,name,index)
+ else
+ report_unicodes("assigning duplicate unicode %U to %U for glyph indexed %05X ",unicode,private,index)
+ end
+ unicode = private
+ indices[index] = unicode
+ private = private + 1
+ else
+ indices[index] = unicode
+ end
+ descriptions[unicode] = glyph
+ end
+ else
+ for index=1,#glyphs do
+ local glyph = glyphs[index]
+ local unicode = glyph.unicode -- this is the primary one
+ if not unicode then
+ unicode = private
+ indices[index] = unicode
+ private = private + 1
+ elseif unicode >= firstprivate then
+ local name = glyph.name
+ unicode = private
+ indices[index] = unicode
+ private = private + 1
+ elseif unicode >= puafirst and unicode <= pualast then
+ local name = glyph.name
+ indices[index] = unicode
+ elseif descriptions[unicode] then
+ local name = glyph.name
+ unicode = private
+ indices[index] = unicode
+ private = private + 1
+ else
+ indices[index] = unicode
+ end
+ descriptions[unicode] = glyph
+ end
+ end
+ --
+ for index=1,#glyphs do
+ local math = glyphs[index].math
+ if math then
+ local list = math.vparts
+ if list then
+ for i=1,#list do local l = list[i] l.glyph = indices[l.glyph] end
+ end
+ local list = math.hparts
+ if list then
+ for i=1,#list do local l = list[i] l.glyph = indices[l.glyph] end
+ end
+ local list = math.vvariants
+ if list then
+ -- for i=1,#list do local l = list[i] l.glyph = indices[l.glyph] end
+ for i=1,#list do list[i] = indices[list[i]] end
+ end
+ local list = math.hvariants
+ if list then
+ -- for i=1,#list do local l = list[i] l.glyph = indices[l.glyph] end
+ for i=1,#list do list[i] = indices[list[i]] end
+ end
+ end
+ end
+ --
+ local colorpalettes = resources.colorpalettes
+ if colorpalettes then
+ for index=1,#glyphs do
+ local colors = glyphs[index].colors
+ if colors then
+ for i=1,#colors do
+ local c = colors[i]
+ c.slot = indices[c.slot]
+ end
+ end
+ end
+ end
+ --
+ fontdata.private = private
+ fontdata.glyphs = nil
+ fontdata.names = names
+ fontdata.descriptions = descriptions
+ fontdata.hashmethod = hashmethod
+ --
+ return indices, names
+end
+
+local p_crappyname do
+
+ local p_hex = R("af","AF","09")
+ local p_digit = R("09")
+ local p_done = S("._-")^0 + P(-1)
+ local p_alpha = R("az","AZ")
+ local p_ALPHA = R("AZ")
+
+ p_crappyname = (
+ -- (P("uni") + P("UNI") + P("Uni") + P("U") + P("u"))
+ lpeg.utfchartabletopattern({ "uni", "u" },true)
+ * S("Xx_")^0
+ * p_hex^1
+ -- + (P("identity") + P("Identity") + P("IDENTITY") + P("glyph") + P("jamo"))
+ + lpeg.utfchartabletopattern({ "identity", "glyph", "jamo" },true)
+ * p_hex^1
+ -- + (P("index") + P("Index") + P("INDEX")+ P("afii"))
+ + lpeg.utfchartabletopattern({ "index", "afii" }, true)
+ * p_digit^1
+ -- also happens l
+ + p_digit
+ * p_hex^3
+ + p_alpha
+ * p_digit^1
+ -- sort of special
+ + P("aj")
+ * p_digit^1
+ + P("eh_")
+ * (p_digit^1 + p_ALPHA * p_digit^1)
+ + (1-P("_"))^1
+ * P("_uni")
+ * p_hex^1
+ + P("_")
+ * P(1)^1
+ ) * p_done
+
+end
+
+-- In context we only keep glyph names because of tracing and access by name
+-- so weird names make no sense.
+
+local forcekeep = false -- only for testing something
+
+directives.register("otf.keepnames",function(v)
+ report_cleanup("keeping weird glyph names, expect larger files and more memory usage")
+ forcekeep = v
+end)
+
+local function stripredundant(fontdata)
+ local descriptions = fontdata.descriptions
+ if descriptions then
+ local n = 0
+ local c = 0
+ -- in context we always strip
+ if (not context and fonts.privateoffsets.keepnames) or forcekeep then
+ for unicode, d in next, descriptions do
+ if d.class == "base" then
+ d.class = nil
+ c = c + 1
+ end
+ end
+ else
+ for unicode, d in next, descriptions do
+ local name = d.name
+ if name and lpegmatch(p_crappyname,name) then
+ d.name = nil
+ n = n + 1
+ end
+ if d.class == "base" then
+ d.class = nil
+ c = c + 1
+ end
+ end
+ end
+ if trace_cleanup then
+ if n > 0 then
+ report_cleanup("%s bogus names removed (verbose unicode)",n)
+ end
+ if c > 0 then
+ report_cleanup("%s base class tags removed (default is base)",c)
+ end
+ end
+ end
+end
+
+readers.stripredundant = stripredundant
+
+function readers.getcomponents(fontdata) -- handy for resolving ligatures when names are missing
+ local resources = fontdata.resources
+ if resources then
+ local sequences = resources.sequences
+ if sequences then
+ local collected = { }
+ for i=1,#sequences do
+ local sequence = sequences[i]
+ if sequence.type == "gsub_ligature" then
+ local steps = sequence.steps
+ if steps then
+ local l = { }
+ local function traverse(p,k,v)
+ if k == "ligature" then
+ collected[v] = { unpack(l) }
+ else
+ insert(l,k)
+ for k, vv in next, v do
+ traverse(p,k,vv)
+ end
+ remove(l)
+ end
+ end
+ for i=1,#steps do
+ -- we actually had/have this in base mode
+ local c = steps[i].coverage
+ if c then
+ for k, v in next, c do
+ traverse(k,k,v)
+ end
+ end
+ end
+ end
+ end
+ end
+ if next(collected) then
+ -- remove self referring
+ -- for k, v in next, collected do
+ -- for i=1,#v do
+ -- local vi = v[i]
+ -- if vi == k then
+ -- -- report("removing self referring ligature @ slot %5X from collected (1)",k)
+ -- collected[k] = nil
+ -- end
+ -- end
+ -- end
+ while true do
+ local done = false
+ for k, v in next, collected do
+ for i=1,#v do
+ local vi = v[i]
+ if vi == k then
+ -- report("removing self referring ligature @ slot %5X from collected (2)",k)
+ collected[k] = nil
+ break
+ else
+ local c = collected[vi]
+ if c then
+ done = true
+ local t = { }
+ local n = i - 1
+ for j=1,n do
+ t[j] = v[j]
+ end
+ for j=1,#c do
+ n = n + 1
+ t[n] = c[j]
+ end
+ for j=i+1,#v do
+ n = n + 1
+ t[n] = v[j]
+ end
+ collected[k] = t
+ break
+ end
+ end
+ end
+ end
+ if not done then
+ break
+ end
+ end
+ return collected
+ end
+ end
+ end
+end
+
+readers.unifymissing = unifymissing
+
+function readers.rehash(fontdata,hashmethod) -- TODO: combine loops in one
+ if not (fontdata and fontdata.glyphs) then
+ return
+ end
+ if hashmethod == "indices" then
+ fontdata.hashmethod = "indices"
+ elseif hashmethod == "names" then
+ fontdata.hashmethod = "names"
+ local indices = unifyglyphs(fontdata,true)
+ unifyresources(fontdata,indices)
+ copyduplicates(fontdata)
+ unifymissing(fontdata)
+ -- stripredundant(fontdata)
+ else
+ fontdata.hashmethod = "unicodes"
+ local indices = unifyglyphs(fontdata)
+ unifyresources(fontdata,indices)
+ copyduplicates(fontdata)
+ unifymissing(fontdata)
+ stripredundant(fontdata)
+ end
+ -- maybe here components
+end
+
+function readers.checkhash(fontdata)
+ local hashmethod = fontdata.hashmethod
+ if hashmethod == "unicodes" then
+ fontdata.names = nil -- just to be sure
+ elseif hashmethod == "names" and fontdata.names then
+ unifyresources(fontdata,fontdata.names)
+ copyduplicates(fontdata)
+ fontdata.hashmethod = "unicodes"
+ fontdata.names = nil -- no need for it
+ else
+ readers.rehash(fontdata,"unicodes")
+ end
+end
+
+function readers.addunicodetable(fontdata)
+ local resources = fontdata.resources
+ local unicodes = resources.unicodes
+ if not unicodes then
+ local descriptions = fontdata.descriptions
+ if descriptions then
+ unicodes = { }
+ resources.unicodes = unicodes
+ for u, d in next, descriptions do
+ local n = d.name
+ if n then
+ unicodes[n] = u
+ end
+ end
+ end
+ end
+end
+
+-- for the moment here:
+
+local concat, sort = table.concat, table.sort
+local next, type, tostring = next, type, tostring
+
+local criterium = 1
+local threshold = 0
+
+local trace_packing = false trackers.register("otf.packing", function(v) trace_packing = v end)
+local trace_loading = false trackers.register("otf.loading", function(v) trace_loading = v end)
+
+local report_otf = logs.reporter("fonts","otf loading")
+
+local function tabstr_normal(t)
+ local s = { }
+ local n = 0
+ for k, v in next, t do
+ n = n + 1
+ if type(v) == "table" then
+ s[n] = k .. ">" .. tabstr_normal(v)
+ elseif v == true then
+ s[n] = k .. "+" -- "=true"
+ elseif v then
+ s[n] = k .. "=" .. v
+ else
+ s[n] = k .. "-" -- "=false"
+ end
+ end
+ if n == 0 then
+ return ""
+ elseif n == 1 then
+ return s[1]
+ else
+ sort(s) -- costly but needed (occasional wrong hit otherwise)
+ return concat(s,",")
+ end
+end
+
+local function tabstr_flat(t)
+ local s = { }
+ local n = 0
+ for k, v in next, t do
+ n = n + 1
+ s[n] = k .. "=" .. v
+ end
+ if n == 0 then
+ return ""
+ elseif n == 1 then
+ return s[1]
+ else
+ sort(s) -- costly but needed (occasional wrong hit otherwise)
+ return concat(s,",")
+ end
+end
+
+local function tabstr_mixed(t) -- indexed
+ local s = { }
+ local n = #t
+ if n == 0 then
+ return ""
+ elseif n == 1 then
+ local k = t[1]
+ if k == true then
+ return "++" -- we need to distinguish from "true"
+ elseif k == false then
+ return "--" -- we need to distinguish from "false"
+ else
+ return tostring(k) -- number or string
+ end
+ else
+ for i=1,n do
+ local k = t[i]
+ if k == true then
+ s[i] = "++" -- we need to distinguish from "true"
+ elseif k == false then
+ s[i] = "--" -- we need to distinguish from "false"
+ else
+ s[i] = k -- number or string
+ end
+ end
+ return concat(s,",")
+ end
+end
+
+local function tabstr_boolean(t)
+ local s = { }
+ local n = 0
+ for k, v in next, t do
+ n = n + 1
+ if v then
+ s[n] = k .. "+"
+ else
+ s[n] = k .. "-"
+ end
+ end
+ if n == 0 then
+ return ""
+ elseif n == 1 then
+ return s[1]
+ else
+ sort(s) -- costly but needed (occasional wrong hit otherwise)
+ return concat(s,",")
+ end
+end
+
+-- beware: we cannot unpack and repack the same table because then sharing
+-- interferes (we could catch this if needed) .. so for now: save, reload
+-- and repack in such cases (never needed anyway) .. a tricky aspect is that
+-- we then need to sort more thanks to random hashing
+
+function readers.pack(data)
+
+ if data then
+
+ local h, t, c = { }, { }, { }
+ local hh, tt, cc = { }, { }, { }
+ local nt, ntt = 0, 0
+
+ local function pack_normal(v)
+ local tag = tabstr_normal(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
+
+ 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
+ c[nt] = 1
+ return nt
+ end
+ end
+
+ local function pack_flat(v)
+ local tag = tabstr_flat(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
+
+ local function pack_indexed(v)
+ local tag = concat(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
+
+ local function pack_mixed(v)
+ local tag = tabstr_mixed(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
+
+ -- 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
+ 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 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
+ report_otf("pack quality: nothing to pack")
+ end
+ return false
+ elseif nt >= threshold then
+ local one = 0
+ local two = 0
+ local rest = 0
+ if pass == 1 then
+ for k,v in next, c do
+ if v == 1 then
+ one = one + 1
+ elseif v == 2 then
+ two = two + 1
+ else
+ rest = rest + 1
+ end
+ end
+ else
+ for k,v in next, cc do
+ if v > 20 then
+ rest = rest + 1
+ elseif v > 10 then
+ two = two + 1
+ else
+ one = one + 1
+ end
+ end
+ data.tables = tt
+ end
+ if trace_loading or trace_packing then
+ report_otf("pack quality: stage %s, pass %s, %s packed, 1-10:%s, 11-20:%s, rest:%s (criterium: %s)",
+ stage, pass, one+two+rest, one, two, rest, criterium)
+ end
+ return true
+ else
+ if trace_loading or trace_packing then
+ report_otf("pack quality: stage %s, pass %s, %s packed, aborting pack (threshold: %s)",
+ stage, pass, nt, threshold)
+ end
+ return false
+ end
+ end
+
+ local function packers(pass)
+ if pass == 1 then
+ 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, pack_final_cc
+ end
+ end
+
+ local resources = data.resources
+ local sequences = resources.sequences
+ 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
+
+ if not descriptions then
+ 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, pack_normal_cc = packers(pass)
+
+ for unicode, description in next, descriptions do
+ local boundingbox = description.boundingbox
+ if boundingbox then
+ description.boundingbox = pack_indexed(boundingbox)
+ end
+ local math = description.math
+ if math then
+ local kerns = math.kerns
+ if kerns then
+ for tag, kern in next, kerns do
+ kerns[tag] = pack_normal(kern)
+ end
+ end
+ end
+ -- if palettes then
+ -- local color = description.color
+ -- if color then
+ -- for i=1,#color do
+ -- color[i] = pack_normal(color[i])
+ -- end
+ -- end
+ -- end
+ end
+
+ local function packthem(sequences)
+ for i=1,#sequences do
+ local sequence = sequences[i]
+ local kind = sequence.type
+ local steps = sequence.steps
+ local order = sequence.order
+ local features = sequence.features
+ local flags = sequence.flags
+ 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 ~= "pair" then
+ for g1, d1 in next, c do
+ c[g1] = pack_normal(d1)
+ end
+ elseif step.shared then
+ -- This branch results from classes. We already share at the reader end. Maybe
+ -- the sharing should be moved there altogether but it becomes kind of messy
+ -- then. Here we're still wasting time because in the second pass we serialize
+ -- and hash. So we compromise. We could merge the two passes ...
+ local shared = { }
+ for g1, d1 in next, c do
+ for g2, d2 in next, d1 do
+ if not shared[d2] then
+ 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
+ shared[d2] = true
+ end
+ end
+ end
+ if pass == 2 then
+ step.shared = nil -- weird, so dups
+ end
+ else
+ for g1, d1 in next, c do
+ 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
+ end
+ end
+ elseif kind == "gpos_single" then
+ local c = step.coverage
+ if c then
+ if step.format == "single" then
+ for g1, d1 in next, c do
+ 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
+ local c = step.coverage
+ if c then
+ for g1, d1 in next, c do
+ local f = d1[2] if f then d1[2] = pack_indexed(f) end
+ local s = d1[3] if s then d1[3] = pack_indexed(s) end
+ end
+ end
+ elseif kind == "gpos_mark2base" or kind == "gpos_mark2mark" then
+ local c = step.baseclasses
+ if c then
+ for g1, d1 in next, c do
+ for g2, d2 in next, d1 do
+ d1[g2] = pack_indexed(d2)
+ end
+ end
+ end
+ local c = step.coverage
+ if c then
+ for g1, d1 in next, c do
+ d1[2] = pack_indexed(d1[2])
+ end
+ end
+ elseif kind == "gpos_mark2ligature" then
+ local c = step.baseclasses
+ if c then
+ for g1, d1 in next, c do
+ for g2, d2 in next, d1 do
+ for g3, d3 in next, d2 do
+ d2[g3] = pack_indexed(d3)
+ end
+ end
+ end
+ end
+ local c = step.coverage
+ if c then
+ for g1, d1 in next, c do
+ d1[2] = pack_indexed(d1[2])
+ end
+ end
+ end
+ -- if ... chain ...
+ local rules = step.rules
+ if rules then
+ for i=1,#rules do
+ local rule = rules[i]
+ 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.replacements if r then rule.replacements = pack_flat (r) end
+ end
+ end
+ end
+ end
+ if order then
+ sequence.order = pack_indexed(order)
+ end
+ if features then
+ for script, feature in next, features do
+ features[script] = pack_normal(feature)
+ end
+ end
+ if flags then
+ sequence.flags = pack_normal(flags)
+ end
+ end
+ end
+
+ if sequences then
+ packthem(sequences)
+ end
+
+ if sublookups then
+ packthem(sublookups)
+ end
+
+ if features then
+ for k, list in next, features do
+ for feature, spec in next, list do
+ list[feature] = pack_normal(spec)
+ end
+ end
+ end
+
+ if palettes then
+ for i=1,#palettes do
+ local p = palettes[i]
+ for j=1,#p do
+ p[j] = pack_indexed(p[j])
+ end
+ 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
+ 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
+
+ end
+
+ if nt > 0 then
+
+ for pass=1,2 do
+
+ if trace_packing then
+ report_otf("start packing: stage 2, pass %s",pass)
+ end
+
+ 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
+ if math then
+ local kerns = math.kerns
+ if kerns then
+ math.kerns = pack_normal(kerns)
+ end
+ end
+ end
+
+ local function packthem(sequences)
+ for i=1,#sequences do
+ local sequence = sequences[i]
+ local kind = sequence.type
+ local steps = sequence.steps
+ local features = sequence.features
+ 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 == "pair" then
+ for g1, d1 in next, c do
+ for g2, d2 in next, d1 do
+ d1[g2] = pack_normal(d2)
+ end
+ end
+ end
+ 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
+ for i=1,#rules do
+ local rule = rules[i]
+ local r = rule.before if r then rule.before = pack_normal(r) end
+ local r = rule.after if r then rule.after = pack_normal(r) end
+ local r = rule.current if r then rule.current = pack_normal(r) end
+ end
+ end
+ end
+ end
+ if features then
+ sequence.features = pack_normal(features)
+ end
+ end
+ end
+ if sequences then
+ packthem(sequences)
+ end
+ if sublookups then
+ packthem(sublookups)
+ end
+ 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
+ if trace_packing then
+ report_otf("start packing: stage 3, pass %s",pass)
+ end
+
+ 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
+ local sequence = sequences[i]
+ local kind = sequence.type
+ local steps = sequence.steps
+ local features = sequence.features
+ 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 == "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
+
+ if sequences then
+ packthem(sequences)
+ end
+ if sublookups then
+ packthem(sublookups)
+ end
+
+ end
+
+ end
+
+ end
+end
+
+local unpacked_mt = {
+ __index =
+ function(t,k)
+ t[k] = false
+ return k -- next time true
+ end
+}
+
+function readers.unpack(data)
+
+ if data then
+ local tables = data.tables
+ if tables then
+ local resources = data.resources
+ local descriptions = data.descriptions or data.glyphs
+ local sequences = resources.sequences
+ 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
+ local tv = tables[description.boundingbox]
+ if tv then
+ description.boundingbox = tv
+ end
+ local math = description.math
+ if math then
+ local kerns = math.kerns
+ if kerns then
+ local tm = tables[kerns]
+ if tm then
+ math.kerns = tm
+ kerns = unpacked[tm]
+ end
+ if kerns then
+ for k, kern in next, kerns do
+ local tv = tables[kern]
+ if tv then
+ kerns[k] = tv
+ end
+ end
+ end
+ end
+ end
+ -- if palettes then
+ -- local color = description.color
+ -- if color then
+ -- for i=1,#color do
+ -- local tv = tables[color[i]]
+ -- if tv then
+ -- color[i] = tv
+ -- end
+ -- end
+ -- end
+ -- 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]
+ local kind = sequence.type
+ local steps = sequence.steps
+ local order = sequence.order
+ 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 == "pair" then
+ for g1, d1 in next, c do
+ local tv = tables[d1]
+ if tv then
+ c[g1] = tv
+ d1 = tv
+ end
+ for g2, d2 in next, d1 do
+ local tv = tables[d2]
+ if tv then
+ d1[g2] = tv
+ d2 = tv
+ end
+ local f = tables[d2[1]] if f then d2[1] = f end
+ 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 == "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
+ end
+ elseif kind == "gpos_mark2base" or kind == "gpos_mark2mark" then
+ local c = step.baseclasses
+ if c then
+ for g1, d1 in next, c do
+ for g2, d2 in next, d1 do
+ local tv = tables[d2]
+ if tv then
+ d1[g2] = tv
+ end
+ end
+ end
+ end
+ local c = step.coverage
+ if c then
+ for g1, d1 in next, c do
+ local tv = tables[d1[2]]
+ if tv then
+ d1[2] = tv
+ end
+ end
+ end
+ elseif kind == "gpos_mark2ligature" then
+ local c = step.baseclasses
+ if c then
+ 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
+ d2[g3] = tv
+ end
+ end
+ end
+ end
+ end
+ local c = step.coverage
+ if c then
+ for g1, d1 in next, c do
+ local tv = tables[d1[2]]
+ if tv then
+ d1[2] = tv
+ end
+ end
+ end
+ end
+ local rules = step.rules
+ if rules then
+ for i=1,#rules do
+ local rule = rules[i]
+ local before = rule.before
+ if before then
+ local tv = tables[before]
+ if tv then
+ rule.before = tv
+ before = tv
+ end
+ for i=1,#before do
+ local tv = tables[before[i]]
+ if tv then
+ 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
+ local tv = tables[after]
+ if tv then
+ rule.after = tv
+ after = tv
+ end
+ for i=1,#after do
+ local tv = tables[after[i]]
+ if tv then
+ 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
+ local tv = tables[current]
+ if tv then
+ rule.current = tv
+ current = tv
+ end
+ for i=1,#current do
+ local tv = tables[current[i]]
+ if tv then
+ 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 replacements = rule.replacements
+ if replacements then
+ local tv = tables[replacements]
+ if tv then
+ rule.replacements = tv
+ end
+ end
+ end
+ end
+ end
+ end
+ if order then
+ local tv = tables[order]
+ if tv then
+ sequence.order = tv
+ end
+ end
+ if flags then
+ local tv = tables[flags]
+ if tv then
+ sequence.flags = tv
+ end
+ end
+ end
+ end
+
+ if sequences then
+ unpackthem(sequences)
+ end
+
+ if sublookups then
+ unpackthem(sublookups)
+ end
+
+ if features then
+ for k, list in next, features do
+ for feature, spec in next, list do
+ local tv = tables[spec]
+ if tv then
+ list[feature] = tv
+ end
+ end
+ end
+ end
+
+ if palettes then
+ for i=1,#palettes do
+ local p = palettes[i]
+ for j=1,#p do
+ local tv = tables[p[j]]
+ if tv then
+ p[j] = tv
+ end
+ end
+ 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
+end
+
+local mt = {
+ __index = function(t,k) -- maybe set it
+ if k == "height" then
+ local ht = t.boundingbox[4]
+ return ht < 0 and 0 or ht
+ elseif k == "depth" then
+ local dp = -t.boundingbox[2]
+ return dp < 0 and 0 or dp
+ elseif k == "width" then
+ return 0
+ elseif k == "name" then -- or maybe uni*
+ return forcenotdef and ".notdef"
+ end
+ end
+}
+
+local function sameformat(sequence,steps,first,nofsteps,kind)
+ return true
+end
+
+local function mergesteps_1(lookup,strict)
+ local steps = lookup.steps
+ local nofsteps = lookup.nofsteps
+ local first = steps[1]
+ if strict then
+ local f = first.format
+ for i=2,nofsteps do
+ if steps[i].format ~= f then
+ if trace_optimizations then
+ report_optimizations("not merging %a steps of %a lookup %a, different formats",nofsteps,lookup.type,lookup.name)
+ end
+ return 0
+ end
+ end
+ end
+ if trace_optimizations then
+ report_optimizations("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
+ end
+ local target = first.coverage
+ for i=2,nofsteps do
+ 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
+ lookup.nofsteps = 1
+ lookup.merged = true
+ lookup.steps = { first }
+ return nofsteps - 1
+end
+
+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]
+ if strict then
+ local f = first.format
+ for i=2,nofsteps do
+ if steps[i].format ~= f then
+ if trace_optimizations then
+ report_optimizations("not merging %a steps of %a lookup %a, different formats",nofsteps,lookup.type,lookup.name)
+ end
+ return 0
+ end
+ end
+ end
+ if trace_optimizations then
+ report_optimizations("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
+ end
+ local target = first.coverage
+ 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
+ for kk, vv in next, v do
+ if tk[kk] == nil then
+ tk[kk] = vv
+ end
+ end
+ else
+ target[k] = v
+ end
+ end
+ end
+ end
+ lookup.nofsteps = 1
+ 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
+ if trace_optimizations then
+ report_optimizations("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
+ end
+ -- check first
+ local coverage = { }
+ for i=1,nofsteps do
+ 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
+ if trace_optimizations then
+ report_optimizations("quitting merge due to multiple checks")
+ end
+ 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
+ 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
+
+local function nested(old,new)
+ for k, v in next, old do
+ if k == "ligature" then
+ if not new.ligature then
+ new.ligature = v
+ end
+ else
+ local n = new[k]
+ if n then
+ nested(v,n)
+ else
+ new[k] = v
+ end
+ end
+ end
+end
+
+local function mergesteps_4(lookup) -- ligatures
+ local steps = lookup.steps
+ local nofsteps = lookup.nofsteps
+ local first = steps[1]
+ if trace_optimizations then
+ report_optimizations("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
+ end
+ local target = first.coverage
+ 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
+ nested(v,tk)
+ else
+ target[k] = v
+ end
+ end
+ end
+ end
+ lookup.nofsteps = 1
+ lookup.steps = { first }
+ 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]
+ if trace_optimizations then
+ report_optimizations("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
+ end
+ 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 == 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
+ if trace_optimizations then
+ report_optimizations("turning pairs of step %a of %a lookup %a into kerns",i,lookup.type,lookup.name)
+ end
+ local c = { }
+ for g1, d1 in next, coverage do
+ if d1 and d1 ~= true then
+ c[g1] = d1[3]
+ end
+ end
+ 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
+
+ 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
+ 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
+ if trace_optimizations then
+ report_optimizations("turning pairs of step %a of %a lookup %a into kerns",i,lookup.type,lookup.name)
+ end
+ for g1, d1 in next, coverage do
+ local d = { }
+ for g2, d2 in next, d1 do
+ 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 = "move"
+ kerned = kerned + 1
+ end
+ end
+ end
+ 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
+ else
+ data.compacted = true
+ end
+ local resources = data.resources
+ local merged = 0
+ local kerned = 0
+ local allsteps = 0
+ local function compact(what)
+ local lookups = resources[what]
+ if lookups then
+ 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 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
+ if merge_ligatures then
+ merged = merged + mergesteps_4(lookup)
+ end
+ elseif kind == "gpos_single" then
+ 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
+ if merge_pairs then
+ merged = merged + mergesteps_2(lookup)
+ end
+ if compact_pairs then
+ kerned = kerned + checkpairs(lookup)
+ end
+ elseif kind == "gpos_cursive" then
+ if merge_cursives then
+ merged = merged + mergesteps_5(lookup)
+ end
+ elseif kind == "gpos_mark2mark" or kind == "gpos_mark2base" or kind == "gpos_mark2ligature" then
+ 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
+ elseif trace_optimizations then
+ report_optimizations("no lookups in %a",what)
+ end
+ end
+ compact("sequences")
+ compact("sublookups")
+ if trace_optimizations then
+ if merged > 0 then
+ report_optimizations("%i steps of %i removed due to merging",merged,allsteps)
+ end
+ if kerned > 0 then
+ report_optimizations("%i steps of %i steps turned from pairs into kerns",kerned,allsteps)
+ end
+ 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
+ else
+ data.expanded = true
+ end
+ local resources = data.resources
+ local sublookups = resources.sublookups
+ local sequences = resources.sequences -- were one level up
+ local markclasses = resources.markclasses
+ local descriptions = data.descriptions
+ if descriptions then
+ local defaultwidth = resources.defaultwidth or 0
+ local defaultheight = resources.defaultheight or 0
+ local defaultdepth = resources.defaultdepth or 0
+ local basename = trace_markwidth and file.basename(resources.filename)
+ for u, d in next, descriptions do
+ local bb = d.boundingbox
+ local wd = d.width
+ if not wd then
+ -- or bb?
+ d.width = defaultwidth
+ elseif trace_markwidth and wd ~= 0 and d.class == "mark" then
+ report_markwidth("mark %a with width %b found in %a",d.name or "<noname>",wd,basename)
+ end
+ if bb then
+ local ht = bb[4]
+ local dp = -bb[2]
+ if ht == 0 or ht < 0 then
+ -- not set
+ else
+ d.height = ht
+ end
+ if dp == 0 or dp < 0 then
+ -- not set
+ else
+ d.depth = dp
+ end
+ 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
+ for i=1,#sequences do
+ 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
+ if not markclasses then
+ report_warning("missing markclasses")
+ sequence.markclass = false
+ else
+ sequence.markclass = markclasses[markclass]
+ end
+ end
+
+ for i=1,nofsteps do
+ local step = steps[i]
+ local baseclasses = step.baseclasses
+ if baseclasses then
+ local coverage = step.coverage
+ for k, v in next, coverage do
+ v[1] = baseclasses[v[1]] -- slot 1 is a placeholder
+ end
+ elseif kind == "gpos_cursive" then
+ local coverage = step.coverage
+ for k, v in next, coverage do
+ v[1] = coverage -- slot 1 is a placeholder
+ end
+ end
+ local rules = step.rules
+ if rules then
+ 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 currentrule=1,nofrules do
+ local rule = rules[currentrule]
+ 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 then
+ 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
+ end
+ end
+ end
+ if sequence[1] then -- we merge coverage into one
+ sequence.n = #sequence -- tiny speedup
+ local ruledata = {
+ currentrule, -- 1 -- original rule number, only use this for tracing!
+ lookuptype, -- 2
+ sequence, -- 3
+ start, -- 4
+ stop, -- 5
+ lookups, -- 6 (6/7 also signal of what to do)
+ replacements, -- 7
+ subtype, -- 8
+ }
+ --
+ -- 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