diff options
author | Taco Hoekwater <taco@elvenkind.com> | 2008-06-12 10:42:53 +0000 |
---|---|---|
committer | Taco Hoekwater <taco@elvenkind.com> | 2008-06-12 10:42:53 +0000 |
commit | 0d01365d53c456d246da0ca1f0b3cd9868f02b35 (patch) | |
tree | 01a655c8028e17cfb371456b299c1848fe08c05b /Master/texmf-dist/tex/context/base/font-def.lua | |
parent | 44f3714442da07fdfc36a7f2a8dcd5d4294c5d26 (diff) |
ConTeXt release 2008.05.21
git-svn-id: svn://tug.org/texlive/trunk@8691 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/tex/context/base/font-def.lua')
-rw-r--r-- | Master/texmf-dist/tex/context/base/font-def.lua | 684 |
1 files changed, 684 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/context/base/font-def.lua b/Master/texmf-dist/tex/context/base/font-def.lua new file mode 100644 index 00000000000..af6f5f39460 --- /dev/null +++ b/Master/texmf-dist/tex/context/base/font-def.lua @@ -0,0 +1,684 @@ +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) -- better in font-tfm + +--[[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.specify or { } +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) + name = name or 'unknown' + local specification = name + local lookup, rest = specification: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 t = { } + local normal = specification.features.normal + if not table.is_empty(normal) then + for _, v in pairs(table.sortedkeys(normal)) do + if v ~= "number" then + t[#t+1] = v .. '=' .. tostring(normal[v]) + end + end + end + local vtf = specification.features.vtf + if not table.is_empty(vtf) then + for _, v in pairs(table.sortedkeys(vtf)) do + t[#t+1] = v .. '=' .. tostring(vtf[v]) + end + end + if next(t) then + 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 + +fonts.designsizes = { } + +--[[ldx-- +<p>In principle we can share tfm tables when we are in node for a font, but then +we need to define a font switch as an id/attr switch which is no fun, so in that +case users can best use dynamic features ... so, we will not use that speedup. Okay, +when we get rid of base mode we can optimize even further by sharing, but then we +loose our testcases for <l n='luatex'/>.</p> +--ldx]]-- + +function fonts.tfm.hash_instance(specification,force) + local hash, size = specification.hash, specification.size + if force or not hash then + hash = fonts.tfm.hash_features(specification) + specification.hash = hash + end + if size < 1000 and fonts.designsizes[hash] then + size = fonts.tfm.scaled(size, fonts.designsizes[hash]) + specification.size = size + end + return hash .. ' @ ' .. tostring(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.starttiming(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) + if not tfmtable then + logs.error("define font",string.format("forced type %s of %s not found",specification.forced,specification.name)) + end + else + for _, reader in ipairs(fonts.tfm.readers.sequence) do + if fonts.tfm.readers[reader] then -- not really needed + if fonts.trace then + logs.report("define font",string.format("trying type %s for %s with file %s",reader,specification.name,specification.filename or "unknown")) + end + 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 + fonts.tfm.fonts[hash] = tfmtable + fonts.designsizes[specification.hash] = tfmtable.designsize -- we only know this for sure after loading once + --~ tfmtable.mode = specification.features.normal.mode or "base" + end + end + input.stoptiming(fonts) + garbagecollector.pop() + if not tfmtable then + logs.error("define font",string.format("font with name %s is not found",specification.name)) + end + 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) + if fontdata then + if not fonts.tfm.internalized[hash] then + id = font.define(fontdata) + fonts.tfm.id[id] = fontdata + fonts.tfm.internalized[hash] = id + if fonts.trace then + logs.report("define font", string.format("loading at 1 id %s, hash: %s",id,hash)) + end + else + id = fonts.tfm.internalized[hash] + end + else + id = 0 -- signal + end + 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) or "" + if fullname == "" then + local fb = fonts.names.old_to_new[specification.name] + if fb then + fullname = input.findbinfile(texmf.instance,fb,suffix) or "" + end + end + if fullname == "" then + local fb = fonts.names.new_to_old[specification.name] + if fb then + fullname = input.findbinfile(texmf.instance,fb,suffix) or "" + end + end + if fullname ~= "" then + specification.filename, specification.format = fullname, what -- hm, so we do set the filename, then + tfmtable = fonts.tfm.read_from_open_type(specification) -- we need to do it for all matches / todo + 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,method) + local fullname, tfmtable = nil, nil + method = method or fonts.define.method + if method == 2 then + fullname = input.findbinfile(texmf.instance,specification.name,"ofm") or "" + if fullname == "" then + tfmtable = fonts.tfm.read_from_afm(specification) + else -- redundant + specification.filename = fullname + tfmtable = fonts.tfm.read_from_tfm(specification) + end + elseif method == 3 then -- maybe also findbinfile here + if fonts.define.auto_afm then + tfmtable = fonts.tfm.read_from_afm(specification) + end + elseif method == 4 then -- maybe also findbinfile here + tfmtable = fonts.tfm.read_from_afm(specification) + end + return tfmtable +end + +function fonts.tfm.readers.tfm(specification) + local fullname, tfmtable = nil, nil + tfmtable = fonts.tfm.read_from_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> +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) -- xetex mode + 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 { } +fonts.define.specify.context_numbers = fonts.define.specify.context_numbers or { } +fonts.define.specify.synonyms = fonts.define.specify.synonyms or { } + +input.storage.register(false,"fonts/setups" , fonts.define.specify.context_setups , "fonts.define.specify.context_setups" ) +input.storage.register(false,"fonts/numbers", fonts.define.specify.context_numbers, "fonts.define.specify.context_numbers") + +--~ local t = aux.settings_to_hash(features) +--~ for k,v in pairs(t) do +--~ k = synonyms[k] or k +--~ t[k] = v:is_boolean() +--~ if type(t[k]) == "nil" then +--~ t[k] = v +--~ end +--~ end + +fonts.triggers = fonts.triggers or { } + +function fonts.define.specify.preset_context(name,parent,features) + if features == "" then + if parent:find("=") then + features = parent + parent = "" + end + end + local fds = fonts.define.specify + local setups, numbers, synonyms = fds.context_setups, fds.context_numbers, fds.synonyms + local number = (setups[name] and setups[name].number) or 0 + local t = (features == "" and { }) or fonts.otf.meanings.normalize(aux.settings_to_hash(features)) + -- todo: synonyms, and not otf bound + if parent ~= "" then + for p in parent:gmatch("[^, ]+") do + local s = setups[p] + if s then + for k,v in pairs(s) do + if t[k] == nil then + t[k] = v + end + end + end + end + end + -- these are auto set so in order to prevent redundant definitions + -- we need to preset them (we hash the features and adding a default + -- setting during initialization may result in a different hash) + local default = fonts.otf.features.default + for k,v in pairs(fonts.triggers) do + if type(t[v]) == "nil" then + local vv = default[v] + if vv then t[v] = vv end + end + end + -- sparse 'm so that we get a better hash and less test (experimental + -- optimization) + local tt = { } + for k,v in pairs(t) do + if v then tt[k] = v end + end + -- needed for dynamic features + if number == 0 then + numbers[#numbers+1] = name + tt.number = #numbers + else + tt.number = number + end + setups[name] = tt +end + +--~ function fonts.define.specify.context_number(name) +--~ local s = fonts.define.specify.context_setups[name] +--~ return (s and s.number) or -1 +--~ end + +do + + -- here we clone features according to languages + + local default = 0 + local setups = fonts.define.specify.context_setups + local numbers = fonts.define.specify.context_numbers + + function fonts.define.specify.context_number(name) + local t = setups[name] + if not t then + return default + elseif t.auto then + local lng = tonumber(tex.language) + local tag = name .. ":" .. lng + local s = setups[tag] + if s then + return s.number or default + else + local script, language = languages.association(lng) + if t.script ~= script or t.language ~= language then + local s = table.fastcopy(t) + local n = #numbers + 1 + setups[tag] = s + numbers[n] = tag + s.number = n + s.script = script + s.language = language + return n + else + setups[tag] = t + return t.number or default + end + end + else + return t.number or default + end + end + +end + +function fonts.define.specify.context_tostring(name,kind,separator,yes,no,strict,omit) + return aux.hash_to_string(table.merged(fonts[kind].features.default or {},fonts.define.specify.context_setups[name] or {}),separator,yes,no,strict,omit) +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) -- nb adapts features ! + local done = false + if table.is_empty(features) then + features, done = table.fastcopy(defaults), true + else + for k,v in pairs(defaults) do + if features[k] == nil then + features[k], done = v, true + end + end + end + return features, done -- done signals a change +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] -- id + 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) + if fontdata then + fonts.tfm.check_virtual_id(fontdata) + end + end + if true then + --~ fontdata = containers.write(fonts.cache(),hash,fontdata) -- for tracing purposes + end + if not fonts.tfm.internalized[hash] then + fonts.tfm.id[id] = fontdata + fonts.tfm.internalized[hash] = id + if fonts.trace then + logs.report("define font", string.format("loading at 2 id %s, hash: %s",id,hash)) + end + else + fontdata = fonts.tfm.internalized[hash] + end + end + if not fontdata then + logs.error("define font", string.format("unknown font %s, loading aborted",specification.name)) + elseif fonts.trace and type(fontdata) == "table" then + logs.report("define font",string.format("using %s font with id %s, n:%s s:%s b:%s e:%s p:%s f:%s", + fontdata.type or "unknown", + id or "?", + fontdata.name or "?", + fontdata.size or "default", + fontdata.encodingbytes or "?", + fontdata.encodingname or "unicode", + fontdata.fullname or "?", + file.basename(fontdata.filename or "?"))) + 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) + name = file.removesuffix(file.basename(name)) + if fonts.tfm.resolve_vf then + local format = fonts.logger.format(name) + if format == 'tfm' or format == 'ofm' then + if fonts.trace then + logs.report("define font",string.format("locating vf for %s",name)) + end + return input.findbinfile(texmf.instance,name,"ovf") + else + if fonts.trace then + logs.report("define font",string.format("vf for %s is already taken care of",name)) + end + return nil -- "" + end + else + if fonts.trace then + logs.report("define font",string.format("locating vf for %s",name)) + end + return input.findbinfile(texmf.instance,name,"ovf") + 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 ) -- not that relevant any more |