summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/context/base/font-def.lua
diff options
context:
space:
mode:
authorTaco Hoekwater <taco@elvenkind.com>2008-06-12 10:42:53 +0000
committerTaco Hoekwater <taco@elvenkind.com>2008-06-12 10:42:53 +0000
commit0d01365d53c456d246da0ca1f0b3cd9868f02b35 (patch)
tree01a655c8028e17cfb371456b299c1848fe08c05b /Master/texmf-dist/tex/context/base/font-def.lua
parent44f3714442da07fdfc36a7f2a8dcd5d4294c5d26 (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.lua684
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