diff options
Diffstat (limited to 'tex/context/base/font-def.lua')
-rw-r--r-- | tex/context/base/font-def.lua | 477 |
1 files changed, 477 insertions, 0 deletions
diff --git a/tex/context/base/font-def.lua b/tex/context/base/font-def.lua new file mode 100644 index 000000000..cb8e6f75b --- /dev/null +++ b/tex/context/base/font-def.lua @@ -0,0 +1,477 @@ +if not modules then modules = { } end modules ['font-def'] = { + version = 1.001, + comment = "companion to font-ini.tex", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +-- check reuse of lmroman1o-regular vs lmr10 + +--[[ldx-- +<p>Here we deal with defining fonts. We do so by intercepting the +default loader that only handles <l n='tfm'/>.</p> +--ldx]]-- + +fonts = fonts or { } +fonts.define = fonts.define or { } +fonts.tfm = fonts.tfm or { } +fonts.vf = fonts.vf or { } +fonts.used = fonts.used or { } + +fonts.tfm.version = 1.01 +fonts.tfm.cache = containers.define("fonts", "tfm", fonts.tfm.version, false) + +--[[ldx-- +<p>Choosing a font by name and specififying its size is only part of the +game. In order to prevent complex commands, <l n='xetex'/> introduced +a method to pass feature information as part of the font name. At the +risk of introducing nasty parsing and compatinility problems, this +syntax was expanded over time.</p> + +<p>For the sake of users who have defined fonts using that syntax, we +will support it, but we will provide additional methods as well. +Normally users will not use this direct way, but use a more abstract +interface.</p> + --ldx]]-- + +--~ name, kind, features = fonts.features.split_xetex("blabla / B : + lnum ; foo = bar ; - whatever ; whow ; + hans ; test = yes") + +fonts.define.method = 3 -- 1: tfm 2: tfm and if not then afm 3: afm and if not then tfm +fonts.define.auto_afm = true +fonts.define.auto_otf = true +fonts.define.specify = { } +fonts.define.splitsymbols = "" +fonts.define.methods = fonts.define.methods or { } + +fonts.tfm.fonts = fonts.tfm.fonts or { } +fonts.tfm.readers = fonts.tfm.readers or { } +fonts.tfm.internalized = fonts.tfm.internalized or { } -- internal tex numbers +fonts.tfm.id = fonts.tfm.id or { } -- font data, maybe use just fonts.ids (faster lookup) + +fonts.tfm.readers.sequence = { 'otf', 'ttf', 'afm', 'tfm' } + +--[[ldx-- +<p>We hardly gain anything when we cache the final (pre scaled) +<l n='tfm'/> table. But it can be handy for debugging.</p> +--ldx]]-- + +fonts.version = 1.05 +fonts.cache = containers.define("fonts", "def", fonts.version, false) + +--[[ldx-- +<p>We can prefix a font specification by <type>name:</type> or +<type>file:</type>. The first case will result in a lookup in the +synonym table.</p> + +<typing> +[ name: | file: ] identifier [ separator [ specification ] ] +</typing> + +<p>The following function split the font specification into components +and prepares a table that will move along as we proceed.</p> +--ldx]]-- + +function fonts.define.analyze(name, size, id) + local specification = name or 'unknown' + local lookup, rest = name:match("^(.-):(.+)$") + local sub = "" + if lookup == 'file' or lookup == 'name' then + name = rest + else + lookup = 'file' + end + local font, method, detail = name:match("^(.-)(["..fonts.define.splitsymbols.."])(.+)$") + if method and detail then + name = font + else + method, detail = "", "" + end + local mainfont, subfont = name:match("^(.*-)(%(.*-)(%)$") + if mainfont and subfont then + name, sub = mainfont, subfont + end + size = size or (65536*10) + return { + lookup = lookup, -- forced type + specification = specification, -- full specification + size = size, -- size in scaled points or -1000*n + name = name, -- font or filename + sub = sub, -- subfont (eg in ttc) + method = method, -- specification method + detail = detail, -- specification + resolved = "", -- resolved font name + forced = "", -- forced loader + id = id, -- font id + features = { }, -- preprocessed features + -- hash = nil + -- filename = nil, + -- encoding = nil, + -- format = nil, + } +end + +function fonts.define.register_split(symbol,action) + fonts.define.splitsymbols = fonts.define.splitsymbols .. "%" .. symbol + fonts.define.specify[symbol] = action +end + +--[[ldx-- +<p>A unique hash value is generated by:</p> +--ldx]]-- + +function fonts.tfm.hash_features(specification) + if specification.features then + local normal = specification.features.normal + if not table.is_empty(normal) then + local t = { } + for _, v in pairs(table.sortedkeys(normal)) do + t[#t+1] = v .. '=' .. tostring(normal[v]) + end + return table.concat(t,"+") + end + end + return "unknown" +end + +function fonts.tfm.hash_instance(specification) + if not specification.hash then + specification.hash = fonts.tfm.hash_features(specification) + end + return specification.hash .. ' @ ' .. tostring(specification.size) +end + +--[[ldx-- +<p>We can resolve the filename using the next function:</p> +--ldx]]-- + +function fonts.define.resolve(specification) + if specification.lookup == 'name' then + specification.resolved, specification.sub = fonts.names.resolve(specification.name,specification.sub) + if specification.resolved then + specification.forced = file.extname(specification.resolved) + specification.name = file.removesuffix(specification.resolved) + end + elseif specification.lookup == 'file' then + specification.forced = file.extname(specification.name) + specification.name = file.removesuffix(specification.name) + end + if specification.forced == "" then + specification.forced = nil + end + specification.hash = specification.name .. ' @ ' .. fonts.tfm.hash_features(specification) + if specification.sub and specification.sub ~= "" then + specification.hash = specification.sub .. ' @ ' .. specification.hash + end + return specification +end + +--[[ldx-- +<p>The main read function either uses a forced reader (as determined by +a lookup) or tries to resolve the name using the list of readers.</p> + +<p>We need to cache when possible. We do cache raw tfm data (from <l +n='tfm'/>, <l n='afm'/> or <l n='otf'/>). After that we can cache based +on specificstion (name) and size, that is, <l n='tex'/> only needs a number +for an already loaded fonts. However, it may make sense to cache fonts +before they're scaled as well (store <l n='tfm'/>'s with applied methods +and features). However, there may be a relation between the size and +features (esp in virtual fonts) so let's not do that now.</p> + +<p>Watch out, here we do load a font, but we don't prepare the +specification yet.</p> +--ldx]]-- + +function fonts.tfm.read(specification) + garbagecollector.push() + input.start_timing(fonts) + local hash = fonts.tfm.hash_instance(specification) + local tfmtable = fonts.tfm.fonts[hash] -- hashes by size ! + if not tfmtable then + if specification.forced and specification.forced ~= "" then + tfmtable = fonts.tfm.readers[specification.forced](specification) + else + for _, reader in ipairs(fonts.tfm.readers.sequence) do + if fonts.tfm.readers[reader] then -- not really needed + tfmtable = fonts.tfm.readers[reader](specification) + if tfmtable then break end + end + end + end + if tfmtable then + if tfmtable.filename and fonts.dontembed[tfmtable.filename] then + tfmtable.embedding = "no" + else + tfmtable.embedding = "subset" + end + end + fonts.tfm.fonts[hash] = tfmtable + end + input.stop_timing(fonts) + garbagecollector.pop() + return tfmtable +end + +--[[ldx-- +<p>For virtual fonts we need a slightly different approach:</p> +--ldx]]-- + +function fonts.tfm.read_and_define(name,size) -- no id + local specification = fonts.define.analyze(name,size,nil) + if specification.method and fonts.define.specify[specification.method] then + specification = fonts.define.specify[specification.method](specification) + end + specification = fonts.define.resolve(specification) + local hash = fonts.tfm.hash_instance(specification) + local id = fonts.tfm.internalized[hash] + if not id then + local fontdata = fonts.tfm.read(specification) + id = font.define(fontdata) + fonts.tfm.id[id] = fontdata + fonts.tfm.internalized[hash] = id + end + return fonts.tfm.id[id], id +end + +--[[ldx-- +<p>A naive callback could be the following:</p> + +<code> +callback.register('define_font', function(name,size,id) + return fonts.define.read(fonts.define.resolve(fonts.define.analyze(name,size,id))) +end) +</code> +--ldx]]-- + + +--[[ldx-- +<p>Next follow the readers. This code was written while <l n='luatex'/> +evolved. Each one has its own way of dealing with its format.</p> +--ldx]]-- + +function fonts.tfm.readers.opentype(specification,suffix,what) + if fonts.define.auto_otf then + local fullname, tfmtable = nil, nil + fullname = input.findbinfile(texmf.instance,specification.name,suffix) + if fullname and fullname ~= "" then + specification.filename, specification.format = fullname, what + tfmtable = fonts.tfm.read_from_open_type(specification) + fonts.logger.save(tfmtable,suffix,specification) + end + return tfmtable + else + return nil + end +end + +function fonts.tfm.readers.otf(specification) return fonts.tfm.readers.opentype(specification,"otf","opentype") end +function fonts.tfm.readers.ttf(specification) return fonts.tfm.readers.opentype(specification,"ttf","truetype") end +function fonts.tfm.readers.ttc(specification) return fonts.tfm.readers.opentype(specification,"ttf","truetype") end -- !! + +function fonts.tfm.readers.afm(specification) + local fullname, tfmtable = nil, nil + if fonts.define.method == 2 then + fullname = input.findbinfile(texmf.instance,specification.name,"ofm") -- ? + if not (fullname and fullname ~= "") then + specification.filename = fullname + tfmtable = fonts.tfm.read_from_afm(specification) + fonts.logger.save(tfmtable,'afm',specification) + end + elseif fonts.define.method == 3 then +-- maybe also findbinfile here + if fonts.define.auto_afm then + tfmtable = fonts.tfm.read_from_afm(specification) + fonts.logger.save(tfmtable,'afm',specification) + end + elseif fonts.define.method == 4 then +-- maybe also findbinfile here + tfmtable = fonts.tfm.read_from_afm(specification) + fonts.logger.save(tfmtable,'afm',specification) + end + return tfmtable +end + +function fonts.tfm.readers.tfm(specification) + local fullname, tfmtable = nil, nil + tfmtable = fonts.tfm.read_from_tfm(specification) + fonts.logger.save(tfmtable,'tfm',specification) + return tfmtable +end + +--[[ldx-- +<p>So far we haven't really dealt with features (or whatever we want +to pass along with the font definition. We distinguish the following +situations:</p> + +<code> +name:xetex like specs +name@virtual font spec +name*context specification +</code> + +<p>Of course one can always define more.</p> +--ldx]]-- + +function fonts.define.specify.predefined(specification) + if specification.detail ~= "" and fonts.define.methods[specification.detail] then + specification.features.vtf = { preset = specification.detail } + end + return specification +end + +fonts.define.register_split("@", fonts.define.specify.predefined) + +function fonts.define.specify.colonized(specification) + local list = { } + if specification.detail and specification.detail ~= "" then + local expanded_features = { } + local function expand(features) + for _,v in pairs(features:split(";")) do + expanded_features[#expanded_features+1] = v + end + end + expand(specification.detail) + for _,v in pairs(expanded_features) do + local a, b = v:match("^%s*(%S+)%s*=%s*(%S+)%s*$") + if a and b then + list[a] = b:is_boolean() + if type(list[a]) == "nil" then + list[a] = b + end + else + local a, b = v:match("^%s*([%+%-]?)%s*(%S+)%s*$") + if a and b then + list[b] = a ~= "-" + end + end + end + end + specification.features.normal = list + return specification +end + +function fonts.tfm.make(specification) + local fvm = fonts.define.methods[specification.features.vtf.preset] + if fvm then + return fvm(specification) + else + return nil + end +end + +fonts.define.register_split(":", fonts.define.specify.colonized) + +fonts.define.specify.context_setups = fonts.define.specify.context_setups or { } + +input.storage.register(false,"fonts/setups", fonts.define.specify.context_setups, "fonts.define.specify.context_setups") + +function fonts.define.specify.preset_context(name,features) + local t = aux.settings_to_hash(features) + for k,v in pairs(t) do + t[k] = v:is_boolean() + if type(t[k]) == "nil" then + t[k] = v + end + end + fonts.define.specify.context_setups[name] = t +end + +function fonts.define.specify.split_context(features) + if fonts.define.specify.context_setups[features] then + return fonts.define.specify.context_setups[features] + else + return fonts.define.specify.preset_context("***",features) + end +end + +function fonts.define.specify.starred(features) + if features.detail and features.detail ~= "" then + features.features.normal = fonts.define.specify.split_context(features.detail) + else + features.features.normal = { } + end + return features +end + +fonts.define.register_split('*',fonts.define.specify.starred) + +--[[ldx-- +<p>We need to check for default features. For this we provide +a helper function.</p> +--ldx]]-- + +function fonts.define.check(features,defaults) + if table.is_empty(features) then + features = table.fastcopy(defaults) -- we could do without copy + else + for k,v in pairs(defaults) do + if features[k] == nil then + features[k] = v + end + end + end + return features +end + +--[[ldx-- +<p>So far the specifyers. Now comes the real definer. Here we cache +based on id's. Here we also intercept the virtual font handler. Since +it evolved stepwise I may rewrite this bit (combine code).</p> + +In the previously defined reader (the one resulting in a <l n='tfm'/> +table) we cached the (scaled) instances. Here we cache them again, but +this time based on id. We could combine this in one cache but this does +not gain much. By the way, passing id's back to in the callback was +introduced later in the development.</p> +--ldx]]-- + +function fonts.define.read(name,size,id) + local specification = fonts.define.analyze(name,size,id) + if specification.method and fonts.define.specify[specification.method] then + specification = fonts.define.specify[specification.method](specification) + end + specification = fonts.define.resolve(specification) + local hash = fonts.tfm.hash_instance(specification) + if true then + local fontdata = containers.read(fonts.cache,hash) -- for tracing purposes + end + local fontdata = fonts.tfm.internalized[hash] + if not fontdata then + if specification.features.vtf and specification.features.vtf.preset then + fontdata = fonts.tfm.make(specification) + else + fontdata = fonts.tfm.read(specification) + end + if true then + fontdata = containers.write(fonts.cache,hash,fontdata) -- for tracing purposes + end + fonts.tfm.id[id] = fontdata + fonts.tfm.internalized[hash] = id + end + return fontdata +end + +--~ table.insert(fonts.tfm.readers.sequence,1,'vtf') + +--~ function fonts.tfm.readers.vtf(specification) +--~ if specification.features.vtf and specification.features.vtf.preset then +--~ return fonts.tfm.make(specification) +--~ else +--~ return nil +--~ end +--~ end + +function fonts.vf.find(name) + if fonts.logger.format(name) == 'tfm' then + return input.findbinfile(texmf.instance,name,"ovf") + else + return "" + end +end + +--[[ldx-- +<p>We overload both the <l n='tfm'/> and <l n='vf'/> readers.</p> +--ldx]]-- + +callback.register('define_font' , fonts.define.read) +callback.register('find_vf_file', fonts.vf.find ) |