example code for http://tex.stackexchange.com/q/119883 (tracking missing glyphs with luaotfload)
| ----------------------------------------------------------------------- | |
| -- FILE: track-missing-glyphs.lua | |
| -- USAGE: dofile "track-missing-glyphs.lua" | |
| -- DESCRIPTION: output a message if a font lacks a glyph for a | |
| -- codepoint | |
| -- REQUIREMENTS: luatex, luaotfload | |
| -- COPYRIGHT: Hans Hagen, Pragma ADE, Hasselt NL | |
| -- AUTHOR: Philipp Gesang (Phg), <phg42.2a@gmail.com> | |
| -- MODIFIED: 2013-06-22 12:39:26+0200 | |
| ----------------------------------------------------------------------- | |
| -- | |
| local stringformat = string.format | |
| local traverse_id = node.traverse_id | |
| local utf8char = unicode.utf8.char | |
| local fonthashes = fonts.hashes | |
| local fontdata = fonthashes.identifiers | |
| local chardata = characters.data | |
| local names_report = logs.names_report | |
| local glyph_t = nodes.nodecodes.glyph | |
| local nullfont = 0 | |
| --- namespace | |
| documentdata = documentdata or { } | |
| documentdata.missing_glyphs = documentdata.missing_glyphs or { } | |
| local missing_glyphs = documentdata.missing_glyphs | |
| local complain = function (font_id, char) | |
| local tfmdata = fontdata[font_id] | |
| if tfmdata then | |
| local fontname = tfmdata.fontname or "" | |
| names_report("both", 0, "missing glyph", | |
| "%d [%s] of %s", char, utf8char(char), fontname) | |
| else | |
| names_report("both", 0, "missing glyph", | |
| "%d [%s] of %d", char, utf8char(char), font_id) | |
| end | |
| end | |
| local fontcharacters = { } | |
| table.setmetatableindex(fontcharacters, function (t, k) | |
| if k == true then | |
| return fontcharacters[currentfont()] | |
| else | |
| local tfmdata = fontdata[k] | |
| if not tfmdata then --- unsafe | |
| tfmdata = font.fonts[k] | |
| if not (tfmdata and type (tfmdata) == "table") then | |
| return false | |
| end | |
| end | |
| local characters = tfmdata.characters | |
| t[k] = characters | |
| return characters | |
| end | |
| end) | |
| local initialize = function ( ) | |
| local chardef = assert(kpse.find_file ("char-def.lua", "lua"), | |
| "\nError: cannot find char-def.lua; \z | |
| please install Context.") | |
| dofile(chardef) --- will overwrite the partial character table | |
| chardata = characters.data | |
| return chardata | |
| end | |
| local is_character = table.tohash({ --- from char-ini.lua | |
| "lu", "ll", "lt", "lm", "lo", | |
| "nd", "nl", "no", | |
| "mn", | |
| "nl", "no", | |
| "pc", "pd", "ps", "pe", "pi", "pf", "po", | |
| "sm", "sc", "sk", "so" | |
| }) | |
| local once = false --- complain only once per glyph | |
| local missing = { } --- (font_id, glyph_id set) hash_t | |
| local initialized = false --- track loading of char-def for older versions | |
| local nodeprocessor = function (head) | |
| local lastfont, characters = nil, nil | |
| local missing = missing | |
| for n in traverse_id(glyph_t, head) do | |
| local font = n.font | |
| local char = n.char | |
| if missing[font] and missing[font][char] == true then | |
| --- already registered | |
| if once == false then | |
| complain(font, char) | |
| end | |
| else | |
| if font ~= lastfont and font ~= nullfont then | |
| characters = fontcharacters[font] | |
| end | |
| if characters ~= false then | |
| lastfont = font | |
| if not characters[char] then | |
| local category = chardata[char].category | |
| if not category then --- old luaotfload | |
| initialize() | |
| category = chardata[char].category | |
| end | |
| if category and is_character[category] then | |
| missing[font] = missing[font] or { } | |
| --- could have a counter and do some stats here | |
| missing[font][char] = true | |
| complain(font, char) | |
| end | |
| end | |
| end | |
| end | |
| end | |
| return head, false | |
| end | |
| local active = false | |
| local enable = function (parm) | |
| if active == false then | |
| if parm == "once" then | |
| once = true | |
| end | |
| luatexbase.add_to_callback( | |
| "pre_linebreak_filter", nodeprocessor, "user.missing_glyphs") | |
| luatexbase.add_to_callback( | |
| "hpack_filter", nodeprocessor, "user.missing_glyphs") | |
| active = true | |
| end | |
| end | |
| local disable = function ( ) | |
| if active == true then | |
| luatexbase.remove_from_callback( | |
| "pre_linebreak_filter", "user.missing_glyphs") | |
| luatexbase.remove_from_callback( | |
| "hpack_filter", "user.missing_glyphs") | |
| once = false | |
| active = false | |
| end | |
| end | |
| documentdata.missing_glyphs.enable = enable | |
| documentdata.missing_glyphs.disable = disable |
| \documentclass{article} | |
| \usepackage{luacode,fontspec} | |
| %% 1) initialize the tracker code (could go to separate file) | |
| \makeatletter | |
| \directlua{dofile "track_missing_glyphs.lua"} | |
| %% 2) Environment start: the optional argument “once”, in square | |
| %% brackets, requests that the missing glyph message be printed | |
| %% only once per character and font. | |
| \def\startreportmissingglyphs{% | |
| \@ifnextchar[\missingglyphs@start@indeed% | |
| {\missingglyphs@start@indeed[]}% | |
| } | |
| \def\missingglyphs@start@indeed[#1]{% | |
| \directlua{documentdata.missing_glyphs.enable"\luaescapestring{#1}"}% | |
| } | |
| %% 3) Environment stop: we need to force a \par here to | |
| %% have the callback apply to the current paragraph. | |
| \def\stopreportmissingglyphs{% | |
| \endgraf %% paragraph-based callback! | |
| \directlua{documentdata.missing_glyphs.disable()}% | |
| } | |
| \makeatother | |
| %% Usage examples. | |
| \begin{document} | |
| %% Latin modern lacks glyphs for the Greek script so we use that for | |
| %% testing. | |
| \startreportmissingglyphs | |
| Program the μC, please. | |
| %% Works in math mode (different font model) as well. | |
| $f = ma$ | |
| \stopreportmissingglyphs | |
| lorem schmipsum | |
| \startreportmissingglyphs[once] | |
| %% With the “once” flag, no message is emitted for repetitions of | |
| %% missing chars. | |
| Θάλαττα, θάλαττα. | |
| \stopreportmissingglyphs | |
| \end{document} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment