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--
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 went away but we kept some code around in other modules.
This version implements a node mode approach so that users can also more easily add features.
--ldx]]-- local fonts, logs, trackers, containers, resolvers = fonts, logs, trackers, containers, resolvers local next, type, tonumber, rawget = next, type, tonumber, rawget local match, gmatch, lower, gsub, strip, find = string.match, string.gmatch, string.lower, string.gsub, string.strip, string.find local char, byte, sub = string.char, string.byte, string.sub local abs = math.abs local bxor, rshift = bit32.bxor, bit32.rshift 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 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 definers = fonts.definers local readers = fonts.readers local constructors = fonts.constructors local afm = constructors.newhandler("afm") local pfb = constructors.newhandler("pfb") local otf = fonts.handlers.otf local otfreaders = otf.readers local otfenhancers = otf.enhancers local afmfeatures = constructors.newfeatures("afm") local registerafmfeature = afmfeatures.register afm.version = 1.507 -- incrementing this number one up will force a re-cache afm.cache = containers.define("fonts", "afm", 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--We start with the basic reader which we give a name similar to the built in
We now use a new (unfinished) pfb loader but I see no differences between the old and new vectors (we actually had one bad vector with the old loader).
--ldx]]-- local get_indexes do local n, m local progress = function(str,position,name,size) local forward = position + tonumber(size) + 3 + 2 n = n + 1 if n >= m then return #str, name elseif forward < #str then return forward, name else return #str, name end end local initialize = function(str,position,size) n = 0 m = tonumber(size) return position + 1 end local charstrings = P("/CharStrings") local name = P("/") * C((R("az")+R("AZ")+R("09")+S("-_."))^1) local size = C(R("09")^1) local spaces = P(" ")^1 local p_filternames = Ct ( (1-charstrings)^0 * charstrings * spaces * Cmt(size,initialize) * (Cmt(name * P(" ")^1 * C(R("09")^1), progress) + P(1))^1 ) -- if one of first 4 not 0-9A-F then binary else hex local decrypt do local r, c1, c2, n = 0, 0, 0, 0 local function step(c) local cipher = byte(c) local plain = bxor(cipher,rshift(r,8)) r = ((cipher + r) * c1 + c2) % 65536 return char(plain) end decrypt = function(binary) r, c1, c2, n = 55665, 52845, 22719, 4 binary = gsub(binary,".",step) return sub(binary,n+1) end -- local pattern = Cs((P(1) / step)^1) -- -- decrypt = function(binary) -- r, c1, c2, n = 55665, 52845, 22719, 4 -- binary = lpegmatch(pattern,binary) -- return sub(binary,n+1) -- end end local function loadpfbvector(filename) -- for the moment limited to encoding only local data = io.loaddata(resolvers.findfile(filename)) if not find(data,"!PS%-AdobeFont%-") then print("no font",filename) return end if not data then print("no data",filename) return end local ascii, binary = match(data,"(.*)eexec%s+......(.*)") if not binary then print("no binary",filename) return end binary = decrypt(binary,4) local vector = lpegmatch(p_filternames,binary) vector[0] = table.remove(vector,1) if not vector then print("no vector",filename) return end return vector end get_indexes = function(data,pfbname) local vector = loadpfbvector(pfbname) if vector then local characters = data.characters if trace_loading then report_afm("getting index data from %a",pfbname) end for index=1,#vector do local name = vector[index] local char = characters[name] if char then if trace_indexing then report_afm("glyph %a has index %a",name,index) end char.index = index end end end end end local function readafm(filename) local ok, afmblob, size = resolvers.loadbinfile(filename) -- has logging if ok and afmblob then local data = { resources = { filename = resolvers.unresolve(filename), version = afm.version, creator = "context mkiv", }, properties = { hasitalics = false, }, goodies = { }, metadata = { filename = file.removesuffix(file.basename(filename)) }, characters = { -- a temporary store }, descriptions = { -- the final store }, } -- afmblob = gsub(afmblob,"StartCharMetrics(.-)EndCharMetrics", function(charmetrics) for charmetrics in gmatch(afmblob,"StartCharMetrics(.-)EndCharMetrics") do if trace_loading then report_afm("loading char metrics") end get_charmetrics(data,charmetrics,vector) break end for kernpairs in gmatch(afmblob,"StartKernPairs(.-)EndKernPairs") do if trace_loading then report_afm("loading kern pairs") end get_kernpairs(data,kernpairs) break end for version, fontmetrics in gmatch(afmblob,"StartFontMetrics%s+([%d%.]+)(.-)EndFontMetrics") do if trace_loading then report_afm("loading variables") end data.afmversion = version get_variables(data,fontmetrics) data.fontdimens = scan_comment(fontmetrics) -- todo: all lpeg, no time now break end return data else if trace_loading then report_afm("no valid afm file %a",filename) end return nil end end --[[ldx--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.
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
These helpers extend the basic table with extra ligatures, texligatures and extra kerns. This saves quite some lookups later.
--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, three = unicodes[pair[1]], 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 enhancers["add ligatures"] = function(rawdata) addthem(rawdata,afm.helpdata.ligatures) end -- enhancers["add tex ligatures"] = function(rawdata) -- addthem(rawdata,afm.helpdata.texligatures) -- end --[[ldx--We keep the extra kerns in separate kerning tables so that we can use them selectively.
--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). enhancers["add extra kerns"] = function(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--The copying routine looks messy (and is indeed a bit messy).
--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, dp = bb[4], -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 = 0x0020 -- space local emdash = 0x2014 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 -- same as otf if properties.monospaced then if descriptions[endash] then spaceunits, spacer = descriptions[endash].width, "space" end if not spaceunits and descriptions[emdash] then spaceunits, spacer = descriptions[emdash].width, "emdash" end if not spaceunits and charwidth then spaceunits, spacer = charwidth, "charwidth" end else if descriptions[endash] then spaceunits, spacer = descriptions[endash].width, "space" end if not spaceunits and charwidth then spaceunits, spacer = charwidth, "charwidth" end end spaceunits = tonumber(spaceunits) if spaceunits < 200 then -- todo: warning 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 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 -- 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 -- if next(characters) then return { characters = characters, descriptions = descriptions, parameters = parameters, resources = resources, properties = properties, goodies = goodies, } end end return nil end --[[ldx--Originally we had features kind of hard coded for
As soon as we could intercept the
We have the usual two modes and related features initializers and processors.
--ldx]]-- local function setmode(tfmdata,value) if value then tfmdata.properties.mode = lower(value) end end registerafmfeature { name = "mode", description = "mode", initializers = { base = setmode, node = setmode, } } registerafmfeature { name = "features", description = "features", default = true, initializers = { node = otf.nodemodeinitializer, base = otf.basemodeinitializer, }, processors = { node = otf.featuresprocessor, } } -- readers local check_tfm = readers.check_tfm 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 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, tfmdata = specification.filename or "", nil if fullname == "" then local forced = specification.forced or "" if forced ~= "" then tfmdata = check_afm(specification,specification.name .. "." .. forced) end if not tfmdata then method = method or definers.method or "afm or tfm" 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.specification = file.replacesuffix(original,"afm") specification.forced = "afm" return readers.afm(specification,method) end