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.
351 lines
13 KiB
351 lines
13 KiB
require('polyglossia') -- just in case...
|
|
|
|
local add_to_callback = luatexbase.add_to_callback
|
|
local remove_from_callback = luatexbase.remove_from_callback
|
|
local priority_in_callback = luatexbase.priority_in_callback
|
|
local new_attribute = luatexbase.new_attribute
|
|
|
|
local node = node
|
|
|
|
local insert_node_before = node.insert_before
|
|
local insert_node_after = node.insert_after
|
|
local remove_node = node.remove
|
|
local has_attribute = node.has_attribute
|
|
local node_copy = node.copy
|
|
local new_node = node.new
|
|
local end_of_math = node.end_of_math
|
|
local getnext = node.getnext
|
|
local getprev = node.getprev
|
|
|
|
-- node types according to node.types()
|
|
local glue_code = node.id"glue"
|
|
local glyph_code = node.id"glyph"
|
|
local penalty_code = node.id"penalty"
|
|
local kern_code = node.id"kern"
|
|
local math_code = node.id"math"
|
|
|
|
-- we need some node subtypes
|
|
local userkern = 1
|
|
local userskip = 0
|
|
local removable_skip = {
|
|
[0] = true, -- userskip
|
|
[13] = true, -- spaceskip
|
|
[14] = true, -- xspaceskip
|
|
}
|
|
|
|
-- we make a new node, so that we can copy it later on
|
|
local kern_node = new_node(kern_code)
|
|
kern_node.subtype = userkern -- this kern can be removed later on
|
|
|
|
local function get_kern_node(dim)
|
|
local n = node_copy(kern_node)
|
|
n.kern = dim
|
|
return n
|
|
end
|
|
|
|
local glue_node = new_node(glue_code)
|
|
glue_node.subtype = userskip
|
|
|
|
local function get_glue_node(dim, stretch, shrink)
|
|
local n = node_copy(glue_node)
|
|
n.width = dim
|
|
n.stretch = stretch
|
|
n.shrink = shrink
|
|
return n
|
|
end
|
|
|
|
local penalty_node = new_node(penalty_code)
|
|
penalty_node.penalty = 10000
|
|
|
|
local function get_penalty_node()
|
|
return node_copy(penalty_node)
|
|
end
|
|
|
|
-- all possible space characters according to section 6.2 of the Unicode Standard
|
|
-- https://www.unicode.org/versions/Unicode12.0.0/ch06.pdf
|
|
local space_chars = {
|
|
[0x20] = true, -- space
|
|
[0xA0] = true, -- no-break space
|
|
[0x1680] = true, -- ogham space mark
|
|
[0x2000] = true, -- en quad
|
|
[0x2001] = true, -- em quad
|
|
[0x2002] = true, -- en space
|
|
[0x2003] = true, -- em space
|
|
[0x2004] = true, -- three-per-em-space
|
|
[0x2005] = true, -- four-per-em space
|
|
[0x2006] = true, -- six-per-em space
|
|
[0x2007] = true, -- figure space
|
|
[0x2008] = true, -- punctuation space
|
|
[0x2009] = true, -- thin space
|
|
[0x200A] = true, -- hair space
|
|
[0x202F] = true, -- narrow no-break space
|
|
[0x205F] = true, -- medium mathematical space
|
|
[0x3000] = true -- ideographic space
|
|
}
|
|
|
|
-- all left bracket characters, referenced by their Unicode slot
|
|
local left_bracket_chars = {
|
|
[0x28] = true, -- left parenthesis
|
|
[0x5B] = true, -- left square bracket
|
|
[0x7B] = true, -- left curly bracket
|
|
[0x27E8] = true -- mathematical left angle bracket
|
|
}
|
|
|
|
-- all right bracket characters, referenced by their Unicode slot
|
|
local right_bracket_chars = {
|
|
[0x29] = true, -- right parenthesis
|
|
[0x5D] = true, -- right square bracket
|
|
[0x7D] = true, -- right curly bracket
|
|
[0x27E9] = true -- mathematical right angle bracket
|
|
}
|
|
|
|
-- question and exclamation marks, referenced by their Unicode slot
|
|
local question_exclamation_chars = {
|
|
[0x21] = true, -- exclamation mark !
|
|
[0x3F] = true, -- question mark ?
|
|
[0x203C] = true, -- double exclamation mark ‼
|
|
[0x203D] = true, -- interrobang ‽
|
|
[0x2047] = true, -- double question mark ⁇
|
|
[0x2048] = true, -- question exclamation mark ⁈
|
|
[0x2049] = true, -- exclamation question mark ⁉
|
|
}
|
|
|
|
-- from nodes-tst.lua, adapted
|
|
local function somespace(n)
|
|
if n then
|
|
local id, subtype = n.id, n.subtype
|
|
if id == glue_code then
|
|
-- it is dangerous to remove all the type of glue
|
|
return removable_skip[subtype]
|
|
elseif id == kern_code then
|
|
-- remove only user's kern
|
|
return subtype == userkern
|
|
elseif id == glyph_code then
|
|
return space_chars[n.char]
|
|
end
|
|
end
|
|
end
|
|
|
|
local function someleftbracket(n)
|
|
if n then
|
|
local id = n.id
|
|
if id == glyph_code then
|
|
return left_bracket_chars[n.char]
|
|
end
|
|
end
|
|
end
|
|
|
|
local function somerightbracket(n)
|
|
if n then
|
|
local id = n.id
|
|
if id == glyph_code then
|
|
return right_bracket_chars[n.char]
|
|
end
|
|
end
|
|
end
|
|
|
|
local function question_exclamation_sequence(n1, n2)
|
|
if n1 and n2 then
|
|
local id1 = n1.id
|
|
local id2 = n2.id
|
|
if id1 == glyph_code and id2 == glyph_code then
|
|
return question_exclamation_chars[n1.char] and question_exclamation_chars[n2.char]
|
|
end
|
|
end
|
|
end
|
|
|
|
-- idem
|
|
local function somepenalty(n, value)
|
|
if n then
|
|
local id = n.id
|
|
if id == penalty_code then
|
|
if value then
|
|
return n.penalty == value
|
|
else
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local punct_attr = new_attribute("polyglossia_punct")
|
|
|
|
local lang_id = {}
|
|
local lang_counter = 0
|
|
local left_space = {}
|
|
local right_space = {}
|
|
|
|
local function ensure_lang_id(lang)
|
|
if not lang_id[lang] then
|
|
lang_counter = lang_counter + 1
|
|
lang_id[lang] = lang_counter
|
|
end
|
|
return lang_id[lang]
|
|
end
|
|
|
|
local function clear_spaced_characters(lang)
|
|
local id = ensure_lang_id(lang)
|
|
left_space[id] = {}
|
|
right_space[id] = {}
|
|
end
|
|
|
|
local function illegal_unit(unit)
|
|
if unit then
|
|
texio.write_nl('Illegal spacing unit "'..unit..'".')
|
|
else
|
|
texio.write_nl('Spacing unit is a nil value.')
|
|
end
|
|
end
|
|
|
|
local function add_left_spaced_character(lang, char, kern, unit, rubber)
|
|
-- The parameter kern is a number meant as a fraction of the unit.
|
|
-- The unit can be "quad" (1em) or "space" (interword space).
|
|
-- The parameter rubber is a Boolean value indicating if the inserted space is
|
|
-- stretchable and shrinkable (only relevant if the unit is "space").
|
|
local id = ensure_lang_id(lang)
|
|
if unit == "quad" or unit == "space" then
|
|
left_space[id][char] = {}
|
|
left_space[id][char]["kern"] = kern
|
|
left_space[id][char]["unit"] = unit
|
|
left_space[id][char]["rubber"] = rubber
|
|
else
|
|
illegal_unit(unit)
|
|
end
|
|
end
|
|
|
|
local function add_right_spaced_character(lang, char, kern, unit, rubber)
|
|
local id = ensure_lang_id(lang)
|
|
if unit == "quad" or unit == "space" then
|
|
right_space[id][char] = {}
|
|
right_space[id][char]["kern"] = kern
|
|
right_space[id][char]["unit"] = unit
|
|
right_space[id][char]["rubber"] = rubber
|
|
else
|
|
illegal_unit(unit)
|
|
end
|
|
end
|
|
|
|
-- from typo-spa.lua, adapted
|
|
local function process(head)
|
|
local current = head
|
|
while current do
|
|
local id = current.id
|
|
if id == glyph_code then
|
|
local attr = has_attribute(current, punct_attr)
|
|
if attr then
|
|
local char = utf8.char(current.char) -- requires Lua 5.3
|
|
local leftspace = left_space[attr][char]
|
|
local rightspace = right_space[attr][char]
|
|
if leftspace or rightspace then
|
|
local fontparameters = fonts.hashes.parameters[current.font]
|
|
local unit, stretch, shrink, spacing_node
|
|
if leftspace and fontparameters then
|
|
local prev = getprev(current)
|
|
local space_exception = false
|
|
if prev then
|
|
local prevprev = getprev(prev)
|
|
-- do not add space after left (opening) bracket and between question/exclamation marks
|
|
space_exception = someleftbracket(prev) or question_exclamation_sequence(prev, current)
|
|
if somespace(prev) then
|
|
-- TODO: there is a question here: do we override a preceding space or not?...
|
|
if somepenalty(prevprev, 10000) then
|
|
head = remove_node(head, prevprev)
|
|
end
|
|
head = remove_node(head, prev)
|
|
end
|
|
end
|
|
if leftspace.unit == "quad" then
|
|
unit = fontparameters.quad
|
|
spacing_node = get_kern_node(leftspace.kern*unit)
|
|
elseif leftspace.unit == "space" then
|
|
unit = fontparameters.space
|
|
if leftspace.rubber then
|
|
stretch = leftspace.kern*fontparameters.space_stretch
|
|
shrink = leftspace.kern*fontparameters.space_shrink
|
|
spacing_node = get_glue_node(leftspace.kern*unit, stretch, shrink)
|
|
head = insert_node_before(head, current, get_penalty_node())
|
|
else
|
|
spacing_node = get_kern_node(leftspace.kern*unit)
|
|
end
|
|
end
|
|
if not space_exception then
|
|
head = insert_node_before(head, current, spacing_node)
|
|
end
|
|
end
|
|
if rightspace and fontparameters then
|
|
local next = getnext(current)
|
|
local space_exception = false
|
|
if next then
|
|
-- do not add space before right (closing) bracket
|
|
space_exception = somerightbracket(next)
|
|
local nextnext = getnext(next)
|
|
if somepenalty(next, 10000) and somespace(nextnext) then
|
|
head = remove_node(head, next)
|
|
head = remove_node(head, nextnext)
|
|
elseif somespace(next) then
|
|
head = remove_node(head, next)
|
|
end
|
|
end
|
|
if rightspace.unit == "quad" then
|
|
unit = fontparameters.quad
|
|
spacing_node = get_kern_node(rightspace.kern*unit)
|
|
elseif rightspace.unit == "space" then
|
|
unit = fontparameters.space
|
|
if rightspace.rubber then
|
|
stretch = rightspace.kern*fontparameters.space_stretch
|
|
shrink = rightspace.kern*fontparameters.space_shrink
|
|
spacing_node = get_glue_node(rightspace.kern*unit, stretch, shrink)
|
|
if not space_exception then
|
|
head, current = insert_node_after(head, current, get_penalty_node())
|
|
end
|
|
else
|
|
spacing_node = get_kern_node(rightspace.kern*unit)
|
|
end
|
|
end
|
|
if not space_exception then
|
|
head, current = insert_node_after(head, current, spacing_node)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
elseif id == math_code then
|
|
-- warning: this is a feature of luatex > 0.76
|
|
current = end_of_math(current) -- weird, can return nil .. no math end?
|
|
end
|
|
current = getnext(current) -- no error even if current is nil
|
|
end
|
|
return head
|
|
end
|
|
|
|
local function activate(lang)
|
|
local id = ensure_lang_id(lang)
|
|
-- We set the punctuation attribute to a language id here. This is
|
|
-- important to be able to intermix languages with different spacings
|
|
-- in one paragraph.
|
|
tex.setattribute(punct_attr, id)
|
|
for _, callback_name in ipairs{ "pre_linebreak_filter", "hpack_filter" } do
|
|
if not priority_in_callback(callback_name, "polyglossia-punct.process") then
|
|
add_to_callback(callback_name, process, "polyglossia-punct.process", 1)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function deactivate()
|
|
tex.setattribute(punct_attr, -0x7FFFFFFF) -- this value means "unset"
|
|
-- Though it would make compilation slightly faster, it is not possible to
|
|
-- safely uncomment the following lines. Imagine the following case: you
|
|
-- start a paragraph by some spaced punctuation text, then, in the same
|
|
-- paragraph, you change the language to something else, and thus call the
|
|
-- following lines. This means that, at the end of the paragraph, the
|
|
-- function won't be in the callback, so the beginning of the paragraph
|
|
-- won't be processed by it.
|
|
-- if priority_in_callback(callback_name, "polyglossia-punct.process") then
|
|
-- remove_from_callback(callback_name, "polyglossia-punct.process")
|
|
-- end
|
|
end
|
|
|
|
polyglossia.activate_punct = activate
|
|
polyglossia.deactivate_punct = deactivate
|
|
polyglossia.add_left_spaced_character = add_left_spaced_character
|
|
polyglossia.add_right_spaced_character = add_right_spaced_character
|
|
polyglossia.clear_spaced_characters = clear_spaced_characters
|
|
|