You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
842 lines
30 KiB
842 lines
30 KiB
if not modules then modules = { } end modules ['font-one'] = {
|
|
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"
|
|
}
|
|
|
|
--[[ldx--
|
|
<p>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 <l n='tfm'/> to
|
|
<l n='afm'/> to <l n='otf'/>.</p>
|
|
|
|
<p>The following code still has traces of intermediate font support where we handles
|
|
font encodings. Eventually font encoding went away but we kept some code around in
|
|
other modules.</p>
|
|
|
|
<p>This version implements a node mode approach so that users can also more easily
|
|
add features.</p>
|
|
--ldx]]--
|
|
|
|
local fonts, logs, trackers, containers, resolvers = fonts, logs, trackers, containers, resolvers
|
|
|
|
local next, type, tonumber, rawget = next, type, tonumber, rawget
|
|
local match, gsub = string.match, string.gsub
|
|
local abs = math.abs
|
|
local P, S, R, Cmt, C, Ct, Cs, Carg = lpeg.P, lpeg.S, lpeg.R, lpeg.Cmt, lpeg.C, lpeg.Ct, lpeg.Cs, lpeg.Carg
|
|
local lpegmatch, patterns = lpeg.match, lpeg.patterns
|
|
local sortedhash = table.sortedhash
|
|
|
|
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 trace_defining = false trackers.register("fonts.defining", function(v) trace_defining = v end)
|
|
|
|
local report_afm = logs.reporter("fonts","afm loading")
|
|
|
|
local setmetatableindex = table.setmetatableindex
|
|
local derivetable = table.derive
|
|
|
|
local findbinfile = resolvers.findbinfile
|
|
|
|
local privateoffset = fonts.constructors and fonts.constructors.privateoffset or 0xF0000 -- 0x10FFFF
|
|
|
|
local definers = fonts.definers
|
|
local readers = fonts.readers
|
|
local constructors = fonts.constructors
|
|
|
|
local afm = constructors.handlers.afm
|
|
local pfb = constructors.handlers.pfb
|
|
local otf = fonts.handlers.otf
|
|
|
|
local otfreaders = otf.readers
|
|
local otfenhancers = otf.enhancers
|
|
|
|
local afmfeatures = constructors.features.afm
|
|
local registerafmfeature = afmfeatures.register
|
|
|
|
local afmenhancers = constructors.enhancers.afm
|
|
local registerafmenhancer = afmenhancers.register
|
|
|
|
afm.version = 1.513 -- incrementing this number one up will force a re-cache
|
|
afm.cache = containers.define("fonts", "one", afm.version, true)
|
|
afm.autoprefixed = true -- this will become false some day (catches texnansi-blabla.*)
|
|
|
|
afm.helpdata = { } -- set later on so no local for this
|
|
afm.syncspace = true -- when true, nicer stretch values
|
|
|
|
local overloads = fonts.mappings.overloads
|
|
|
|
local applyruntimefixes = fonts.treatments and fonts.treatments.applyfixes
|
|
|
|
--[[ldx--
|
|
<p>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.</p>
|
|
|
|
<p>We still keep the loading two phased: first we load the data in a traditional
|
|
fashion and later we transform it to sequences. Then we apply some methods also
|
|
used in opentype fonts (like <t>tlig</t>).</p>
|
|
--ldx]]--
|
|
|
|
function afm.load(filename)
|
|
filename = resolvers.findfile(filename,'afm') or ""
|
|
if filename ~= "" and not fonts.names.ignoredfile(filename) then
|
|
local name = file.removesuffix(file.basename(filename))
|
|
local data = containers.read(afm.cache,name)
|
|
local attr = lfs.attributes(filename)
|
|
local size = attr and attr.size or 0
|
|
local time = attr and attr.modification or 0
|
|
--
|
|
local pfbfile = file.replacesuffix(name,"pfb")
|
|
local pfbname = resolvers.findfile(pfbfile,"pfb") or ""
|
|
if pfbname == "" then
|
|
pfbname = resolvers.findfile(file.basename(pfbfile),"pfb") or ""
|
|
end
|
|
local pfbsize = 0
|
|
local pfbtime = 0
|
|
if pfbname ~= "" then
|
|
local attr = lfs.attributes(pfbname)
|
|
pfbsize = attr.size or 0
|
|
pfbtime = attr.modification or 0
|
|
end
|
|
if not data or data.size ~= size or data.time ~= time or data.pfbsize ~= pfbsize or data.pfbtime ~= pfbtime then
|
|
report_afm("reading %a",filename)
|
|
data = afm.readers.loadfont(filename,pfbname)
|
|
if data then
|
|
afmenhancers.apply(data,filename)
|
|
-- otfreaders.addunicodetable(data) -- only when not done yet
|
|
fonts.mappings.addtounicode(data,filename)
|
|
otfreaders.stripredundant(data)
|
|
-- otfreaders.extend(data)
|
|
otfreaders.pack(data)
|
|
data.size = size
|
|
data.time = time
|
|
data.pfbsize = pfbsize
|
|
data.pfbtime = pfbtime
|
|
report_afm("saving %a in cache",name)
|
|
-- data.resources.unicodes = nil -- consistent with otf but here we save not much
|
|
data = containers.write(afm.cache, name, data)
|
|
data = containers.read(afm.cache,name)
|
|
end
|
|
end
|
|
if data then
|
|
-- constructors.addcoreunicodes(unicodes)
|
|
otfreaders.unpack(data)
|
|
otfreaders.expand(data) -- inline tables
|
|
otfreaders.addunicodetable(data) -- only when not done yet
|
|
otfenhancers.apply(data,filename,data)
|
|
if applyruntimefixes then
|
|
applyruntimefixes(filename,data)
|
|
end
|
|
end
|
|
return data
|
|
end
|
|
end
|
|
|
|
-- we run a more advanced analyzer later on anyway
|
|
|
|
local uparser = fonts.mappings.makenameparser() -- each time
|
|
|
|
local function enhance_unify_names(data, filename)
|
|
local unicodevector = fonts.encodings.agl.unicodes -- loaded runtime in context
|
|
local unicodes = { }
|
|
local names = { }
|
|
local private = data.private or privateoffset
|
|
local descriptions = data.descriptions
|
|
for name, blob in sortedhash(data.characters) do -- sorting is nicer for privates
|
|
local code = unicodevector[name] -- or characters.name_to_unicode[name]
|
|
if not code then
|
|
code = lpegmatch(uparser,name)
|
|
if type(code) ~= "number" then
|
|
code = private
|
|
private = private + 1
|
|
report_afm("assigning private slot %U for unknown glyph name %a",code,name)
|
|
end
|
|
end
|
|
local index = blob.index
|
|
unicodes[name] = code
|
|
names[name] = index
|
|
blob.name = name
|
|
descriptions[code] = {
|
|
boundingbox = blob.boundingbox,
|
|
width = blob.width,
|
|
kerns = blob.kerns,
|
|
index = index,
|
|
name = name,
|
|
}
|
|
end
|
|
for unicode, description in next, descriptions do
|
|
local kerns = description.kerns
|
|
if kerns then
|
|
local krn = { }
|
|
for name, kern in next, kerns do
|
|
local unicode = unicodes[name]
|
|
if unicode then
|
|
krn[unicode] = kern
|
|
else
|
|
-- print(unicode,name)
|
|
end
|
|
end
|
|
description.kerns = krn
|
|
end
|
|
end
|
|
data.characters = nil
|
|
data.private = private
|
|
local resources = data.resources
|
|
local filename = resources.filename or file.removesuffix(file.basename(filename))
|
|
resources.filename = resolvers.unresolve(filename) -- no shortcut
|
|
resources.unicodes = unicodes -- name to unicode
|
|
resources.marks = { } -- todo
|
|
-- resources.names = names -- name to index
|
|
end
|
|
|
|
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 extrakerns = setmetatableindex("table")
|
|
for u, c in next, data.descriptions do
|
|
local l = c.ligatures
|
|
local k = c.kerns
|
|
local e = c.extrakerns
|
|
if l then
|
|
ligatures[u] = l
|
|
for u, v in next, l do
|
|
l[u] = { ligature = v }
|
|
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
|
|
if e then
|
|
extrakerns[u] = e
|
|
for u, v in next, e do
|
|
e[u] = v -- { v, 0 }
|
|
end
|
|
c.extrakerns = nil
|
|
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
|
|
if next(extrakerns) then
|
|
features.gpos.extrakerns = everywhere
|
|
data.properties.haskerns = true
|
|
sequences[#sequences+1] = {
|
|
features = {
|
|
extrakerns = everywhere,
|
|
},
|
|
flags = noflags,
|
|
name = "p_s_1",
|
|
nofsteps = 1,
|
|
order = { "extrakerns" },
|
|
type = "gpos_pair",
|
|
steps = {
|
|
{
|
|
format = "kern",
|
|
coverage = extrakerns,
|
|
},
|
|
},
|
|
}
|
|
end
|
|
-- todo: compress kerns
|
|
data.resources.features = features
|
|
data.resources.sequences = sequences
|
|
end
|
|
|
|
local function enhance_fix_names(data)
|
|
for k, v in next, data.descriptions do
|
|
local n = v.name
|
|
local r = overloads[n]
|
|
if r then
|
|
local name = r.name
|
|
if trace_indexing then
|
|
report_afm("renaming characters %a to %a",n,name)
|
|
end
|
|
v.name = name
|
|
v.unicode = r.unicode
|
|
end
|
|
end
|
|
end
|
|
|
|
--[[ldx--
|
|
<p>These helpers extend the basic table with extra ligatures, texligatures
|
|
and extra kerns. This saves quite some lookups later.</p>
|
|
--ldx]]--
|
|
|
|
local addthem = function(rawdata,ligatures)
|
|
if ligatures then
|
|
local descriptions = rawdata.descriptions
|
|
local resources = rawdata.resources
|
|
local unicodes = resources.unicodes
|
|
-- local names = resources.names
|
|
for ligname, ligdata in next, ligatures do
|
|
local one = descriptions[unicodes[ligname]]
|
|
if one then
|
|
for _, pair in next, ligdata do
|
|
local two = unicodes[pair[1]]
|
|
local three = unicodes[pair[2]]
|
|
if two and three then
|
|
local ol = one.ligatures
|
|
if ol then
|
|
if not ol[two] then
|
|
ol[two] = three
|
|
end
|
|
else
|
|
one.ligatures = { [two] = three }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function enhance_add_ligatures(rawdata)
|
|
addthem(rawdata,afm.helpdata.ligatures)
|
|
end
|
|
|
|
--[[ldx--
|
|
<p>We keep the extra kerns in separate kerning tables so that we can use
|
|
them selectively.</p>
|
|
--ldx]]--
|
|
|
|
-- This is rather old code (from the beginning when we had only tfm). If
|
|
-- we unify the afm data (now we have names all over the place) then
|
|
-- we can use shcodes but there will be many more looping then. But we
|
|
-- could get rid of the tables in char-cmp then. Als, in the generic version
|
|
-- we don't use the character database. (Ok, we can have a context specific
|
|
-- variant).
|
|
|
|
local function enhance_add_extra_kerns(rawdata) -- using shcodes is not robust here
|
|
local descriptions = rawdata.descriptions
|
|
local resources = rawdata.resources
|
|
local unicodes = resources.unicodes
|
|
local function do_it_left(what)
|
|
if what then
|
|
for unicode, description in next, descriptions do
|
|
local kerns = description.kerns
|
|
if kerns then
|
|
local extrakerns
|
|
for complex, simple in next, what do
|
|
complex = unicodes[complex]
|
|
simple = unicodes[simple]
|
|
if complex and simple then
|
|
local ks = kerns[simple]
|
|
if ks and not kerns[complex] then
|
|
if extrakerns then
|
|
extrakerns[complex] = ks
|
|
else
|
|
extrakerns = { [complex] = ks }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if extrakerns then
|
|
description.extrakerns = extrakerns
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local function do_it_copy(what)
|
|
if what then
|
|
for complex, simple in next, what do
|
|
complex = unicodes[complex]
|
|
simple = unicodes[simple]
|
|
if complex and simple then
|
|
local complexdescription = descriptions[complex]
|
|
if complexdescription then -- optional
|
|
local simpledescription = descriptions[complex]
|
|
if simpledescription then
|
|
local extrakerns
|
|
local kerns = simpledescription.kerns
|
|
if kerns then
|
|
for unicode, kern in next, kerns do
|
|
if extrakerns then
|
|
extrakerns[unicode] = kern
|
|
else
|
|
extrakerns = { [unicode] = kern }
|
|
end
|
|
end
|
|
end
|
|
local extrakerns = simpledescription.extrakerns
|
|
if extrakerns then
|
|
for unicode, kern in next, extrakerns do
|
|
if extrakerns then
|
|
extrakerns[unicode] = kern
|
|
else
|
|
extrakerns = { [unicode] = kern }
|
|
end
|
|
end
|
|
end
|
|
if extrakerns then
|
|
complexdescription.extrakerns = extrakerns
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
-- add complex with values of simplified when present
|
|
do_it_left(afm.helpdata.leftkerned)
|
|
do_it_left(afm.helpdata.bothkerned)
|
|
-- copy kerns from simple char to complex char unless set
|
|
do_it_copy(afm.helpdata.bothkerned)
|
|
do_it_copy(afm.helpdata.rightkerned)
|
|
end
|
|
|
|
--[[ldx--
|
|
<p>The copying routine looks messy (and is indeed a bit messy).</p>
|
|
--ldx]]--
|
|
|
|
local function adddimensions(data) -- we need to normalize afm to otf i.e. indexed table instead of name
|
|
if data then
|
|
for unicode, description in next, data.descriptions do
|
|
local bb = description.boundingbox
|
|
if bb then
|
|
local ht = bb[4]
|
|
local dp = -bb[2]
|
|
if ht == 0 or ht < 0 then
|
|
-- no need to set it and no negative heights, nil == 0
|
|
else
|
|
description.height = ht
|
|
end
|
|
if dp == 0 or dp < 0 then
|
|
-- no negative depths and no negative depths, nil == 0
|
|
else
|
|
description.depth = dp
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function copytotfm(data)
|
|
if data and data.descriptions then
|
|
local metadata = data.metadata
|
|
local resources = data.resources
|
|
local properties = derivetable(data.properties)
|
|
local descriptions = derivetable(data.descriptions)
|
|
local goodies = derivetable(data.goodies)
|
|
local characters = { }
|
|
local parameters = { }
|
|
local unicodes = resources.unicodes
|
|
--
|
|
for unicode, description in next, data.descriptions do -- use parent table
|
|
characters[unicode] = { }
|
|
end
|
|
--
|
|
local filename = constructors.checkedfilename(resources)
|
|
local fontname = metadata.fontname or metadata.fullname
|
|
local fullname = metadata.fullname or metadata.fontname
|
|
local endash = 0x2013
|
|
local emdash = 0x2014
|
|
local space = 0x0020 -- space
|
|
local spacer = "space"
|
|
local spaceunits = 500
|
|
--
|
|
local monospaced = metadata.monospaced
|
|
local charwidth = metadata.charwidth
|
|
local italicangle = metadata.italicangle
|
|
local charxheight = metadata.xheight and metadata.xheight > 0 and metadata.xheight
|
|
properties.monospaced = monospaced
|
|
parameters.italicangle = italicangle
|
|
parameters.charwidth = charwidth
|
|
parameters.charxheight = charxheight
|
|
-- nearly the same as otf, catches
|
|
local d_endash = descriptions[endash]
|
|
local d_emdash = descriptions[emdash]
|
|
local d_space = descriptions[space]
|
|
if not d_space or d_space == 0 then
|
|
d_space = d_endash
|
|
end
|
|
if d_space then
|
|
spaceunits, spacer = d_space.width or 0, "space"
|
|
end
|
|
if properties.monospaced then
|
|
if spaceunits == 0 and d_emdash then
|
|
spaceunits, spacer = d_emdash.width or 0, "emdash"
|
|
end
|
|
else
|
|
if spaceunits == 0 and d_endash then
|
|
spaceunits, spacer = d_emdash.width or 0, "endash"
|
|
end
|
|
end
|
|
if spaceunits == 0 and charwidth then
|
|
spaceunits, spacer = charwidth or 0, "charwidth"
|
|
end
|
|
if spaceunits == 0 then
|
|
spaceunits = tonumber(spaceunits) or 500
|
|
end
|
|
if spaceunits == 0 then
|
|
spaceunits = 500
|
|
end
|
|
--
|
|
parameters.slant = 0
|
|
parameters.space = spaceunits
|
|
parameters.space_stretch = 500
|
|
parameters.space_shrink = 333
|
|
parameters.x_height = 400
|
|
parameters.quad = 1000
|
|
--
|
|
if italicangle and italicangle ~= 0 then
|
|
parameters.italicangle = italicangle
|
|
parameters.italicfactor = math.cos(math.rad(90+italicangle))
|
|
parameters.slant = - math.tan(italicangle*math.pi/180)
|
|
end
|
|
if monospaced 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 charxheight then
|
|
parameters.x_height = charxheight
|
|
else
|
|
-- same as otf
|
|
local x = 0x0078 -- x
|
|
if x then
|
|
local x = descriptions[x]
|
|
if x then
|
|
parameters.x_height = x.height
|
|
end
|
|
end
|
|
--
|
|
end
|
|
--
|
|
if metadata.sup then
|
|
local dummy = { 0, 0, 0 }
|
|
parameters[ 1] = metadata.designsize or 0
|
|
parameters[ 2] = metadata.checksum or 0
|
|
parameters[ 3],
|
|
parameters[ 4],
|
|
parameters[ 5] = unpack(metadata.space or dummy)
|
|
parameters[ 6] = metadata.quad or 0
|
|
parameters[ 7] = metadata.extraspace or 0
|
|
parameters[ 8],
|
|
parameters[ 9],
|
|
parameters[10] = unpack(metadata.num or dummy)
|
|
parameters[11],
|
|
parameters[12] = unpack(metadata.denom or dummy)
|
|
parameters[13],
|
|
parameters[14],
|
|
parameters[15] = unpack(metadata.sup or dummy)
|
|
parameters[16],
|
|
parameters[17] = unpack(metadata.sub or dummy)
|
|
parameters[18] = metadata.supdrop or 0
|
|
parameters[19] = metadata.subdrop or 0
|
|
parameters[20],
|
|
parameters[21] = unpack(metadata.delim or dummy)
|
|
parameters[22] = metadata.axisheight or 0
|
|
end
|
|
--
|
|
parameters.designsize = (metadata.designsize or 10)*65536
|
|
parameters.ascender = abs(metadata.ascender or 0)
|
|
parameters.descender = abs(metadata.descender or 0)
|
|
parameters.units = 1000
|
|
--
|
|
properties.spacer = spacer
|
|
properties.encodingbytes = 2
|
|
properties.format = fonts.formats[filename] or "type1"
|
|
properties.filename = filename
|
|
properties.fontname = fontname
|
|
properties.fullname = fullname
|
|
properties.psname = fullname
|
|
properties.name = filename or fullname or fontname
|
|
properties.private = properties.private or data.private or privateoffset
|
|
--
|
|
if next(characters) then
|
|
return {
|
|
characters = characters,
|
|
descriptions = descriptions,
|
|
parameters = parameters,
|
|
resources = resources,
|
|
properties = properties,
|
|
goodies = goodies,
|
|
}
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
--[[ldx--
|
|
<p>Originally we had features kind of hard coded for <l n='afm'/> files but since I
|
|
expect to support more font formats, I decided to treat this fontformat like any
|
|
other and handle features in a more configurable way.</p>
|
|
--ldx]]--
|
|
|
|
function afm.setfeatures(tfmdata,features)
|
|
local okay = constructors.initializefeatures("afm",tfmdata,features,trace_features,report_afm)
|
|
if okay then
|
|
return constructors.collectprocessors("afm",tfmdata,features,trace_features,report_afm)
|
|
else
|
|
return { } -- will become false
|
|
end
|
|
end
|
|
|
|
local function addtables(data)
|
|
local resources = data.resources
|
|
local lookuptags = resources.lookuptags
|
|
local unicodes = resources.unicodes
|
|
if not lookuptags then
|
|
lookuptags = { }
|
|
resources.lookuptags = lookuptags
|
|
end
|
|
setmetatableindex(lookuptags,function(t,k)
|
|
local v = type(k) == "number" and ("lookup " .. k) or k
|
|
t[k] = v
|
|
return v
|
|
end)
|
|
if not unicodes then
|
|
unicodes = { }
|
|
resources.unicodes = unicodes
|
|
setmetatableindex(unicodes,function(t,k)
|
|
setmetatableindex(unicodes,nil)
|
|
for u, d in next, data.descriptions do
|
|
local n = d.name
|
|
if n then
|
|
t[n] = u
|
|
end
|
|
end
|
|
return rawget(t,k)
|
|
end)
|
|
end
|
|
constructors.addcoreunicodes(unicodes) -- do we really need this?
|
|
end
|
|
|
|
local function afmtotfm(specification)
|
|
local afmname = specification.filename or specification.name
|
|
if specification.forced == "afm" or specification.format == "afm" then -- move this one up
|
|
if trace_loading then
|
|
report_afm("forcing afm format for %a",afmname)
|
|
end
|
|
else
|
|
local tfmname = findbinfile(afmname,"ofm") or ""
|
|
if tfmname ~= "" then
|
|
if trace_loading then
|
|
report_afm("fallback from afm to tfm for %a",afmname)
|
|
end
|
|
return -- just that
|
|
end
|
|
end
|
|
if afmname ~= "" then
|
|
-- weird, isn't this already done then?
|
|
local features = constructors.checkedfeatures("afm",specification.features.normal)
|
|
specification.features.normal = features
|
|
constructors.hashinstance(specification,true) -- also weird here
|
|
--
|
|
specification = definers.resolve(specification) -- new, was forgotten
|
|
local cache_id = specification.hash
|
|
local tfmdata = containers.read(constructors.cache, cache_id) -- cache with features applied
|
|
if not tfmdata then
|
|
local rawdata = afm.load(afmname)
|
|
if rawdata and next(rawdata) then
|
|
addtables(rawdata)
|
|
adddimensions(rawdata)
|
|
tfmdata = copytotfm(rawdata)
|
|
if tfmdata and next(tfmdata) then
|
|
local shared = tfmdata.shared
|
|
if not shared then
|
|
shared = { }
|
|
tfmdata.shared = shared
|
|
end
|
|
shared.rawdata = rawdata
|
|
shared.dynamics = { }
|
|
tfmdata.changed = { }
|
|
shared.features = features
|
|
shared.processes = afm.setfeatures(tfmdata,features)
|
|
end
|
|
elseif trace_loading then
|
|
report_afm("no (valid) afm file found with name %a",afmname)
|
|
end
|
|
tfmdata = containers.write(constructors.cache,cache_id,tfmdata)
|
|
end
|
|
return tfmdata
|
|
end
|
|
end
|
|
|
|
--[[ldx--
|
|
<p>As soon as we could intercept the <l n='tfm'/> reader, I implemented an
|
|
<l n='afm'/> reader. Since traditional <l n='pdftex'/> could use <l n='opentype'/>
|
|
fonts with <l n='afm'/> companions, the following method also could handle
|
|
those cases, but now that we can handle <l n='opentype'/> directly we no longer
|
|
need this features.</p>
|
|
--ldx]]--
|
|
|
|
local function read_from_afm(specification)
|
|
local tfmdata = afmtotfm(specification)
|
|
if tfmdata then
|
|
tfmdata.properties.name = specification.name
|
|
tfmdata = constructors.scale(tfmdata, specification)
|
|
local allfeatures = tfmdata.shared.features or specification.features.normal
|
|
constructors.applymanipulators("afm",tfmdata,allfeatures,trace_features,report_afm)
|
|
fonts.loggers.register(tfmdata,'afm',specification)
|
|
end
|
|
return tfmdata
|
|
end
|
|
|
|
--[[ldx--
|
|
<p>We have the usual two modes and related features initializers and processors.</p>
|
|
--ldx]]--
|
|
|
|
registerafmfeature {
|
|
name = "mode",
|
|
description = "mode",
|
|
initializers = {
|
|
base = otf.modeinitializer,
|
|
node = otf.modeinitializer,
|
|
}
|
|
}
|
|
|
|
registerafmfeature {
|
|
name = "features",
|
|
description = "features",
|
|
default = true,
|
|
initializers = {
|
|
node = otf.nodemodeinitializer,
|
|
base = otf.basemodeinitializer,
|
|
},
|
|
processors = {
|
|
node = otf.featuresprocessor,
|
|
}
|
|
}
|
|
|
|
-- readers
|
|
|
|
fonts.formats.afm = "type1"
|
|
fonts.formats.pfb = "type1"
|
|
|
|
local function check_afm(specification,fullname)
|
|
local foundname = findbinfile(fullname, 'afm') or "" -- just to be sure
|
|
if foundname == "" then
|
|
foundname = fonts.names.getfilename(fullname,"afm") or ""
|
|
end
|
|
if fullname and foundname == "" and afm.autoprefixed then
|
|
local encoding, shortname = match(fullname,"^(.-)%-(.*)$") -- context: encoding-name.*
|
|
if encoding and shortname and fonts.encodings.known[encoding] then
|
|
shortname = findbinfile(shortname,'afm') or "" -- just to be sure
|
|
if shortname ~= "" then
|
|
foundname = shortname
|
|
if trace_defining then
|
|
report_afm("stripping encoding prefix from filename %a",afmname)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if foundname ~= "" then
|
|
specification.filename = foundname
|
|
specification.format = "afm"
|
|
return read_from_afm(specification)
|
|
end
|
|
end
|
|
|
|
function readers.afm(specification,method)
|
|
local fullname = specification.filename or ""
|
|
local tfmdata = nil
|
|
if fullname == "" then
|
|
local forced = specification.forced or ""
|
|
if forced ~= "" then
|
|
tfmdata = check_afm(specification,specification.name .. "." .. forced)
|
|
end
|
|
if not tfmdata then
|
|
local check_tfm = readers.check_tfm
|
|
method = (check_tfm and (method or definers.method or "afm or tfm")) or "afm"
|
|
if method == "tfm" then
|
|
tfmdata = check_tfm(specification,specification.name)
|
|
elseif method == "afm" then
|
|
tfmdata = check_afm(specification,specification.name)
|
|
elseif method == "tfm or afm" then
|
|
tfmdata = check_tfm(specification,specification.name) or check_afm(specification,specification.name)
|
|
else -- method == "afm or tfm" or method == "" then
|
|
tfmdata = check_afm(specification,specification.name) or check_tfm(specification,specification.name)
|
|
end
|
|
end
|
|
else
|
|
tfmdata = check_afm(specification,fullname)
|
|
end
|
|
return tfmdata
|
|
end
|
|
|
|
function readers.pfb(specification,method) -- only called when forced
|
|
local original = specification.specification
|
|
if trace_defining then
|
|
report_afm("using afm reader for %a",original)
|
|
end
|
|
specification.forced = "afm"
|
|
local function swap(name)
|
|
local value = specification[swap]
|
|
if value then
|
|
specification[swap] = gsub("%.pfb",".afm",1)
|
|
end
|
|
end
|
|
swap("filename")
|
|
swap("fullname")
|
|
swap("forcedname")
|
|
swap("specification")
|
|
return readers.afm(specification,method)
|
|
end
|
|
|
|
-- now we register them
|
|
|
|
registerafmenhancer("unify names", enhance_unify_names)
|
|
registerafmenhancer("add ligatures", enhance_add_ligatures)
|
|
registerafmenhancer("add extra kerns", enhance_add_extra_kerns)
|
|
registerafmenhancer("normalize features", enhance_normalize_features)
|
|
registerafmenhancer("check extra features", otfenhancers.enhance)
|
|
registerafmenhancer("fix names", enhance_fix_names)
|
|
|