diff options
Diffstat (limited to 'Master/texmf-dist/tex/context/base/font-otn.lua')
-rw-r--r-- | Master/texmf-dist/tex/context/base/font-otn.lua | 1865 |
1 files changed, 913 insertions, 952 deletions
diff --git a/Master/texmf-dist/tex/context/base/font-otn.lua b/Master/texmf-dist/tex/context/base/font-otn.lua index d4f89adc6ec..81b3fd267cc 100644 --- a/Master/texmf-dist/tex/context/base/font-otn.lua +++ b/Master/texmf-dist/tex/context/base/font-otn.lua @@ -10,14 +10,6 @@ if not modules then modules = { } end modules ['font-otn'] = { -- much functionality could only be implemented thanks to the husayni font -- of Idris Samawi Hamid to who we dedicate this module. --- I'm in the process of cleaning up the code (which happens in another --- file) so don't rely on things staying the same. - --- some day when we can jit this, we can use more functions - --- we can use more lpegs when lpeg is extended with function args and so --- resolving to unicode does not gain much - -- in retrospect it always looks easy but believe it or not, it took a lot -- of work to get proper open type support done: buggy fonts, fuzzy specs, -- special made testfonts, many skype sessions between taco, idris and me, @@ -32,10 +24,7 @@ if not modules then modules = { } end modules ['font-otn'] = { -- alternative loop quitters -- check cursive and r2l -- find out where ignore-mark-classes went --- remove unused tables --- slide tail (always glue at the end so only needed once -- default features (per language, script) --- cleanup kern(class) code, remove double info -- handle positions (we need example fonts) -- handle gpos_single (we might want an extra width field in glyph nodes because adding kerns might interfere) @@ -111,6 +100,8 @@ results in different tables.</p> -- gpos_context ok -- -- gpos_contextchain ok -- -- +-- todo: contextpos and contextsub and class stuff +-- -- actions: -- -- handler : actions triggered by lookup @@ -120,13 +111,20 @@ results in different tables.</p> -- remark: the 'not implemented yet' variants will be done when we have fonts that use them -- remark: we need to check what to do with discretionaries +-- We used to have independent hashes for lookups but as the tags are unique +-- we now use only one hash. If needed we can have multiple again but in that +-- case I will probably prefix (i.e. rename) the lookups in the cached font file. + local concat, insert, remove = table.concat, table.insert, table.remove local format, gmatch, gsub, find, match, lower, strip = string.format, string.gmatch, string.gsub, string.find, string.match, string.lower, string.strip local type, next, tonumber, tostring = type, next, tonumber, tostring local lpegmatch = lpeg.match +local random = math.random + +local logs, trackers, nodes, attributes = logs, trackers, nodes, attributes -local otf = fonts.otf -local tfm = fonts.tfm +local fonts = fonts +local otf = fonts.handlers.otf local trace_lookups = false trackers.register("otf.lookups", function(v) trace_lookups = v end) local trace_singles = false trackers.register("otf.singles", function(v) trace_singles = v end) @@ -145,6 +143,12 @@ local trace_steps = false trackers.register("otf.steps", function local trace_skips = false trackers.register("otf.skips", function(v) trace_skips = v end) local trace_directions = false trackers.register("otf.directions", function(v) trace_directions = v end) +local report_direct = logs.reporter("fonts","otf direct") +local report_subchain = logs.reporter("fonts","otf subchain") +local report_chain = logs.reporter("fonts","otf chain") +local report_process = logs.reporter("fonts","otf process") +local report_prepare = logs.reporter("fonts","otf prepare") + trackers.register("otf.verbose_chain", function(v) otf.setcontextchain(v and "verbose") end) trackers.register("otf.normal_chain", function(v) otf.setcontextchain(v and "normal") end) @@ -155,83 +159,84 @@ trackers.register("otf.injections","nodes.injections") trackers.register("*otf.sample","otf.steps,otf.actions,otf.analyzing") -local insert_node_after = node.insert_after -local delete_node = nodes.delete -local copy_node = node.copy -local find_node_tail = node.tail or node.slide -local set_attribute = node.set_attribute -local has_attribute = node.has_attribute +local insert_node_after = node.insert_after +local delete_node = nodes.delete +local copy_node = node.copy +local find_node_tail = node.tail or node.slide +local set_attribute = node.set_attribute +local has_attribute = node.has_attribute + +local setmetatableindex = table.setmetatableindex + +local zwnj = 0x200C +local zwj = 0x200D +local wildcard = "*" +local default = "dflt" + +local nodecodes = nodes.nodecodes +local whatcodes = nodes.whatcodes +local glyphcodes = nodes.glyphcodes + +local glyph_code = nodecodes.glyph +local glue_code = nodecodes.glue +local disc_code = nodecodes.disc +local whatsit_code = nodecodes.whatsit -local zwnj = 0x200C -local zwj = 0x200D -local wildcard = "*" -local default = "dflt" +local dir_code = whatcodes.dir +local localpar_code = whatcodes.localpar -local split_at_space = lpeg.splitters[" "] or lpeg.Ct(lpeg.splitat(" ")) -- no trailing or multiple spaces anyway +local ligature_code = glyphcodes.ligature -local glyph = node.id('glyph') -local glue = node.id('glue') -local kern = node.id('kern') -local disc = node.id('disc') -local whatsit = node.id('whatsit') +local privateattribute = attributes.private -local state = attributes.private('state') -local markbase = attributes.private('markbase') -local markmark = attributes.private('markmark') -local markdone = attributes.private('markdone') -local cursbase = attributes.private('cursbase') -local curscurs = attributes.private('curscurs') -local cursdone = attributes.private('cursdone') -local kernpair = attributes.private('kernpair') +local state = privateattribute('state') +local markbase = privateattribute('markbase') +local markmark = privateattribute('markmark') +local markdone = privateattribute('markdone') +local cursbase = privateattribute('cursbase') +local curscurs = privateattribute('curscurs') +local cursdone = privateattribute('cursdone') +local kernpair = privateattribute('kernpair') -local set_mark = nodes.set_mark -local set_cursive = nodes.set_cursive -local set_kern = nodes.set_kern -local set_pair = nodes.set_pair +local injections = nodes.injections +local setmark = injections.setmark +local setcursive = injections.setcursive +local setkern = injections.setkern +local setpair = injections.setpair -local markonce = true -local cursonce = true -local kernonce = true +local markonce = true +local cursonce = true +local kernonce = true -local fontdata = fonts.ids +local fonthashes = fonts.hashes +local fontdata = fonthashes.identifiers -otf.features.process = { } +local otffeatures = fonts.constructors.newfeatures("otf") +local registerotffeature = otffeatures.register + +local onetimemessage = fonts.loggers.onetimemessage -- we share some vars here, after all, we have no nested lookups and -- less code -local tfmdata = false -local otfdata = false -local characters = false -local descriptions = false -local marks = false -local indices = false -local unicodes = false -local currentfont = false -local lookuptable = false -local anchorlookups = false -local handlers = { } -local rlmode = 0 -local featurevalue = false - --- we cheat a bit and assume that a font,attr combination are kind of ranged - -local context_setups = fonts.define.specify.context_setups -local context_numbers = fonts.define.specify.context_numbers -local context_merged = fonts.define.specify.context_merged - --- we cannot optimize with "start = first_character(head)" because then we don't +local tfmdata = false +local characters = false +local descriptions = false +local resources = false +local marks = false +local currentfont = false +local lookuptable = false +local anchorlookups = false +local lookuptypes = false +local handlers = { } +local rlmode = 0 +local featurevalue = false + +-- we cannot optimize with "start = first_glyph(head)" because then we don't -- know which rlmode we're in which messes up cursive handling later on -- -- head is always a whatsit so we can safely assume that head is not changed -local special_attributes = { - init = 1, - medi = 2, - fina = 3, - isol = 4 -} - -- we use this for special testing and documentation local checkstep = (nodes and nodes.tracers and nodes.tracers.steppers.check) or function() end @@ -242,10 +247,11 @@ local function logprocess(...) if trace_steps then registermessage(...) end - logs.report("otf direct",...) + report_direct(...) end + local function logwarning(...) - logs.report("otf direct",...) + report_direct(...) end local function gref(n) @@ -253,9 +259,9 @@ local function gref(n) local description = descriptions[n] local name = description and description.name if name then - return format("U+%04X (%s)",n,name) + return format("U+%05X (%s)",n,name) else - return format("U+%04X",n) + return format("U+%05X",n) end elseif not n then return "<error in tracing>" @@ -263,9 +269,11 @@ local function gref(n) local num, nam = { }, { } for i=1,#n do local ni = n[i] - num[#num+1] = format("U+%04X",ni) - local dni = descriptions[ni] - nam[#num] = (dni and dni.name) or "?" + if tonumber(ni) then -- later we will start at 2 + local di = descriptions[ni] + num[i] = format("U+%05X",ni) + nam[i] = di and di.name or "?" + end end return format("%s (%s)",concat(num," "), concat(nam," ")) end @@ -303,89 +311,77 @@ local function markstoligature(kind,lookupname,start,stop,char) snext.prev = current end start.prev, stop.next = nil, nil - current.char, current.subtype, current.components = char, 2, start + current.char, current.subtype, current.components = char, ligature_code, start return keep end local function toligature(kind,lookupname,start,stop,char,markflag,discfound) -- brr head - if start ~= stop then ---~ if discfound then ---~ local lignode = copy_node(start) ---~ lignode.font = start.font ---~ lignode.char = char ---~ lignode.subtype = 2 ---~ start = node.do_ligature_n(start, stop, lignode) ---~ if start.id == disc then ---~ local prev = start.prev ---~ start = start.next ---~ end - if discfound then - -- print("start->stop",nodes.tosequence(start,stop)) - local lignode = copy_node(start) - lignode.font, lignode.char, lignode.subtype = start.font, char, 2 - local next, prev = stop.next, start.prev - stop.next = nil - lignode = node.do_ligature_n(start, stop, lignode) - prev.next = lignode - if next then - next.prev = lignode - end - lignode.next, lignode.prev = next, prev - start = lignode - -- print("start->end",nodes.tosequence(start)) - else -- start is the ligature - local deletemarks = markflag ~= "mark" - local n = copy_node(start) - local current - current, start = insert_node_after(start,start,n) - local snext = stop.next - current.next = snext - if snext then - snext.prev = current - end - start.prev, stop.next = nil, nil - current.char, current.subtype, current.components = char, 2, start - local head = current - if deletemarks then - if trace_marks then - while start do - if marks[start.char] then - logwarning("%s: remove mark %s",pref(kind,lookupname),gref(start.char)) - end - start = start.next - end - end - else - local i = 0 + if start == stop then + start.char = char + elseif discfound then + -- print("start->stop",nodes.tosequence(start,stop)) + local lignode = copy_node(start) + lignode.font, lignode.char, lignode.subtype = start.font, char, ligature_code + local next, prev = stop.next, start.prev + stop.next = nil + lignode = node.do_ligature_n(start, stop, lignode) + prev.next = lignode + if next then + next.prev = lignode + end + lignode.next, lignode.prev = next, prev + start = lignode + -- print("start->end",nodes.tosequence(start)) + else -- start is the ligature + local deletemarks = markflag ~= "mark" + local n = copy_node(start) + local current + current, start = insert_node_after(start,start,n) + local snext = stop.next + current.next = snext + if snext then + snext.prev = current + end + start.prev, stop.next = nil, nil + current.char, current.subtype, current.components = char, ligature_code, start + local head = current + if deletemarks then + if trace_marks then while start do if marks[start.char] then - set_attribute(start,markdone,i) - if trace_marks then - logwarning("%s: keep mark %s, gets index %s",pref(kind,lookupname),gref(start.char),i) - end - head, current = insert_node_after(head,current,copy_node(start)) - else - i = i + 1 + logwarning("%s: remove mark %s",pref(kind,lookupname),gref(start.char)) end start = start.next end - start = current.next - while start and start.id == glyph do - if marks[start.char] then - set_attribute(start,markdone,i) - if trace_marks then - logwarning("%s: keep mark %s, gets index %s",pref(kind,lookupname),gref(start.char),i) - end - else - break + end + else + local i = 0 + while start do + if marks[start.char] then + set_attribute(start,markdone,i) + if trace_marks then + logwarning("%s: keep mark %s, gets index %s",pref(kind,lookupname),gref(start.char),i) end - start = start.next + head, current = insert_node_after(head,current,copy_node(start)) + else + i = i + 1 end + start = start.next + end + start = current.next + while start and start.id == glyph_code do + if marks[start.char] then + set_attribute(start,markdone,i) + if trace_marks then + logwarning("%s: keep mark %s, gets index %s",pref(kind,lookupname),gref(start.char),i) + end + else + break + end + start = start.next end - return head end - else - start.char = char + return head end return start end @@ -401,7 +397,7 @@ end local function alternative_glyph(start,alternatives,kind,chainname,chainlookupname,lookupname) -- chainname and chainlookupname optional local value, choice, n = featurevalue or tfmdata.shared.features[kind], nil, #alternatives -- global value, brrr if value == "random" then - local r = math.random(1,n) + local r = random(1,n) value, choice = format("random, choice %s",r), alternatives[r] elseif value == "first" then value, choice = format("first, choice %s",1), alternatives[1] @@ -428,6 +424,33 @@ local function alternative_glyph(start,alternatives,kind,chainname,chainlookupna return choice, value end +local function multiple_glyphs(start,multiple) + local nofmultiples = #multiple + if nofmultiples > 0 then + start.char = multiple[1] + if nofmultiples > 1 then + local sn = start.next + for k=2,nofmultiples do -- todo: use insert_node + local n = copy_node(start) + n.char = multiple[k] + n.next = sn + n.prev = start + if sn then + sn.prev = n + end + start.next = n + start = n + end + end + return start, true + else + if trace_multiples then + logprocess("no multiple for %s",gref(start.char)) + end + return start, false + end +end + function handlers.gsub_alternate(start,kind,lookupname,alternative,sequence) local choice, index = alternative_glyph(start,alternative,kind,lookupname) if trace_alternatives then @@ -441,41 +464,21 @@ function handlers.gsub_multiple(start,kind,lookupname,multiple) if trace_multiples then logprocess("%s: replacing %s by multiple %s",pref(kind,lookupname),gref(start.char),gref(multiple)) end - start.char = multiple[1] - if #multiple > 1 then - for k=2,#multiple do - local n = copy_node(start) - n.char = multiple[k] - local sn = start.next - n.next = sn - n.prev = start - if sn then - sn.prev = n - end - start.next = n - start = n - end - end - return start, true + return multiple_glyphs(start,multiple) end -function handlers.gsub_ligature(start,kind,lookupname,ligature,sequence) --or maybe pass lookup ref +function handlers.gsub_ligature(start,kind,lookupname,ligature,sequence) local s, stop, discfound = start.next, nil, false local startchar = start.char if marks[startchar] then while s do local id = s.id - if id == glyph and s.subtype<256 then - if s.font == currentfont then - local char = s.char - local lg = ligature[1][char] - if not lg then - break - else - stop = s - ligature = lg - s = s.next - end + if id == glyph_code and s.subtype<256 and s.font == currentfont then + local lg = ligature[s.char] + if lg then + stop = s + ligature = lg + s = s.next else break end @@ -483,54 +486,64 @@ function handlers.gsub_ligature(start,kind,lookupname,ligature,sequence) --or ma break end end - if stop and ligature[2] then - if trace_ligatures then - local stopchar = stop.char - start = markstoligature(kind,lookupname,start,stop,ligature[2]) - logprocess("%s: replacing %s upto %s by ligature %s",pref(kind,lookupname),gref(startchar),gref(stopchar),gref(start.char)) + if stop then + local lig = ligature.ligature + if lig then + if trace_ligatures then + local stopchar = stop.char + start = markstoligature(kind,lookupname,start,stop,lig) + logprocess("%s: replacing %s upto %s by ligature %s",pref(kind,lookupname),gref(startchar),gref(stopchar),gref(start.char)) + else + start = markstoligature(kind,lookupname,start,stop,lig) + end + return start, true else - start = markstoligature(kind,lookupname,start,stop,ligature[2]) + -- ok, goto next lookup end - return start, true end else local skipmark = sequence.flags[1] while s do local id = s.id - if id == glyph and s.subtype<256 then + if id == glyph_code and s.subtype<256 then if s.font == currentfont then local char = s.char if skipmark and marks[char] then s = s.next else - local lg = ligature[1][char] - if not lg then - break - else + local lg = ligature[char] + if lg then stop = s ligature = lg s = s.next + else + break end end else break end - elseif id == disc then + elseif id == disc_code then discfound = true s = s.next else break end end - if stop and ligature[2] then - if trace_ligatures then - local stopchar = stop.char - start = toligature(kind,lookupname,start,stop,ligature[2],skipmark,discfound) - logprocess("%s: replacing %s upto %s by ligature %s",pref(kind,lookupname),gref(startchar),gref(stopchar),gref(start.char)) + if stop then + local lig = ligature.ligature + if lig then + if trace_ligatures then + local stopchar = stop.char + start = toligature(kind,lookupname,start,stop,lig,skipmark,discfound) + logprocess("%s: replacing %s upto %s by ligature %s",pref(kind,lookupname),gref(startchar),gref(stopchar),gref(start.char)) + else + start = toligature(kind,lookupname,start,stop,lig,skipmark,discfound) + end + return start, true else - start = toligature(kind,lookupname,start,stop,ligature[2],skipmark,discfound) + -- ok, goto next lookup end - return start, true end end return start, false @@ -545,12 +558,12 @@ function handlers.gpos_mark2base(start,kind,lookupname,markanchors,sequence) local markchar = start.char if marks[markchar] then local base = start.prev -- [glyph] [start=mark] - if base and base.id == glyph and base.subtype<256 and base.font == currentfont then + if base and base.id == glyph_code and base.subtype<256 and base.font == currentfont then local basechar = base.char if marks[basechar] then while true do base = base.prev - if base and base.id == glyph and base.subtype<256 and base.font == currentfont then + if base and base.id == glyph_code and base.subtype<256 and base.font == currentfont then basechar = base.char if not marks[basechar] then break @@ -575,7 +588,7 @@ function handlers.gpos_mark2base(start,kind,lookupname,markanchors,sequence) if al[anchor] then local ma = markanchors[anchor] if ma then - local dx, dy, bound = set_mark(start,base,tfmdata.factor,rlmode,ba,ma) + local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma) if trace_marks then logprocess("%s, anchor %s, bound %s: anchoring mark %s to basechar %s => (%s,%s)", pref(kind,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy) @@ -590,7 +603,7 @@ function handlers.gpos_mark2base(start,kind,lookupname,markanchors,sequence) end else -- if trace_bugs then -- logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(basechar)) - fonts.register_message(currentfont,basechar,"no base anchors") + onetimemessage(currentfont,basechar,"no base anchors",report_fonts) end elseif trace_bugs then logwarning("%s: prev node is no char",pref(kind,lookupname)) @@ -607,13 +620,13 @@ function handlers.gpos_mark2ligature(start,kind,lookupname,markanchors,sequence) if marks[markchar] then local base = start.prev -- [glyph] [optional marks] [start=mark] local index = 1 - if base and base.id == glyph and base.subtype<256 and base.font == currentfont then + if base and base.id == glyph_code and base.subtype<256 and base.font == currentfont then local basechar = base.char if marks[basechar] then index = index + 1 while true do base = base.prev - if base and base.id == glyph and base.subtype<256 and base.font == currentfont then + if base and base.id == glyph_code and base.subtype<256 and base.font == currentfont then basechar = base.char if marks[basechar] then index = index + 1 @@ -643,7 +656,7 @@ function handlers.gpos_mark2ligature(start,kind,lookupname,markanchors,sequence) if ma then ba = ba[index] if ba then - local dx, dy, bound = set_mark(start,base,tfmdata.factor,rlmode,ba,ma,index) + local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,index) if trace_marks then logprocess("%s, anchor %s, index %s, bound %s: anchoring mark %s to baselig %s at index %s => (%s,%s)", pref(kind,lookupname),anchor,index,bound,gref(markchar),gref(basechar),index,dx,dy) @@ -660,7 +673,7 @@ function handlers.gpos_mark2ligature(start,kind,lookupname,markanchors,sequence) end else -- if trace_bugs then -- logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(basechar)) - fonts.register_message(currentfont,basechar,"no base anchors") + onetimemessage(currentfont,basechar,"no base anchors",report_fonts) end elseif trace_bugs then logwarning("%s: prev node is no char",pref(kind,lookupname)) @@ -677,7 +690,7 @@ function handlers.gpos_mark2mark(start,kind,lookupname,markanchors,sequence) --~ local alreadydone = markonce and has_attribute(start,markmark) --~ if not alreadydone then local base = start.prev -- [glyph] [basemark] [start=mark] - if base and base.id == glyph and base.subtype<256 and base.font == currentfont then -- subtype test can go + if base and base.id == glyph_code and base.subtype<256 and base.font == currentfont then -- subtype test can go local basechar = base.char local baseanchors = descriptions[basechar] if baseanchors then @@ -690,7 +703,7 @@ function handlers.gpos_mark2mark(start,kind,lookupname,markanchors,sequence) if al[anchor] then local ma = markanchors[anchor] if ma then - local dx, dy, bound = set_mark(start,base,tfmdata.factor,rlmode,ba,ma) + local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma) if trace_marks then logprocess("%s, anchor %s, bound %s: anchoring mark %s to basemark %s => (%s,%s)", pref(kind,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy) @@ -706,7 +719,7 @@ function handlers.gpos_mark2mark(start,kind,lookupname,markanchors,sequence) end else -- if trace_bugs then -- logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(basechar)) - fonts.register_message(currentfont,basechar,"no base anchors") + onetimemessage(currentfont,basechar,"no base anchors",report_fonts) end elseif trace_bugs then logwarning("%s: prev node is no mark",pref(kind,lookupname)) @@ -731,7 +744,7 @@ function handlers.gpos_cursive(start,kind,lookupname,exitanchors,sequence) -- to end else local nxt = start.next - while not done and nxt and nxt.id == glyph and nxt.subtype<256 and nxt.font == currentfont do + while not done and nxt and nxt.id == glyph_code and nxt.subtype<256 and nxt.font == currentfont do local nextchar = nxt.char if marks[nextchar] then -- should not happen (maybe warning) @@ -748,7 +761,7 @@ function handlers.gpos_cursive(start,kind,lookupname,exitanchors,sequence) -- to if al[anchor] then local exit = exitanchors[anchor] if exit then - local dx, dy, bound = set_cursive(start,nxt,tfmdata.factor,rlmode,exit,entry,characters[startchar],characters[nextchar]) + local dx, dy, bound = setcursive(start,nxt,tfmdata.parameters.factor,rlmode,exit,entry,characters[startchar],characters[nextchar]) if trace_cursive then logprocess("%s: moving %s to %s cursive (%s,%s) using anchor %s and bound %s in rlmode %s",pref(kind,lookupname),gref(startchar),gref(nextchar),dx,dy,anchor,bound,rlmode) end @@ -761,7 +774,7 @@ function handlers.gpos_cursive(start,kind,lookupname,exitanchors,sequence) -- to end else -- if trace_bugs then -- logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(startchar)) - fonts.register_message(currentfont,startchar,"no entry anchors") + onetimemessage(currentfont,startchar,"no entry anchors",report_fonts) end break end @@ -778,7 +791,7 @@ end function handlers.gpos_single(start,kind,lookupname,kerns,sequence) local startchar = start.char - local dx, dy, w, h = set_pair(start,tfmdata.factor,rlmode,sequence.flags[4],kerns,characters[startchar]) + local dx, dy, w, h = setpair(start,tfmdata.parameters.factor,rlmode,sequence.flags[4],kerns,characters[startchar]) if trace_kerns then logprocess("%s: shifting single %s by (%s,%s) and correction (%s,%s)",pref(kind,lookupname),gref(startchar),dx,dy,w,h) end @@ -793,10 +806,11 @@ function handlers.gpos_pair(start,kind,lookupname,kerns,sequence) return start, false else local prev, done = start, false - local factor = tfmdata.factor - while snext and snext.id == glyph and snext.subtype<256 and snext.font == currentfont do + local factor = tfmdata.parameters.factor + local lookuptype = lookuptypes[lookupname] + while snext and snext.id == glyph_code and snext.subtype<256 and snext.font == currentfont do local nextchar = snext.char -local krn = kerns[nextchar] + local krn = kerns[nextchar] if not krn and marks[nextchar] then prev = snext snext = snext.next @@ -805,38 +819,38 @@ local krn = kerns[nextchar] if not krn then -- skip elseif type(krn) == "table" then - if krn[1] == "pair" then - local a, b = krn[3], krn[4] + if lookuptype == "pair" then -- probably not needed + local a, b = krn[2], krn[3] if a and #a > 0 then local startchar = start.char - local x, y, w, h = set_pair(start,factor,rlmode,sequence.flags[4],a,characters[startchar]) + local x, y, w, h = setpair(start,factor,rlmode,sequence.flags[4],a,characters[startchar]) if trace_kerns then logprocess("%s: shifting first of pair %s and %s by (%s,%s) and correction (%s,%s)",pref(kind,lookupname),gref(startchar),gref(nextchar),x,y,w,h) end end if b and #b > 0 then local startchar = start.char - local x, y, w, h = set_pair(snext,factor,rlmode,sequence.flags[4],b,characters[nextchar]) + local x, y, w, h = setpair(snext,factor,rlmode,sequence.flags[4],b,characters[nextchar]) if trace_kerns then logprocess("%s: shifting second of pair %s and %s by (%s,%s) and correction (%s,%s)",pref(kind,lookupname),gref(startchar),gref(nextchar),x,y,w,h) end end - else - logs.report("%s: check this out (old kern stuff)",pref(kind,lookupname)) - local a, b = krn[3], krn[7] - if a and a ~= 0 then - local k = set_kern(snext,factor,rlmode,a) - if trace_kerns then - logprocess("%s: inserting first kern %s between %s and %s",pref(kind,lookupname),k,gref(prev.char),gref(nextchar)) - end - end - if b and b ~= 0 then - logwarning("%s: ignoring second kern xoff %s",pref(kind,lookupname),b*factor) - end + else -- wrong ... position has different entries + report_process("%s: check this out (old kern stuff)",pref(kind,lookupname)) + -- local a, b = krn[2], krn[6] + -- if a and a ~= 0 then + -- local k = setkern(snext,factor,rlmode,a) + -- if trace_kerns then + -- logprocess("%s: inserting first kern %s between %s and %s",pref(kind,lookupname),k,gref(prev.char),gref(nextchar)) + -- end + -- end + -- if b and b ~= 0 then + -- logwarning("%s: ignoring second kern xoff %s",pref(kind,lookupname),b*factor) + -- end end done = true elseif krn ~= 0 then - local k = set_kern(snext,factor,rlmode,krn) + local k = setkern(snext,factor,rlmode,krn) if trace_kerns then logprocess("%s: inserting kern %s between %s and %s",pref(kind,lookupname),k,gref(prev.char),gref(nextchar)) end @@ -861,59 +875,51 @@ local function logprocess(...) if trace_steps then registermessage(...) end - logs.report("otf subchain",...) -end -local function logwarning(...) - logs.report("otf subchain",...) + report_subchain(...) end --- ['coverage']={ --- ['after']={ "r" }, --- ['before']={ "q" }, --- ['current']={ "a", "b", "c" }, --- }, --- ['lookups']={ "ls_l_1", "ls_l_1", "ls_l_1" }, +local logwarning = report_subchain -function chainmores.chainsub(start,stop,kind,chainname,currentcontext,cache,lookuplist,chainlookupname,n) +function chainmores.chainsub(start,stop,kind,chainname,currentcontext,lookuphash,lookuplist,chainlookupname,n) logprocess("%s: a direct call to chainsub cannot happen",cref(kind,chainname,chainlookupname)) return start, false end -- handled later: -- --- function chainmores.gsub_single(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,n) --- return chainprocs.gsub_single(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,n) +-- function chainmores.gsub_single(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,n) +-- return chainprocs.gsub_single(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,n) -- end -function chainmores.gsub_multiple(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,n) +function chainmores.gsub_multiple(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,n) logprocess("%s: gsub_multiple not yet supported",cref(kind,chainname,chainlookupname)) return start, false end -function chainmores.gsub_alternate(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,n) + +function chainmores.gsub_alternate(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,n) logprocess("%s: gsub_alternate not yet supported",cref(kind,chainname,chainlookupname)) return start, false end -- handled later: -- --- function chainmores.gsub_ligature(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,n) --- return chainprocs.gsub_ligature(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,n) +-- function chainmores.gsub_ligature(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,n) +-- return chainprocs.gsub_ligature(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,n) -- end local function logprocess(...) if trace_steps then registermessage(...) end - logs.report("otf chain",...) -end -local function logwarning(...) - logs.report("otf chain",...) + report_chain(...) end +local logwarning = report_chain + -- We could share functions but that would lead to extra function calls with many -- arguments, redundant tests and confusing messages. -function chainprocs.chainsub(start,stop,kind,chainname,currentcontext,cache,lookuplist,chainlookupname) +function chainprocs.chainsub(start,stop,kind,chainname,currentcontext,lookuphash,lookuplist,chainlookupname) logwarning("%s: a direct call to chainsub cannot happen",cref(kind,chainname,chainlookupname)) return start, false end @@ -922,7 +928,7 @@ end -- in a bit weird way. There is no lookup and the replacement comes from the lookup -- itself. It is meant mostly for dealing with Urdu. -function chainprocs.reversesub(start,stop,kind,chainname,currentcontext,cache,replacements) +function chainprocs.reversesub(start,stop,kind,chainname,currentcontext,lookuphash,replacements) local char = start.char local replacement = replacements[char] if replacement then @@ -955,11 +961,10 @@ as less as needed but that would also mke the code even more messy.</p> local function delete_till_stop(start,stop,ignoremarks) if start ~= stop then -- todo keep marks - local done = false - while not done do - done = start == stop - delete_node(start,start.next) - end + repeat + local next = start.next + delete_node(start,next) + until next == stop end end @@ -968,18 +973,22 @@ end match.</p> --ldx]]-- -function chainprocs.gsub_single(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,chainindex) +function chainprocs.gsub_single(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex) -- todo: marks ? - if not chainindex then - delete_till_stop(start,stop) -- ,currentlookup.flags[1]) - end +--~ if not chainindex then +--~ delete_till_stop(start,stop) -- ,currentlookup.flags[1] +--~ stop = start +--~ end local current = start local subtables = currentlookup.subtables + if #subtables > 1 then + logwarning("todo: check if we need to loop over the replacements: %s",concat(subtables," ")) + end while current do - if current.id == glyph then + if current.id == glyph_code then local currentchar = current.char - local lookupname = subtables[1] - local replacement = cache.gsub_single[lookupname] + local lookupname = subtables[1] -- only 1 + local replacement = lookuphash[lookupname] if not replacement then if trace_bugs then logwarning("%s: no single hits",cref(kind,chainname,chainlookupname,lookupname,chainindex)) @@ -1014,12 +1023,12 @@ chainmores.gsub_single = chainprocs.gsub_single the match.</p> --ldx]]-- -function chainprocs.gsub_multiple(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname) +function chainprocs.gsub_multiple(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) delete_till_stop(start,stop) local startchar = start.char local subtables = currentlookup.subtables local lookupname = subtables[1] - local replacements = cache.gsub_multiple[lookupname] + local replacements = lookuphash[lookupname] if not replacements then if trace_bugs then logwarning("%s: no multiple hits",cref(kind,chainname,chainlookupname,lookupname)) @@ -1034,21 +1043,7 @@ function chainprocs.gsub_multiple(start,stop,kind,chainname,currentcontext,cache if trace_multiples then logprocess("%s: replacing %s by multiple characters %s",cref(kind,chainname,chainlookupname,lookupname),gref(startchar),gref(replacements)) end - local sn = start.next - for k=1,#replacements do - if k == 1 then - start.char = replacements[k] - else - local n = copy_node(start) -- maybe delete the components and such - n.char = replacements[k] - n.next, n.prev = sn, start - if sn then - sn.prev = n - end - start.next, start = n, n - end - end - return start, true + return multiple_glyphs(start,replacements) end end return start, false @@ -1058,16 +1053,16 @@ end <p>Here we replace start by new glyph. First we delete the rest of the match.</p> --ldx]]-- -function chainprocs.gsub_alternate(start,stop,kind,lookupname,currentcontext,cache,currentlookup) +function chainprocs.gsub_alternate(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) -- todo: marks ? delete_till_stop(start,stop) local current = start local subtables = currentlookup.subtables while current do - if current.id == glyph then + if current.id == glyph_code then local currentchar = current.char local lookupname = subtables[1] - local alternatives = cache.gsub_alternate[lookupname] + local alternatives = lookuphash[lookupname] if not alternatives then if trace_bugs then logwarning("%s: no alternative hits",cref(kind,chainname,chainlookupname,lookupname)) @@ -1102,11 +1097,11 @@ this function (move code inline and handle the marks by a separate function). We assume rather stupid ligatures (no complex disc nodes).</p> --ldx]]-- -function chainprocs.gsub_ligature(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,chainindex) +function chainprocs.gsub_ligature(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex) local startchar = start.char local subtables = currentlookup.subtables local lookupname = subtables[1] - local ligatures = cache.gsub_ligature[lookupname] + local ligatures = lookuphash[lookupname] if not ligatures then if trace_bugs then logwarning("%s: no ligature hits",cref(kind,chainname,chainlookupname,lookupname,chainindex)) @@ -1121,7 +1116,7 @@ function chainprocs.gsub_ligature(start,stop,kind,chainname,currentcontext,cache local s, discfound, last, nofreplacements = start.next, false, stop, 0 while s do local id = s.id - if id == disc then + if id == disc_code then s = s.next discfound = true else @@ -1129,21 +1124,21 @@ function chainprocs.gsub_ligature(start,stop,kind,chainname,currentcontext,cache if marks[schar] then -- marks s = s.next else - local lg = ligatures[1][schar] - if not lg then - break - else + local lg = ligatures[schar] + if lg then ligatures, last, nofreplacements = lg, s, nofreplacements + 1 if s == stop then break else s = s.next end + else + break end end end end - local l2 = ligatures[2] + local l2 = ligatures.ligature if l2 then if chainindex then stop = last @@ -1155,7 +1150,7 @@ function chainprocs.gsub_ligature(start,stop,kind,chainname,currentcontext,cache logprocess("%s: replacing character %s upto %s by ligature %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(stop.char),gref(l2)) end end - start = toligature(kind,lookup,start,stop,l2,currentlookup.flags[1],discfound) + start = toligature(kind,lookupname,start,stop,l2,currentlookup.flags[1],discfound) return start, true, nofreplacements elseif trace_bugs then if start == stop then @@ -1171,23 +1166,23 @@ end chainmores.gsub_ligature = chainprocs.gsub_ligature -function chainprocs.gpos_mark2base(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname) +function chainprocs.gpos_mark2base(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) local markchar = start.char if marks[markchar] then local subtables = currentlookup.subtables local lookupname = subtables[1] - local markanchors = cache.gpos_mark2base[lookupname] + local markanchors = lookuphash[lookupname] if markanchors then markanchors = markanchors[markchar] end if markanchors then local base = start.prev -- [glyph] [start=mark] - if base and base.id == glyph and base.subtype<256 and base.font == currentfont then + if base and base.id == glyph_code and base.subtype<256 and base.font == currentfont then local basechar = base.char if marks[basechar] then while true do base = base.prev - if base and base.id == glyph and base.subtype<256 and base.font == currentfont then + if base and base.id == glyph_code and base.subtype<256 and base.font == currentfont then basechar = base.char if not marks[basechar] then break @@ -1209,7 +1204,7 @@ function chainprocs.gpos_mark2base(start,stop,kind,chainname,currentcontext,cach if al[anchor] then local ma = markanchors[anchor] if ma then - local dx, dy, bound = set_mark(start,base,tfmdata.factor,rlmode,ba,ma) + local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma) if trace_marks then logprocess("%s, anchor %s, bound %s: anchoring mark %s to basechar %s => (%s,%s)", cref(kind,chainname,chainlookupname,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy) @@ -1235,25 +1230,25 @@ function chainprocs.gpos_mark2base(start,stop,kind,chainname,currentcontext,cach return start, false end -function chainprocs.gpos_mark2ligature(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname) +function chainprocs.gpos_mark2ligature(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) local markchar = start.char if marks[markchar] then local subtables = currentlookup.subtables local lookupname = subtables[1] - local markanchors = cache.gpos_mark2ligature[lookupname] + local markanchors = lookuphash[lookupname] if markanchors then markanchors = markanchors[markchar] end if markanchors then local base = start.prev -- [glyph] [optional marks] [start=mark] local index = 1 - if base and base.id == glyph and base.subtype<256 and base.font == currentfont then + if base and base.id == glyph_code and base.subtype<256 and base.font == currentfont then local basechar = base.char if marks[basechar] then index = index + 1 while true do base = base.prev - if base and base.id == glyph and base.subtype<256 and base.font == currentfont then + if base and base.id == glyph_code and base.subtype<256 and base.font == currentfont then basechar = base.char if marks[basechar] then index = index + 1 @@ -1282,7 +1277,7 @@ function chainprocs.gpos_mark2ligature(start,stop,kind,chainname,currentcontext, if ma then ba = ba[index] if ba then - local dx, dy, bound = set_mark(start,base,tfmdata.factor,rlmode,ba,ma,index) + local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,index) if trace_marks then logprocess("%s, anchor %s, bound %s: anchoring mark %s to baselig %s at index %s => (%s,%s)", cref(kind,chainname,chainlookupname,lookupname),anchor,a or bound,gref(markchar),gref(basechar),index,dx,dy) @@ -1309,7 +1304,7 @@ function chainprocs.gpos_mark2ligature(start,stop,kind,chainname,currentcontext, return start, false end -function chainprocs.gpos_mark2mark(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname) +function chainprocs.gpos_mark2mark(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) local markchar = start.char if marks[markchar] then --~ local alreadydone = markonce and has_attribute(start,markmark) @@ -1317,13 +1312,13 @@ function chainprocs.gpos_mark2mark(start,stop,kind,chainname,currentcontext,cach -- local markanchors = descriptions[markchar].anchors markanchors = markanchors and markanchors.mark local subtables = currentlookup.subtables local lookupname = subtables[1] - local markanchors = cache.gpos_mark2mark[lookupname] + local markanchors = lookuphash[lookupname] if markanchors then markanchors = markanchors[markchar] end if markanchors then local base = start.prev -- [glyph] [basemark] [start=mark] - if base and base.id == glyph and base.subtype<256 and base.font == currentfont then -- subtype test can go + if base and base.id == glyph_code and base.subtype<256 and base.font == currentfont then -- subtype test can go local basechar = base.char local baseanchors = descriptions[basechar].anchors if baseanchors then @@ -1334,7 +1329,7 @@ function chainprocs.gpos_mark2mark(start,stop,kind,chainname,currentcontext,cach if al[anchor] then local ma = markanchors[anchor] if ma then - local dx, dy, bound = set_mark(start,base,tfmdata.factor,rlmode,ba,ma) + local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma) if trace_marks then logprocess("%s, anchor %s, bound %s: anchoring mark %s to basemark %s => (%s,%s)", cref(kind,chainname,chainlookupname,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy) @@ -1365,13 +1360,13 @@ end -- ! ! ! untested ! ! ! -function chainprocs.gpos_cursive(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname) +function chainprocs.gpos_cursive(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) local alreadydone = cursonce and has_attribute(start,cursbase) if not alreadydone then local startchar = start.char local subtables = currentlookup.subtables local lookupname = subtables[1] - local exitanchors = cache.gpos_cursive[lookupname] + local exitanchors = lookuphash[lookupname] if exitanchors then exitanchors = exitanchors[startchar] end @@ -1383,7 +1378,7 @@ function chainprocs.gpos_cursive(start,stop,kind,chainname,currentcontext,cache, end else local nxt = start.next - while not done and nxt and nxt.id == glyph and nxt.subtype<256 and nxt.font == currentfont do + while not done and nxt and nxt.id == glyph_code and nxt.subtype<256 and nxt.font == currentfont do local nextchar = nxt.char if marks[nextchar] then -- should not happen (maybe warning) @@ -1400,7 +1395,7 @@ function chainprocs.gpos_cursive(start,stop,kind,chainname,currentcontext,cache, if al[anchor] then local exit = exitanchors[anchor] if exit then - local dx, dy, bound = set_cursive(start,nxt,tfmdata.factor,rlmode,exit,entry,characters[startchar],characters[nextchar]) + local dx, dy, bound = setcursive(start,nxt,tfmdata.parameters.factor,rlmode,exit,entry,characters[startchar],characters[nextchar]) if trace_cursive then logprocess("%s: moving %s to %s cursive (%s,%s) using anchor %s and bound %s in rlmode %s",pref(kind,lookupname),gref(startchar),gref(nextchar),dx,dy,anchor,bound,rlmode) end @@ -1413,7 +1408,7 @@ function chainprocs.gpos_cursive(start,stop,kind,chainname,currentcontext,cache, end else -- if trace_bugs then -- logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(startchar)) - fonts.register_message(currentfont,startchar,"no entry anchors") + onetimemessage(currentfont,startchar,"no entry anchors",report_fonts) end break end @@ -1430,16 +1425,16 @@ function chainprocs.gpos_cursive(start,stop,kind,chainname,currentcontext,cache, return start, false end -function chainprocs.gpos_single(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,chainindex,sequence) - -- untested +function chainprocs.gpos_single(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex,sequence) + -- untested .. needs checking for the new model local startchar = start.char local subtables = currentlookup.subtables local lookupname = subtables[1] - local kerns = cache.gpos_single[lookupname] + local kerns = lookuphash[lookupname] if kerns then - kerns = kerns[startchar] + kerns = kerns[startchar] -- needed ? if kerns then - local dx, dy, w, h = set_pair(start,tfmdata.factor,rlmode,sequence.flags[4],kerns,characters[startchar]) + local dx, dy, w, h = setpair(start,tfmdata.parameters.factor,rlmode,sequence.flags[4],kerns,characters[startchar]) if trace_kerns then logprocess("%s: shifting single %s by (%s,%s) and correction (%s,%s)",cref(kind,chainname,chainlookupname),gref(startchar),dx,dy,w,h) end @@ -1450,20 +1445,21 @@ end -- when machines become faster i will make a shared function -function chainprocs.gpos_pair(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,chainindex,sequence) +function chainprocs.gpos_pair(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex,sequence) -- logwarning("%s: gpos_pair not yet supported",cref(kind,chainname,chainlookupname)) local snext = start.next if snext then local startchar = start.char local subtables = currentlookup.subtables local lookupname = subtables[1] - local kerns = cache.gpos_pair[lookupname] + local kerns = lookuphash[lookupname] if kerns then kerns = kerns[startchar] if kerns then + local lookuptype = lookuptypes[lookupname] local prev, done = start, false - local factor = tfmdata.factor - while snext and snext.id == glyph and snext.subtype<256 and snext.font == currentfont do + local factor = tfmdata.parameters.factor + while snext and snext.id == glyph_code and snext.subtype<256 and snext.font == currentfont do local nextchar = snext.char local krn = kerns[nextchar] if not krn and marks[nextchar] then @@ -1473,27 +1469,27 @@ function chainprocs.gpos_pair(start,stop,kind,chainname,currentcontext,cache,cur if not krn then -- skip elseif type(krn) == "table" then - if krn[1] == "pair" then - local a, b = krn[3], krn[4] + if lookuptype == "pair" then + local a, b = krn[2], krn[3] if a and #a > 0 then local startchar = start.char - local x, y, w, h = set_pair(start,factor,rlmode,sequence.flags[4],a,characters[startchar]) + local x, y, w, h = setpair(start,factor,rlmode,sequence.flags[4],a,characters[startchar]) if trace_kerns then logprocess("%s: shifting first of pair %s and %s by (%s,%s) and correction (%s,%s)",cref(kind,chainname,chainlookupname),gref(startchar),gref(nextchar),x,y,w,h) end end if b and #b > 0 then local startchar = start.char - local x, y, w, h = set_pair(snext,factor,rlmode,sequence.flags[4],b,characters[nextchar]) + local x, y, w, h = setpair(snext,factor,rlmode,sequence.flags[4],b,characters[nextchar]) if trace_kerns then logprocess("%s: shifting second of pair %s and %s by (%s,%s) and correction (%s,%s)",cref(kind,chainname,chainlookupname),gref(startchar),gref(nextchar),x,y,w,h) end end else - logs.report("%s: check this out (old kern stuff)",cref(kind,chainname,chainlookupname)) - local a, b = krn[3], krn[7] + report_process("%s: check this out (old kern stuff)",cref(kind,chainname,chainlookupname)) + local a, b = krn[2], krn[6] if a and a ~= 0 then - local k = set_kern(snext,factor,rlmode,a) + local k = setkern(snext,factor,rlmode,a) if trace_kerns then logprocess("%s: inserting first kern %s between %s and %s",cref(kind,chainname,chainlookupname),k,gref(prev.char),gref(nextchar)) end @@ -1504,7 +1500,7 @@ function chainprocs.gpos_pair(start,stop,kind,chainname,currentcontext,cache,cur end done = true elseif krn ~= 0 then - local k = set_kern(snext,factor,rlmode,krn) + local k = setkern(snext,factor,rlmode,krn) if trace_kerns then logprocess("%s: inserting kern %s between %s and %s",cref(kind,chainname,chainlookupname),k,gref(prev.char),gref(nextchar)) end @@ -1536,7 +1532,7 @@ local function show_skip(kind,chainname,char,ck,class) end end -local function normal_handle_contextchain(start,kind,chainname,contexts,sequence,cache) +local function normal_handle_contextchain(start,kind,chainname,contexts,sequence,lookuphash) -- local rule, lookuptype, sequence, f, l, lookups = ck[1], ck[2] ,ck[3], ck[4], ck[5], ck[6] local flags, done = sequence.flags, false local skipmark, skipligature, skipbase = flags[1], flags[2], flags[3] @@ -1551,11 +1547,12 @@ local function normal_handle_contextchain(start,kind,chainname,contexts,sequence -- f..l = mid string if s == 1 then -- never happens - match = current.id == glyph and current.subtype<256 and current.font == currentfont and seq[1][current.char] + match = current.id == glyph_code and current.subtype<256 and current.font == currentfont and seq[1][current.char] else -- todo: better space check (maybe check for glue) local f, l = ck[4], ck[5] - if f == l then + -- current match + if f == 1 and f == l then -- already a hit match = true else @@ -1565,12 +1562,12 @@ local function normal_handle_contextchain(start,kind,chainname,contexts,sequence -- we cannot optimize for n=2 because there can be disc nodes -- if not someskip and n == l then -- -- n=2 and no skips then faster loop - -- match = last and last.id == glyph and last.subtype<256 and last.font == currentfont and seq[n][last.char] + -- match = last and last.id == glyph_code and last.subtype<256 and last.font == currentfont and seq[n][last.char] -- else while n <= l do if last then local id = last.id - if id == glyph then + if id == glyph_code then if last.subtype<256 and last.font == currentfont then local char = last.char local ccd = descriptions[char] @@ -1596,7 +1593,7 @@ local function normal_handle_contextchain(start,kind,chainname,contexts,sequence else match = false break end - elseif id == disc then -- what to do with kerns? + elseif id == disc_code then -- what to do with kerns? last = last.next else match = false break @@ -1607,15 +1604,15 @@ local function normal_handle_contextchain(start,kind,chainname,contexts,sequence end -- end end + -- before if match and f > 1 then - -- before local prev = start.prev if prev then local n = f-1 while n >= 1 do if prev then local id = prev.id - if id == glyph then + if id == glyph_code then if prev.subtype<256 and prev.font == currentfont then -- normal char local char = prev.char local ccd = descriptions[char] @@ -1637,7 +1634,7 @@ local function normal_handle_contextchain(start,kind,chainname,contexts,sequence else match = false break end - elseif id == disc then + elseif id == disc_code then -- skip 'm elseif seq[n][32] then n = n -1 @@ -1645,7 +1642,7 @@ local function normal_handle_contextchain(start,kind,chainname,contexts,sequence match = false break end prev = prev.prev - elseif seq[n][32] then + elseif seq[n][32] then -- somehat special, as zapfino can have many preceding spaces n = n -1 else match = false break @@ -1661,16 +1658,16 @@ local function normal_handle_contextchain(start,kind,chainname,contexts,sequence end end end + -- after if match and s > l then - -- after - local current = last.next + local current = last and last.next if current then -- removed optimization for s-l == 1, we have to deal with marks anyway local n = l + 1 while n <= s do if current then local id = current.id - if id == glyph then + if id == glyph_code then if current.subtype<256 and current.font == currentfont then -- normal char local char = current.char local ccd = descriptions[char] @@ -1692,7 +1689,7 @@ local function normal_handle_contextchain(start,kind,chainname,contexts,sequence else match = false break end - elseif id == disc then + elseif id == disc_code then -- skip 'm elseif seq[n][32] then -- brrr n = n + 1 @@ -1723,9 +1720,11 @@ local function normal_handle_contextchain(start,kind,chainname,contexts,sequence local rule, lookuptype, f, l = ck[1], ck[2], ck[4], ck[5] local char = start.char if ck[9] then - logwarning("%s: rule %s matches at char %s for (%s,%s,%s) chars, lookuptype %s (%s=>%s)",cref(kind,chainname),rule,gref(char),f-1,l-f+1,s-l,lookuptype,ck[9],ck[10]) + logwarning("%s: rule %s matches at char %s for (%s,%s,%s) chars, lookuptype %s (%s=>%s)", + cref(kind,chainname),rule,gref(char),f-1,l-f+1,s-l,lookuptype,ck[9],ck[10]) else - logwarning("%s: rule %s matches at char %s for (%s,%s,%s) chars, lookuptype %s",cref(kind,chainname),rule,gref(char),f-1,l-f+1,s-l,lookuptype) + logwarning("%s: rule %s matches at char %s for (%s,%s,%s) chars, lookuptype %s", + cref(kind,chainname),rule,gref(char),f-1,l-f+1,s-l,lookuptype) end end local chainlookups = ck[6] @@ -1737,59 +1736,35 @@ local function normal_handle_contextchain(start,kind,chainname,contexts,sequence local chainlookup = lookuptable[chainlookupname] local cp = chainprocs[chainlookup.type] if cp then - start, done = cp(start,last,kind,chainname,ck,cache,chainlookup,chainlookupname,nil,sequence) + start, done = cp(start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence) else logprocess("%s: %s is not yet supported",cref(kind,chainname,chainlookupname),chainlookup.type) end else - -- actually this needs a more complex treatment for which we will use chainmores ---~ local i = 1 ---~ repeat ---~ local chainlookupname = chainlookups[i] ---~ local chainlookup = lookuptable[chainlookupname] ---~ local cp = chainmores[chainlookup.type] ---~ if cp then ---~ local ok, n ---~ start, ok, n = cp(start,last,kind,chainname,ck,cache,chainlookup,chainlookupname,i,sequence) ---~ -- messy since last can be changed ! ---~ if ok then ---~ done = true ---~ start = start.next ---~ if n then ---~ -- skip next one(s) if ligature ---~ i = i + n - 1 ---~ end ---~ end ---~ else ---~ logprocess("%s: multiple subchains for %s are not yet supported",cref(kind,chainname,chainlookupname),chainlookup.type) ---~ end ---~ i = i + 1 ---~ until i > nofchainlookups - local i = 1 repeat -if skipped then - while true do - local char = start.char - local ccd = descriptions[char] - if ccd then - local class = ccd.class - if class == skipmark or class == skipligature or class == skipbase or (markclass and class == "mark" and not markclass[char]) then - start = start.next - else - break - end - else - break - end - end -end + if skipped then + while true do + local char = start.char + local ccd = descriptions[char] + if ccd then + local class = ccd.class + if class == skipmark or class == skipligature or class == skipbase or (markclass and class == "mark" and not markclass[char]) then + start = start.next + else + break + end + else + break + end + end + end local chainlookupname = chainlookups[i] local chainlookup = lookuptable[chainlookupname] local cp = chainmores[chainlookup.type] if cp then local ok, n - start, ok, n = cp(start,last,kind,chainname,ck,cache,chainlookup,chainlookupname,i,sequence) + start, ok, n = cp(start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,i,sequence) -- messy since last can be changed ! if ok then done = true @@ -1804,12 +1779,11 @@ end end start = start.next until i > nofchainlookups - end else local replacements = ck[7] if replacements then - start, done = chainprocs.reversesub(start,last,kind,chainname,ck,cache,replacements) -- sequence + start, done = chainprocs.reversesub(start,last,kind,chainname,ck,lookuphash,replacements) -- sequence else done = true -- can be meant to be skipped if trace_contexts then @@ -1864,18 +1838,17 @@ local function logprocess(...) if trace_steps then registermessage(...) end - logs.report("otf process",...) -end -local function logwarning(...) - logs.report("otf process",...) + report_process(...) end +local logwarning = report_process + local function report_missing_cache(typ,lookup) local f = missing[currentfont] if not f then f = { } missing[currentfont] = f end local t = f[typ] if not t then t = { } f[typ] = t end if not t[lookup] then t[lookup] = true - logwarning("missing cache for lookup %s of type %s in font %s (%s)",lookup,typ,currentfont,tfmdata.fullname) + logwarning("missing cache for lookup %s of type %s in font %s (%s)",lookup,typ,currentfont,tfmdata.properties.fullname) end end @@ -1883,135 +1856,144 @@ local resolved = { } -- we only resolve a font,script,language pair once -- todo: pass all these 'locals' in a table -function fonts.methods.node.otf.features(head,font,attr) - if trace_steps then - checkstep(head) +local lookuphashes = { } + +setmetatableindex(lookuphashes, function(t,font) + local lookuphash = fontdata[font].resources.lookuphash + if not lookuphash or not next(lookuphash) then + lookuphash = false end - tfmdata = fontdata[font] - local shared = tfmdata.shared - otfdata = shared.otfdata - local luatex = otfdata.luatex - descriptions = tfmdata.descriptions - characters = tfmdata.characters - indices = tfmdata.indices - unicodes = tfmdata.unicodes - marks = tfmdata.marks - anchorlookups = luatex.lookup_to_anchor - currentfont = font - rlmode = 0 - local featuredata = otfdata.shared.featuredata -- can be made local to closure - local sequences = luatex.sequences - lookuptable = luatex.lookups - local done = false - local script, language, s_enabled, a_enabled, dyn - local attribute_driven = attr and attr ~= 0 - if attribute_driven then - local features = context_setups[context_numbers[attr]] -- could be a direct list - dyn = context_merged[attr] or 0 - language, script = features.language or "dflt", features.script or "dflt" - a_enabled = features -- shared.features -- can be made local to the resolver - if dyn == 2 or dyn == -2 then - -- font based - s_enabled = shared.features + t[font] = lookuphash + return lookuphash +end) + +-- fonts.hashes.lookups = lookuphashes + +local special_attributes = { + init = 1, + medi = 2, + fina = 3, + isol = 4 +} + +local function initialize(sequence,script,language,enabled) + local features = sequence.features + if features then + for kind, scripts in next, features do + local valid = enabled[kind] + if valid then + local languages = scripts[script] or scripts[wildcard] + if languages and (languages[language] or languages[wildcard]) then + return { valid, special_attributes[kind] or false, sequence.chain or 0, kind } + end + end end - else - language, script = tfmdata.language or "dflt", tfmdata.script or "dflt" - s_enabled = shared.features -- can be made local to the resolver - dyn = 0 end - -- we can save some runtime by caching feature tests - local res = resolved[font] if not res then res = { } resolved[font] = res end - local rs = res [script] if not rs then rs = { } res [script] = rs end - local rl = rs [language] if not rl then rl = { } rs [language] = rl end - local ra = rl [attr] if ra == nil then ra = { } rl [attr] = ra end -- attr can be false - -- sequences always > 1 so no need for optimization + return false +end + +function otf.dataset(ftfmdata,sequences,font) -- generic variant, overloaded in context + local shared = tfmdata.shared + local properties = tfmdata.properties + local language = properties.language or "dflt" + local script = properties.script or "dflt" + local enabled = shared.features + local res = resolved[font] + if not res then + res = { } + resolved[font] = res + end + local rs = res[script] + if not rs then + rs = { } + res[script] = rs + end + local rl = rs[language] + if not rl then + rl = { } + rs[language] = rl + setmetatableindex(rl, function(t,k) + local v = enabled and initialize(sequences[k],script,language,enabled) + t[k] = v + return v + end) + end + return rl +end + +local function featuresprocessor(head,font,attr) + + local lookuphash = lookuphashes[font] -- we can also check sequences here + + if not lookuphash then + return head, false + end + + if trace_steps then + checkstep(head) + end + + tfmdata = fontdata[font] + descriptions = tfmdata.descriptions + characters = tfmdata.characters + resources = tfmdata.resources + + marks = resources.marks + anchorlookups = resources.lookup_to_anchor + lookuptable = resources.lookups + lookuptypes = resources.lookuptypes + + currentfont = font + rlmode = 0 + + local sequences = resources.sequences + local done = false + local datasets = otf.dataset(tfmdata,sequences,font,attr) + for s=1,#sequences do - local pardir, txtdir = 0, { } - local success = false + local pardir, txtdir, success = 0, { }, false -- we could reuse txtdir and use a top pointer local sequence = sequences[s] - local r = ra[s] -- cache - if r == nil then - -- - -- this bit will move to font-ctx and become a function - --- - local chain = sequence.chain or 0 - local features = sequence.features - if not features then - -- indirect lookup, part of chain (todo: make this a separate table) - r = false -- { false, false, chain } - else - local valid, attribute, kind, what = false, false - for k,v in next, features do - -- we can quit earlier but for the moment we want the tracing - local s_e = s_enabled and s_enabled[k] - local a_e = a_enabled and a_enabled[k] - if s_e or a_e then - local l = v[script] or v[wildcard] - if l then - -- not l[language] or l[default] or l[wildcard] because we want tracing - -- only first attribute match check, so we assume simple fina's - -- default can become a font feature itself - if l[language] then - valid, what = s_e or a_e, language - -- elseif l[default] then - -- valid, what = true, default - elseif l[wildcard] then - valid, what = s_e or a_e, wildcard - end - if valid then - kind, attribute = k, special_attributes[k] or false - if a_e and dyn < 0 then - valid = false - end - if trace_applied then - local typ, action = match(sequence.type,"(.*)_(.*)") - logs.report("otf node mode", - "%s font: %03i, dynamic: %03i, kind: %s, lookup: %3i, script: %-4s, language: %-4s (%-4s), type: %s, action: %s, name: %s", - (valid and "+") or "-",font,attr or 0,kind,s,script,language,what,typ,action,sequence.name) - end - break - end - end - end - end - if valid then - r = { valid, attribute, chain, kind } - else - r = false -- { valid, attribute, chain, "generic" } -- false anyway, could be flag instead of table - end - end - ra[s] = r - end - featurevalue = r and r[1] -- todo: pass to function instead of using a global + local dataset = datasets[s] -- cache + featurevalue = dataset and dataset[1] -- todo: pass to function instead of using a global if featurevalue then - local attribute, chain, typ, subtables = r[2], r[3], sequence.type, sequence.subtables + local attribute, chain, typ, subtables = dataset[2], dataset[3], sequence.type, sequence.subtables +--~ print(typ) +--~ table.print(table.keys(sequence)) if chain < 0 then -- this is a limited case, no special treatments like 'init' etc local handler = handlers[typ] - local thecache = featuredata[typ] or { } -- we need to get rid of this slide ! local start = find_node_tail(head) -- slow (we can store tail because there's always a skip at the end): todo while start do local id = start.id - if id == glyph then - if start.subtype<256 and start.font == font and (not attr or has_attribute(start,0,attr)) then ---~ if start.subtype<256 and start.font == font and has_attribute(start,0,attr) then - for i=1,#subtables do - local lookupname = subtables[i] - local lookupcache = thecache[lookupname] - if lookupcache then - local lookupmatch = lookupcache[start.char] - if lookupmatch then - start, success = handler(start,r[4],lookupname,lookupmatch,sequence,featuredata,i) - if success then - break + if id == glyph_code then + if start.subtype<256 and start.font == font then + local a = has_attribute(start,0) + if a then + a = a == attr + else + a = true + end + if a then + for i=1,#subtables do + local lookupname = subtables[i] + local lookupcache = lookuphash[lookupname] + if lookupcache then + local lookupmatch = lookupcache[start.char] + if lookupmatch then + start, success = handler(start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,i) + if success then + break + end end + else + report_missing_cache(typ,lookupname) end - else - report_missing_cache(typ,lookupname) end + if start then start = start.prev end + else + start = start.prev end - if start then start = start.prev end else start = start.prev end @@ -2022,34 +2004,42 @@ function fonts.methods.node.otf.features(head,font,attr) else local handler = handlers[typ] local ns = #subtables - local thecache = featuredata[typ] or { } local start = head -- local ? rlmode = 0 -- to be checked ? if ns == 1 then local lookupname = subtables[1] - local lookupcache = thecache[lookupname] + local lookupcache = lookuphash[lookupname] if not lookupcache then report_missing_cache(typ,lookupname) else while start do local id = start.id - if id == glyph then ---~ if start.font == font and start.subtype<256 and has_attribute(start,0,attr) and (not attribute or has_attribute(start,state,attribute)) then - if start.font == font and start.subtype<256 and (not attr or has_attribute(start,0,attr)) and (not attribute or has_attribute(start,state,attribute)) then - local lookupmatch = lookupcache[start.char] - if lookupmatch then - -- sequence kan weg - local ok - start, ok = handler(start,r[4],lookupname,lookupmatch,sequence,featuredata,1) - if ok then - success = true + if id == glyph_code then + if start.subtype<256 and start.font == font then + local a = has_attribute(start,0) + if a then + a = (a == attr) and (not attribute or has_attribute(start,state,attribute)) + else + a = not attribute or has_attribute(start,state,attribute) + end + if a then + local lookupmatch = lookupcache[start.char] + if lookupmatch then + -- sequence kan weg + local ok + start, ok = handler(start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,1) + if ok then + success = true + end end + if start then start = start.next end + else + start = start.next end - if start then start = start.next end else start = start.next end - -- elseif id == glue then + -- elseif id == glue_code then -- if p[5] then -- chain -- local pc = pp[32] -- if pc then @@ -2064,9 +2054,9 @@ function fonts.methods.node.otf.features(head,font,attr) -- else -- start = start.next -- end - elseif id == whatsit then + elseif id == whatsit_code then local subtype = start.subtype - if subtype == 7 then + if subtype == dir_code then local dir = start.dir if dir == "+TRT" or dir == "+TLT" then insert(txtdir,dir) @@ -2082,9 +2072,9 @@ function fonts.methods.node.otf.features(head,font,attr) rlmode = pardir end if trace_directions then - logs.report("fonts","directions after textdir %s: pardir=%s, txtdir=%s:%s, rlmode=%s",dir,pardir,#txtdir,txtdir[#txtdir] or "unset",rlmode) + report_process("directions after textdir %s: pardir=%s, txtdir=%s:%s, rlmode=%s",dir,pardir,#txtdir,txtdir[#txtdir] or "unset",rlmode) end - elseif subtype == 6 then + elseif subtype == localpar_code then local dir = start.dir if dir == "TRT" then pardir = -1 @@ -2096,7 +2086,7 @@ function fonts.methods.node.otf.features(head,font,attr) rlmode = pardir --~ txtdir = { } if trace_directions then - logs.report("fonts","directions after pardir %s: pardir=%s, txtdir=%s:%s, rlmode=%s",dir,pardir,#txtdir,txtdir[#txtdir] or "unset",rlmode) + report_process("directions after pardir %s: pardir=%s, txtdir=%s:%s, rlmode=%s",dir,pardir,#txtdir,txtdir[#txtdir] or "unset",rlmode) end end start = start.next @@ -2108,32 +2098,41 @@ function fonts.methods.node.otf.features(head,font,attr) else while start do local id = start.id - if id == glyph then - if start.subtype<256 and start.font == font and (not attr or has_attribute(start,0,attr)) and (not attribute or has_attribute(start,state,attribute)) then ---~ if start.subtype<256 and start.font == font and has_attribute(start,0,attr) and (not attribute or has_attribute(start,state,attribute)) then - for i=1,ns do - local lookupname = subtables[i] - local lookupcache = thecache[lookupname] - if lookupcache then - local lookupmatch = lookupcache[start.char] - if lookupmatch then - -- we could move all code inline but that makes things even more unreadable - local ok - start, ok = handler(start,r[4],lookupname,lookupmatch,sequence,featuredata,i) - if ok then - success = true - break + if id == glyph_code then + if start.subtype<256 and start.font == font then + local a = has_attribute(start,0) + if a then + a = (a == attr) and (not attribute or has_attribute(start,state,attribute)) + else + a = not attribute or has_attribute(start,state,attribute) + end + if a then + for i=1,ns do + local lookupname = subtables[i] + local lookupcache = lookuphash[lookupname] + if lookupcache then + local lookupmatch = lookupcache[start.char] + if lookupmatch then + -- we could move all code inline but that makes things even more unreadable + local ok + start, ok = handler(start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,i) + if ok then + success = true + break + end end + else + report_missing_cache(typ,lookupname) end - else - report_missing_cache(typ,lookupname) end + if start then start = start.next end + else + start = start.next end - if start then start = start.next end else start = start.next end - -- elseif id == glue then + -- elseif id == glue_code then -- if p[5] then -- chain -- local pc = pp[32] -- if pc then @@ -2148,10 +2147,9 @@ function fonts.methods.node.otf.features(head,font,attr) -- else -- start = start.next -- end - elseif id == whatsit then + elseif id == whatsit_code then local subtype = start.subtype - local subtype = start.subtype - if subtype == 7 then + if subtype == dir_code then local dir = start.dir if dir == "+TRT" or dir == "+TLT" then insert(txtdir,dir) @@ -2167,9 +2165,9 @@ function fonts.methods.node.otf.features(head,font,attr) rlmode = pardir end if trace_directions then - logs.report("fonts","directions after textdir %s: pardir=%s, txtdir=%s:%s, rlmode=%s",dir,pardir,#txtdir,txtdir[#txtdir] or "unset",rlmode) + report_process("directions after textdir %s: pardir=%s, txtdir=%s:%s, rlmode=%s",dir,pardir,#txtdir,txtdir[#txtdir] or "unset",rlmode) end - elseif subtype == 6 then + elseif subtype == localpar_code then local dir = start.dir if dir == "TRT" then pardir = -1 @@ -2181,7 +2179,7 @@ function fonts.methods.node.otf.features(head,font,attr) rlmode = pardir --~ txtdir = { } if trace_directions then - logs.report("fonts","directions after pardir %s: pardir=%s, txtdir=%s:%s, rlmode=%s",dir,pardir,#txtdir,txtdir[#txtdir] or "unset",rlmode) + report_process("directions after pardir %s: pardir=%s, txtdir=%s:%s, rlmode=%s",dir,pardir,#txtdir,txtdir[#txtdir] or "unset",rlmode) end end start = start.next @@ -2202,459 +2200,422 @@ function fonts.methods.node.otf.features(head,font,attr) return head, done end -otf.features.prepare = { } - --- we used to share code in the following functions but that costs a lot of --- memory due to extensive calls to functions (easily hundreds of thousands per --- document) - -local function split(replacement,original,cache,unicodes) - -- we can cache this too, but not the same (although unicode is a unique enough hash) - local o, t, n = { }, { }, 0 - for s in gmatch(original,"[^ ]+") do - local us = unicodes[s] - if type(us) == "number" then -- tonumber(us) - o[#o+1] = us - else - o[#o+1] = us[1] - end - end - for s in gmatch(replacement,"[^ ]+") do - n = n + 1 - local us = unicodes[s] - if type(us) == "number" then -- tonumber(us) - t[o[n]] = us - else - t[o[n]] = us[1] - end +local function generic(lookupdata,lookupname,unicode,lookuphash) + local target = lookuphash[lookupname] + if target then + target[unicode] = lookupdata + else + lookuphash[lookupname] = { [unicode] = lookupdata } end - return t end -local function uncover(covers,result,cache,unicodes) - -- lpeg hardly faster (.005 sec on mk) - for n=1,#covers do - local c = covers[n] - local cc = cache[c] - if not cc then - local t = { } - for s in gmatch(c,"[^ ]+") do - local us = unicodes[s] - if type(us) == "number" then - t[us] = true - else - for i=1,#us do - t[us[i]] = true - end - end +local action = { + + substitution = generic, + multiple = generic, + alternate = generic, + position = generic, + + ligature = function(lookupdata,lookupname,unicode,lookuphash) + local target = lookuphash[lookupname] + if not target then + target = { } + lookuphash[lookupname] = target + end + for i=1,#lookupdata do + local li = lookupdata[i] + local tu = target[li] + if not tu then + tu = { } + target[li] = tu end - cache[c] = t - result[#result+1] = t + target = tu + end + target.ligature = unicode + end, + + pair = function(lookupdata,lookupname,unicode,lookuphash) + local target = lookuphash[lookupname] + if not target then + target = { } + lookuphash[lookupname] = target + end + local others = target[unicode] + local paired = lookupdata[1] + if others then + others[paired] = lookupdata else - result[#result+1] = cc + others = { [paired] = lookupdata } + target[unicode] = others end - end -end + end, + +} local function prepare_lookups(tfmdata) - local otfdata = tfmdata.shared.otfdata - local featuredata = otfdata.shared.featuredata - local anchor_to_lookup = otfdata.luatex.anchor_to_lookup - local lookup_to_anchor = otfdata.luatex.lookup_to_anchor - -- - local multiple = featuredata.gsub_multiple - local alternate = featuredata.gsub_alternate - local single = featuredata.gsub_single - local ligature = featuredata.gsub_ligature - local pair = featuredata.gpos_pair - local position = featuredata.gpos_single - local kerns = featuredata.gpos_pair - local mark = featuredata.gpos_mark2mark - local cursive = featuredata.gpos_cursive - -- - local unicodes = tfmdata.unicodes -- names to unicodes - local indices = tfmdata.indices - local descriptions = tfmdata.descriptions - -- - -- we can change the otf table after loading but then we need to adapt base mode - -- as well (no big deal) - -- - local action = { - substitution = function(p,lookup,k,glyph,unicode) - local old, new = unicode, unicodes[p[2]] - if type(new) == "table" then - new = new[1] - end - local s = single[lookup] - if not s then s = { } single[lookup] = s end - s[old] = new - --~ if trace_lookups then - --~ logs.report("define otf","lookup %s: substitution %s => %s",lookup,old,new) - --~ end - end, - multiple = function (p,lookup,k,glyph,unicode) - local old, new = unicode, { } - local m = multiple[lookup] - if not m then m = { } multiple[lookup] = m end - m[old] = new - for pc in gmatch(p[2],"[^ ]+") do - local upc = unicodes[pc] - if type(upc) == "number" then - new[#new+1] = upc - else - new[#new+1] = upc[1] - end - end - --~ if trace_lookups then - --~ logs.report("define otf","lookup %s: multiple %s => %s",lookup,old,concat(new," ")) - --~ end - end, - alternate = function(p,lookup,k,glyph,unicode) - local old, new = unicode, { } - local a = alternate[lookup] - if not a then a = { } alternate[lookup] = a end - a[old] = new - for pc in gmatch(p[2],"[^ ]+") do - local upc = unicodes[pc] - if type(upc) == "number" then - new[#new+1] = upc - else - new[#new+1] = upc[1] - end - end - --~ if trace_lookups then - --~ logs.report("define otf","lookup %s: alternate %s => %s",lookup,old,concat(new,"|")) - --~ end - end, - ligature = function (p,lookup,k,glyph,unicode) - --~ if trace_lookups then - --~ logs.report("define otf","lookup %s: ligature %s => %s",lookup,p[2],glyph.name) - --~ end - local first = true - local t = ligature[lookup] - if not t then t = { } ligature[lookup] = t end - for s in gmatch(p[2],"[^ ]+") do - if first then - local u = unicodes[s] - if not u then - logs.report("define otf","lookup %s: ligature %s => %s ignored due to invalid unicode",lookup,p[2],glyph.name) - break - elseif type(u) == "number" then - if not t[u] then - t[u] = { { } } - end - t = t[u] - else - local tt = t - local tu - for i=1,#u do - local u = u[i] - if i==1 then - if not t[u] then - t[u] = { { } } - end - tu = t[u] - t = tu - else - if not t[u] then - tt[u] = tu - end - end - end - end - first = false - else - s = unicodes[s] - local t1 = t[1] - if not t1[s] then - t1[s] = { { } } - end - t = t1[s] - end - end - t[2] = unicode - end, - position = function(p,lookup,k,glyph,unicode) - -- not used - local s = position[lookup] - if not s then s = { } position[lookup] = s end - s[unicode] = p[2] -- direct pointer to kern spec - end, - pair = function(p,lookup,k,glyph,unicode) - local s = pair[lookup] - if not s then s = { } pair[lookup] = s end - local others = s[unicode] - if not others then others = { } s[unicode] = others end - -- todo: fast check for space - local two = p[2] - local upc = unicodes[two] - if not upc then - for pc in gmatch(two,"[^ ]+") do - local upc = unicodes[pc] - if type(upc) == "number" then - others[upc] = p -- direct pointer to main table - else - for i=1,#upc do - others[upc[i]] = p -- direct pointer to main table - end - end - end - elseif type(upc) == "number" then - others[upc] = p -- direct pointer to main table - else - for i=1,#upc do - others[upc[i]] = p -- direct pointer to main table - end - end - --~ if trace_lookups then - --~ logs.report("define otf","lookup %s: pair for U+%04X",lookup,unicode) - --~ end - end, - } - -- - for unicode, glyph in next, descriptions do - local lookups = glyph.slookups + + local rawdata = tfmdata.shared.rawdata + local resources = rawdata.resources + local lookuphash = resources.lookuphash + local anchor_to_lookup = resources.anchor_to_lookup + local lookup_to_anchor = resources.lookup_to_anchor + local lookuptypes = resources.lookuptypes + local characters = tfmdata.characters + local descriptions = tfmdata.descriptions + + -- we cannot free the entries in the descriptions as sometimes we access + -- then directly (for instance anchors) ... selectively freeing does save + -- much memory as it's only a reference to a table and the slot in the + -- description hash is not freed anyway + + for unicode, character in next, characters do -- we cannot loop over descriptions ! + + local description = descriptions[unicode] + + local lookups = description.slookups if lookups then - for lookup, p in next, lookups do - action[p[1]](p,lookup,k,glyph,unicode) + for lookupname, lookupdata in next, lookups do + action[lookuptypes[lookupname]](lookupdata,lookupname,unicode,lookuphash) end end - local lookups = glyph.mlookups + + local lookups = description.mlookups if lookups then - for lookup, whatever in next, lookups do - for i=1,#whatever do -- normaly one - local p = whatever[i] - action[p[1]](p,lookup,k,glyph,unicode) + for lookupname, lookuplist in next, lookups do + local lookuptype = lookuptypes[lookupname] + for l=1,#lookuplist do + local lookupdata = lookuplist[l] + action[lookuptype](lookupdata,lookupname,unicode,lookuphash) end end end - local list = glyph.mykerns + + local list = description.kerns if list then - for lookup, krn in next, list do - local k = kerns[lookup] - if not k then k = { } kerns[lookup] = k end - k[unicode] = krn -- ref to glyph, saves lookup - --~ if trace_lookups then - --~ logs.report("define otf","lookup %s: kern for U+%04X",lookup,unicode) - --~ end + for lookup, krn in next, list do -- ref to glyph, saves lookup + local target = lookuphash[lookup] + if target then + target[unicode] = krn + else + lookuphash[lookup] = { [unicode] = krn } + end end end - local oanchor = glyph.anchors - if oanchor then - for typ, anchors in next, oanchor do -- types - if typ == "mark" then - for name, anchor in next, anchors do - local lookups = anchor_to_lookup[name] - if lookups then - for lookup, _ in next, lookups do - local f = mark[lookup] - if not f then f = { } mark[lookup] = f end - f[unicode] = anchors -- ref to glyph, saves lookup - --~ if trace_lookups then - --~ logs.report("define otf","lookup %s: mark anchor %s for U+%04X",lookup,name,unicode) - --~ end - end - end - end - elseif typ == "cexit" then -- or entry? + + local list = description.anchors + if list then + for typ, anchors in next, list do -- types + if typ == "mark" or typ == "cexit" then -- or entry? for name, anchor in next, anchors do local lookups = anchor_to_lookup[name] if lookups then for lookup, _ in next, lookups do - local f = cursive[lookup] - if not f then f = { } cursive[lookup] = f end - f[unicode] = anchors -- ref to glyph, saves lookup - --~ if trace_lookups then - --~ logs.report("define otf","lookup %s: exit anchor %s for U+%04X",lookup,name,unicode) - --~ end + local target = lookuphash[lookup] + if target then + target[unicode] = anchors + else + lookuphash[lookup] = { [unicode] = anchors } + end end end end end end end + + end + +end + +local function split(replacement,original) + local result = { } + for i=1,#replacement do + result[original[i]] = replacement[i] end + return result end --- local cache = { } -luatex = luatex or {} -- this has to change ... we need a better one +-- not shared as we hook into lookups now + +--~ local function uncover_1(covers,result) -- multiple covers +--~ local nofresults = #result +--~ for n=1,#covers do +--~ nofresults = nofresults + 1 +--~ local u = { } +--~ local c = covers[n] +--~ for i=1,#c do +--~ u[c[i]] = true +--~ end +--~ result[nofresults] = u +--~ end +--~ end + +--~ local function uncover_2(covers,result) -- single covers (turned into multiple with n=1) +--~ local nofresults = #result +--~ for n=1,#covers do +--~ nofresults = nofresults + 1 +--~ result[nofresults] = { [covers[n]] = true } +--~ end +--~ end + +--~ local function uncover_1(covers,result) -- multiple covers +--~ local nofresults = #result +--~ for n=1,#covers do +--~ nofresults = nofresults + 1 +--~ result[nofresults] = covers[n] +--~ end +--~ end + +--~ local function prepare_contextchains(tfmdata) +--~ local rawdata = tfmdata.shared.rawdata +--~ local resources = rawdata.resources +--~ local lookuphash = resources.lookuphash +--~ local lookups = rawdata.lookups +--~ if lookups then +--~ for lookupname, lookupdata in next, rawdata.lookups do +--~ local lookuptype = lookupdata.type +--~ if not lookuptype then +--~ report_prepare("missing lookuptype for %s",lookupname) +--~ else -- => lookuphash[lookupname][unicode] +--~ local rules = lookupdata.rules +--~ if rules then +--~ local fmt = lookupdata.format +--~ -- if fmt == "coverage" then +--~ if fmt == "coverage" or fmt == "glyphs" then +--~ if lookuptype ~= "chainsub" and lookuptype ~= "chainpos" then +--~ -- todo: dejavu-serif has one (but i need to see what use it has) +--~ report_prepare("unsupported coverage %s for %s",lookuptype,lookupname) +--~ else +--~ local contexts = lookuphash[lookupname] +--~ if not contexts then +--~ contexts = { } +--~ lookuphash[lookupname] = contexts +--~ end +--~ local t, nt = { }, 0 +--~ for nofrules=1,#rules do -- does #rules>1 happen often? +--~ local rule = rules[nofrules] +--~ local current = rule.current +--~ local before = rule.before +--~ local after = rule.after +--~ local sequence = { } +--~ if before then +--~ uncover_1(before,sequence) +--~ end +--~ local start = #sequence + 1 +--~ uncover_1(current,sequence) +--~ local stop = #sequence +--~ if after then +--~ uncover_1(after,sequence) +--~ end +--~ if sequence[1] then +--~ nt = nt + 1 +--~ t[nt] = { nofrules, lookuptype, sequence, start, stop, rule.lookups } +--~ for unic, _ in next, sequence[start] do +--~ local cu = contexts[unic] +--~ if not cu then +--~ contexts[unic] = t +--~ end +--~ end +--~ end +--~ end +--~ end +--~ elseif fmt == "reversecoverage" then -- we could combine both branches (only dufference is replacements) +--~ if lookuptype ~= "reversesub" then +--~ report_prepare("unsupported reverse coverage %s for %s",lookuptype,lookupname) +--~ else +--~ local contexts = lookuphash[lookupname] +--~ if not contexts then +--~ contexts = { } +--~ lookuphash[lookupname] = contexts +--~ end +--~ local t, nt = { }, 0 +--~ for nofrules=1,#rules do +--~ local rule = rules[nofrules] +--~ local current = rule.current +--~ local before = rule.before +--~ local after = rule.after +--~ local replacements = rule.replacements +--~ local sequence = { } +--~ if before then +--~ uncover_1(before,sequence) +--~ end +--~ local start = #sequence + 1 +--~ uncover_1(current,sequence) +--~ local stop = #sequence +--~ if after then +--~ uncover_1(after,sequence) +--~ end +--~ if sequence[1] then +--~ nt = nt + 1 +--~ t[nt] = { nofrules, lookuptype, sequence, start, stop, rule.lookups, replacements } +--~ for unic, _ in next, sequence[start] do +--~ local cu = contexts[unic] +--~ if not cu then +--~ contexts[unic] = t +--~ end +--~ end +--~ end +--~ end +--~ end +--~ -- elseif fmt == "glyphs" then --maybe just make then before = { fore } and share with coverage +--~ -- if lookuptype ~= "chainsub" and lookuptype ~= "chainpos" then +--~ -- report_prepare("unsupported coverage %s for %s",lookuptype,lookupname) +--~ -- else +--~ -- local contexts = lookuphash[lookupname] +--~ -- if not contexts then +--~ -- contexts = { } +--~ -- lookuphash[lookupname] = contexts +--~ -- end +--~ -- local t, nt = { }, 0 +--~ -- for nofrules=1,#rules do -- we can make glyphs a special case (less tables) +--~ -- local rule = rules[nofrules] +--~ -- local current = rule.names +--~ -- local before = rule.fore +--~ -- local after = rule.back +--~ -- local sequence = { } +--~ -- if before then +--~ -- uncover_1(before,sequence) +--~ -- end +--~ -- local start = #sequence + 1 +--~ -- uncover_1(current,sequence) +--~ -- local stop = #sequence +--~ -- if after then +--~ -- uncover_1(after,sequence) +--~ -- end +--~ -- if sequence then +--~ -- nt = nt + 1 +--~ -- t[nt] = { nofrules, lookuptype, sequence, start, stop, rule.lookups } +--~ -- for unic, _ in next, sequence[start] do +--~ -- local cu = contexts[unic] +--~ -- if not cu then +--~ -- contexts[unic] = t +--~ -- end +--~ -- end +--~ -- end +--~ -- end +--~ -- end +--~ end +--~ end +--~ end +--~ end +--~ end +--~ end + +local valid = { + coverage = { chainsub = true, chainpos = true }, + reversecoverage = { reversesub = true }, + glyphs = { chainsub = true, chainpos = true }, +} -function prepare_contextchains(tfmdata) - local otfdata = tfmdata.shared.otfdata - local lookups = otfdata.lookups +local function prepare_contextchains(tfmdata) + local rawdata = tfmdata.shared.rawdata + local resources = rawdata.resources + local lookuphash = resources.lookuphash + local lookups = rawdata.lookups if lookups then - local featuredata = otfdata.shared.featuredata - local contextchain = featuredata.gsub_contextchain -- shared with gpos - local reversecontextchain = featuredata.gsub_reversecontextchain -- shared with gpos - local characters = tfmdata.characters - local unicodes = tfmdata.unicodes - local indices = tfmdata.indices - local cache = luatex.covers - if not cache then - cache = { } - luatex.covers = cache - end - -- - for lookupname, lookupdata in next, otfdata.lookups do + for lookupname, lookupdata in next, rawdata.lookups do local lookuptype = lookupdata.type - if not lookuptype then - logs.report("otf process","missing lookuptype for %s",lookupname) - else + if lookuptype then local rules = lookupdata.rules if rules then - local fmt = lookupdata.format - -- contextchain[lookupname][unicode] - if fmt == "coverage" then - if lookuptype ~= "chainsub" and lookuptype ~= "chainpos" then - logs.report("otf process","unsupported coverage %s for %s",lookuptype,lookupname) - else - local contexts = contextchain[lookupname] - if not contexts then - contexts = { } - contextchain[lookupname] = contexts - end - local t = { } - for nofrules=1,#rules do -- does #rules>1 happen often? - local rule = rules[nofrules] - local coverage = rule.coverage - if coverage and coverage.current then - local current, before, after, sequence = coverage.current, coverage.before, coverage.after, { } - if before then - uncover(before,sequence,cache,unicodes) - end - local start = #sequence + 1 - uncover(current,sequence,cache,unicodes) - local stop = #sequence - if after then - uncover(after,sequence,cache,unicodes) - end - if sequence[1] then - t[#t+1] = { nofrules, lookuptype, sequence, start, stop, rule.lookups } - for unic, _ in next, sequence[start] do - local cu = contexts[unic] - if not cu then - contexts[unic] = t - end - end - end + local format = lookupdata.format + local validformat = valid[format] + if not validformat then + report_prepare("unsupported format %s",format) + elseif not validformat[lookuptype] then + -- todo: dejavu-serif has one (but i need to see what use it has) + report_prepare("unsupported %s %s for %s",format,lookuptype,lookupname) + else + local contexts = lookuphash[lookupname] + if not contexts then + contexts = { } + lookuphash[lookupname] = contexts + end + local t, nt = { }, 0 + for nofrules=1,#rules do + local rule = rules[nofrules] + local current = rule.current + local before = rule.before + local after = rule.after + local replacements = rule.replacements + local sequence = { } + local nofsequences = 0 + -- Wventually we can store start, stop and sequence in the cached file + -- but then less sharing takes place so best not do that without a lot + -- of profiling so let's forget about it. + if before then + for n=1,#before do + nofsequences = nofsequences + 1 + sequence[nofsequences] = before[n] end end - end - elseif fmt == "reversecoverage" then - if lookuptype ~= "reversesub" then - logs.report("otf process","unsupported reverse coverage %s for %s",lookuptype,lookupname) - else - local contexts = reversecontextchain[lookupname] - if not contexts then - contexts = { } - reversecontextchain[lookupname] = contexts + local start = nofsequences + 1 + for n=1,#current do + nofsequences = nofsequences + 1 + sequence[nofsequences] = current[n] end - local t = { } - for nofrules=1,#rules do - local rule = rules[nofrules] - local reversecoverage = rule.reversecoverage - if reversecoverage and reversecoverage.current then - local current, before, after, replacements, sequence = reversecoverage.current, reversecoverage.before, reversecoverage.after, reversecoverage.replacements, { } - if before then - uncover(before,sequence,cache,unicodes) - end - local start = #sequence + 1 - uncover(current,sequence,cache,unicodes) - local stop = #sequence - if after then - uncover(after,sequence,cache,unicodes) - end - if replacements then - replacements = split(replacements,current[1],cache,unicodes) - end - if sequence[1] then - -- this is different from normal coverage, we assume only replacements - t[#t+1] = { nofrules, lookuptype, sequence, start, stop, rule.lookups, replacements } - for unic, _ in next, sequence[start] do - local cu = contexts[unic] - if not cu then - contexts[unic] = t - end - end - end + local stop = nofsequences + if after then + for n=1,#after do + nofsequences = nofsequences + 1 + sequence[nofsequences] = after[n] end end - end - elseif fmt == "glyphs" then - if lookuptype ~= "chainsub" and lookuptype ~= "chainpos" then - logs.report("otf process","unsupported coverage %s for %s",lookuptype,lookupname) - else - local contexts = contextchain[lookupname] - if not contexts then - contexts = { } - contextchain[lookupname] = contexts - end - local t = { } - for nofrules=1,#rules do - -- nearly the same as coverage so we could as well rename it - local rule = rules[nofrules] - local glyphs = rule.glyphs - if glyphs and glyphs.names then - local fore, back, names, sequence = glyphs.fore, glyphs.back, glyphs.names, { } - if fore and fore ~= "" then - fore = lpegmatch(split_at_space,fore) - uncover(fore,sequence,cache,unicodes) - end - local start = #sequence + 1 - names = lpegmatch(split_at_space,names) - uncover(names,sequence,cache,unicodes) - local stop = #sequence - if back and back ~= "" then - back = lpegmatch(split_at_space,back) - uncover(back,sequence,cache,unicodes) - end - if sequence[1] then - t[#t+1] = { nofrules, lookuptype, sequence, start, stop, rule.lookups } - for unic, _ in next, sequence[start] do - local cu = contexts[unic] - if not cu then - contexts[unic] = t - end - end + if sequence[1] then + -- Replacements only happen with reverse lookups as they are single only. We + -- could pack them into current (replacement value instead of true) and then + -- use sequence[start] instead but it's somewhat ugly. + nt = nt + 1 + t[nt] = { nofrules, lookuptype, sequence, start, stop, rule.lookups, replacements } + for unic, _ in next, sequence[start] do + local cu = contexts[unic] + if not cu then + contexts[unic] = t end end end end end + else + -- no rules end + else + report_prepare("missing lookuptype for %s",lookupname) end end end end -function fonts.initializers.node.otf.features(tfmdata,value) +-- we can consider lookuphash == false (initialized but empty) vs lookuphash == table + +local function featuresinitializer(tfmdata,value) if true then -- value then - if not tfmdata.shared.otfdata.shared.initialized then - local t = trace_preparing and os.clock() - local otfdata = tfmdata.shared.otfdata - local featuredata = otfdata.shared.featuredata - -- caches - featuredata.gsub_multiple = { } - featuredata.gsub_alternate = { } - featuredata.gsub_single = { } - featuredata.gsub_ligature = { } - featuredata.gsub_contextchain = { } - featuredata.gsub_reversecontextchain = { } - featuredata.gpos_pair = { } - featuredata.gpos_single = { } - featuredata.gpos_mark2base = { } - featuredata.gpos_mark2ligature = featuredata.gpos_mark2base - featuredata.gpos_mark2mark = featuredata.gpos_mark2base - featuredata.gpos_cursive = { } - featuredata.gpos_contextchain = featuredata.gsub_contextchain - featuredata.gpos_reversecontextchain = featuredata.gsub_reversecontextchain - -- + -- beware we need to use the topmost properties table + local rawdata = tfmdata.shared.rawdata + local properties = rawdata.properties + if not properties.initialized then + local starttime = trace_preparing and os.clock() + local resources = rawdata.resources + resources.lookuphash = resources.lookuphash or { } prepare_contextchains(tfmdata) prepare_lookups(tfmdata) - otfdata.shared.initialized = true + properties.initialized = true if trace_preparing then - logs.report("otf process","preparation time is %0.3f seconds for %s",os.clock()-t,tfmdata.fullname or "?") + report_prepare("preparation time is %0.3f seconds for %s",os.clock()-starttime,tfmdata.properties.fullname or "?") end end end end + +registerotffeature { + name = "features", + description = "features", + default = true, + initializers = { + position = 1, + node = featuresinitializer, + }, + processors = { + node = featuresprocessor, + } +} |