You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
375 lines
12 KiB
375 lines
12 KiB
-----------------------------------------------------------------------
|
|
-- FILE: luaotfload-notdef.lua
|
|
-- DESCRIPTION: part of luaotfload / notdef
|
|
-----------------------------------------------------------------------
|
|
|
|
local ProvidesLuaModule = {
|
|
name = "luaotfload-notdef",
|
|
version = "3.14", --TAGVERSION
|
|
date = "2020-05-06", --TAGDATE
|
|
description = "luaotfload submodule / color",
|
|
license = "GPL v2.0",
|
|
author = "Marcel Krüger"
|
|
}
|
|
|
|
if luatexbase and luatexbase.provides_module then
|
|
luatexbase.provides_module (ProvidesLuaModule)
|
|
end
|
|
|
|
local harfbuzz = luaotfload.harfbuzz
|
|
local flush_node = node.direct.flush_node
|
|
local getfont = font.getfont
|
|
local getnext = node.direct.getnext
|
|
local getwhd = node.direct.getwhd
|
|
local insert = table.insert
|
|
local insert_after = node.direct.insert_after
|
|
local kern_id = node.id'kern'
|
|
local disc_id = node.id'disc'
|
|
local nodenew = node.direct.new
|
|
local nodecopy = node.direct.copy
|
|
local otfregister = fonts.constructors.features.otf.register
|
|
local protect_glyph = node.direct.protect_glyph
|
|
local remove = node.direct.remove
|
|
local setfont = node.direct.setfont
|
|
local traverse_char = node.direct.traverse_char
|
|
local traverse_id = node.direct.traverse_id
|
|
local setchar = node.direct.setchar
|
|
local getwidth = node.direct.getwidth
|
|
local setkern = node.direct.setkern
|
|
local setattributelist = node.direct.setattributelist
|
|
local getattributelist = node.direct.getattributelist
|
|
local setmove = luaotfload.fontloader.nodes.injections.setmove
|
|
|
|
-- According to DerivedCoreProperties.txt, Default_Ignorable_Code_Point
|
|
-- is generated from:
|
|
-- Other_Default_Ignorable_Code_Point
|
|
-- + Cf (Format characters)
|
|
-- + Variation_Selector
|
|
-- - White_Space
|
|
-- - FFF9..FFFB (Interlinear annotation format characters)
|
|
-- - 13430..13438 (Egyptian hieroglyph format characters)
|
|
-- - Prepended_Concatenation_Mark (Exceptional format characters that should be visible)
|
|
-- Based on HarfBuzz, we add the exclusion
|
|
-- - Lo (Letter, Other)
|
|
-- This affects Hangul fillers.
|
|
local ignorable_codepoints do
|
|
local sep = lpeg.P' '^0 * ';' * lpeg.P' '^0
|
|
local codepoint = lpeg.S'0123456789ABCDEF'^4/function(c)return tonumber(c, 16)end
|
|
local codepoint_range = codepoint * ('..' * codepoint + lpeg.Cc(false))
|
|
local function multirawset(table, key1, key2, value)
|
|
for key = key1,(key2 or key1) do
|
|
rawset(table, key, value)
|
|
end
|
|
return table
|
|
end
|
|
local entry = lpeg.Cg(codepoint * ';' * (1-lpeg.P';')^0 * ';Cf;' * lpeg.Cc(true))^-1 * (1-lpeg.P'\n')^0 * '\n'
|
|
local file = lpeg.Cf(
|
|
lpeg.Ct''
|
|
* entry^0
|
|
, rawset)
|
|
local f = io.open(kpse.find_file"UnicodeData.txt")
|
|
ignorable_codepoints = file:match(f:read'*a')
|
|
f:close()
|
|
entry = lpeg.Cg(codepoint_range * sep * ('Other_Default_Ignorable_Code_Point' * lpeg.Cc(true)
|
|
+ 'Variation_Selector' * lpeg.Cc(true)
|
|
+ 'White_Space' * lpeg.Cc(nil)
|
|
+ 'Prepended_Concatenation_Mark' * lpeg.Cc(nil)
|
|
) * ' # ' * (1-lpeg.P'Lo'))^-1 * (1-lpeg.P'\n')^0 * '\n'
|
|
file = lpeg.Cf(
|
|
lpeg.Carg(1)
|
|
* entry^0
|
|
, multirawset)
|
|
f = io.open(kpse.find_file"PropList.txt")
|
|
ignorable_codepoints = file:match(f:read'*a', 1, ignorable_codepoints)
|
|
f:close()
|
|
for i = 0xFFF9,0xFFFB do
|
|
ignorable_codepoints[i] = nil
|
|
end
|
|
for i = 0x13430,0x13438 do
|
|
ignorable_codepoints[i] = nil
|
|
end
|
|
end
|
|
|
|
local function setnotdef(tfmdata, factor)
|
|
local desc = tfmdata.shared.rawdata.descriptions
|
|
-- So we have to find the .notdef glyph. We only know that it has GID
|
|
-- 0, but we need it's Unicode mapping. Normally it isn't mapped in
|
|
-- the font, so we auto-assigned the first private slot:
|
|
local guess = desc[0xF0000]
|
|
if guess and guess.index == 0 then
|
|
tfmdata.notdefcode = 0xF0000
|
|
return
|
|
end
|
|
-- If this didn't happen, it might be mapped to one of the
|
|
-- replacement characters:
|
|
for code = 0xFFFC,0xFFFF do
|
|
guess = desc[code]
|
|
if guess and guess.index == 0 then
|
|
tfmdata.notdefcode = code
|
|
return
|
|
end
|
|
end
|
|
-- Oh no, we couldn't find it. Maybe we can find it by name?
|
|
local code = tfmdata.resources.unicodes[".notdef"]
|
|
-- Better safe than sorry
|
|
guess = code and desc[code]
|
|
if guess and guess.index == 0 then
|
|
tfmdata.notdefcode = code
|
|
return
|
|
end
|
|
-- So the font didn't do the obvious things and then it lied to us.
|
|
-- At this point we should think about sending an automated complain
|
|
-- to the font author, but we probably can't trust the contact
|
|
-- information either.
|
|
-- We will fall back to brute force now:
|
|
for code, char in pairs(desc) do
|
|
if char.index == 0 then
|
|
tfmdata.notdefcode = code
|
|
return
|
|
end
|
|
end
|
|
-- If we ever reach this point, something odd happened. Either there
|
|
-- are no glyphs at all (then LuaTeX will complain anyway, so let's
|
|
-- ignore that case) or someone tried to use this with a legacy font.
|
|
-- In that case there most likely isn't a `.notdef` glyph anyway and
|
|
-- inserting glyph 0 would insert a random character, so `notdefcode`
|
|
-- better stays `nil`.
|
|
end
|
|
|
|
local glyph_id = node.id'glyph'
|
|
local function donotdef(head, font, _, _, _)
|
|
local tfmdata = getfont(font)
|
|
local notdef, chars = tfmdata.unscaled.notdefcode, tfmdata.characters
|
|
if not notdef then return end
|
|
for cur, cid, fid in traverse_char(head) do if fid == font then
|
|
local w, h, d = getwhd(cur)
|
|
if w == 0 and h == 0 and d == 0 and not chars[cid] and not ignorable_codepoints[cid] then
|
|
local notdefnode = nodenew(glyph_id, 256)
|
|
setfont(notdefnode, font, notdef)
|
|
insert_after(cur, cur, notdefnode)
|
|
protect_glyph(cur)
|
|
end
|
|
end end
|
|
end
|
|
|
|
otfregister {
|
|
name = "notdef",
|
|
description = "Add notdef glyphs",
|
|
default = 1,
|
|
initializers = {
|
|
node = setnotdef,
|
|
},
|
|
processors = {
|
|
node = donotdef,
|
|
}
|
|
}
|
|
|
|
local font_invisible_replacement = setmetatable({}, {__index = function(t, fid)
|
|
local fontdata = font.getfont(fid)
|
|
local replacement = fontdata.shared.features.invisible
|
|
if replacement == "remove" then
|
|
t[fid] = false
|
|
return false
|
|
end
|
|
replacement = tonumber(replacement) or 32
|
|
local char = fontdata.characters[replacement]
|
|
if char then
|
|
t[fid] = {replacement, -char.width}
|
|
return t[fid]
|
|
else
|
|
t[fid] = false
|
|
return false
|
|
end
|
|
end})
|
|
|
|
local push, pop do
|
|
local function checkprop(n)
|
|
local p = node.direct.getproperty(n)
|
|
return p and p.zwnj
|
|
end
|
|
local list = {}
|
|
function push(head)
|
|
head = node.direct.todirect(head)
|
|
local l = {}
|
|
list[#list+1] = l
|
|
for n, id in node.direct.traverse(head) do
|
|
if checkprop(n) then
|
|
head = node.direct.remove(head, n)
|
|
l[#l+1] = n
|
|
elseif id == disc_id then
|
|
local pre, post, replace = node.direct.getdisc(n)
|
|
for nn in node.direct.traverse(pre) do
|
|
if checkprop(nn) then
|
|
local after
|
|
pre, after = node.direct.remove(pre, nn)
|
|
l[#l+1] = {nn, n, 'pre'}
|
|
end
|
|
end
|
|
for nn in node.direct.traverse(post) do
|
|
if checkprop(nn) then
|
|
post = node.direct.remove(post, nn)
|
|
l[#l+1] = {nn, n, 'post'}
|
|
end
|
|
end
|
|
for nn in node.direct.traverse(replace) do
|
|
if checkprop(nn) then
|
|
replace = node.direct.remove(replace, nn)
|
|
l[#l+1] = {nn, n, 'replace'}
|
|
end
|
|
end
|
|
node.direct.setdisc(n, pre, post, replace)
|
|
end
|
|
end
|
|
return head
|
|
end
|
|
local getfield, setfield = node.direct.getfield, node.direct.setfield
|
|
local function pop(head)
|
|
head = node.direct.todirect(head)
|
|
local l = list[#list]
|
|
list[#list] = nil
|
|
for i = #l,1,-1 do
|
|
local e = l[i]
|
|
local n = tonumber(e)
|
|
local disc, thishead
|
|
if n then
|
|
thishead = head
|
|
else
|
|
disc, n = e[2], e[1]
|
|
thishead = getfield(disc, e[3])
|
|
end
|
|
local prev, next = node.direct.getboth(n)
|
|
if prev or not next then
|
|
thishead = node.direct.insert_after(thishead, prev, n)
|
|
else
|
|
thishead = node.direct.insert_before(thishead, next, n)
|
|
end
|
|
if disc then
|
|
setfield(disc, e[3], thishead)
|
|
else
|
|
head = thishead
|
|
end
|
|
end
|
|
return head
|
|
end
|
|
fonts.handlers.otf.handlers.marked_push = push
|
|
fonts.handlers.otf.handlers.marked_pop = pop
|
|
end
|
|
local sequence1 = {
|
|
features = {["semiignored-node"] = {["*"] = {["*"] = true}}},
|
|
flags = {false, false, false, false},
|
|
name = "semiignored-node",
|
|
order = {"semiignored-node"},
|
|
type = "marked_push",
|
|
}
|
|
local sequence2 = {
|
|
features = {["semiignored-node"] = {["*"] = {["*"] = true}}},
|
|
flags = {false, false, false, false},
|
|
name = "semiignored-node",
|
|
order = {"semiignored-node"},
|
|
type = "marked_pop",
|
|
}
|
|
local function pushpopinitialiser(tfmdata, value, features)
|
|
local resources = tfmdata.resources
|
|
local sequences = resources and resources.sequences
|
|
local first_gpos, last_gpos
|
|
if sequences then
|
|
local alreadydone
|
|
for i=1,#sequences do
|
|
local sequence = sequences[i]
|
|
if sequence1 == sequence then
|
|
return
|
|
elseif sequence.type:sub(1,5) == "gpos_" then
|
|
if not first_gpos then
|
|
first_gpos = i
|
|
end
|
|
last_gpos = i
|
|
end
|
|
end
|
|
if first_gpos then
|
|
insert(sequences, last_gpos+1, sequence2)
|
|
insert(sequences, first_gpos, sequence1)
|
|
end
|
|
end
|
|
end
|
|
|
|
otfregister {
|
|
name = 'semiignored-node',
|
|
description = 'Allow adding nodes which break ligatures but do not affect kerning',
|
|
default = true, -- Should basically never be disabled manually
|
|
initializers = {
|
|
node = pushpopinitialiser,
|
|
-- plug = ? -- TODO: Manually handle in luaotfload-harf-plug.lua
|
|
},
|
|
}
|
|
|
|
ignorable_replacement = {}
|
|
|
|
local delayed_remove do
|
|
local delayed
|
|
function delayed_remove(n)
|
|
flush_node(delayed)
|
|
delayed = n
|
|
end
|
|
end
|
|
|
|
local function ignorablehandler(head, fid, ...) -- FIXME: The arguments are probably wrong
|
|
local fontparam = font_invisible_replacement[fid]
|
|
local replacement = fontparam and fontparam[1]
|
|
local font_kern = fontparam and fontparam[2]
|
|
for n, c, f in traverse_char(head) do if f == fid then
|
|
local lookup = ignorable_codepoints[c]
|
|
if lookup then
|
|
if replacement then
|
|
setchar(n, replacement)
|
|
if font_kern then
|
|
local k = nodenew(kern_id)
|
|
setkern(k, font_kern)
|
|
setattributelist(k, getattributelist(n))
|
|
head = insert_after(head, n, k)
|
|
end
|
|
else
|
|
local after
|
|
head, after = remove(head, n)
|
|
delayed_remove(n)
|
|
end
|
|
end
|
|
end end
|
|
delayed_remove()
|
|
for n in traverse_id(head, disc_id) do
|
|
local a, b, c = getdisc(n)
|
|
setdisc(ignorablehandler(a, fid), ignorablehandler(b, fid), ignorablehandler(c, fid))
|
|
end
|
|
return head
|
|
end
|
|
|
|
if harfbuzz then
|
|
local harf_settings = luaotfload.harf
|
|
local preserve_flag = harfbuzz.Buffer.FLAG_PRESERVE_DEFAULT_IGNORABLES or 0
|
|
local remove_flag = harfbuzz.Buffer.FLAG_REMOVE_DEFAULT_IGNORABLES or 0
|
|
local dotted_circle_flag = harfbuzz.Buffer.FLAG_DO_NOT_INSERT_DOTTED_CIRCLE or 0
|
|
harf_settings.default_buf_flags = (harf_settings.default_buf_flags & ~remove_flag) | preserve_flag | dotted_circle_flag
|
|
local function dottedcircleinitialize(tfmdata, value)
|
|
if not tfmdata.hb then return end
|
|
local hb = tfmdata.hb
|
|
hb.buf_flags = hb.buf_flags & ~dotted_circle_flag
|
|
end
|
|
otfregister {
|
|
name = 'dottedcircle',
|
|
description = 'Insert dotted circle to fix invalid clusters',
|
|
default = true,
|
|
initializers = {
|
|
plug = dottedcircleinitialize,
|
|
},
|
|
}
|
|
end
|
|
otfregister {
|
|
name = 'invisible',
|
|
description = 'Remove invisible control characters',
|
|
default = true,
|
|
processors = {
|
|
node = ignorablehandler,
|
|
plug = ignorablehandler,
|
|
},
|
|
}
|
|
|
|
--- vim:sw=2:ts=2:expandtab:tw=71
|
|
|