summaryrefslogtreecommitdiff
path: root/tex/context/base/mkiv/font-chk.lua
diff options
context:
space:
mode:
Diffstat (limited to 'tex/context/base/mkiv/font-chk.lua')
-rw-r--r--tex/context/base/mkiv/font-chk.lua462
1 files changed, 462 insertions, 0 deletions
diff --git a/tex/context/base/mkiv/font-chk.lua b/tex/context/base/mkiv/font-chk.lua
new file mode 100644
index 000000000..5b1ad9920
--- /dev/null
+++ b/tex/context/base/mkiv/font-chk.lua
@@ -0,0 +1,462 @@
+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"
+}
+
+-- possible optimization: delayed initialization of vectors
+-- move to the nodes namespace
+
+local next = next
+
+local formatters = string.formatters
+local bpfactor = number.dimenfactors.bp
+local fastcopy = table.fastcopy
+
+local report_fonts = 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 helpers = fonts.helpers
+
+local addprivate = helpers.addprivate
+local hasprivate = helpers.hasprivate
+local getprivatenode = helpers.getprivatenode
+
+local otffeatures = fonts.constructors.newfeatures("otf")
+local registerotffeature = otffeatures.register
+local afmfeatures = fonts.constructors.newfeatures("afm")
+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 nuts = nodes.nuts
+local tonut = nuts.tonut
+local tonode = nuts.tonode
+
+local getfont = nuts.getfont
+local getchar = nuts.getchar
+
+local setfield = nuts.setfield
+local setchar = nuts.setchar
+
+local traverse_id = nuts.traverse_id
+local remove_node = nuts.remove
+local insert_node_after = nuts.insert_after
+
+-- maybe in fonts namespace
+-- deletion can be option
+
+local action = false
+
+-- to tfmdata.properties ?
+
+local function onetimemessage(font,char,message) -- char == false returns table
+ local tfmdata = fontdata[font]
+ local shared = tfmdata.shared
+ 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 table.sortedkeys(category)
+ elseif not category[char] then
+ report_fonts("char %C in font %a with id %a: %s",char,tfmdata.properties.fullname,font,message)
+ category[char] = true
+ end
+end
+
+fonts.loggers.onetimemessage = onetimemessage
+
+local mapping = allocate { -- this is just an experiment to illustrate some principles elsewhere
+ lu = "placeholder uppercase red",
+ ll = "placeholder lowercase red",
+ lt = "placeholder uppercase red",
+ lm = "placeholder lowercase red",
+ lo = "placeholder lowercase red",
+ mn = "placeholder mark green",
+ mc = "placeholder mark green",
+ me = "placeholder mark green",
+ nd = "placeholder lowercase blue",
+ nl = "placeholder lowercase blue",
+ no = "placeholder lowercase blue",
+ pc = "placeholder punctuation cyan",
+ pd = "placeholder punctuation cyan",
+ ps = "placeholder punctuation cyan",
+ pe = "placeholder punctuation cyan",
+ pi = "placeholder punctuation cyan",
+ pf = "placeholder punctuation cyan",
+ po = "placeholder punctuation cyan",
+ sm = "placeholder lowercase magenta",
+ sc = "placeholder lowercase yellow",
+ sk = "placeholder lowercase yellow",
+ so = "placeholder lowercase yellow",
+}
+
+table.setmetatableindex(mapping,
+ function(t,k)
+ v = "placeholder unknown gray"
+ t[k] = v
+ return v
+ end
+)
+
+local fakes = allocate {
+ {
+ name = "lowercase",
+ code = ".025 -.175 m .425 -.175 l .425 .525 l .025 .525 l .025 -.175 l .025 0 l .425 0 l .025 -.175 m h S",
+ width = .45,
+ height = .55,
+ depth = .20,
+ },
+ {
+ name = "uppercase",
+ code = ".025 -.225 m .625 -.225 l .625 .675 l .025 .675 l .025 -.225 l .025 0 l .625 0 l .025 -.225 m h S",
+ width = .65,
+ height = .70,
+ depth = .25,
+ },
+ {
+ name = "mark",
+ code = ".025 .475 m .125 .475 l .125 .675 l .025 .675 l .025 .475 l h B",
+ width = .15,
+ height = .70,
+ depth = -.50,
+ },
+ {
+ name = "punctuation",
+ code = ".025 -.175 m .125 -.175 l .125 .525 l .025 .525 l .025 -.175 l h B",
+ width = .15,
+ height = .55,
+ depth = .20,
+ },
+ {
+ name = "unknown",
+ code = ".025 0 m .425 0 l .425 .175 l .025 .175 l .025 0 l h B",
+ width = .45,
+ height = .20,
+ depth = 0,
+ },
+}
+
+local variants = allocate {
+ { tag = "gray", r = .6, g = .6, b = .6 },
+ { tag = "red", r = .6, g = 0, b = 0 },
+ { tag = "green", r = 0, g = .6, b = 0 },
+ { tag = "blue", r = 0, g = 0, b = .6 },
+ { tag = "cyan", r = 0, g = .6, b = .6 },
+ { tag = "magenta", r = .6, g = 0, b = .6 },
+ { tag = "yellow", r = .6, g = .6, b = 0 },
+}
+
+local pdf_blob = "pdf: q %0.6F 0 0 %0.6F 0 0 cm %s %s %s rg %s %s %s RG 10 M 1 j 1 J 0.05 w %s Q"
+
+local cache = { } -- saves some tables but not that impressive
+
+local function addmissingsymbols(tfmdata) -- we can have an alternative with rules
+ local characters = tfmdata.characters
+ local size = tfmdata.parameters.size
+ local privates = tfmdata.properties.privates
+ local scale = size * bpfactor
+ for i=1,#variants do
+ local v = variants[i]
+ local tag, r, g, b = v.tag, v.r, v.g, v.b
+ for i =1, #fakes do
+ local fake = fakes[i]
+ local name = fake.name
+ local privatename = formatters["placeholder %s %s"](name,tag)
+ if not hasprivate(tfmdata,privatename) then
+ local hash = formatters["%s_%s_%s_%s_%s_%s"](name,tag,r,g,b,size)
+ local char = cache[hash]
+ if not char then
+ char = {
+ width = size*fake.width,
+ height = size*fake.height,
+ depth = size*fake.depth,
+ -- bah .. low level pdf ... should be a rule or plugged in
+ commands = { { "special", formatters[pdf_blob](scale,scale,r,g,b,r,g,b,fake.code) } }
+ }
+ cache[hash] = char
+ end
+ addprivate(tfmdata, privatename, char)
+ end
+ end
+ end
+end
+
+registerotffeature {
+ name = "missing",
+ description = "missing symbols",
+ manipulators = {
+ base = addmissingsymbols,
+ node = addmissingsymbols,
+ }
+}
+
+fonts.loggers.add_placeholders = function(id) addmissingsymbols(fontdata[id or true]) end
+fonts.loggers.category_to_placeholder = mapping
+
+function commands.getplaceholderchar(name)
+ local id = font.current()
+ addmissingsymbols(fontdata[id])
+ context(helpers.getprivatenode(fontdata[id],name))
+end
+
+local function placeholder(font,char)
+ local tfmdata = fontdata[font]
+ local properties = tfmdata.properties
+ local privates = properties.privates
+ local category = chardata[char].category
+ local fakechar = mapping[category]
+ local p = privates and privates[fakechar]
+ if not p then
+ addmissingsymbols(tfmdata)
+ p = properties.privates[fakechar]
+ end
+ if properties.lateprivates then
+ -- frozen already
+ return "node", getprivatenode(tfmdata,fakechar)
+ else
+ -- good, we have \definefontfeature[default][default][missing=yes]
+ return "char", p
+ end
+end
+
+checkers.placeholder = placeholder
+
+function checkers.missing(head)
+ local lastfont, characters, found = nil, nil, nil
+ head = tonut(head)
+ for n in traverse_id(glyph_code,head) do -- faster than while loop so we delay removal
+ local font = getfont(n)
+ local char = getchar(n)
+ if font ~= lastfont then
+ characters = fontcharacters[font]
+ lastfont = font
+ end
+ if not characters[char] and is_character[chardata[char].category] then
+ if action == "remove" then
+ onetimemessage(font,char,"missing (will be deleted)")
+ elseif action == "replace" then
+ onetimemessage(font,char,"missing (will be flagged)")
+ else
+ onetimemessage(font,char,"missing")
+ end
+ if not found then
+ found = { n }
+ else
+ found[#found+1] = n
+ end
+ end
+ end
+ if not found then
+ -- all well
+ elseif action == "remove" then
+ for i=1,#found do
+ head = remove_node(head,found[i],true)
+ end
+ elseif action == "replace" then
+ for i=1,#found do
+ local node = found[i]
+ local kind, char = placeholder(getfont(node),getchar(node))
+ if kind == "node" then
+ insert_node_after(head,node,tonut(char))
+ head = remove_node(head,node,true)
+ elseif kind == "char" then
+ setchar(node,char)
+ else
+ -- error
+ end
+ end
+ else
+ -- maye write a report to the log
+ end
+ return tonode(head), false
+end
+
+local relevant = {
+ "missing (will be deleted)",
+ "missing (will be flagged)",
+ "missing"
+}
+
+local function getmissing(id)
+ if id then
+ local list = getmissing(font.current())
+ if list then
+ local _, list = next(getmissing(font.current()))
+ return list
+ else
+ return { }
+ end
+ else
+ local t = { }
+ for id, d in next, fontdata do
+ local shared = d.shared
+ local messages = shared.messages
+ if messages then
+ local tf = t[d.properties.filename] or { }
+ for i=1,#relevant do
+ local tm = messages[relevant[i]]
+ if tm then
+ tf = table.merged(tf,tm)
+ end
+ end
+ if next(tf) then
+ t[d.properties.filename] = tf
+ end
+ end
+ end
+ for k, v in next, t do
+ t[k] = table.sortedkeys(v)
+ end
+ return t
+ end
+end
+
+checkers.getmissing = getmissing
+
+local tracked = false
+
+trackers.register("fonts.missing", function(v)
+ if v then
+ enableaction("processors","fonts.checkers.missing")
+ tracked = true
+ else
+ disableaction("processors","fonts.checkers.missing")
+ end
+ if v == "replace" then
+ otffeatures.defaults.missing = true
+ end
+ action = v
+end)
+
+local report_characters = logs.reporter("fonts","characters")
+local report_character = logs.reporter("missing")
+
+local logsnewline = logs.newline
+local logspushtarget = logs.pushtarget
+local logspoptarget = logs.poptarget
+
+luatex.registerstopactions(function()
+ if tracked then
+ local collected = checkers.getmissing()
+ if next(collected) then
+ logspushtarget("logfile")
+ for filename, list in table.sortedhash(collected) do
+ logsnewline()
+ report_characters("start missing characters: %s",filename)
+ logsnewline()
+ for i=1,#list do
+ local u = list[i]
+ report_character("%U %c %s",u,u,chardata[u].description)
+ end
+ logsnewline()
+ report_characters("stop missing characters")
+ logsnewline()
+ end
+ logspoptarget()
+ 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
+
+registerotffeature {
+ name = "dummies",
+ description = "dummy symbols",
+ default = true,
+ manipulators = {
+ base = adddummysymbols,
+ node = adddummysymbols,
+ }
+}
+
+registerafmfeature {
+ name = "dummies",
+ description = "dummy symbols",
+ default = true,
+ manipulators = {
+ base = adddummysymbols,
+ node = adddummysymbols,
+ }
+}
+
+-- callback.register("char_exists",function(f,c) -- to slow anyway as called often so we should flag in tfmdata
+-- return true
+-- end)