if not modules then modules = { } end modules ['font-otn'] = { version = 1.001, comment = "companion to font-ini.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files", } -- this is a context version which can contain experimental code, but when we -- have serious patches we also need to change the other two font-otn files -- at some point i might decide to convert the whole list into a table and then -- run over that instead (but it has some drawbacks as we also need to deal with -- attributes and such so we need to keep a lot of track - which is why i rejected -- that method - although it has become a bit easier in the meantime so it might -- become an alternative (by that time i probably have gone completely lua) .. the -- usual chicken-egg issues ... maybe mkix as it's no real tex any more then -- preprocessors = { "nodes" } -- anchor class : mark, mkmk, curs, mklg (todo) -- anchor type : mark, basechar, baselig, basemark, centry, cexit, max (todo) -- this is still somewhat preliminary and it will get better in due time; -- much functionality could only be implemented thanks to the husayni font -- of Idris Samawi Hamid to who we dedicate this module. -- in retrospect it always looks easy but believe it or not, it took a lot -- of work to get proper open type support done: buggy fonts, fuzzy specs, -- special made testfonts, many skype sessions between taco, idris and me, -- torture tests etc etc ... unfortunately the code does not show how much -- time it took ... -- todo: -- -- extension infrastructure (for usage out of context) -- sorting features according to vendors/renderers -- alternative loop quitters -- check cursive and r2l -- find out where ignore-mark-classes went -- default features (per language, script) -- handle positions (we need example fonts) -- handle gpos_single (we might want an extra width field in glyph nodes because adding kerns might interfere) -- mark (to mark) code is still not what it should be (too messy but we need some more extreem husayni tests) -- remove some optimizations (when I have a faster machine) -- -- beware: -- -- we do some disc jugling where we need to keep in mind that the -- pre, post and replace fields can have prev pointers to a nesting -- node ... i wonder if that is still needed -- -- not possible: -- -- \discretionary {alpha-} {betagammadelta} -- {\discretionary {alphabeta-} {gammadelta} -- {\discretionary {alphabetagamma-} {delta} -- {alphabetagammadelta}}} --[[ldx--
This module is a bit more split up that I'd like but since we also want to test
with plain
The specification of OpenType is kind of vague. Apart from a lack of a proper free specifications there's also the problem that Microsoft and Adobe may have their own interpretation of how and in what order to apply features. In general the Microsoft website has more detailed specifications and is a better reference. There is also some information in the FontForge help files.
Because there is so much possible, fonts might contain bugs and/or be made to work with certain rederers. These may evolve over time which may have the side effect that suddenly fonts behave differently.
After a lot of experiments (mostly by Taco, me and Idris) we're now at yet another
implementation. Of course all errors are mine and of course the code can be
improved. There are quite some optimizations going on here and processing speed
is currently acceptable. Not all functions are implemented yet, often because I
lack the fonts for testing. Many scripts are not yet supported either, but I will
look into them as soon as
The specification leaves room for interpretation. In case of doubt the microsoft implementation is the reference as it is the most complete one. As they deal with lots of scripts and fonts, Kai and Ivo did a lot of testing of the generic code and their suggestions help improve the code. I'm aware that not all border cases can be taken care of, unless we accept excessive runtime, and even then the interference with other mechanisms (like hyphenation) are not trivial.
Glyphs are indexed not by unicode but in their own way. This is because there is no
relationship with unicode at all, apart from the fact that a font might cover certain
ranges of characters. One character can have multiple shapes. However, at the
The raw table as it coms from
This module is sparsely documented because it is a moving target. The table format of the reader changes and we experiment a lot with different methods for supporting features.
As with the
Incrementing the version number will force a re-cache. We jump the number by one
when there's a fix in the
We get hits on a mark, but we're not sure if the it has to be applied so we need to explicitly test for basechar, baselig and basemark entries.
--ldx]]-- function handlers.gpos_mark2base(head,start,kind,lookupname,markanchors,sequence) local markchar = getchar(start) if marks[markchar] then local base = getprev(start) -- [glyph] [start=mark] if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then local basechar = getchar(base) if marks[basechar] then while true do base = getprev(base) if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then basechar = getchar(base) if not marks[basechar] then break end else if trace_bugs then logwarning("%s: no base for mark %s",pref(kind,lookupname),gref(markchar)) end return head, start, false end end end local baseanchors = descriptions[basechar] if baseanchors then baseanchors = baseanchors.anchors end if baseanchors then local baseanchors = baseanchors['basechar'] if baseanchors then local al = anchorlookups[lookupname] for anchor,ba in next, baseanchors do if al[anchor] then local ma = markanchors[anchor] if ma then local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,characters[basechar]) if trace_marks then logprocess("%s, anchor %s, bound %s: anchoring mark %s to basechar %s => (%p,%p)", pref(kind,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy) end return head, start, true end end end if trace_bugs then logwarning("%s, no matching anchors for mark %s and base %s",pref(kind,lookupname),gref(markchar),gref(basechar)) end end elseif trace_bugs then -- logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(basechar)) onetimemessage(currentfont,basechar,"no base anchors",report_fonts) end elseif trace_bugs then logwarning("%s: prev node is no char",pref(kind,lookupname)) end elseif trace_bugs then logwarning("%s: mark %s is no mark",pref(kind,lookupname),gref(markchar)) end return head, start, false end function handlers.gpos_mark2ligature(head,start,kind,lookupname,markanchors,sequence) -- check chainpos variant local markchar = getchar(start) if marks[markchar] then local base = getprev(start) -- [glyph] [optional marks] [start=mark] if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then local basechar = getchar(base) if marks[basechar] then while true do base = getprev(base) if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then basechar = getchar(base) if not marks[basechar] then break end else if trace_bugs then logwarning("%s: no base for mark %s",pref(kind,lookupname),gref(markchar)) end return head, start, false end end end local index = getligaindex(start) local baseanchors = descriptions[basechar] if baseanchors then baseanchors = baseanchors.anchors if baseanchors then local baseanchors = baseanchors['baselig'] if baseanchors then local al = anchorlookups[lookupname] for anchor, ba in next, baseanchors do if al[anchor] then local ma = markanchors[anchor] if ma then ba = ba[index] if ba then local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,characters[basechar]) -- index if trace_marks then logprocess("%s, anchor %s, index %s, bound %s: anchoring mark %s to baselig %s at index %s => (%p,%p)", pref(kind,lookupname),anchor,index,bound,gref(markchar),gref(basechar),index,dx,dy) end return head, start, true else if trace_bugs then logwarning("%s: no matching anchors for mark %s and baselig %s with index %a",pref(kind,lookupname),gref(markchar),gref(basechar),index) end end end end end if trace_bugs then logwarning("%s: no matching anchors for mark %s and baselig %s",pref(kind,lookupname),gref(markchar),gref(basechar)) end end end elseif trace_bugs then -- logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(basechar)) onetimemessage(currentfont,basechar,"no base anchors",report_fonts) end elseif trace_bugs then logwarning("%s: prev node is no char",pref(kind,lookupname)) end elseif trace_bugs then logwarning("%s: mark %s is no mark",pref(kind,lookupname),gref(markchar)) end return head, start, false end function handlers.gpos_mark2mark(head,start,kind,lookupname,markanchors,sequence) local markchar = getchar(start) if marks[markchar] then local base = getprev(start) -- [glyph] [basemark] [start=mark] local slc = getligaindex(start) if slc then -- a rather messy loop ... needs checking with husayni while base do local blc = getligaindex(base) if blc and blc ~= slc then base = getprev(base) else break end end end if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then -- subtype test can go local basechar = getchar(base) local baseanchors = descriptions[basechar] if baseanchors then baseanchors = baseanchors.anchors if baseanchors then baseanchors = baseanchors['basemark'] if baseanchors then local al = anchorlookups[lookupname] for anchor,ba in next, baseanchors do if al[anchor] then local ma = markanchors[anchor] if ma then local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,characters[basechar],true) if trace_marks then logprocess("%s, anchor %s, bound %s: anchoring mark %s to basemark %s => (%p,%p)", pref(kind,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy) end return head, start, true end end end if trace_bugs then logwarning("%s: no matching anchors for mark %s and basemark %s",pref(kind,lookupname),gref(markchar),gref(basechar)) end end end elseif trace_bugs then -- logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(basechar)) onetimemessage(currentfont,basechar,"no base anchors",report_fonts) end elseif trace_bugs then logwarning("%s: prev node is no mark",pref(kind,lookupname)) end elseif trace_bugs then logwarning("%s: mark %s is no mark",pref(kind,lookupname),gref(markchar)) end return head, start, false end function handlers.gpos_cursive(head,start,kind,lookupname,exitanchors,sequence) -- to be checked local alreadydone = cursonce and getprop(start,a_cursbase) if not alreadydone then local done = false local startchar = getchar(start) if marks[startchar] then if trace_cursive then logprocess("%s: ignoring cursive for mark %s",pref(kind,lookupname),gref(startchar)) end else local nxt = getnext(start) while not done and nxt and getid(nxt) == glyph_code and getfont(nxt) == currentfont and getsubtype(nxt)<256 do local nextchar = getchar(nxt) if marks[nextchar] then -- should not happen (maybe warning) nxt = getnext(nxt) else local entryanchors = descriptions[nextchar] if entryanchors then entryanchors = entryanchors.anchors if entryanchors then entryanchors = entryanchors['centry'] if entryanchors then local al = anchorlookups[lookupname] for anchor, entry in next, entryanchors do if al[anchor] then local exit = exitanchors[anchor] if exit then local dx, dy, bound = setcursive(start,nxt,tfmdata.parameters.factor,rlmode,exit,entry,characters[startchar],characters[nextchar]) if trace_cursive then logprocess("%s: moving %s to %s cursive (%p,%p) using anchor %s and bound %s in rlmode %s",pref(kind,lookupname),gref(startchar),gref(nextchar),dx,dy,anchor,bound,rlmode) end done = true break end end end end end elseif trace_bugs then -- logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(startchar)) onetimemessage(currentfont,startchar,"no entry anchors",report_fonts) end break end end end return head, start, done else if trace_cursive and trace_details then logprocess("%s, cursive %s is already done",pref(kind,lookupname),gref(getchar(start)),alreadydone) end return head, start, false end end --[[ldx--I will implement multiple chain replacements once I run into a font that uses it. It's not that complex to handle.
--ldx]]-- local chainprocs = { } local function logprocess(...) if trace_steps then registermessage(...) end report_subchain(...) end local logwarning = report_subchain local function logprocess(...) if trace_steps then registermessage(...) end report_chain(...) end local logwarning = report_chain -- We could share functions but that would lead to extra function calls with many -- arguments, redundant tests and confusing messages. function chainprocs.chainsub(head,start,stop,kind,chainname,currentcontext,lookuphash,lookuplist,chainlookupname) logwarning("%s: a direct call to chainsub cannot happen",cref(kind,chainname,chainlookupname)) return head, start, false end -- The reversesub is a special case, which is why we need to store the replacements -- in a bit weird way. There is no lookup and the replacement comes from the lookup -- itself. It is meant mostly for dealing with Urdu. function chainprocs.reversesub(head,start,stop,kind,chainname,currentcontext,lookuphash,replacements) local char = getchar(start) local replacement = replacements[char] if replacement then if trace_singles then logprocess("%s: single reverse replacement of %s by %s",cref(kind,chainname),gref(char),gref(replacement)) end resetinjection(start) setfield(start,"char",replacement) return head, start, true else return head, start, false end end --[[ldx--This chain stuff is somewhat tricky since we can have a sequence of actions to be applied: single, alternate, multiple or ligature where ligature can be an invalid one in the sense that it will replace multiple by one but not neccessary one that looks like the combination (i.e. it is the counterpart of multiple then). For example, the following is valid:
Therefore we we don't really do the replacement here already unless we have the single lookup case. The efficiency of the replacements can be improved by deleting as less as needed but that would also make the code even more messy.
--ldx]]-- -- local function delete_till_stop(head,start,stop,ignoremarks) -- keeps start -- local n = 1 -- if start == stop then -- -- done -- elseif ignoremarks then -- repeat -- start x x m x x stop => start m -- local next = getnext(start) -- if not marks[getchar(next)] then -- local components = getfield(next,"components") -- if components then -- probably not needed -- flush_node_list(components) -- end -- head = delete_node(head,next) -- end -- n = n + 1 -- until next == stop -- else -- start x x x stop => start -- repeat -- local next = getnext(start) -- local components = getfield(next,"components") -- if components then -- probably not needed -- flush_node_list(components) -- end -- head = delete_node(head,next) -- n = n + 1 -- until next == stop -- end -- return head, n -- end --[[ldx--Here we replace start by a single variant.
--ldx]]-- function chainprocs.gsub_single(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex) -- todo: marks ? local current = start local subtables = currentlookup.subtables if #subtables > 1 then logwarning("todo: check if we need to loop over the replacements: % t",subtables) end while current do if getid(current) == glyph_code then local currentchar = getchar(current) local lookupname = subtables[1] -- only 1 local replacement = lookuphash[lookupname] if not replacement then if trace_bugs then logwarning("%s: no single hits",cref(kind,chainname,chainlookupname,lookupname,chainindex)) end else replacement = replacement[currentchar] if not replacement or replacement == "" then if trace_bugs then logwarning("%s: no single for %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(currentchar)) end else if trace_singles then logprocess("%s: replacing single %s by %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(currentchar),gref(replacement)) end resetinjection(current) setfield(current,"char",replacement) end end return head, start, true elseif current == stop then break else current = getnext(current) end end return head, start, false end --[[ldx--Here we replace start by a sequence of new glyphs.
--ldx]]-- function chainprocs.gsub_multiple(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) -- local head, n = delete_till_stop(head,start,stop) local startchar = getchar(start) local subtables = currentlookup.subtables local lookupname = subtables[1] local replacements = lookuphash[lookupname] if not replacements then if trace_bugs then logwarning("%s: no multiple hits",cref(kind,chainname,chainlookupname,lookupname)) end else replacements = replacements[startchar] if not replacements or replacement == "" then if trace_bugs then logwarning("%s: no multiple for %s",cref(kind,chainname,chainlookupname,lookupname),gref(startchar)) end else if trace_multiples then logprocess("%s: replacing %s by multiple characters %s",cref(kind,chainname,chainlookupname,lookupname),gref(startchar),gref(replacements)) end return multiple_glyphs(head,start,replacements,currentlookup.flags[1]) end end return head, start, false end --[[ldx--Here we replace start by new glyph. First we delete the rest of the match.
--ldx]]-- -- char_1 mark_1 -> char_x mark_1 (ignore marks) -- char_1 mark_1 -> char_x -- to be checked: do we always have just one glyph? -- we can also have alternates for marks -- marks come last anyway -- are there cases where we need to delete the mark function chainprocs.gsub_alternate(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) local current = start local subtables = currentlookup.subtables local value = featurevalue == true and tfmdata.shared.features[kind] or featurevalue while current do if getid(current) == glyph_code then -- is this check needed? local currentchar = getchar(current) local lookupname = subtables[1] local alternatives = lookuphash[lookupname] if not alternatives then if trace_bugs then logwarning("%s: no alternative hit",cref(kind,chainname,chainlookupname,lookupname)) end else alternatives = alternatives[currentchar] if alternatives then local choice, comment = get_alternative_glyph(current,alternatives,value,trace_alternatives) if choice then if trace_alternatives then logprocess("%s: replacing %s by alternative %a to %s, %s",cref(kind,chainname,chainlookupname,lookupname),gref(char),choice,gref(choice),comment) end resetinjection(start) setfield(start,"char",choice) else if trace_alternatives then logwarning("%s: no variant %a for %s, %s",cref(kind,chainname,chainlookupname,lookupname),value,gref(char),comment) end end elseif trace_bugs then logwarning("%s: no alternative for %s, %s",cref(kind,chainname,chainlookupname,lookupname),gref(currentchar),comment) end end return head, start, true elseif current == stop then break else current = getnext(current) end end return head, start, false end --[[ldx--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).
--ldx]]-- function chainprocs.gsub_ligature(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex) local startchar = getchar(start) local subtables = currentlookup.subtables local lookupname = subtables[1] local ligatures = lookuphash[lookupname] if not ligatures then if trace_bugs then logwarning("%s: no ligature hits",cref(kind,chainname,chainlookupname,lookupname,chainindex)) end else ligatures = ligatures[startchar] if not ligatures then if trace_bugs then logwarning("%s: no ligatures starting with %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar)) end else local s = getnext(start) local discfound = false local last = stop local nofreplacements = 1 local skipmark = currentlookup.flags[1] while s do local id = getid(s) if id == disc_code then if not discfound then discfound = s end if s == stop then break -- okay? or before the disc else s = getnext(s) end else local schar = getchar(s) if skipmark and marks[schar] then -- marks s = getnext(s) else local lg = ligatures[schar] if lg then ligatures, last, nofreplacements = lg, s, nofreplacements + 1 if s == stop then break else s = getnext(s) end else break end end end end local l2 = ligatures.ligature if l2 then if chainindex then stop = last end if trace_ligatures then if start == stop then logprocess("%s: replacing character %s by ligature %s case 3",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(l2)) else logprocess("%s: replacing character %s upto %s by ligature %s case 4",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(getchar(stop)),gref(l2)) end end head, start = toligature(kind,lookupname,head,start,stop,l2,currentlookup.flags[1],discfound) return head, start, true, nofreplacements, discfound elseif trace_bugs then if start == stop then logwarning("%s: replacing character %s by ligature fails",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar)) else logwarning("%s: replacing character %s upto %s by ligature fails",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(getchar(stop))) end end end end return head, start, false, 0, false end function chainprocs.gpos_single(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex,sequence) -- untested .. needs checking for the new model local startchar = getchar(start) local subtables = currentlookup.subtables local lookupname = subtables[1] local kerns = lookuphash[lookupname] if kerns then kerns = kerns[startchar] -- needed ? if kerns then local dx, dy, w, h = setpair(start,tfmdata.parameters.factor,rlmode,sequence.flags[4],kerns) -- ,characters[startchar]) if trace_kerns then logprocess("%s: shifting single %s by (%p,%p) and correction (%p,%p)",cref(kind,chainname,chainlookupname),gref(startchar),dx,dy,w,h) end end end return head, start, false end function chainprocs.gpos_pair(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex,sequence) local snext = getnext(start) if snext then local startchar = getchar(start) local subtables = currentlookup.subtables local lookupname = subtables[1] local kerns = lookuphash[lookupname] if kerns then kerns = kerns[startchar] if kerns then local lookuptype = lookuptypes[lookupname] local prev, done = start, false local factor = tfmdata.parameters.factor while snext and getid(snext) == glyph_code and getfont(snext) == currentfont and getsubtype(snext)<256 do local nextchar = getchar(snext) local krn = kerns[nextchar] if not krn and marks[nextchar] then prev = snext snext = getnext(snext) else if not krn then -- skip elseif type(krn) == "table" then if lookuptype == "pair" then local a, b = krn[2], krn[3] if a and #a > 0 then local startchar = getchar(start) local x, y, w, h = setpair(start,factor,rlmode,sequence.flags[4],a) -- ,characters[startchar]) if trace_kerns then logprocess("%s: shifting first of pair %s and %s by (%p,%p) and correction (%p,%p)",cref(kind,chainname,chainlookupname),gref(startchar),gref(nextchar),x,y,w,h) 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) -- ,characters[nextchar]) if trace_kerns then logprocess("%s: shifting second of pair %s and %s by (%p,%p) and correction (%p,%p)",cref(kind,chainname,chainlookupname),gref(startchar),gref(nextchar),x,y,w,h) end end else report_process("%s: check this out (old kern stuff)",cref(kind,chainname,chainlookupname)) -- local a, b = krn[2], krn[6] -- if a and a ~= 0 then -- local k = setkern(snext,factor,rlmode,a) -- if trace_kerns then -- logprocess("%s: inserting first kern %s between %s and %s",cref(kind,chainname,chainlookupname),k,gref(getchar(prev)),gref(nextchar)) -- end -- end -- if b and b ~= 0 then -- logwarning("%s: ignoring second kern xoff %s",cref(kind,chainname,chainlookupname),b*factor) -- end end done = 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(kind,chainname,chainlookupname),k,gref(getchar(prev)),gref(nextchar)) end done = true end break end end return head, start, done end end end return head, start, false end function chainprocs.gpos_mark2base(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) local markchar = getchar(start) if marks[markchar] then local subtables = currentlookup.subtables local lookupname = subtables[1] local markanchors = lookuphash[lookupname] if markanchors then markanchors = markanchors[markchar] end if markanchors then local base = getprev(start) -- [glyph] [start=mark] if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then local basechar = getchar(base) if marks[basechar] then while true do base = getprev(base) if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then basechar = getchar(base) if not marks[basechar] then break end else if trace_bugs then logwarning("%s: no base for mark %s",pref(kind,lookupname),gref(markchar)) end return head, start, false end end end local baseanchors = descriptions[basechar].anchors if baseanchors then local baseanchors = baseanchors['basechar'] if baseanchors then local al = anchorlookups[lookupname] for anchor,ba in next, baseanchors do if al[anchor] then local ma = markanchors[anchor] if ma then local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,characters[basechar]) if trace_marks then logprocess("%s, anchor %s, bound %s: anchoring mark %s to basechar %s => (%p,%p)", cref(kind,chainname,chainlookupname,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy) end return head, start, true end end end if trace_bugs then logwarning("%s, no matching anchors for mark %s and base %s",cref(kind,chainname,chainlookupname,lookupname),gref(markchar),gref(basechar)) end end end elseif trace_bugs then logwarning("%s: prev node is no char",cref(kind,chainname,chainlookupname,lookupname)) end elseif trace_bugs then logwarning("%s: mark %s has no anchors",cref(kind,chainname,chainlookupname,lookupname),gref(markchar)) end elseif trace_bugs then logwarning("%s: mark %s is no mark",cref(kind,chainname,chainlookupname),gref(markchar)) end return head, start, false end function chainprocs.gpos_mark2ligature(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) local markchar = getchar(start) if marks[markchar] then local subtables = currentlookup.subtables local lookupname = subtables[1] local markanchors = lookuphash[lookupname] if markanchors then markanchors = markanchors[markchar] end if markanchors then local base = getprev(start) -- [glyph] [optional marks] [start=mark] if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then local basechar = getchar(base) if marks[basechar] then while true do base = getprev(base) if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then basechar = getchar(base) if not marks[basechar] then break end else if trace_bugs then logwarning("%s: no base for mark %s",cref(kind,chainname,chainlookupname,lookupname),markchar) end return head, start, false end end end -- todo: like marks a ligatures hash local index = getligaindex(start) local baseanchors = descriptions[basechar].anchors if baseanchors then local baseanchors = baseanchors['baselig'] if baseanchors then local al = anchorlookups[lookupname] for anchor,ba in next, baseanchors do if al[anchor] then local ma = markanchors[anchor] if ma then ba = ba[index] if ba then local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,characters[basechar]) if trace_marks then logprocess("%s, anchor %s, bound %s: anchoring mark %s to baselig %s at index %s => (%p,%p)", cref(kind,chainname,chainlookupname,lookupname),anchor,a or bound,gref(markchar),gref(basechar),index,dx,dy) end return head, start, true end end end end if trace_bugs then logwarning("%s: no matching anchors for mark %s and baselig %s",cref(kind,chainname,chainlookupname,lookupname),gref(markchar),gref(basechar)) end end end elseif trace_bugs then logwarning("feature %s, lookup %s: prev node is no char",kind,lookupname) end elseif trace_bugs then logwarning("%s: mark %s has no anchors",cref(kind,chainname,chainlookupname,lookupname),gref(markchar)) end elseif trace_bugs then logwarning("%s: mark %s is no mark",cref(kind,chainname,chainlookupname),gref(markchar)) end return head, start, false end function chainprocs.gpos_mark2mark(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) local markchar = getchar(start) if marks[markchar] then -- local markanchors = descriptions[markchar].anchors markanchors = markanchors and markanchors.mark local subtables = currentlookup.subtables local lookupname = subtables[1] local markanchors = lookuphash[lookupname] if markanchors then markanchors = markanchors[markchar] end if markanchors then local base = getprev(start) -- [glyph] [basemark] [start=mark] local slc = getligaindex(start) if slc then -- a rather messy loop ... needs checking with husayni while base do local blc = getligaindex(base) if blc and blc ~= slc then base = getprev(base) else break end end end if base and getid(base) == glyph_code and getfont(base) == currentfont and getsubtype(base)<256 then -- subtype test can go local basechar = getchar(base) local baseanchors = descriptions[basechar].anchors if baseanchors then baseanchors = baseanchors['basemark'] if baseanchors then local al = anchorlookups[lookupname] for anchor,ba in next, baseanchors do if al[anchor] then local ma = markanchors[anchor] if ma then local dx, dy, bound = setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,characters[basechar],true) if trace_marks then logprocess("%s, anchor %s, bound %s: anchoring mark %s to basemark %s => (%p,%p)", cref(kind,chainname,chainlookupname,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy) end return head, start, true end end end if trace_bugs then logwarning("%s: no matching anchors for mark %s and basemark %s",gref(kind,chainname,chainlookupname,lookupname),gref(markchar),gref(basechar)) end end end elseif trace_bugs then logwarning("%s: prev node is no mark",cref(kind,chainname,chainlookupname,lookupname)) end elseif trace_bugs then logwarning("%s: mark %s has no anchors",cref(kind,chainname,chainlookupname,lookupname),gref(markchar)) end elseif trace_bugs then logwarning("%s: mark %s is no mark",cref(kind,chainname,chainlookupname),gref(markchar)) end return head, start, false end function chainprocs.gpos_cursive(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) local alreadydone = cursonce and getprop(start,a_cursbase) if not alreadydone then local startchar = getchar(start) local subtables = currentlookup.subtables local lookupname = subtables[1] local exitanchors = lookuphash[lookupname] if exitanchors then exitanchors = exitanchors[startchar] end if exitanchors then local done = false if marks[startchar] then if trace_cursive then logprocess("%s: ignoring cursive for mark %s",pref(kind,lookupname),gref(startchar)) end else local nxt = getnext(start) while not done and nxt and getid(nxt) == glyph_code and getfont(nxt) == currentfont and getsubtype(nxt)<256 do local nextchar = getchar(nxt) if marks[nextchar] then -- should not happen (maybe warning) nxt = getnext(nxt) else local entryanchors = descriptions[nextchar] if entryanchors then entryanchors = entryanchors.anchors if entryanchors then entryanchors = entryanchors['centry'] if entryanchors then local al = anchorlookups[lookupname] for anchor, entry in next, entryanchors do if al[anchor] then local exit = exitanchors[anchor] if exit then local dx, dy, bound = setcursive(start,nxt,tfmdata.parameters.factor,rlmode,exit,entry,characters[startchar],characters[nextchar]) if trace_cursive then logprocess("%s: moving %s to %s cursive (%p,%p) using anchor %s and bound %s in rlmode %s",pref(kind,lookupname),gref(startchar),gref(nextchar),dx,dy,anchor,bound,rlmode) end done = true break end end end end end elseif trace_bugs then -- logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(startchar)) onetimemessage(currentfont,startchar,"no entry anchors",report_fonts) end break end end end return head, start, done else if trace_cursive and trace_details then logprocess("%s, cursive %s is already done",pref(kind,lookupname),gref(getchar(start)),alreadydone) end return head, start, false end end return head, start, false end -- what pointer to return, spec says stop -- to be discussed ... is bidi changer a space? -- elseif char == zwnj and sequence[n][32] then -- brrr -- somehow l or f is global -- we don't need to pass the currentcontext, saves a bit -- make a slow variant then can be activated but with more tracing local function show_skip(kind,chainname,char,ck,class) if ck[9] then logwarning("%s: skipping char %s, class %a, rule %a, lookuptype %a, %a => %a",cref(kind,chainname),gref(char),class,ck[1],ck[2],ck[9],ck[10]) else logwarning("%s: skipping char %s, class %a, rule %a, lookuptype %a",cref(kind,chainname),gref(char),class,ck[1],ck[2]) end 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). -- -- 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.) -- -- 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 function chaindisk(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,chainindex,sequence,chainproc) if not start then return head, start, false end local startishead = start == head local seq = ck[3] local f = ck[4] local l = ck[5] local s = #seq local done = false local sweepnode = sweepnode local sweeptype = sweeptype local sweepoverflow = false local checkdisc = getprev(head) -- hm bad name head local keepdisc = not sweepnode local lookaheaddisc = nil local backtrackdisc = nil local current = start local last = start local prev = getprev(start) -- fishy: so we can overflow and then go on in the sweep? local i = f while i <= l do local id = getid(current) if id == glyph_code then i = i + 1 last = current current = getnext(current) elseif id == disc_code then if keepdisc then keepdisc = false if notmatchpre[current] ~= notmatchreplace[current] then lookaheaddisc = current end local replace = getfield(current,"replace") while replace and i <= l do if getid(replace) == glyph_code then i = i + 1 end replace = getnext(replace) end last = current current = getnext(c) else head, current = flattendisk(head,current) end else last = current current = getnext(current) end if current then -- go on elseif sweepoverflow then -- we already are folling up on sweepnode break elseif sweeptype == "post" or sweeptype == "replace" then current = getnext(sweepnode) if current then sweeptype = nil sweepoverflow = true else break end end end if sweepoverflow then local prev = current and getprev(current) if not current or prev ~= sweepnode then local head = getnext(sweepnode) local tail = nil if prev then tail = prev setfield(current,"prev",sweepnode) else tail = find_node_tail(head) end setfield(sweepnode,"next",current) setfield(head,"prev",nil) setfield(tail,"next",nil) appenddisc(sweepnode,head) end end if l < s then local i = l local t = sweeptype == "post" or sweeptype == "replace" while current and i < s do local id = getid(current) if id == glyph_code then i = i + 1 current = getnext(current) elseif id == disc_code then if keepdisc then keepdisc = false if notmatchpre[current] ~= notmatchreplace[current] then lookaheaddisc = current end local replace = getfield(c,"replace") while replace and i < s do if getid(replace) == glyph_code then i = i + 1 end replace = getnext(replace) end current = getnext(current) elseif notmatchpre[current] ~= notmatchreplace[current] then head, current = flattendisk(head,current) else current = getnext(current) -- HH end else current = getnext(current) end if not current and t then current = getnext(sweepnode) if current then sweeptype = nil end end end end if f > 1 then local current = prev local i = f local t = sweeptype == "pre" or sweeptype == "replace" if not current and t and current == checkdisk then current = getprev(sweepnode) end while current and i > 1 do -- missing getprev added / moved outside local id = getid(current) if id == glyph_code then i = i - 1 elseif id == disc_code then if keepdisc then keepdisc = false if notmatchpost[current] ~= notmatchreplace[current] then backtrackdisc = current end local replace = getfield(current,"replace") while replace and i > 1 do if getid(replace) == glyph_code then i = i - 1 end replace = getnext(replace) end elseif notmatchpost[current] ~= notmatchreplace[current] then head, current = flattendisk(head,current) end end current = getprev(current) if t and current == checkdisk then current = getprev(sweepnode) end end end local ok = false if lookaheaddisc then local cf = start local cl = getprev(lookaheaddisc) local cprev = getprev(start) local insertedmarks = 0 while cprev and getid(cf) == glyph_code and getfont(cf) == currentfont and getsubtype(cf) < 256 and marks[getchar(cf)] do insertedmarks = insertedmarks + 1 cf = cprev startishead = cf == head cprev = getprev(cprev) end setfield(lookaheaddisc,"prev",cprev) if cprev then setfield(cprev,"next",lookaheaddisc) end setfield(cf,"prev",nil) setfield(cl,"next",nil) if startishead then head = lookaheaddisc end local replace = getfield(lookaheaddisc,"replace") local pre = getfield(lookaheaddisc,"pre") local new = copy_node_list(cf) local cnew = new for i=1,insertedmarks do cnew = getnext(cnew) end local clast = cnew for i=f,l do clast = getnext(clast) end if not notmatchpre[lookaheaddisc] then cf, start, ok = chainproc(cf,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence) end if not notmatchreplace[lookaheaddisc] then new, cnew, ok = chainproc(new,cnew,clast,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence) end if pre then setfield(cl,"next",pre) setfield(pre,"prev",cl) end if replace then local tail = find_node_tail(new) setfield(tail,"next",replace) setfield(replace,"prev",tail) end setfield(lookaheaddisc,"pre",cf) -- also updates tail setfield(lookaheaddisc,"replace",new) -- also updates tail start = getprev(lookaheaddisc) sweephead[cf] = getnext(clast) sweephead[new] = getnext(last) elseif backtrackdisc then local cf = getnext(backtrackdisc) local cl = start local cnext = getnext(start) local insertedmarks = 0 while cnext and getid(cnext) == glyph_code and getfont(cnext) == currentfont and getsubtype(cnext) < 256 and marks[getchar(cnext)] do insertedmarks = insertedmarks + 1 cl = cnext cnext = getnext(cnext) end if cnext then setfield(cnext,"prev",backtrackdisc) end setfield(backtrackdisc,"next",cnext) setfield(cf,"prev",nil) setfield(cl,"next",nil) local replace = getfield(backtrackdisc,"replace") local post = getfield(backtrackdisc,"post") local new = copy_node_list(cf) local cnew = find_node_tail(new) for i=1,insertedmarks do cnew = getprev(cnew) end local clast = cnew for i=f,l do clast = getnext(clast) end if not notmatchpost[backtrackdisc] then cf, start, ok = chainproc(cf,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence) end if not notmatchreplace[backtrackdisc] then new, cnew, ok = chainproc(new,cnew,clast,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence) end if post then local tail = find_node_tail(post) setfield(tail,"next",cf) setfield(cf,"prev",tail) else post = cf end if replace then local tail = find_node_tail(replace) setfield(tail,"next",new) setfield(new,"prev",tail) else replace = new end setfield(backtrackdisc,"post",post) -- also updates tail setfield(backtrackdisc,"replace",replace) -- also updates tail start = getprev(backtrackdisc) sweephead[post] = getnext(clast) sweephead[replace] = getnext(last) else head, start, ok = chainproc(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence) end return head, start, ok end local function normal_handle_contextchain(head,start,kind,chainname,contexts,sequence,lookuphash) local sweepnode = sweepnode local sweeptype = sweeptype local diskseen = false local checkdisc = getprev(head) local flags = sequence.flags local done = false local skipmark = flags[1] local skipligature = flags[2] local skipbase = flags[3] local markclass = sequence.markclass local skipped = false for k=1,#contexts do -- i've only seen ccmp having > 1 (e.g. dejavu) local match = true local current = start local last = start local ck = contexts[k] local seq = ck[3] local s = #seq -- f..l = mid string if s == 1 then -- never happens match = getid(current) == glyph_code and getfont(current) == currentfont and getsubtype(current)<256 and seq[1][getchar(current)] 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 if f == 1 and f == l then -- current only -- already a hit -- match = true else -- before/current/after | before/current | current/after -- no need to test first hit (to be optimized) if f == l then -- new, else last out of sync (f is > 1) -- match = true else local discfound = nil local n = f + 1 last = getnext(last) while n <= l do if not last and (sweeptype == "post" or sweeptype == "replace") then last = getnext(sweepnode) sweeptype = nil end if last then local id = getid(last) if id == glyph_code then if getfont(last) == currentfont and getsubtype(last)<256 then local char = getchar(last) local ccd = descriptions[char] if ccd then local class = ccd.class or "base" 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(kind,chainname,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 match = not notmatchpre[discfound] else match = false end break end else if discfound then notmatchreplace[discfound] = true match = not notmatchpre[discfound] else match = false end break end else if discfound then notmatchreplace[discfound] = true match = not notmatchpre[discfound] else match = false end break end elseif id == disc_code then diskseen = true discfound = last notmatchpre[last] = nil notmatchpost[last] = true notmatchreplace[last] = nil local pre = getfield(last,"pre") local replace = getfield(last,"replace") 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 break end end 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 replace = getnext(replace) if n > l then break end else notmatchreplace[last] = true match = not notmatchpre[last] break end end match = not notmatchpre[last] end last = getnext(last) else match = false break end else match = false break end end end end -- before if match and f > 1 then local prev = getprev(start) if prev then 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 id = getid(prev) if id == glyph_code then if getfont(prev) == currentfont and getsubtype(prev)<256 then -- normal char local char = getchar(prev) local ccd = descriptions[char] if ccd then local class = ccd.class if class == skipmark or class == skipligature or class == skipbase or (markclass and class == "mark" and not markclass[char]) then skipped = true if trace_skips then show_skip(kind,chainname,char,ck,class) end elseif seq[n][char] then n = n -1 else if discfound then notmatchreplace[discfound] = true match = not notmatchpost[discfound] else match = false end break end else if discfound then notmatchreplace[discfound] = true match = not notmatchpost[discfound] else match = false end break end else if discfound then notmatchreplace[discfound] = true match = not notmatchpost[discfound] else match = false end break end 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 = getfield(prev,"pre") local post = getfield(prev,"post") local replace = getfield(prev,"replace") if pre ~= start and post ~= start and replace ~= start then if post then local n = n local posttail = find_node_tail(post) 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 break end end if n >= 1 then notmatchpost[prev] = true end else notmatchpost[prev] = true end if replace then -- we seldom enter this branch (e.g. on brill efficient) local replacetail = find_node_tail(replace) 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 notmatchreplace[prev] = true match = not notmatchpost[prev] break end end if not match then break end else -- skip 'm end else -- skip 'm end elseif seq[n][32] then n = n -1 else match = false break end prev = getprev(prev) elseif seq[n][32] then -- somewhat special, as zapfino can have many preceding spaces n = n - 1 else match = false break end end else match = false end else match = false end end -- after if match and s > l then local current = last and getnext(last) if not current then if sweeptype == "post" or sweeptype == "replace" then current = getnext(sweepnode) -- sweeptype = nil end 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 id = getid(current) if id == glyph_code then if getfont(current) == currentfont and getsubtype(current)<256 then -- normal char local char = getchar(current) local ccd = descriptions[char] if ccd then local class = ccd.class if class == skipmark or class == skipligature or class == skipbase or (markclass and class == "mark" and not markclass[char]) then skipped = true if trace_skips then show_skip(kind,chainname,char,ck,class) end elseif seq[n][char] then n = n + 1 else if discfound then notmatchreplace[discfound] = true match = not notmatchpre[discfound] else match = false end break end else if discfound then notmatchreplace[discfound] = true match = not notmatchpre[discfound] else match = false end break end else if discfound then notmatchreplace[discfound] = true match = not notmatchpre[discfound] else match = false end break end elseif id == disc_code then diskseen = true discfound = current notmatchpre[current] = nil notmatchpost[current] = true notmatchreplace[current] = nil local pre = getfield(current,"pre") local replace = getfield(current,"replace") 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 break end end if n <= s then notmatchpre[current] = true end 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 replace = getnext(replace) if n > s then break end else notmatchreplace[current] = true match = notmatchpre[current] break end end if not match then break end else -- skip 'm end elseif seq[n][32] then -- brrr n = n + 1 else match = false break end current = getnext(current) elseif seq[n][32] then n = n + 1 else match = false break end end else match = false end end end if match then -- can lookups be of a different type ? local diskchain = diskseen or sweepnode if trace_contexts then local rule, lookuptype, f, l = ck[1], ck[2], ck[4], ck[5] local char = getchar(start) if ck[9] then logwarning("%s: rule %s matches at char %s for (%s,%s,%s) chars, lookuptype %a, %a => %a", cref(kind,chainname),rule,gref(char),f-1,l-f+1,s-l,lookuptype,ck[9],ck[10]) else logwarning("%s: rule %s matches at char %s for (%s,%s,%s) chars, lookuptype %a", cref(kind,chainname),rule,gref(char),f-1,l-f+1,s-l,lookuptype) end end local chainlookups = ck[6] if chainlookups then local nofchainlookups = #chainlookups -- we can speed this up if needed if nofchainlookups == 1 then local chainlookupname = chainlookups[1] local chainlookup = lookuptable[chainlookupname] if chainlookup then local chainproc = chainprocs[chainlookup.type] if chainproc then local ok if diskchain then head, start, ok = chaindisk(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence,chainproc) else head, start, ok = chainproc(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence) end if ok then done = true end else logprocess("%s: %s is not yet supported",cref(kind,chainname,chainlookupname),chainlookup.type) end else -- shouldn't happen logprocess("%s is not yet supported",cref(kind,chainname,chainlookupname)) end else local i = 1 while start and true do if skipped then while true do -- todo: use properties local char = getchar(start) local ccd = descriptions[char] if ccd then local class = ccd.class or "base" if class == skipmark or class == skipligature or class == skipbase or (markclass and class == "mark" and not markclass[char]) then start = getnext(start) else break end else break end end end -- see remark in ms standard under : LookupType 5: Contextual Substitution Subtable local chainlookupname = chainlookups[i] local chainlookup = lookuptable[chainlookupname] if not chainlookup then -- we just advance i = i + 1 else local chainproc = chainprocs[chainlookup.type] if not chainproc then -- actually an error logprocess("%s: %s is not yet supported",cref(kind,chainname,chainlookupname),chainlookup.type) i = i + 1 else local ok, n if diskchain then head, start, ok = chaindisk(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence,chainproc) else head, start, ok, n = chainproc(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,i,sequence) end -- messy since last can be changed ! if ok then done = true if n and n > 1 then -- we have a ligature (cf the spec we advance one but we really need to test it -- as there are fonts out there that are fuzzy and have too many lookups: -- -- U+1105 U+119E U+1105 U+119E : sourcehansansklight: script=hang ccmp=yes -- if i + n > nofchainlookups then -- if trace_contexts then -- logprocess("%s: quitting lookups",cref(kind,chainname)) -- end break else -- we need to carry one end end end i = i + 1 end end if i > nofchainlookups or not start then break elseif start then start = getnext(start) end end end else local replacements = ck[7] if replacements then head, start, done = chainprocs.reversesub(head,start,last,kind,chainname,ck,lookuphash,replacements) -- sequence else done = quit_on_no_replacement -- can be meant to be skipped / quite inconsistent in fonts if trace_contexts then logprocess("%s: skipping match",cref(kind,chainname)) end end end if done then break -- out of contexts (new, needs checking) end end end if diskseen then -- maybe move up so that we can turn checking on/off notmatchpre = { } notmatchpost = { } notmatchreplace = { } end return head, start, done end -- Because we want to keep this elsewhere (an because speed is less an issue) we -- pass the font id so that the verbose variant can access the relevant helper tables. local verbose_handle_contextchain = function(font,...) logwarning("no verbose handler installed, reverting to 'normal'") otf.setcontextchain() return normal_handle_contextchain(...) end otf.chainhandlers = { normal = normal_handle_contextchain, verbose = verbose_handle_contextchain, } function otf.setcontextchain(method) if not method or method == "normal" or not otf.chainhandlers[method] then if handlers.contextchain then -- no need for a message while making the format logwarning("installing normal contextchain handler") end handlers.contextchain = normal_handle_contextchain else logwarning("installing contextchain handler %a",method) local handler = otf.chainhandlers[method] handlers.contextchain = function(...) return handler(currentfont,...) -- hm, get rid of ... end end handlers.gsub_context = handlers.contextchain handlers.gsub_contextchain = handlers.contextchain handlers.gsub_reversecontextchain = handlers.contextchain handlers.gpos_contextchain = handlers.contextchain handlers.gpos_context = handlers.contextchain end otf.setcontextchain() local missing = { } -- we only report once local function logprocess(...) if trace_steps then registermessage(...) end report_process(...) end local logwarning = report_process local function report_missing_cache(typ,lookup) local f = missing[currentfont] if not f then f = { } missing[currentfont] = f end local t = f[typ] if not t then t = { } f[typ] = t end if not t[lookup] then t[lookup] = true logwarning("missing cache for lookup %a, type %a, font %a, name %a",lookup,typ,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 lookuphashes = { } setmetatableindex(lookuphashes, function(t,font) local lookuphash = fontdata[font].resources.lookuphash if not lookuphash or not next(lookuphash) then lookuphash = false end t[font] = lookuphash return lookuphash end) -- fonts.hashes.lookups = lookuphashes local autofeatures = fonts.analyzers.features -- was: constants local function initialize(sequence,script,language,enabled) local features = sequence.features if features then local order = sequence.order if order then for i=1,#order do -- local kind = order[i] -- local valid = enabled[kind] if valid then local scripts = features[kind] -- local languages = scripts[script] or scripts[wildcard] if languages and (languages[language] or languages[wildcard]) then return { valid, autofeatures[kind] or false, sequence, kind } end end end else -- can't happen end end return false end function otf.dataset(tfmdata,font) -- generic variant, overloaded in context local shared = tfmdata.shared local properties = tfmdata.properties local language = properties.language or "dflt" local script = properties.script or "dflt" local enabled = shared.features local res = resolved[font] if not res then res = { } resolved[font] = res end local rs = res[script] if not rs then rs = { } res[script] = rs end local rl = rs[language] if not rl then rl = { -- indexed but we can also add specific data by key } rs[language] = rl local sequences = tfmdata.resources.sequences for s=1,#sequences do local v = enabled and initialize(sequences[s],script,language,enabled) if v then rl[#rl+1] = v end end end return rl end -- assumptions: -- -- * languages that use complex disc nodes local function kernrun(disc,run) -- -- we catch