summaryrefslogtreecommitdiff
path: root/tex/context/base/font-def.lua
diff options
context:
space:
mode:
Diffstat (limited to 'tex/context/base/font-def.lua')
-rw-r--r--tex/context/base/font-def.lua477
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 )