if not modules then modules = { } end modules ['font-tfm'] = {
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 next, type = next, type
local match, format = string.match, string.format
local concat, sortedhash = table.concat, table.sortedhash
local idiv = number.idiv
local trace_defining = false trackers.register("fonts.defining", function(v) trace_defining = v end)
local trace_features = false trackers.register("tfm.features", function(v) trace_features = v end)
local report_defining = logs.reporter("fonts","defining")
local report_tfm = logs.reporter("fonts","tfm loading")
local findbinfile = resolvers.findbinfile
local setmetatableindex = table.setmetatableindex
local fonts = fonts
local handlers = fonts.handlers
local helpers = fonts.helpers
local readers = fonts.readers
local constructors = fonts.constructors
local encodings = fonts.encodings
local tfm = constructors.handlers.tfm
tfm.version = 1.000
tfm.maxnestingdepth = 5
tfm.maxnestingsize = 65536*1024
local otf = fonts.handlers.otf
local otfenhancers = otf.enhancers
local tfmfeatures = constructors.features.tfm
local registertfmfeature = tfmfeatures.register
local tfmenhancers = constructors.enhancers.tfm
local registertfmenhancer = tfmenhancers.register
local charcommand = helpers.commands.char
constructors.resolvevirtualtoo = false -- wil be set in font-ctx.lua
fonts.formats.tfm = "type1" -- we need to have at least a value here
fonts.formats.ofm = "type1" -- we need to have at least a value here
--[[ldx--
The next function encapsulates the standard loader as
supplied by .
--ldx]]--
-- this might change: not scaling and then apply features and do scaling in the
-- usual way with dummy descriptions but on the other hand .. we no longer use
-- tfm so why bother
-- ofm directive blocks local path search unless set; btw, in context we
-- don't support ofm files anyway as this format is obsolete
-- we need to deal with nested virtual fonts, but because we load in the
-- frontend we also need to make sure we don't nest too deep (esp when sizes
-- get large)
--
-- (VTITLE Example of a recursion)
-- (MAPFONT D 0 (FONTNAME recurse)(FONTAT D 2))
-- (CHARACTER C A (CHARWD D 1)(CHARHT D 1)(MAP (SETRULE D 1 D 1)))
-- (CHARACTER C B (CHARWD D 2)(CHARHT D 2)(MAP (SETCHAR C A)))
-- (CHARACTER C C (CHARWD D 4)(CHARHT D 4)(MAP (SETCHAR C B)))
--
-- we added the same checks as below to the luatex engine
function tfm.setfeatures(tfmdata,features)
local okay = constructors.initializefeatures("tfm",tfmdata,features,trace_features,report_tfm)
if okay then
return constructors.collectprocessors("tfm",tfmdata,features,trace_features,report_tfm)
else
return { } -- will become false
end
end
local depth = { } -- table.setmetatableindex("number")
-- Normally we just load the tfm data and go on. However there was some demand for
-- loading good old tfm /pfb files where afm files were lacking and even enc files
-- of dubious quality so we now support loading such (often messy) setups too.
--
-- Because such fonts also use (ugly) tweaks achieve some purpose (like swapping
-- accents) we need to delay the unicoding actions till after the features have been
-- applied.
--
-- It must be noted that in ConTeXt we don't expect this to be used at all. Here is
-- example:
--
-- tfm metrics + pfb vector for index + pfb file for shapes
--
-- \font\foo=file:csr10.tfm:reencode=auto;mode=node;liga=yes;kern=yes
--
-- tfm metrics + pfb vector for index + enc file for tfm mapping + pfb file for shapes
--
-- \font\foo=file:csr10.tfm:reencode=csr.enc;mode=node;liga=yes;kern=yes
--
-- tfm metrics + enc file for mapping to tfm + bitmaps shapes
--
-- \font\foo=file:csr10.tfm:reencode=csr.enc;bitmap=yes;mode=node;liga=yes;kern=yes
--
-- One can add features:
--
-- fonts.handlers.otf.addfeature {
-- name = "czechdqcheat",
-- type = "substitution",
-- data = {
-- quotedblright = "csquotedblright",
-- },
-- }
--
-- So "czechdqcheat=yes" is then a valid feature. And yes, it's a cheat.
local read_from_tfm, check_tfm do
local tfmreaders = context and tfm.readers
local loadtfmvf = tfmreaders and tfmreaders.loadtfmvf
local loadtfm = font.read_tfm
local loadvf = font.read_vf
directives.register("fonts.tfm.builtin",function(v)
loadtfmvf = tfmreaders and tfmreaders.loadtfmvf
if v and loadtfm then
loadtfmvf = false
end
end)
read_from_tfm = function(specification)
local filename = specification.filename
local size = specification.size
depth[filename] = (depth[filename] or 0) + 1
if trace_defining then
report_defining("loading tfm file %a at size %s",filename,size)
end
local tfmdata -- not cached, fast enough
if loadtfmvf then
tfmdata = loadtfmvf(filename,size)
else
tfmdata = loadtfm(filename,size)
end
if tfmdata then
local features = specification.features and specification.features.normal or { }
local features = constructors.checkedfeatures("tfm",features)
specification.features.normal = features
-- If reencode returns a new table, we assume that we're doing something
-- special. An 'auto' reencode picks up its vector from the pfb file.
local newtfmdata = (depth[filename] == 1) and tfm.reencode(tfmdata,specification)
if newtfmdata then
tfmdata = newtfmdata
end
local resources = tfmdata.resources or { }
local properties = tfmdata.properties or { }
local parameters = tfmdata.parameters or { }
local shared = tfmdata.shared or { }
--
shared.features = features
shared.resources = resources
--
properties.name = tfmdata.name -- todo: fallback
properties.fontname = tfmdata.fontname -- todo: fallback
properties.psname = tfmdata.psname -- todo: fallback
properties.fullname = tfmdata.fullname -- todo: fallback
properties.filename = specification.filename -- todo: fallback
properties.format = tfmdata.format or fonts.formats.tfm -- better than nothing
properties.usedbitmap = tfmdata.usedbitmap
--
tfmdata.properties = properties
tfmdata.resources = resources
tfmdata.parameters = parameters
tfmdata.shared = shared
--
shared.rawdata = { resources = resources }
shared.features = features
--
-- The next branch is only entered when we have a proper encoded file i.e.
-- unicodes and such. It really nakes no sense to do feature juggling when
-- we have no names and unicodes.
--
if newtfmdata then
--
-- Some opentype processing assumes these to be present:
--
if not resources.marks then
resources.marks = { }
end
if not resources.sequences then
resources.sequences = { }
end
if not resources.features then
resources.features = {
gsub = { },
gpos = { },
}
end
if not tfmdata.changed then
tfmdata.changed = { }
end
if not tfmdata.descriptions then
tfmdata.descriptions = tfmdata.characters
end
--
-- It might be handy to have this:
--
otf.readers.addunicodetable(tfmdata)
--
-- We make a pseudo opentype font, e.g. kerns and ligatures etc:
--
tfmenhancers.apply(tfmdata,filename)
--
-- Now user stuff can kick in.
--
constructors.applymanipulators("tfm",tfmdata,features,trace_features,report_tfm)
--
-- As that can also mess with names and such, we are now ready for finalizing
-- the unicode information. This is a different order that for instance type one
-- (afm) files. First we try to deduce unicodes from already present information.
--
otf.readers.unifymissing(tfmdata)
--
-- Next we fill in the gaps, based on names from teh agl. Probably not much will
-- happen here.
--
fonts.mappings.addtounicode(tfmdata,filename)
--
-- The tounicode data is passed to the backend that constructs the vectors for us.
--
tfmdata.tounicode = 1
local tounicode = fonts.mappings.tounicode
for unicode, v in next, tfmdata.characters do
local u = v.unicode
if u then
v.tounicode = tounicode(u)
end
end
--
-- However, when we use a bitmap font those vectors can't be constructed because
-- that information is not carried with those fonts (there is no name info, nor
-- proper index info, nor unicodes at that end). So, we provide it ourselves.
--
if tfmdata.usedbitmap then
tfm.addtounicode(tfmdata)
end
end
--
shared.processes = next(features) and tfm.setfeatures(tfmdata,features) or nil
--
if size < 0 then
size = idiv(65536 * -size,100)
end
parameters.factor = 1 -- already scaled
parameters.units = 1000 -- just in case
parameters.size = size
parameters.slant = parameters.slant or parameters[1] or 0
parameters.space = parameters.space or parameters[2] or 0
parameters.space_stretch = parameters.space_stretch or parameters[3] or 0
parameters.space_shrink = parameters.space_shrink or parameters[4] or 0
parameters.x_height = parameters.x_height or parameters[5] or 0
parameters.quad = parameters.quad or parameters[6] or 0
parameters.extra_space = parameters.extra_space or parameters[7] or 0
--
constructors.enhanceparameters(parameters) -- official copies for us
--
properties.private = properties.private or tfmdata.private or privateoffset
--
if newtfmdata then
--
-- We do nothing as we assume flat tfm files. It would become real messy
-- otherwise and I don't have something for testing on my system anyway.
--
elseif loadtfmvf then
-- already loaded
local fonts = tfmdata.fonts
if fonts then
for i=1,#fonts do
local font = fonts[i]
local id = font.id
if not id then
local name = font.name
local size = font.size
if name and size then
local data, id = constructors.readanddefine(name,size)
if id then
font.id = id
font.name = nil
font.size = nil
end
end
end
end
end
elseif constructors.resolvevirtualtoo then
fonts.loggers.register(tfmdata,file.suffix(filename),specification) -- strange, why here
local vfname = findbinfile(specification.name, 'ovf')
if vfname and vfname ~= "" then
local vfdata = loadvf(vfname,size)
if vfdata then
local chars = tfmdata.characters
for k,v in next, vfdata.characters do
chars[k].commands = v.commands
end
properties.virtualized = true
tfmdata.fonts = vfdata.fonts
tfmdata.type = "virtual" -- else nested calls with cummulative scaling
local fontlist = vfdata.fonts
local name = file.nameonly(filename)
for i=1,#fontlist do
local n = fontlist[i].name
local s = fontlist[i].size
local d = depth[filename]
s = constructors.scaled(s,vfdata.designsize)
if d > tfm.maxnestingdepth then
report_defining("too deeply nested virtual font %a with size %a, max nesting depth %s",n,s,tfm.maxnestingdepth)
fontlist[i] = { id = 0 }
elseif (d > 1) and (s > tfm.maxnestingsize) then
report_defining("virtual font %a exceeds size %s",n,s)
fontlist[i] = { id = 0 }
else
local t, id = constructors.readanddefine(n,s)
fontlist[i] = { id = id }
end
end
end
end
end
--
-- This is for old times sake (and context specific) so we comment it. It has
-- to do with encoding prefixes (a context naming that was later adopted by
-- the lm/gyre project)
--
-- if not features.encoding then
-- local encoding, filename = match(properties.filename,"^(.-)%-(.*)$")
-- if filename and encoding and encodings.known and encodings.known[encoding] then
-- features.encoding = encoding
-- end
-- end
--
-- Some afterthoughts:
--
properties.haskerns = true
properties.hasligatures = true
properties.hasitalics = true
resources.unicodes = { }
resources.lookuptags = { }
--
depth[filename] = depth[filename] - 1
--
return tfmdata
else
depth[filename] = depth[filename] - 1
end
end
check_tfm = function(specification,fullname) -- we could split up like afm/otf
local foundname = findbinfile(fullname, 'tfm') or ""
if foundname == "" then
foundname = findbinfile(fullname, 'ofm') or "" -- not needed in context
end
if foundname == "" then
foundname = fonts.names.getfilename(fullname,"tfm") or ""
end
if foundname ~= "" then
specification.filename = foundname
specification.format = "ofm"
return read_from_tfm(specification)
elseif trace_defining then
report_defining("loading tfm with name %a fails",specification.name)
end
end
end
readers.check_tfm = check_tfm
function readers.tfm(specification)
local fullname = specification.filename or ""
if fullname == "" then
local forced = specification.forced or ""
if forced ~= "" then
fullname = specification.name .. "." .. forced
else
fullname = specification.name
end
end
return check_tfm(specification,fullname)
end
readers.ofm = readers.tfm
-- The reencoding acts upon the 'reencode' feature which can have values 'auto' or
-- an enc file. You can also specify a 'pfbfile' feature (but it defaults to the
-- tfm filename) and a 'bitmap' feature. When no enc file is givven (auto) we will
-- get the vectors from the pfb file.
do
local outfiles = { }
local tfmcache = table.setmetatableindex(function(t,tfmdata)
local id = font.define(tfmdata)
t[tfmdata] = id
return id
end)
local encdone = table.setmetatableindex("table")
function tfm.reencode(tfmdata,specification)
local features = specification.features
if not features then
return
end
local features = features.normal
if not features then
return
end
local tfmfile = file.basename(tfmdata.name)
local encfile = features.reencode -- or features.enc
local pfbfile = features.pfbfile -- or features.pfb
local bitmap = features.bitmap -- or features.pk
if not encfile then
return
end
local pfbfile = outfiles[tfmfile]
if pfbfile == nil then
if bitmap then
pfbfile = false
elseif type(pfbfile) ~= "string" then
pfbfile = tfmfile
end
if type(pfbfile) == "string" then
pfbfile = file.addsuffix(pfbfile,"pfb")
-- pdf.mapline(tfmfile .. "<" .. pfbfile)
report_tfm("using type1 shapes from %a for %a",pfbfile,tfmfile)
else
report_tfm("using bitmap shapes for %a",tfmfile)
pfbfile = false -- use bitmap
end
outfiles[tfmfile] = pfbfile
end
local encoding = false
local vector = false
if type(pfbfile) == "string" then
local pfb = constructors.handlers.pfb
if pfb and pfb.loadvector then
local v, e = pfb.loadvector(pfbfile)
if v then
vector = v
end
if e then
encoding = e
end
end
end
if type(encfile) == "string" and encfile ~= "auto" then
encoding = fonts.encodings.load(file.addsuffix(encfile,"enc"))
if encoding then
encoding = encoding.vector
end
end
if not encoding then
report_tfm("bad encoding for %a, quitting",tfmfile)
return
end
local unicoding = fonts.encodings.agl and fonts.encodings.agl.unicodes
local virtualid = tfmcache[tfmdata]
local tfmdata = table.copy(tfmdata) -- good enough for small fonts
local characters = { }
local originals = tfmdata.characters
local indices = { }
local parentfont = { "font", 1 }
local private = tfmdata.privateoffset or constructors.privateoffset
local reported = encdone[tfmfile][encfile]
-- create characters table
local backmap = vector and table.swapped(vector)
local done = { } -- prevent duplicate
for index, name in sortedhash(encoding) do -- predictable order
local unicode = unicoding[name]
local original = originals[index]
if original then
if unicode then
original.unicode = unicode
else
unicode = private
private = private + 1
if not reported then
report_tfm("glyph %a in font %a with encoding %a gets unicode %U",name,tfmfile,encfile,unicode)
end
end
characters[unicode] = original
indices[index] = unicode
original.name = name -- so one can lookup weird names
if backmap then
original.index = backmap[name]
else -- probably bitmap
original.commands = { parentfont, charcommand[index] } -- or "slot"
original.oindex = index
end
done[name] = true
elseif not done[name] then
report_tfm("bad index %a in font %a with name %a",index,tfmfile,name)
end
end
encdone[tfmfile][encfile] = true
-- redo kerns and ligatures
for k, v in next, characters do
local kerns = v.kerns
if kerns then
local t = { }
for k, v in next, kerns do
local i = indices[k]
if i then
t[i] = v
end
end
v.kerns = next(t) and t or nil
end
local ligatures = v.ligatures
if ligatures then
local t = { }
for k, v in next, ligatures do
local i = indices[k]
if i then
t[i] = v
v.char = indices[v.char]
end
end
v.ligatures = next(t) and t or nil
end
end
-- wrap up
tfmdata.fonts = { { id = virtualid } }
tfmdata.characters = characters
tfmdata.fullname = tfmdata.fullname or tfmdata.name
tfmdata.psname = file.nameonly(pfbfile or tfmdata.name)
tfmdata.filename = pfbfile
tfmdata.encodingbytes = 2
-- tfmdata.format = bitmap and "type3" or "type1"
tfmdata.format = "type1"
tfmdata.tounicode = 1
tfmdata.embedding = "subset"
tfmdata.usedbitmap = bitmap and virtualid
tfmdata.private = private
return tfmdata
end
end
-- This code adds a ToUnicode vector for bitmap fonts. We don't bother about
-- ranges because we have small fonts. it works ok with acrobat but fails with
-- the other viewers (they get confused by the bitmaps I guess).
do
local template = [[
/CIDInit /ProcSet findresource begin
12 dict begin
begincmap
/CIDSystemInfo << /Registry (TeX) /Ordering (bitmap-%s) /Supplement 0 >> def
/CMapName /TeX-bitmap-%s def
/CMapType 2 def
1 begincodespacerange
<00>
endcodespacerange
%s beginbfchar
%s
endbfchar
endcmap
CMapName currentdict /CMap defineresource pop end
end
end
]]
local flushstreamobject = lpdf and lpdf.flushstreamobject -- context
local setfontattributes = lpdf and lpdf.setfontattributes -- context
if not flushstreamobject then
flushstreamobject = function(data)
return pdf.obj { immediate = true, type = "stream", string = data } -- generic
end
end
if not setfontattributes then
setfontattributes = function(id,data)
return pdf.setfontattributes(id,data) -- generic
end
end
function tfm.addtounicode(tfmdata)
local id = tfmdata.usedbitmap
local map = { }
local char = { } -- no need for range, hardly used
for k, v in next, tfmdata.characters do
local index = v.oindex
local tounicode = v.tounicode
if index and tounicode then
map[index] = tounicode
end
end
for k, v in sortedhash(map) do
char[#char+1] = format("<%02X> <%s>",k,v)
end
char = concat(char,"\n")
local stream = format(template,id,id,#char,char)
local reference = flushstreamobject(stream,nil,true)
setfontattributes(id,format("/ToUnicode %i 0 R",reference))
end
end
-- Now we implement the regular features handlers. We need to convert the
-- tfm specific structures to opentype structures. In basemode they are
-- converted back so that is a bit of a waste but it's fast enough.
do
local everywhere = { ["*"] = { ["*"] = true } } -- or: { ["*"] = { "*" } }
local noflags = { false, false, false, false }
local function enhance_normalize_features(data)
local ligatures = setmetatableindex("table")
local kerns = setmetatableindex("table")
local characters = data.characters
for u, c in next, characters do
local l = c.ligatures
local k = c.kerns
if l then
ligatures[u] = l
for u, v in next, l do
l[u] = { ligature = v.char }
end
c.ligatures = nil
end
if k then
kerns[u] = k
for u, v in next, k do
k[u] = v -- { v, 0 }
end
c.kerns = nil
end
end
for u, l in next, ligatures do
for k, v in next, l do
local vl = v.ligature
local dl = ligatures[vl]
if dl then
for kk, vv in next, dl do
v[kk] = vv -- table.copy(vv)
end
end
end
end
local features = {
gpos = { },
gsub = { },
}
local sequences = {
-- only filled ones
}
if next(ligatures) then
features.gsub.liga = everywhere
data.properties.hasligatures = true
sequences[#sequences+1] = {
features = {
liga = everywhere,
},
flags = noflags,
name = "s_s_0",
nofsteps = 1,
order = { "liga" },
type = "gsub_ligature",
steps = {
{
coverage = ligatures,
},
},
}
end
if next(kerns) then
features.gpos.kern = everywhere
data.properties.haskerns = true
sequences[#sequences+1] = {
features = {
kern = everywhere,
},
flags = noflags,
name = "p_s_0",
nofsteps = 1,
order = { "kern" },
type = "gpos_pair",
steps = {
{
format = "kern",
coverage = kerns,
},
},
}
end
data.resources.features = features
data.resources.sequences = sequences
data.shared.resources = data.shared.resources or resources
end
registertfmenhancer("normalize features", enhance_normalize_features)
registertfmenhancer("check extra features", otfenhancers.enhance)
end
-- As with type one (afm) loading, we just use the opentype ones:
registertfmfeature {
name = "mode",
description = "mode",
initializers = {
base = otf.modeinitializer,
node = otf.modeinitializer,
}
}
registertfmfeature {
name = "features",
description = "features",
default = true,
initializers = {
base = otf.basemodeinitializer,
node = otf.nodemodeinitializer,
},
processors = {
node = otf.featuresprocessor,
}
}