12

Someone gives me a box, and my goal is to know as much as possible about the amount of glue inside that box. I can assume that the box is not huge, but it may contain arbitrary stuff (so I cannot deconstruct the box using \unskip and friends).

For definiteness, say that the box \mybox was defined with \newbox\mybox then constructed using \setbox\mybox\hbox to 50pt{a b c}. To get the natural width of the box's contents, I can do \setbox0\hbox{\unhcopy\mybox}\showthe\wd0 (and I'll get 21.66669pt).

To know if the box contains infinite stretch I can typeset it inside a box of width \maxdimen and check for the badness (well, that's not entirely fool-proof, but should be ok), and similarly for infinite shrink.

What I'd be interested in is to know the stretch or shrink in the finite case. My feeling is that it should be possible to extract that by checking the \badness after \setbox0\hbox spread \somedim{\unhcopy\mybox} with various \somedim. Is this correct? Is there a more efficient approach?

  • 1
    You know s>0 (spread) and b<10000 (badness) and you need to calculate f (total stretch in the box). This can be done by f = root 3 of {100 s^3 \over b}. I mean that you are specialist for such type of calculations in TeX... – wipet Jul 12 '14 at 05:12
  • 2
    I suppose using \showbox and reading a copy of the log (with TeX) is cheating? – David Carlisle Jul 12 '14 at 07:29
  • @DavidCarlisle Given the goal, which would be to have a more clever method for math layout (where e.g., subscripts do not have to take their natural width), yes, reading the log is cheating. – Bruno Le Floch Jul 15 '14 at 12:43

2 Answers2

4

Short answer: one can find the natural width of the box, the exact amount of stretch if it is finite (otherwise one can only say "it's infinite"), and similarly the exact amount of shrink if it is finite.

Let us assume for definiteness that \mybox is a horizontal box such as

\newbox\mybox
\setbox\mybox\hbox spread 5pt{Some text perhaps.}

To find its natural width, do \setbox0\hbox{\unhcopy\mybox}: then the answer is the width \wd0 of box 0.

We can only probe the amount of glue stretch or shrink by setting the box with a width different from its natural width, and testing the \badness. Setting the box to a width larger than its natural width probes the stretch component, and setting it to a width smaller than its natural width probes the shrink component. In both cases, if the corresponding component (stretch for larger widths, shrink for smaller widths) is infinite then the \badness will be zero, telling us nothing about the value or sign of that infinite component. I see no other way to probe such infinite values, so let's focus on the finite case.

The general formula for the badness is quite complicated: it involves in some cases the combination 100(t/s)^3 where t is the difference between the natural width of the box and the width that was asked for, and s is the amount of stretch (or shrink, if the box was squeezed). Fortunately, we can go by with only a very simple case. Our basic construction for testing the amount of stretch is \setbox0\hbox spread 1sp{\unhcopy\mybox\hskip 0pt plus -\stest} where \stest is a dimension that we will vary. The amount of stretch in this box is the amount in \mybox, minus \stest. If that is positive, then the box can stretch, and the \badness is not too big (as it turns out, 100 or less). Otherwise, the box is underfull, as it cannot stretch even by 1sp, and the \badness is 10000 (when testing the shrink, we get overfull boxes with a \badness of 1000000). We thus have a simple way to test whether the amount of stretch in \mybox is >\stest or <=\stest for any dimension \stest. The task is thus reduced to a dichotomy.

\hbadness=1000000
\hfuzz=\maxdimen
\newdimen\stest
\newdimen\smin
\newdimen\smax
\def\find#1#2#3{%
  \smin = -\maxdimen
  \smax = \maxdimen
  \loop
    \stest = \smin
    \advance \stest by \smax
    \divide \stest by 2 % now \stest=(\smin+\smax)/2 truncated to 0
    \ifdim \stest = \smax
      \advance \stest by -1sp % ensure that \smin<=\stest<\smax.
    \fi
    \setbox 0 = \hbox spread #2 1sp {%
      \unhcopy\mybox
      \hskip 0pt #3-\stest
    }%
    \ifnum \badness > 100 % (shrink/stretch in \mybox) <= \stest
      \smax = \stest
    \else
      \smin = \stest
      \advance \smin by 1sp
      % since ">\stest" implies ">=\stest+1sp"
    \fi
    % In both cases, the interval [\smin,\smax] becomes smaller.
  \ifdim\smin<\smax
  \repeat
  \ifdim\smin>\smax\BOOM\fi% cannot happen, I think
  \message{^^JThe #1 is
    \ifdim\smin=\maxdimen infinite\else\the\smin\fi.}% (\smin=\smax)
}
\setbox0\hbox{\unhcopy\mybox}
\message{^^JThe natural width is \the\wd0 .}
\find{stretch}{}{plus}
\find{shrink}{-}{minus}
2

If \mybox is a vbox, then we can solve the problem almost entirely by saving the current page, dumping the box contents on the main vertical list, accumulating \pagestretch and friends inside an output routine, and then restoring the current page.

This method can not be used to measure infinitely shrinkable glue, because TeX does not allow such glue on the main vertical list. As Bruno pointed out, you could at least check if the box contains infinitely shrinkable glue by unboxing it in a vbox of height -\maxdimen and checking the \badness.

\newbox\savedpage
\newdimen\savedprevdepth
\newdimen\stretch
\newdimen\filstretch
\newdimen\fillstretch
\newdimen\filllstretch
\newdimen\shrink
\def\savepage{%
    \savedprevdepth=\prevdepth
    {\output={\global\setbox\savedpage=\box255}%
    \holdinginserts=1 \eject}}
\def\restorepage{\unvbox\savedpage \prevdepth=\savedprevdepth}
\def\findglue{%
    \savepage
    \stretch=0pt
    \filstretch=0pt
    \fillstretch=0pt
    \filllstretch=0pt
    \shrink=0pt
    \begingroup
        \output={%
            \global\advance\stretch\pagestretch
            \global\advance\filstretch\pagefilstretch
            \global\advance\fillstretch\pagefillstretch
            \global\advance\filllstretch\pagefilllstretch
            \global\advance\shrink\pageshrink
            \setbox0=\box255
            \null % Keep discardables after page break.
        }%
        \vsize=\maxdimen
        \topskip=0pt
        \null % Keep discardables at top of mybox.
        \unvcopy\mybox
        \eject
        \output={\setbox0=\box255}%
        \eject % Remove the \null and \penalty10000 remaining on the page.
    \endgroup
    \restorepage}

% Only needed for the print-out {\catcode\p=12 \catcode\t=12 \gdef\#1pt{#1}} \def\decimal{\expandafter\\the}

\setbox0=\vbox{\boxmaxdepth=0pt \unvcopy\mybox} \findglue Height-plus-depth of box: \the\ht0\ plus \the\stretch\ plus \decimal\filstretch fil plus \decimal\fillstretch fill plus \decimal\filllstretch filll minus \the\shrink

The \savepage and \restorepage macros assume that you have not been manually touching \pagegoal, \pagetotal, etc., and that \topskip has not changed since the saved page was started. If this is not the case, you should save and restore those quantities in addition to the page contents.

RobertR
  • 379