if not modules then modules = { } end modules ['font-fbk'] = { 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 cos, tan, rad, format = math.cos, math.tan, math.rad, string.format local utfbyte, utfchar = utf.byte, utf.char local next = next local trace_visualize = false trackers.register("fonts.composing.visualize", function(v) trace_visualize = v end) local trace_define = false trackers.register("fonts.composing.define", function(v) trace_define = v end) local report = logs.reporter("fonts","composing") local allocate = utilities.storage.allocate local fonts = fonts local handlers = fonts.handlers local constructors = fonts.constructors local helpers = fonts.helpers local otf = handlers.otf local afm = handlers.afm local registerotffeature = otf.features.register local registerafmfeature = afm.features.register local addotffeature = otf.addfeature local unicodecharacters = characters.data local unicodefallbacks = characters.fallbacks local vfcommands = helpers.commands local charcommand = vfcommands.char local rightcommand = vfcommands.right local downcommand = vfcommands.down local upcommand = vfcommands.up local push = vfcommands.push local pop = vfcommands.pop local force_combining = false -- just for demo purposes (see mk) local fraction = 0.15 -- 30 units for lucida -- todo: we also need to update the feature hashes ... i'll do that when i'm in the mood -- and/or when i need it -- \starttext -- \definefontfeature[default][default][fakecombining=yes,compose=yes] -- \setupbodyfont[minion] -- [x][\char"2D9][x][\char"323] ṭḍṃḥ -- \stoptext local missing = { -- [0x323] = { kind = "bottom", top = 0x307 }, -- dot below [0x323] = { kind = "bottom", top = 0x2D9 }, -- dot below -- -- TODO -- } local function fakecharacters(tfmdata,value) if value then local characters = tfmdata.characters local descriptions = tfmdata.descriptions for unicode, detail in next, missing do local c = characters[k] if not c then local kind = detail.kind if kind == "bottom" then local u = detail.top local d = characters[u] if d then local ex = tfmdata.parameters.xheight characters[unicode] = { width = d.width, height = 0, depth = ex/2, commands = { { "offset", 0, -d.height - ex/5 , u } }, unicode = unicode, } -- we need this for the composer local d = descriptions[u] local x = descriptions[0x78] if d and x then local t = table.copy(d) local x = -x.boundingbox[4]/2 local b = t.boundingbox b[2] = x b[4] = x descriptions[unicode] = t end if trace then report("deriving %C from %C",unicode,u) end end end end end end end local specification = { name = "fakecombining", description = "add missing combining accents", manipulators = { -- position = 1, -- todo: just before compose base = fakecharacters, node = fakecharacters, } } fonts.handlers.otf.features.register(specification) local function composecharacters(tfmdata) -- this assumes that slot 1 is self, there will be a proper self some day local characters = tfmdata.characters local descriptions = tfmdata.descriptions local parameters = tfmdata.parameters local properties = tfmdata.properties local Xdesc = descriptions[utfbyte("X")] local xdesc = descriptions[utfbyte("x")] if Xdesc and xdesc then local scale = parameters.factor or 1 local deltaxheight = scale * (Xdesc.boundingbox[4] - xdesc.boundingbox[4]) local extraxheight = fraction * deltaxheight -- maybe use compose value local italicfactor = parameters.italicfactor or 0 local red, green, blue, black if trace_visualize then red = { "startcolor", "red" } green = { "startcolor", "green" } blue = { "startcolor", "blue" } black = { "stopcolor" } end local compose = fonts.goodies.getcompositions(tfmdata) if compose and trace_visualize then report("using compose information from goodies file") end local done = false for i, c in next, unicodecharacters do -- loop over all characters ... not that efficient but a specials hash takes memory if force_combining or not characters[i] then local s = c.specials if s and s[1] == 'char' then local chr = s[2] local charschr = characters[chr] if charschr then local cc = c.category if cc == 'll' or cc == 'lu' or cc == 'lt' then -- characters.is_letter[cc] local acc = s[3] local t = { } for k, v in next, charschr do if k ~= "commands" then t[k] = v end end local charsacc = characters[acc] -- local ca = charsacc.category -- if ca == "mn" then -- -- mark nonspacing -- elseif ca == "ms" then -- -- mark spacing combining -- elseif ca == "me" then -- -- mark enclosing -- else if not charsacc then -- fallback accents acc = unicodefallbacks[acc] charsacc = acc and characters[acc] end local chr_t = charcommand[chr] if charsacc then if trace_define then report("composed %C, base %C, accent %C",i,chr,acc) end local acc_t = charcommand[acc] local cb = descriptions[chr] local ab = descriptions[acc] local cb = cb and cb.boundingbox local ab = ab and ab.boundingbox -- todo: adapt height if cb and ab then local c_llx = scale*cb[1] local c_lly = scale*cb[2] local c_urx = scale*cb[3] local c_ury = scale*cb[4] local a_llx = scale*ab[1] local a_lly = scale*ab[2] local a_urx = scale*ab[3] local a_ury = scale*ab[4] local done = false if compose then local i_compose = compose[i] local i_anchored = i_compose and i_compose.anchored if i_anchored then local c_compose = compose[chr] local a_compose = compose[acc] local c_anchors = c_compose and c_compose.anchors local a_anchors = a_compose and a_compose.anchors if c_anchors and a_anchors then local c_anchor = c_anchors[i_anchored] local a_anchor = a_anchors[i_anchored] if c_anchor and a_anchor then local cx = c_anchor.x or 0 local cy = c_anchor.y or 0 local ax = a_anchor.x or 0 local ay = a_anchor.y or 0 local dx = cx - ax local dy = cy - ay if trace_define then report("building %C from %C and %C",i,chr,acc) report(" boundingbox:") report(" chr: %3i %3i %3i %3i",unpack(cb)) report(" acc: %3i %3i %3i %3i",unpack(ab)) report(" anchors:") report(" chr: %3i %3i",cx,cy) report(" acc: %3i %3i",ax,ay) report(" delta:") report(" %s: %3i %3i",i_anchored,dx,dy) end local right = rightcommand[scale*dx] local down = upcommand[scale*dy] if trace_visualize then t.commands = { push, right, down, green, acc_t, black, pop, chr_t, } else t.commands = { push, right, down, acc_t, pop, chr_t, } end done = true end end end end if not done then -- can be sped up for scale == 1 local dx = (c_urx - a_urx - a_llx + c_llx)/2 local dd = (c_urx - c_llx)*italicfactor if a_ury < 0 then local right = rightcommand[dx-dd] if trace_visualize then t.commands = { push, right, red, acc_t, black, pop, chr_t, } else t.commands = { push, right, acc_t, pop, chr_t, } end t.depth = a_ury elseif c_ury > a_lly then -- messy test local dy if compose then -- experimental: we could use sx but all that testing -- takes time and code dy = compose[i] if dy then dy = dy.dy end if not dy then dy = compose[acc] if dy then dy = dy and dy.dy end end if not dy then dy = compose.dy end if not dy then dy = - deltaxheight + extraxheight elseif dy > -1.5 and dy < 1.5 then -- we assume a fraction of (percentage) dy = - dy * deltaxheight else -- we assume fontunits (value smaller than 2 make no sense) dy = - dy * scale end else dy = - deltaxheight + extraxheight end t.height = a_ury-dy local right = rightcommand[dx+dd] local down = downcommand[dy] if trace_visualize then t.commands = { push, right, down, green, acc_t, black, pop, chr_t, } else t.commands = { push, right, down, acc_t, pop, chr_t, } end else local right = rightcommand[dx+dd] if trace_visualize then t.commands = { push, right, blue, acc_t, black, pop, chr_t, } else t.commands = { push, right, acc_t, pop, chr_t, } end t.height = a_ury end end else t.commands = { chr_t, -- else index mess } end else if trace_define then report("%C becomes simplified %C",i,chr) end t.commands = { chr_t, -- else index mess } end done = true characters[i] = t local d = { } for k, v in next, descriptions[chr] do d[k] = v end descriptions[i] = d end end end end end end end local specification = { name = "compose", description = "additional composed characters", manipulators = { base = composecharacters, node = composecharacters, } } registerotffeature(specification) registerafmfeature(specification) addotffeature { name = "char-ligatures", type = "ligature", data = characters.splits.char, order = { "char-ligatures" }, prepend = true, } addotffeature { name = "compat-ligatures", type = "ligature", data = characters.splits.compat, order = { "compat-ligatures" }, prepend = true, } registerotffeature { name = 'char-ligatures', description = 'unicode char specials to ligatures', } registerotffeature { name = 'compat-ligatures', description = 'unicode compat specials to ligatures', } -- We now provide the composer in the helpers namespace (too): fonts.helpers.composecharacters = composecharacters -- We keep this just because we have a few demos but there often are other ways to achieve -- the same. This code installs the builder into the regular virtual font builder, which -- only makes sense as demo. It also shows a bit what the evolution is. do local vf = handlers.vf local commands = vf.combiner.commands vf.helpers.composecharacters = composecharacters commands["compose.trace.enable"] = function() trace_visualize = true end commands["compose.trace.disable"] = function() trace_visualize = false end commands["compose.force.enable"] = function() force_combining = true end commands["compose.force.disable"] = function() force_combining = false end commands["compose.trace.set"] = function(g,v) if v[2] == nil then trace_visualize = true else trace_visualize = v[2] end end commands["compose.apply"] = function(g,v) composecharacters(g) end end