diff options
author | Philipp Gesang <phg@phi-gamma.net> | 2015-10-29 07:14:40 +0100 |
---|---|---|
committer | Philipp Gesang <phg@phi-gamma.net> | 2015-10-29 07:14:40 +0100 |
commit | 4f053696e1813fde4bd6cebbb77ff2a1e1f6800b (patch) | |
tree | 308ec3223eec7744136e95c40284602ba35efacc /src/fontloader/misc | |
parent | d7a36a33d8d57a5aec9232364cf0bd307523281c (diff) | |
download | luaotfload-4f053696e1813fde4bd6cebbb77ff2a1e1f6800b.tar.gz |
[fontloader] sync with Context as of 2015-10-29
Diffstat (limited to 'src/fontloader/misc')
22 files changed, 2771 insertions, 1233 deletions
diff --git a/src/fontloader/misc/fontloader-basics-nod.lua b/src/fontloader/misc/fontloader-basics-nod.lua index 1ec2895..39400a3 100644 --- a/src/fontloader/misc/fontloader-basics-nod.lua +++ b/src/fontloader/misc/fontloader-basics-nod.lua @@ -56,6 +56,9 @@ local whatcodes = { } for k,v in next, node.whatsits() do whatcodes[string.gs local glyphcodes = { [0] = "character", "glyph", "ligature", "ghost", "left", "right" } local disccodes = { [0] = "discretionary", "explicit", "automatic", "regular", "first", "second" } +for i=0,#glyphcodes do glyphcodes[glyphcodes[i]] = i end +for i=0,#disccodes do disccodes [disccodes [i]] = i end + nodes.nodecodes = nodecodes nodes.whatcodes = whatcodes nodes.whatsitcodes = whatcodes diff --git a/src/fontloader/misc/fontloader-font-afm.lua b/src/fontloader/misc/fontloader-font-afm.lua index a96c668..329639b 100644 --- a/src/fontloader/misc/fontloader-font-afm.lua +++ b/src/fontloader/misc/fontloader-font-afm.lua @@ -152,14 +152,14 @@ end local keys = { } -function keys.FontName (data,line) data.metadata.fontname = strip (line) -- get rid of spaces - data.metadata.fullname = strip (line) end -function keys.ItalicAngle (data,line) data.metadata.italicangle = tonumber (line) end -function keys.IsFixedPitch(data,line) data.metadata.isfixedpitch = toboolean(line,true) end -function keys.CharWidth (data,line) data.metadata.charwidth = tonumber (line) end -function keys.XHeight (data,line) data.metadata.xheight = tonumber (line) end -function keys.Descender (data,line) data.metadata.descender = tonumber (line) end -function keys.Ascender (data,line) data.metadata.ascender = tonumber (line) end +function keys.FontName (data,line) data.metadata.fontname = strip (line) -- get rid of spaces + data.metadata.fullname = strip (line) end +function keys.ItalicAngle (data,line) data.metadata.italicangle = tonumber (line) end +function keys.IsFixedPitch(data,line) data.metadata.monospaced = toboolean(line,true) end +function keys.CharWidth (data,line) data.metadata.charwidth = tonumber (line) end +function keys.XHeight (data,line) data.metadata.xheight = tonumber (line) end +function keys.Descender (data,line) data.metadata.descender = tonumber (line) end +function keys.Ascender (data,line) data.metadata.ascender = tonumber (line) end function keys.Comment (data,line) -- Comment DesignSize 12 (pts) -- Comment TFM designsize: 12 (in points) @@ -640,7 +640,7 @@ local function copytotfm(data) local spacer = "space" local spaceunits = 500 -- - local monospaced = metadata.isfixedpitch + local monospaced = metadata.monospaced local charwidth = metadata.charwidth local italicangle = metadata.italicangle local charxheight = metadata.xheight and metadata.xheight > 0 and metadata.xheight diff --git a/src/fontloader/misc/fontloader-font-con.lua b/src/fontloader/misc/fontloader-font-con.lua index 72fbb5c..383a403 100644 --- a/src/fontloader/misc/fontloader-font-con.lua +++ b/src/fontloader/misc/fontloader-font-con.lua @@ -191,10 +191,9 @@ in the process; numbers are of course copies. Here 65536 equals 1pt. (Due to excessive memory usage in CJK fonts, we no longer pass the boundingbox.)</p> --ldx]]-- --- The scaler is only used for otf and afm and virtual fonts. If --- a virtual font has italic correction make sure to set the --- hasitalics flag. Some more flags will be added in --- the future. +-- The scaler is only used for otf and afm and virtual fonts. If a virtual font has italic +-- correction make sure to set the hasitalics flag. Some more flags will be added in the +-- future. --[[ldx-- <p>The reason why the scaler was originally split, is that for a while we experimented @@ -426,6 +425,7 @@ function constructors.scale(tfmdata,specification) local vdelta = delta -- target.designsize = parameters.designsize -- not really needed so it might become obsolete + target.units = units target.units_per_em = units -- just a trigger for the backend -- local direction = properties.direction or tfmdata.direction or 0 -- pointless, as we don't use omf fonts at all @@ -562,26 +562,27 @@ function constructors.scale(tfmdata,specification) target.mathparameters = nil -- nop end -- - local italickey = "italic" - local useitalics = true -- something context - -- - -- some context specific trickery (this will move to a plugin) + -- Here we support some context specific trickery (this might move to a plugin). During the + -- transition to opentype the engine had troubles with italics so we had some additional code + -- for fixing that. In node mode (text) we don't care much if italics gets passed because + -- the engine does nothign with them then. -- if hasmath then - -- the latest luatex can deal with it itself so we now disable this - -- mechanism here - -- - -- if properties.mathitalics then - -- italickey = "italic_correction" - -- if trace_defining then - -- report_defining("math italics disabled for font %a, fullname %a, filename %a",name,fullname,filename) - -- end - -- end - autoitalicamount = false -- new - elseif properties.textitalics then - italickey = "italic_correction" - useitalics = false - if properties.delaytextitalics then + local mathitalics = properties.mathitalics + if mathitalics == false then + if trace_defining then + report_defining("%s italics %s for font %a, fullname %a, filename %a","math",hasitalics and "ignored" or "disabled",name,fullname,filename) + end + hasitalics = false + autoitalicamount = false + end + else + local textitalics = properties.textitalics + if textitalics == false then + if trace_defining then + report_defining("%s italics %s for font %a, fullname %a, filename %a","text",hasitalics and "ignored" or "disabled",name,fullname,filename) + end + hasitalics = false autoitalicamount = false end end @@ -590,8 +591,7 @@ function constructors.scale(tfmdata,specification) -- if trace_defining then report_defining("defining tfm, name %a, fullname %a, filename %a, hscale %a, vscale %a, math %a, italics %a", - name,fullname,filename,hdelta,vdelta, - hasmath and "enabled" or "disabled",useitalics and "enabled" or "disabled") + name,fullname,filename,hdelta,vdelta,hasmath and "enabled" or "disabled",hasitalics and "enabled" or "disabled") end -- constructors.beforecopyingcharacters(target,tfmdata) @@ -606,15 +606,15 @@ function constructors.scale(tfmdata,specification) local c = changed[unicode] if c then description = descriptions[c] or descriptions[unicode] or character - character = characters[c] or character - index = description.index or c + character = characters[c] or character + index = description.index or c else description = descriptions[unicode] or character - index = description.index or unicode + index = description.index or unicode end else description = descriptions[unicode] or character - index = description.index or unicode + index = description.index or unicode end local width = description.width local height = description.height @@ -699,23 +699,6 @@ function constructors.scale(tfmdata,specification) end end -- - if autoitalicamount then - local vi = description.italic - if not vi then - local vi = description.boundingbox[3] - description.width + autoitalicamount - if vi > 0 then -- < 0 indicates no overshoot or a very small auto italic - chr[italickey] = vi*hdelta - end - elseif vi ~= 0 then - chr[italickey] = vi*hdelta - end - elseif hasitalics then - local vi = description.italic - if vi and vi ~= 0 then - chr[italickey] = vi*hdelta - end - end - -- to be tested if hasmath then -- todo, just operate on descriptions.math local vn = character.next @@ -754,7 +737,7 @@ function constructors.scale(tfmdata,specification) end end end - local va = character.top_accent + local va = character.accent if va then chr.top_accent = vdelta*va end @@ -777,6 +760,27 @@ function constructors.scale(tfmdata,specification) chr.mathkern = kerns -- singular -> should be patched in luatex ! end end + if hasitalics then + local vi = character.italic + if vi and vi ~= 0 then + chr.italic = vi*hdelta + end + end + elseif autoitalicamount then -- itlc feature + local vi = description.italic + if not vi then + local vi = description.boundingbox[3] - description.width + autoitalicamount + if vi > 0 then -- < 0 indicates no overshoot or a very small auto italic + chr.italic = vi*hdelta + end + elseif vi ~= 0 then + chr.italic = vi*hdelta + end + elseif hasitalics then -- unlikely + local vi = character.italic + if vi and vi ~= 0 then + chr.italic = vi*hdelta + end end if haskerns then local vk = character.kerns @@ -843,6 +847,8 @@ function constructors.scale(tfmdata,specification) targetcharacters[unicode] = chr end -- + properties.setitalics = hasitalics -- for postprocessing + -- constructors.aftercopyingcharacters(target,tfmdata) -- constructors.trytosharefont(target,tfmdata) @@ -895,12 +901,21 @@ function constructors.finalize(tfmdata) parameters.slantfactor = tfmdata.slant or 0 end -- - if not parameters.designsize then - parameters.designsize = tfmdata.designsize or (factors.pt * 10) + local designsize = parameters.designsize + if designsize then + parameters.minsize = tfmdata.minsize or designsize + parameters.maxsize = tfmdata.maxsize or designsize + else + designsize = factors.pt * 10 + parameters.designsize = designsize + parameters.minsize = designsize + parameters.maxsize = designsize end + parameters.minsize = tfmdata.minsize or parameters.designsize + parameters.maxsize = tfmdata.maxsize or parameters.designsize -- if not parameters.units then - parameters.units = tfmdata.units_per_em or 1000 + parameters.units = tfmdata.units or tfmdata.units_per_em or 1000 end -- if not tfmdata.descriptions then @@ -976,6 +991,7 @@ function constructors.finalize(tfmdata) tfmdata.auto_protrude = nil tfmdata.extend = nil tfmdata.slant = nil + tfmdata.units = nil tfmdata.units_per_em = nil -- tfmdata.cache = nil diff --git a/src/fontloader/misc/fontloader-font-def.lua b/src/fontloader/misc/fontloader-font-def.lua index fdded3c..add42ee 100644 --- a/src/fontloader/misc/fontloader-font-def.lua +++ b/src/fontloader/misc/fontloader-font-def.lua @@ -183,10 +183,11 @@ end function resolvers.name(specification) local resolve = fonts.names.resolve if resolve then - local resolved, sub = resolve(specification.name,specification.sub,specification) -- we pass specification for overloaded versions + local resolved, sub, subindex = resolve(specification.name,specification.sub,specification) -- we pass specification for overloaded versions if resolved then specification.resolved = resolved specification.sub = sub + specification.subindex = subindex local suffix = lower(suffixonly(resolved)) if fonts.formats[suffix] then specification.forced = suffix @@ -204,10 +205,11 @@ end function resolvers.spec(specification) local resolvespec = fonts.names.resolvespec if resolvespec then - local resolved, sub = resolvespec(specification.name,specification.sub,specification) -- we pass specification for overloaded versions + local resolved, sub, subindex = resolvespec(specification.name,specification.sub,specification) -- we pass specification for overloaded versions if resolved then specification.resolved = resolved specification.sub = sub + specification.subindex = subindex specification.forced = lower(suffixonly(resolved)) specification.forcedname = resolved specification.name = removesuffix(resolved) diff --git a/src/fontloader/misc/fontloader-font-map.lua b/src/fontloader/misc/fontloader-font-map.lua index 69474ba..b645d9a 100644 --- a/src/fontloader/misc/fontloader-font-map.lua +++ b/src/fontloader/misc/fontloader-font-map.lua @@ -31,25 +31,27 @@ of obsolete. Some code may move to runtime or auxiliary modules.</p> <p>The name to unciode related code will stay of course.</p> --ldx]]-- -local function loadlumtable(filename) -- will move to font goodies - local lumname = file.replacesuffix(file.basename(filename),"lum") - local lumfile = resolvers.findfile(lumname,"map") or "" - if lumfile ~= "" and lfs.isfile(lumfile) then - if trace_loading or trace_mapping then - report_fonts("loading map table %a",lumfile) - end - lumunic = dofile(lumfile) - return lumunic, lumfile - end -end +-- local function loadlumtable(filename) -- will move to font goodies +-- local lumname = file.replacesuffix(file.basename(filename),"lum") +-- local lumfile = resolvers.findfile(lumname,"map") or "" +-- if lumfile ~= "" and lfs.isfile(lumfile) then +-- if trace_loading or trace_mapping then +-- report_fonts("loading map table %a",lumfile) +-- end +-- lumunic = dofile(lumfile) +-- return lumunic, lumfile +-- end +-- end local hex = R("AF","09") -local hexfour = (hex*hex*hex*hex) / function(s) return tonumber(s,16) end -local hexsix = (hex*hex*hex*hex*hex*hex) / function(s) return tonumber(s,16) end +----- hexfour = (hex*hex*hex*hex) / function(s) return tonumber(s,16) end +----- hexsix = (hex*hex*hex*hex*hex*hex) / function(s) return tonumber(s,16) end +local hexfour = (hex*hex*hex^-2) / function(s) return tonumber(s,16) end +local hexsix = (hex*hex*hex^-4) / function(s) return tonumber(s,16) end local dec = (R("09")^1) / tonumber local period = P(".") -local unicode = P("uni") * (hexfour * (period + P(-1)) * Cc(false) + Ct(hexfour^1) * Cc(true)) -local ucode = P("u") * (hexsix * (period + P(-1)) * Cc(false) + Ct(hexsix ^1) * Cc(true)) +local unicode = (P("uni") + P("UNI")) * (hexfour * (period + P(-1)) * Cc(false) + Ct(hexfour^1) * Cc(true)) -- base planes +local ucode = (P("u") + P("U") ) * (hexsix * (period + P(-1)) * Cc(false) + Ct(hexsix ^1) * Cc(true)) -- extended local index = P("index") * dec * Cc(false) local parser = unicode + ucode + index @@ -168,7 +170,6 @@ end -- return s -- end -mappings.loadlumtable = loadlumtable mappings.makenameparser = makenameparser mappings.tounicode = tounicode mappings.tounicode16 = tounicode16 @@ -179,13 +180,13 @@ local ligseparator = P("_") local varseparator = P(".") local namesplitter = Ct(C((1 - ligseparator - varseparator)^1) * (ligseparator * C((1 - ligseparator - varseparator)^1))^0) +-- maybe: ff fi fl ffi ffl => f_f f_i f_l f_f_i f_f_l + -- local function test(name) -- local split = lpegmatch(namesplitter,name) -- print(string.formatters["%s: [% t]"](name,split)) -- end --- maybe: ff fi fl ffi ffl => f_f f_i f_l f_f_i f_f_l - -- test("i.f_") -- test("this") -- test("this.that") @@ -221,332 +222,184 @@ end mappings.overloads = overloads -function mappings.addtounicode(data,filename) - local resources = data.resources - local properties = data.properties - local descriptions = data.descriptions - local unicodes = resources.unicodes - local lookuptypes = resources.lookuptypes +function mappings.addtounicode(data,filename,checklookups) + local resources = data.resources + local unicodes = resources.unicodes if not unicodes then return end + local properties = data.properties + local descriptions = data.descriptions -- we need to move this code unicodes['space'] = unicodes['space'] or 32 unicodes['hyphen'] = unicodes['hyphen'] or 45 unicodes['zwj'] = unicodes['zwj'] or 0x200D unicodes['zwnj'] = unicodes['zwnj'] or 0x200C - local private = fonts.constructors.privateoffset - local unicodevector = fonts.encodings.agl.unicodes -- loaded runtime in context - ----- namevector = fonts.encodings.agl.names -- loaded runtime in context - local missing = { } - local lumunic, uparser, oparser - local cidinfo, cidnames, cidcodes, usedmap - -- - cidinfo = properties.cidinfo - usedmap = cidinfo and fonts.cid.getmap(cidinfo) -- + local private = fonts.constructors and fonts.constructors.privateoffset or 0xF0000 -- 0x10FFFF + local unicodevector = fonts.encodings.agl.unicodes or { } -- loaded runtime in context + local contextvector = fonts.encodings.agl.ctxcodes or { } -- loaded runtime in context + local missing = { } + local nofmissing = 0 + local oparser = nil + local cidnames = nil + local cidcodes = nil + local cidinfo = properties.cidinfo + local usedmap = cidinfo and fonts.cid.getmap(cidinfo) + local uparser = makenameparser() -- hm, every time? if usedmap then - oparser = usedmap and makenameparser(cidinfo.ordering) - cidnames = usedmap.names - cidcodes = usedmap.unicodes + oparser = usedmap and makenameparser(cidinfo.ordering) + cidnames = usedmap.names + cidcodes = usedmap.unicodes end - uparser = makenameparser() - local ns, nl = 0, 0 + local ns = 0 + local nl = 0 + -- for unic, glyph in next, descriptions do - local index = glyph.index - local name = glyph.name - local r = overloads[name] - if r then - -- get rid of weird ligatures - -- glyph.name = r.name - glyph.unicode = r.unicode - elseif unic == -1 or unic >= private or (unic >= 0xE000 and unic <= 0xF8FF) or unic == 0xFFFE or unic == 0xFFFF then - local unicode = lumunic and lumunic[name] or unicodevector[name] - if unicode then - glyph.unicode = unicode - ns = ns + 1 - end - -- cidmap heuristics, beware, there is no guarantee for a match unless - -- the chain resolves - if (not unicode) and usedmap then - local foundindex = lpegmatch(oparser,name) - if foundindex then - unicode = cidcodes[foundindex] -- name to number - if unicode then - glyph.unicode = unicode - ns = ns + 1 - else - local reference = cidnames[foundindex] -- number to name - if reference then - local foundindex = lpegmatch(oparser,reference) - if foundindex then - unicode = cidcodes[foundindex] - if unicode then - glyph.unicode = unicode - ns = ns + 1 + local name = glyph.name + if name then + local index = glyph.index + local r = overloads[name] + if r then + -- get rid of weird ligatures + -- glyph.name = r.name + glyph.unicode = r.unicode + elseif not unic or unic == -1 or unic >= private or (unic >= 0xE000 and unic <= 0xF8FF) or unic == 0xFFFE or unic == 0xFFFF then + local unicode = unicodevector[name] or contextvector[name] + if unicode then + glyph.unicode = unicode + ns = ns + 1 + end + -- cidmap heuristics, beware, there is no guarantee for a match unless + -- the chain resolves + if (not unicode) and usedmap then + local foundindex = lpegmatch(oparser,name) + if foundindex then + unicode = cidcodes[foundindex] -- name to number + if unicode then + glyph.unicode = unicode + ns = ns + 1 + else + local reference = cidnames[foundindex] -- number to name + if reference then + local foundindex = lpegmatch(oparser,reference) + if foundindex then + unicode = cidcodes[foundindex] + if unicode then + glyph.unicode = unicode + ns = ns + 1 + end end - end - if not unicode or unicode == "" then - local foundcodes, multiple = lpegmatch(uparser,reference) - if foundcodes then - glyph.unicode = foundcodes - if multiple then - nl = nl + 1 - unicode = true - else - ns = ns + 1 - unicode = foundcodes + if not unicode or unicode == "" then + local foundcodes, multiple = lpegmatch(uparser,reference) + if foundcodes then + glyph.unicode = foundcodes + if multiple then + nl = nl + 1 + unicode = true + else + ns = ns + 1 + unicode = foundcodes + end end end end end end end - end - -- a.whatever or a_b_c.whatever or a_b_c (no numbers) a.b_ - -- - -- It is not trivial to find a solution that suits all fonts. We tried several alternatives - -- and this one seems to work reasonable also with fonts that use less standardized naming - -- schemes. The extra private test is tested by KE and seems to work okay with non-typical - -- fonts as well. - -- - -- The next time I look into this, I'll add an extra analysis step to the otf loader (we can - -- resolve some tounicodes by looking into the gsub data tables that are bound to glyphs. - -- --- a real tricky last resort: --- --- local lookups = glyph.lookups --- if lookups then --- for _, lookup in next, lookups do -- assume consistency else we need to sort --- for i=1,#lookup do --- local l = lookup[i] --- if l.type == "ligature" then --- local s = l.specification --- if s.char == glyph.name then --- local components = s.components --- if components then --- local t, n = { }, 0 --- unicode = true --- for l=1,#components do --- local base = components[l] --- local u = unicodes[base] or unicodevector[base] --- if not u then --- break --- elseif type(u) == "table" then --- if u[1] >= private then --- unicode = false --- break --- end --- n = n + 1 --- t[n] = u[1] --- else --- if u >= private then --- unicode = false --- break --- end --- n = n + 1 --- t[n] = u --- end --- end --- if n == 0 then -- done then --- -- nothing --- elseif n == 1 then --- glyph.unicode = t[1] --- else --- glyph.unicode = t --- end --- nl = nl + 1 --- break --- end --- end --- end --- end --- if unicode then --- break --- end --- end --- end - if not unicode or unicode == "" then - local split = lpegmatch(namesplitter,name) - local nsplit = split and #split or 0 - local t, n = { }, 0 - unicode = true - for l=1,nsplit do - local base = split[l] - local u = unicodes[base] or unicodevector[base] - if not u then - break - elseif type(u) == "table" then - if u[1] >= private then - unicode = false - break + -- a.whatever or a_b_c.whatever or a_b_c (no numbers) a.b_ + -- + -- It is not trivial to find a solution that suits all fonts. We tried several alternatives + -- and this one seems to work reasonable also with fonts that use less standardized naming + -- schemes. The extra private test is tested by KE and seems to work okay with non-typical + -- fonts as well. + -- + if not unicode or unicode == "" then + local split = lpegmatch(namesplitter,name) + local nsplit = split and #split or 0 -- add if + if nsplit == 0 then + -- skip + elseif nsplit == 1 then + local base = split[1] + local u = unicodes[base] or unicodevector[base] or contextvector[name] + if not u then + -- skip + elseif type(u) == "table" then + -- unlikely + if u[1] < private then + unicode = u + glyph.unicode = unicode + end + elseif u < private then + unicode = u + glyph.unicode = unicode end - n = n + 1 - t[n] = u[1] else - if u >= private then - unicode = false - break - end - n = n + 1 - t[n] = u - end - end - if n == 0 then -- done then - -- nothing - elseif n == 1 then - glyph.unicode = t[1] - else - glyph.unicode = t - end - nl = nl + 1 - end - -- last resort (we might need to catch private here as well) - if not unicode or unicode == "" then - local foundcodes, multiple = lpegmatch(uparser,name) - if foundcodes then - glyph.unicode = foundcodes - if multiple then - nl = nl + 1 - unicode = true - else - ns = ns + 1 - unicode = foundcodes - end - end - end - -- check using substitutes and alternates - local r = overloads[unicode] - if r then - unicode = r.unicode - glyph.unicode = unicode - end - -- - if not unicode then - missing[name] = true - end - end - end - if next(missing) then - local guess = { } - -- helper - local function check(gname,code,unicode) - local description = descriptions[code] - -- no need to add a self reference - local variant = description.name - if variant == gname then - return - end - -- the variant already has a unicode (normally that resultrs in a default tounicode to self) - local unic = unicodes[variant] - if unic == -1 or unic >= private or (unic >= 0xE000 and unic <= 0xF8FF) or unic == 0xFFFE or unic == 0xFFFF then - -- no default mapping and therefore maybe no tounicode yet - else - return - end - -- the variant already has a tounicode - if descriptions[code].unicode then - return - end - -- add to the list - local g = guess[variant] - -- local r = overloads[unicode] - -- if r then - -- unicode = r.unicode - -- end - if g then - g[gname] = unicode - else - guess[variant] = { [gname] = unicode } - end - end - -- - for unicode, description in next, descriptions do - local slookups = description.slookups - if slookups then - local gname = description.name - for tag, data in next, slookups do - local lookuptype = lookuptypes[tag] - if lookuptype == "alternate" then - for i=1,#data do - check(gname,data[i],unicode) - end - elseif lookuptype == "substitution" then - check(gname,data,unicode) - end - end - end - local mlookups = description.mlookups - if mlookups then - local gname = description.name - for tag, list in next, mlookups do - local lookuptype = lookuptypes[tag] - if lookuptype == "alternate" then - for i=1,#list do - local data = list[i] - for i=1,#data do - check(gname,data[i],unicode) + local t, n = { }, 0 + for l=1,nsplit do + local base = split[l] + local u = unicodes[base] or unicodevector[base] or contextvector[name] + if not u then + break + elseif type(u) == "table" then + if u[1] >= private then + break + end + n = n + 1 + t[n] = u[1] + else + if u >= private then + break + end + n = n + 1 + t[n] = u end end - elseif lookuptype == "substitution" then - for i=1,#list do - check(gname,list[i],unicode) + if n > 0 then + if n == 1 then + unicode = t[1] + else + unicode = t + end + glyph.unicode = unicode end end + nl = nl + 1 end - end - end - -- resolve references - local done = true - while done do - done = false - for k, v in next, guess do - if type(v) ~= "number" then - for kk, vv in next, v do - if vv == -1 or vv >= private or (vv >= 0xE000 and vv <= 0xF8FF) or vv == 0xFFFE or vv == 0xFFFF then - local uu = guess[kk] - if type(uu) == "number" then - guess[k] = uu - done = true - end + -- last resort (we might need to catch private here as well) + if not unicode or unicode == "" then + local foundcodes, multiple = lpegmatch(uparser,name) + if foundcodes then + glyph.unicode = foundcodes + if multiple then + nl = nl + 1 + unicode = true else - guess[k] = vv - done = true + ns = ns + 1 + unicode = foundcodes end end end - end - end - -- wrap up - local orphans = 0 - local guessed = 0 - for k, v in next, guess do - if type(v) == "number" then - descriptions[unicodes[k]].unicode = descriptions[v].unicode or v -- can also be a table - guessed = guessed + 1 - else - local t = nil - local l = lower(k) - local u = unicodes[l] - if not u then - orphans = orphans + 1 - elseif u == -1 or u >= private or (u >= 0xE000 and u <= 0xF8FF) or u == 0xFFFE or u == 0xFFFF then - local unicode = descriptions[u].unicode - if unicode then - descriptions[unicodes[k]].unicode = unicode - guessed = guessed + 1 - else - orphans = orphans + 1 - end - else - orphans = orphans + 1 + -- check using substitutes and alternates + local r = overloads[unicode] + if r then + unicode = r.unicode + glyph.unicode = unicode + end + -- + if not unicode then + missing[unic] = true + nofmissing = nofmissing + 1 end end + else + -- no name end - if trace_loading and orphans > 0 or guessed > 0 then - report_fonts("%s glyphs with no related unicode, %s guessed, %s orphans",guessed+orphans,guessed,orphans) - end end + if type(checklookups) == "function" then + checklookups(data,missing,nofmissing) + end + -- todo: go lowercase if trace_mapping then for unic, glyph in table.sortedhash(descriptions) do local name = glyph.name diff --git a/src/fontloader/misc/fontloader-font-otb.lua b/src/fontloader/misc/fontloader-font-otb.lua index 4e955a1..c9f5d4a 100644 --- a/src/fontloader/misc/fontloader-font-otb.lua +++ b/src/fontloader/misc/fontloader-font-otb.lua @@ -321,14 +321,14 @@ local function preparesubstitutions(tfmdata,feature,value,validlookups,lookuplis for unicode, character in next, characters do local description = descriptions[unicode] - local lookups = description.slookups + local lookups = description.slookups if lookups then for l=1,#lookuplist do local lookupname = lookuplist[l] local lookupdata = lookups[lookupname] if lookupdata then local lookuptype = lookuptypes[lookupname] - local action = actions[lookuptype] + local action = actions[lookuptype] if action then action(lookupdata,lookuptags,lookupname,description,unicode) end @@ -342,7 +342,7 @@ local function preparesubstitutions(tfmdata,feature,value,validlookups,lookuplis local lookuplist = lookups[lookupname] if lookuplist then local lookuptype = lookuptypes[lookupname] - local action = actions[lookuptype] + local action = actions[lookuptype] if action then for i=1,#lookuplist do action(lookuplist[i],lookuptags,lookupname,description,unicode) @@ -614,8 +614,8 @@ local function featuresinitializer(tfmdata,value) local collectlookups = otf.collectlookups local rawdata = tfmdata.shared.rawdata local properties = tfmdata.properties - local script = properties.script - local language = properties.language + local script = properties.script -- or "dflt" -- can be nil + local language = properties.language -- or "dflt" -- can be nil local basesubstitutions = rawdata.resources.features.gsub local basepositionings = rawdata.resources.features.gpos -- diff --git a/src/fontloader/misc/fontloader-font-otf.lua b/src/fontloader/misc/fontloader-font-otf.lua index e7a97c6..0ca1e98 100644 --- a/src/fontloader/misc/fontloader-font-otf.lua +++ b/src/fontloader/misc/fontloader-font-otf.lua @@ -12,15 +12,19 @@ if not modules then modules = { } end modules ['font-otf'] = { -- to_table -> totable -- ascent descent +-- to be checked: combinations like: +-- +-- current="ABCD" with [A]=nothing, [BC]=ligature, [D]=single (applied to result of BC so funny index) +-- +-- unlikely but possible + -- more checking against low level calls of functions local utfbyte = utf.byte -local format, gmatch, gsub, find, match, lower, strip = string.format, string.gmatch, string.gsub, string.find, string.match, string.lower, string.strip +local gmatch, gsub, find, match, lower, strip = string.gmatch, string.gsub, string.find, string.match, string.lower, string.strip local type, next, tonumber, tostring = type, next, tonumber, tostring local abs = math.abs -local insert = table.insert -local lpegmatch = lpeg.match -local reversed, concat, remove, sortedkeys = table.reversed, table.concat, table.remove, table.sortedkeys +local reversed, concat, insert, remove, sortedkeys = table.reversed, table.concat, table.insert, table.remove, table.sortedkeys local ioflush = io.flush local fastcopy, tohash, derivetable = table.fastcopy, table.tohash, table.derive local formatters = string.formatters @@ -54,7 +58,7 @@ local otf = fonts.handlers.otf otf.glists = { "gsub", "gpos" } -otf.version = 2.812 -- beware: also sync font-mis.lua +otf.version = 2.819 -- beware: also sync font-mis.lua and in mtx-fonts otf.cache = containers.define("fonts", "otf", otf.version, true) local hashes = fonts.hashes @@ -283,13 +287,17 @@ local ordered_enhancers = { "check glyphs", "check metadata", - "check extra features", -- after metadata +-- "check extra features", -- after metadata "prepare tounicode", "check encoding", -- moved "add duplicates", + "expand lookups", -- a temp hack awaiting the lua loader + +-- "check extra features", -- after metadata and duplicates + "cleanup tables", "compact lookups", @@ -386,6 +394,7 @@ function otf.load(filename,sub,featurefile) -- second argument (format) is gone if featurefile then name = name .. "@" .. file.removesuffix(file.basename(featurefile)) end + -- or: sub = tonumber(sub) if sub == "" then sub = false end @@ -442,6 +451,7 @@ function otf.load(filename,sub,featurefile) -- second argument (format) is gone end end if reload then + starttiming("fontloader") report_otf("loading %a, hash %a",filename,hash) local fontdata, messages if sub then @@ -476,6 +486,7 @@ function otf.load(filename,sub,featurefile) -- second argument (format) is gone data = { size = size, time = time, + subfont = sub, format = otf_format(filename), featuredata = featurefiles, resources = { @@ -512,7 +523,6 @@ function otf.load(filename,sub,featurefile) -- second argument (format) is gone tounicodetable = Ct(splitter), }, } - starttiming(data) report_otf("file size: %s", size) enhancers.apply(data,filename,fontdata) local packtime = { } @@ -529,10 +539,10 @@ function otf.load(filename,sub,featurefile) -- second argument (format) is gone if cleanup > 1 then collectgarbage("collect") end - stoptiming(data) + stoptiming("fontloader") if elapsedtime then -- not in generic - report_otf("preprocessing and caching time %s, packtime %s", - elapsedtime(data),packdata and elapsedtime(packtime) or 0) + report_otf("loading, optimizing, packing and caching time %s, pack time %s", + elapsedtime("fontloader"),packdata and elapsedtime(packtime) or 0) end close_font(fontdata) -- free memory if cleanup > 3 then @@ -543,6 +553,7 @@ function otf.load(filename,sub,featurefile) -- second argument (format) is gone collectgarbage("collect") end else + stoptiming("fontloader") data = nil report_otf("loading failed due to read error") end @@ -589,6 +600,7 @@ function otf.load(filename,sub,featurefile) -- second argument (format) is gone applyruntimefixes(filename,data) end enhance("add dimensions",data,filename,nil,false) +enhance("check extra features",data,filename) if trace_sequences then showfeatureorder(data,filename) end @@ -697,7 +709,7 @@ local function somecopy(old) -- fast one end end --- not setting hasitalics and class (when nil) during table cronstruction can save some mem +-- not setting hasitalics and class (when nil) during table construction can save some mem actions["prepare glyphs"] = function(data,filename,raw) local tableversion = tonumber(raw.table_version) or 0 @@ -780,7 +792,7 @@ actions["prepare glyphs"] = function(data,filename,raw) end if not unicode or unicode == -1 then -- or unicode >= criterium then if not name then - name = format("u%06X.ctx",private) + name = formatters["u%06X.ctx"](private) end unicode = private unicodes[name] = private @@ -803,7 +815,7 @@ actions["prepare glyphs"] = function(data,filename,raw) -- end -- end if not name then - name = format("u%06X.ctx",unicode) + name = formatters["u%06X.ctx"](unicode) end unicodes[name] = unicode nofunicodes = nofunicodes + 1 @@ -819,35 +831,35 @@ actions["prepare glyphs"] = function(data,filename,raw) glyph = glyph, } descriptions[unicode] = description -local altuni = glyph.altuni -if altuni then - -- local d - for i=1,#altuni do - local a = altuni[i] - local u = a.unicode - if u ~= unicode then - local v = a.variant - if v then - -- tricky: no addition to d? needs checking but in practice such dups are either very simple - -- shapes or e.g cjk with not that many features - local vv = variants[v] - if vv then - vv[u] = unicode - else -- xits-math has some: - vv = { [u] = unicode } - variants[v] = vv - end - -- elseif d then - -- d[#d+1] = u - -- else - -- d = { u } - end - end - end - -- if d then - -- duplicates[unicode] = d -- is this needed ? - -- end -end + local altuni = glyph.altuni + if altuni then + -- local d + for i=1,#altuni do + local a = altuni[i] + local u = a.unicode + if u ~= unicode then + local v = a.variant + if v then + -- tricky: no addition to d? needs checking but in practice such dups are either very simple + -- shapes or e.g cjk with not that many features + local vv = variants[v] + if vv then + vv[u] = unicode + else -- xits-math has some: + vv = { [u] = unicode } + variants[v] = vv + end + -- elseif d then + -- d[#d+1] = u + -- else + -- d = { u } + end + end + end + -- if d then + -- duplicates[unicode] = d -- is this needed ? + -- end + end end end else @@ -916,7 +928,7 @@ end end indices[index] = unicode -- if not name then - -- name = format("u%06X",unicode) -- u%06X.ctx + -- name = formatters["u%06X"](unicode) -- u%06X.ctx -- end descriptions[unicode] = { -- width = glyph.width, @@ -1089,7 +1101,7 @@ actions["add duplicates"] = function(data,filename,raw) end if u > 0 then -- and local duplicate = table.copy(description) -- else packing problem - duplicate.comment = format("copy of U+%05X", unicode) + duplicate.comment = formatters["copy of %U"](unicode) descriptions[u] = duplicate -- validduplicates[#validduplicates+1] = u if trace_loading then @@ -1107,16 +1119,16 @@ end -- boundingbox: split into ht/dp takes more memory (larger tables and less sharing) actions["analyze glyphs"] = function(data,filename,raw) -- maybe integrate this in the previous - local descriptions = data.descriptions - local resources = data.resources - local metadata = data.metadata - local properties = data.properties - local hasitalics = false - local widths = { } - local marks = { } -- always present (saves checking) + local descriptions = data.descriptions + local resources = data.resources + local metadata = data.metadata + local properties = data.properties + local hasitalics = false + local widths = { } + local marks = { } -- always present (saves checking) for unicode, description in next, descriptions do - local glyph = description.glyph - local italic = glyph.italic_correction + local glyph = description.glyph + local italic = glyph.italic_correction -- only in a math font (we also have vert/horiz) if not italic then -- skip elseif italic == 0 then @@ -1185,7 +1197,8 @@ end actions["reorganize features"] = function(data,filename,raw) -- combine with other local features = { } data.resources.features = features - for k, what in next, otf.glists do + for k=1,#otf.glists do + local what = otf.glists[k] local dw = raw[what] if dw then local f = { } @@ -1254,6 +1267,140 @@ actions["reorganize anchor classes"] = function(data,filename,raw) end end +-- local function checklookups(data,missing,nofmissing) +-- local resources = data.resources +-- local unicodes = resources.unicodes +-- local lookuptypes = resources.lookuptypes +-- if not unicodes or not lookuptypes then +-- return +-- elseif nofmissing <= 0 then +-- return +-- end +-- local descriptions = data.descriptions +-- local private = fonts.constructors and fonts.constructors.privateoffset or 0xF0000 -- 0x10FFFF +-- -- +-- local ns, nl = 0, 0 + +-- local guess = { } +-- -- helper +-- local function check(gname,code,unicode) +-- local description = descriptions[code] +-- -- no need to add a self reference +-- local variant = description.name +-- if variant == gname then +-- return +-- end +-- -- the variant already has a unicode (normally that results in a default tounicode to self) +-- local unic = unicodes[variant] +-- if unic == -1 or unic >= private or (unic >= 0xE000 and unic <= 0xF8FF) or unic == 0xFFFE or unic == 0xFFFF then +-- -- no default mapping and therefore maybe no tounicode yet +-- else +-- return +-- end +-- -- the variant already has a tounicode +-- if descriptions[code].unicode then +-- return +-- end +-- -- add to the list +-- local g = guess[variant] +-- -- local r = overloads[unicode] +-- -- if r then +-- -- unicode = r.unicode +-- -- end +-- if g then +-- g[gname] = unicode +-- else +-- guess[variant] = { [gname] = unicode } +-- end +-- end +-- -- +-- for unicode, description in next, descriptions do +-- local slookups = description.slookups +-- if slookups then +-- local gname = description.name +-- for tag, data in next, slookups do +-- local lookuptype = lookuptypes[tag] +-- if lookuptype == "alternate" then +-- for i=1,#data do +-- check(gname,data[i],unicode) +-- end +-- elseif lookuptype == "substitution" then +-- check(gname,data,unicode) +-- end +-- end +-- end +-- local mlookups = description.mlookups +-- if mlookups then +-- local gname = description.name +-- for tag, list in next, mlookups do +-- local lookuptype = lookuptypes[tag] +-- if lookuptype == "alternate" then +-- for i=1,#list do +-- local data = list[i] +-- for i=1,#data do +-- check(gname,data[i],unicode) +-- end +-- end +-- elseif lookuptype == "substitution" then +-- for i=1,#list do +-- check(gname,list[i],unicode) +-- end +-- end +-- end +-- end +-- end +-- -- resolve references +-- local done = true +-- while done do +-- done = false +-- for k, v in next, guess do +-- if type(v) ~= "number" then +-- for kk, vv in next, v do +-- if vv == -1 or vv >= private or (vv >= 0xE000 and vv <= 0xF8FF) or vv == 0xFFFE or vv == 0xFFFF then +-- local uu = guess[kk] +-- if type(uu) == "number" then +-- guess[k] = uu +-- done = true +-- end +-- else +-- guess[k] = vv +-- done = true +-- end +-- end +-- end +-- end +-- end +-- -- wrap up +-- local orphans = 0 +-- local guessed = 0 +-- for k, v in next, guess do +-- if type(v) == "number" then +-- descriptions[unicodes[k]].unicode = descriptions[v].unicode or v -- can also be a table +-- guessed = guessed + 1 +-- else +-- local t = nil +-- local l = lower(k) +-- local u = unicodes[l] +-- if not u then +-- orphans = orphans + 1 +-- elseif u == -1 or u >= private or (u >= 0xE000 and u <= 0xF8FF) or u == 0xFFFE or u == 0xFFFF then +-- local unicode = descriptions[u].unicode +-- if unicode then +-- descriptions[unicodes[k]].unicode = unicode +-- guessed = guessed + 1 +-- else +-- orphans = orphans + 1 +-- end +-- else +-- orphans = orphans + 1 +-- end +-- end +-- end +-- if trace_loading and orphans > 0 or guessed > 0 then +-- report_otf("%s glyphs with no related unicode, %s guessed, %s orphans",guessed+orphans,guessed,orphans) +-- end +-- end + actions["prepare tounicode"] = function(data,filename,raw) fonts.mappings.addtounicode(data,filename) end @@ -1288,8 +1435,9 @@ actions["reorganize subtables"] = function(data,filename,raw) local lookups = { } local chainedfeatures = { } resources.sequences = sequences - resources.lookups = lookups - for _, what in next, otf.glists do + resources.lookups = lookups -- we also have lookups in data itself + for k=1,#otf.glists do + local what = otf.glists[k] local dw = raw[what] if dw then for k=1,#dw do @@ -1375,12 +1523,6 @@ actions["reorganize subtables"] = function(data,filename,raw) end end --- test this: --- --- for _, what in next, otf.glists do --- raw[what] = nil --- end - actions["prepare lookups"] = function(data,filename,raw) local lookups = raw.lookups if lookups then @@ -1494,12 +1636,16 @@ end actions["reorganize lookups"] = function(data,filename,raw) -- we could check for "" and n == 0 -- we prefer the before lookups in a normal order if data.lookups then - local splitter = data.helpers.tounicodetable - local t_u_cache = { } - local s_u_cache = t_u_cache -- string keys - local t_h_cache = { } - local s_h_cache = t_h_cache -- table keys (so we could use one cache) - local r_u_cache = { } -- maybe shared + local helpers = data.helpers + local duplicates = data.resources.duplicates + local splitter = helpers.tounicodetable + local t_u_cache = { } + local s_u_cache = t_u_cache -- string keys + local t_h_cache = { } + local s_h_cache = t_h_cache -- table keys (so we could use one cache) + local r_u_cache = { } -- maybe shared + helpers.matchcache = t_h_cache -- so that we can add duplicates + -- for _, lookup in next, data.lookups do local rules = lookup.rules if rules then @@ -1653,6 +1799,50 @@ actions["reorganize lookups"] = function(data,filename,raw) -- we could check fo end end +actions["expand lookups"] = function(data,filename,raw) -- we could check for "" and n == 0 + if data.lookups then + local cache = data.helpers.matchcache + if cache then + local duplicates = data.resources.duplicates + for key, hash in next, cache do + local done = nil + for key in next, hash do + local unicode = duplicates[key] + if not unicode then + -- no duplicate + elseif type(unicode) == "table" then + -- multiple duplicates + for i=1,#unicode do + local u = unicode[i] + if hash[u] then + -- already in set + elseif done then + done[u] = key + else + done = { [u] = key } + end + end + else + -- one duplicate + if hash[unicode] then + -- already in set + elseif done then + done[unicode] = key + else + done = { [unicode] = key } + end + end + end + if done then + for u in next, done do + hash[u] = true + end + end + end + end + end +end + local function check_variants(unicode,the_variants,splitter,unicodes) local variants = the_variants.variants if variants then -- use splitter @@ -1693,11 +1883,11 @@ local function check_variants(unicode,the_variants,splitter,unicodes) parts = nil end end - local italic_correction = the_variants.italic_correction - if italic_correction and italic_correction == 0 then - italic_correction = nil + local italic = the_variants.italic + if italic and italic == 0 then + italic = nil end - return variants, parts, italic_correction + return variants, parts, italic end actions["analyze math"] = function(data,filename,raw) @@ -1706,15 +1896,16 @@ actions["analyze math"] = function(data,filename,raw) local unicodes = data.resources.unicodes local splitter = data.helpers.tounicodetable for unicode, description in next, data.descriptions do - local glyph = description.glyph - local mathkerns = glyph.mathkern -- singular - local horiz_variants = glyph.horiz_variants - local vert_variants = glyph.vert_variants - local top_accent = glyph.top_accent - if mathkerns or horiz_variants or vert_variants or top_accent then + local glyph = description.glyph + local mathkerns = glyph.mathkern -- singular + local hvariants = glyph.horiz_variants + local vvariants = glyph.vert_variants + local accent = glyph.top_accent + local italic = glyph.italic_correction + if mathkerns or hvariants or vvariants or accent or italic then local math = { } - if top_accent then - math.top_accent = top_accent + if accent then + math.accent = accent end if mathkerns then for k, v in next, mathkerns do @@ -1730,15 +1921,14 @@ actions["analyze math"] = function(data,filename,raw) end math.kerns = mathkerns end - if horiz_variants then - math.horiz_variants, math.horiz_parts, math.horiz_italic_correction = check_variants(unicode,horiz_variants,splitter,unicodes) + if hvariants then + math.hvariants, math.hparts, math.hitalic = check_variants(unicode,hvariants,splitter,unicodes) end - if vert_variants then - math.vert_variants, math.vert_parts, math.vert_italic_correction = check_variants(unicode,vert_variants,splitter,unicodes) + if vvariants then + math.vvariants, math.vparts, math.vitalic = check_variants(unicode,vvariants,splitter,unicodes) end - local italic_correction = description.italic - if italic_correction and italic_correction ~= 0 then - math.italic_correction = italic_correction + if italic and italic ~= 0 then + math.italic = italic end description.math = math end @@ -1903,7 +2093,7 @@ actions["merge kern classes"] = function(data,filename,raw) report_otf("%s kern overloads ignored",ignored) end if blocked > 0 then - report_otf("%s succesive kerns blocked",blocked) + report_otf("%s successive kerns blocked",blocked) end end end @@ -1940,18 +2130,21 @@ actions["check metadata"] = function(data,filename,raw) end end -- + local names = raw.names + -- if metadata.validation_state and table.contains(metadata.validation_state,"bad_ps_fontname") then -- the ff library does a bit too much (and wrong) checking ... so we need to catch this -- at least for now local function valid(what) - local names = raw.names - for i=1,#names do - local list = names[i] - local names = list.names - if names then - local name = names[what] - if name and valid_ps_name(name) then - return name + if names then + for i=1,#names do + local list = names[i] + local names = list.names + if names then + local name = names[what] + if name and valid_ps_name(name) then + return name + end end end end @@ -1975,6 +2168,33 @@ actions["check metadata"] = function(data,filename,raw) check("fullname") end -- + if names then + local psname = metadata.psname + if not psname or psname == "" then + for i=1,#names do + local name = names[i] + -- Currently we use the same restricted search as in the new context (specific) font loader + -- but we might add more lang checks (it worked ok in the new loaded so now we're in sync) + -- This check here is also because there are (esp) cjk fonts out there with psnames different + -- from fontnames (gives a bad lookup in backend). + if lower(name.lang) == "english (us)" then + local specification = name.names + if specification then + local postscriptname = specification.postscriptname + if postscriptname then + psname = postscriptname + end + end + end + break + end + end + if psname ~= metadata.fontname then + report_otf("fontname %a, fullname %a, psname %a",metadata.fontname,metadata.fullname,psname) + end + metadata.psname = psname + end + -- end actions["cleanup tables"] = function(data,filename,raw) @@ -2000,7 +2220,9 @@ end -- we can share { } as it is never set ---- ligatures have an extra specification.char entry that we don't use +-- ligatures have an extra specification.char entry that we don't use + +-- mlookups only with pairs and ligatures actions["reorganize glyph lookups"] = function(data,filename,raw) local resources = data.resources @@ -2081,14 +2303,14 @@ actions["reorganize glyph lookups"] = function(data,filename,raw) if mlookups then description.mlookups = mlookups end + -- description.lookups = nil end end - end local zero = { 0, 0 } -actions["reorganize glyph anchors"] = function(data,filename,raw) -- when we replace inplace we safe entries +actions["reorganize glyph anchors"] = function(data,filename,raw) local descriptions = data.descriptions for unicode, description in next, descriptions do local anchors = description.glyph.anchors @@ -2311,7 +2533,7 @@ end -- we cannot share descriptions as virtual fonts might extend them (ok, -- we could use a cache with a hash -- --- we already assing an empty tabel to characters as we can add for +-- we already assign an empty tabel to characters as we can add for -- instance protruding info and loop over characters; one is not supposed -- to change descriptions and if one does so one should make a copy! @@ -2334,10 +2556,14 @@ local function copytotfm(data,cache_id) local spaceunits = 500 local spacer = "space" local designsize = metadata.designsize or metadata.design_size or 100 + local minsize = metadata.minsize or metadata.design_range_bottom or designsize + local maxsize = metadata.maxsize or metadata.design_range_top or designsize local mathspecs = metadata.math -- if designsize == 0 then designsize = 100 + minsize = 100 + maxsize = 100 end if mathspecs then for name, value in next, mathspecs do @@ -2355,8 +2581,11 @@ local function copytotfm(data,cache_id) local m = d.math if m then -- watch out: luatex uses horiz_variants for the parts - local variants = m.horiz_variants - local parts = m.horiz_parts + -- + local italic = m.italic + -- + local variants = m.hvariants + local parts = m.hparts -- local done = { [unicode] = true } if variants then local c = character @@ -2373,9 +2602,11 @@ local function copytotfm(data,cache_id) c.horiz_variants = parts elseif parts then character.horiz_variants = parts + italic = m.hitalic end - local variants = m.vert_variants - local parts = m.vert_parts + -- + local variants = m.vvariants + local parts = m.vparts -- local done = { [unicode] = true } if variants then local c = character @@ -2392,15 +2623,18 @@ local function copytotfm(data,cache_id) c.vert_variants = parts elseif parts then character.vert_variants = parts + italic = m.vitalic end - local italic_correction = m.vert_italic_correction - if italic_correction then - character.vert_italic_correction = italic_correction -- was c. + -- + if italic and italic ~= 0 then + character.italic = italic -- overload end - local top_accent = m.top_accent - if top_accent then - character.top_accent = top_accent + -- + local accent = m.accent + if accent then + character.accent = accent end + -- local kerns = m.kerns if kerns then character.mathkerns = kerns @@ -2413,16 +2647,16 @@ local function copytotfm(data,cache_id) local filename = constructors.checkedfilename(resources) local fontname = metadata.fontname local fullname = metadata.fullname or fontname - local psname = fontname or fullname - local units = metadata.units_per_em or 1000 + local psname = metadata.psname or fontname or fullname + local units = metadata.units or metadata.units_per_em or 1000 -- if units == 0 then -- catch bugs in fonts units = 1000 -- maybe 2000 when ttf - metadata.units_per_em = 1000 + metadata.units = 1000 report_otf("changing %a units to %a",0,units) end -- - local monospaced = metadata.isfixedpitch or (pfminfo.panose and pfminfo.panose.proportion == "Monospaced") + local monospaced = metadata.monospaced or metadata.isfixedpitch or (pfminfo.panose and pfminfo.panose.proportion == "Monospaced") local charwidth = pfminfo.avgwidth -- or unset local charxheight = pfminfo.os2_xheight and pfminfo.os2_xheight > 0 and pfminfo.os2_xheight -- charwidth = charwidth * units/1000 @@ -2492,17 +2726,16 @@ local function copytotfm(data,cache_id) end end -- - parameters.designsize = (designsize/10)*65536 - parameters.ascender = abs(metadata.ascent or 0) - parameters.descender = abs(metadata.descent or 0) - parameters.units = units + parameters.designsize = (designsize/10)*65536 + parameters.minsize = (minsize /10)*65536 + parameters.maxsize = (maxsize /10)*65536 + parameters.ascender = abs(metadata.ascender or metadata.ascent or 0) + parameters.descender = abs(metadata.descender or metadata.descent or 0) + parameters.units = units -- properties.space = spacer properties.encodingbytes = 2 properties.format = data.format or otf_format(filename) or formats.otf --- if units ~= 1000 and format ~= "truetype" then --- properties.format = "truetype" --- end properties.noglyphnames = true properties.filename = filename properties.fontname = fontname @@ -2703,3 +2936,111 @@ function otf.scriptandlanguage(tfmdata,attr) local properties = tfmdata.properties return properties.script or "dflt", properties.language or "dflt" end + +-- a little bit of abstraction + +local function justset(coverage,unicode,replacement) + coverage[unicode] = replacement +end + +otf.coverup = { + stepkey = "subtables", + actions = { + substitution = justset, + alternate = justset, + multiple = justset, + ligature = justset, + kern = justset, + }, + register = function(coverage,lookuptype,format,feature,n,descriptions,resources) + local name = formatters["ctx_%s_%s"](feature,n) + if lookuptype == "kern" then + resources.lookuptypes[name] = "position" + else + resources.lookuptypes[name] = lookuptype + end + for u, c in next, coverage do + local description = descriptions[u] + local slookups = description.slookups + if slookups then + slookups[name] = c + else + description.slookups = { [name] = c } + end +-- inspect(feature,description) + end + return name + end +} + +-- moved from font-oth.lua + +local function getgsub(tfmdata,k,kind) + local description = tfmdata.descriptions[k] + if description then + local slookups = description.slookups -- we assume only slookups (we can always extend) + if slookups then + local shared = tfmdata.shared + local rawdata = shared and shared.rawdata + if rawdata then + local lookuptypes = rawdata.resources.lookuptypes + if lookuptypes then + local properties = tfmdata.properties + -- we could cache these + local validlookups, lookuplist = otf.collectlookups(rawdata,kind,properties.script,properties.language) + if validlookups then + for l=1,#lookuplist do + local lookup = lookuplist[l] + local found = slookups[lookup] + if found then + return found, lookuptypes[lookup] + end + end + end + end + end + end + end +end + +otf.getgsub = getgsub -- returns value, gsub_kind + +function otf.getsubstitution(tfmdata,k,kind,value) + local found, kind = getgsub(tfmdata,k,kind) + if not found then + -- + elseif kind == "substitution" then + return found + elseif kind == "alternate" then + local choice = tonumber(value) or 1 -- no random here (yet) + return found[choice] or found[1] or k + end + return k +end + +otf.getalternate = otf.getsubstitution + +function otf.getmultiple(tfmdata,k,kind) + local found, kind = getgsub(tfmdata,k,kind) + if found and kind == "multiple" then + return found + end + return { k } +end + +function otf.getkern(tfmdata,left,right,kind) + local kerns = getgsub(tfmdata,left,kind or "kern",true) -- for now we use getsub + if kerns then + local found = kerns[right] + local kind = type(found) + if kind == "table" then + found = found[1][3] -- can be more clever + elseif kind ~= "number" then + found = false + end + if found then + return found * tfmdata.parameters.factor + end + end + return 0 +end diff --git a/src/fontloader/misc/fontloader-font-otp.lua b/src/fontloader/misc/fontloader-font-otp.lua index ebf36ed..91bd05b 100644 --- a/src/fontloader/misc/fontloader-font-otp.lua +++ b/src/fontloader/misc/fontloader-font-otp.lua @@ -12,9 +12,8 @@ if not modules then modules = { } end modules ['font-otp'] = { -- -- unless we sort all hashes we can get a different pack order (no big deal but size can differ) -local next, type = next, type +local next, type, tostring = next, type, tostring local sort, concat = table.sort, table.concat -local sortedhash = table.sortedhash local trace_packing = false trackers.register("otf.packing", function(v) trace_packing = v end) local trace_loading = false trackers.register("otf.loading", function(v) trace_loading = v end) @@ -148,6 +147,7 @@ end -- we then need to sort more thanks to random hashing local function packdata(data) + if data then -- stripdata(data) local h, t, c = { }, { }, { } @@ -537,6 +537,7 @@ local unpacked_mt = { } local function unpackdata(data) + if data then local tables = data.tables if tables then diff --git a/src/fontloader/misc/fontloader-font-tfm.lua b/src/fontloader/misc/fontloader-font-tfm.lua index ab03788..401dc83 100644 --- a/src/fontloader/misc/fontloader-font-tfm.lua +++ b/src/fontloader/misc/fontloader-font-tfm.lua @@ -24,6 +24,7 @@ local constructors = fonts.constructors local encodings = fonts.encodings local tfm = constructors.newhandler("tfm") +tfm.version = 1.000 local tfmfeatures = constructors.newfeatures("tfm") local registertfmfeature = tfmfeatures.register diff --git a/src/fontloader/misc/fontloader-fonts-cbk.lua b/src/fontloader/misc/fontloader-fonts-cbk.lua index 81b5b6e..9da8151 100644 --- a/src/fontloader/misc/fontloader-fonts-cbk.lua +++ b/src/fontloader/misc/fontloader-fonts-cbk.lua @@ -160,12 +160,31 @@ function nodes.handlers.nodepass(head) local range = basefonts[i] local start = range[1] local stop = range[2] - if stop then - start, stop = ligaturing(start,stop) - start, stop = kerning(start,stop) - elseif start then - start = ligaturing(start) - start = kerning(start) + -- maybe even: if start and start ~= stop then + if start or stop then + local prev = nil + local next = nil + local front = start == head + if stop then + next = stop.next + start, stop = ligaturing(start,stop) + start, stop = kerning(start,stop) + elseif start then + prev = start.prev + start = ligaturing(start) + start = kerning(start) + end + if prev then + start.prev = prev + prev.next = start + end + if next then + stop.next = next + next.prev = stop + end + if front then + head = start + end end end end @@ -176,7 +195,7 @@ function nodes.handlers.nodepass(head) end function nodes.handlers.basepass(head) - if not basepass then + if basepass then head = ligaturing(head) head = kerning(head) end diff --git a/src/fontloader/misc/fontloader-fonts-inj.lua b/src/fontloader/misc/fontloader-fonts-inj.lua index 332e920..36781f7 100644 --- a/src/fontloader/misc/fontloader-fonts-inj.lua +++ b/src/fontloader/misc/fontloader-fonts-inj.lua @@ -8,7 +8,16 @@ if not modules then modules = { } end modules ['font-inj'] = { -- This property based variant is not faster but looks nicer than the attribute one. We -- need to use rawget (which is apbout 4 times slower than a direct access but we cannot --- get/set that one for our purpose! +-- get/set that one for our purpose! This version does a bit more with discretionaries +-- (and Kai has tested it with his collection of weird fonts.) + +-- There is some duplicate code here (especially in the the pre/post/replace branches) but +-- we go for speed. We could store a list of glyph and mark nodes when registering but it's +-- cleaner to have an identification pass here. Also, I need to keep tracing in mind so +-- being too clever here is dangerous. + +-- The subtype test is not needed as there will be no (new) properties set, given that we +-- reset the properties. if not nodes.properties then return end @@ -80,8 +89,7 @@ function injections.resetcounts() keepregisteredcounts = false end --- We need to make sure that a possible metatable will not kick in --- unexpectedly. +-- We need to make sure that a possible metatable will not kick in unexpectedly. function injections.reset(n) local p = rawget(properties,n) @@ -146,7 +154,8 @@ end function injections.setcursive(start,nxt,factor,rlmode,exit,entry,tfmstart,tfmnext) -- hm: nuts or nodes local dx = factor*(exit[1]-entry[1]) local dy = -factor*(exit[2]-entry[2]) - local ws, wn = tfmstart.width, tfmnext.width + local ws = tfmstart.width + local wn = tfmnext.width nofregisteredcursives = nofregisteredcursives + 1 if rlmode < 0 then dx = -(dx + wn) @@ -195,7 +204,10 @@ function injections.setcursive(start,nxt,factor,rlmode,exit,entry,tfmstart,tfmne end function injections.setpair(current,factor,rlmode,r2lflag,spec,injection) -- r2lflag & tfmchr not used - local x, y, w, h = factor*spec[1], factor*spec[2], factor*spec[3], factor*spec[4] + local x = factor*spec[1] + local y = factor*spec[2] + local w = factor*spec[3] + local h = factor*spec[4] if x ~= 0 or w ~= 0 or y ~= 0 or h ~= 0 then -- okay? local yoffset = y - h local leftkern = x -- both kerns are set in a pair kern compared @@ -205,9 +217,12 @@ function injections.setpair(current,factor,rlmode,r2lflag,spec,injection) -- r2l if rlmode and rlmode < 0 then leftkern, rightkern = rightkern, leftkern end + if not injection then + injection = "injections" + end local p = rawget(properties,current) if p then - local i = rawget(p,"injections") + local i = rawget(p,injection) if i then if leftkern ~= 0 then i.leftkern = (i.leftkern or 0) + leftkern @@ -219,19 +234,19 @@ function injections.setpair(current,factor,rlmode,r2lflag,spec,injection) -- r2l i.yoffset = (i.yoffset or 0) + yoffset end elseif leftkern ~= 0 or rightkern ~= 0 then - p.injections = { + p[injection] = { leftkern = leftkern, rightkern = rightkern, yoffset = yoffset, } else - p.injections = { + p[injection] = { yoffset = yoffset, } end elseif leftkern ~= 0 or rightkern ~= 0 then properties[current] = { - injections = { + [injection] = { leftkern = leftkern, rightkern = rightkern, yoffset = yoffset, @@ -239,7 +254,7 @@ function injections.setpair(current,factor,rlmode,r2lflag,spec,injection) -- r2l } else properties[current] = { - injections = { + [injection] = { yoffset = yoffset, }, } @@ -250,10 +265,9 @@ function injections.setpair(current,factor,rlmode,r2lflag,spec,injection) -- r2l return x, y, w, h -- no bound end --- this needs checking for rl < 0 but it is unlikely that a r2l script --- uses kernclasses between glyphs so we're probably safe (KE has a --- problematic font where marks interfere with rl < 0 in the previous --- case) +-- This needs checking for rl < 0 but it is unlikely that a r2l script uses kernclasses between +-- glyphs so we're probably safe (KE has a problematic font where marks interfere with rl < 0 in +-- the previous case) function injections.setkern(current,factor,rlmode,x,injection) local dx = factor * x @@ -285,7 +299,7 @@ function injections.setkern(current,factor,rlmode,x,injection) end end -function injections.setmark(start,base,factor,rlmode,ba,ma,tfmbase) -- ba=baseanchor, ma=markanchor +function injections.setmark(start,base,factor,rlmode,ba,ma,tfmbase,mkmk) -- ba=baseanchor, ma=markanchor local dx, dy = factor*(ba[1]-ma[1]), factor*(ba[2]-ma[2]) nofregisteredmarks = nofregisteredmarks + 1 -- markanchors[nofregisteredmarks] = base @@ -293,14 +307,20 @@ function injections.setmark(start,base,factor,rlmode,ba,ma,tfmbase) -- ba=basean dx = tfmbase.width - dx -- see later commented ox end local p = rawget(properties,start) + -- hm, dejavu serif does a sloppy mark2mark before mark2base if p then local i = rawget(p,"injections") if i then - i.markx = dx - i.marky = dy - i.markdir = rlmode or 0 - i.markbase = nofregisteredmarks - i.markbasenode = base + if i.markmark then + -- out of order mkmk: yes or no or option + else + i.markx = dx + i.marky = dy + i.markdir = rlmode or 0 + i.markbase = nofregisteredmarks + i.markbasenode = base + i.markmark = mkmk + end else p.injections = { markx = dx, @@ -308,6 +328,7 @@ function injections.setmark(start,base,factor,rlmode,ba,ma,tfmbase) -- ba=basean markdir = rlmode or 0, markbase = nofregisteredmarks, markbasenode = base, + markmark = mkmk, } end else @@ -318,6 +339,7 @@ function injections.setmark(start,base,factor,rlmode,ba,ma,tfmbase) -- ba=basean markdir = rlmode or 0, markbase = nofregisteredmarks, markbasenode = base, + markmark = mkmk, }, } end @@ -430,30 +452,36 @@ local function show_result(head) end end --- we could also check for marks here but maybe not all are registered (needs checking) - -local function collect_glyphs_1(head) - local glyphs, nofglyphs = { }, 0 - local marks, nofmarks = { }, 0 +local function collect_glyphs(head,offsets) + local glyphs, glyphi, nofglyphs = { }, { }, 0 + local marks, marki, nofmarks = { }, { }, 0 local nf, tm = nil, nil - for n in traverse_id(glyph_code,head) do -- only needed for relevant fonts - if getsubtype(n) < 256 then - local f = getfont(n) - if f ~= nf then - nf = f - tm = fontdata[nf].resources.marks -- other hash in ctx - end - if tm and tm[getchar(n)] then - nofmarks = nofmarks + 1 - marks[nofmarks] = n - else - nofglyphs = nofglyphs + 1 - glyphs[nofglyphs] = n + local n = head + + local function identify(n,what) + local f = getfont(n) + if f ~= nf then + nf = f + -- other hash in ctx: + tm = fontdata[nf].resources + if tm then + tm = tm.marks end + end + if tm and tm[getchar(n)] then + nofmarks = nofmarks + 1 + marks[nofmarks] = n + marki[nofmarks] = "injections" + else + nofglyphs = nofglyphs + 1 + glyphs[nofglyphs] = n + glyphi[nofglyphs] = what + end + if offsets then -- yoffsets can influence curs steps local p = rawget(properties,n) if p then - local i = rawget(p,"injections") + local i = rawget(p,what) if i then local yoffset = i.yoffset if yoffset and yoffset ~= 0 then @@ -463,38 +491,50 @@ local function collect_glyphs_1(head) end end end - return glyphs, nofglyphs, marks, nofmarks -end -local function collect_glyphs_2(head) - local glyphs, nofglyphs = { }, 0 - local marks, nofmarks = { }, 0 - local nf, tm = nil, nil - for n in traverse_id(glyph_code,head) do - if getsubtype(n) < 256 then - local f = getfont(n) - if f ~= nf then - nf = f - tm = fontdata[nf].resources.marks -- other hash in ctx - end - if tm and tm[getchar(n)] then - nofmarks = nofmarks + 1 - marks[nofmarks] = n - else - nofglyphs = nofglyphs + 1 - glyphs[nofglyphs] = n - end + while n do -- only needed for relevant fonts + local id = getid(n) + if id == glyph_code then + identify(n,"injections") + elseif id == disc_code then + local d = getfield(n,"pre") + if d then + for n in traverse_id(glyph_code,d) do + if getsubtype(n) < 256 then + identify(n,"preinjections") + end + end + end + local d = getfield(n,"post") + if d then + for n in traverse_id(glyph_code,d) do + if getsubtype(n) < 256 then + identify(n,"postinjections") + end + end + end + local d = getfield(n,"replace") + if d then + for n in traverse_id(glyph_code,d) do + if getsubtype(n) < 256 then + identify(n,"replaceinjections") + end + end + end end + n = getnext(n) end - return glyphs, nofglyphs, marks, nofmarks + + return glyphs, glyphi, nofglyphs, marks, marki, nofmarks end -local function inject_marks(marks,nofmarks) +local function inject_marks(marks,marki,nofmarks) for i=1,nofmarks do - local n = marks[i] + local n = marks[i] local pn = rawget(properties,n) if pn then - pn = rawget(pn,"injections") + local ni = marki[i] + local pn = rawget(pn,ni) if pn then local p = pn.markbasenode if p then @@ -503,7 +543,7 @@ local function inject_marks(marks,nofmarks) local rightkern = nil local pp = rawget(properties,p) if pp then - pp = rawget(pp,"injections") + pp = rawget(pp,ni) if pp then rightkern = pp.rightkern end @@ -516,13 +556,22 @@ local function inject_marks(marks,nofmarks) else -- kern(x) glyph(p) kern(w-x) mark(n) -- ox = px - getfield(p,"width") + pn.markx - pp.leftkern - local leftkern = pp.leftkern - if leftkern then - ox = px - pn.markx + -- + -- According to Kai we don't need to handle leftkern here but I'm + -- pretty sure I've run into a case where it was needed so maybe + -- some day we need something more clever here. + -- + if false then + -- a mark with kerning + local leftkern = pp.leftkern + if leftkern then + ox = px - pn.markx - leftkern + else + ox = px - pn.markx + end else - ox = px - pn.markx - leftkern + ox = px - pn.markx end --- report_injections("l2r case 1: %p",ox) end else -- we need to deal with fonts that have marks with width @@ -548,12 +597,13 @@ local function inject_marks(marks,nofmarks) setfield(n,"xoffset",ox) -- local py = getfield(p,"yoffset") - local oy = 0 - if marks[p] then - oy = py + pn.marky - else - oy = getfield(n,"yoffset") + py + pn.marky - end +-- local oy = 0 +-- if marks[p] then +-- oy = py + pn.marky +-- else +-- oy = getfield(n,"yoffset") + py + pn.marky +-- end + local oy = getfield(n,"yoffset") + py + pn.marky setfield(n,"yoffset",oy) else -- normally this can't happen (only when in trace mode which is a special case anyway) @@ -564,14 +614,14 @@ local function inject_marks(marks,nofmarks) end end -local function inject_cursives(glyphs,nofglyphs) +local function inject_cursives(glyphs,glyphi,nofglyphs) local cursiveanchor, lastanchor = nil, nil local minc, maxc, last = 0, 0, nil for i=1,nofglyphs do - local n = glyphs[i] + local n = glyphs[i] local pn = rawget(properties,n) if pn then - pn = rawget(pn,"injections") + pn = rawget(pn,glyphi[i]) end if pn then local cursivex = pn.cursivex @@ -630,7 +680,7 @@ local function inject_cursives(glyphs,nofglyphs) -- if maxc > 0 and not cursiveanchor then -- local ny = getfield(n,"yoffset") -- for i=maxc,minc,-1 do - -- local ti = glyphs[i] + -- local ti = glyphs[i][1] -- ny = ny + properties[ti].cursivedy -- setfield(ti,"yoffset",ny) -- why not add ? -- end @@ -647,23 +697,67 @@ local function inject_cursives(glyphs,nofglyphs) end end -local function inject_kerns(head,list,length) - -- todo: pre/post/replace +-- G +D-pre G +-- D-post+ +-- +D-replace+ +-- +-- G +D-pre +D-pre +-- D-post +D-post +-- +D-replace +D-replace + +local function inject_kerns(head,glist,ilist,length) -- not complete ! compare with inject_kerns_only (but unlikely disc here) for i=1,length do - local n = list[i] + local n = glist[i] local pn = rawget(properties,n) if pn then - local i = rawget(pn,"injections") - if i then - local leftkern = i.leftkern - if leftkern and leftkern ~= 0 then - insert_node_before(head,n,newkern(leftkern)) -- type 0/2 - end - local rightkern = i.rightkern - if rightkern and rightkern ~= 0 then - insert_node_after(head,n,newkern(rightkern)) -- type 0/2 - end - end + local dp = nil + local dr = nil + local ni = ilist[i] + local p = nil + if ni == "injections" then + p = getprev(n) + if p then + local id = getid(p) + if id == disc_code then + dp = getfield(p,"post") + dr = getfield(p,"replace") + end + end + end + if dp then + local i = rawget(pn,"postinjections") + if i then + local leftkern = i.leftkern + if leftkern and leftkern ~= 0 then + local t = find_tail(dp) + insert_node_after(dp,t,newkern(leftkern)) + setfield(p,"post",dp) -- currently we need to force a tail refresh + end + end + end + if dr then + local i = rawget(pn,"replaceinjections") + if i then + local leftkern = i.leftkern + if leftkern and leftkern ~= 0 then + local t = find_tail(dr) + insert_node_after(dr,t,newkern(leftkern)) + setfield(p,"replace",dr) -- currently we need to force a tail refresh + end + end + else + local i = rawget(pn,ni) + if i then + local leftkern = i.leftkern + if leftkern and leftkern ~= 0 then + insert_node_before(head,n,newkern(leftkern)) -- type 0/2 + end + local rightkern = i.rightkern + if rightkern and rightkern ~= 0 then + insert_node_after(head,n,newkern(rightkern)) -- type 0/2 + end + end + end end end end @@ -673,23 +767,18 @@ local function inject_everything(head,where) if trace_injections then trace(head,"everything") end - local glyphs, nofglyphs, marks, nofmarks - if nofregisteredpairs > 0 then - glyphs, nofglyphs, marks, nofmarks = collect_glyphs_1(head) - else - glyphs, nofglyphs, marks, nofmarks = collect_glyphs_2(head) - end + local glyphs, glyphi, nofglyphs, marks, marki, nofmarks = collect_glyphs(head,nofregisteredpairs > 0) if nofglyphs > 0 then if nofregisteredcursives > 0 then - inject_cursives(glyphs,nofglyphs) + inject_cursives(glyphs,glyphi,nofglyphs) end if nofregisteredmarks > 0 then -- and nofmarks > 0 - inject_marks(marks,nofmarks) + inject_marks(marks,marki,nofmarks) end - inject_kerns(head,glyphs,nofglyphs) + inject_kerns(head,glyphs,glyphi,nofglyphs) end if nofmarks > 0 then - inject_kerns(head,marks,nofmarks) + inject_kerns(head,marks,marki,nofmarks) end if keepregisteredcounts then keepregisteredcounts = false @@ -702,13 +791,21 @@ local function inject_everything(head,where) return tonode(head), true end +-- G +D-pre G +-- D-post+ +-- +D-replace+ +-- +-- G +D-pre +D-pre +-- D-post +D-post +-- +D-replace +D-replace + local function inject_kerns_only(head,where) head = tonut(head) if trace_injections then trace(head,"kerns") end local n = head - local p = nil + local p = nil -- disc node when non-nil while n do local id = getid(n) if id == glyph_code then @@ -724,6 +821,7 @@ local function inject_kerns_only(head,where) if leftkern and leftkern ~= 0 then local t = find_tail(d) insert_node_after(d,t,newkern(leftkern)) + setfield(p,"post",d) -- currently we need to force a tail refresh end end end @@ -735,6 +833,7 @@ local function inject_kerns_only(head,where) if leftkern and leftkern ~= 0 then local t = find_tail(d) insert_node_after(d,t,newkern(leftkern)) + setfield(p,"replace",d) -- currently we need to force a tail refresh end end else @@ -747,6 +846,7 @@ local function inject_kerns_only(head,where) end end else + -- this is the most common case local i = rawget(pn,"injections") if i then local leftkern = i.leftkern @@ -756,8 +856,6 @@ local function inject_kerns_only(head,where) end end end - else - break end p = nil elseif id == disc_code then @@ -812,7 +910,7 @@ local function inject_kerns_only(head,where) local h = d for n in traverse_id(glyph_code,d) do if getsubtype(n) < 256 then - local pn = rawget(properties,n) -- why can it be empty { } + local pn = rawget(properties,n) if pn then local i = rawget(pn,"replaceinjections") if i then @@ -850,9 +948,8 @@ local function inject_pairs_only(head,where) if trace_injections then trace(head,"pairs") end - -- local n = head - local p = nil + local p = nil -- disc node when non-nil while n do local id = getid(n) if id == glyph_code then @@ -868,6 +965,7 @@ local function inject_pairs_only(head,where) if leftkern and leftkern ~= 0 then local t = find_tail(d) insert_node_after(d,t,newkern(leftkern)) + setfield(p,"post",d) -- currently we need to force a tail refresh end -- local rightkern = i.rightkern -- if rightkern and rightkern ~= 0 then @@ -884,6 +982,7 @@ local function inject_pairs_only(head,where) if leftkern and leftkern ~= 0 then local t = find_tail(d) insert_node_after(d,t,newkern(leftkern)) + setfield(p,"replace",d) -- currently we need to force a tail refresh end -- local rightkern = i.rightkern -- if rightkern and rightkern ~= 0 then @@ -909,24 +1008,22 @@ local function inject_pairs_only(head,where) -- this is the most common case local i = rawget(pn,"injections") if i then - local yoffset = i.yoffset - if yoffset and yoffset ~= 0 then - setfield(n,"yoffset",yoffset) - end local leftkern = i.leftkern if leftkern and leftkern ~= 0 then - insert_node_before(head,n,newkern(leftkern)) + head = insert_node_before(head,n,newkern(leftkern)) end local rightkern = i.rightkern if rightkern and rightkern ~= 0 then insert_node_after(head,n,newkern(rightkern)) n = getnext(n) -- to be checked end + local yoffset = i.yoffset + if yoffset and yoffset ~= 0 then + setfield(n,"yoffset",yoffset) + end end end end - else - break end p = nil elseif id == disc_code then @@ -935,16 +1032,12 @@ local function inject_pairs_only(head,where) local h = d for n in traverse_id(glyph_code,d) do if getsubtype(n) < 256 then - local p = rawget(properties,n) - if p then - local i = rawget(p,"preinjections") + local pn = rawget(properties,n) + if pn then + local i = rawget(pn,"preinjections") if i then - local yoffset = i.yoffset - if yoffset and yoffset ~= 0 then - setfield(n,"yoffset",yoffset) - end local leftkern = i.leftkern - if leftkern ~= 0 then + if leftkern and leftkern ~= 0 then h = insert_node_before(h,n,newkern(leftkern)) end local rightkern = i.rightkern @@ -952,6 +1045,10 @@ local function inject_pairs_only(head,where) insert_node_after(head,n,newkern(rightkern)) n = getnext(n) -- to be checked end + local yoffset = i.yoffset + if yoffset and yoffset ~= 0 then + setfield(n,"yoffset",yoffset) + end end end else @@ -967,14 +1064,10 @@ local function inject_pairs_only(head,where) local h = d for n in traverse_id(glyph_code,d) do if getsubtype(n) < 256 then - local p = rawget(properties,n) - if p then - local i = rawget(p,"postinjections") + local pn = rawget(properties,n) + if pn then + local i = rawget(pn,"postinjections") if i then - local yoffset = i.yoffset - if yoffset and yoffset ~= 0 then - setfield(n,"yoffset",yoffset) - end local leftkern = i.leftkern if leftkern and leftkern ~= 0 then h = insert_node_before(h,n,newkern(leftkern)) @@ -984,6 +1077,10 @@ local function inject_pairs_only(head,where) insert_node_after(head,n,newkern(rightkern)) n = getnext(n) -- to be checked end + local yoffset = i.yoffset + if yoffset and yoffset ~= 0 then + setfield(n,"yoffset",yoffset) + end end end else @@ -999,14 +1096,10 @@ local function inject_pairs_only(head,where) local h = d for n in traverse_id(glyph_code,d) do if getsubtype(n) < 256 then - local p = rawget(properties,n) - if p then - local i = rawget(p,"replaceinjections") + local pn = rawget(properties,n) + if pn then + local i = rawget(pn,"replaceinjections") if i then - local yoffset = i.yoffset - if yoffset and yoffset ~= 0 then - setfield(n,"yoffset",yoffset) - end local leftkern = i.leftkern if leftkern and leftkern ~= 0 then h = insert_node_before(h,n,newkern(leftkern)) @@ -1016,6 +1109,10 @@ local function inject_pairs_only(head,where) insert_node_after(head,n,newkern(rightkern)) n = getnext(n) -- to be checked end + local yoffset = i.yoffset + if yoffset and yoffset ~= 0 then + setfield(n,"yoffset",yoffset) + end end end else @@ -1042,7 +1139,7 @@ local function inject_pairs_only(head,where) return tonode(head), true end -function injections.handler(head,where) -- optimize for n=1 ? +function injections.handler(head,where) if nofregisteredmarks > 0 or nofregisteredcursives > 0 then return inject_everything(head,where) elseif nofregisteredpairs > 0 then diff --git a/src/fontloader/misc/fontloader-fonts-otn.lua b/src/fontloader/misc/fontloader-fonts-otn.lua index dd3aa61..1b99c56 100644 --- a/src/fontloader/misc/fontloader-fonts-otn.lua +++ b/src/fontloader/misc/fontloader-fonts-otn.lua @@ -6,12 +6,16 @@ if not modules then modules = { } end modules ['font-otn'] = { license = "see context related readme files", } --- todo: looks like we have a leak somewhere (probably in ligatures) --- todo: copy attributes to disc - -- 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) @@ -40,7 +44,18 @@ if not modules then modules = { } end modules ['font-otn'] = { -- 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) -- --- maybe redo the lot some way (more context specific) +-- 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-- <p>This module is a bit more split up that I'd like but since we also want to test @@ -65,9 +80,12 @@ 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 <l n='context'/> users ask for it.</p> -<p>Because there are different interpretations possible, I will extend the code -with more (configureable) variants. I can also add hooks for users so that they can -write their own extensions.</p> +<p>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.</p> <p>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 @@ -94,12 +112,12 @@ when there's a fix in the <l n='fontforge'/> library or <l n='lua'/> code that results in different tables.</p> --ldx]]-- --- action handler chainproc chainmore comment +-- action handler chainproc -- --- gsub_single ok ok ok --- gsub_multiple ok ok not implemented yet --- gsub_alternate ok ok not implemented yet --- gsub_ligature ok ok ok +-- gsub_single ok ok +-- gsub_multiple ok ok +-- gsub_alternate ok ok +-- gsub_ligature ok ok -- gsub_context ok -- -- gsub_contextchain ok -- -- gsub_reversecontextchain ok -- @@ -123,7 +141,6 @@ results in different tables.</p> -- chainmore : multiple substitutions triggered by contextual lookup (e.g. fij -> f + ij) -- -- remark: the 'not implemented yet' variants will be done when we have fonts that use them --- remark: we need to check what to do with discretionaries -- We used to have independent hashes for lookups but as the tags are unique -- we now use only one hash. If needed we can have multiple again but in that @@ -131,16 +148,14 @@ results in different tables.</p> -- Todo: make plugin feature that operates on char/glyphnode arrays -local concat, insert, remove = table.concat, table.insert, table.remove -local gmatch, gsub, find, match, lower, strip = string.gmatch, string.gsub, string.find, string.match, string.lower, string.strip -local type, next, tonumber, tostring = type, next, tonumber, tostring -local lpegmatch = lpeg.match +local type, next, tonumber = type, next, tonumber local random = math.random local formatters = string.formatters local logs, trackers, nodes, attributes = logs, trackers, nodes, attributes -local registertracker = trackers.register +local registertracker = trackers.register +local registerdirective = directives.register local fonts = fonts local otf = fonts.handlers.otf @@ -162,6 +177,16 @@ local trace_steps = false registertracker("otf.steps", function(v 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_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 quit_on_no_replacement = true -- maybe per font +local zwnjruns = true + +registerdirective("otf.zwnjruns", function(v) zwnjruns = v end) +registerdirective("otf.chain.quitonnoreplacement",function(value) quit_on_no_replacement = value end) + local report_direct = logs.reporter("fonts","otf direct") local report_subchain = logs.reporter("fonts","otf subchain") local report_chain = logs.reporter("fonts","otf chain") @@ -230,11 +255,7 @@ local math_code = nodecodes.math local dir_code = whatcodes.dir local localpar_code = whatcodes.localpar - local discretionary_code = disccodes.discretionary -local regular_code = disccodes.regular -local automatic_code = disccodes.automatic - local ligature_code = glyphcodes.ligature local privateattribute = attributes.private @@ -286,6 +307,15 @@ local handlers = { } local rlmode = 0 local featurevalue = false +local sweephead = { } +local sweepnode = nil +local sweepprev = nil +local sweepnext = nil + +local notmatchpre = { } +local notmatchpost = { } +local notmatchreplace = { } + -- head is always a whatsit so we can safely assume that head is not changed -- we use this for special testing and documentation @@ -376,8 +406,66 @@ local function copy_glyph(g) -- next and prev are untouched ! end end --- +local function flattendisk(head,disc) + local replace = getfield(disc,"replace") + setfield(disc,"replace",nil) + free_node(disc) + if head == disc then + local next = getnext(disc) + if replace then + if next then + local tail = find_node_tail(replace) + setfield(tail,"next",next) + setfield(next,"prev",tail) + end + return replace, replace + elseif next then + return next, next + else + return -- maybe warning + end + else + local next = getnext(disc) + local prev = getprev(disc) + if replace then + local tail = find_node_tail(replace) + if next then + setfield(tail,"next",next) + setfield(next,"prev",tail) + end + setfield(prev,"next",replace) + setfield(replace,"prev",prev) + return head, replace + else + if next then + setfield(next,"prev",prev) + end + setfield(prev,"next",next) + return head, next + end + end +end +local function appenddisc(disc,list) + local post = getfield(disc,"post") + local replace = getfield(disc,"replace") + local phead = list + local rhead = copy_node_list(list) + local ptail = find_node_tail(post) + local rtail = find_node_tail(replace) + if post then + setfield(ptail,"next",phead) + setfield(phead,"prev",ptail) + else + setfield(disc,"post",phead) + end + if replace then + setfield(rtail,"next",rhead) + setfield(rhead,"prev",rtail) + else + setfield(disc,"replace",rhead) + end +end -- start is a mark and we need to keep that one @@ -416,8 +504,8 @@ end -- iteration this becomes a KAF-LAM-ALEF with a SHADDA on the second and a FATHA on the -- third component. -local function getcomponentindex(start) - if getid(start) ~= glyph_code then +local function getcomponentindex(start) -- we could store this offset in the glyph (nofcomponents) + if getid(start) ~= glyph_code then -- and then get rid of all components return 0 elseif getsubtype(start) == ligature_code then local i = 0 @@ -434,16 +522,28 @@ local function getcomponentindex(start) end end --- eventually we will do positioning in an other way (needs addional w/h/d fields) +local a_noligature = attributes.private("noligature") local function toligature(kind,lookupname,head,start,stop,char,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) setfield(start,"char",char) return head, start end + -- needs testing (side effects): + local components = getfield(start,"components") + if components then + -- we get a double free .. needs checking + -- flush_node_list(components) + end + -- local prev = getprev(start) local next = getnext(stop) + local comp = start setfield(start,"prev",nil) setfield(stop,"next",nil) local base = copy_glyph(start) @@ -453,15 +553,15 @@ local function toligature(kind,lookupname,head,start,stop,char,markflag,discfoun resetinjection(base) setfield(base,"char",char) setfield(base,"subtype",ligature_code) - setfield(base,"components",start) -- start can have components + setfield(base,"components",comp) -- start can have components ... do we need to flush? if prev then setfield(prev,"next",base) end if next then setfield(next,"prev",base) end - setfield(base,"next",next) setfield(base,"prev",prev) + setfield(base,"next",next) if not discfound then local deletemarks = markflag ~= "mark" local components = start @@ -480,7 +580,9 @@ local function toligature(kind,lookupname,head,start,stop,char,markflag,discfoun if trace_marks then logwarning("%s: keep mark %s, gets index %s",pref(kind,lookupname),gref(char),getligaindex(start)) end - head, current = insert_node_after(head,current,copy_node(start)) -- unlikely that mark has components + 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(kind,lookupname),gref(char)) end @@ -501,17 +603,85 @@ local function toligature(kind,lookupname,head,start,stop,char,markflag,discfoun end start = getnext(start) end + else + -- discfound ... forget about marks .. probably no scripts that hyphenate and have marks + local discprev = getfield(discfound,"prev") + local discnext = getfield(discfound,"next") + 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 = getfield(discfound,"pre") + local post = getfield(discfound,"post") + local replace = getfield(discfound,"replace") + if not replace then -- todo: signal simple hyphen + local prev = getfield(base,"prev") + local copied = copy_node_list(comp) + setfield(discnext,"prev",nil) -- also blocks funny assignments + setfield(discprev,"next",nil) -- also blocks funny assignments + if pre then + setfield(discprev,"next",pre) + setfield(pre,"prev",discprev) + end + pre = comp + if post then + local tail = find_node_tail(post) + setfield(tail,"next",discnext) + setfield(discnext,"prev",tail) + setfield(post,"prev",nil) + else + post = discnext + end + setfield(prev,"next",discfound) + setfield(discfound,"prev",prev) + setfield(discfound,"next",next) + setfield(next,"prev",discfound) + setfield(base,"next",nil) + setfield(base,"prev",nil) + setfield(base,"components",copied) + setfield(discfound,"pre",pre) + setfield(discfound,"post",post) + setfield(discfound,"replace",base) + setfield(discfound,"subtype",discretionary_code) + base = prev -- restart + end + end end return head, base end -function handlers.gsub_single(head,start,kind,lookupname,replacement) - if trace_singles then - logprocess("%s: replacing %s by single %s",pref(kind,lookupname),gref(getchar(start)),gref(replacement)) +local function multiple_glyphs(head,start,multiple,ignoremarks) + local nofmultiples = #multiple + if nofmultiples > 0 then + resetinjection(start) + setfield(start,"char",multiple[1]) + if nofmultiples > 1 then + local sn = getnext(start) + for k=2,nofmultiples do -- todo: use insert_node +-- untested: +-- +-- while ignoremarks and marks[getchar(sn)] then +-- local sn = getnext(sn) +-- end + local n = copy_node(start) -- ignore components + resetinjection(n) + setfield(n,"char",multiple[k]) + setfield(n,"prev",start) + setfield(n,"next",sn) + if sn then + setfield(sn,"prev",n) + end + setfield(start,"next",n) + start = n + end + end + return head, start, true + else + if trace_multiples then + logprocess("no multiple for %s",gref(getchar(start))) + end + return head, start, false end - resetinjection(start) - setfield(start,"char",replacement) - return head, start, true end local function get_alternative_glyph(start,alternatives,value,trace_alternatives) @@ -546,38 +716,15 @@ local function get_alternative_glyph(start,alternatives,value,trace_alternatives end end -local function multiple_glyphs(head,start,multiple,ignoremarks) - local nofmultiples = #multiple - if nofmultiples > 0 then - resetinjection(start) - setfield(start,"char",multiple[1]) - if nofmultiples > 1 then - local sn = getnext(start) - for k=2,nofmultiples do -- todo: use insert_node --- untested: --- --- while ignoremarks and marks[getchar(sn)] then --- local sn = getnext(sn) --- end - local n = copy_node(start) -- ignore components - resetinjection(n) - setfield(n,"char",multiple[k]) - setfield(n,"next",sn) - setfield(n,"prev",start) - if sn then - setfield(sn,"prev",n) - end - setfield(start,"next",n) - start = n - end - end - return head, start, true - else - if trace_multiples then - logprocess("no multiple for %s",gref(getchar(start))) - end - return head, start, false +-- handlers + +function handlers.gsub_single(head,start,kind,lookupname,replacement) + if trace_singles then + logprocess("%s: replacing %s by single %s",pref(kind,lookupname),gref(getchar(start)),gref(replacement)) end + resetinjection(start) + setfield(start,"char",replacement) + return head, start, true end function handlers.gsub_alternate(head,start,kind,lookupname,alternative,sequence) @@ -605,7 +752,7 @@ function handlers.gsub_multiple(head,start,kind,lookupname,multiple,sequence) end function handlers.gsub_ligature(head,start,kind,lookupname,ligature,sequence) - local s, stop, discfound = getnext(start), nil, false + local s, stop = getnext(start), nil local startchar = getchar(start) if marks[startchar] then while s do @@ -633,24 +780,30 @@ function handlers.gsub_ligature(head,start,kind,lookupname,ligature,sequence) else head, start = markstoligature(kind,lookupname,head,start,stop,lig) end - return head, start, true + return head, start, true, false else -- ok, goto next lookup end end else - local skipmark = sequence.flags[1] + local skipmark = sequence.flags[1] + local discfound = false + local lastdisc = nil while s do local id = getid(s) - if id == glyph_code and getsubtype(s)<256 then - if getfont(s) == currentfont then + if id == glyph_code and getsubtype(s)<256 then -- not needed + if getfont(s) == currentfont then -- also not needed only when mark local char = getchar(s) if skipmark and marks[char] then s = getnext(s) - else - local lg = ligature[char] + else -- ligature is a tree + local lg = ligature[char] -- can there be multiple in a row? maybe in a bad font if lg then - stop = s + if not discfound and lastdisc then + discfound = lastdisc + lastdisc = nil + end + stop = s -- needed for fake so outside then ligature = lg s = getnext(s) else @@ -661,13 +814,13 @@ function handlers.gsub_ligature(head,start,kind,lookupname,ligature,sequence) break end elseif id == disc_code then - discfound = true + lastdisc = s s = getnext(s) else break end end - local lig = ligature.ligature + local lig = ligature.ligature -- can't we get rid of this .ligature? if lig then if stop then if trace_ligatures then @@ -685,14 +838,88 @@ function handlers.gsub_ligature(head,start,kind,lookupname,ligature,sequence) logprocess("%s: replacing %s by (no real) ligature %s case 3",pref(kind,lookupname),gref(startchar),gref(lig)) end end - return head, start, true + return head, start, true, discfound else - -- weird but happens + -- weird but happens, pseudo ligatures ... just the components end end + return head, start, false, discfound +end + +function handlers.gpos_single(head,start,kind,lookupname,kerns,sequence,injection) + local startchar = getchar(start) + local dx, dy, w, h = setpair(start,tfmdata.parameters.factor,rlmode,sequence.flags[4],kerns,injection) -- ,characters[startchar]) + if trace_kerns then + logprocess("%s: shifting single %s by (%p,%p) and correction (%p,%p)",pref(kind,lookupname),gref(startchar),dx,dy,w,h) + end return head, start, false end +function handlers.gpos_pair(head,start,kind,lookupname,kerns,sequence,lookuphash,i,injection) + -- todo: kerns in disc nodes: pre, post, replace -> loop over disc too + -- todo: kerns in components of ligatures + local snext = getnext(start) + if not snext then + return head, start, false + else + local prev = start + local done = false + local factor = tfmdata.parameters.factor + local lookuptype = lookuptypes[lookupname] + 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 -- probably not needed + local a, b = krn[2], krn[3] + if a and #a > 0 then + local x, y, w, h = setpair(start,factor,rlmode,sequence.flags[4],a,injection) -- characters[startchar]) + if trace_kerns then + local startchar = getchar(start) + logprocess("%s: shifting first of pair %s and %s by (%p,%p) and correction (%p,%p)",pref(kind,lookupname),gref(startchar),gref(nextchar),x,y,w,h) + end + end + if b and #b > 0 then + local x, y, w, h = setpair(snext,factor,rlmode,sequence.flags[4],b,injection) -- characters[nextchar]) + if trace_kerns then + local startchar = getchar(start) + logprocess("%s: shifting second of pair %s and %s by (%p,%p) and correction (%p,%p)",pref(kind,lookupname),gref(startchar),gref(nextchar),x,y,w,h) + end + end + else -- wrong ... position has different entries + report_process("%s: check this out (old kern stuff)",pref(kind,lookupname)) + -- local a, b = krn[2], krn[6] + -- if a and a ~= 0 then + -- local k = setkern(snext,factor,rlmode,a) + -- if trace_kerns then + -- logprocess("%s: inserting first kern %s between %s and %s",pref(kind,lookupname),k,gref(getchar(prev)),gref(nextchar)) + -- end + -- end + -- if b and b ~= 0 then + -- logwarning("%s: ignoring second kern xoff %s",pref(kind,lookupname),b*factor) + -- end + end + done = true + elseif krn ~= 0 then + local k = setkern(snext,factor,rlmode,krn,injection) + if trace_kerns then + logprocess("%s: inserting kern %s between %s and %s",pref(kind,lookupname),k,gref(getchar(prev)),gref(nextchar)) -- prev? + end + done = true + end + break + end + end + return head, start, done + end +end + --[[ldx-- <p>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.</p> @@ -855,7 +1082,7 @@ function handlers.gpos_mark2mark(head,start,kind,lookupname,markanchors,sequence 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]) + 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) @@ -938,85 +1165,11 @@ function handlers.gpos_cursive(head,start,kind,lookupname,exitanchors,sequence) end end -function handlers.gpos_single(head,start,kind,lookupname,kerns,sequence) - local startchar = getchar(start) - 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)",pref(kind,lookupname),gref(startchar),dx,dy,w,h) - end - return head, start, false -end - -function handlers.gpos_pair(head,start,kind,lookupname,kerns,sequence) - -- todo: kerns in disc nodes: pre, post, replace -> loop over disc too - -- todo: kerns in components of ligatures - local snext = getnext(start) - if not snext then - return head, start, false - else - local prev, done = start, false - local factor = tfmdata.parameters.factor - local lookuptype = lookuptypes[lookupname] - 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 -- probably not needed - 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)",pref(kind,lookupname),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)",pref(kind,lookupname),gref(startchar),gref(nextchar),x,y,w,h) - end - end - else -- wrong ... position has different entries - report_process("%s: check this out (old kern stuff)",pref(kind,lookupname)) - -- local a, b = krn[2], krn[6] - -- if a and a ~= 0 then - -- local k = setkern(snext,factor,rlmode,a) - -- if trace_kerns then - -- logprocess("%s: inserting first kern %s between %s and %s",pref(kind,lookupname),k,gref(getchar(prev)),gref(nextchar)) - -- end - -- end - -- if b and b ~= 0 then - -- logwarning("%s: ignoring second kern xoff %s",pref(kind,lookupname),b*factor) - -- end - end - done = true - elseif krn ~= 0 then - local k = setkern(snext,factor,rlmode,krn) - if trace_kerns then - logprocess("%s: inserting kern %s between %s and %s",pref(kind,lookupname),k,gref(getchar(prev)),gref(nextchar)) - end - done = true - end - break - end - end - return head, start, done - end -end - --[[ldx-- <p>I will implement multiple chain replacements once I run into a font that uses it. It's not that complex to handle.</p> --ldx]]-- -local chainmores = { } local chainprocs = { } local function logprocess(...) @@ -1045,11 +1198,6 @@ function chainprocs.chainsub(head,start,stop,kind,chainname,currentcontext,looku return head, start, false end -function chainmores.chainsub(head,start,stop,kind,chainname,currentcontext,lookuphash,lookuplist,chainlookupname,n) - logprocess("%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. @@ -1116,8 +1264,7 @@ as less as needed but that would also make the code even more messy.</p> -- end --[[ldx-- -<p>Here we replace start by a single variant, First we delete the rest of the -match.</p> +<p>Here we replace start by a single variant.</p> --ldx]]-- function chainprocs.gsub_single(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex) @@ -1125,7 +1272,7 @@ function chainprocs.gsub_single(head,start,stop,kind,chainname,currentcontext,lo local current = start local subtables = currentlookup.subtables if #subtables > 1 then - logwarning("todo: check if we need to loop over the replacements: %s",concat(subtables," ")) + logwarning("todo: check if we need to loop over the replacements: % t",subtables) end while current do if getid(current) == glyph_code then @@ -1160,11 +1307,8 @@ function chainprocs.gsub_single(head,start,stop,kind,chainname,currentcontext,lo return head, start, false end -chainmores.gsub_single = chainprocs.gsub_single - --[[ldx-- -<p>Here we replace start by a sequence of new glyphs. First we delete the rest of -the match.</p> +<p>Here we replace start by a sequence of new glyphs.</p> --ldx]]-- function chainprocs.gsub_multiple(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) @@ -1193,8 +1337,6 @@ function chainprocs.gsub_multiple(head,start,stop,kind,chainname,currentcontext, return head, start, false end -chainmores.gsub_multiple = chainprocs.gsub_multiple - --[[ldx-- <p>Here we replace start by new glyph. First we delete the rest of the match.</p> --ldx]]-- @@ -1249,8 +1391,6 @@ function chainprocs.gsub_alternate(head,start,stop,kind,chainname,currentcontext return head, start, false end -chainmores.gsub_alternate = chainprocs.gsub_alternate - --[[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 @@ -1276,13 +1416,19 @@ function chainprocs.gsub_ligature(head,start,stop,kind,chainname,currentcontext, local s = getnext(start) local discfound = false local last = stop - local nofreplacements = 0 + local nofreplacements = 1 local skipmark = currentlookup.flags[1] while s do local id = getid(s) if id == disc_code then - s = getnext(s) - discfound = true + 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 @@ -1315,7 +1461,7 @@ function chainprocs.gsub_ligature(head,start,stop,kind,chainname,currentcontext, end end head, start = toligature(kind,lookupname,head,start,stop,l2,currentlookup.flags[1],discfound) - return head, start, true, nofreplacements + 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)) @@ -1325,10 +1471,96 @@ function chainprocs.gsub_ligature(head,start,stop,kind,chainname,currentcontext, end end end - return head, start, false, 0 + return head, start, false, 0, false end -chainmores.gsub_ligature = chainprocs.gsub_ligature +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) @@ -1497,7 +1729,7 @@ function chainprocs.gpos_mark2mark(head,start,stop,kind,chainname,currentcontext 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]) + 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) @@ -1588,133 +1820,346 @@ function chainprocs.gpos_cursive(head,start,stop,kind,chainname,currentcontext,l return head, start, 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) +-- 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 - return head, start, false -end -chainmores.gpos_single = chainprocs.gpos_single -- okay? + 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 --- when machines become faster i will make a shared function + 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 -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 + 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 - break + replace = getnext(replace) end + elseif notmatchpost[current] ~= notmatchreplace[current] then + head, current = flattendisk(head,current) end - return head, start, done + end + current = getprev(current) + if t and current == checkdisk then + current = getprev(sweepnode) end end end - return head, start, false -end -chainmores.gpos_pair = chainprocs.gpos_pair -- okay? + local ok = false + if lookaheaddisc then --- what pointer to return, spec says stop --- to be discussed ... is bidi changer a space? --- elseif char == zwnj and sequence[n][32] then -- brrr + local cf = start + local cl = getprev(lookaheaddisc) + local cprev = getprev(start) + local insertedmarks = 0 --- 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 + 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) -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 -local quit_on_no_replacement = true + head, start, ok = chainproc(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence) -directives.register("otf.chain.quitonnoreplacement",function(value) -- maybe per font - quit_on_no_replacement = value -end) + end + + return head, start, ok +end local function normal_handle_contextchain(head,start,kind,chainname,contexts,sequence,lookuphash) - -- local rule, lookuptype, sequence, f, l, lookups = ck[1], ck[2] ,ck[3], ck[4], ck[5], ck[6] + local 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 someskip = skipmark or skipligature or skipbase -- could be stored in flags for a fast test (hm, flags could be false !) - local markclass = sequence.markclass -- todo, first we need a proper test + local markclass = sequence.markclass local skipped = false - for k=1,#contexts do + + for k=1,#contexts do -- i've only seen ccmp having > 1 (e.g. dejavu) local match = true local current = start local last = start @@ -1728,7 +2173,8 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq 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, l = ck[4], ck[5] + local f = ck[4] + local l = ck[5] -- current match if f == 1 and f == l then -- current only -- already a hit @@ -1738,9 +2184,14 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq 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 @@ -1748,7 +2199,7 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq local char = getchar(last) local ccd = descriptions[char] if ccd then - local class = ccd.class + 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 @@ -1761,18 +2212,77 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq end n = n + 1 else - match = false + if discfound then + notmatchreplace[discfound] = true + match = not notmatchpre[discfound] + else + match = false + end break end else - match = false + if discfound then + notmatchreplace[discfound] = true + match = not notmatchpre[discfound] + else + match = false + end break end else - match = false + 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 @@ -1789,50 +2299,137 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq if match and f > 1 then local prev = getprev(start) if prev then - 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) + 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 - elseif seq[n][char] then - n = n -1 else - match = false + if discfound then + notmatchreplace[discfound] = true + match = not notmatchpost[discfound] + else + match = false + end break end else - match = false + 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 - elseif id == disc_code then - -- skip 'm - elseif seq[n][32] then - n = n -1 + 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 - 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 + else + match = false end else match = false @@ -1841,7 +2438,14 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq -- 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 @@ -1861,19 +2465,81 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq elseif seq[n][char] then n = n + 1 else - match = false + if discfound then + notmatchreplace[discfound] = true + match = not notmatchpre[discfound] + else + match = false + end break end else - match = false + if discfound then + notmatchreplace[discfound] = true + match = not notmatchpre[discfound] + else + match = false + end break end else - match = false + if discfound then + notmatchreplace[discfound] = true + match = not notmatchpre[discfound] + else + match = false + end break end elseif id == disc_code then - -- skip 'm + 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 @@ -1894,7 +2560,8 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq end end if match then - -- ck == currentcontext + -- 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) @@ -1914,10 +2581,14 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq local chainlookupname = chainlookups[1] local chainlookup = lookuptable[chainlookupname] if chainlookup then - local cp = chainprocs[chainlookup.type] - if cp then + local chainproc = chainprocs[chainlookup.type] + if chainproc then local ok - head, start, ok = cp(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence) + 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 @@ -1929,13 +2600,13 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq end else local i = 1 - while true do + while start and true do if skipped then - while true do + while true do -- todo: use properties local char = getchar(start) local ccd = descriptions[char] if ccd then - local class = ccd.class + 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 @@ -1946,36 +2617,51 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq 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 - -- okay, n matches, < n replacements + -- we just advance i = i + 1 else - local cp = chainmores[chainlookup.type] - if not cp then + 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 - head, start, ok, n = cp(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,i,sequence) + 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 - -- skip next one(s) if ligature - i = i + (n or 1) - else - i = i + 1 + 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 then + if i > nofchainlookups or not start then break elseif start then start = getnext(start) - else - -- weird end end end @@ -1990,8 +2676,16 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq 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 @@ -2076,13 +2770,13 @@ local function initialize(sequence,script,language,enabled) local order = sequence.order if order then for i=1,#order do -- - local kind = order[i] -- + 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.chain or 0, kind, sequence } + return { valid, autofeatures[kind] or false, sequence, kind } end end end @@ -2126,32 +2820,216 @@ function otf.dataset(tfmdata,font) -- generic variant, overloaded in context return rl end --- elseif id == glue_code then --- if p[5] then -- chain --- local pc = pp[32] --- if pc then --- start, ok = start, false -- p[1](start,kind,p[2],pc,p[3],p[4]) --- if ok then --- done = true --- end --- if start then start = getnext(start) end --- else --- start = getnext(start) --- end --- else --- start = getnext(start) --- end +-- assumptions: +-- +-- * languages that use complex disc nodes + +local function kernrun(disc,run) + -- + -- we catch <font 1><disc font 2> + -- + if trace_kernruns then + report_run("kern") -- will be more detailed + end + -- + local prev = getprev(disc) -- todo, keep these in the main loop + local next = getnext(disc) -- todo, keep these in the main loop + -- + local pre = getfield(disc,"pre") + local post = getfield(disc,"post") + local replace = getfield(disc,"replace") + -- + local prevmarks = prev + -- + -- can be optional, because why on earth do we get a disc after a mark (okay, maybe when a ccmp + -- has happened but then it should be in the disc so basically this test indicates an error) + -- + while prevmarks and getid(prevmarks) == glyph_code and marks[getchar(prevmarks)] and getfont(prevmarks) == currentfont and getsubtype(prevmarks) < 256 do + prevmarks = getprev(prevmarks) + end + -- + if prev and (pre or replace) and not (getid(prev) == glyph_code and getfont(prev) == currentfont and getsubtype(prev)<256) then + prev = false + end + if next and (post or replace) and not (getid(next) == glyph_code and getfont(next) == currentfont and getsubtype(next)<256) then + next = false + end + -- + if not pre then + -- go on + elseif prev then + local nest = getprev(pre) + setfield(pre,"prev",prev) + setfield(prev,"next",pre) + run(prevmarks,"preinjections") + setfield(pre,"prev",nest) + setfield(prev,"next",disc) + else + run(pre,"preinjections") + end + -- + if not post then + -- go on + elseif next then + local tail = find_node_tail(post) + setfield(tail,"next",next) + setfield(next,"prev",tail) + run(post,"postinjections",next) + setfield(tail,"next",nil) + setfield(next,"prev",disc) + else + run(post,"postinjections") + end + -- + if not replace and prev and next then + -- this should be already done by discfound + setfield(prev,"next",next) + setfield(next,"prev",prev) + run(prevmarks,"injections",next) + setfield(prev,"next",disc) + setfield(next,"prev",disc) + elseif prev and next then + local tail = find_node_tail(replace) + local nest = getprev(replace) + setfield(replace,"prev",prev) + setfield(prev,"next",replace) + setfield(tail,"next",next) + setfield(next,"prev",tail) + run(prevmarks,"replaceinjections",next) + setfield(replace,"prev",nest) + setfield(prev,"next",disc) + setfield(tail,"next",nil) + setfield(next,"prev",disc) + elseif prev then + local nest = getprev(replace) + setfield(replace,"prev",prev) + setfield(prev,"next",replace) + run(prevmarks,"replaceinjections") + setfield(replace,"prev",nest) + setfield(prev,"next",disc) + elseif next then + local tail = find_node_tail(replace) + setfield(tail,"next",next) + setfield(next,"prev",tail) + run(replace,"replaceinjections",next) + setfield(tail,"next",nil) + setfield(next,"prev",disc) + else + run(replace,"replaceinjections") + end +end --- there will be a new direction parser (pre-parsed etc) +-- the if new test might be dangerous as luatex will check / set some tail stuff +-- in a temp node --- less bytecode: 290 -> 254 --- --- attr = attr or false --- --- local a = getattr(start,0) --- if (a == attr and (not attribute or getprop(start,a_state) == attribute)) or (not attribute or getprop(start,a_state) == attribute) then --- -- the action --- end +local function comprun(disc,run) + if trace_compruns then + report_run("comp: %s",languages.serializediscretionary(disc)) + end + -- + local pre = getfield(disc,"pre") + if pre then + sweepnode = disc + sweeptype = "pre" -- in alternative code preinjections is used (also used then for proeprties, saves a variable) + local new, done = run(pre) + if done then + setfield(disc,"pre",new) + end + end + -- + local post = getfield(disc,"post") + if post then + sweepnode = disc + sweeptype = "post" + local new, done = run(post) + if done then + setfield(disc,"post",new) + end + end + -- + local replace = getfield(disc,"replace") + if replace then + sweepnode = disc + sweeptype = "replace" + local new, done = run(replace) + if done then + setfield(disc,"replace",new) + end + end + sweepnode = nil + sweeptype = nil +end + +local function testrun(disc,trun,crun) -- use helper + local next = getnext(disc) + if next then + local replace = getfield(disc,"replace") + if replace then + local prev = getprev(disc) + if prev then + -- only look ahead + local tail = find_node_tail(replace) + -- local nest = getprev(replace) + setfield(tail,"next",next) + setfield(next,"prev",tail) + if trun(replace,next) then + setfield(disc,"replace",nil) -- beware, side effects of nest so first + setfield(prev,"next",replace) + setfield(replace,"prev",prev) + setfield(next,"prev",tail) + setfield(tail,"next",next) + setfield(disc,"prev",nil) + setfield(disc,"next",nil) + flush_node_list(disc) + return replace -- restart + else + setfield(tail,"next",nil) + setfield(next,"prev",disc) + end + else + -- weird case + end + else + -- no need + end + else + -- weird case + end + comprun(disc,crun) + return next +end + +local function discrun(disc,drun,krun) + local next = getnext(disc) + local prev = getprev(disc) + if trace_discruns then + report_run("disc") -- will be more detailed + end + if next and prev then + setfield(prev,"next",next) + -- setfield(next,"prev",prev) + drun(prev) + setfield(prev,"next",disc) + -- setfield(next,"prev",disc) + end + -- + local pre = getfield(disc,"pre") + if not pre then + -- go on + elseif prev then + local nest = getprev(pre) + setfield(pre,"prev",prev) + setfield(prev,"next",pre) + krun(prev,"preinjections") + setfield(pre,"prev",nest) + setfield(prev,"next",disc) + else + krun(pre,"preinjections") + end + return next +end + +-- todo: maybe run lr and rl stretches local function featuresprocessor(head,font,attr) @@ -2180,6 +3058,7 @@ local function featuresprocessor(head,font,attr) currentfont = font rlmode = 0 + sweephead = { } local sequences = resources.sequences local done = false @@ -2195,23 +3074,27 @@ local function featuresprocessor(head,font,attr) -- Keeping track of the headnode is needed for devanagari (I generalized it a bit -- so that multiple cases are also covered.) - -- todo: retain prev + -- We don't goto the next node of a disc node is created so that we can then treat + -- the pre, post and replace. It's abit of a hack but works out ok for most cases. + + -- there can be less subtype and attr checking in the comprun etc helpers for s=1,#datasets do - local dataset = datasets[s] - featurevalue = dataset[1] -- todo: pass to function instead of using a global - - local sequence = dataset[5] -- sequences[s] -- also dataset[5] - local rlparmode = 0 - local topstack = 0 - local success = false - local attribute = dataset[2] - local chain = dataset[3] -- sequence.chain or 0 - local typ = sequence.type - local subtables = sequence.subtables - if chain < 0 then + 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 kind = dataset[4] + ----- chain = dataset[5] -- sequence.chain or 0 + local rlparmode = 0 + local topstack = 0 + local success = false + local typ = sequence.type + local gpossing = typ == "gpos_single" or typ == "gpos_pair" -- maybe all of them + local subtables = sequence.subtables + local handler = handlers[typ] + if typ == "gsub_reversecontextchain" then -- chain < 0 -- this is a limited case, no special treatments like 'init' etc - local handler = handlers[typ] -- we need to get rid of this slide! probably no longer needed in latest luatex local start = find_node_tail(head) -- slow (we can store tail because there's always a skip at the end): todo while start do @@ -2225,13 +3108,15 @@ local function featuresprocessor(head,font,attr) a = true end if a then + local char = getchar(start) for i=1,#subtables do local lookupname = subtables[i] local lookupcache = lookuphash[lookupname] if lookupcache then - local lookupmatch = lookupcache[getchar(start)] + local lookupmatch = lookupcache[char] if lookupmatch then - head, start, success = handler(head,start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,i) + -- todo: disc? + head, start, success = handler(head,start,kind,lookupname,lookupmatch,sequence,lookuphash,i) if success then break end @@ -2252,24 +3137,30 @@ local function featuresprocessor(head,font,attr) end end else - local handler = handlers[typ] local ns = #subtables local start = head -- local ? rlmode = 0 -- to be checked ? if ns == 1 then -- happens often - local lookupname = subtables[1] + local lookupname = subtables[1] local lookupcache = lookuphash[lookupname] if not lookupcache then -- also check for empty cache report_missing_cache(typ,lookupname) else - local function subrun(start) - -- mostly for gsub, gpos would demand a more clever approach - local head = start - local done = false + local function c_run(head) -- no need to check for 256 and attr probably also the same + local done = false + local start = sweephead[head] + if start then + sweephead[head] = nil + else + start = head + end while start do local id = getid(start) - if id == glyph_code and getfont(start) == font and getsubtype(start) < 256 then + if id ~= glyph_code then + -- very unlikely + start = getnext(start) + elseif getfont(start) == font and getsubtype(start) < 256 then local a = getattr(start,0) if a then a = (a == attr) and (not attribute or getprop(start,a_state) == attribute) @@ -2281,7 +3172,7 @@ local function featuresprocessor(head,font,attr) if lookupmatch then -- sequence kan weg local ok - head, start, ok = handler(head,start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,1) + head, start, ok = handler(head,start,kind,lookupname,lookupmatch,sequence,lookuphash,1) if ok then done = true end @@ -2291,48 +3182,106 @@ local function featuresprocessor(head,font,attr) start = getnext(start) end else - start = getnext(start) + return head, false end end if done then - success = true - return head + success = true -- needed in this subrun? end + return head, done end - local function kerndisc(disc) -- we can assume that prev and next are glyphs - local prev = getprev(disc) - local next = getnext(disc) - if prev and next then - setfield(prev,"next",next) - -- setfield(next,"prev",prev) - local a = getattr(prev,0) - if a then - a = (a == attr) and (not attribute or getprop(prev,a_state) == attribute) + local function t_run(start,stop) + while start ~= stop do + local id = getid(start) + if id == glyph_code and getfont(start) == font and getsubtype(start) < 256 then + local a = 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 + if a then + local lookupmatch = lookupcache[getchar(start)] + 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 = getnext(start) + local l = nil + while s do + 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 + end + end + end + start = getnext(start) else - a = not attribute or getprop(prev,a_state) == attribute + break end - if a then - local lookupmatch = lookupcache[getchar(prev)] - if lookupmatch then - -- sequence kan weg - local h, d, ok = handler(head,prev,dataset[4],lookupname,lookupmatch,sequence,lookuphash,1) - if ok then - done = true - success = true + end + end + + local function d_run(prev) -- we can assume that prev and next are glyphs + local a = getattr(prev,0) + if a then + a = (a == attr) and (not attribute or getprop(prev,a_state) == attribute) + else + a = not attribute or getprop(prev,a_state) == attribute + end + if a then + local lookupmatch = lookupcache[getchar(prev)] + if lookupmatch then + -- sequence kan weg + local h, d, ok = handler(head,prev,kind,lookupname,lookupmatch,sequence,lookuphash,1) + if ok then + done = true + success = true + end + end + end + end + + local function k_run(sub,injection,last) + local a = getattr(sub,0) + if a then + a = (a == attr) and (not attribute or getprop(sub,a_state) == attribute) + else + a = not attribute or getprop(sub,a_state) == attribute + end + if a then + -- sequence kan weg + for n in traverse_nodes(sub) do -- only gpos + if n == last then + break + end + local id = getid(n) + if id == glyph_code then + local lookupmatch = lookupcache[getchar(n)] + if lookupmatch then + local h, d, ok = handler(sub,n,kind,lookupname,lookupmatch,sequence,lookuphash,1,injection) + if ok then + done = true + success = true + end end + else + -- message end end - setfield(prev,"next",disc) - -- setfield(next,"prev",disc) end - return next end while start do local id = getid(start) if id == glyph_code then - if getfont(start) == font and getsubtype(start) < 256 then + if getfont(start) == font and getsubtype(start) < 256 then -- why a 256 test ... local a = getattr(start,0) if a then a = (a == attr) and (not attribute or getprop(start,a_state) == attribute) @@ -2340,59 +3289,52 @@ local function featuresprocessor(head,font,attr) a = not attribute or getprop(start,a_state) == attribute end if a then - local lookupmatch = lookupcache[getchar(start)] + local char = getchar(start) + local lookupmatch = lookupcache[char] if lookupmatch then -- sequence kan weg local ok - head, start, ok = handler(head,start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,1) + head, start, ok = handler(head,start,kind,lookupname,lookupmatch,sequence,lookuphash,1) if ok then success = true + elseif gpossing and zwnjruns and char == zwnj then + discrun(start,d_run) end + elseif gpossing and zwnjruns and char == zwnj then + discrun(start,d_run) end if start then start = getnext(start) end else - start = getnext(start) + start = getnext(start) end else start = getnext(start) end elseif id == disc_code then - -- mostly for gsub - if getsubtype(start) == discretionary_code then - local pre = getfield(start,"pre") - if pre then - local new = subrun(pre) - if new then setfield(start,"pre",new) end - end - local post = getfield(start,"post") - if post then - local new = subrun(post) - if new then setfield(start,"post",new) end - end - local replace = getfield(start,"replace") - if replace then - local new = subrun(replace) - if new then setfield(start,"replace",new) end - end -elseif typ == "gpos_single" or typ == "gpos_pair" then - kerndisc(start) + if gpossing then + kernrun(start,k_run) + start = getnext(start) + elseif typ == "gsub_ligature" then + start = testrun(start,t_run,c_run) + else + comprun(start,c_run) + start = getnext(start) end - start = getnext(start) elseif id == whatsit_code then -- will be function local subtype = getsubtype(start) if subtype == dir_code then local dir = getfield(start,"dir") - if dir == "+TRT" or dir == "+TLT" then + if dir == "+TLT" then topstack = topstack + 1 dirstack[topstack] = dir - elseif dir == "-TRT" or dir == "-TLT" then - topstack = topstack - 1 - end - local newdir = dirstack[topstack] - if newdir == "+TRT" then - rlmode = -1 - elseif newdir == "+TLT" then rlmode = 1 + elseif dir == "+TRT" then + topstack = topstack + 1 + dirstack[topstack] = dir + rlmode = -1 + elseif dir == "-TLT" or dir == "-TRT" then + topstack = topstack - 1 + rlmode = dirstack[topstack] == "+TRT" and -1 or 1 else rlmode = rlparmode end @@ -2422,15 +3364,23 @@ elseif typ == "gpos_single" or typ == "gpos_pair" then end end end + else - local function subrun(start) - -- mostly for gsub, gpos would demand a more clever approach - local head = start - local done = false + local function c_run(head) + local done = false + local start = sweephead[head] + if start then + sweephead[head] = nil + else + start = head + end while start do local id = getid(start) - if id == glyph_code and getfont(start) == font and getsubtype(start) < 256 then + if id ~= glyph_code then + -- very unlikely + start = getnext(start) + elseif getfont(start) == font and getsubtype(start) < 256 then local a = getattr(start,0) if a then a = (a == attr) and (not attribute or getprop(start,a_state) == attribute) @@ -2438,15 +3388,16 @@ elseif typ == "gpos_single" or typ == "gpos_pair" then a = not attribute or getprop(start,a_state) == attribute end if a then + local char = getchar(start) for i=1,ns do local lookupname = subtables[i] local lookupcache = lookuphash[lookupname] if lookupcache then - local lookupmatch = lookupcache[getchar(start)] + 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[4],lookupname,lookupmatch,sequence,lookuphash,i) + head, start, ok = handler(head,start,kind,lookupname,lookupmatch,sequence,lookuphash,i) if ok then done = true break @@ -2464,50 +3415,127 @@ elseif typ == "gpos_single" or typ == "gpos_pair" then start = getnext(start) end else - start = getnext(start) + return head, false end end if done then success = true - return head end + return head, done end - local function kerndisc(disc) -- we can assume that prev and next are glyphs - local prev = getprev(disc) - local next = getnext(disc) - if prev and next then - setfield(prev,"next",next) - -- setfield(next,"prev",prev) - local a = getattr(prev,0) - if a then - a = (a == attr) and (not attribute or getprop(prev,a_state) == attribute) - else - a = not attribute or getprop(prev,a_state) == attribute + local function d_run(prev) + local a = getattr(prev,0) + if a then + a = (a == attr) and (not attribute or getprop(prev,a_state) == attribute) + else + a = not attribute or getprop(prev,a_state) == attribute + end + if a then + -- brr prev can be disc + local char = getchar(prev) + for i=1,ns do + local lookupname = subtables[i] + local lookupcache = lookuphash[lookupname] + if lookupcache then + local lookupmatch = lookupcache[char] + if lookupmatch then + -- we could move all code inline but that makes things even more unreadable + local h, d, ok = handler(head,prev,kind,lookupname,lookupmatch,sequence,lookuphash,i) + if ok then + done = true + break + end + end + else + report_missing_cache(typ,lookupname) + end end - if a then - for i=1,ns do - local lookupname = subtables[i] - local lookupcache = lookuphash[lookupname] - if lookupcache then - local lookupmatch = lookupcache[getchar(prev)] - if lookupmatch then - -- we could move all code inline but that makes things even more unreadable - local h, d, ok = handler(head,prev,dataset[4],lookupname,lookupmatch,sequence,lookuphash,i) - if ok then - done = true - break + end + end + + local function k_run(sub,injection,last) + local a = getattr(sub,0) + if a then + a = (a == attr) and (not attribute or getprop(sub,a_state) == attribute) + else + a = not attribute or getprop(sub,a_state) == attribute + end + if a then + for n in traverse_nodes(sub) do -- only gpos + if n == last then + break + end + local id = getid(n) + if id == glyph_code then + local char = getchar(n) + for i=1,ns do + local lookupname = subtables[i] + local lookupcache = lookuphash[lookupname] + if lookupcache then + local lookupmatch = lookupcache[char] + if lookupmatch then + local h, d, ok = handler(head,n,kind,lookupname,lookupmatch,sequence,lookuphash,i,injection) + if ok then + done = true + break + end end + else + report_missing_cache(typ,lookupname) + end + end + else + -- message + end + end + end + end + + local function t_run(start,stop) + while start ~= stop do + local id = getid(start) + if id == glyph_code and getfont(start) == font and getsubtype(start) < 256 then + local a = 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 + if a then + local char = getchar(start) + for i=1,ns do + local lookupname = subtables[i] + local lookupcache = lookuphash[lookupname] + 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 = getnext(start) + local l = nil + while s do + 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 + end + end + else + report_missing_cache(typ,lookupname) end - else - report_missing_cache(typ,lookupname) end end + start = getnext(start) + else + break end - setfield(prev,"next",disc) - -- setfield(next,"prev",disc) end - return next end while start do @@ -2522,21 +3550,26 @@ elseif typ == "gpos_single" or typ == "gpos_pair" then end if a then for i=1,ns do - local lookupname = subtables[i] + local lookupname = subtables[i] local lookupcache = lookuphash[lookupname] if lookupcache then - local lookupmatch = lookupcache[getchar(start)] + local char = getchar(start) + 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[4],lookupname,lookupmatch,sequence,lookuphash,i) + head, start, ok = handler(head,start,kind,lookupname,lookupmatch,sequence,lookuphash,i) if ok then success = true break elseif not start then -- don't ask why ... shouldn't happen break + elseif gpossing and zwnjruns and char == zwnj then + discrun(start,d_run) end + elseif gpossing and zwnjruns and char == zwnj then + discrun(start,d_run) end else report_missing_cache(typ,lookupname) @@ -2550,42 +3583,30 @@ elseif typ == "gpos_single" or typ == "gpos_pair" then start = getnext(start) end elseif id == disc_code then - -- mostly for gsub - if getsubtype(start) == discretionary_code then - local pre = getfield(start,"pre") - if pre then - local new = subrun(pre) - if new then setfield(start,"pre",new) end - end - local post = getfield(start,"post") - if post then - local new = subrun(post) - if new then setfield(start,"post",new) end - end - local replace = getfield(start,"replace") - if replace then - local new = subrun(replace) - if new then setfield(start,"replace",new) end - end -elseif typ == "gpos_single" or typ == "gpos_pair" then - kerndisc(start) + if gpossing then + kernrun(start,k_run) + start = getnext(start) + elseif typ == "gsub_ligature" then + start = testrun(start,t_run,c_run) + else + comprun(start,c_run) + start = getnext(start) end - start = getnext(start) elseif id == whatsit_code then local subtype = getsubtype(start) if subtype == dir_code then local dir = getfield(start,"dir") - if dir == "+TRT" or dir == "+TLT" then + if dir == "+TLT" then topstack = topstack + 1 dirstack[topstack] = dir - elseif dir == "-TRT" or dir == "-TLT" then - topstack = topstack - 1 - end - local newdir = dirstack[topstack] - if newdir == "+TRT" then - rlmode = -1 - elseif newdir == "+TLT" then rlmode = 1 + elseif dir == "+TRT" then + topstack = topstack + 1 + dirstack[topstack] = dir + rlmode = -1 + elseif dir == "-TLT" or dir == "-TRT" then + topstack = topstack - 1 + rlmode = dirstack[topstack] == "+TRT" and -1 or 1 else rlmode = rlparmode end @@ -2629,6 +3650,8 @@ elseif typ == "gpos_single" or typ == "gpos_pair" then return head, done end +-- this might move to the loader + local function generic(lookupdata,lookupname,unicode,lookuphash) local target = lookuphash[lookupname] if target then @@ -2638,47 +3661,48 @@ local function generic(lookupdata,lookupname,unicode,lookuphash) end end -local action = { +local function ligature(lookupdata,lookupname,unicode,lookuphash) + local target = lookuphash[lookupname] + if not target then + target = { } + lookuphash[lookupname] = target + end + for i=1,#lookupdata do + local li = lookupdata[i] + local tu = target[li] + if not tu then + tu = { } + target[li] = tu + end + target = tu + end + target.ligature = unicode +end + +local function pair(lookupdata,lookupname,unicode,lookuphash) + local target = lookuphash[lookupname] + if not target then + target = { } + lookuphash[lookupname] = target + end + local others = target[unicode] + local paired = lookupdata[1] + if others then + others[paired] = lookupdata + else + others = { [paired] = lookupdata } + target[unicode] = others + end +end +local action = { substitution = generic, multiple = generic, alternate = generic, position = generic, - - ligature = function(lookupdata,lookupname,unicode,lookuphash) - local target = lookuphash[lookupname] - if not target then - target = { } - lookuphash[lookupname] = target - end - for i=1,#lookupdata do - local li = lookupdata[i] - local tu = target[li] - if not tu then - tu = { } - target[li] = tu - end - target = tu - end - target.ligature = unicode - end, - - pair = function(lookupdata,lookupname,unicode,lookuphash) - local target = lookuphash[lookupname] - if not target then - target = { } - lookuphash[lookupname] = target - end - local others = target[unicode] - local paired = lookupdata[1] - if others then - others[paired] = lookupdata - else - others = { [paired] = lookupdata } - target[unicode] = others - end - end, - + ligature = ligature, + pair = pair, + kern = pair, } local function prepare_lookups(tfmdata) @@ -2691,12 +3715,17 @@ local function prepare_lookups(tfmdata) local lookuptypes = resources.lookuptypes local characters = tfmdata.characters local descriptions = tfmdata.descriptions + local duplicates = resources.duplicates -- we cannot free the entries in the descriptions as sometimes we access -- then directly (for instance anchors) ... selectively freeing does save -- much memory as it's only a reference to a table and the slot in the -- description hash is not freed anyway + -- we can delay this using metatables so that we don't make the hashes for + -- features we don't use but then we need to loop over the characters + -- many times so we gain nothing + for unicode, character in next, characters do -- we cannot loop over descriptions ! local description = descriptions[unicode] @@ -2706,7 +3735,7 @@ local function prepare_lookups(tfmdata) local lookups = description.slookups if lookups then for lookupname, lookupdata in next, lookups do - action[lookuptypes[lookupname]](lookupdata,lookupname,unicode,lookuphash) + action[lookuptypes[lookupname]](lookupdata,lookupname,unicode,lookuphash,duplicates) end end @@ -2716,7 +3745,7 @@ local function prepare_lookups(tfmdata) local lookuptype = lookuptypes[lookupname] for l=1,#lookuplist do local lookupdata = lookuplist[l] - action[lookuptype](lookupdata,lookupname,unicode,lookuphash) + action[lookuptype](lookupdata,lookupname,unicode,lookuphash,duplicates) end end end @@ -2740,7 +3769,7 @@ local function prepare_lookups(tfmdata) for name, anchor in next, anchors do local lookups = anchor_to_lookup[name] if lookups then - for lookup, _ in next, lookups do + for lookup in next, lookups do local target = lookuphash[lookup] if target then target[unicode] = anchors @@ -2760,6 +3789,8 @@ local function prepare_lookups(tfmdata) end +-- so far + local function split(replacement,original) local result = { } for i=1,#replacement do @@ -2835,7 +3866,7 @@ local function prepare_contextchains(tfmdata) -- use sequence[start] instead but it's somewhat ugly. nt = nt + 1 t[nt] = { nofrules, lookuptype, sequence, start, stop, rule.lookups, replacements } - for unic, _ in next, sequence[start] do + for unic in next, sequence[start] do local cu = contexts[unic] if not cu then contexts[unic] = t diff --git a/src/fontloader/misc/fontloader-fonts.lua b/src/fontloader/misc/fontloader-fonts.lua index c81e8cd..f18ba35 100644 --- a/src/fontloader/misc/fontloader-fonts.lua +++ b/src/fontloader/misc/fontloader-fonts.lua @@ -27,16 +27,10 @@ if not modules then modules = { } end modules ['luatex-fonts'] = { -- also add more helper code here, but that depends to what extend metatex (sidetrack of context) -- evolves into a low level layer (depends on time, as usual). -texio.write_nl("") -texio.write_nl("--------------------------------------------------------------------------------") -texio.write_nl("The font code has been brought in sync with the context version of 2014.12.21 so") -texio.write_nl("if things don't work out as expected the interfacing needs to be checked. When") -texio.write_nl("this works as expected a second upgrade will happen that gives a more complete") -texio.write_nl("support and another sync with the context code (that new code is currently being") -texio.write_nl("tested. The base pass is now integrated in the main pass. The results can differ") -texio.write_nl("from those in context because there we integrate some mechanisms differently.") -texio.write_nl("--------------------------------------------------------------------------------") -texio.write_nl("") +-- The code here is the same as in context version 2015.09.11 but the rendering in context can be +-- different from generic. This can be a side effect of additional callbacks, additional features +-- and interferences between mechanisms between macro packages. We use the rendering in context +-- and luatex-plain as reference for issues. utf = utf or unicode.utf8 @@ -221,9 +215,9 @@ if non_generic_context.luatex_fonts.skip_loading ~= true then loadmodule('font-oti.lua') loadmodule('font-otf.lua') loadmodule('font-otb.lua') - loadmodule('luatex-fonts-inj.lua') + loadmodule('luatex-fonts-inj.lua') -- normally the same as font-inj.lua loadmodule('luatex-fonts-ota.lua') - loadmodule('luatex-fonts-otn.lua') + loadmodule('luatex-fonts-otn.lua') -- normally the same as font-otn.lua loadmodule('font-otp.lua') loadmodule('luatex-fonts-lua.lua') loadmodule('font-def.lua') -- this code (stripped) might end up in luatex-fonts-def.lua diff --git a/src/fontloader/misc/fontloader-l-lpeg.lua b/src/fontloader/misc/fontloader-l-lpeg.lua index 55a0d89..5be1246 100644 --- a/src/fontloader/misc/fontloader-l-lpeg.lua +++ b/src/fontloader/misc/fontloader-l-lpeg.lua @@ -82,7 +82,7 @@ local lpegtype, lpegmatch, lpegprint = lpeg.type, lpeg.match, lpeg.print -- let's start with an inspector: if setinspector then - setinspector(function(v) if lpegtype(v) then lpegprint(v) return true end end) + setinspector("lpeg",function(v) if lpegtype(v) then lpegprint(v) return true end end) end -- Beware, we predefine a bunch of patterns here and one reason for doing so diff --git a/src/fontloader/misc/fontloader-l-lua.lua b/src/fontloader/misc/fontloader-l-lua.lua index 1a2a987..cb61829 100644 --- a/src/fontloader/misc/fontloader-l-lua.lua +++ b/src/fontloader/misc/fontloader-l-lua.lua @@ -129,22 +129,36 @@ local print, select, tostring = print, select, tostring local inspectors = { } -function setinspector(inspector) -- global function - inspectors[#inspectors+1] = inspector +function setinspector(kind,inspector) -- global function + inspectors[kind] = inspector end function inspect(...) -- global function for s=1,select("#",...) do local value = select(s,...) - local done = false - for i=1,#inspectors do - done = inspectors[i](value) - if done then - break + if value == nil then + print("nil") + else + local done = false + -- type driven (table) + local kind = type(value) + local inspector = inspectors[kind] + if inspector then + done = inspector(value) + if done then + break + end + end + -- whatever driven (token, node, ...) + for kind, inspector in next, inspectors do + done = inspector(value) + if done then + break + end + end + if not done then + print(tostring(value)) end - end - if not done then - print(tostring(value)) end end end diff --git a/src/fontloader/misc/fontloader-l-string.lua b/src/fontloader/misc/fontloader-l-string.lua index 70c66f6..e9dc2bb 100644 --- a/src/fontloader/misc/fontloader-l-string.lua +++ b/src/fontloader/misc/fontloader-l-string.lua @@ -192,10 +192,11 @@ string.itself = function(s) return s end -- also handy (see utf variant) -local pattern = Ct(C(1)^0) -- string and not utf ! +local pattern_c = Ct( C(1) ^0) -- string and not utf ! +local pattern_b = Ct((C(1)/byte)^0) -function string.totable(str) - return lpegmatch(pattern,str) +function string.totable(str,bytes) + return lpegmatch(bytes and pattern_b or pattern_c,str) end -- handy from within tex: diff --git a/src/fontloader/misc/fontloader-l-table.lua b/src/fontloader/misc/fontloader-l-table.lua index b02f210..552097e 100644 --- a/src/fontloader/misc/fontloader-l-table.lua +++ b/src/fontloader/misc/fontloader-l-table.lua @@ -1144,7 +1144,7 @@ function table.print(t,...) end if setinspector then - setinspector(function(v) if type(v) == "table" then serialize(print,v,"table") return true end end) + setinspector("table",function(v) if type(v) == "table" then serialize(print,v,"table") return true end end) end -- -- -- obsolete but we keep them for a while and might comment them later -- -- -- diff --git a/src/fontloader/misc/fontloader-mplib.lua b/src/fontloader/misc/fontloader-mplib.lua index c6628ac..fd6eb97 100644 --- a/src/fontloader/misc/fontloader-mplib.lua +++ b/src/fontloader/misc/fontloader-mplib.lua @@ -22,7 +22,9 @@ if metapost and metapost.version then else - local format, concat, abs, match = string.format, table.concat, math.abs, string.match + local format, match, gsub = string.format, string.match, string.gsub + local concat = table.concat + local abs = math.abs local mplib = require ('mplib') local kpse = require ('kpse') @@ -144,10 +146,101 @@ else metapost.make = metapost.make or function() end + local template = [[ + \pdfoutput=1 + \pdfpkresolution600 + \pdfcompresslevel=9 + %s\relax + \hsize=100in + \vsize=\hsize + \hoffset=-1in + \voffset=\hoffset + \topskip=0pt + \setbox0=\hbox{%s}\relax + \pageheight=\ht0 + \pagewidth=\wd0 + \box0 + \bye + ]] + + metapost.texrunner = "mtxrun --script plain" + + local texruns = 0 -- per document + local texhash = { } -- per document + + function metapost.maketext(mpd,str,what) + -- inefficient but one can always use metafun .. it's more a test + -- feature + local verbatimtex = mpd.verbatimtex + if not verbatimtex then + verbatimtex = { } + mpd.verbatimtex = verbatimtex + end + if what == 1 then + table.insert(verbatimtex,str) + else + local texcode = format(template,concat(verbatimtex,"\n"),str) + local texdone = texhash[texcode] + local jobname = tex.jobname + if not texdone then + texruns = texruns + 1 + texdone = texruns + texhash[texcode] = texdone + local texname = format("%s-mplib-%s.tmp",jobname,texdone) + local logname = format("%s-mplib-%s.log",jobname,texdone) + local pdfname = format("%s-mplib-%s.pdf",jobname,texdone) + io.savedata(texname,texcode) + os.execute(format("%s %s",metapost.texrunner,texname)) + os.remove(texname) + os.remove(logname) + end + return format('"image::%s-mplib-%s.pdf" infont defaultfont',jobname,texdone) + end + end + + local function mpprint(buffer,...) + for i=1,select("#",...) do + local value = select(i,...) + if value ~= nil then + local t = type(value) + if t == "number" then + buffer[#buffer+1] = format("%.16f",value) + elseif t == "string" then + buffer[#buffer+1] = value + elseif t == "table" then + buffer[#buffer+1] = "(" .. concat(value,",") .. ")" + else -- boolean or whatever + buffer[#buffer+1] = tostring(value) + end + end + end + end + + function metapost.runscript(mpd,code) + local code = loadstring(code) + if type(code) == "function" then + local buffer = { } + function metapost.print(...) + mpprint(buffer,...) + end + code() + -- mpd.buffer = buffer -- for tracing + return concat(buffer,"") + end + return "" + end + function metapost.load(name) + local mpd = { + buffer = { }, + verbatim = { } + } local mpx = mplib.new { ini_version = true, - find_file = metapost.finder, + find_file = metapost.finder, + make_text = function(...) return metapost.maketext (mpd,...) end, + run_script = function(...) return metapost.runscript(mpd,...) end, + extensions = 1, } local result if not mpx then @@ -217,8 +310,8 @@ else return figure:objects() end - function metapost.convert(result, flusher) - metapost.flush(result, flusher) + function metapost.convert(result,flusher) + metapost.flush(result,flusher) return true -- done end @@ -239,8 +332,13 @@ else end function pdf_textfigure(font,size,text,width,height,depth) - text = text:gsub(".","\\hbox{%1}") -- kerning happens in metapost - tex.sprint(format("\\MPLIBtextext{%s}{%s}{%s}{%s}{%s}",font,size,text,0,-( 7200/ 7227)/65536*depth)) + local how, what = match(text,"^(.-)::(.+)$") + if how == "image" then + tex.sprint(format("\\MPLIBpdftext{%s}{%s}",what,depth)) + else + text = gsub(text,".","\\hbox{%1}") -- kerning happens in metapost + tex.sprint(format("\\MPLIBtextext{%s}{%s}{%s}{%s}",font,size,text,depth)) + end end local bend_tolerance = 131/65536 @@ -375,8 +473,10 @@ else pdf_literalcode("Q") else local cs = object.color + local cr = false if cs and #cs > 0 then - pdf_literalcode(metapost.colorconverter(cs)) + cs, cr = metapost.colorconverter(cs) + pdf_literalcode(cs) end local ml = object.miterlimit if ml and ml ~= miterlimit then diff --git a/src/fontloader/misc/fontloader-mplib.tex b/src/fontloader/misc/fontloader-mplib.tex index 09dd179..f9de4b2 100644 --- a/src/fontloader/misc/fontloader-mplib.tex +++ b/src/fontloader/misc/fontloader-mplib.tex @@ -106,15 +106,14 @@ %D Text items have a special handler: -\def\MPLIBtextext#1#2#3#4#5% +\def\MPLIBtextext#1#2#3#4% {\begingroup \setbox\mplibscratchbox\hbox {\font\temp=#1 at #2bp% \temp #3}% \setbox\mplibscratchbox\hbox - {\hskip#4 bp% - \raise#5 bp% + {\raise#4sp% \box\mplibscratchbox}% \wd\mplibscratchbox0pt% \ht\mplibscratchbox0pt% @@ -122,4 +121,20 @@ \box\mplibscratchbox \endgroup} +\def\MPLIBpdftext#1#2% + {\ifcsname mplib::#1\endcsname + % already done, forgotten outside convert group + \message{<reusing mplib: #1>}% + \else + \message{<embedding mplib: #1>}% + \immediate\pdfximage{#1}% we cannot remove the file as it is included last + \expandafter\edef\csname mplib::#1\endcsname{\the\pdflastximage}% + \fi + \setbox\mplibscratchbox\hbox + {\raise#2sp\hbox{\pdfrefximage\csname mplib::#1\endcsname}}% + \wd\mplibscratchbox0pt% + \ht\mplibscratchbox0pt% + \dp\mplibscratchbox0pt% + \box\mplibscratchbox} + \endinput diff --git a/src/fontloader/misc/fontloader-plain.tex b/src/fontloader/misc/fontloader-plain.tex index c9a9e36..9902c49 100644 --- a/src/fontloader/misc/fontloader-plain.tex +++ b/src/fontloader/misc/fontloader-plain.tex @@ -11,7 +11,26 @@ \directlua {tex.enableprimitives('', tex.extraprimitives())} -\pdfoutput=1 +% We assume that pdf is used. + +\pdfoutput 1 + +% We set the page dimensions because otherwise the backend does weird things +% when we have for instance this on a line of its own: +% +% \hbox to 100cm {\hss wide indeed\hss} +% +% The page dimension calculation is a fuzzy one as there are some compensations +% for the \hoffset and \voffset and such. I remember long discussions and much +% trial and error in figuring this out during pdftex development times. Where +% a dvi driver will project on a papersize (and thereby clip) the pdf backend +% has to deal with the lack of a page concept on tex by some guessing. Normally +% a macro package will set the dimensions to something reasonable anyway. + +\pagewidth 8.5in +\pageheight 11.0in + +% We load some code at runtime: \everyjob \expandafter {% \the\everyjob @@ -20,9 +39,11 @@ \input {luatex-math}% \input {luatex-languages}% \input {luatex-mplib}% - % \input {luatex-gadgets}% + \input {luatex-gadgets}% } +% We also patch the version number: + \edef\fmtversion{\fmtversion+luatex} \dump diff --git a/src/fontloader/misc/fontloader-test.tex b/src/fontloader/misc/fontloader-test.tex index 6f48e0c..f851aab 100644 --- a/src/fontloader/misc/fontloader-test.tex +++ b/src/fontloader/misc/fontloader-test.tex @@ -1,3 +1,5 @@ +% texformat=luatex-plain + %D \module %D [ file=luatex-test, %D version=2009.12.01, @@ -33,12 +35,12 @@ \font\mathtest=cambria(math) {\mathtest 123} -\font\gothic=msgothic(ms-gothic) {\gothic whatever} +% \font\gothic=msgothic(ms-gothic) {\gothic whatever} % no longer in windows 10 \bgroup - \pdfprotrudechars2 - \pdfadjustspacing2 + \ifdefined\pdfprotrudechars \pdfprotrudechars \else \protrudechars \fi 2 \relax + \ifdefined\pdfadjustspacing \pdfadjustspacing \else \adjustspacing \fi 2 \relax \font\testb=file:lmroman12-regular:+liga;extend=1.5 at 12pt \testb \input tufte \par \font\testb=file:lmroman12-regular:+liga;slant=0.8 at 12pt \testb \input tufte \par @@ -48,12 +50,30 @@ \setmplibformat{plain} +\directlua { + function MpTest() + metapost.print("fullcircle scaled 3cm") + end +} + \mplibcode beginfig(1) ; draw fullcircle scaled 10cm withcolor red withpen pencircle xscaled 4mm yscaled 2mm rotated 30 ; + draw "test" infont defaultfont scaled 4 ; + verbatimtex \sl etex; + draw btex some more test etex scaled 2 ; + currentpicture := currentpicture shifted (0,1cm) ; + verbatimtex \bf etex; + draw btex another test etex scaled 2 ; + currentpicture := currentpicture shifted (0,1cm) ; + draw btex another test etex scaled 2 ; + draw + runscript("MpTest()") + withcolor green + withpen pencircle xscaled 2mm yscaled 1mm rotated 20 ; endfig ; \endmplibcode diff --git a/src/fontloader/misc/fontloader-util-str.lua b/src/fontloader/misc/fontloader-util-str.lua index c2139b1..95534c8 100644 --- a/src/fontloader/misc/fontloader-util-str.lua +++ b/src/fontloader/misc/fontloader-util-str.lua @@ -6,7 +6,7 @@ if not modules then modules = { } end modules ['util-str'] = { license = "see context related readme files" } -utilities = utilities or {} +utilities = utilities or { } utilities.strings = utilities.strings or { } local strings = utilities.strings @@ -354,7 +354,16 @@ function string.autosingle(s,sep) return ("'" .. tostring(s) .. "'") end -local tracedchars = { } +local tracedchars = { [0] = + -- the regular bunch + "[null]", "[soh]", "[stx]", "[etx]", "[eot]", "[enq]", "[ack]", "[bel]", + "[bs]", "[ht]", "[lf]", "[vt]", "[ff]", "[cr]", "[so]", "[si]", + "[dle]", "[dc1]", "[dc2]", "[dc3]", "[dc4]", "[nak]", "[syn]", "[etb]", + "[can]", "[em]", "[sub]", "[esc]", "[fs]", "[gs]", "[rs]", "[us]", + -- plus space + "[space]", -- 0x20 +} + string.tracedchars = tracedchars strings.tracers = tracedchars |