How to preserve parskip in vsplit?
If you do want the box to have the exact height, LuaTeX supports
\setbox1=\vsplit 0 upto 2\baselineskip
as a built-in alternative to
\setbox1=\vsplit 0 to 2\baselineskip
\setbox1=\vbox{\unvbox1}
As you already seen, this does not really fix the problem because the parskip is already discarded by the \vsplit
.
But LuaTeX also allows saving and inspecting the discarded elements from a split from Lua, so we can reinsert the parskip:
\directlua{
function my_split()
% Scan the parameters
local result_box = token.scan_int()
token.scan_keyword'='
local original_box = token.scan_int()
token.scan_keyword'upto'
local height = token.scan_dimen()
% Temporarly set tex.savingvdiscards
local savediscards = tex.savingvdiscards
tex.savingvdiscards = 1
% Do the actual split
local b = tex.splitbox(original_box, height, 'additional')
tex.savingvdiscards = savediscards
% Analyze tex.lists.split_discards_head to find the parskip
local current = tex.lists.split_discards_head
if current then current.prev = nil end
while current do
if current.id == 12 and current.subtype == 3 then
local this = current
tex.lists.split_discards_head, current = node.remove(tex.lists.split_discards_head, current)
b.head = node.insert_after(b.head, nil, this)
b.height = b.height + this.width
else
current = current.next
end
end
% Save the result
tex.box[result_box] = b
end
}
\protected\def\setparsplit{\relax\directlua{my_split()}}
This \setparsplit
can be used as
\directlua{
function my_split()
% Scan the parameters
local result_box = token.scan_int()
token.scan_keyword'='
local original_box = token.scan_int()
token.scan_keyword'upto'
local height = token.scan_dimen()
% Temporarly set tex.savingvdiscards
local savediscards = tex.savingvdiscards
tex.savingvdiscards = 1
% Do the actual split
local b = tex.splitbox(original_box, height, 'additional')
tex.savingvdiscards = savediscards
% Analyze tex.lists.split_discards_head to find the parskip
local current = tex.lists.split_discards_head
if current then current.prev = nil end
while current do
if current.id == 12 and current.subtype == 3 then
local this = current
tex.lists.split_discards_head, current = node.remove(tex.lists.split_discards_head, current)
b.head = node.insert_after(b.head, nil, this)
b.height = b.height + this.width
else
current = current.next
end
end
% Save the result
tex.box[result_box] = b
end
}
\protected\def\setparsplit{\relax\directlua{my_split()}}
\parskip=7pt
\setbox0=\vbox{\input knuth\par}
\splittopskip=0pt
\setparsplit1=0 upto 2\baselineskip
\ifdim\dp1<\dp\strutbox
\dp1=\dp\strutbox
\fi
\box1
\box0
\bye
First of all, your example includes inexact dimension measurement. You set \splittopskip
to 0pt, so the result of box1 includes two lines with height of first line plus one baselineskip. But you say \vsplit to2\baselineskip
, so the rest to two baselineskips creates an empty space under these two lines inside the underfull box1. If you use \setbox1=\vbox{\unvbox1}
then this empty space is removed.
Moreover, the next box0 begins with the line of its variable height, so you cannot keep the uniform baseline distance between second and third line in our example even if you corrected the depth of box1. So, the solution of these problems are
- keep
\splittopskip
value 10pt - use trick
\penalty0
as first element and\vsplit0 to0pt
as first splitting operation: it adds topskip in the first line, so the first baselineskip is exactly 10pt from the top. - use more exactly calculated split point as 1 topskip plus (n-1) baselineskips.
The code, which does not solve the problem formulated by you but solves the problems mentioned by me follows:
\parskip=7pt
\setbox0=\vbox{\penalty0 \input knuth\par}
\splittopskip=10pt
\setbox1=\vsplit0 to0pt
\dimen0=\splittopskip \advance\dimen0 by 1\baselineskip
\setbox1=\vsplit 0 to \dimen0
\setbox1=\vbox{\unvbox1}
\ifdim\dp1<\dp\strutbox
\dp1=\dp\strutbox
\fi
\box1
\box0
\bye
Now, we can solve your problem. You can redefine \par
locally when data are read into box0 and you can check if the last penalty is 10001. If this is true then you can add \vskip\parskip
:
\parskip=7pt
\def\redefpar{\def\par{\endgraf \penalty10001 \penalty0 }}
\setbox0=\vbox{\penalty0 \redefpar \input knuth\par}
\splittopskip=10pt
\setbox1=\vsplit0 to0pt
\dimen0=\splittopskip \advance\dimen0 by 1\baselineskip
\setbox1=\vsplit 0 to \dimen0
\setbox1=\vbox{\unvbox1
\ifnum\lastpenalty=10001 \penalty0 \vskip\parskip\fi}
\ifdim\dp1<\dp\strutbox
\dp1=\dp\strutbox
\fi
\box1
\box0
\bye
This is heavily based on @wipet answer. I did a bit of experimenting with it and here is my version for better reconstructing vertical spacing.
(pdftex
by default has the e-TeX extensions which I use (unnecessarily) here once)
\parskip=7pt
\def\redefpar{\def\par{\endgraf \penalty10001 \penalty0 }}
\setbox0=\vbox{\penalty0 \redefpar \input knuth\par}
\splittopskip=10pt
\setbox1=\vsplit0 to0pt
\dimen0=\splittopskip \advance\dimen0 by 1\baselineskip
\setbox1=\vsplit 0 to \dimen0
\dimen0\dp1
\setbox1=\vbox{\unvbox1
\ifnum\lastpenalty=10001 \penalty0 \vskip\parskip\fi
}
\box1
\nointerlineskip
\kern-\dimen0
\vskip\dimexpr\baselineskip-\splittopskip\relax
\box0
\tracingoutput 1 %\showboxbreadth \maxdimen \showboxdepth \maxdimen
\bye
By the way, I am using this knuth.tex
file
Thus, I came to the conclusion that the designer of a new system must not only
be the implementer and first large--scale user; the designer should also write
the first user manual.
The separation of any of these four components would have hurt \TeX\
significantly. If I had not participated fully in all these activities,
literally hundreds of improvements would never have been made, because I would
never have thought of them or perceived why they were important.
But a system cannot be successful if it is too strongly influenced by a single
person. Once the initial design is complete and fairly robust, the real test
begins as people with many different viewpoints undertake their own
experiments.
I copied the source from the PDF documentation of ConTeXT where it appears in small print. I don't know where the original source can be found.