if not modules then modules = { } end modules ['font-ctx'] = { 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" } -- needs a cleanup: merge of replace, lang/script etc local texsprint, count, texsetcount = tex.sprint, tex.count, tex.setcount local format, concat, gmatch, match, find, lower, gsub, byte = string.format, table.concat, string.gmatch, string.match, string.find, string.lower, string.gsub, string.byte local tostring, next, type = tostring, next, type local lpegmatch = lpeg.match local round = math.round local ctxcatcodes = tex.ctxcatcodes local trace_defining = false trackers.register("fonts.defining", function(v) trace_defining = v end) local tfm = fonts.tfm local define = fonts.define local fontdata = fonts.identifiers local specify = define.specify specify.context_setups = specify.context_setups or { } specify.context_numbers = specify.context_numbers or { } specify.context_merged = specify.context_merged or { } specify.synonyms = specify.synonyms or { } local setups = specify.context_setups local numbers = specify.context_numbers local merged = specify.context_merged local synonyms = specify.synonyms local triggers = fonts.triggers --[[ldx--

So far we haven't really dealt with features (or whatever we want to pass along with the font definition. We distinguish the following situations:

situations:

name:xetex like specs name@virtual font spec name*context specification --ldx]]-- function specify.predefined(specification) local detail = specification.detail if detail ~= "" then -- detail = gsub(detail,"["..define.splitsymbols.."].*$","") -- get rid of *whatever specs and such if define.methods[detail] then -- since these may be appended at the specification.features.vtf = { preset = detail } -- tex end by default end end return specification end define.register_split("@", specify.predefined) storage.register("fonts/setups" , define.specify.context_setups , "fonts.define.specify.context_setups" ) storage.register("fonts/numbers", define.specify.context_numbers, "fonts.define.specify.context_numbers") storage.register("fonts/merged", define.specify.context_merged, "fonts.define.specify.context_merged") storage.register("fonts/synonyms", define.specify.synonyms, "fonts.define.specify.synonyms") local normalize_meanings = fonts.otf.meanings.normalize local settings_to_hash = aux.settings_to_hash local default_features = fonts.otf.features.default local function preset_context(name,parent,features) -- currently otf only if features == "" and find(parent,"=") then features = parent parent = "" end if features == "" then features = { } elseif type(features) == "string" then features = normalize_meanings(settings_to_hash(features)) else features = normalize_meanings(features) end -- todo: synonyms, and not otf bound if parent ~= "" then for p in gmatch(parent,"[^, ]+") do local s = setups[p] if s then for k,v in next, s do if features[k] == nil then features[k] = v end end end end end -- these are auto set so in order to prevent redundant definitions -- we need to preset them (we hash the features and adding a default -- setting during initialization may result in a different hash) for k,v in next, triggers do if features[v] == nil then -- not false ! local vv = default_features[v] if vv then features[v] = vv end end end -- sparse 'm so that we get a better hash and less test (experimental -- optimization) local t = { } -- can we avoid t ? for k,v in next, features do if v then t[k] = v end end -- needed for dynamic features local number = (setups[name] and setups[name].number) or 0 if number == 0 then number = #numbers + 1 numbers[number] = name end t.number = number setups[name] = t return number, t end local function context_number(name) -- will be replaced local t = setups[name] if not t then return 0 elseif t.auto then local lng = tonumber(tex.language) local tag = name .. ":" .. lng local s = setups[tag] if s then return s.number or 0 else local script, language = languages.association(lng) if t.script ~= script or t.language ~= language then local s = table.fastcopy(t) local n = #numbers + 1 setups[tag] = s numbers[n] = tag s.number = n s.script = script s.language = language return n else setups[tag] = t return t.number or 0 end end else return t.number or 0 end end local function merge_context(currentnumber,extraname,option) local current = setups[numbers[currentnumber]] local extra = setups[extraname] if extra then local mergedfeatures, mergedname = { }, nil if option < 0 then if current then for k, v in next, current do if not extra[k] then mergedfeatures[k] = v end end end mergedname = currentnumber .. "-" .. extraname else if current then for k, v in next, current do mergedfeatures[k] = v end end for k, v in next, extra do mergedfeatures[k] = v end mergedname = currentnumber .. "+" .. extraname end local number = #numbers + 1 mergedfeatures.number = number numbers[number] = mergedname merged[number] = option setups[mergedname] = mergedfeatures return number -- context_number(mergedname) else return currentnumber end end local function register_context(fontnumber,extraname,option) local extra = setups[extraname] if extra then local mergedfeatures, mergedname = { }, nil if option < 0 then mergedname = fontnumber .. "-" .. extraname else mergedname = fontnumber .. "+" .. extraname end for k, v in next, extra do mergedfeatures[k] = v end local number = #numbers + 1 mergedfeatures.number = number numbers[number] = mergedname merged[number] = option setups[mergedname] = mergedfeatures return number -- context_number(mergedname) else return 0 end end specify.preset_context = preset_context specify.context_number = context_number specify.merge_context = merge_context specify.register_context = register_context local current_font = font.current local tex_attribute = tex.attribute local cache = { } -- concat might be less efficient than nested tables function fonts.withset(name,what) local zero = tex_attribute[0] local hash = zero .. "+" .. name .. "*" .. what local done = cache[hash] if not done then done = merge_context(zero,name,what) cache[hash] = done end tex_attribute[0] = done end function fonts.withfnt(name,what) local font = current_font() local hash = font .. "*" .. name .. "*" .. what local done = cache[hash] if not done then done = register_context(font,name,what) cache[hash] = done end tex_attribute[0] = done end function specify.show_context(name) return setups[name] or setups[numbers[name]] or setups[numbers[tonumber(name)]] or { } end local function split_context(features) return setups[features] or (preset_context(features,"","") and setups[features]) end specify.split_context = split_context function specify.context_tostring(name,kind,separator,yes,no,strict,omit) -- not used return aux.hash_to_string(table.merged(fonts[kind].features.default or {},setups[name] or {}),separator,yes,no,strict,omit) end local splitter = lpeg.splitat(",") function specify.starred(features) -- no longer fallbacks here local detail = features.detail if detail and detail ~= "" then features.features.normal = split_context(detail) else features.features.normal = { } end return features end define.register_split('*',specify.starred) -- define (two steps) local P, C, Cc = lpeg.P, lpeg.C, lpeg.Cc local space = P(" ") local spaces = space^0 local leftparent = (P"(") local rightparent = (P")") local value = C((leftparent * (1-rightparent)^0 * rightparent + (1-space))^1) local dimension = C((space/"" + P(1))^1) local rest = C(P(1)^0) local scale_none = Cc(0) local scale_at = P("at") * Cc(1) * spaces * dimension -- value local scale_sa = P("sa") * Cc(2) * spaces * dimension -- value local scale_mo = P("mo") * Cc(3) * spaces * dimension -- value local scale_scaled = P("scaled") * Cc(4) * spaces * dimension -- value local sizepattern = spaces * (scale_at + scale_sa + scale_mo + scale_scaled + scale_none) local splitpattern = spaces * value * spaces * rest local specification -- local get_specification = define.get_specification -- we can make helper macros which saves parsing (but normaly not -- that many calls, e.g. in mk a couple of 100 and in metafun 3500) function define.command_1(str) statistics.starttiming(fonts) local fullname, size = lpegmatch(splitpattern,str) local lookup, name, sub, method, detail = get_specification(fullname) if not name then logs.report("define font","strange definition '%s'",str) texsprint(ctxcatcodes,"\\fcglet\\somefontname\\defaultfontfile") elseif name == "unknown" then texsprint(ctxcatcodes,"\\fcglet\\somefontname\\defaultfontfile") else texsprint(ctxcatcodes,"\\fcxdef\\somefontname{",name,"}") end -- we can also use a count for the size if size and size ~= "" then local mode, size = lpegmatch(sizepattern,size) if size and mode then count.scaledfontmode = mode texsprint(ctxcatcodes,"\\def\\somefontsize{",size,"}") else count.scaledfontmode = 0 texsprint(ctxcatcodes,"\\let\\somefontsize\\empty") end elseif true then -- so we don't need to check in tex count.scaledfontmode = 2 texsprint(ctxcatcodes,"\\let\\somefontsize\\empty") else count.scaledfontmode = 0 texsprint(ctxcatcodes,"\\let\\somefontsize\\empty") end specification = define.makespecification(str,lookup,name,sub,method,detail,size) end local n = 0 -- we can also move rscale to here (more consistent) function define.command_2(global,cs,str,size,classfeatures,fontfeatures,classfallbacks,fontfallbacks,mathsize,textsize,relativeid) if trace_defining then logs.report("define font","memory usage before: %s",statistics.memused()) end -- name is now resolved and size is scaled cf sa/mo local lookup, name, sub, method, detail = get_specification(str or "") -- asome settings can be overloaded if lookup and lookup ~= "" then specification.lookup = lookup end if relativeid and relativeid ~= "" then -- experimental hook local id = tonumber(relativeid) or 0 specification.relativeid = id > 0 and id end specification.name = name specification.size = size specification.sub = (sub and sub ~= "" and sub) or specification.sub specification.mathsize = mathsize specification.textsize = textsize if detail and detail ~= "" then specification.method, specification.detail = method or "*", detail elseif specification.detail and specification.detail ~= "" then -- already set elseif fontfeatures and fontfeatures ~= "" then specification.method, specification.detail = "*", fontfeatures elseif classfeatures and classfeatures ~= "" then specification.method, specification.detail = "*", classfeatures end if fontfallbacks and fontfallbacks ~= "" then specification.fallbacks = fontfallbacks elseif classfallbacks and classfallbacks ~= "" then specification.fallbacks = classfallbacks end local tfmdata = define.read(specification,size) -- id not yet known if not tfmdata then logs.report("define font","unable to define %s as \\%s",name,cs) texsetcount("global","lastfontid",-1) elseif type(tfmdata) == "number" then if trace_defining then logs.report("define font","reusing %s with id %s as \\%s (features: %s/%s, fallbacks: %s/%s)",name,tfmdata,cs,classfeatures,fontfeatures,classfallbacks,fontfallbacks) end tex.definefont(global,cs,tfmdata) -- resolved (when designsize is used): texsprint(ctxcatcodes,format("\\def\\somefontsize{%isp}",fontdata[tfmdata].size)) texsetcount("global","lastfontid",tfmdata) else -- local t = os.clock(t) local id = font.define(tfmdata) -- print(name,os.clock()-t) tfmdata.id = id define.register(tfmdata,id) tex.definefont(global,cs,id) tfm.cleanup_table(tfmdata) if trace_defining then logs.report("define font","defining %s with id %s as \\%s (features: %s/%s, fallbacks: %s/%s)",name,id,cs,classfeatures,fontfeatures,classfallbacks,fontfallbacks) end -- resolved (when designsize is used): texsprint(ctxcatcodes,format("\\def\\somefontsize{%isp}",tfmdata.size)) --~ if specification.fallbacks then --~ fonts.collections.prepare(specification.fallbacks) --~ end texsetcount("global","lastfontid",id) end if trace_defining then logs.report("define font","memory usage after: %s",statistics.memused()) end statistics.stoptiming(fonts) end local enable_auto_r_scale = false experiments.register("fonts.autorscale", function(v) enable_auto_r_scale = v end) local calculate_scale = fonts.tfm.calculate_scale function fonts.tfm.calculate_scale(tfmtable, scaledpoints, relativeid) local scaledpoints, delta, units = calculate_scale(tfmtable, scaledpoints, relativeid) if enable_auto_r_scale and relativeid then -- for the moment this is rather context specific local relativedata = fontdata[relativeid] local id_x_height = relativedata and relativedata.parameters and relativedata.parameters.x_height local tf_x_height = id_x_height and tfmtable.parameters and tfmtable.parameters.x_height * delta if tf_x_height then scaledpoints = (id_x_height/tf_x_height) * scaledpoints delta = scaledpoints/units end end return scaledpoints, delta, units end --~ table.insert(readers.sequence,1,'vtf') --~ function readers.vtf(specification) --~ if specification.features.vtf and specification.features.vtf.preset then --~ return tfm.make(specification) --~ else --~ return nil --~ end --~ end -- we need a place for this .. outside the generic scope local dimenfactors = number.dimenfactors function fonts.dimenfactor(unit,tfmdata) if unit == "ex" then return (tfmdata and tfmdata.parameters.x_height) or 655360 elseif unit == "em" then return (tfmdata and tfmdata.parameters.em_height) or 655360 else return dimenfactors[unit] or unit end end function fonts.cleanname(name) texsprint(ctxcatcodes,fonts.names.cleanname(name)) end local p, f = 1, "%0.1fpt" -- normally this value is changed only once local stripper = lpeg.patterns.strip_zeros function fonts.nbfs(amount,precision) if precision ~= p then p = precision f = "%0." .. p .. "fpt" end texsprint(ctxcatcodes,lpegmatch(stripper,format(f,amount/65536))) end -- for the moment here, this will become a chain of extras that is -- hooked into the ctx registration (or scaler or ...) function fonts.set_digit_width(font) local tfmtable = fontdata[font] local parameters = tfmtable.parameters local width = parameters.digitwidth if not width then width = round(parameters.quad/2) -- maybe tex.scale local characters = tfmtable.characters for i=48,57 do local wd = round(characters[i].width) if wd > width then width = wd end end parameters.digitwidth = width end return width end fonts.get_digit_width = fonts.set_digit_width -- soon to be obsolete: local loaded = { -- prevent loading (happens in cont-sys files) ["original-base.map" ] = true, ["original-ams-base.map" ] = true, ["original-ams-euler.map"] = true, ["original-public-lm.map"] = true, } function fonts.map.loadfile(name) name = file.addsuffix(name,"map") if not loaded[name] then pdf.mapfile(name) loaded[name] = true end end local loaded = { -- prevent double loading } function fonts.map.loadline(how,line) if line then how = how .. " " .. line elseif how == "" then how = "= " .. line end if not loaded[how] then pdf.mapline(how) loaded[how] = true end end function fonts.map.reset() pdf.mapfile("") end fonts.map.reset() -- resets the default file -- we need an 'do after the banner hook' -- pdf.mapfile("mkiv-base.map") -- loads the default file local nounicode = byte("?") local function name_to_slot(name) -- maybe some day rawdata local tfmdata = fonts.ids[font.current()] local shared = tfmdata and tfmdata.shared local fntdata = shared and shared.otfdata or shared.afmdata if fntdata then local unicode = fntdata.luatex.unicodes[name] if not unicode then return nounicode elseif type(unicode) == "number" then return unicode else -- multiple unicodes return unicode[1] end end return nounicode end fonts.name_to_slot = name_to_slot function fonts.char(n) -- todo: afm en tfm if type(n) == "string" then n = name_to_slot(n) end if type(n) == "number" then texsprint(ctxcatcodes,format("\\char%s ",n)) end end -- moved from ini: fonts.color = { } -- dummy in ini local attribute = attributes.private('color') local mapping = (attributes and attributes.list[attribute]) or { } local set_attribute = node.set_attribute local unset_attribute = node.unset_attribute function fonts.color.set(n,c) local mc = mapping[c] if not mc then unset_attribute(n,attribute) else set_attribute(n,attribute,mc) end end function fonts.color.reset(n) unset_attribute(n,attribute) end -- this will become obsolete: fonts.otf.name_to_slot = name_to_slot fonts.afm.name_to_slot = name_to_slot fonts.otf.char = fonts.char fonts.afm.char = fonts.char -- this will change ... function fonts.show_char_data(n) local tfmdata = fonts.ids[font.current()] if tfmdata then if type(n) == "string" then n = utf.byte(n) end local chr = tfmdata.characters[n] if chr then write_nl(format("%s @ %s => U%04X => %s => ",tfmdata.fullname,tfmdata.size,n,utf.char(n)) .. serialize(chr,false)) end end end function fonts.show_font_parameters() local tfmdata = fonts.ids[font.current()] if tfmdata then local parameters, mathconstants = tfmdata.parameters, tfmdata.MathConstants local hasparameters, hasmathconstants = parameters and next(parameters), mathconstants and next(mathconstants) if hasparameters then write_nl(format("%s @ %s => parameters => ",tfmdata.fullname,tfmdata.size) .. serialize(parameters,false)) end if hasmathconstants then write_nl(format("%s @ %s => math constants => ",tfmdata.fullname,tfmdata.size) .. serialize(mathconstants,false)) end if not hasparameters and not hasmathconstants then write_nl(format("%s @ %s => no parameters and/or mathconstants",tfmdata.fullname,tfmdata.size)) end end end