if not modules then modules = { } end modules ['font-afm'] = { 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" } --[[ldx--
Some code may look a bit obscure but this has to do with the
fact that we also use this code for testing and much code evolved
in the transition from
The following code still has traces of intermediate font support where we handles font encodings. Eventually font encoding goes away.
--ldx]]-- local trace_features = false trackers.register("afm.features", function(v) trace_features = v end) local trace_indexing = false trackers.register("afm.indexing", function(v) trace_indexing = v end) local trace_loading = false trackers.register("afm.loading", function(v) trace_loading = v end) local format, match, gmatch, lower = string.format, string.match, string.gmatch, string.lower fonts = fonts or { } fonts.afm = fonts.afm or { } local afm = fonts.afm local tfm = fonts.tfm afm.version = 1.400 -- incrementing this number one up will force a re-cache afm.syncspace = true -- when true, nicer stretch values afm.enhance_data = true -- best leave this set to true afm.features = { } afm.features.aux = { } afm.features.data = { } afm.features.list = { } afm.features.default = { } afm.cache = containers.define("fonts", "afm", afm.version, true) --[[ldx--We start with the basic reader which we give a name similar to the
built in
We cache files. Caching is taken care of in the loader. We cheat a bit by adding ligatures and kern information to the afm derived data. That way we can set them faster when defining a font.
--ldx]]-- function afm.load(filename) -- hm, for some reasons not resolved yet filename = resolvers.find_file(filename,'afm') or "" if filename ~= "" then local name = file.removesuffix(file.basename(filename)) local data = containers.read(afm.cache(),name) local size = lfs.attributes(filename,"size") or 0 if not data or data.verbose ~= fonts.verbose or data.size ~= size then logs.report("load afm", "reading %s",filename) data = afm.read_afm(filename) if data then -- data.luatex = data.luatex or { } logs.report("load afm", "unifying %s",filename) afm.unify(data,filename) if afm.enhance_data then logs.report("load afm", "add ligatures") afm.add_ligatures(data,'ligatures') -- easier this way logs.report("load afm", "add tex-ligatures") afm.add_ligatures(data,'texligatures') -- easier this way logs.report("load afm", "add extra kerns") afm.add_kerns(data) -- faster this way end data.size = size data.verbose = fonts.verbose logs.report("load afm","saving: %s in cache",name) data = containers.write(afm.cache(), name, data) data = containers.read(afm.cache(),name) end end return data else return nil end end function afm.unify(data, filename) local unicodevector = fonts.enc.load('unicode').hash local glyphs, indices, unicodes, names = { }, { }, { }, { } local verbose, private = fonts.verbose, fonts.private for name, blob in next, data.characters do local code = unicodevector[name] -- or characters.name_to_unicode[name] if not code then local u = match(name,"^uni(%x+)$") code = u and tonumber(u,16) if not code then code = private private = private + 1 logs.report("afm glyph", "assigning private slot U+%04X for unknown glyph name %s", code, name) end end local index = blob.index unicodes[name] = code indices[code] = index glyphs[index] = blob names[name] = index blob.name = name if verbose then local bu = blob.unicode if not bu then blob.unicode = code elseif type(bu) == "table" then bu[#bu+1] = code else blob.unicode = { bu, code } end else blob.index = nil end end data.glyphs = glyphs data.characters = nil local luatex = data.luatex luatex.filename = luatex.filename or file.removesuffix(file.basename(filename)) luatex.unicodes = unicodes -- name to unicode luatex.indices = indices -- unicode to index luatex.marks = { } -- todo luatex.names = names -- name to index luatex.private = private end --[[ldx--These helpers extend the basic table with extra ligatures, texligatures and extra kerns. This saves quite some lookups later.
--ldx]]-- function afm.add_ligatures(afmdata,ligatures) local glyphs, luatex = afmdata.glyphs, afmdata.luatex local indices, unicodes, names = luatex.indices, luatex.unicodes, luatex.names for k,v in next, characters[ligatures] do -- main characters table local one = glyphs[names[k]] if one then for _, b in next, v do two, three = b[1], b[2] if two and three and names[two] and names[three] then local ol = one[ligatures] if ol then if not ol[two] then -- was one.ligatures ... bug ol[two] = three end else one[ligatures] = { [two] = three } end end end end end end --[[ldx--We keep the extra kerns in separate kerning tables so that we can use them selectively.
--ldx]]-- function afm.add_kerns(afmdata) local glyphs = afmdata.glyphs local names = afmdata.luatex.names local uncomposed = characters.uncomposed local function do_it_left(what) for index, glyph in next, glyphs do local kerns = glyph.kerns if kerns then local extrakerns = glyph.extrakerns or { } for complex, simple in next, uncomposed[what] do if names[compex] then local ks = kerns[simple] if ks and not kerns[complex] then extrakerns[complex] = ks end end end if next(extrakerns) then glyph.extrakerns = extrakerns end end end end local function do_it_copy(what) for complex, simple in next, uncomposed[what] do local c = glyphs[names[complex]] if c then -- optional local s = glyphs[names[simple]] if s then if not c.kerns then c.extrakerns = s.kerns or { } end if s.extrakerns then local extrakerns = c.extrakerns or { } for k, v in next, s.extrakerns do extrakerns[k] = v end if next(extrakerns) then s.extrakerns = extrakerns end end end end end end -- add complex with values of simplified when present do_it_left("left") do_it_left("both") -- copy kerns from simple char to complex char unless set do_it_copy("both") do_it_copy("right") end --[[ldx--The copying routine looks messy (and is indeed a bit messy).
--ldx]]-- -- once we have otf sorted out (new format) we can try to make the afm -- cache similar to it (similar tables) function afm.add_dimensions(data) -- we need to normalize afm to otf i.e. indexed table instead of name if data then for index, glyph in next, data.glyphs do local bb = glyph.boundingbox if bb then local ht, dp = bb[4], -bb[2] if ht == 0 or ht < 0 then -- no need to set it and no negative heights, nil == 0 else glyph.height = ht end if dp == 0 or dp < 0 then -- no negative depths and no negative depths, nil == 0 else glyph.depth = dp end end end end end function afm.copy_to_tfm(data) if data then local glyphs = data.glyphs if glyphs then local metadata, luatex = data.metadata, data.luatex local unicodes, indices = luatex.unicodes, luatex.indices local characters, parameters, descriptions = { }, { }, { } local tfm = { characters = characters, parameters = parameters, descriptions = descriptions, indices = indices, unicodes = unicodes, luatex = luatex, } for u, i in next, indices do local d = glyphs[i] characters[u] = { } descriptions[u] = d end tfm.encodingbytes = metadata.encodingbytes or 2 tfm.fullname = metadata.fullname tfm.filename = metadata.filename tfm.name = tfm.fullname tfm.psname = tfm.fullname tfm.type = "real" tfm.units = 1000 tfm.stretch = stretch -- nil tfm.slant = slant -- nil tfm.direction = 0 tfm.boundarychar_label = 0 tfm.boundarychar = 65536 --~ tfm.false_boundarychar = 65536 -- produces invalid tfm in luatex tfm.designsize = (metadata.designsize or 10)*65536 local spaceunits = 500 tfm.spacer = "500 units" -- same as otf local endash, emdash = unicodes['space'], unicodes['emdash'] if metadata.isfixedpitch then if descriptions[endash] then spaceunits, tfm.spacer = descriptions[endash].width, "space" end if not spaceunits and descriptions[emdash] then spaceunits, tfm.spacer = descriptions[emdash].width, "emdash" end if not spaceunits and metadata.charwidth then spaceunits, tfm.spacer = metadata.charwidth, "charwidth" end else if descriptions[endash] then spaceunits, tfm.spacer = descriptions[endash].width, "space" end -- if not spaceunits and descriptions[emdash] then -- spaceunits, tfm.spacer = descriptions[emdash].width/2, "emdash/2" -- end if not spaceunits and metadata.charwidth then spaceunits, tfm.spacer = metadata.charwidth, "charwidth" end end -- spaceunits = tonumber(spaceunits) parameters.slant = 0 parameters.space = spaceunits parameters.space_stretch = 500 parameters.space_shrink = 333 parameters.x_height = 400 parameters.quad = 1000 if spaceunits < 200 then -- todo: warning end tfm.ascender = math.abs(metadata.ascender or 0) tfm.descender = math.abs(metadata.descender or 0) local italicangle = data.metadata.italicangle if italicangle then tfm.italicangle = italicangle parameters.slant = parameters.slant - math.round(math.tan(italicangle*math.pi/180)) end if metadata.isfixedpitch then parameters.space_stretch = 0 parameters.space_shrink = 0 elseif afm.syncspace then parameters.space_stretch = spaceunits/2 parameters.space_shrink = spaceunits/3 end parameters.extra_space = parameters.space_shrink if metadata.xheight and metadata.xheight > 0 then parameters.x_height = metadata.xheight else -- same as otf local x = unicodes['x'] if x then local x = descriptions[x] if x then parameters.x_height = x.height end end -- end local fd = data.fontdimens if fd and fd[8] and fd[9] and fd[10] then -- math for k,v in next, fd do parameters[k] = v end end if next(characters) then return tfm end end end return nil end --[[ldx--Originally we had features kind of hard coded for
As soon as we could intercept the
Here comes the implementation of a few features. We only implement those that make sense for this format.
--ldx]]-- function afm.features.prepare_ligatures(tfmdata,ligatures,value) if value then local afmdata = tfmdata.shared.afmdata local luatex = afmdata.luatex local unicodes = luatex.unicodes local descriptions = tfmdata.descriptions for u, chr in next, tfmdata.characters do local d = descriptions[u] local l = d[ligatures] if l then local ligatures = chr.ligatures if not ligatures then ligatures = { } chr.ligatures = ligatures end for k, v in next, l do local uk, uv = unicodes[k], unicodes[v] if uk and uv then ligatures[uk] = { char = uv, type = 0 } end end end end end end function afm.features.prepare_kerns(tfmdata,kerns,value) if value then local afmdata = tfmdata.shared.afmdata local luatex = afmdata.luatex local unicodes = luatex.unicodes local descriptions = tfmdata.descriptions for u, chr in next, tfmdata.characters do local d = descriptions[u] local newkerns = d[kerns] if newkerns then local kerns = chr.kerns if not kerns then kerns = { } chr.kerns = kerns end for k,v in next, newkerns do local uk = unicodes[k] if uk then kerns[uk] = v end end end end end end -- hm, register? function fonts.initializers.base.afm.ligatures (tfmdata,value) afm.features.prepare_ligatures(tfmdata,'ligatures', value) end function fonts.initializers.base.afm.texligatures(tfmdata,value) afm.features.prepare_ligatures(tfmdata,'texligatures',value) end function fonts.initializers.base.afm.kerns (tfmdata,value) afm.features.prepare_kerns (tfmdata,'kerns', value) end function fonts.initializers.base.afm.extrakerns (tfmdata,value) afm.features.prepare_kerns (tfmdata,'extrakerns', value) end afm.features.register('liga',true) afm.features.register('kerns',true) afm.features.register('extrakerns') -- needed? fonts.initializers.node.afm.ligatures = fonts.initializers.base.afm.ligatures fonts.initializers.node.afm.texligatures = fonts.initializers.base.afm.texligatures fonts.initializers.node.afm.kerns = fonts.initializers.base.afm.kerns fonts.initializers.node.afm.extrakerns = fonts.initializers.base.afm.extrakerns fonts.initializers.base.afm.liga = fonts.initializers.base.afm.ligatures fonts.initializers.node.afm.liga = fonts.initializers.base.afm.ligatures fonts.initializers.base.afm.tlig = fonts.initializers.base.afm.texligatures fonts.initializers.node.afm.tlig = fonts.initializers.base.afm.texligatures fonts.initializers.base.afm.trep = tfm.replacements fonts.initializers.node.afm.trep = tfm.replacements afm.features.register('tlig',true) -- todo: also proper features for afm afm.features.register('trep',true) -- todo: also proper features for afm -- tfm features fonts.initializers.base.afm.equaldigits = fonts.initializers.common.equaldigits fonts.initializers.node.afm.equaldigits = fonts.initializers.common.equaldigits fonts.initializers.base.afm.lineheight = fonts.initializers.common.lineheight fonts.initializers.node.afm.lineheight = fonts.initializers.common.lineheight -- vf features fonts.initializers.base.afm.compose = fonts.initializers.common.compose fonts.initializers.node.afm.compose = fonts.initializers.common.compose -- afm specific, encodings ...kind of obsolete afm.features.register('encoding') fonts.initializers.base.afm.encoding = fonts.initializers.common.encoding fonts.initializers.node.afm.encoding = fonts.initializers.common.encoding -- todo: oldstyle smallcaps as features for afm files (use with care) fonts.initializers.base.afm.onum = fonts.initializers.common.oldstyle fonts.initializers.base.afm.smcp = fonts.initializers.common.smallcaps fonts.initializers.base.afm.fkcp = fonts.initializers.common.fakecaps afm.features.register('onum',false) afm.features.register('smcp',false) afm.features.register('fkcp',false)