[Tex/LaTex] Fit text into given box by adjusting the fontsize

automationboxesfontsizeformattingminipage

I would like to fit text (potentially several paragraphs) into a box of given size. This should be done by adjusting the fontsize of the contained text.

Clarification edit: The given dimensions are the maximum space the result should occupy. There is no need to fill the box fully, but under no circumstances should the dimensions be exceeded.

Something like

\fitbox{<width>}{<height}{Some text to be squeezed into a box \par With paragraphs}

or

\begin{fitbox}{<width>}{<height>}
Some text to be squeezed into a box

With paragraphs
\end{fitbox}

There are a number of questions here that ask the same in the title, but then only target to constrain the width or the height. I want to constrain both. Also, I would like the text only the be adjusted in the fontsize, not scaled disproportionally in one direction.

@TH solves this for another question by fitting text on a page. Unfortunately, my TeX knowledge is too limited to adapt that to a box. See here: Fitting and centering text (both!) in a constrained area For a page-fitting, his solution looks great to me. Maybe that's a starting point.


Edit: comparing the solutions from Werner and Martin

I've used XeLaTeX from TeXLive 2011 to compare the solutions from Werner and Martin below. The font is a TrueType Times New Roman vector font. The fitboxes are contained in a framebox to help comparison.

Case 1 is a box of 2cm x 2cm. Case 2 is the same text in a box of 8cm x 2cm.

Fitbox Comparison

Both boxes can be stacked next to each other horizontally and only consume the space they should. However, in Case 1 the box with Martin's approach is too high, while Werner's box fits the content correctly.


Edit 2: some more tests show: both suggestions can fail

After some more tests, the case illustrated below fails for both suggestions from Werner and Martin. That means, the question is still open.

Current Approaches fail with Overfull hbox

As Werner points out in a comment to his answer, the reason is that TeX cannot hyphenate the pattern. At the same time, it finds it acceptable to set it into the box and run over the edge.

After some research I believe that the algorithm after each line would have to check for \badness and if that exceeds 10000 reduce the font size further. At least the TeX pearl from Paweł Jackowski points in that direction.

Has anyone the insights and experience to put these pieces and the excellent work from Werner and Martin together into something that works and really stays inside the given box?


Edit 3: Another idea that might help to solve this

Another approach that might help would be a way to measure the width of the longest word (or box) of the paragraph, let's say it is wl. The associated font size is f. The maximum desired box width as given by the user is wmax.

Then, the upper bound for the font size could be calculated by

fmax = f * wmax / wl

This, of course, assumes that everything works out proportionally, which probably isn't entirely true considering inter word and inter character spacing. But it should be sufficiently good to keep the text in the fitbox horizontally.

Since this can only shrink the fontsize (it's an upper bound), the vertical condition if met before would still be met afterwards. That means, this could be applied after the suggestion of @Werner as a check and adjustment if required.

Does anyone have the TeX experience to stick that in?

Best Answer

Using the suggestion in Fitting and centering text (both!) in a constrained area, together with Martin's answer that uses the environ package, the following provides the environment

\begin{fitbox}{<width>}{<height>}
  <stuff>
\end{fitbox}

which typesets <stuff> using a form of binary search to fit the text within the given height <height> while under a fixed width <width> constraint set by a minipage. This is required in order to maintain a proportionate scaling of the font and leading (\baselineskip).

\documentclass{article}
\usepackage{lmodern}
\usepackage{environ}% http://ctan.org/pkg/environ
\usepackage{lipsum}% http://ctan.org/pkg/lipsum
\newdimen\fontdim
\newdimen\upperfontdim
\newdimen\lowerfontdim
\newif\ifmoreiterations
\fontdim12pt

\makeatletter
\NewEnviron{fitbox}[2]{% \begin{fitbox}{<width>}{<height>} stuff \end{fitbox}
  \def\buildbox{%
    \setbox0\vbox{\hbox{\minipage{#1}%
      \fontsize{\fontdim}{1.2\fontdim}%
      \selectfont%
      \stuff%
    \endminipage}}%
    \dimen@\ht0
    \advance\dimen@\dp0
  }
  \def\stuff{\BODY}% Store environment body
  \buildbox
  % Compute upper and lower bounds
  \ifdim\dimen@>#2
    \loop
      \fontdim.5\fontdim % Reduce font size by half
      \buildbox
    \ifdim\dimen@>#2 \repeat
    \lowerfontdim\fontdim
    \upperfontdim2\fontdim
    \fontdim1.5\fontdim
  \else
    \loop
      \fontdim2\fontdim % Double font size
      \buildbox
    \ifdim\dimen@<#2 \repeat
    \upperfontdim\fontdim
    \lowerfontdim.5\fontdim
    \fontdim.75\fontdim
  \fi
  % Now try to find the optimum size
  \loop
    %\message{Bounds: \the\lowerfontdim\space
    %         \the\fontdim\space \the\upperfontdim^^J}
    \buildbox
    \ifdim\dimen@>#2
      \moreiterationstrue
      \upperfontdim\fontdim
      \advance\fontdim\lowerfontdim
      \fontdim.5\fontdim
    \else
      \advance\dimen@-#2
      \ifdim\dimen@<10pt
        \lowerfontdim\fontdim
        \advance\fontdim\upperfontdim
        \fontdim.5\fontdim
        \dimen@\upperfontdim
        \advance\dimen@-\lowerfontdim
        \ifdim\dimen@<.2pt
          \moreiterationsfalse
        \else
          \moreiterationstrue
        \fi
      \else
        \moreiterationsfalse
      \fi
    \fi
  \ifmoreiterations \repeat
  \box0% Typeset content
}
\makeatother
\begin{document}
\lipsum[1]
\begin{fitbox}{.5\textwidth}{0.5\textheight}
  \lipsum[1-2]
\end{fitbox}
\lipsum[2]

\clearpage

\lipsum[1]
\begin{fitbox}{300pt}{300pt}
  \lipsum[1-2]
\end{fitbox}
\lipsum[2]
\end{document}​

In the figure below, two pages are typeset, each starting with \lipsum[1] and ending with \lipsum[2] to provide some frame of reference. The left page has a fitbox of dimension .5\textwidth x .5\textwidth while the page on the right is set at 300pt x 300pt (square).

Fixed-width-height box

Interestingly enough, I'm having trouble compiling this under TeXLive 2011. Although, there is no problem compiling it using the online LaTeX compiler ScribTeX, which runs TeXLive 2009. I don't know what the cause behind this is... This has been fixed by the replacement of \protected@edef\stuff{\BODY} with \def\stuff{\BODY}. The original code used this form since it provided two macros - one for parsing the content (called \fillthepage{<stuff>}) and another for updating a resized version of the content (called \buildbox). I assume the coding structure required this. However, with everything contained in a single environment fitbox above, this is not needed anymore.

Related Question