@wipet in his answer has shown how to resolve the problem as long as we can assume that that the \prevdepth
of all material in the document is sufficiently small (that it lies below \maxdepth
in fact). In that case we can use the depth of box 255 as a measure for the \prevdepth
calculations that will have taken place if there is any remainder in recent contributions, and use this to adjust the new depth to match that. An if there isn't any remainder then this doesn't really matter either. This trick is in fact already mentioned by Don Knuth in the TeXbook where he discusses an output routine that add index headings in random places in the text (and normal text has this nice property of having a depth smaller than \maxdepth
... normally but unfortunately not always.
However, this stops working if this can't guaranteed and the general case is what I was and am after. He is also correct in stating that finding out what the value of \prevdepth
on the main vertical list is isn't really going to help us as such, so my question above was partly incorrect: we also need to know if there is in fact something on the recent contributions and if so deal with it.
So what I tried to get my head around over the last days if that is really impossible to find out within base TeX (or if it needs an extension in luatex or is already available there and resolved in ConTeXt ... would be interesting to learn about it beside that claim @Martin you made). And as it stands now it is possible in TeX after all. The solution is a bit complex and perhaps it can be simplified further but it is not so complex that it can't be used (and hopefully not too complex that I overlooked some cases).
The main idea is not to try to determine the situation directly but instead use 2 output routines in succession that just ensure that the recent contributions are empty. Along the way we gather enough information to afterwards dissect the material gathered and do whatever we like.
My initial idea of using \aftergroup
to regain control and have a look doesn't work because the token inserted that way will not be executed when the output routine ends. Instead TeX immediately calls again on a procedure "buildpage" that takes anything on recent contributions and moves it to the main vertical list and only once that has happened my inserted token is looked at (in other words too late).
So the more complicated approach is that the first OR puts box255 back but only it has changed \vsize
to the largest possible dimen. Additionally it uses \aftergroup
so that we gain control later again. As we have changed \vsize
we will get everything including the material recent contributions onto the main vertical list and only then our control token kicks in. Finally we change the output routine to a second one and then return.
The token inserted by \aftergroup
will then issue a forcing penalty (actually it does a little more, see below) so that everything is grabbed and the second OR is called.
Inside that OR we are now in a better situation:
- we know that recent contributions is empty (except for a (
\penalty 10000
)
- we can store away what was gathered in box255
- or we could manipulate it using
\vsplit
etc to get, for example the amount split of that we would have gotten in the first OR
- and we can use
\aftergroup
to gain control after the OR has ended.
- the latter allows us to change
\prevdepth
on the main vertical list to represent whatever is needed
And that should (I believe) do the trick completely.
Here is the (more or less documented code including a bit of test data to play out various scenarios). It is a bit longish but largely because I tried to properly document the most important aspects and some of the more subtle points. Enjoy:
\tracingoutput1
\showboxbreadth\maxdimen\showboxdepth\maxdimen
\tracingpages1
\tracingonline1
\vsize20\baselineskip
\lineskip=13pt % for identifications
% this is our trial material used below. We will arrange things so
% that the first para will be longer than a page so that we will end
% up with some material on recent contributions. The OR is actually
% then triggered when the first ``p'' is seen. Alternatively one can
% uncomment the \vskip or the \penalty in which case the OR will be
% triggered by them or you could uncomment ``Line2 and3'' then the
% break happens somewhere in the middle of the first paragraph (in
% vmode inn that case)
\def\testmaterial{%
Line 1 \hfil\break
% Line 2 \hfil\break
% Line 3 \hfil\break
\vadjust{\penalty -333 }
some text some text some text some text some text some text
some text some text some text
and some more text ggg \vrule depth 88pt
%\ vskip 17pt
% \penalty 15
pppppppppppppppppppppppppp
\showlists
}
% now this here is just to see the whole stuff being processed by the
% normal OR and see the \showlists result for it. One can then compare
% that to the showlists result we get later to check for differences
\testmaterial
\vfill
\eject
%==============================================
\newdimen\savedvsize
\newbox\savedORbox
% now for a set of special output routines:
% the first one does the following
%
% - save away current \vsize and set it to \maxdimen
% - then unbox 255 and readd the output penalty (unless it is 10000)
% - set up a new output routine for the next time
% - finally install control with \aftergroup\addendpenalty
%
% The point here is that the \aftergroup token is not actually
% directly executed the moment the OR ends. If TeX ends an OR it looks
% at the recent contribution and if they are not empty it will call
% ``buildpage'' and move them to the main vertical list. And only if
% this has happened the \aftergroup token gets executed. Now given
% that we set \vsize to the largest possible dimen this means that all
% the remainder that was not used first time around will now end up on
% the main vertial list and only then \addendpenalty kicks in.
\output{%
\global\savedvsize\vsize
\global\vsize\maxdimen
%--- tracing --------------
\showthe\outputpenalty
\showlists
%--------------------------
\unvbox255
%
% above I claimed we put \outputpenalty back (which we should) but to
% make things more visible I put back a special penalty that can be
% easily recognised in \showlists
%
% \penalty \ifnum\outputenalty=10000 0 \else \outputpenalty \fi
\penalty-777
\global\output{\ORtwo}%
\aftergroup\addendpenalty
}
% The macro \addendpenalty is used with \aftergroup from the output
% routine to gain control again. It adds a penalty to trigger the next
% output routine. However, we are quite likely in horizontal mode when
% the OR returns (just have seen the start of a paragraph) so we check
% for this. If true we remove the indentation box and end the
% paragraph. As a result all that get contributed is \parskip but no
% box (so \prevdepth is not touched). We signal with the penalty value
% whether or not we have seen hmode as we will have to remove that
% extra parskip in the next OR.
\def\addendpenalty{%
\ifhmode
\setbox0\lastbox\par\penalty-10010
\else
\penalty-10011
\fi}
% the second Or now should receive everything that was on the main
% vertical list with the recent contributions being empty (well empty
% except for a \penalty10000 that TeX puts in the place where it
% triggered the OR).
%
% What we have to do now is to remove the surplus \parskip at the
% bottom of 255 if we have been in hmode before. This is something we
% can determine by looking at the \outputpenalty that should be -10010
% in that case (otherwise -10011)
%
% then we save all of 255 in a spare box and return from the OR. To
% gain control afterwards we issue \aftergroup\XXX
\def\ORtwo{%
%--- tracing --------------
\showthe\outputpenalty
\showbox255\showlists
%--------------------------
\ifnum\outputpenalty=-10010
\setbox255=\vbox
{\unvbox255
\unskip % this gets rid of the \parskip from hmode
}
\fi
\global\setbox\savedORbox\box255
\aftergroup\XXX
}
% Note that now the macro \XXX is immediately called when the OR ends
% as the recent contributions are empty now. Thus this macro now is
% getting us in a good shape:
%
% - it can access \prevdepth and \prevgraf (which is in fact having
% the same issue) and it can change them as necessary.
%
% - it has the complete main vertical list at its disposal (saved
% in \savedORbox)
%
% - there is nothing left in recent contributions so anything
% following is new material, so we can now arrange everything to our
% liking and reprocess
\def\XXX{%
%--- tracing --------------
\showthe\prevdepth % this is finally the outer one and we could
% change it if needed
\showlists % nothing on it not even the penalty remains
% only prevdepth and prevgraf set (incorrectly)
%--------------------------
% \global\vsize\savedvsize
\global\vsize20\baselineskip
\global\output{\plainoutput}%
\unvbox\savedORbox
}
% what we do above is set the vsize back to 20 baselines set up the
% plainoutput routine again and reprocess and we get 100% the same as
% in the initial test (well, in one place there is penalty 777 but
% that was just mark that spot, normally we would have \outputpenalty
% there which was 0. In the original there was nothing in this space
% only glue but that is equivalent
% and here is now the real test: we set a very short vsize so the
% first OR is triggered with \testmaterial and some part of it ends up
% in recent contributions.
\vsize=3\baselineskip
\testmaterial
\bye
You need that the macros are expanded before \lowercase
processes the token list.
Let's say you have \def\foo{baz}
. If you do
\lowercase{\edef\tmpa{\foo}}
you get exactly the same as \edef\tmpa{\foo}
, because \lowercase
only changes character tokens. You might do
\lowercase\expandafter{\expandafter\def\expandafter\tmpa\expandafter{\foo}}
so that \foo
is expanded prior to \lowercase
starting its job. But this wouldn't work if instead of just \foo
you have something more complex that expands to character tokens, say two macros, for instance.
To overcome the issue you can use \expanded
, if you're sure that a fairly recent version of pdftex (or other engine) is used.
\newcommand{\setCmd}[3]{%
\expanded{\lowercase{\def\noexpand\tmpa{#1}}}%
\expanded{\lowercase{\def\noexpand\tmpb{#2}}}%
\IfSubStr{\tmpa}{\tmpb}
{\renewcommand{#3}{#2}}% true
{\edef\dbgstr{inputs: #1, #2; lowercased: \tmpa, \tmpb}}% false
}
Otherwise, the usual trick:
\newcommand{\setCmd}[3]{%
\begingroup\edef\x{\endgroup
\lowercase{\def\noexpand\tmpa{#1}}%
}\x
\begingroup\edef\x{\endgroup
\lowercase{\def\noexpand\tmpb{#2}}%
}\x
\IfSubStr{\tmpa}{\tmpb}
{\renewcommand{#3}{#2}}% true
{\edef\dbgstr{inputs: #1, #2; lowercased: \tmpa, \tmpb}}% false
}
Best Answer
For example with expl3: