TeX - LaTeX Asked on October 4, 2021
This is a follow-up question connected to this: Non-breakable space after isolated one letter
After testing proposed solution on plain text, the finalizer
works. However, after using it in document containing sectioning commands (title
, but even others) ConTeXt process crashes.
MWE:
startluacode
function userdata.prevent_single_letter(head)
local cur = head
while cur do
if cur.id == node.id("glyph") then
if cur.prev.id == node.id("glue") and cur.next.id == node.id("glue") then
local p = node.new("penalty")
p.penalty = 10000
-- This is for debugging only, but then you have to
-- remove the last node.insert_after line:
--local w = node.new("whatsit","pdf_literal")
--w.data = "q 1 0 1 RG 1 0 1 rg 0 0 m 0 5 l 2 5 l 2 0 l b Q"
--node.insert_after(head,cur,w)
--node.insert_after(head,w,p)
node.insert_after(head,cur,p)
end
end
cur = cur.next
end
return head, true
end
stopluacode
startluacode
nodes.tasks.appendaction("processors", "before", "userdata.prevent_single_letter")
stopluacode
starttext
title[title:poznamky]{Notes z ConTeX{}t}
Filling text filling text filling text filling text filling text filling text filling text fil V text
stoptext
Even more trubling is, that I am unable to get any meaningfull error message other than the issue is caused by title
. The only readable error message I am getting from console:
hpack filter: error: [directlua]:6: attempt to index a nil value (field 'prev')
I have no idea what is hpack_filter
. In order to next time be able to debug issues I am hitting upon a little better, what reference for ConTeXt should I use?
The problem comes from the fact that the ConTeXt processors
run both on pre_linebreak_filter
and on hpack_filter
. In pre_linebreak_filter
the problem will not show up because the first node is always local_par
. In hpack_filter
, however, the first node is very likely a glyph
which is a problem because there will be no other node preceding it, i.e. the prev
field is nil
. Right now the code just queries the id
property of the prev
field but since the prev
field doesn't exist in the first place, this fails. We can work around this by harnessing a property of the and logical and
(and or
) operator which is called short-circuit evaluation.
More information on those callbacks in ConTeXt can be found in the manual MkIV Hybrid Technology, in particular the tables in Chapter 9 “Callbacks” (source).
The common idiom in Lua is to guard this access using a statement like
if cur.prev and cur.prev.id == ... then
When the left-hand side operand evaluates to false
, the right-hand side operand is not even evaluated and the composite expression results in false
. This is because false and <anything>
will always be false
by Boolean logic, irregardless of the value of <anything>
.
In Lua this idiom extends even further, because the language assigns a “truthiness” to all values. The values nil
and false
are always considered “false-y” whereas everything else is considered “true-y” (in particular the integer values 0
and 1
both evaluate “true-y” even though you might expect 0
to be “false-y” as it is in C-like languages). On top of that, a logical statement always returns the last operand that was evaluated. This allows for constructs like
userdata = userdata or {}
which you see a lot in the ConTeXt core Lua code. What this does is that it evaluates userdata
and if this is nil
(which is considered “false-y”) it evaluates the {}
(which is considered “true-y”) and returns the empty table from the expression which is then assigned to userdata
. This is a great way to quickly write a guarded variable initialization which does nothing when the variable is already initialized.
In the present case, we can actually fuse the two nested if
statements together by using the short-circuit evaluation property of and
. This makes the code quite a bit more compact and you might gain an infinitesimal performance improvement. What's probably more relevant for performance is caching the IDs of the glyph
and glue
nodes, because these never change.
startluacode
local glyph_id = node.id("glyph")
local glue_id = node.id("glue")
function userdata.prevent_single_letter(head)
local cur = head
while cur do
if cur.id == glyph_id and
cur.prev and
cur.next and
cur.prev.id == glue_id and
cur.next.id == glue_id
then
local p = node.new("penalty")
p.penalty = 10000
node.insert_after(head,cur,p)
end
cur = cur.next
end
return head, true
end
stopluacode
startluacode
nodes.tasks.appendaction("processors", "before", "userdata.prevent_single_letter")
stopluacode
starttext
title[title:poznamky]{Notes z ConTeX{}t}
Filling text filling text filling text filling text filling text filling text filling text fil V text
stoptext
Correct answer by Henri Menke on October 4, 2021
Get help from others!
Recent Answers
Recent Questions
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP