if not modules then modules = { } end modules ['font-chk'] = { 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" } -- This is kind of old but it makes no real sense to upgrade it to for instance -- using delayed type 3 fonts in order to be lean and mean and cut'n'paste -- compliant. When this kicks one needs to fix the choice of fonts anyway! So, -- instead we just keep the method we use but slightly adapted to the backend -- of lmtx. local next = next local floor = math.floor local context = context local formatters = string.formatters local bpfactor = number.dimenfactors.bp local fastcopy = table.fastcopy local sortedkeys = table.sortedkeys local sortedhash = table.sortedhash local report = logs.reporter("fonts") local report_checking = logs.reporter("fonts","checking") local allocate = utilities.storage.allocate local fonts = fonts fonts.checkers = fonts.checkers or { } local checkers = fonts.checkers local fonthashes = fonts.hashes local fontdata = fonthashes.identifiers local fontcharacters = fonthashes.characters local currentfont = font.current local addcharacters = font.addcharacters local helpers = fonts.helpers local addprivate = helpers.addprivate local hasprivate = helpers.hasprivate local getprivateslot = helpers.getprivateslot local getprivatecharornode = helpers.getprivatecharornode local otffeatures = fonts.constructors.features.otf local afmfeatures = fonts.constructors.features.afm local registerotffeature = otffeatures.register local registerafmfeature = afmfeatures.register local is_character = characters.is_character local chardata = characters.data local tasks = nodes.tasks local enableaction = tasks.enableaction local disableaction = tasks.disableaction local implement = interfaces.implement local glyph_code = nodes.nodecodes.glyph local hpack_node = node.hpack local nuts = nodes.nuts local tonut = nuts.tonut local isglyph = nuts.isglyph local setchar = nuts.setchar local nextglyph = nuts.traversers.glyph local remove_node = nuts.remove local insert_node_after = nuts.insert_after local action = false -- to tfmdata.properties ? local function onetimemessage(font,char,message) -- char == false returns table local tfmdata = fontdata[font] local shared = tfmdata.shared if not shared then shared = { } tfmdata.shared = shared end local messages = shared.messages if not messages then messages = { } shared.messages = messages end local category = messages[message] if not category then category = { } messages[message] = category end if char == false then return sortedkeys(category), category end local cc = category[char] if not cc then report_checking("char %C in font %a with id %a: %s",char,tfmdata.properties.fullname,font,message) category[char] = 1 else category[char] = cc + 1 end end fonts.loggers.onetimemessage = onetimemessage local fakes = { lowercase = { width = .45, height = .55, depth = .20 }, uppercase = { width = .65, height = .70, depth = .25 }, mark = { width = .15, height = .70, depth = -.50 }, punctuation = { width = .15, height = .55, depth = .20 }, unknown = { width = .45, height = .20, depth = 0 }, } local mapping = allocate { lu = { "uppercase", "darkred" }, ll = { "lowercase", "darkred" }, lt = { "uppercase", "darkred" }, lm = { "lowercase", "darkred" }, lo = { "lowercase", "darkred" }, mn = { "mark", "darkgreen" }, mc = { "mark", "darkgreen" }, me = { "mark", "darkgreen" }, nd = { "lowercase", "darkblue" }, nl = { "lowercase", "darkblue" }, no = { "lowercase", "darkblue" }, pc = { "punctuation", "darkcyan" }, pd = { "punctuation", "darkcyan" }, ps = { "punctuation", "darkcyan" }, pe = { "punctuation", "darkcyan" }, pi = { "punctuation", "darkcyan" }, pf = { "punctuation", "darkcyan" }, po = { "punctuation", "darkcyan" }, sm = { "lowercase", "darkmagenta" }, sc = { "lowercase", "darkyellow" }, sk = { "lowercase", "darkyellow" }, so = { "lowercase", "darkyellow" }, } table.setmetatableindex(mapping, { "unknown", "darkgray" }) -- We provide access by (private) name for tracing purposes. We also need -- to make sure the dimensions are known at the lua and tex end. local cache = { } local function add(tfmdata,name,color,size,collected) local hash = formatters["%s_%s_%i"](name,color,floor(size)) local chardata = cache[hash] if not chardata then local fake = fakes[name] local width = size * fake.width local height = size * fake.height local depth = size * fake.depth chardata = { width = width, height = height, depth = depth, commands = { { "frame", width, height, depth, 65536/5, false, true, true, color }, } } cache[hash] = chardata end if not hasprivate(tfmdata,privatename) then local privatename = formatters["placeholder %s %s"](name,color) local privatecode = addprivate(tfmdata, privatename, chardata) collected[privatecode] = chardata end return chardata end local function addplaceholder(font,char) local tfmdata = fontdata[font or true] local characters = tfmdata.characters local size = tfmdata.parameters.size local scale = size * bpfactor local collected = { } local category = chardata[char].category or "unknown" local fakedata = mapping[category] local chardata = add(tfmdata,fakedata[1],fakedata[2],size,collected) collected [char] = chardata characters[char] = chardata addcharacters(font, { characters = collected }) return "char", char -- needed for math-noa end -- For old times sake we keep this: a whole bunch of fake symbols local function addplaceholders(tfmdata) local properties = tfmdata.properties local size = tfmdata.parameters.size local scale = size * bpfactor local collected = { } local colors = { "darkred", "darkgreen", "darkblue", "darkcyan", "darkmagenta", "darkyellow", "darkgray" } for name, v in sortedhash(fakes) do for i=1,#colors do add(tfmdata,name,colors[i],size,collected) end end if next(collected) then local id = properties.id if id then addcharacters(id, { characters = collected }) end end end registerotffeature { name = "missing", description = "missing symbols", manipulators = { base = addplaceholders, node = addplaceholders, } } -- fonts.loggers.add_placeholders = function(id) addplaceholders(fontdata[id or true]) end -- fonts.loggers.category_to_placeholder = mapping checkers.placeholder = addplaceholder function checkers.missing(head) local lastfont = nil local characters = nil if action == "replace" then for n, char, font in nextglyph, head do if font ~= lastfont then lastfont = font characters = fontcharacters[font] end if font > 0 and not characters[char] and is_character[chardata[char].category or "unknown"] then onetimemessage(font,char,"missing (will be flagged)") addplaceholder(font,char) end end elseif action == "remove" then -- faster than while loop so we delay removal local found = nil for n, char, font in nextglyph, head do if font ~= lastfont then lastfont = font characters = fontcharacters[font] end if font > 0 and not characters[char] and is_character[chardata[char].category or "unknown"] then onetimemessage(font,char,"missing (will be deleted)") if not found then found = { n } else found[#found+1] = n end end end if found then for i=1,#found do head = remove_node(head,found[i],true) end end else for n, char, font in nextglyph, head do if font ~= lastfont then lastfont = font characters = fontcharacters[font] end if font > 0 and not characters[char] and is_character[chardata[char].category or "unknown"] then onetimemessage(font,char,"missing") end end end return head end local relevant = { "missing (will be deleted)", "missing (will be flagged)", "missing" } local function getmissing(id) if id then local list = getmissing(currentfont()) if list then local _, list = next(getmissing(currentfont())) return list else return { } end else local t = { } for id, d in next, fontdata do local shared = d.shared local messages = shared and shared.messages if messages then local filename = d.properties.filename if not filename then filename = tostring(d) end local tf = t[filename] or { } for i=1,#relevant do local tm = messages[relevant[i]] if tm then for k, v in next, tm do tf[k] = (tf[k] or 0) + v end end end if next(tf) then t[filename] = tf end end end local l = { } for k, v in next, t do l[k] = sortedkeys(v) end return l, t end end checkers.getmissing = getmissing do local reported = true callback.register("glyph_not_found",function(font,char) if font > 0 then if char > 0 then onetimemessage(font,char,"missing") else -- we have a special case end elseif not reported then report("nullfont is used, maybe no bodyfont is defined") reported = true end end) trackers.register("fonts.missing", function(v) if v then enableaction("processors","fonts.checkers.missing") else disableaction("processors","fonts.checkers.missing") end if v == "replace" then otffeatures.defaults.missing = true end action = v end) logs.registerfinalactions(function() local collected, details = getmissing() if next(collected) then for filename, list in sortedhash(details) do logs.startfilelogging(report,"missing characters",filename) for u, v in sortedhash(list) do report("%4i %U %c %s",v,u,u,chardata[u].description) end logs.stopfilelogging() end if logs.loggingerrors() then for filename, list in sortedhash(details) do logs.starterrorlogging(report,"missing characters",filename) for u, v in sortedhash(list) do report("%4i %U %c %s",v,u,u,chardata[u].description) end logs.stoperrorlogging() end end end end) end -- for the moment here local function expandglyph(characters,index,done) done = done or { } if not done[index] then local data = characters[index] if data then done[index] = true local d = fastcopy(data) local n = d.next if n then d.next = expandglyph(characters,n,done) end local h = d.horiz_variants if h then for i=1,#h do h[i].glyph = expandglyph(characters,h[i].glyph,done) end end local v = d.vert_variants if v then for i=1,#v do v[i].glyph = expandglyph(characters,v[i].glyph,done) end end return d end end end helpers.expandglyph = expandglyph -- should not be needed as we add .notdef in the engine local dummyzero = { -- width = 0, -- height = 0, -- depth = 0, commands = { { "special", "" } }, } local function adddummysymbols(tfmdata) local characters = tfmdata.characters if not characters[0] then characters[0] = dummyzero end -- if not characters[1] then -- characters[1] = dummyzero -- test only -- end end local dummies_specification = { name = "dummies", description = "dummy symbols", default = true, manipulators = { base = adddummysymbols, node = adddummysymbols, } } registerotffeature(dummies_specification) registerafmfeature(dummies_specification) -- local function addvisualspace(tfmdata) local spacechar = tfmdata.characters[32] if spacechar and not spacechar.commands then local w = spacechar.width local h = tfmdata.parameters.xheight local c = { width = w, commands = { { "rule", h, w } } } local u = addprivate(tfmdata, "visualspace", c) end end local visualspace_specification = { name = "visualspace", description = "visual space", default = true, manipulators = { base = addvisualspace, node = addvisualspace, } } registerotffeature(visualspace_specification) registerafmfeature(visualspace_specification)