if not modules then modules = { } end modules ['font-def'] = { 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 concat = table.concat local format, gmatch, match, find, lower, gsub = string.format, string.gmatch, string.match, string.find, string.lower, string.gsub local tostring, next = tostring, next local lpegmatch = lpeg.match local allocate = utilities.storage.allocate local trace_defining = false trackers .register("fonts.defining", function(v) trace_defining = v end) local directive_embedall = false directives.register("fonts.embedall", function(v) directive_embedall = v end) trackers.register("fonts.loading", "fonts.defining", "otf.loading", "afm.loading", "tfm.loading") trackers.register("fonts.all", "fonts.*", "otf.*", "afm.*", "tfm.*") local report_defining = logs.reporter("fonts","defining") --[[ldx--
Here we deal with defining fonts. We do so by intercepting the
default loader that only handles
We hardly gain anything when we cache the final (pre scaled)
We can prefix a font specification by
The following function split the font specification into components and prepares a table that will move along as we proceed.
--ldx]]-- -- beware, we discard additional specs -- -- method:name method:name(sub) method:name(sub)*spec method:name*spec -- name name(sub) name(sub)*spec name*spec -- name@spec*oeps local splitter, splitspecifiers = nil, "" local P, C, S, Cc = lpeg.P, lpeg.C, lpeg.S, lpeg.Cc local left = P("(") local right = P(")") local colon = P(":") local space = P(" ") definers.defaultlookup = "file" local prefixpattern = P(false) local function addspecifier(symbol) splitspecifiers = splitspecifiers .. symbol local method = S(splitspecifiers) local lookup = C(prefixpattern) * colon local sub = left * C(P(1-left-right-method)^1) * right local specification = C(method) * C(P(1)^1) local name = C((1-sub-specification)^1) splitter = P((lookup + Cc("")) * name * (sub + Cc("")) * (specification + Cc(""))) end local function addlookup(str,default) prefixpattern = prefixpattern + P(str) end definers.addlookup = addlookup addlookup("file") addlookup("name") addlookup("spec") local function getspecification(str) return lpegmatch(splitter,str) end definers.getspecification = getspecification function definers.registersplit(symbol,action,verbosename) addspecifier(symbol) variants[symbol] = action if verbosename then variants[verbosename] = action end end function definers.makespecification(specification, lookup, name, sub, method, detail, size) size = size or 655360 if trace_defining then report_defining("%s -> lookup: %s, name: %s, sub: %s, method: %s, detail: %s", specification, (lookup ~= "" and lookup) or "[file]", (name ~= "" and name) or "-", (sub ~= "" and sub) or "-", (method ~= "" and method) or "-", (detail ~= "" and detail) or "-") end if not lookup or lookup == "" then lookup = definers.defaultlookup end local t = { 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 features = { }, -- preprocessed features } return t end function definers.analyze(specification, size) -- can be optimized with locals local lookup, name, sub, method, detail = getspecification(specification or "") return definers.makespecification(specification, lookup, name, sub, method, detail, size) end --[[ldx--A unique hash value is generated by:
--ldx]]-- local sortedhashkeys = table.sortedhashkeys function tfm.hashfeatures(specification) local features = specification.features if features then local t, tn = { }, 0 local normal = features.normal if normal and next(normal) then local f = sortedhashkeys(normal) for i=1,#f do local v = f[i] if v ~= "number" and v ~= "features" then -- i need to figure this out, features tn = tn + 1 t[tn] = v .. '=' .. tostring(normal[v]) end end end local vtf = features.vtf if vtf and next(vtf) then local f = sortedhashkeys(vtf) for i=1,#f do local v = f[i] tn = tn + 1 t[tn] = v .. '=' .. tostring(vtf[v]) end end -- if specification.mathsize then -- tn = tn + 1 -- t[tn] = "mathsize=" .. specification.mathsize -- end if tn > 0 then return concat(t,"+") end end return "unknown" end fonts.designsizes = allocate() --[[ldx--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
We can resolve the filename using the next function:
--ldx]]-- definers.resolvers = definers.resolvers or { } local resolvers = definers.resolvers -- todo: reporter function resolvers.file(specification) local suffix = file.suffix(specification.name) if fonts.formats[suffix] then specification.forced = suffix specification.name = file.removesuffix(specification.name) end end function resolvers.name(specification) local resolve = fonts.names.resolve if resolve then local resolved, sub = fonts.names.resolve(specification) specification.resolved, specification.sub = resolved, sub if resolved then local suffix = file.suffix(resolved) if fonts.formats[suffix] then specification.forced = suffix specification.name = file.removesuffix(resolved) else specification.name = resolved end end else resolvers.file(specification) end end function resolvers.spec(specification) local resolvespec = fonts.names.resolvespec if resolvespec then specification.resolved, specification.sub = fonts.names.resolvespec(specification) if specification.resolved then specification.forced = file.extname(specification.resolved) specification.name = file.removesuffix(specification.resolved) end else resolvers.name(specification) end end function definers.resolve(specification) if not specification.resolved or specification.resolved == "" then -- resolved itself not per se in mapping hash local r = resolvers[specification.lookup] if r then r(specification) end end if specification.forced == "" then specification.forced = nil else specification.forced = specification.forced end -- for the moment here (goodies set outside features) local goodies = specification.goodies if goodies and goodies ~= "" then local normalgoodies = specification.features.normal.goodies if not normalgoodies or normalgoodies == "" then specification.features.normal.goodies = goodies end end -- specification.hash = lower(specification.name .. ' @ ' .. tfm.hashfeatures(specification)) if specification.sub and specification.sub ~= "" then specification.hash = specification.sub .. ' @ ' .. specification.hash end return specification end --[[ldx--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.
We need to cache when possible. We do cache raw tfm data (from
Watch out, here we do load a font, but we don't prepare the specification yet.
--ldx]]-- function tfm.read(specification) local hash = tfm.hashinstance(specification) local tfmtable = tfm.fonts[hash] -- hashes by size ! if not tfmtable then local forced = specification.forced or "" if forced ~= "" then local reader = readers[lower(forced)] tfmtable = reader and reader(specification) if not tfmtable then report_defining("forced type %s of %s not found",forced,specification.name) end else for s=1,#sequence do -- reader sequence local reader = sequence[s] if readers[reader] then -- not really needed if trace_defining then report_defining("trying (reader sequence driven) type %s for %s with file %s",reader,specification.name,specification.filename or "unknown") end tfmtable = readers[reader](specification) if tfmtable then break else specification.filename = nil end end end end if tfmtable then if directive_embedall then tfmtable.embedding = "full" elseif tfmtable.filename and fonts.dontembed[tfmtable.filename] then tfmtable.embedding = "no" else tfmtable.embedding = "subset" end -- fonts.goodies.postprocessors.apply(tfmdata) -- only here local postprocessors = tfmtable.postprocessors if postprocessors then for i=1,#postprocessors do local extrahash = postprocessors[i](tfmtable) -- after scaling etc if type(extrahash) == "string" and extrahash ~= "" then -- e.g. a reencoding needs this extrahash = gsub(lower(extrahash),"[^a-z]","-") tfmtable.fullname = format("%s-%s",tfmtable.fullname,extrahash) end end end -- 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 if not tfmtable then report_defining("font with asked name '%s' is not found using lookup '%s'",specification.name,specification.lookup) end return tfmtable end --[[ldx--For virtual fonts we need a slightly different approach:
--ldx]]-- function tfm.readanddefine(name,size) -- no id local specification = definers.analyze(name,size) local method = specification.method if method and variants[method] then specification = variants[method](specification) end specification = definers.resolve(specification) local hash = tfm.hashinstance(specification) local id = definers.registered(hash) if not id then local tfmdata = tfm.read(specification) if tfmdata then tfmdata.hash = hash id = font.define(tfmdata) definers.register(tfmdata,id) tfm.cleanuptable(tfmdata) else id = 0 -- signal end end return fonts.identifiers[id], id end --[[ldx--We need to check for default features. For this we provide a helper function.
--ldx]]-- function definers.check(features,defaults) -- nb adapts features ! local done = false if features and next(features) then for k,v in next, defaults do if features[k] == nil then features[k], done = v, true end end else features, done = table.fastcopy(defaults), true end return features, done -- done signals a change end --[[ldx--So far the specifiers. 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).
In the previously defined reader (the one resulting in aWe overload both the