diff options
author | Karl Berry <karl@freefriends.org> | 2018-03-20 22:10:59 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2018-03-20 22:10:59 +0000 |
commit | 65d009a0596b194597f47531523addd3e64ed005 (patch) | |
tree | e13bd9beecf63e26491a66aa7a8fa702482f0067 /Master/texmf-dist/tex/context/base/mkiv/font-ots.lua | |
parent | d1a186333ced7b341e26877907343f08211199f7 (diff) |
context [current] (20mar18)
git-svn-id: svn://tug.org/texlive/trunk@47050 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/tex/context/base/mkiv/font-ots.lua')
-rw-r--r-- | Master/texmf-dist/tex/context/base/mkiv/font-ots.lua | 3144 |
1 files changed, 1639 insertions, 1505 deletions
diff --git a/Master/texmf-dist/tex/context/base/mkiv/font-ots.lua b/Master/texmf-dist/tex/context/base/mkiv/font-ots.lua index 04ecaa92341..3e338f61421 100644 --- a/Master/texmf-dist/tex/context/base/mkiv/font-ots.lua +++ b/Master/texmf-dist/tex/context/base/mkiv/font-ots.lua @@ -108,45 +108,59 @@ mechanisms. Both put some constraints on the code here.</p> -- Remark: We can provide a fast loop when there are no disc nodes (tests show a 1% -- gain). Smaller functions might perform better cache-wise. But ... memory becomes -- faster anyway, so ... +-- +-- Remark: Some optimizations made sense for 5.2 but seem less important for 5.3 but +-- anyway served their purpose. +-- +-- Todo: just (0=l2r and 1=r2l) or maybe (r2l = true) local type, next, tonumber = type, next, tonumber local random = math.random local formatters = string.formatters local insert = table.insert -local registertracker = trackers.register - -local logs = logs -local trackers = trackers -local nodes = nodes -local attributes = attributes -local fonts = fonts - -local otf = fonts.handlers.otf -local tracers = nodes.tracers - -local trace_singles = false registertracker("otf.singles", function(v) trace_singles = v end) -local trace_multiples = false registertracker("otf.multiples", function(v) trace_multiples = v end) -local trace_alternatives = false registertracker("otf.alternatives", function(v) trace_alternatives = v end) -local trace_ligatures = false registertracker("otf.ligatures", function(v) trace_ligatures = v end) -local trace_contexts = false registertracker("otf.contexts", function(v) trace_contexts = v end) -local trace_marks = false registertracker("otf.marks", function(v) trace_marks = v end) -local trace_kerns = false registertracker("otf.kerns", function(v) trace_kerns = v end) -local trace_cursive = false registertracker("otf.cursive", function(v) trace_cursive = v end) -local trace_preparing = false registertracker("otf.preparing", function(v) trace_preparing = v end) -local trace_bugs = false registertracker("otf.bugs", function(v) trace_bugs = v end) -local trace_details = false registertracker("otf.details", function(v) trace_details = v end) -local trace_steps = false registertracker("otf.steps", function(v) trace_steps = v end) -local trace_skips = false registertracker("otf.skips", function(v) trace_skips = v end) -local trace_directions = false registertracker("otf.directions", function(v) trace_directions = v end) -local trace_plugins = false registertracker("otf.plugins", function(v) trace_plugins = v end) - -local trace_kernruns = false registertracker("otf.kernruns", function(v) trace_kernruns = v end) -local trace_discruns = false registertracker("otf.discruns", function(v) trace_discruns = v end) -local trace_compruns = false registertracker("otf.compruns", function(v) trace_compruns = v end) -local trace_testruns = false registertracker("otf.testruns", function(v) trace_testruns = v end) - -local optimizekerns = true +local registertracker = trackers.register + +local logs = logs +local trackers = trackers +local nodes = nodes +local attributes = attributes +local fonts = fonts + +local otf = fonts.handlers.otf +local tracers = nodes.tracers + +local trace_singles = false registertracker("otf.singles", function(v) trace_singles = v end) +local trace_multiples = false registertracker("otf.multiples", function(v) trace_multiples = v end) +local trace_alternatives = false registertracker("otf.alternatives", function(v) trace_alternatives = v end) +local trace_ligatures = false registertracker("otf.ligatures", function(v) trace_ligatures = v end) +local trace_contexts = false registertracker("otf.contexts", function(v) trace_contexts = v end) +local trace_marks = false registertracker("otf.marks", function(v) trace_marks = v end) +local trace_kerns = false registertracker("otf.kerns", function(v) trace_kerns = v end) +local trace_cursive = false registertracker("otf.cursive", function(v) trace_cursive = v end) +local trace_preparing = false registertracker("otf.preparing", function(v) trace_preparing = v end) +local trace_bugs = false registertracker("otf.bugs", function(v) trace_bugs = v end) +local trace_details = false registertracker("otf.details", function(v) trace_details = v end) +local trace_steps = false registertracker("otf.steps", function(v) trace_steps = v end) +local trace_skips = false registertracker("otf.skips", function(v) trace_skips = v end) +local trace_plugins = false registertracker("otf.plugins", function(v) trace_plugins = v end) +local trace_chains = false registertracker("otf.chains", function(v) trace_chains = v end) + +local trace_kernruns = false registertracker("otf.kernruns", function(v) trace_kernruns = v end) +----- trace_discruns = false registertracker("otf.discruns", function(v) trace_discruns = v end) +local trace_compruns = false registertracker("otf.compruns", function(v) trace_compruns = v end) +local trace_testruns = false registertracker("otf.testruns", function(v) trace_testruns = v end) + +local forcediscretionaries = false +local forcepairadvance = false -- for testing + +directives.register("otf.forcediscretionaries",function(v) + forcediscretionaries = v +end) + +directives.register("otf.forcepairadvance",function(v) + forcepairadvance = v +end) local report_direct = logs.reporter("fonts","otf direct") local report_subchain = logs.reporter("fonts","otf subchain") @@ -155,18 +169,17 @@ local report_process = logs.reporter("fonts","otf process") local report_warning = logs.reporter("fonts","otf warning") local report_run = logs.reporter("fonts","otf run") -registertracker("otf.replacements", "otf.singles,otf.multiples,otf.alternatives,otf.ligatures") -registertracker("otf.positions","otf.marks,otf.kerns,otf.cursive") -registertracker("otf.actions","otf.replacements,otf.positions") -registertracker("otf.injections","nodes.injections") -registertracker("otf.sample","otf.steps,otf.actions,otf.analyzing") +registertracker("otf.substitutions", "otf.singles","otf.multiples","otf.alternatives","otf.ligatures") +registertracker("otf.positions", "otf.marks","otf.kerns","otf.cursive") +registertracker("otf.actions", "otf.substitutions","otf.positions") +registertracker("otf.sample", "otf.steps","otf.substitutions","otf.positions","otf.analyzing") +registertracker("otf.sample.silent", "otf.steps=silent","otf.substitutions","otf.positions","otf.analyzing") local nuts = nodes.nuts local tonode = nuts.tonode local tonut = nuts.tonut local getfield = nuts.getfield -local setfield = nuts.setfield local getnext = nuts.getnext local setnext = nuts.setnext local getprev = nuts.getprev @@ -192,22 +205,25 @@ local getdir = nuts.getdir local getwidth = nuts.getwidth local ischar = nuts.is_char +local usesfont = nuts.uses_font local insert_node_after = nuts.insert_after local copy_node = nuts.copy local copy_node_list = nuts.copy_list +local remove_node = nuts.remove local find_node_tail = nuts.tail local flush_node_list = nuts.flush_list local flush_node = nuts.flush_node local end_of_math = nuts.end_of_math local traverse_nodes = nuts.traverse -local traverse_id = nuts.traverse_id +----- traverse_id = nuts.traverse_id local set_components = nuts.set_components local take_components = nuts.take_components local count_components = nuts.count_components local copy_no_components = nuts.copy_no_components local copy_only_glyphs = nuts.copy_only_glyphs +local setmetatable = setmetatable local setmetatableindex = table.setmetatableindex ----- zwnj = 0x200C @@ -234,7 +250,8 @@ local injections = nodes.injections local setmark = injections.setmark local setcursive = injections.setcursive local setkern = injections.setkern -local setpair = injections.setpair +local setmove = injections.setmove +local setposition = injections.setposition local resetinjection = injections.reset local copyinjection = injections.copy local setligaindex = injections.setligaindex @@ -265,14 +282,15 @@ local factor = 0 local threshold = 0 local checkmarks = false +local discs = false +local spaces = false + local sweepnode = nil -local sweepprev = nil -local sweepnext = nil -local sweephead = { } +local sweephead = { } -- we don't nil entries but false them (no collection and such) -local notmatchpre = { } -local notmatchpost = { } -local notmatchreplace = { } +local notmatchpre = { } -- to be checked: can we use false instead of nil / what if a == b tests +local notmatchpost = { } -- to be checked: can we use false instead of nil / what if a == b tests +local notmatchreplace = { } -- to be checked: can we use false instead of nil / what if a == b tests local handlers = { } @@ -283,16 +301,19 @@ local checkstep = (tracers and tracers.steppers.check) or function() local registerstep = (tracers and tracers.steppers.register) or function() end local registermessage = (tracers and tracers.steppers.message) or function() end -local function checkdisccontent(d) - local pre, post, replace = getdisc(d) - if pre then for n in traverse_id(glue_code,pre) do print("pre",nodes.idstostring(pre)) break end end - if post then for n in traverse_id(glue_code,post) do print("pos",nodes.idstostring(post)) break end end - if replace then for n in traverse_id(glue_code,replace) do print("rep",nodes.idstostring(replace)) break end end -end +-- local function checkdisccontent(d) +-- local pre, post, replace = getdisc(d) +-- if pre then for n in traverse_id(glue_code,pre) do report("pre: %s",nodes.idstostring(pre)) break end end +-- if post then for n in traverse_id(glue_code,post) do report("pos: %s",nodes.idstostring(post)) break end end +-- if replace then for n in traverse_id(glue_code,replace) do report("rep: %s",nodes.idstostring(replace)) break end end +-- end local function logprocess(...) if trace_steps then registermessage(...) + if trace_steps == "silent" then + return + end end report_direct(...) end @@ -301,56 +322,67 @@ local function logwarning(...) report_direct(...) end -local f_unicode = formatters["%U"] -local f_uniname = formatters["%U (%s)"] -local f_unilist = formatters["% t (% t)"] +local gref do -local function gref(n) -- currently the same as in font-otb - if type(n) == "number" then - local description = descriptions[n] - local name = description and description.name - if name then - return f_uniname(n,name) - else - return f_unicode(n) - end - elseif n then - local num, nam = { }, { } - for i=1,#n do - local ni = n[i] - if tonumber(ni) then -- later we will start at 2 - local di = descriptions[ni] - num[i] = f_unicode(ni) - nam[i] = di and di.name or "-" + local f_unicode = formatters["U+%X"] -- was ["%U"] + local f_uniname = formatters["U+%X (%s)"] -- was ["%U (%s)"] + local f_unilist = formatters["% t"] + + gref = function(n) -- currently the same as in font-otb + if type(n) == "number" then + local description = descriptions[n] + local name = description and description.name + if name then + return f_uniname(n,name) + else + return f_unicode(n) + end + elseif n then + local t = { } + for i=1,#n do + local ni = n[i] + if tonumber(ni) then -- later we will start at 2 + local di = descriptions[ni] + local nn = di and di.name + if nn then + t[#t+1] = f_uniname(ni,nn) + else + t[#t+1] = f_unicode(ni) + end + end end + return f_unilist(t) + else + return "<error in node mode tracing>" end - return f_unilist(num,nam) - else - return "<error in node mode tracing>" end + end local function cref(dataset,sequence,index) if not dataset then return "no valid dataset" - elseif index then - return formatters["feature %a, type %a, chain lookup %a, index %a"](dataset[4],sequence.type,sequence.name,index) + end + local merged = sequence.merged and "merged " or "" + if index then + return formatters["feature %a, type %a, %schain lookup %a, index %a"]( + dataset[4],sequence.type,merged,sequence.name,index) else - return formatters["feature %a, type %a, chain lookup %a"](dataset[4],sequence.type,sequence.name) + return formatters["feature %a, type %a, %schain lookup %a"]( + dataset[4],sequence.type,merged,sequence.name) end end local function pref(dataset,sequence) - return formatters["feature %a, type %a, lookup %a"](dataset[4],sequence.type,sequence.name) + return formatters["feature %a, type %a, %slookup %a"]( + dataset[4],sequence.type,sequence.merged and "merged " or "",sequence.name) end local function mref(rlmode) - if not rlmode or rlmode == 0 then - return "---" - elseif rlmode == -1 or rlmode == "+TRT" then - return "r2l" - else + if not rlmode or rlmode >= 0 then return "l2r" + else + return "r2l" end end @@ -391,7 +423,7 @@ local function flattendisk(head,disc) elseif next then return next, next else - return -- maybe warning + -- return -- maybe warning end else if replace then @@ -469,124 +501,15 @@ local function markstoligature(head,start,stop,char) end end --- local function toligature(head,start,stop,char,dataset,sequence,markflag,discfound) -- brr head --- if getattr(start,a_noligature) == 1 then --- -- so we can do: e\noligature{ff}e e\noligature{f}fie (we only look at the first) --- return head, start --- end --- if start == stop and getchar(start) == char then --- resetinjection(start) --- setchar(start,char) --- return head, start --- end --- local prev = getprev(start) --- local next = getnext(stop) --- local comp = start --- setprev(start) --- setnext(stop) --- local base = copy_no_components(start,copyinjection) --- if start == head then --- head = base --- end --- resetinjection(base) --- setchar(base,char) --- setsubtype(base,ligature_code) --- set_components(base,comp) --- setlink(prev,base,next) --- if not discfound then --- local deletemarks = markflag ~= "mark" --- local components = start --- local baseindex = 0 --- local componentindex = 0 --- local head = base --- local current = base --- -- first we loop over the glyphs in start .. stop --- while start do --- local char = getchar(start) --- if not marks[char] then --- baseindex = baseindex + componentindex --- componentindex = getcomponentindex(start,marks) --- elseif not deletemarks then -- quite fishy --- setligaindex(start,baseindex + getligaindex(start,componentindex)) --- if trace_marks then --- logwarning("%s: keep mark %s, gets index %s",pref(dataset,sequence),gref(char),getligaindex(start)) --- end --- local n = copy_node(start) --- copyinjection(n,start) --- head, current = insert_node_after(head,current,n) -- unlikely that mark has components --- elseif trace_marks then --- logwarning("%s: delete mark %s",pref(dataset,sequence),gref(char)) --- end --- start = getnext(start) --- end --- -- we can have one accent as part of a lookup and another following --- -- local start = components -- was wrong (component scanning was introduced when more complex ligs in devanagari was added) --- local start = getnext(current) --- while start do --- local char = ischar(start) --- if char then --- if marks[char] then --- setligaindex(start,baseindex + getligaindex(start,componentindex)) --- if trace_marks then --- logwarning("%s: set mark %s, gets index %s",pref(dataset,sequence),gref(char),getligaindex(start)) --- end --- start = getnext(start) --- else --- break --- end --- else --- break --- end --- end --- else --- -- discfound ... forget about marks .. probably no scripts that hyphenate and have marks --- local discprev, discnext = getboth(discfound) --- if discprev and discnext then --- -- we assume normalization in context, and don't care about generic ... especially --- -- \- can give problems as there we can have a negative char but that won't match --- -- anyway --- local pre, post, replace, pretail, posttail, replacetail = getdisc(discfound,true) --- if not replace then -- todo: signal simple hyphen --- local prev = getprev(base) --- local current = comp --- local previous = nil --- local copied = nil --- while current do --- if getid(current) == glyph_code then --- local n = copy_node(current) --- if copied then --- setlink(previous,n) --- else --- copied = n --- end --- previous = n --- end --- current = getnext(current) --- end --- setprev(discnext) -- also blocks funny assignments --- setnext(discprev) -- also blocks funny assignments --- if pre then --- setlink(discprev,pre) --- end --- pre = comp --- if post then --- setlink(posttail,discnext) --- setprev(post) --- else --- post = discnext --- end --- setlink(prev,discfound,next) --- setboth(base) --- set_components(base,copied) --- setdisc(discfound,pre,post,base) -- was discretionary_code --- base = prev -- restart --- end --- end --- end --- return head, base --- end +-- Remark for Kai: (some arabic fonts do mark + mark = other mark and such) +-- +-- The hasmarks is needed for ligatures of marks that are part of a ligature in +-- which case we assume that we can delete the marks anyway (we can always become +-- more clever if needed) .. in fact the whole logic here should be redone. We're +-- in the not discfound branch then. We now have skiphash too so we can be more +-- selective if needed (todo). -local function toligature(head,start,stop,char,dataset,sequence,markflag,discfound) -- brr head +local function toligature(head,start,stop,char,dataset,sequence,skiphash,discfound,hasmarks) -- brr head if getattr(start,a_noligature) == 1 then -- so we can do: e\noligature{ff}e e\noligature{f}fie (we only look at the first) return head, start @@ -611,25 +534,28 @@ local function toligature(head,start,stop,char,dataset,sequence,markflag,discfou set_components(base,comp) setlink(prev,base,next) if not discfound then - local deletemarks = markflag ~= "mark" + local deletemarks = not skiphash or hasmarks local components = start local baseindex = 0 local componentindex = 0 local head = base local current = base - -- first we loop over the glyphs in start .. stop + -- first we loop over the glyphs in start ... stop while start do local char = getchar(start) if not marks[char] then baseindex = baseindex + componentindex componentindex = count_components(start,marks) - elseif not deletemarks then -- quite fishy + -- we can be more clever here: "not deletemarks or (skiphash and not skiphash[char])" + -- and such: + elseif not deletemarks then + -- we can get a loop when the font expects otherwise (i.e. unexpected deletemarks) setligaindex(start,baseindex + getligaindex(start,componentindex)) if trace_marks then logwarning("%s: keep mark %s, gets index %s",pref(dataset,sequence),gref(char),getligaindex(start)) end local n = copy_node(start) - copyinjection(n,start) + copyinjection(n,start) -- is this ok ? we position later anyway head, current = insert_node_after(head,current,n) -- unlikely that mark has components elseif trace_marks then logwarning("%s: delete mark %s",pref(dataset,sequence),gref(char)) @@ -641,6 +567,7 @@ local function toligature(head,start,stop,char,dataset,sequence,markflag,discfou while start do local char = ischar(start) if char then + -- also something skiphash here? if marks[char] then setligaindex(start,baseindex + getligaindex(start,componentindex)) if trace_marks then @@ -684,7 +611,11 @@ local function toligature(head,start,stop,char,dataset,sequence,markflag,discfou -- here components have a pointer so we can't free it! set_components(base,copied) replace = base - setdisc(discfound,pre,post,replace) -- was discretionary_code + if forcediscretionaries then + setdisc(discfound,pre,post,replace,discretionary_code) + else + setdisc(discfound,pre,post,replace) + end base = prev end end @@ -692,7 +623,7 @@ local function toligature(head,start,stop,char,dataset,sequence,markflag,discfou return head, base end -local function multiple_glyphs(head,start,multiple,ignoremarks,what) +local function multiple_glyphs(head,start,multiple,skiphash,what) -- what to do with skiphash matches here local nofmultiples = #multiple if nofmultiples > 0 then resetinjection(start) @@ -804,21 +735,25 @@ function handlers.gsub_alternate(head,start,dataset,sequence,alternative) return head, start, true end -function handlers.gsub_multiple(head,start,dataset,sequence,multiple) +function handlers.gsub_multiple(head,start,dataset,sequence,multiple,rlmode,skiphash) if trace_multiples then logprocess("%s: replacing %s by multiple %s",pref(dataset,sequence),gref(getchar(start)),gref(multiple)) end - return multiple_glyphs(head,start,multiple,sequence.flags[1],dataset[1]) + return multiple_glyphs(head,start,multiple,skiphash,dataset[1]) end -function handlers.gsub_ligature(head,start,dataset,sequence,ligature) +-- Don't we deal with disc otherwise now? I need to check if the next one can be +-- simplified. Anyway, it can be way messier: marks that get removed as well as +-- marks that are kept. + +function handlers.gsub_ligature(head,start,dataset,sequence,ligature,rlmode,skiphash) local current = getnext(start) if not current then return head, start, false, nil end local stop = nil local startchar = getchar(start) - if marks[startchar] then + if skiphash and skiphash[startchar] then while current do local char = ischar(current,currentfont) if char then @@ -849,15 +784,16 @@ function handlers.gsub_ligature(head,start,dataset,sequence,ligature) -- ok, goto next lookup end end - else - local skipmark = sequence.flags[1] + else -- is the check for disc still valid here ? and why only replace then local discfound = false local lastdisc = nil + local hasmarks = marks[startchar] while current do local char, id = ischar(current,currentfont) if char then - if skipmark and marks[char] then + if skiphash and skiphash[char] then current = getnext(current) + -- if stop then stop = current end -- ? else -- ligature is a tree local lg = ligature[char] -- can there be multiple in a row? maybe in a bad font if lg then @@ -865,6 +801,9 @@ function handlers.gsub_ligature(head,start,dataset,sequence,ligature) discfound = lastdisc lastdisc = nil end + if marks[char] then + hasmarks = true + end stop = current -- needed for fake so outside then ligature = lg current = getnext(current) @@ -876,8 +815,20 @@ function handlers.gsub_ligature(head,start,dataset,sequence,ligature) -- kind of weird break elseif id == disc_code then - -- tricky .. we also need to do pre here - local replace = getfield(current,"replace") + -- + -- Kai: see chainprocs, we probably could do the same here or was there a reason + -- why we kept the replace check here. + -- + -- if not discfound then + -- discfound = current + -- end + -- if current == stop then + -- break -- okay? or before the disc + -- else + -- current = getnext(current) + -- end + -- + local replace = getfield(current,"replace") -- hm: pre and post if replace then -- of{f-}{}{f}e o{f-}{}{f}fe o{-}{}{ff}e (oe and ff ligature) -- we can end up here when we have a start run .. testruns start at a disc but @@ -887,6 +838,9 @@ function handlers.gsub_ligature(head,start,dataset,sequence,ligature) if char then local lg = ligature[char] -- can there be multiple in a row? maybe in a bad font if lg then + if marks[char] then + hasmarks = true -- very unlikely + end ligature = lg replace = getnext(replace) else @@ -909,10 +863,10 @@ function handlers.gsub_ligature(head,start,dataset,sequence,ligature) if stop then if trace_ligatures then local stopchar = getchar(stop) - head, start = toligature(head,start,stop,lig,dataset,sequence,skipmark,discfound) + head, start = toligature(head,start,stop,lig,dataset,sequence,skiphash,discfound,hasmarks) logprocess("%s: replacing %s upto %s by ligature %s case 2",pref(dataset,sequence),gref(startchar),gref(stopchar),gref(lig)) else - head, start = toligature(head,start,stop,lig,dataset,sequence,skipmark,discfound) + head, start = toligature(head,start,stop,lig,dataset,sequence,skiphash,discfound,hasmarks) end else -- weird but happens (in some arabic font) @@ -930,24 +884,24 @@ function handlers.gsub_ligature(head,start,dataset,sequence,ligature) return head, start, false, discfound end -function handlers.gpos_single(head,start,dataset,sequence,kerns,rlmode,step,i,injection) +function handlers.gpos_single(head,start,dataset,sequence,kerns,rlmode,skiphash,step,injection) local startchar = getchar(start) - if step.format == "pair" then - local dx, dy, w, h = setpair(start,factor,rlmode,sequence.flags[4],kerns,injection) + local format = step.format + if format == "single" or type(kerns) == "table" then -- the table check can go + local dx, dy, w, h = setposition(0,start,factor,rlmode,kerns,injection) if trace_kerns then - logprocess("%s: shifting single %s by (%p,%p) and correction (%p,%p)",pref(dataset,sequence),gref(startchar),dx,dy,w,h) + logprocess("%s: shifting single %s by %s xy (%p,%p) and wh (%p,%p)",pref(dataset,sequence),gref(startchar),format,dx,dy,w,h) end else - -- needs checking .. maybe no kerns format for single - local k = setkern(start,factor,rlmode,kerns,injection) + local k = (format == "move" and setmove or setkern)(start,factor,rlmode,kerns,injection) if trace_kerns then - logprocess("%s: shifting single %s by %p",pref(dataset,sequence),gref(startchar),k) + logprocess("%s: shifting single %s by %s %p",pref(dataset,sequence),gref(startchar),format,k) end end - return head, start, false + return head, start, true end -function handlers.gpos_pair(head,start,dataset,sequence,kerns,rlmode,step,i,injection) +function handlers.gpos_pair(head,start,dataset,sequence,kerns,rlmode,skiphash,step,injection) local snext = getnext(start) if not snext then return head, start, false @@ -956,48 +910,49 @@ function handlers.gpos_pair(head,start,dataset,sequence,kerns,rlmode,step,i,inje while snext do local nextchar = ischar(snext,currentfont) if nextchar then - local krn = kerns[nextchar] - if not krn and marks[nextchar] then - prev = snext + if skiphash and skiphash[nextchar] then -- includes marks too when flag + prev = snext snext = getnext(snext) - elseif not krn then - break - elseif step.format == "pair" then - local a, b = krn[1], krn[2] - if optimizekerns then - -- this permits a mixed table, but we could also decide to optimize this - -- in the loader and use format 'kern' - if not b and a[1] == 0 and a[2] == 0 and a[4] == 0 then - local k = setkern(snext,factor,rlmode,a[3],injection) + else + local krn = kerns[nextchar] + if not krn then + break + end + local format = step.format + if format == "pair" then + local a, b = krn[1], krn[2] + if a == true then + -- zero + elseif a then -- #a > 0 + local x, y, w, h = setposition(1,start,factor,rlmode,a,injection) if trace_kerns then - logprocess("%s: shifting single %s by %p",pref(dataset,sequence),gref(nextchar),k) + local startchar = getchar(start) + logprocess("%s: shifting first of pair %s and %s by xy (%p,%p) and wh (%p,%p) as %s",pref(dataset,sequence),gref(startchar),gref(nextchar),x,y,w,h,injection or "injections") end - return head, start, true end - end - if a and #a > 0 then - local x, y, w, h = setpair(start,factor,rlmode,sequence.flags[4],a,injection) - if trace_kerns then - local startchar = getchar(start) - logprocess("%s: shifting first of pair %s and %s by (%p,%p) and correction (%p,%p) as %s",pref(dataset,sequence),gref(startchar),gref(nextchar),x,y,w,h,injection or "injections") + if b == true then + -- zero + start = snext -- cf spec + elseif b then -- #b > 0 + local x, y, w, h = setposition(2,snext,factor,rlmode,b,injection) + if trace_kerns then + local startchar = getchar(snext) + logprocess("%s: shifting second of pair %s and %s by xy (%p,%p) and wh (%p,%p) as %s",pref(dataset,sequence),gref(startchar),gref(nextchar),x,y,w,h,injection or "injections") + end + start = snext -- cf spec + elseif forcepairadvance then + start = snext -- for testing, not cf spec end - end - if b and #b > 0 then - local x, y, w, h = setpair(snext,factor,rlmode,sequence.flags[4],b,injection) + return head, start, true + elseif krn ~= 0 then + local k = (format == "move" and setmove or setkern)(snext,factor,rlmode,krn,injection) if trace_kerns then - local startchar = getchar(snext) - logprocess("%s: shifting second of pair %s and %s by (%p,%p) and correction (%p,%p) as %s",pref(dataset,sequence),gref(startchar),gref(nextchar),x,y,w,h,injection or "injections") + logprocess("%s: inserting %s %p between %s and %s as %s",pref(dataset,sequence),format,k,gref(getchar(prev)),gref(nextchar),injection or "injections") end + return head, start, true + else -- can't happen + break end - return head, start, true - elseif krn ~= 0 then - local k = setkern(snext,factor,rlmode,krn,injection) - if trace_kerns then - logprocess("%s: inserting kern %p between %s and %s as %s",pref(dataset,sequence),k,gref(getchar(prev)),gref(nextchar),injection or "injections") - end - return head, start, true - else -- can't happen - break end else break @@ -1012,7 +967,7 @@ end we need to explicitly test for basechar, baselig and basemark entries.</p> --ldx]]-- -function handlers.gpos_mark2base(head,start,dataset,sequence,markanchors,rlmode) +function handlers.gpos_mark2base(head,start,dataset,sequence,markanchors,rlmode,skiphash) local markchar = getchar(start) if marks[markchar] then local base = getprev(start) -- [glyph] [start=mark] @@ -1047,10 +1002,13 @@ function handlers.gpos_mark2base(head,start,dataset,sequence,markanchors,rlmode) local ma = markanchors[2] local dx, dy, bound = setmark(start,base,factor,rlmode,ba,ma,characters[basechar],false,checkmarks) if trace_marks then - logprocess("%s, anchor %s, bound %s: anchoring mark %s to basechar %s => (%p,%p)", - pref(dataset,sequence),anchor,bound,gref(markchar),gref(basechar),dx,dy) + logprocess("%s, bound %s, anchoring mark %s to basechar %s => (%p,%p)", + pref(dataset,sequence),bound,gref(markchar),gref(basechar),dx,dy) end return head, start, true + elseif trace_bugs then + -- onetimemessage(currentfont,basechar,"no base anchors",report_fonts) + logwarning("%s: mark %s is not anchored to %s",pref(dataset,sequence),gref(markchar),gref(basechar)) end elseif trace_bugs then logwarning("%s: nothing preceding, case %i",pref(dataset,sequence),1) @@ -1064,7 +1022,7 @@ function handlers.gpos_mark2base(head,start,dataset,sequence,markanchors,rlmode) return head, start, false end -function handlers.gpos_mark2ligature(head,start,dataset,sequence,markanchors,rlmode) +function handlers.gpos_mark2ligature(head,start,dataset,sequence,markanchors,rlmode,skiphash) local markchar = getchar(start) if marks[markchar] then local base = getprev(start) -- [glyph] [optional marks] [start=mark] @@ -1103,8 +1061,8 @@ function handlers.gpos_mark2ligature(head,start,dataset,sequence,markanchors,rlm if ba then local dx, dy, bound = setmark(start,base,factor,rlmode,ba,ma,characters[basechar],false,checkmarks) if trace_marks then - logprocess("%s, anchor %s, index %s, bound %s: anchoring mark %s to baselig %s at index %s => (%p,%p)", - pref(dataset,sequence),anchor,index,bound,gref(markchar),gref(basechar),index,dx,dy) + logprocess("%s, index %s, bound %s, anchoring mark %s to baselig %s at index %s => (%p,%p)", + pref(dataset,sequence),index,bound,gref(markchar),gref(basechar),index,dx,dy) end return head, start, true else @@ -1129,7 +1087,7 @@ function handlers.gpos_mark2ligature(head,start,dataset,sequence,markanchors,rlm return head, start, false end -function handlers.gpos_mark2mark(head,start,dataset,sequence,markanchors,rlmode) +function handlers.gpos_mark2mark(head,start,dataset,sequence,markanchors,rlmode,skiphash) local markchar = getchar(start) if marks[markchar] then local base = getprev(start) -- [glyph] [basemark] [start=mark] @@ -1152,8 +1110,8 @@ function handlers.gpos_mark2mark(head,start,dataset,sequence,markanchors,rlmode) local ma = markanchors[2] local dx, dy, bound = setmark(start,base,factor,rlmode,ba,ma,characters[basechar],true,checkmarks) if trace_marks then - logprocess("%s, anchor %s, bound %s: anchoring mark %s to basemark %s => (%p,%p)", - pref(dataset,sequence),anchor,bound,gref(markchar),gref(basechar),dx,dy) + logprocess("%s, bound %s, anchoring mark %s to basemark %s => (%p,%p)", + pref(dataset,sequence),bound,gref(markchar),gref(basechar),dx,dy) end return head, start, true end @@ -1165,7 +1123,7 @@ function handlers.gpos_mark2mark(head,start,dataset,sequence,markanchors,rlmode) return head, start, false end -function handlers.gpos_cursive(head,start,dataset,sequence,exitanchors,rlmode,step,i) -- to be checked +function handlers.gpos_cursive(head,start,dataset,sequence,exitanchors,rlmode,skiphash,step) -- to be checked local startchar = getchar(start) if marks[startchar] then if trace_cursive then @@ -1177,8 +1135,7 @@ function handlers.gpos_cursive(head,start,dataset,sequence,exitanchors,rlmode,st local nextchar = ischar(nxt,currentfont) if not nextchar then break - elseif marks[nextchar] then - -- should not happen (maybe warning) + elseif marks[nextchar] then -- always sequence.flags[1] nxt = getnext(nxt) else local exit = exitanchors[3] @@ -1187,9 +1144,10 @@ function handlers.gpos_cursive(head,start,dataset,sequence,exitanchors,rlmode,st if entry then entry = entry[2] if entry then - local dx, dy, bound = setcursive(start,nxt,factor,rlmode,exit,entry,characters[startchar],characters[nextchar]) + local r2lflag = sequence.flags[4] -- mentioned in the standard + local dx, dy, bound = setcursive(start,nxt,factor,rlmode,exit,entry,characters[startchar],characters[nextchar],r2lflag) if trace_cursive then - logprocess("%s: moving %s to %s cursive (%p,%p) using anchor %s and bound %s in %s mode",pref(dataset,sequence),gref(startchar),gref(nextchar),dx,dy,anchor,bound,mref(rlmode)) + logprocess("%s: moving %s to %s cursive (%p,%p) using bound %s in %s mode",pref(dataset,sequence),gref(startchar),gref(nextchar),dx,dy,bound,mref(rlmode)) end return head, start, true end @@ -1212,6 +1170,9 @@ local chainprocs = { } local function logprocess(...) if trace_steps then registermessage(...) + if trace_steps == "silent" then + return + end end report_subchain(...) end @@ -1221,6 +1182,9 @@ local logwarning = report_subchain local function logprocess(...) if trace_steps then registermessage(...) + if trace_steps == "silent" then + return + end end report_chain(...) end @@ -1234,7 +1198,7 @@ local logwarning = report_chain -- 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. -local function reversesub(head,start,stop,dataset,sequence,replacements,rlmode) +local function reversesub(head,start,stop,dataset,sequence,replacements,rlmode,skiphash) local char = getchar(start) local replacement = replacements[char] if replacement then @@ -1288,17 +1252,31 @@ end -- logwarning("%s: bad step, no proper return values",cref(dataset,sequence)) -- end -function chainprocs.gsub_single(head,start,stop,dataset,sequence,currentlookup,chainindex) +local function getmapping(dataset,sequence,currentlookup) local steps = currentlookup.steps local nofsteps = currentlookup.nofsteps - if nofsteps > 1 then - reportmoresteps(dataset,sequence) - end if nofsteps == 0 then reportzerosteps(dataset,sequence) + currentlookup.mapping = false + return false else - local current = start + if nofsteps > 1 then + reportmoresteps(dataset,sequence) + end local mapping = steps[1].coverage + currentlookup.mapping = mapping + currentlookup.format = steps[1].format + return mapping + end +end + +function chainprocs.gsub_single(head,start,stop,dataset,sequence,currentlookup,rlmode,skiphash,chainindex) + local mapping = currentlookup.mapping + if mapping == nil then + mapping = getmapping(dataset,sequence,currentlookup) + end + if mapping then + local current = start while current do local currentchar = ischar(current) if currentchar then @@ -1329,35 +1307,6 @@ function chainprocs.gsub_single(head,start,stop,dataset,sequence,currentlookup,c end --[[ldx-- -<p>Here we replace start by a sequence of new glyphs.</p> ---ldx]]-- - -function chainprocs.gsub_multiple(head,start,stop,dataset,sequence,currentlookup) - local steps = currentlookup.steps - local nofsteps = currentlookup.nofsteps - if nofsteps > 1 then - reportmoresteps(dataset,sequence) - end - if nofsteps == 0 then - reportzerosteps(dataset,sequence) - else - local startchar = getchar(start) - local replacement = steps[1].coverage[startchar] - if not replacement or replacement == "" then - if trace_bugs then - logwarning("%s: no multiple for %s",cref(dataset,sequence),gref(startchar)) - end - else - if trace_multiples then - logprocess("%s: replacing %s by multiple characters %s",cref(dataset,sequence),gref(startchar),gref(replacement)) - end - return multiple_glyphs(head,start,replacement,sequence.flags[1],dataset[1]) - end - end - return head, start, false -end - ---[[ldx-- <p>Here we replace start by new glyph. First we delete the rest of the match.</p> --ldx]]-- @@ -1369,20 +1318,16 @@ end -- marks come last anyway -- are there cases where we need to delete the mark -function chainprocs.gsub_alternate(head,start,stop,dataset,sequence,currentlookup) - local steps = currentlookup.steps - local nofsteps = currentlookup.nofsteps - if nofsteps > 1 then - reportmoresteps(dataset,sequence) +function chainprocs.gsub_alternate(head,start,stop,dataset,sequence,currentlookup,rlmode,skiphash,chainindex) + local mapping = currentlookup.mapping + if mapping == nil then + mapping = getmapping(dataset,sequence,currentlookup) end - if nofsteps == 0 then - reportzerosteps(dataset,sequence) - else + if mapping then local kind = dataset[4] local what = dataset[1] local value = what == true and tfmdata.shared.features[kind] or what -- todo: optimize in ctx local current = start - local mapping = steps[1].coverage while current do local currentchar = ischar(current) if currentchar then @@ -1391,13 +1336,13 @@ function chainprocs.gsub_alternate(head,start,stop,dataset,sequence,currentlooku local choice, comment = get_alternative_glyph(current,alternatives,value) if choice then if trace_alternatives then - logprocess("%s: replacing %s by alternative %a to %s, %s",cref(dataset,sequence),gref(char),choice,gref(choice),comment) + logprocess("%s: replacing %s by alternative %a to %s, %s",cref(dataset,sequence),gref(currentchar),choice,gref(choice),comment) end resetinjection(start) setchar(start,choice) else if trace_alternatives then - logwarning("%s: no variant %a for %s, %s",cref(dataset,sequence),value,gref(char),comment) + logwarning("%s: no variant %a for %s, %s",cref(dataset,sequence),value,gref(currentchar),comment) end end end @@ -1416,32 +1361,57 @@ function chainprocs.gsub_alternate(head,start,stop,dataset,sequence,currentlooku end --[[ldx-- +<p>Here we replace start by a sequence of new glyphs.</p> +--ldx]]-- + +function chainprocs.gsub_multiple(head,start,stop,dataset,sequence,currentlookup,rlmode,skiphash,chainindex) + local mapping = currentlookup.mapping + if mapping == nil then + mapping = getmapping(dataset,sequence,currentlookup) + end + if mapping then + local startchar = getchar(start) + local replacement = mapping[startchar] + if not replacement or replacement == "" then + if trace_bugs then + logwarning("%s: no multiple for %s",cref(dataset,sequence),gref(startchar)) + end + else + if trace_multiples then + logprocess("%s: replacing %s by multiple characters %s",cref(dataset,sequence),gref(startchar),gref(replacement)) + end + return multiple_glyphs(head,start,replacement,skiphash,dataset[1]) + end + end + return head, start, false +end + +--[[ldx-- <p>When we replace ligatures we use a helper that handles the marks. I might change 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(head,start,stop,dataset,sequence,currentlookup,chainindex) - local steps = currentlookup.steps - local nofsteps = currentlookup.nofsteps - if nofsteps > 1 then - reportmoresteps(dataset,sequence) +-- compare to handlers.gsub_ligature which is more complex ... why + +function chainprocs.gsub_ligature(head,start,stop,dataset,sequence,currentlookup,rlmode,skiphash,chainindex) + local mapping = currentlookup.mapping + if mapping == nil then + mapping = getmapping(dataset,sequence,currentlookup) end - if nofsteps == 0 then - reportzerosteps(dataset,sequence) - else + if mapping then local startchar = getchar(start) - local ligatures = steps[1].coverage[startchar] + local ligatures = mapping[startchar] if not ligatures then if trace_bugs then logwarning("%s: no ligatures starting with %s",cref(dataset,sequence,chainindex),gref(startchar)) end else + local hasmarks = marks[startchar] local current = getnext(start) local discfound = false local last = stop local nofreplacements = 1 - local skipmark = currentlookup.flags[1] -- sequence.flags? while current do -- todo: ischar ... can there really be disc nodes here? local id = getid(current) @@ -1456,7 +1426,7 @@ function chainprocs.gsub_ligature(head,start,stop,dataset,sequence,currentlookup end else local schar = getchar(current) - if skipmark and marks[schar] then -- marks + if skiphash and skiphash[schar] then -- marks -- if current == stop then -- maybe add this -- break -- else @@ -1468,6 +1438,9 @@ function chainprocs.gsub_ligature(head,start,stop,dataset,sequence,currentlookup ligatures = lg last = current nofreplacements = nofreplacements + 1 + if marks[char] then + hasmarks = true + end if current == stop then break else @@ -1491,7 +1464,7 @@ function chainprocs.gsub_ligature(head,start,stop,dataset,sequence,currentlookup logprocess("%s: replacing character %s upto %s by ligature %s case 4",cref(dataset,sequence,chainindex),gref(startchar),gref(getchar(stop)),gref(ligature)) end end - head, start = toligature(head,start,stop,ligature,dataset,sequence,skipmark,discfound) + head, start = toligature(head,start,stop,ligature,dataset,sequence,skiphash,discfound,hasmarks) return head, start, true, nofreplacements, discfound elseif trace_bugs then if start == stop then @@ -1505,49 +1478,43 @@ function chainprocs.gsub_ligature(head,start,stop,dataset,sequence,currentlookup return head, start, false, 0, false end -function chainprocs.gpos_single(head,start,stop,dataset,sequence,currentlookup,rlmode,chainindex) - local steps = currentlookup.steps - local nofsteps = currentlookup.nofsteps - if nofsteps > 1 then - reportmoresteps(dataset,sequence) +function chainprocs.gpos_single(head,start,stop,dataset,sequence,currentlookup,rlmode,skiphash,chainindex) + local mapping = currentlookup.mapping + if mapping == nil then + mapping = getmapping(dataset,sequence,currentlookup) end - if nofsteps == 0 then - reportzerosteps(dataset,sequence) - else + if mapping then local startchar = getchar(start) - local step = steps[1] - local kerns = step.coverage[startchar] - if not kerns then - -- skip - elseif step.format == "pair" then - local dx, dy, w, h = setpair(start,factor,rlmode,sequence.flags[4],kerns) -- currentlookup.flags ? - if trace_kerns then - logprocess("%s: shifting single %s by (%p,%p) and correction (%p,%p)",cref(dataset,sequence),gref(startchar),dx,dy,w,h) - end - else -- needs checking .. maybe no kerns format for single - local k = setkern(start,factor,rlmode,kerns,injection) - if trace_kerns then - logprocess("%s: shifting single %s by %p",cref(dataset,sequence),gref(startchar),k) + local kerns = mapping[startchar] + if kerns then + local format = currentlookup.format + if format == "single" then + local dx, dy, w, h = setposition(0,start,factor,rlmode,kerns) -- currentlookup.flags ? + if trace_kerns then + logprocess("%s: shifting single %s by %s (%p,%p) and correction (%p,%p)",cref(dataset,sequence),gref(startchar),format,dx,dy,w,h) + end + else -- needs checking .. maybe no kerns format for single + local k = (format == "move" and setmove or setkern)(start,factor,rlmode,kerns,injection) + if trace_kerns then + logprocess("%s: shifting single %s by %s %p",cref(dataset,sequence),gref(startchar),format,k) + end end + return head, start, true end end return head, start, false end -function chainprocs.gpos_pair(head,start,stop,dataset,sequence,currentlookup,rlmode,chainindex) -- todo: injections ? - local steps = currentlookup.steps - local nofsteps = currentlookup.nofsteps - if nofsteps > 1 then - reportmoresteps(dataset,sequence) +function chainprocs.gpos_pair(head,start,stop,dataset,sequence,currentlookup,rlmode,skiphash,chainindex) -- todo: injections ? + local mapping = currentlookup.mapping + if mapping == nil then + mapping = getmapping(dataset,sequence,currentlookup) end - if nofsteps == 0 then - reportzerosteps(dataset,sequence) - else + if mapping then local snext = getnext(start) if snext then local startchar = getchar(start) - local step = steps[1] - local kerns = step.coverage[startchar] -- always 1 step + local kerns = mapping[startchar] -- always 1 step if kerns then local prev = start while snext do @@ -1555,48 +1522,49 @@ function chainprocs.gpos_pair(head,start,stop,dataset,sequence,currentlookup,rlm if not nextchar then break end - local krn = kerns[nextchar] - if not krn and marks[nextchar] then - prev = snext + if skiphash and skiphash[nextchar] then + prev = snext snext = getnext(snext) - elseif not krn then - break - elseif step.format == "pair" then - local a, b = krn[1], krn[2] - if optimizekerns then - -- this permits a mixed table, but we could also decide to optimize this - -- in the loader and use format 'kern' - if not b and a[1] == 0 and a[2] == 0 and a[4] == 0 then - local k = setkern(snext,factor,rlmode,a[3],"injections") + else + local krn = kerns[nextchar] + if not krn then + break + end + local format = currentlookup.format + if format == "pair" then + local a, b = krn[1], krn[2] + if a == true then + -- zero + elseif a then + local x, y, w, h = setposition(1,start,factor,rlmode,a,"injections") -- currentlookups flags? if trace_kerns then - logprocess("%s: shifting single %s by %p",cref(dataset,sequence),gref(startchar),k) + local startchar = getchar(start) + logprocess("%s: shifting first of pair %s and %s by (%p,%p) and correction (%p,%p)",cref(dataset,sequence),gref(startchar),gref(nextchar),x,y,w,h) end - return head, start, true end - end - if a and #a > 0 then - local startchar = getchar(start) - local x, y, w, h = setpair(start,factor,rlmode,sequence.flags[4],a,"injections") -- currentlookups flags? - if trace_kerns then - logprocess("%s: shifting first of pair %s and %s by (%p,%p) and correction (%p,%p)",cref(dataset,sequence),gref(startchar),gref(nextchar),x,y,w,h) + if b == true then + -- zero + start = snext -- cf spec + elseif b then -- #b > 0 + local x, y, w, h = setposition(2,snext,factor,rlmode,b,"injections") + if trace_kerns then + local startchar = getchar(start) + logprocess("%s: shifting second of pair %s and %s by (%p,%p) and correction (%p,%p)",cref(dataset,sequence),gref(startchar),gref(nextchar),x,y,w,h) + end + start = snext -- cf spec + elseif forcepairadvance then + start = snext -- for testing, not cf spec end - end - if b and #b > 0 then - local startchar = getchar(start) - local x, y, w, h = setpair(snext,factor,rlmode,sequence.flags[4],b,"injections") + return head, start, true + elseif krn ~= 0 then + local k = (format == "move" and setmove or setkern)(snext,factor,rlmode,krn) if trace_kerns then - logprocess("%s: shifting second of pair %s and %s by (%p,%p) and correction (%p,%p)",cref(dataset,sequence),gref(startchar),gref(nextchar),x,y,w,h) + logprocess("%s: inserting %s %p between %s and %s",cref(dataset,sequence),format,k,gref(getchar(prev)),gref(nextchar)) end + return head, start, true + else + break end - return head, start, true - elseif krn ~= 0 then - local k = setkern(snext,factor,rlmode,krn) - if trace_kerns then - logprocess("%s: inserting kern %s between %s and %s",cref(dataset,sequence),k,gref(getchar(prev)),gref(nextchar)) - end - return head, start, true - else - break end end end @@ -1605,18 +1573,15 @@ function chainprocs.gpos_pair(head,start,stop,dataset,sequence,currentlookup,rlm return head, start, false end -function chainprocs.gpos_mark2base(head,start,stop,dataset,sequence,currentlookup,rlmode) - local steps = currentlookup.steps - local nofsteps = currentlookup.nofsteps - if nofsteps > 1 then - reportmoresteps(dataset,sequence) +function chainprocs.gpos_mark2base(head,start,stop,dataset,sequence,currentlookup,rlmode,skiphash,chainindex) + local mapping = currentlookup.mapping + if mapping == nil then + mapping = getmapping(dataset,sequence,currentlookup) end - if nofsteps == 0 then - reportzerosteps(dataset,sequence) - else + if mapping then local markchar = getchar(start) if marks[markchar] then - local markanchors = steps[1].coverage[markchar] -- always 1 step + local markanchors = mapping[markchar] -- always 1 step if markanchors then local base = getprev(start) -- [glyph] [start=mark] if base then @@ -1651,8 +1616,8 @@ function chainprocs.gpos_mark2base(head,start,stop,dataset,sequence,currentlooku if ma then local dx, dy, bound = setmark(start,base,factor,rlmode,ba,ma,characters[basechar],false,checkmarks) if trace_marks then - logprocess("%s, anchor %s, bound %s: anchoring mark %s to basechar %s => (%p,%p)", - cref(dataset,sequence),anchor,bound,gref(markchar),gref(basechar),dx,dy) + logprocess("%s, bound %s, anchoring mark %s to basechar %s => (%p,%p)", + cref(dataset,sequence),bound,gref(markchar),gref(basechar),dx,dy) end return head, start, true end @@ -1673,18 +1638,15 @@ function chainprocs.gpos_mark2base(head,start,stop,dataset,sequence,currentlooku return head, start, false end -function chainprocs.gpos_mark2ligature(head,start,stop,dataset,sequence,currentlookup,rlmode) - local steps = currentlookup.steps - local nofsteps = currentlookup.nofsteps - if nofsteps > 1 then - reportmoresteps(dataset,sequence) +function chainprocs.gpos_mark2ligature(head,start,stop,dataset,sequence,currentlookup,rlmode,skiphash,chainindex) + local mapping = currentlookup.mapping + if mapping == nil then + mapping = getmapping(dataset,sequence,currentlookup) end - if nofsteps == 0 then - reportzerosteps(dataset,sequence) - else + if mapping then local markchar = getchar(start) if marks[markchar] then - local markanchors = steps[1].coverage[markchar] -- always 1 step + local markanchors = mapping[markchar] -- always 1 step if markanchors then local base = getprev(start) -- [glyph] [optional marks] [start=mark] if base then @@ -1722,8 +1684,8 @@ function chainprocs.gpos_mark2ligature(head,start,stop,dataset,sequence,currentl if ba then local dx, dy, bound = setmark(start,base,factor,rlmode,ba,ma,characters[basechar],false,checkmarks) if trace_marks then - logprocess("%s, anchor %s, bound %s: anchoring mark %s to baselig %s at index %s => (%p,%p)", - cref(dataset,sequence),anchor,a or bound,gref(markchar),gref(basechar),index,dx,dy) + logprocess("%s, bound %s, anchoring mark %s to baselig %s at index %s => (%p,%p)", + cref(dataset,sequence),a or bound,gref(markchar),gref(basechar),index,dx,dy) end return head, start, true end @@ -1745,18 +1707,15 @@ function chainprocs.gpos_mark2ligature(head,start,stop,dataset,sequence,currentl return head, start, false end -function chainprocs.gpos_mark2mark(head,start,stop,dataset,sequence,currentlookup,rlmode) - local steps = currentlookup.steps - local nofsteps = currentlookup.nofsteps - if nofsteps > 1 then - reportmoresteps(dataset,sequence) +function chainprocs.gpos_mark2mark(head,start,stop,dataset,sequence,currentlookup,rlmode,skiphash,chainindex) + local mapping = currentlookup.mapping + if mapping == nil then + mapping = getmapping(dataset,sequence,currentlookup) end - if nofsteps == 0 then - reportzerosteps(dataset,sequence) - else + if mapping then local markchar = getchar(start) if marks[markchar] then - local markanchors = steps[1].coverage[markchar] -- always 1 step + local markanchors = mapping[markchar] -- always 1 step if markanchors then local base = getprev(start) -- [glyph] [basemark] [start=mark] local slc = getligaindex(start) @@ -1779,8 +1738,8 @@ function chainprocs.gpos_mark2mark(head,start,stop,dataset,sequence,currentlooku if ma then local dx, dy, bound = setmark(start,base,factor,rlmode,ba,ma,characters[basechar],true,checkmarks) if trace_marks then - logprocess("%s, anchor %s, bound %s: anchoring mark %s to basemark %s => (%p,%p)", - cref(dataset,sequence),anchor,bound,gref(markchar),gref(basechar),dx,dy) + logprocess("%s, bound %s, anchoring mark %s to basemark %s => (%p,%p)", + cref(dataset,sequence),bound,gref(markchar),gref(basechar),dx,dy) end return head, start, true end @@ -1801,17 +1760,14 @@ function chainprocs.gpos_mark2mark(head,start,stop,dataset,sequence,currentlooku return head, start, false end -function chainprocs.gpos_cursive(head,start,stop,dataset,sequence,currentlookup,rlmode) - local steps = currentlookup.steps - local nofsteps = currentlookup.nofsteps - if nofsteps > 1 then - reportmoresteps(dataset,sequence) +function chainprocs.gpos_cursive(head,start,stop,dataset,sequence,currentlookup,rlmode,skiphash,chainindex) + local mapping = currentlookup.mapping + if mapping == nil then + mapping = getmapping(dataset,sequence,currentlookup) end - if nofsteps == 0 then - reportzerosteps(dataset,sequence) - else + if mapping then local startchar = getchar(start) - local exitanchors = steps[1].coverage[startchar] -- always 1 step + local exitanchors = mapping[startchar] -- always 1 step if exitanchors then if marks[startchar] then if trace_cursive then @@ -1833,9 +1789,10 @@ function chainprocs.gpos_cursive(head,start,stop,dataset,sequence,currentlookup, if entry then entry = entry[2] if entry then - local dx, dy, bound = setcursive(start,nxt,factor,rlmode,exit,entry,characters[startchar],characters[nextchar]) + local r2lflag = sequence.flags[4] -- mentioned in the standard + local dx, dy, bound = setcursive(start,nxt,factor,rlmode,exit,entry,characters[startchar],characters[nextchar],r2lflag) if trace_cursive then - logprocess("%s: moving %s to %s cursive (%p,%p) using anchor %s and bound %s in %s mode",pref(dataset,sequence),gref(startchar),gref(nextchar),dx,dy,anchor,bound,mref(rlmode)) + logprocess("%s: moving %s to %s cursive (%p,%p) using bound %s in %s mode",pref(dataset,sequence),gref(startchar),gref(nextchar),dx,dy,bound,mref(rlmode)) end return head, start, true end @@ -1865,36 +1822,50 @@ end -- A previous version had disc collapsing code in the (single sub) handler plus some -- checking in the main loop, but that left the pre/post sequences undone. The best -- solution is to add some checking there and backtrack when a replace/post matches --- but it takes a bit of work to figure out an efficient way (this is what the sweep* --- names refer to). I might look into that variant one day again as it can replace --- some other code too. In that approach we can have a special version for gub and pos --- which gains some speed. This method does the test and passes info to the handlers --- (sweepnode, sweepmode, sweepprev, sweepnext, etc). Here collapsing is handled in the --- main loop which also makes code elsewhere simpler (i.e. no need for the other special --- runners and disc code in ligature building). I also experimented with pushing preceding --- glyphs sequences in the replace/pre fields beforehand which saves checking afterwards --- but at the cost of duplicate glyphs (memory) but it's too much overhead (runtime). +-- but it takes a bit of work to figure out an efficient way (this is what the +-- sweep* names refer to). I might look into that variant one day again as it can +-- replace some other code too. In that approach we can have a special version for +-- gub and pos which gains some speed. This method does the test and passes info to +-- the handlers. Here collapsing is handled in the main loop which also makes code +-- elsewhere simpler (i.e. no need for the other special runners and disc code in +-- ligature building). I also experimented with pushing preceding glyphs sequences +-- in the replace/pre fields beforehand which saves checking afterwards but at the +-- cost of duplicate glyphs (memory) but it's too much overhead (runtime). -- --- In the meantime Kai had moved the code from the single chain into a more general handler --- and this one (renamed to chaindisk) is used now. I optimized the code a bit and brought --- it in sycn with the other code. Hopefully I didn't introduce errors. Note: this somewhat --- complex approach is meant for fonts that implement (for instance) ligatures by character --- replacement which to some extend is not that suitable for hyphenation. I also use some --- helpers. This method passes some states but reparses the list. There is room for a bit of --- speed up but that will be done in the context version. (In fact a partial rewrite of all --- code can bring some more efficientry.) +-- In the meantime Kai had moved the code from the single chain into a more general +-- handler and this one (renamed to chaindisk) is used now. I optimized the code a +-- bit and brought it in sycn with the other code. Hopefully I didn't introduce +-- errors. Note: this somewhat complex approach is meant for fonts that implement +-- (for instance) ligatures by character replacement which to some extend is not +-- that suitable for hyphenation. I also use some helpers. This method passes some +-- states but reparses the list. There is room for a bit of speed up but that will +-- be done in the context version. (In fact a partial rewrite of all code can bring +-- some more efficiency.) -- --- I didn't test it with extremes but successive disc nodes still can give issues but in --- order to handle that we need more complex code which also slows down even more. The main --- loop variant could deal with that: test, collapse, backtrack. +-- I didn't test it with extremes but successive disc nodes still can give issues +-- but in order to handle that we need more complex code which also slows down even +-- more. The main loop variant could deal with that: test, collapse, backtrack. + +local userkern = nuts.pool and nuts.pool.newkern -- context + +do if not userkern then -- generic + + local thekern = nuts.new("kern",1) -- userkern + local setkern = nuts.setkern -- not injections.setkern + + userkern = function(k) + local n = copy_node(thekern) + setkern(n,k) + return n + end -local new_kern = nuts.pool.kern +end end local function checked(head) local current = head while current do if getid(current) == glue_code then - local kern = new_kern(getwidth(current)) + local kern = userkern(getwidth(current)) if head == current then local next = getnext(current) if next then @@ -1925,23 +1896,23 @@ end local noflags = { false, false, false, false } -local function chainrun(head,start,last,dataset,sequence,rlmode,ck,skipped) +local function chainrun(head,start,last,dataset,sequence,rlmode,skiphash,ck) local size = ck[5] - ck[4] + 1 - local flags = sequence.flags or noflags - local done = false - local skipmark = flags[1] local chainlookups = ck[6] + local done = false -- current match if chainlookups then - local nofchainlookups = #chainlookups -- Lookups can be like { 1, false, 3 } or { false, 2 } or basically anything and -- #lookups can be less than #current + if size == 1 then + -- if nofchainlookups > size then -- -- bad rules -- end + local chainlookup = chainlookups[1] for j=1,#chainlookup do local chainstep = chainlookup[j] @@ -1949,7 +1920,7 @@ local function chainrun(head,start,last,dataset,sequence,rlmode,ck,skipped) local chainproc = chainprocs[chainkind] if chainproc then local ok - head, start, ok = chainproc(head,start,last,dataset,sequence,chainstep,rlmode,1) + head, start, ok = chainproc(head,start,last,dataset,sequence,chainstep,rlmode,skiphash) if ok then done = true end @@ -1957,7 +1928,9 @@ local function chainrun(head,start,last,dataset,sequence,rlmode,ck,skipped) logprocess("%s: %s is not yet supported (1)",cref(dataset,sequence),chainkind) end end + else + -- See LookupType 5: Contextual Substitution Subtable. Now it becomes messy. The -- easiest case is where #current maps on #lookups i.e. one-to-one. But what if -- we have a ligature. Cf the spec we then need to advance one character but we @@ -1968,14 +1941,19 @@ local function chainrun(head,start,last,dataset,sequence,rlmode,ck,skipped) -- -- Even worse are these family emoji shapes as they can have multiple lookups -- per slot (probably only for gpos). + + -- It's very unlikely that we will have skip classes here but still ... we seldom + -- enter this branch anyway. + local i = 1 + local laststart = start + local nofchainlookups = #chainlookups -- useful? while start do - if skipped then + if skiphash then -- hm, so we know we skip some while start do - local char = getchar(start) - local class = classes[char] - if class then - if class == skipmark or class == skipligature or class == skipbase or (markclass and class == "mark" and not markclass[char]) then + local char = ischar(start,currentfont) + if char then + if skiphash and skiphash[char] then start = getnext(start) else break @@ -1993,12 +1971,13 @@ local function chainrun(head,start,last,dataset,sequence,rlmode,ck,skipped) local chainproc = chainprocs[chainkind] if chainproc then local ok, n - head, start, ok, n = chainproc(head,start,last,dataset,sequence,chainstep,rlmode,i) + head, start, ok, n = chainproc(head,start,last,dataset,sequence,chainstep,rlmode,skiphash,i) -- messy since last can be changed ! if ok then done = true if n and n > 1 and i + n > nofchainlookups then -- this is a safeguard, we just ignore the rest of the lookups + i = size -- prevents an advance break end end @@ -2012,15 +1991,20 @@ local function chainrun(head,start,last,dataset,sequence,rlmode,ck,skipped) if i > size or not start then break elseif start then + laststart = start start = getnext(start) end end + if not start then + start = laststart + end + end else -- todo: needs checking for holes in the replacements local replacements = ck[7] if replacements then - head, start, done = reversesub(head,start,last,dataset,sequence,replacements,rlmode) + head, start, done = reversesub(head,start,last,dataset,sequence,replacements,rlmode,skiphash) else done = true if trace_contexts then @@ -2031,7 +2015,7 @@ local function chainrun(head,start,last,dataset,sequence,rlmode,ck,skipped) return head, start, done end -local function chaindisk(head,start,dataset,sequence,rlmode,ck,skipped) +local function chaindisk(head,start,dataset,sequence,rlmode,skiphash,ck) if not start then return head, start, false @@ -2098,7 +2082,7 @@ local function chaindisk(head,start,dataset,sequence,rlmode,ck,skipped) if current then -- go on elseif sweepoverflow then - -- we already are folling up on sweepnode + -- we already are following up on sweepnode break elseif sweeptype == "post" or sweeptype == "replace" then current = getnext(sweepnode) @@ -2213,7 +2197,6 @@ local function chaindisk(head,start,dataset,sequence,rlmode,ck,skipped) end end end - local done = false if lookaheaddisc then @@ -2241,7 +2224,7 @@ local function chaindisk(head,start,dataset,sequence,rlmode,ck,skipped) head = lookaheaddisc end local pre, post, replace = getdisc(lookaheaddisc) - local new = copy_node_list(cf) + local new = copy_node_list(cf) -- br, how often does that happen local cnew = new if pre then setlink(find_node_tail(cf),pre) @@ -2261,14 +2244,14 @@ local function chaindisk(head,start,dataset,sequence,rlmode,ck,skipped) end if not notmatchpre[lookaheaddisc] then local ok = false - cf, start, ok = chainrun(cf,start,cl,dataset,sequence,rlmode,ck,skipped) + cf, start, ok = chainrun(cf,start,cl,dataset,sequence,rlmode,skiphash,ck) if ok then done = true end end if not notmatchreplace[lookaheaddisc] then local ok = false - new, cnew, ok = chainrun(new,cnew,clast,dataset,sequence,rlmode,ck,skipped) + new, cnew, ok = chainrun(new,cnew,clast,dataset,sequence,rlmode,skiphash,ck) if ok then done = true end @@ -2279,8 +2262,8 @@ local function chaindisk(head,start,dataset,sequence,rlmode,ck,skipped) setdisc(lookaheaddisc,cf,post,new) end start = getprev(lookaheaddisc) - sweephead[cf] = getnext(clast) - sweephead[new] = getnext(cl) + sweephead[cf] = getnext(clast) or false + sweephead[new] = getnext(cl) or false elseif backtrackdisc then @@ -2299,10 +2282,7 @@ local function chaindisk(head,start,dataset,sequence,rlmode,ck,skipped) break end end - if cnext then - setprev(cnext,backtrackdisc) - end - setnext(backtrackdisc,cnext) + setlink(backtrackdisc,cnext) setprev(cf) setnext(cl) local pre, post, replace, pretail, posttail, replacetail = getdisc(backtrackdisc,true) @@ -2317,14 +2297,14 @@ local function chaindisk(head,start,dataset,sequence,rlmode,ck,skipped) end if not notmatchpost[backtrackdisc] then local ok = false - cf, start, ok = chainrun(cf,start,last,dataset,sequence,rlmode,ck,skipped) + cf, start, ok = chainrun(cf,start,last,dataset,sequence,rlmode,skiphash,ck) if ok then done = true end end if not notmatchreplace[backtrackdisc] then local ok = false - new, cnew, ok = chainrun(new,cnew,clast,dataset,sequence,rlmode,ck,skipped) + new, cnew, ok = chainrun(new,cnew,clast,dataset,sequence,rlmode,skiphash,ck) if ok then done = true end @@ -2345,13 +2325,13 @@ local function chaindisk(head,start,dataset,sequence,rlmode,ck,skipped) setdisc(backtrackdisc,pre,post,replace) end start = getprev(backtrackdisc) - sweephead[post] = getnext(clast) - sweephead[replace] = getnext(last) + sweephead[post] = getnext(clast) or false + sweephead[replace] = getnext(last) or false else local ok = false - head, start, ok = chainrun(head,start,last,dataset,sequence,rlmode,ck,skipped) + head, start, ok = chainrun(head,start,last,dataset,sequence,rlmode,skiphash,ck) if ok then done = true end @@ -2361,463 +2341,464 @@ local function chaindisk(head,start,dataset,sequence,rlmode,ck,skipped) return head, start, done end -local function chaintrac(head,start,dataset,sequence,rlmode,ck,skipped) +local function chaintrac(head,start,dataset,sequence,rlmode,skiphash,ck,match,discseen,sweepnode) local rule = ck[1] local lookuptype = ck[8] or ck[2] local nofseq = #ck[3] local first = ck[4] local last = ck[5] local char = getchar(start) - logwarning("%s: rule %s matches at char %s for (%s,%s,%s) chars, lookuptype %a", - cref(dataset,sequence),rule,gref(char),first-1,last-first+1,nofseq-last,lookuptype) + logwarning("%s: rule %s %s at char %s for (%s,%s,%s) chars, lookuptype %a, %sdisc seen, %ssweeping", + cref(dataset,sequence),rule,match and "matches" or "nomatch", + gref(char),first-1,last-first+1,nofseq-last,lookuptype, + discseen and "" or "no ", sweepnode and "" or "not ") end -local function handle_contextchain(head,start,dataset,sequence,contexts,rlmode) +-- The next one is quite optimized but still somewhat slow, fonts like ebgaramond +-- are real torture tests because they have many steps with one context (having +-- multiple contexts makes more sense) also because we (can) reduce them. Instead of +-- a match boolean variable and check for that I decided to use a goto with labels +-- instead. This is one of the cases where it makes the code more readable and we +-- might even gain a bit performance. + +-- when we have less replacements (lookups) then current matches we can push too much into +-- the previous disc .. such be it (<before><disc><current=fl><after> with only f done) + +local function handle_contextchain(head,start,dataset,sequence,contexts,rlmode,skiphash) + -- optimizing for rlmode gains nothing local sweepnode = sweepnode local sweeptype = sweeptype + local postreplace + local prereplace + local checkdisc + local discseen -- = false + if sweeptype then + if sweeptype == "replace" then + postreplace = true + prereplace = true + else + postreplace = sweeptype == "post" + prereplace = sweeptype == "pre" + end + checkdisc = getprev(head) + end local currentfont = currentfont - local diskseen = false - local checkdisc = sweeptype and getprev(head) - local flags = sequence.flags or noflags - local done = false - local skipmark = flags[1] - local skipligature = flags[2] - local skipbase = flags[3] - local markclass = sequence.markclass - local skipped = false + + local skipped -- = false + local startprev, startnext = getboth(start) - for k=1,#contexts do -- i've only seen ccmp having > 1 (e.g. dejavu) - local match = true + local done -- = false + + -- we can have multiple hits and as we scan (currently) all we need to check + -- if we have a match ... contextchains have no real coverage table (with + -- unique entries) + + -- fonts can have many steps (each doing one check) or many contexts + + -- todo: make a per-char cache so that we have small contexts (when we have a context + -- n == 1 and otherwise it can be more so we can even distingish n == 1 or more) + + local nofcontexts = contexts.n -- #contexts + + local startchar = nofcontext == 1 or ischar(start,currentfont) -- only needed in a chain + + for k=1,nofcontexts do -- does this disc mess work well with n > 1 + + local ck = contexts[k] + local seq = ck[3] + local f = ck[4] -- first current + if not startchar or not seq[f][startchar] then + -- report("no hit in %a at %i of %i contexts",sequence.type,k,nofcontexts) + goto next + end + local s = seq.n -- or #seq + local l = ck[5] -- last current local current = start local last = start - local ck = contexts[k] - local seq = ck[3] - local s = #seq - local size = 1 - -- f..l = mid string - if s == 1 then - -- this seldom happens as it makes no sense (bril, ebgaramond, husayni, minion) - local char = ischar(current,currentfont) - if char then - if not seq[1][char] then - match = false + + -- current match + + if l > f then + -- before/current/after | before/current | current/after + local discfound -- = nil + local n = f + 1 + last = startnext -- the second in current (first already matched) + while n <= l do + if postreplace and not last then + last = getnext(sweepnode) + sweeptype = nil end - end - else - -- maybe we need a better space check (maybe check for glue or category or combination) - -- we cannot optimize for n=2 because there can be disc nodes - local f = ck[4] - local l = ck[5] - -- current match - size = l - f + 1 - if size > 1 then - -- before/current/after | before/current | current/after - local discfound -- = nil - local n = f + 1 - -- last = getnext(last) -- the second in current (first already matched) - last = startnext -- the second in current (first already matched) - while n <= l do - if not last and (sweeptype == "post" or sweeptype == "replace") then - last = getnext(sweepnode) - sweeptype = nil - end - if last then - local char, id = ischar(last,currentfont) - if char then - local class = classes[char] - if class then - if class == skipmark or class == skipligature or class == skipbase or (markclass and class == "mark" and not markclass[char]) then - skipped = true - if trace_skips then - show_skip(dataset,sequence,char,ck,class) - end - last = getnext(last) - elseif seq[n][char] then - if n < l then - last = getnext(last) - end - n = n + 1 - else - if discfound then - notmatchreplace[discfound] = true - if notmatchpre[discfound] then - match = false - end - else - match = false - end - break - end + if last then + local char, id = ischar(last,currentfont) + if char then + if skiphash and skiphash[char] then + skipped = true + if trace_skips then + show_skip(dataset,sequence,char,ck,classes[char]) + end + last = getnext(last) + elseif seq[n][char] then + if n < l then + last = getnext(last) + end + n = n + 1 + elseif discfound then + notmatchreplace[discfound] = true + if notmatchpre[discfound] then + goto next else - if discfound then - notmatchreplace[discfound] = true - if notmatchpre[discfound] then - match = false - end - else - match = false - end break end - elseif char == false then - if discfound then - notmatchreplace[discfound] = true - if notmatchpre[discfound] then - match = false - end + else + goto next + end + elseif char == false then + if discfound then + notmatchreplace[discfound] = true + if notmatchpre[discfound] then + goto next else - match = false + break end - break - elseif id == disc_code then - diskseen = true - discfound = last - notmatchpre[last] = nil - notmatchpost[last] = true - notmatchreplace[last] = nil - local pre, post, replace = getdisc(last) - if pre then - local n = n - while pre do - if seq[n][getchar(pre)] then - n = n + 1 - pre = getnext(pre) - if n > l then - break - end - else - notmatchpre[last] = true + else + goto next + end + elseif id == disc_code then + -- elseif id == disc_code and (not discs or discs[last]) then + discseen = true + discfound = last + notmatchpre[last] = nil + notmatchpost[last] = true + notmatchreplace[last] = nil + local pre, post, replace = getdisc(last) + if pre then + local n = n + while pre do + if seq[n][getchar(pre)] then + n = n + 1 + if n > l then break end - end - if n <= l then + pre = getnext(pre) + else notmatchpre[last] = true + break end - else - notmatchpre[last] = true end - if replace then - -- so far we never entered this branch - while replace do - if seq[n][getchar(replace)] then - n = n + 1 - replace = getnext(replace) - if n > l then - break - end + -- commented, for Kai to check + -- if n <= l then + -- notmatchpre[last] = true + -- end + else + notmatchpre[last] = true + end + if replace then + -- so far we never entered this branch + while replace do + if seq[n][getchar(replace)] then + n = n + 1 + if n > l then + break + end + replace = getnext(replace) + else + notmatchreplace[last] = true + if notmatchpre[last] then + goto next else - notmatchreplace[last] = true - if notmatchpre[last] then - match = false - end break end end - -- why here again - if notmatchpre[last] then - match = false - end end - -- maybe only if match - last = getnext(last) - else - match = false - break + -- why here again + if notmatchpre[last] then + goto next + end end + -- maybe only if match + last = getnext(last) else - match = false - break + goto next end + else + goto next end end - -- before - if match and f > 1 then - -- local prev = getprev(start) - -- if prev then - if startprev then - local prev = startprev - if prev == checkdisc and (sweeptype == "pre" or sweeptype == "replace") then - prev = getprev(sweepnode) - -- sweeptype = nil - end - if prev then - local discfound -- = nil - local n = f - 1 - while n >= 1 do - if prev then - local char, id = ischar(prev,currentfont) - if char then - local class = classes[char] - if class then - if class == skipmark or class == skipligature or class == skipbase or (markclass and class == "mark" and not markclass[char]) then - skipped = true - if trace_skips then - show_skip(dataset,sequence,char,ck,class) - end - prev = getprev(prev) - elseif seq[n][char] then - if n > 1 then - prev = getprev(prev) - end - n = n - 1 - else - if discfound then - notmatchreplace[discfound] = true - if notmatchpost[discfound] then - match = false - end - else - match = false - end - break - end + end + + -- before + + if f > 1 then + if startprev then + local prev = startprev + if prereplace and prev == checkdisc then + prev = getprev(sweepnode) + end + if prev then + local discfound -- = nil + local n = f - 1 + while n >= 1 do + if prev then + local char, id = ischar(prev,currentfont) + if char then + if skiphash and skiphash[char] then + skipped = true + if trace_skips then + show_skip(dataset,sequence,char,ck,classes[char]) + end + prev = getprev(prev) + elseif seq[n][char] then + if n > 1 then + prev = getprev(prev) + end + n = n - 1 + elseif discfound then + notmatchreplace[discfound] = true + if notmatchpost[discfound] then + goto next else - if discfound then - notmatchreplace[discfound] = true - if notmatchpost[discfound] then - match = false - end - else - match = false - end break end - elseif char == false then - if discfound then - notmatchreplace[discfound] = true - if notmatchpost[discfound] then - match = false - end - else - match = false + else + goto next + end + elseif char == false then + if discfound then + notmatchreplace[discfound] = true + if notmatchpost[discfound] then + goto next end - break - elseif id == disc_code then - -- the special case: f i where i becomes dottless i .. - diskseen = true - discfound = prev - notmatchpre[prev] = true - notmatchpost[prev] = nil - notmatchreplace[prev] = nil - local pre, post, replace, pretail, posttail, replacetail = getdisc(prev,true) - if pre ~= start and post ~= start and replace ~= start then - if post then - local n = n - while posttail do - if seq[n][getchar(posttail)] then - n = n - 1 - if posttail == post then - break - else - posttail = getprev(posttail) - if n < 1 then - break - end - end - else - notmatchpost[prev] = true + else + goto next + end + break + elseif id == disc_code then + -- elseif id == disc_code and (not discs or discs[prev]) then + -- the special case: f i where i becomes dottless i .. + discseen = true + discfound = prev + notmatchpre[prev] = true + notmatchpost[prev] = nil + notmatchreplace[prev] = nil + local pre, post, replace, pretail, posttail, replacetail = getdisc(prev,true) + -- weird test: needs checking + if pre ~= start and post ~= start and replace ~= start then + if post then + local n = n + while posttail do + if seq[n][getchar(posttail)] then + n = n - 1 + if posttail == post or n < 1 then break + else + posttail = getprev(posttail) end - end - if n >= 1 then + else notmatchpost[prev] = true + break end - else + end + if n >= 1 then notmatchpost[prev] = true end - if replace then - -- we seldom enter this branch (e.g. on brill efficient) - while replacetail do - if seq[n][getchar(replacetail)] then - n = n - 1 - if replacetail == replace then - break - else - replacetail = getprev(replacetail) - if n < 1 then - break - end - end + else + notmatchpost[prev] = true + end + if replace then + -- we seldom enter this branch (e.g. on brill efficient) + while replacetail do + if seq[n][getchar(replacetail)] then + n = n - 1 + if replacetail == replace or n < 1 then + break + else + replacetail = getprev(replacetail) + end + else + notmatchreplace[prev] = true + if notmatchpost[prev] then + goto next else - notmatchreplace[prev] = true - if notmatchpost[prev] then - match = false - end break end end - if not match then - break - end end + else + notmatchreplace[prev] = true -- new, for Kai to check end - -- maybe only if match - prev = getprev(prev) - elseif id == glue_code and seq[n][32] and isspace(prev,threshold,id) then + end + prev = getprev(prev) + -- elseif id == glue_code and seq[n][32] and isspace(prev,threshold,id) then + -- elseif seq[n][32] and spaces[prev] then + -- n = n - 1 + -- prev = getprev(prev) + elseif id == glue_code then + local sn = seq[n] + if (sn[32] and spaces[prev]) or sn[0xFFFC] then n = n - 1 prev = getprev(prev) else - match = false - break + goto next end + elseif seq[n][0xFFFC] then + n = n - 1 + prev = getprev(prev) else - match = false - break + goto next end + else + goto next end - else - match = false end else - match = false + goto next end + else + goto next end - -- after - if match and s > l then - local current = last and getnext(last) - if not current and (sweeptype == "post" or sweeptype == "replace") then - current = getnext(sweepnode) - -- sweeptype = nil - end - if current then - local discfound -- = nil - -- 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 char, id = ischar(current,currentfont) - if char then - local class = classes[char] - if class then - if class == skipmark or class == skipligature or class == skipbase or (markclass and class == "mark" and not markclass[char]) then - skipped = true - if trace_skips then - show_skip(dataset,sequence,char,ck,class) - end - current = getnext(current) -- was absent - elseif seq[n][char] then - if n < s then -- new test - current = getnext(current) -- was absent - end - n = n + 1 - else - if discfound then - notmatchreplace[discfound] = true - if notmatchpre[discfound] then - match = false - end - else - match = false - end - break - end + end + + -- after + + if s > l then + local current = last and getnext(last) + if not current and postreplace then + current = getnext(sweepnode) + end + if current then + local discfound -- = nil + local n = l + 1 + while n <= s do + if current then + local char, id = ischar(current,currentfont) + if char then + if skiphash and skiphash[char] then + skipped = true + if trace_skips then + show_skip(dataset,sequence,char,ck,classes[char]) + end + current = getnext(current) -- was absent + elseif seq[n][char] then + if n < s then -- new test + current = getnext(current) -- was absent + end + n = n + 1 + elseif discfound then + notmatchreplace[discfound] = true + if notmatchpre[discfound] then + goto next else - if discfound then - notmatchreplace[discfound] = true - if notmatchpre[discfound] then - match = false - end - else - match = false - end break end - elseif char == false then - if discfound then - notmatchreplace[discfound] = true - if notmatchpre[discfound] then - match = false - end + else + goto next + end + elseif char == false then + if discfound then + notmatchreplace[discfound] = true + if notmatchpre[discfound] then + goto next else - match = false + break end - break - elseif id == disc_code then - diskseen = true - discfound = current - notmatchpre[current] = nil - notmatchpost[current] = true - notmatchreplace[current] = nil - local pre, post, replace = getdisc(current) - if pre then - local n = n - while pre do - if seq[n][getchar(pre)] then - n = n + 1 - pre = getnext(pre) - if n > s then - break - end - else - notmatchpre[current] = true + else + goto next + end + elseif id == disc_code then + -- elseif id == disc_code and (not discs or discs[current]) then + discseen = true + discfound = current + notmatchpre[current] = nil + notmatchpost[current] = true + notmatchreplace[current] = nil + local pre, post, replace = getdisc(current) + if pre then + local n = n + while pre do + if seq[n][getchar(pre)] then + n = n + 1 + if n > s then break + else + pre = getnext(pre) end - end - if n <= s then + else notmatchpre[current] = true + break end - else + end + if n <= s then notmatchpre[current] = true end - if replace then - -- so far we never entered this branch - while replace do - if seq[n][getchar(replace)] then - n = n + 1 + else + notmatchpre[current] = true + end + if replace then + -- so far we never entered this branch + while replace do + if seq[n][getchar(replace)] then + n = n + 1 + if n > s then + break + else replace = getnext(replace) - if n > s then - break - end + end + else + notmatchreplace[current] = true + if notmatchpre[current] then + goto next else - notmatchreplace[current] = true - -- different than others, needs checking if "not" is okay - if not notmatchpre[current] then - match = false - end break end end - if not match then - break - end - else - -- skip 'm end - -- maybe only if match - current = getnext(current) - elseif id == glue_code and seq[n][32] and isspace(current,threshold,id) then + else + notmatchreplace[current] = true -- new, for Kai to check + end + current = getnext(current) + elseif id == glue_code then + local sn = seq[n] + if (sn[32] and spaces[current]) or sn[0xFFFC] then n = n + 1 current = getnext(current) else - match = false - break + goto next end + elseif seq[n][0xFFFC] then + n = n + 1 + current = getnext(current) else - match = false - break + goto next end + else + goto next end - else - match = false end - end - end - if match then - if trace_contexts then - chaintrac(head,start,dataset,sequence,rlmode,ck,skipped) - end - if diskseen or sweepnode then - head, start, done = chaindisk(head,start,dataset,sequence,rlmode,ck,skipped) else - head, start, done = chainrun(head,start,last,dataset,sequence,rlmode,ck,skipped) - end - if done then - break -- out of contexts (new, needs checking) + goto next end end + + if trace_contexts then + chaintrac(head,start,dataset,sequence,rlmode,skipped and skiphash,ck,true,discseen,sweepnode) + end + if discseen or sweepnode then + head, start, done = chaindisk(head,start,dataset,sequence,rlmode,skipped and skiphash,ck) + else + head, start, done = chainrun(head,start,last,dataset,sequence,rlmode,skipped and skiphash,ck) + end + if done then + break + -- else + -- next context + end + ::next:: + -- if trace_chains then + -- chaintrac(head,start,dataset,sequence,rlmode,skipped and skiphash,ck,false,discseen,sweepnode) + -- end end - if diskseen then + if discseen then notmatchpre = { } notmatchpost = { } notmatchreplace = { } @@ -2833,13 +2814,19 @@ handlers.gpos_context = handle_contextchain -- this needs testing -local function chained_contextchain(head,start,stop,dataset,sequence,currentlookup,rlmode) +local function chained_contextchain(head,start,stop,dataset,sequence,currentlookup,rlmode,skiphash) local steps = currentlookup.steps local nofsteps = currentlookup.nofsteps if nofsteps > 1 then reportmoresteps(dataset,sequence) end - return handle_contextchain(head,start,dataset,sequence,currentlookup,rlmode) + -- probably wrong + local l = steps[1].coverage[getchar(start)] + if l then + return handle_contextchain(head,start,dataset,sequence,l,rlmode,skiphash) + else + return head, start, false + end end chainprocs.gsub_context = chained_contextchain @@ -2848,16 +2835,18 @@ chainprocs.gsub_reversecontextchain = chained_contextchain chainprocs.gpos_contextchain = chained_contextchain chainprocs.gpos_context = chained_contextchain +------------------------------ + -- experiment (needs no handler in font-otc so not now): -- -- function otf.registerchainproc(name,f) -- -- chainprocs[name] = f --- chainprocs[name] = function(head,start,stop,dataset,sequence,currentlookup,rlmode) +-- chainprocs[name] = function(head,start,stop,dataset,sequence,currentlookup,rlmode,skiphash) -- local done = currentlookup.nofsteps > 0 -- if not done then -- reportzerosteps(dataset,sequence) -- else --- head, start, done = f(head,start,stop,dataset,sequence,currentlookup,rlmode) +-- head, start, done = f(head,start,stop,dataset,sequence,currentlookup,rlmode,skiphash) -- if not head or not start then -- reportbadsteps(dataset,sequence) -- end @@ -2866,28 +2855,20 @@ chainprocs.gpos_context = chained_contextchain -- end -- end -local missing = setmetatableindex("table") +local missing = setmetatableindex("table") +local logwarning = report_process +local resolved = { } -- we only resolve a font,script,language pair once local function logprocess(...) if trace_steps then registermessage(...) + if trace_steps == "silent" then + return + end end report_process(...) end -local logwarning = report_process - -local function report_missing_coverage(dataset,sequence) - local t = missing[currentfont] - if not t[sequence] then - t[sequence] = true - logwarning("missing coverage for feature %a, lookup %a, type %a, font %a, name %a", - dataset[4],sequence.name,sequence.type,currentfont,tfmdata.properties.fullname) - end -end - -local resolved = { } -- we only resolve a font,script,language pair once - -- todo: pass all these 'locals' in a table local sequencelists = setmetatableindex(function(t,font) @@ -3038,14 +3019,12 @@ local function kernrun(disc,k_run,font,attr,...) done = true end if prev then - local nest = getprev(pre) setlink(prev,pre) if k_run(prevmarks,"preinjections",pre,font,attr,...) then -- or prev? done = true end - setprev(pre,nest) --- setprev(pre) - setnext(prev,disc) + setprev(pre) + setlink(prev,disc) end end -- @@ -3059,7 +3038,7 @@ local function kernrun(disc,k_run,font,attr,...) done = true end setnext(posttail) - setprev(next,disc) + setlink(disc,next) end end -- @@ -3068,14 +3047,12 @@ local function kernrun(disc,k_run,font,attr,...) done = true end if prev then - local nest = getprev(replace) setlink(prev,replace) if k_run(prevmarks,"replaceinjections",replace,font,attr,...) then -- getnext(replace)) done = true end - setprev(replace,nest) - -- setprev(replace) - setnext(prev,disc) + setprev(replace) + setlink(prev,disc) end if next then setlink(replacetail,next) @@ -3083,7 +3060,7 @@ local function kernrun(disc,k_run,font,attr,...) done = true end setnext(replacetail) - setprev(next,disc) + setlink(disc,next) end elseif prev and next then setlink(prev,next) @@ -3092,6 +3069,9 @@ local function kernrun(disc,k_run,font,attr,...) end setlink(prev,disc,next) end + if done and trace_testruns then + report_disc("done",disc) + end return nextstart, done end @@ -3108,7 +3088,7 @@ local function comprun(disc,c_run,...) -- vararg faster than the whole list -- if pre then sweepnode = disc - sweeptype = "pre" -- in alternative code preinjections is uc_c_sed (also used then for properties, saves a variable) + sweeptype = "pre" -- in alternative code preinjections is used (also used then for properties, saves a variable) local new, done = c_run(pre,...) if done then pre = new @@ -3139,88 +3119,17 @@ local function comprun(disc,c_run,...) -- vararg faster than the whole list sweepnode = nil sweeptype = nil if renewed then + if trace_testruns then + report_disc("done",disc) + end setdisc(disc,pre,post,replace) end -- return getnext(disc), renewed end --- local function testrun(disc,t_run,c_run,...) --- if trace_testruns then --- report_disc("test",disc) --- end --- local prev, next = getboth(disc) --- if not next then --- -- weird discretionary --- return --- end --- local pre, post, replace, pretail, posttail, replacetail = getdisc(disc,true) --- local done = false --- if replace and prev then --- -- this is a bit strange as we only do replace here and not post --- -- anyway, we only look ahead ... the idea is that we discard a --- -- disc when there is a ligature crossing the replace boundary --- setlink(replacetail,next) --- local ok, overflow = t_run(replace,next,...) --- if ok and overflow then --- -- so, we can have crossed the boundary --- setfield(disc,"replace") --- setlink(prev,replace) --- -- setlink(replacetail,next) --- setboth(disc) --- flush_node_list(disc) --- return replace, true -- restart .. tricky ! --- else --- -- we stay inside the disc --- setnext(replacetail) --- setprev(next,disc) --- end --- -- pre, post, replace, pretail, posttail, replacetail = getdisc(disc,true) --- end --- -- --- -- like comprun --- -- --- local renewed = false --- -- --- if pre then --- sweepnode = disc --- sweeptype = "pre" --- local new, ok = c_run(pre,...) --- if ok then --- pre = new --- renewed = true --- end --- end --- -- --- if post then --- sweepnode = disc --- sweeptype = "post" --- local new, ok = c_run(post,...) --- if ok then --- post = new --- renewed = true --- end --- end --- -- --- if replace then --- sweepnode = disc --- sweeptype = "replace" --- local new, ok = c_run(replace,...) --- if ok then --- replace = new --- renewed = true --- end --- end --- -- --- sweepnode = nil --- sweeptype = nil --- if renewed then --- setdisc(disc,pre,post,replace) --- return next, true --- else --- return next, done --- end --- end +-- if we can hyphenate in a lig then unlikely a lig so we +-- could have a option here to ignore lig local function testrun(disc,t_run,c_run,...) if trace_testruns then @@ -3232,7 +3141,7 @@ local function testrun(disc,t_run,c_run,...) return end local pre, post, replace, pretail, posttail, replacetail = getdisc(disc,true) - local done = false + local renewed = false if (post or replace) and prev then if post then setlink(posttail,next) @@ -3246,19 +3155,23 @@ local function testrun(disc,t_run,c_run,...) end local d_post = t_run(post,next,...) local d_replace = t_run(replace,next,...) - if (d_post and d_post > 0) or (d_replace and d_replace > 0) then - local d = d_replace or d_post - if d_post and d < d_post then - d = d_post - end - local head, tail = getnext(disc), disc + if d_post > 0 or d_replace > 0 then + local d = d_replace > d_post and d_replace or d_post + local head = getnext(disc) -- is: next + local tail = head for i=1,d do - tail = getnext(tail) - if getid(tail) == disc_code then - head, tail = flattendisk(head,tail) + local nx = getnext(tail) + local id = getid(nx) + if id == disc_code then + head, tail = flattendisk(head,nx) + elseif id == glyph_code then + tail = nx + else + -- we can have overrun into a glue + break end end - local next = getnext(tail) + next = getnext(tail) setnext(tail) setprev(head) local new = copy_node_list(head) @@ -3272,7 +3185,6 @@ local function testrun(disc,t_run,c_run,...) else replace = new end - setlink(disc,next) else -- we stay inside the disc if posttail then @@ -3280,20 +3192,21 @@ local function testrun(disc,t_run,c_run,...) else post = nil end - setnext(replacetail) if replacetail then setnext(replacetail) else replace = nil end - setprev(next,disc) end + setlink(disc,next) -- pre, post, replace, pretail, posttail, replacetail = getdisc(disc,true) end -- -- like comprun -- - local renewed = false + if trace_testruns then + report_disc("more",disc) + end -- if pre then sweepnode = disc @@ -3329,10 +3242,12 @@ local function testrun(disc,t_run,c_run,...) sweeptype = nil if renewed then setdisc(disc,pre,post,replace) - return next, true - else - return next, done + if trace_testruns then + report_disc("done",disc) + end end + -- next can have changed (copied list) + return getnext(disc), renewed end -- We can make some assumptions with respect to discretionaries. First of all it is very @@ -3360,20 +3275,20 @@ end -- 1{2{\oldstyle\discretionary{3}{4}{5}}6}7\par -- 1{2\discretionary{3{\oldstyle3}}{{\oldstyle4}4}{5{\oldstyle5}5}6}7\par - local nesting = 0 -local function c_run_single(head,font,attr,lookupcache,step,dataset,sequence,rlmode,handler) +local function c_run_single(head,font,attr,lookupcache,step,dataset,sequence,rlmode,skiphash,handler) local done = false local sweep = sweephead[head] if sweep then start = sweep - sweephead[head] = nil + -- sweephead[head] = nil + sweephead[head] = false else start = head end while start do - local char = ischar(start,font) + local char, id = ischar(start,font) if char then local a -- happens often so no assignment is faster if attr then @@ -3383,7 +3298,7 @@ local function c_run_single(head,font,attr,lookupcache,step,dataset,sequence,rlm local lookupmatch = lookupcache[char] if lookupmatch then local ok - head, start, ok = handler(head,start,dataset,sequence,lookupmatch,rlmode,step,1) + head, start, ok = handler(head,start,dataset,sequence,lookupmatch,rlmode,skiphash,step) if ok then done = true end @@ -3408,49 +3323,7 @@ local function c_run_single(head,font,attr,lookupcache,step,dataset,sequence,rlm return head, done end --- local function t_run_single(start,stop,font,attr,lookupcache) --- while start ~= stop do --- local char = ischar(start,font) --- if char then --- local a -- happens often so no assignment is faster --- if attr then --- a = getattr(start,0) --- end --- local startnext = getnext(start) --- if not a or (a == attr) then --- local lookupmatch = lookupcache[char] --- if lookupmatch then -- hm, hyphens can match (tlig) so we need to really check --- -- if we need more than ligatures we can outline the code and use functions --- local s = startnext --- local l = nil --- local d = 0 --- while s do --- if s == stop then --- d = 1 --- elseif d > 0 then --- d = d + 1 --- end --- local lg = lookupmatch[getchar(s)] --- if lg then --- l = lg --- s = getnext(s) --- else --- break --- end --- end --- if l and l.ligature then --- return true, d > 1 --- end --- end --- else --- -- go on can be a mixed one --- end --- start = starttnext --- else --- break --- end --- end --- end +-- only replace? local function t_run_single(start,stop,font,attr,lookupcache) local lastd = nil @@ -3473,6 +3346,8 @@ local function t_run_single(start,stop,font,attr,lookupcache) s = ss ss = nil end + -- a bit weird: why multiple ... anyway we can't have a disc in a disc + -- how about post ... we can probably merge this into the while while getid(s) == disc_code do ss = getnext(s) s = getfield(s,"replace") @@ -3484,38 +3359,48 @@ local function t_run_single(start,stop,font,attr,lookupcache) local l = nil local d = 0 while s do - local lg = lookupmatch[getchar(s)] - if lg then - if sstop then - d = 1 - elseif d > 0 then - d = d + 1 - end - l = lg - s = getnext(s) - sstop = s == stop - if not s then - s = ss - ss = nil - end - while getid(s) == disc_code do - ss = getnext(s) - s = getfield(s,"replace") + local char = ischar(s,font) + if char then + local lg = lookupmatch[char] + if lg then + if sstop then + d = 1 + elseif d > 0 then + d = d + 1 + end + l = lg + s = getnext(s) + sstop = s == stop if not s then s = ss ss = nil end + while getid(s) == disc_code do + ss = getnext(s) + s = getfield(s,"replace") + if not s then + s = ss + ss = nil + end + end + else + break end else break end end - if l and l.ligature then + if l and l.ligature then -- so we test for ligature lastd = d end +-- why not: if not l then break elseif l.ligature then return d end + else +-- why not: break + -- no match (yet) end else -- go on can be a mixed one +-- why not: break end if lastd then return lastd @@ -3525,9 +3410,10 @@ local function t_run_single(start,stop,font,attr,lookupcache) break end end + return 0 end -local function k_run_single(sub,injection,last,font,attr,lookupcache,step,dataset,sequence,rlmode,handler) +local function k_run_single(sub,injection,last,font,attr,lookupcache,step,dataset,sequence,rlmode,skiphash,handler) local a -- happens often so no assignment is faster if attr then a = getattr(sub,0) @@ -3541,7 +3427,7 @@ local function k_run_single(sub,injection,last,font,attr,lookupcache,step,datase if char then local lookupmatch = lookupcache[char] if lookupmatch then - local h, d, ok = handler(sub,n,dataset,sequence,lookupmatch,rlmode,step,1,injection) + local h, d, ok = handler(sub,n,dataset,sequence,lookupmatch,rlmode,skiphash,step,injection) if ok then return true end @@ -3551,12 +3437,13 @@ local function k_run_single(sub,injection,last,font,attr,lookupcache,step,datase end end -local function c_run_multiple(head,font,attr,steps,nofsteps,dataset,sequence,rlmode,handler) +local function c_run_multiple(head,font,attr,steps,nofsteps,dataset,sequence,rlmode,skiphash,handler) local done = false local sweep = sweephead[head] if sweep then start = sweep - sweephead[head] = nil + -- sweephead[head] = nil + sweephead[head] = false else start = head end @@ -3571,22 +3458,18 @@ local function c_run_multiple(head,font,attr,steps,nofsteps,dataset,sequence,rlm for i=1,nofsteps do local step = steps[i] local lookupcache = step.coverage - if lookupcache then - local lookupmatch = lookupcache[char] - if lookupmatch then - -- we could move all code inline but that makes things even more unreadable - local ok - head, start, ok = handler(head,start,dataset,sequence,lookupmatch,rlmode,step,i) - if ok then - done = true - break - elseif not start then - -- don't ask why ... shouldn't happen - break - end + local lookupmatch = lookupcache[char] + if lookupmatch then + -- we could move all code inline but that makes things even more unreadable + local ok + head, start, ok = handler(head,start,dataset,sequence,lookupmatch,rlmode,skiphash,step) + if ok then + done = true + break + elseif not start then + -- don't ask why ... shouldn't happen + break end - else - report_missing_coverage(dataset,sequence) end end if start then @@ -3610,58 +3493,6 @@ local function c_run_multiple(head,font,attr,steps,nofsteps,dataset,sequence,rlm return head, done end --- local function t_run_multiple(start,stop,font,attr,steps,nofsteps) --- while start ~= stop do --- local char = ischar(start,font) --- if char then --- local a -- happens often so no assignment is faster --- if attr then --- a = getattr(start,0) --- end --- local startnext = getnext(start) --- if not a or (a == attr) then --- for i=1,nofsteps do --- local step = steps[i] --- local lookupcache = step.coverage --- if lookupcache then --- local lookupmatch = lookupcache[char] --- if lookupmatch then --- -- if we need more than ligatures we can outline the code and use functions --- local s = startnext --- local l = nil --- local d = 0 --- while s do --- if s == stop then --- d = 1 --- elseif d > 0 then --- d = d + 1 --- end --- local lg = lookupmatch[getchar(s)] --- if lg then --- l = lg --- s = getnext(s) --- else --- break --- end --- end --- if l and l.ligature then --- return true, d > 1 --- end --- end --- else --- report_missing_coverage(dataset,sequence) --- end --- end --- else --- -- go on can be a mixed one --- end --- start = startnext --- else --- break --- end --- end --- end - local function t_run_multiple(start,stop,font,attr,steps,nofsteps) local lastd = nil while start ~= stop do @@ -3676,29 +3507,30 @@ local function t_run_multiple(start,stop,font,attr,steps,nofsteps) for i=1,nofsteps do local step = steps[i] local lookupcache = step.coverage - if lookupcache then - local lookupmatch = lookupcache[char] - if lookupmatch then - -- if we need more than ligatures we can outline the code and use functions - local s = startnext - local ss = nil - local sstop = s == stop + local lookupmatch = lookupcache[char] + if lookupmatch then + -- if we need more than ligatures we can outline the code and use functions + local s = startnext + local ss = nil + local sstop = s == stop + if not s then + s = ss + ss = nil + end + while getid(s) == disc_code do + ss = getnext(s) + s = getfield(s,"replace") if not s then s = ss ss = nil end - while getid(s) == disc_code do - ss = getnext(s) - s = getfield(s,"replace") - if not s then - s = ss - ss = nil - end - end - local l = nil - local d = 0 - while s do - local lg = lookupmatch[getchar(s)] + end + local l = nil + local d = 0 + while s do + local char = ischar(s) + if char then + local lg = lookupmatch[char] if lg then if sstop then d = 1 @@ -3723,13 +3555,13 @@ local function t_run_multiple(start,stop,font,attr,steps,nofsteps) else break end - end - if l and l.ligature then - lastd = d + else + break end end - else - report_missing_coverage(dataset,sequence) + if l and l.ligature then + lastd = d + end end end else @@ -3743,9 +3575,10 @@ local function t_run_multiple(start,stop,font,attr,steps,nofsteps) break end end + return 0 end -local function k_run_multiple(sub,injection,last,font,attr,steps,nofsteps,dataset,sequence,rlmode,handler) +local function k_run_multiple(sub,injection,last,font,attr,steps,nofsteps,dataset,sequence,rlmode,skiphash,handler) local a -- happens often so no assignment is faster if attr then a = getattr(sub,0) @@ -3760,16 +3593,12 @@ local function k_run_multiple(sub,injection,last,font,attr,steps,nofsteps,datase for i=1,nofsteps do local step = steps[i] local lookupcache = step.coverage - if lookupcache then - local lookupmatch = lookupcache[char] - if lookupmatch then - local h, d, ok = handler(head,n,dataset,sequence,lookupmatch,step,rlmode,i,injection) - if ok then - return true - end + local lookupmatch = lookupcache[char] + if lookupmatch then + local h, d, ok = handler(sub,n,dataset,sequence,lookupmatch,rlmode,skiphash,step,injection) -- sub was head + if ok then + return true end - else - report_missing_coverage(dataset,sequence) end end end @@ -3780,44 +3609,127 @@ end -- to be checked, nowadays we probably can assume properly matched directions -- so maybe we no longer need a stack +-- local function txtdirstate(start,stack,top,rlparmode) +-- local dir = getdir(start) +-- local new = 1 +-- if dir == "+TRT" then +-- top = top + 1 +-- stack[top] = dir +-- new = -1 +-- elseif dir == "+TLT" then +-- top = top + 1 +-- stack[top] = dir +-- elseif dir == "-TRT" or dir == "-TLT" then +-- if top == 1 then +-- top = 0 +-- new = rlparmode +-- else +-- top = top - 1 +-- if stack[top] == "+TRT" then +-- new = -1 +-- end +-- end +-- else +-- new = rlparmode +-- end +-- return getnext(start), top, new +-- end +-- +-- local function pardirstate(start) +-- local dir = getdir(start) +-- local new = 0 +-- if dir == "TLT" then +-- new = 1 +-- elseif dir == "TRT" then +-- new = -1 +-- end +-- return getnext(start), new, new +-- end + local function txtdirstate(start,stack,top,rlparmode) + local nxt = getnext(start) local dir = getdir(start) - local new = 1 if dir == "+TRT" then top = top + 1 stack[top] = dir - new = -1 + return nxt, top, -1 elseif dir == "+TLT" then top = top + 1 stack[top] = dir + return nxt, top, 1 elseif dir == "-TRT" or dir == "-TLT" then - top = top - 1 - if stack[top] == "+TRT" then - new = -1 + if top == 1 then + return nxt, 0, rlparmode + else + top = top - 1 + if stack[top] == "+TRT" then + return nxt, top, -1 + else + return nxt, top, 1 + end end else - new = rlparmode + return nxt, top, rlparmode end - if trace_directions then - report_process("directions after txtdir %a: parmode %a, txtmode %a, level %a",dir,mref(rlparmode),mref(new),top) - end - return getnext(start), top, new end local function pardirstate(start) + local nxt = getnext(start) local dir = getdir(start) - local new = 0 if dir == "TLT" then - new = 1 + return nxt, 1, 1 elseif dir == "TRT" then - new = -1 - end - if trace_directions then - report_process("directions after pardir %a: parmode %a",dir,mref(new)) + return nxt, -1, -1 + else + return nxt, 0, 0 end - return getnext(start), new, new end +-- -- this will become: +-- +-- local getdirection = nuts.getdirection +-- +-- local function txtdirstate1(start,stack,top,rlparmode) +-- local nxt = getnext(start) +-- local dir, sub = getdirection(start) +-- if sub then +-- if top == 1 then +-- return nxt, 0, rlparmode +-- elseif dir < 2 then +-- top = top - 1 +-- if stack[top] == 1 then +-- return nxt, top, -1 +-- else +-- return nxt, top, 1 +-- end +-- else +-- return nxt, top, rlparmode +-- end +-- elseif dir == 1 then +-- top = top + 1 +-- stack[top] = 1 +-- return nxt, top, -1 +-- elseif dir == 0 then +-- top = top + 1 +-- stack[top] = 0 +-- return nxt, top, 1 +-- else +-- return nxt, top, rlparmode +-- end +-- end +-- +-- local function pardirstate1(start) +-- local nxt = getnext(start) +-- local dir = getdirection(start) +-- if dir == 0 then +-- return nxt, 1, 1 +-- elseif dir == 1 then +-- return nxt, -1, -1 +-- else +-- return nxt, 0, 0 +-- end +-- end + otf.helpers = otf.helpers or { } otf.helpers.txtdirstate = txtdirstate otf.helpers.pardirstate = pardirstate @@ -3830,182 +3742,245 @@ otf.helpers.pardirstate = pardirstate -- optimizations the principles of processing the features hasn't changed much since -- the beginning. -local function featuresprocessor(head,font,attr,direction) - - local sequences = sequencelists[font] -- temp hack +do - if not sequencelists then - return head, false - end - - nesting = nesting + 1 - - if nesting == 1 then - currentfont = font - tfmdata = fontdata[font] - descriptions = tfmdata.descriptions -- only needed in gref so we could pass node there instead - characters = tfmdata.characters -- but this branch is not entered that often anyway - local resources = tfmdata.resources - marks = resources.marks - classes = resources.classes - threshold, - factor = getthreshold(font) - checkmarks = tfmdata.properties.checkmarks + -- reference: + -- + -- local a = attr and getattr(start,0) + -- if a then + -- a = (a == attr) and (not attribute or getprop(start,a_state) == attribute) + -- else + -- a = not attribute or getprop(start,a_state) == attribute + -- end + -- + -- used: + -- + -- local a -- happens often so no assignment is faster + -- if attr then + -- if getattr(start,0) == attr and (not attribute or getprop(start,a_state) == attribute) then + -- a = true + -- end + -- elseif not attribute or getprop(start,a_state) == attribute then + -- a = true + -- end + + -- This is a measurable experimental speedup (only with hyphenated text and multiple + -- fonts per processor call), especially for fonts with lots of contextual lookups. + + local fastdisc = true + local testdics = false + + directives.register("otf.fastdisc",function(v) fastdisc = v end) -- normally enabled + + -- using a merged combined hash as first test saves some 30% on ebgaramond and + -- about 15% on arabtype .. then moving the a test also saves a bit (even when + -- often a is not set at all so that one is a bit debatable + + local otfdataset = nil -- todo: make an installer + + local getfastdisc = { __index = function(t,k) + local v = usesfont(k,currentfont) + t[k] = v + return v + end } + + local getfastspace = { __index = function(t,k) + -- we don't pass the id so that one can overload isspace + local v = isspace(k,threshold) or false + t[k] = v + return v + end } + + function otf.featuresprocessor(head,font,attr,direction,n) + + local sequences = sequencelists[font] -- temp hack + + nesting = nesting + 1 + + if nesting == 1 then + currentfont = font + tfmdata = fontdata[font] + descriptions = tfmdata.descriptions -- only needed in gref so we could pass node there instead + characters = tfmdata.characters -- but this branch is not entered that often anyway + local resources = tfmdata.resources + marks = resources.marks + classes = resources.classes + threshold, + factor = getthreshold(font) + checkmarks = tfmdata.properties.checkmarks + + if not otfdataset then + otfdataset = otf.dataset + end - elseif currentfont ~= font then + discs = fastdisc and n and n > 1 and setmetatable({},getfastdisc) -- maybe inline + spaces = setmetatable({},getfastspace) - report_warning("nested call with a different font, level %s, quitting",nesting) - nesting = nesting - 1 - return head, false + elseif currentfont ~= font then - end + report_warning("nested call with a different font, level %s, quitting",nesting) + nesting = nesting - 1 + return head, false - -- some 10% faster when no dynamics but hardly measureable on real runs .. but: it only - -- works when we have no other dynamics as otherwise the zero run will be applied to the - -- whole stream for which we then need to pass another variable which we won't + end - -- if attr == 0 then - -- attr = false - -- end + -- some 10% faster when no dynamics but hardly measureable on real runs .. but: it only + -- works when we have no other dynamics as otherwise the zero run will be applied to the + -- whole stream for which we then need to pass another variable which we won't - head = tonut(head) + -- if attr == 0 then + -- attr = false + -- end - if trace_steps then - checkstep(head) - end + local head = tonut(head) - local initialrl = direction == "TRT" and -1 or 0 + if trace_steps then + checkstep(head) + end - local done = false - local datasets = otf.dataset(tfmdata,font,attr) - local dirstack = { } -- could move outside function but we can have local runs - sweephead = { } - - -- Keeping track of the headnode is needed for devanagari. (I generalized it a bit - -- so that multiple cases are also covered.) We could prepend a temp node. - - -- We don't goto the next node when a disc node is created so that we can then treat - -- the pre, post and replace. It's a bit of a hack but works out ok for most cases. - - for s=1,#datasets do - local dataset = datasets[s] - ----- featurevalue = dataset[1] -- todo: pass to function instead of using a global - local attribute = dataset[2] - local sequence = dataset[3] -- sequences[s] -- also dataset[5] - local rlparmode = initialrl - local topstack = 0 - local typ = sequence.type - local gpossing = typ == "gpos_single" or typ == "gpos_pair" -- store in dataset - local handler = handlers[typ] - local steps = sequence.steps - local nofsteps = sequence.nofsteps - if not steps then - -- this permits injection, watch the different arguments - local h, d, ok = handler(head,head,dataset,sequence,nil,nil,nil,0,font,attr) - if ok then - done = true - if h then + local initialrl = direction == "TRT" and -1 or 0 + -- local initialrl = (direction == 1 or direction == "TRT") and -1 or 0 + + local done = false + -- local datasets = otf.dataset(tfmdata,font,attr) + local datasets = otfdataset(tfmdata,font,attr) + local dirstack = { } -- could move outside function but we can have local runs + sweephead = { } + + -- Keeping track of the headnode is needed for devanagari. (I generalized it a bit + -- so that multiple cases are also covered.) We could prepend a temp node. + + -- We don't goto the next node when a disc node is created so that we can then treat + -- the pre, post and replace. It's a bit of a hack but works out ok for most cases. + + for s=1,#datasets do + local dataset = datasets[s] + local attribute = dataset[2] + local sequence = dataset[3] -- sequences[s] -- also dataset[5] + local rlparmode = initialrl + local topstack = 0 + local typ = sequence.type + local gpossing = typ == "gpos_single" or typ == "gpos_pair" -- store in dataset + local forcetestrun = typ == "gsub_ligature" -- testrun is only for ligatures + local handler = handlers[typ] -- store in dataset + local steps = sequence.steps + local nofsteps = sequence.nofsteps + local skiphash = sequence.skiphash + + if not steps then + -- This permits injection, watch the different arguments. Watch out, the arguments passed + -- are not frozen as we might extend or change this. Is this used at all apart from some + -- experiments? + local h, ok = handler(head,dataset,sequence,initialrl,font,attr) -- less arguments now + if ok then + done = true + end + if h and h ~= head then head = h end - end - elseif typ == "gsub_reversecontextchain" then - -- this is a limited case, no special treatments like 'init' etc - local start = find_node_tail(head) - local rlmode = 0 -- how important is this .. do we need to check for dir? - while start do - local char = ischar(start,font) - if char then - local a -- happens often so no assignment is faster - if attr then - a = getattr(start,0) - end - if not a or (a == attr) then - for i=1,nofsteps do - local step = steps[i] - local lookupcache = step.coverage - if lookupcache then - local lookupmatch = lookupcache[char] - if lookupmatch then - -- todo: disc? - local ok - head, start, ok = handler(head,start,dataset,sequence,lookupmatch,rlmode,step,i) - if ok then - done = true - break + elseif typ == "gsub_reversecontextchain" then + -- + -- This might need a check: if we have #before or #after > 0 then we might need to reverse + -- the before and after lists in the loader. But first I need to see a font that uses multiple + -- matches. + -- + local start = find_node_tail(head) + local rlmode = 0 -- how important is this .. do we need to check for dir? + local merged = steps.merged + while start do + local char = ischar(start,font) + if char then + local m = merged[char] + if m then + local a -- happens often so no assignment is faster + if attr then + a = getattr(start,0) + end + if not a or (a == attr) then + for i=m[1],m[2] do + local step = steps[i] + -- for i=1,#m do + -- local step = m[i] + local lookupcache = step.coverage + local lookupmatch = lookupcache[char] + if lookupmatch then + local ok + head, start, ok = handler(head,start,dataset,sequence,lookupmatch,rlmode,skiphash,step) + if ok then + done = true + break + end end end + if start then + start = getprev(start) + end else - report_missing_coverage(dataset,sequence) + start = getprev(start) end - end - if start then + else start = getprev(start) end else start = getprev(start) end - else - start = getprev(start) end - end - else - local start = head - local rlmode = initialrl - if nofsteps == 1 then -- happens often - local step = steps[1] - local lookupcache = step.coverage - if not lookupcache then - report_missing_coverage(dataset,sequence) - else + else + local start = head + local rlmode = initialrl + if nofsteps == 1 then -- happens often + local step = steps[1] + local lookupcache = step.coverage while start do local char, id = ischar(start,font) if char then - -- local a = attr and getattr(start,0) - -- if a then - -- a = (a == attr) and (not attribute or getprop(start,a_state) == attribute) - -- else - -- a = not attribute or getprop(start,a_state) == attribute - -- end - local a -- happens often so no assignment is faster - if attr then - if getattr(start,0) == attr and (not attribute or getprop(start,a_state) == attribute) then - a = true - end - elseif not attribute or getprop(start,a_state) == attribute then - a = true - end - if a then + if skiphash and skiphash[char] then -- we never needed it here but let's try + start = getnext(start) + else local lookupmatch = lookupcache[char] if lookupmatch then - local ok - head, start, ok = handler(head,start,dataset,sequence,lookupmatch,rlmode,step,1) - if ok then - done = true + local a -- happens often so no assignment is faster + if attr then + if getattr(start,0) == attr and (not attribute or getprop(start,a_state) == attribute) then + a = true + end + elseif not attribute or getprop(start,a_state) == attribute then + a = true end + if a then + local ok + head, start, ok = handler(head,start,dataset,sequence,lookupmatch,rlmode,skiphash,step) + if ok then + done = true + end + if start then + start = getnext(start) + end + else + start = getnext(start) + end + else + start = getnext(start) end - if start then - start = getnext(start) - end - else - start = getnext(start) end - elseif char == false then - -- whatever glyph - start = getnext(start) - elseif id == glue_code then - -- happens often - start = getnext(start) + elseif char == false or id == glue_code then + -- a different font|state or glue (happens often) + start = getnext(start) elseif id == disc_code then - local ok - if gpossing then - start, ok = kernrun(start,k_run_single, font,attr,lookupcache,step,dataset,sequence,rlmode,handler) - elseif typ == "gsub_ligature" then - start, ok = testrun(start,t_run_single,c_run_single,font,attr,lookupcache,step,dataset,sequence,rlmode,handler) + if not discs or discs[start] == true then + local ok + if gpossing then + start, ok = kernrun(start,k_run_single, font,attr,lookupcache,step,dataset,sequence,rlmode,skiphash,handler) + elseif forcetestrun then + start, ok = testrun(start,t_run_single,c_run_single,font,attr,lookupcache,step,dataset,sequence,rlmode,skiphash,handler) + else + start, ok = comprun(start,c_run_single, font,attr,lookupcache,step,dataset,sequence,rlmode,skiphash,handler) + end + if ok then + done = true + end else - start, ok = comprun(start,c_run_single, font,attr,lookupcache,step,dataset,sequence,rlmode,handler) - end - if ok then - done = true + start = getnext(start) end elseif id == math_code then start = getnext(end_of_math(start)) @@ -4017,95 +3992,200 @@ local function featuresprocessor(head,font,attr,direction) start = getnext(start) end end - end - - else - while start do - local char, id = ischar(start,font) - if char then - -- local a = attr and getattr(start,0) - -- if a then - -- a = (a == attr) and (not attribute or getprop(start,a_state) == attribute) - -- else - -- a = not attribute or getprop(start,a_state) == attribute - -- end - local a -- happens often so no assignment is faster - if attr then - if getattr(start,0) == attr and (not attribute or getprop(start,a_state) == attribute) then - a = true - end - elseif not attribute or getprop(start,a_state) == attribute then - a = true - end - if a then - for i=1,nofsteps do - local step = steps[i] - local lookupcache = step.coverage - if lookupcache then - local lookupmatch = lookupcache[char] - if lookupmatch then - -- we could move all code inline but that makes things even more unreadable - local ok - head, start, ok = handler(head,start,dataset,sequence,lookupmatch,rlmode,step,i) - if ok then - done = true - break - elseif not start then - -- don't ask why ... shouldn't happen - break + else + local merged = steps.merged + while start do + local char, id = ischar(start,font) + if char then + if skiphash and skiphash[char] then -- we never needed it here but let's try + start = getnext(start) + else + local m = merged[char] + if m then + local a -- happens often so no assignment is faster + if attr then + if getattr(start,0) == attr and (not attribute or getprop(start,a_state) == attribute) then + a = true + end + elseif not attribute or getprop(start,a_state) == attribute then + a = true + end + if a then + for i=m[1],m[2] do + local step = steps[i] + -- for i=1,#m do + -- local step = m[i] + local lookupcache = step.coverage + local lookupmatch = lookupcache[char] + if lookupmatch then + -- we could move all code inline but that makes things even more unreadable + local ok + head, start, ok = handler(head,start,dataset,sequence,lookupmatch,rlmode,skiphash,step) + if ok then + done = true + break + elseif not start then + -- don't ask why ... shouldn't happen + break + end + end + end + if start then + start = getnext(start) end + else + start = getnext(start) end else - report_missing_coverage(dataset,sequence) + start = getnext(start) end end - if start then + elseif char == false or id == glue_code then + -- a different font|state or glue (happens often) + start = getnext(start) + elseif id == disc_code then + if not discs or discs[start] == true then + local ok + if gpossing then + start, ok = kernrun(start,k_run_multiple, font,attr,steps,nofsteps,dataset,sequence,rlmode,skiphash,handler) + elseif forcetestrun then + start, ok = testrun(start,t_run_multiple,c_run_multiple,font,attr,steps,nofsteps,dataset,sequence,rlmode,skiphash,handler) + else + start, ok = comprun(start,c_run_multiple, font,attr,steps,nofsteps,dataset,sequence,rlmode,skiphash,handler) + end + if ok then + done = true + end + else start = getnext(start) end + elseif id == math_code then + start = getnext(end_of_math(start)) + elseif id == dir_code then + start, topstack, rlmode = txtdirstate(start,dirstack,topstack,rlparmode) + elseif id == localpar_code then + start, rlparmode, rlmode = pardirstate(start) else start = getnext(start) end - elseif char == false then - -- whatever glyph - start = getnext(start) - elseif id == glue_code then - -- happens often + end + end + end + + if trace_steps then -- ? + registerstep(head) + end + + end + + nesting = nesting - 1 + head = tonode(head) + + return head, done + end + + -- This is not an official helpoer and used for tracing experiments. It can be changed as I like + -- at any moment. At some point it might be used in a module that can help font development. + + function otf.datasetpositionprocessor(head,font,direction,dataset) + + currentfont = font + tfmdata = fontdata[font] + descriptions = tfmdata.descriptions -- only needed in gref so we could pass node there instead + characters = tfmdata.characters -- but this branch is not entered that often anyway + local resources = tfmdata.resources + marks = resources.marks + classes = resources.classes + threshold, + factor = getthreshold(font) + checkmarks = tfmdata.properties.checkmarks + + if type(dataset) == "number" then + dataset = otfdataset(tfmdata,font,0)[dataset] + end + + local sequence = dataset[3] -- sequences[s] -- also dataset[5] + local typ = sequence.type + -- local gpossing = typ == "gpos_single" or typ == "gpos_pair" -- store in dataset + + -- gpos_contextchain gpos_context + + -- if not gpossing then + -- return head, false + -- end + + local handler = handlers[typ] -- store in dataset + local steps = sequence.steps + local nofsteps = sequence.nofsteps + + local head = tonut(head) + local done = false + local dirstack = { } -- could move outside function but we can have local runs + local start = head + local initialrl = direction == "TRT" and -1 or 0 + -- local initialrl = (direction == 1 or direction == "TRT") and -1 or 0 + local rlmode = initialrl + local rlparmode = initialrl + local topstack = 0 + local merged = steps.merged + + -- local matches = false + local position = 0 + + while start do + local char, id = ischar(start,font) + if char then + position = position + 1 + local m = merged[char] + if m then + if skiphash and skiphash[char] then -- we never needed it here but let's try start = getnext(start) - elseif id == disc_code then - local ok - if gpossing then - start, ok = kernrun(start,k_run_multiple, font,attr,steps,nofsteps,dataset,sequence,rlmode,handler) - elseif typ == "gsub_ligature" then - start, ok = testrun(start,t_run_multiple,c_run_multiple,font,attr,steps,nofsteps,dataset,sequence,rlmode,handler) - else - start, ok = comprun(start,c_run_multiple, font,attr,steps,nofsteps,dataset,sequence,rlmode,handler) + else + for i=m[1],m[2] do + local step = steps[i] + local lookupcache = step.coverage + local lookupmatch = lookupcache[char] + if lookupmatch then + local ok + head, start, ok = handler(head,start,dataset,sequence,lookupmatch,rlmode,skiphash,step) + if ok then + -- if matches then + -- matches[position] = i + -- else + -- matches = { [position] = i } + -- end + break + elseif not start then + break + end + end end - if ok then - done = true + if start then + start = getnext(start) end - elseif id == math_code then - start = getnext(end_of_math(start)) - elseif id == dir_code then - start, topstack, rlmode = txtdirstate(start,dirstack,topstack,rlparmode) - elseif id == localpar_code then - start, rlparmode, rlmode = pardirstate(start) - else - start = getnext(start) end + else + start = getnext(start) end + elseif char == false or id == glue_code then + -- a different font|state or glue (happens often) + start = getnext(start) + elseif id == math_code then + start = getnext(end_of_math(start)) + elseif id == dir_code then + start, topstack, rlmode = txtdirstate(start,dirstack,topstack,rlparmode) + elseif id == localpar_code then + start, rlparmode, rlmode = pardirstate(start) + else + start = getnext(start) end end - if trace_steps then -- ? - registerstep(head) - end - + return tonode(head) -- , matches end - nesting = nesting - 1 - head = tonode(head) + -- end of experiment - return head, done end -- so far @@ -4119,26 +4199,26 @@ function otf.registerplugin(name,f) end end -local function plugininitializer(tfmdata,value) +function otf.plugininitializer(tfmdata,value) if type(value) == "string" then tfmdata.shared.plugin = plugins[value] end end -local function pluginprocessor(head,font) +function otf.pluginprocessor(head,font,attr,direction) -- n local s = fontdata[font].shared local p = s and s.plugin if p then if trace_plugins then report_process("applying plugin %a",p[1]) end - return p[2](head,font) + return p[2](head,font,attr,direction) else return head, false end end -local function featuresinitializer(tfmdata,value) +function otf.featuresinitializer(tfmdata,value) -- nothing done here any more end @@ -4148,67 +4228,113 @@ registerotffeature { default = true, initializers = { position = 1, - node = featuresinitializer, - plug = plugininitializer, + node = otf.featuresinitializer, + plug = otf.plugininitializer, }, processors = { - node = featuresprocessor, - plug = pluginprocessor, + node = otf.featuresprocessor, + plug = otf.pluginprocessor, } } -otf.nodemodeinitializer = featuresinitializer -otf.featuresprocessor = featuresprocessor - --- This can be used for extra handlers, but should be used with care! - -otf.handlers = handlers -- used in devanagari +-- This can be used for extra handlers, but should be used with care! We implement one +-- here but some more can be found in the osd (script devanagary) file. Now watch out: +-- when a handlers has steps, it is called as the other ones, but when we have no steps, +-- we use a different call: +-- +-- function(head,dataset,sequence,initialrl,font,attr) +-- return head, done +-- end +-- +-- Also see (!!). --- We implement one here: +otf.handlers = handlers local setspacekerns = nodes.injections.setspacekerns if not setspacekerns then os.exit() end +local tag = "kern" -- maybe some day a merge + if fontfeatures then - function otf.handlers.trigger_space_kerns(head,start,dataset,sequence,_,_,_,_,font,attr) + function handlers.trigger_space_kerns(head,dataset,sequence,initialrl,font,attr) local features = fontfeatures[font] - local enabled = features and features.spacekern and features.kern + local enabled = features and features.spacekern and features[tag] if enabled then setspacekerns(font,sequence) end - return head, start, enabled + return head, enabled end else -- generic (no hashes) - function otf.handlers.trigger_space_kerns(head,start,dataset,sequence,_,_,_,_,font,attr) + function handlers.trigger_space_kerns(head,dataset,sequence,initialrl,font,attr) local shared = fontdata[font].shared local features = shared and shared.features - local enabled = features and features.spacekern and features.kern + local enabled = features and features.spacekern and features[tag] if enabled then setspacekerns(font,sequence) end - return head, start, enabled + return head, enabled end end +-- There are fonts out there that change the space but we don't do that kind of +-- things in TeX. + local function hasspacekerns(data) - local sequences = data.resources.sequences - for i=1,#sequences do - local sequence = sequences[i] - local steps = sequence.steps - if steps and sequence.features.kern then - for i=1,#steps do - local coverage = steps[i].coverage - if not coverage then - -- maybe an issue - elseif coverage[32] then - return true - else - for k, v in next, coverage do - if v[32] then - return true + local resources = data.resources + local sequences = resources.sequences + local validgpos = resources.features.gpos + if validgpos and sequences then + for i=1,#sequences do + local sequence = sequences[i] + local steps = sequence.steps + if steps and sequence.features[tag] then + local kind = sequence.type + if kind == "gpos_pair" or kind == "gpos_single" then + for i=1,#steps do + local step = steps[i] + local coverage = step.coverage + local rules = step.rules + if rules then + -- not now: analyze (simple) rules + elseif not coverage then + -- nothing to do + elseif kind == "gpos_single" then + -- maybe a message that we ignore + elseif kind == "gpos_pair" then + local format = step.format + if format == "move" or format == "kern" then + local kerns = coverage[32] + if kerns then + return true + end + for k, v in next, coverage do + if v[32] then + return true + end + end + elseif format == "pair" then + local kerns = coverage[32] + if kerns then + for k, v in next, kerns do + local one = v[1] + if one and one ~= true then + return true + end + end + end + for k, v in next, coverage do + local kern = v[32] + if kern then + local one = kern[1] + if one and one ~= true then + return true + end + end + end + end end end end @@ -4225,112 +4351,120 @@ otf.readers.registerextender { end } --- we merge the lookups but we still honor the language / script - local function spaceinitializer(tfmdata,value) -- attr local resources = tfmdata.resources local spacekerns = resources and resources.spacekerns - local properties = tfmdata.properties if value and spacekerns == nil then + local rawdata = tfmdata.shared and tfmdata.shared.rawdata + local properties = rawdata.properties if properties and properties.hasspacekerns then local sequences = resources.sequences - local left = { } - local right = { } - local last = 0 - local feat = nil - for i=1,#sequences do - local sequence = sequences[i] - local steps = sequence.steps - if steps then - local kern = sequence.features.kern - if kern then - if feat then - for script, languages in next, kern do - local f = feat[script] - if f then - for l in next, languages do - f[l] = true - end - else - feat[script] = languages - end - end - else - feat = kern - end - for i=1,#steps do - local step = steps[i] - local coverage = step.coverage - local rules = step.rules - local format = step.format - if rules then - -- not now: analyze (simple) rules - elseif coverage then - -- what to do if we have no [1] but only [2] - local single = format == gpos_single - local kerns = coverage[32] - if kerns then - for k, v in next, kerns do - if type(v) ~= "table" then - right[k] = v - elseif single then - right[k] = v[3] - else - local one = v[1] - if one then - right[k] = one[3] + local validgpos = resources.features.gpos + if validgpos and sequences then + local left = { } + local right = { } + local last = 0 + local feat = nil + for i=1,#sequences do + local sequence = sequences[i] + local steps = sequence.steps + if steps then + -- we don't support space kerns in other features + local kern = sequence.features[tag] + if kern then + local kind = sequence.type + if kind == "gpos_pair" or kind == "gpos_single" then + if feat then + for script, languages in next, kern do + local f = feat[script] + if f then + for l in next, languages do + f[l] = true end + else + feat[script] = languages end end + else + feat = kern end - for k, v in next, coverage do - local kern = v[32] - if kern then - if type(kern) ~= "table" then - left[k] = kern - elseif single then - left[k] = kern[3] - else - local one = kern[1] - if one then - left[k] = one[3] + for i=1,#steps do + local step = steps[i] + local coverage = step.coverage + local rules = step.rules + if rules then + -- not now: analyze (simple) rules + elseif not coverage then + -- nothng to do + elseif kind == "gpos_single" then + -- makes no sense in TeX + elseif kind == "gpos_pair" then + local format = step.format + if format == "move" or format == "kern" then + local kerns = coverage[32] + if kerns then + for k, v in next, kerns do + right[k] = v + end + end + for k, v in next, coverage do + local kern = v[32] + if kern then + left[k] = kern + end + end + elseif format == "pair" then + local kerns = coverage[32] + if kerns then + for k, v in next, kerns do + local one = v[1] + if one and one ~= true then + right[k] = one[3] + end + end + end + for k, v in next, coverage do + local kern = v[32] + if kern then + local one = kern[1] + if one and one ~= true then + left[k] = one[3] + end + end end end end end + last = i end + else + -- no steps ... needed for old one ... we could use the basekerns + -- instead end - last = i end - else - -- no steps ... needed for old one ... we could use the basekerns - -- instead end - end - left = next(left) and left or false - right = next(right) and right or false - if left or right then - spacekerns = { - left = left, - right = right, - } - if last > 0 then - local triggersequence = { - features = { kern = feat or { dflt = { dflt = true, } } }, - flags = noflags, - name = "trigger_space_kerns", - order = { "kern" }, - type = "trigger_space_kerns", - left = left, - right = right, + left = next(left) and left or false + right = next(right) and right or false + if left or right then + spacekerns = { + left = left, + right = right, } - insert(sequences,last,triggersequence) + if last > 0 then + local triggersequence = { + -- no steps, see (!!) + features = { [tag] = feat or { dflt = { dflt = true, } } }, + flags = noflags, + name = "trigger_space_kerns", + order = { tag }, + type = "trigger_space_kerns", + left = left, + right = right, + } + insert(sequences,last,triggersequence) + end end - else - spacekerns = false end - else - spacekerns = false end resources.spacekerns = spacekerns end |