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
\vsplitentirely and do it in LuaTeX... perhaps that may be interesting as then one could manually remove the last glue or whatever, after checking that it isn't the parskip glue. (Just guesswork, haven't tried anything and don't even know much about\vsplit, and this sort of solution may not work for you...) – ShreevatsaR Dec 10 '18 at 03:52\parskipis already discarded after\vsplitand not only at\unvboxas I had suspected. That complicates the matter a bit. – Henri Menke Dec 10 '18 at 05:20\parskipglue or every kind of glue found between paragraphs? For instance, if two paragraphs are separated by a\bigskip, do you want to reinsert also that glue? A final hint: @FrankMittelbach’s suggestion of using\splitdiscards(an e-TeX extension) is probably the key to the simplest, most general, and most elegant solution. – GuM Dec 14 '18 at 21:33