From 8d8d528d2ad52599f11250cfc567fea4f37f2a8b Mon Sep 17 00:00:00 2001 From: Context Git Mirror Bot Date: Tue, 12 Jan 2016 17:15:07 +0100 Subject: 2016-01-12 16:26:00 --- tex/context/base/mkiv/font-otb.lua | 707 +++++++++++++++++++++++++++++++++++++ 1 file changed, 707 insertions(+) create mode 100644 tex/context/base/mkiv/font-otb.lua (limited to 'tex/context/base/mkiv/font-otb.lua') diff --git a/tex/context/base/mkiv/font-otb.lua b/tex/context/base/mkiv/font-otb.lua new file mode 100644 index 000000000..c9f5d4aca --- /dev/null +++ b/tex/context/base/mkiv/font-otb.lua @@ -0,0 +1,707 @@ +if not modules then modules = { } end modules ['font-otb'] = { + version = 1.001, + comment = "companion to font-ini.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} +local concat = table.concat +local format, gmatch, gsub, find, match, lower, strip = string.format, string.gmatch, string.gsub, string.find, string.match, string.lower, string.strip +local type, next, tonumber, tostring, rawget = type, next, tonumber, tostring, rawget +local lpegmatch = lpeg.match +local utfchar = utf.char + +local trace_baseinit = false trackers.register("otf.baseinit", function(v) trace_baseinit = v end) +local trace_singles = false trackers.register("otf.singles", function(v) trace_singles = v end) +local trace_multiples = false trackers.register("otf.multiples", function(v) trace_multiples = v end) +local trace_alternatives = false trackers.register("otf.alternatives", function(v) trace_alternatives = v end) +local trace_ligatures = false trackers.register("otf.ligatures", function(v) trace_ligatures = v end) +local trace_ligatures_detail = false trackers.register("otf.ligatures.detail", function(v) trace_ligatures_detail = v end) +local trace_kerns = false trackers.register("otf.kerns", function(v) trace_kerns = v end) +local trace_preparing = false trackers.register("otf.preparing", function(v) trace_preparing = v end) + +local report_prepare = logs.reporter("fonts","otf prepare") + +local fonts = fonts +local otf = fonts.handlers.otf + +local otffeatures = otf.features +local registerotffeature = otffeatures.register + +otf.defaultbasealternate = "none" -- first last + +local wildcard = "*" +local default = "dflt" + +local formatters = string.formatters +local f_unicode = formatters["%U"] +local f_uniname = formatters["%U (%s)"] +local f_unilist = formatters["% t (% t)"] + +local function gref(descriptions,n) + if type(n) == "number" then + local name = descriptions[n].name + if name then + return f_uniname(n,name) + else + return f_unicode(n) + end + elseif n then + local num, nam, j = { }, { }, 0 + for i=1,#n do + local ni = n[i] + if tonumber(ni) then -- first is likely a key + j = j + 1 + local di = descriptions[ni] + num[j] = f_unicode(ni) + nam[j] = di and di.name or "-" + end + end + return f_unilist(num,nam) + else + return "" + end +end + +local function cref(feature,lookuptags,lookupname) + if lookupname then + return formatters["feature %a, lookup %a"](feature,lookuptags[lookupname]) + else + return formatters["feature %a"](feature) + end +end + +local function report_alternate(feature,lookuptags,lookupname,descriptions,unicode,replacement,value,comment) + report_prepare("%s: base alternate %s => %s (%S => %S)", + cref(feature,lookuptags,lookupname), + gref(descriptions,unicode), + replacement and gref(descriptions,replacement), + value, + comment) +end + +local function report_substitution(feature,lookuptags,lookupname,descriptions,unicode,substitution) + report_prepare("%s: base substitution %s => %S", + cref(feature,lookuptags,lookupname), + gref(descriptions,unicode), + gref(descriptions,substitution)) +end + +local function report_ligature(feature,lookuptags,lookupname,descriptions,unicode,ligature) + report_prepare("%s: base ligature %s => %S", + cref(feature,lookuptags,lookupname), + gref(descriptions,ligature), + gref(descriptions,unicode)) +end + +local function report_kern(feature,lookuptags,lookupname,descriptions,unicode,otherunicode,value) + report_prepare("%s: base kern %s + %s => %S", + cref(feature,lookuptags,lookupname), + gref(descriptions,unicode), + gref(descriptions,otherunicode), + value) +end + +local basemethods = { } +local basemethod = "" + +local function applybasemethod(what,...) + local m = basemethods[basemethod][what] + if m then + return m(...) + end +end + +-- We need to make sure that luatex sees the difference between +-- base fonts that have different glyphs in the same slots in fonts +-- that have the same fullname (or filename). LuaTeX will merge fonts +-- eventually (and subset later on). If needed we can use a more +-- verbose name as long as we don't use <()<>[]{}/%> and the length +-- is < 128. + +local basehash, basehashes, applied = { }, 1, { } + +local function registerbasehash(tfmdata) + local properties = tfmdata.properties + local hash = concat(applied," ") + local base = basehash[hash] + if not base then + basehashes = basehashes + 1 + base = basehashes + basehash[hash] = base + end + properties.basehash = base + properties.fullname = properties.fullname .. "-" .. base + -- report_prepare("fullname base hash '%a, featureset %a",tfmdata.properties.fullname,hash) + applied = { } +end + +local function registerbasefeature(feature,value) + applied[#applied+1] = feature .. "=" .. tostring(value) +end + +-- The original basemode ligature builder used the names of components +-- and did some expression juggling to get the chain right. The current +-- variant starts with unicodes but still uses names to make the chain. +-- This is needed because we have to create intermediates when needed +-- but use predefined snippets when available. To some extend the +-- current builder is more stupid but I don't worry that much about it +-- as ligatures are rather predicatable. +-- +-- Personally I think that an ff + i == ffi rule as used in for instance +-- latin modern is pretty weird as no sane person will key that in and +-- expect a glyph for that ligature plus the following character. Anyhow, +-- as we need to deal with this, we do, but no guarantes are given. +-- +-- latin modern dejavu +-- +-- f+f 102 102 102 102 +-- f+i 102 105 102 105 +-- f+l 102 108 102 108 +-- f+f+i 102 102 105 +-- f+f+l 102 102 108 102 102 108 +-- ff+i 64256 105 64256 105 +-- ff+l 64256 108 +-- +-- As you can see here, latin modern is less complete than dejavu but +-- in practice one will not notice it. +-- +-- The while loop is needed because we need to resolve for instance +-- pseudo names like hyphen_hyphen to endash so in practice we end +-- up with a bit too many definitions but the overhead is neglectable. +-- +-- We can have changed[first] or changed[second] but it quickly becomes +-- messy if we need to take that into account. + +local trace = false + +local function finalize_ligatures(tfmdata,ligatures) + local nofligatures = #ligatures + if nofligatures > 0 then + local characters = tfmdata.characters + local descriptions = tfmdata.descriptions + local resources = tfmdata.resources + local unicodes = resources.unicodes -- we use rawget in order to avoid bulding the table + local private = resources.private + local alldone = false + while not alldone do + local done = 0 + for i=1,nofligatures do + local ligature = ligatures[i] + if ligature then + local unicode, lookupdata = ligature[1], ligature[2] + if trace_ligatures_detail then + report_prepare("building % a into %a",lookupdata,unicode) + end + local size = #lookupdata + local firstcode = lookupdata[1] -- [2] + local firstdata = characters[firstcode] + local okay = false + if firstdata then + local firstname = "ctx_" .. firstcode + for i=1,size-1 do -- for i=2,size-1 do + local firstdata = characters[firstcode] + if not firstdata then + firstcode = private + if trace_ligatures_detail then + report_prepare("defining %a as %a",firstname,firstcode) + end + unicodes[firstname] = firstcode + firstdata = { intermediate = true, ligatures = { } } + characters[firstcode] = firstdata + descriptions[firstcode] = { name = firstname } + private = private + 1 + end + local target + local secondcode = lookupdata[i+1] + local secondname = firstname .. "_" .. secondcode + if i == size - 1 then + target = unicode + if not rawget(unicodes,secondname) then + unicodes[secondname] = unicode -- map final ligature onto intermediates + end + okay = true + else + target = rawget(unicodes,secondname) + if not target then + break + end + end + if trace_ligatures_detail then + report_prepare("codes (%a,%a) + (%a,%a) -> %a",firstname,firstcode,secondname,secondcode,target) + end + local firstligs = firstdata.ligatures + if firstligs then + firstligs[secondcode] = { char = target } + else + firstdata.ligatures = { [secondcode] = { char = target } } + end + firstcode = target + firstname = secondname + end + elseif trace_ligatures_detail then + report_prepare("no glyph (%a,%a) for building %a",firstname,firstcode,target) + end + if okay then + ligatures[i] = false + done = done + 1 + end + end + end + alldone = done == 0 + end + if trace_ligatures_detail then + for k, v in table.sortedhash(characters) do + if v.ligatures then + table.print(v,k) + end + end + end + resources.private = private + return true + end +end + +local function preparesubstitutions(tfmdata,feature,value,validlookups,lookuplist) + local characters = tfmdata.characters + local descriptions = tfmdata.descriptions + local resources = tfmdata.resources + local properties = tfmdata.properties + local changed = tfmdata.changed + local lookuphash = resources.lookuphash + local lookuptypes = resources.lookuptypes + local lookuptags = resources.lookuptags + + local ligatures = { } + local alternate = tonumber(value) or true and 1 + local defaultalt = otf.defaultbasealternate + + local trace_singles = trace_baseinit and trace_singles + local trace_alternatives = trace_baseinit and trace_alternatives + local trace_ligatures = trace_baseinit and trace_ligatures + + local actions = { + substitution = function(lookupdata,lookuptags,lookupname,description,unicode) + if trace_singles then + report_substitution(feature,lookuptags,lookupname,descriptions,unicode,lookupdata) + end + changed[unicode] = lookupdata + end, + alternate = function(lookupdata,lookuptags,lookupname,description,unicode) + local replacement = lookupdata[alternate] + if replacement then + changed[unicode] = replacement + if trace_alternatives then + report_alternate(feature,lookuptags,lookupname,descriptions,unicode,replacement,value,"normal") + end + elseif defaultalt == "first" then + replacement = lookupdata[1] + changed[unicode] = replacement + if trace_alternatives then + report_alternate(feature,lookuptags,lookupname,descriptions,unicode,replacement,value,defaultalt) + end + elseif defaultalt == "last" then + replacement = lookupdata[#data] + if trace_alternatives then + report_alternate(feature,lookuptags,lookupname,descriptions,unicode,replacement,value,defaultalt) + end + else + if trace_alternatives then + report_alternate(feature,lookuptags,lookupname,descriptions,unicode,replacement,value,"unknown") + end + end + end, + ligature = function(lookupdata,lookuptags,lookupname,description,unicode) + if trace_ligatures then + report_ligature(feature,lookuptags,lookupname,descriptions,unicode,lookupdata) + end + ligatures[#ligatures+1] = { unicode, lookupdata } + end, + } + + for unicode, character in next, characters do + local description = descriptions[unicode] + 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] + if action then + action(lookupdata,lookuptags,lookupname,description,unicode) + end + end + end + end + local lookups = description.mlookups + if lookups then + for l=1,#lookuplist do + local lookupname = lookuplist[l] + local lookuplist = lookups[lookupname] + if lookuplist then + local lookuptype = lookuptypes[lookupname] + local action = actions[lookuptype] + if action then + for i=1,#lookuplist do + action(lookuplist[i],lookuptags,lookupname,description,unicode) + end + end + end + end + end + end + properties.hasligatures = finalize_ligatures(tfmdata,ligatures) +end + +local function preparepositionings(tfmdata,feature,value,validlookups,lookuplist) -- todo what kind of kerns, currently all + local characters = tfmdata.characters + local descriptions = tfmdata.descriptions + local resources = tfmdata.resources + local properties = tfmdata.properties + local lookuptags = resources.lookuptags + local sharedkerns = { } + local traceindeed = trace_baseinit and trace_kerns + local haskerns = false + for unicode, character in next, characters do + local description = descriptions[unicode] + local rawkerns = description.kerns -- shared + if rawkerns then + local s = sharedkerns[rawkerns] + if s == false then + -- skip + elseif s then + character.kerns = s + else + local newkerns = character.kerns + local done = false + for l=1,#lookuplist do + local lookup = lookuplist[l] + local kerns = rawkerns[lookup] + if kerns then + for otherunicode, value in next, kerns do + if value == 0 then + -- maybe no 0 test here + elseif not newkerns then + newkerns = { [otherunicode] = value } + done = true + if traceindeed then + report_kern(feature,lookuptags,lookup,descriptions,unicode,otherunicode,value) + end + elseif not newkerns[otherunicode] then -- first wins + newkerns[otherunicode] = value + done = true + if traceindeed then + report_kern(feature,lookuptags,lookup,descriptions,unicode,otherunicode,value) + end + end + end + end + end + if done then + sharedkerns[rawkerns] = newkerns + character.kerns = newkerns -- no empty assignments + haskerns = true + else + sharedkerns[rawkerns] = false + end + end + end + end + properties.haskerns = haskerns +end + +basemethods.independent = { + preparesubstitutions = preparesubstitutions, + preparepositionings = preparepositionings, +} + +local function makefake(tfmdata,name,present) + local resources = tfmdata.resources + local private = resources.private + local character = { intermediate = true, ligatures = { } } + resources.unicodes[name] = private + tfmdata.characters[private] = character + tfmdata.descriptions[private] = { name = name } + resources.private = private + 1 + present[name] = private + return character +end + +local function make_1(present,tree,name) + for k, v in next, tree do + if k == "ligature" then + present[name] = v + else + make_1(present,v,name .. "_" .. k) + end + end +end + +local function make_2(present,tfmdata,characters,tree,name,preceding,unicode,done,lookuptags,lookupname) + for k, v in next, tree do + if k == "ligature" then + local character = characters[preceding] + if not character then + if trace_baseinit then + report_prepare("weird ligature in lookup %a, current %C, preceding %C",lookuptags[lookupname],v,preceding) + end + character = makefake(tfmdata,name,present) + end + local ligatures = character.ligatures + if ligatures then + ligatures[unicode] = { char = v } + else + character.ligatures = { [unicode] = { char = v } } + end + if done then + local d = done[lookupname] + if not d then + done[lookupname] = { "dummy", v } + else + d[#d+1] = v + end + end + else + local code = present[name] or unicode + local name = name .. "_" .. k + make_2(present,tfmdata,characters,v,name,code,k,done,lookuptags,lookupname) + end + end +end + +local function preparesubstitutions(tfmdata,feature,value,validlookups,lookuplist) + local characters = tfmdata.characters + local descriptions = tfmdata.descriptions + local resources = tfmdata.resources + local changed = tfmdata.changed + local lookuphash = resources.lookuphash + local lookuptypes = resources.lookuptypes + local lookuptags = resources.lookuptags + + local ligatures = { } + local alternate = tonumber(value) or true and 1 + local defaultalt = otf.defaultbasealternate + + local trace_singles = trace_baseinit and trace_singles + local trace_alternatives = trace_baseinit and trace_alternatives + local trace_ligatures = trace_baseinit and trace_ligatures + + for l=1,#lookuplist do + local lookupname = lookuplist[l] + local lookupdata = lookuphash[lookupname] + local lookuptype = lookuptypes[lookupname] + for unicode, data in next, lookupdata do + if lookuptype == "substitution" then + if trace_singles then + report_substitution(feature,lookuptags,lookupname,descriptions,unicode,data) + end + changed[unicode] = data + elseif lookuptype == "alternate" then + local replacement = data[alternate] + if replacement then + changed[unicode] = replacement + if trace_alternatives then + report_alternate(feature,lookuptags,lookupname,descriptions,unicode,replacement,value,"normal") + end + elseif defaultalt == "first" then + replacement = data[1] + changed[unicode] = replacement + if trace_alternatives then + report_alternate(feature,lookuptags,lookupname,descriptions,unicode,replacement,value,defaultalt) + end + elseif defaultalt == "last" then + replacement = data[#data] + if trace_alternatives then + report_alternate(feature,lookuptags,lookupname,descriptions,unicode,replacement,value,defaultalt) + end + else + if trace_alternatives then + report_alternate(feature,lookuptags,lookupname,descriptions,unicode,replacement,value,"unknown") + end + end + elseif lookuptype == "ligature" then + ligatures[#ligatures+1] = { unicode, data, lookupname } + if trace_ligatures then + report_ligature(feature,lookuptags,lookupname,descriptions,unicode,data) + end + end + end + end + + local nofligatures = #ligatures + + if nofligatures > 0 then + + local characters = tfmdata.characters + local present = { } + local done = trace_baseinit and trace_ligatures and { } + + for i=1,nofligatures do + local ligature = ligatures[i] + local unicode, tree = ligature[1], ligature[2] + make_1(present,tree,"ctx_"..unicode) + end + + for i=1,nofligatures do + local ligature = ligatures[i] + local unicode, tree, lookupname = ligature[1], ligature[2], ligature[3] + make_2(present,tfmdata,characters,tree,"ctx_"..unicode,unicode,unicode,done,lookuptags,lookupname) + end + + end + +end + +local function preparepositionings(tfmdata,feature,value,validlookups,lookuplist) + local characters = tfmdata.characters + local descriptions = tfmdata.descriptions + local resources = tfmdata.resources + local properties = tfmdata.properties + local lookuphash = resources.lookuphash + local lookuptags = resources.lookuptags + local traceindeed = trace_baseinit and trace_kerns + -- check out this sharedkerns trickery + for l=1,#lookuplist do + local lookupname = lookuplist[l] + local lookupdata = lookuphash[lookupname] + for unicode, data in next, lookupdata do + local character = characters[unicode] + local kerns = character.kerns + if not kerns then + kerns = { } + character.kerns = kerns + end + if traceindeed then + for otherunicode, kern in next, data do + if not kerns[otherunicode] and kern ~= 0 then + kerns[otherunicode] = kern + report_kern(feature,lookuptags,lookup,descriptions,unicode,otherunicode,kern) + end + end + else + for otherunicode, kern in next, data do + if not kerns[otherunicode] and kern ~= 0 then + kerns[otherunicode] = kern + end + end + end + end + end + +end + +local function initializehashes(tfmdata) + nodeinitializers.features(tfmdata) +end + +basemethods.shared = { + initializehashes = initializehashes, + preparesubstitutions = preparesubstitutions, + preparepositionings = preparepositionings, +} + +basemethod = "independent" + +local function featuresinitializer(tfmdata,value) + if true then -- value then + local starttime = trace_preparing and os.clock() + local features = tfmdata.shared.features + local fullname = tfmdata.properties.fullname or "?" + if features then + applybasemethod("initializehashes",tfmdata) + local collectlookups = otf.collectlookups + local rawdata = tfmdata.shared.rawdata + local properties = tfmdata.properties + 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 + -- + -- if basesubstitutions then + -- for feature, data in next, basesubstitutions do + -- local value = features[feature] + -- if value then + -- local validlookups, lookuplist = collectlookups(rawdata,feature,script,language) + -- if validlookups then + -- applybasemethod("preparesubstitutions",tfmdata,feature,value,validlookups,lookuplist) + -- registerbasefeature(feature,value) + -- end + -- end + -- end + -- end + -- if basepositionings then + -- for feature, data in next, basepositionings do + -- local value = features[feature] + -- if value then + -- local validlookups, lookuplist = collectlookups(rawdata,feature,script,language) + -- if validlookups then + -- applybasemethod("preparepositionings",tfmdata,feature,features[feature],validlookups,lookuplist) + -- registerbasefeature(feature,value) + -- end + -- end + -- end + -- end + -- + if basesubstitutions or basepositionings then + local sequences = tfmdata.resources.sequences + for s=1,#sequences do + local sequence = sequences[s] + local sfeatures = sequence.features + if sfeatures then + local order = sequence.order + if order then + for i=1,#order do -- + local feature = order[i] + local value = features[feature] + if value then + local validlookups, lookuplist = collectlookups(rawdata,feature,script,language) + if not validlookups then + -- skip + elseif basesubstitutions and basesubstitutions[feature] then + if trace_preparing then + report_prepare("filtering base %s feature %a for %a with value %a","sub",feature,fullname,value) + end + applybasemethod("preparesubstitutions",tfmdata,feature,value,validlookups,lookuplist) + registerbasefeature(feature,value) + elseif basepositionings and basepositionings[feature] then + if trace_preparing then + report_prepare("filtering base %a feature %a for %a with value %a","pos",feature,fullname,value) + end + applybasemethod("preparepositionings",tfmdata,feature,value,validlookups,lookuplist) + registerbasefeature(feature,value) + end + end + end + end + end + end + end + -- + registerbasehash(tfmdata) + end + if trace_preparing then + report_prepare("preparation time is %0.3f seconds for %a",os.clock()-starttime,fullname) + end + end +end + +registerotffeature { + name = "features", + description = "features", + default = true, + initializers = { + -- position = 1, -- after setscript (temp hack ... we need to force script / language to 1 + base = featuresinitializer, + } +} + +-- independent : collect lookups independently (takes more runtime ... neglectable) +-- shared : shares lookups with node mode (takes more memory unless also a node mode variant is used ... noticeable) + +directives.register("fonts.otf.loader.basemethod", function(v) + if basemethods[v] then + basemethod = v + end +end) -- cgit v1.2.3