Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

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?

share|improve this question
    
concerning your second edit: the solutions don't really fail. After all you just provide one "word", that LaTeX does not know how to syllabificate. The only way I see for the 2x2 case is two rows of XXX, while the 8x2 can't be solved, I think. As Werner's example shows, if the text fits vertically it is to long, if it would fit horizontally it would not be high enough. Also splitting the string in multiple ones does not help as then the horizontal fit would be even worse. I guess in such single word cases, sometimes you can only fullfill either size constraint. –  Tom Bombadil Aug 23 '11 at 8:45
1  
@TomBombadil Thanks for your comment. Maybe I wasn't clear enough in my question (and I'll change that right away): The constraints of the box are maximum dimensions. That means, I would like to fit the text inside the box. There is no need to fill the box in height. The desired outcome with the xxxxxx pattern would be to shrink the fontsize so that it fits into one line limited by the given width. –  kongo09 Aug 23 '11 at 9:51
    
Ah, thanks, much clearer now. –  Tom Bombadil Aug 23 '11 at 11:29
    
See tex.stackexchange.com/questions/123614/… for another approach. –  Steven B. Segletes Jul 16 '13 at 17:22

4 Answers 4

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.

share|improve this answer
    
Ideally I tried to output the requested and actual <height> as well, but was unable to so since \typeout{\ht0} yielded 0.0. Again, I don't know why this is the case... –  Werner Aug 18 '11 at 6:55
    
Should be \typeout{\the\ht0} AFAIK. –  Martin Scharrer Aug 18 '11 at 6:59
2  
The \protected@edef\stuff{\BODY}% is the problem. What is its purpose? –  Ulrike Fischer Aug 18 '11 at 9:48
1  
@kongo09: I've updated my code to correct for this behaviour. The minipage was typeset in a \vbox{...}. Changing it to an \vbox{\hbox{...}} does the trick. Or you could merely change the \vbox{...} to \hbox{...}, but this mode change requires some spacing changes to break paragraphs. –  Werner Aug 18 '11 at 19:30
1  
@kongo09: The bizarre choice of words is the reason for it failing. LaTeX breaks lines at the end for hyphenation based on rules. If you provide words (or even a language) for which no rules exist, then you need to provide LaTeX with some suggestion as to where hyphenation should/could occur. This is done by adding \- inside your words. In the example you provided "kränkeln...mäh" is considered a single word, for which not hyphenation exists. This would also have resulted in Overfull hbox warnings. –  Werner Aug 19 '11 at 21:07

Here my idea: Capture the content using the environ package, place it in a box which uses a minibox with the given width. Then measure the ratio between the requested height and the total height of the box and use relsize and its \relscale macro to typeset the content again with this size. This depends on the use of a scalable font. It doesn't work for me for some input, because there seem to be scaling limits.

\documentclass{article}

\usepackage[T1]{fontenc}
\usepackage{lmodern}

\usepackage{graphicx}
\usepackage{environ}
\usepackage{relsize}

\makeatletter
\NewEnviron{fitbox}[2]{%
    \minipage{#1}%
        \sbox0{\minipage{#1}\strut\BODY\strut\endminipage}%
        \Gscale@div\factor{#2}{\dimexpr\ht0+\dp0\relax}%
        \relscale{\factor}%
        \BODY
    \endminipage
}
\makeatother

\usepackage{lipsum}

\begin{document}

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

\end{document}
share|improve this answer
    
Thanks for the suggestion but this does not what I want. This scales the font disproportionally instead of reflowing the text with a new fontsize. –  kongo09 Aug 18 '11 at 8:46
    
@kongo: I don't think so. \relscale should scale the fontsize. –  Martin Scharrer Aug 18 '11 at 9:02
    
I just tried again and now the font is scaled but the box is too large. I'll attach an image to my original question comparing with Werner's solution. –  kongo09 Aug 18 '11 at 18:46

The problem as stated cannot be optimized, certainly not with the TeX engine, that introduces paragraph and line optimization and hyphenation. Consider the following code:

\documentclass{article}
\usepackage[T1]{fontenc}
\def\sample{This paper discusses the protocol used for electing the
Doge of Venice between 1268 and the end of the Republic
in 1797. We will show that it has some useful properties that\ldots}
\begin{document}
\makeatletter
\gdef\alist{\tiny,\small,\large,\Large}
\def\check#1#2{
\@for\i:=#1\do{%
\fbox{
\minipage[t]{3cm}
 \i{#2}
\endminipage
}\hspace{3pt}
}
}
\check\alist\sample
\makeatother
\end{document}

which produces:

enter image description here

The code constraints the width and lets the height be adjustable. I have used a comma delimited list for the allowable fonts (this way you can change the baseline, as you should with different size fonts and even you can produce definitions with hyphenation parameters as well) and hence one can optimize over a discrete domain. The problem is then easy to solve. The first font that results in the box height exceeding the constraint, points to the solution which is the n-1 font count used.

share|improve this answer
    
-1 With all due respect, but this answer is not an answer and it doesn't add anything new. We already found an automated way of doing what you suggest as a manual approach. We also found already that a minipage does not constrain the width of the contained text - it only tries to do so for a given font size but let's the text run over the edge if it can't break it. Werner's and Martin's suggestions are almost solving it but miss only a little last step as outlined in my Edits 2+3. –  kongo09 Aug 29 '11 at 10:17
1  
@kongo09 With all due respect also, but the aim of my answer was to claim that there is no solution to the problem as stated. –  Yiannis Lazarides Aug 29 '11 at 16:32
    
Well, it turns out there is. Please see my answer below. –  kongo09 Aug 31 '11 at 16:07
    
@kongo09 Try this \fbox{% \begin{fitbox}{8cm}{4cm} \"UBERCODERSE \"UBERCODE \"UBERCODE \end{fitbox} }% –  Yiannis Lazarides Sep 1 '11 at 11:59
1  
@kongo09 Glad you did. Although the effort and the result are laudable, I am sure it can still break and you will continue adding new constraints such as badness, single paragraph etc. My suggestion is to rephrase the question slightly to define more clearly the constraints. It will make both a better question as well as a better answer. –  Yiannis Lazarides Sep 4 '11 at 20:32
up vote 3 down vote accepted

I found a way to solve the problem - almost. It builds on the approach outlined by @Werner. Essentially, it adds a test for the badness of the box as a measure for text running over the edge. The test is a recursion that unfortunately only works for the last paragraph of the text in the environment. So there is still some refinement required.

\documentclass{article}
\usepackage{xltxtra}
\usepackage{fontspec}
\setmainfont[Mapping=tex-text]{Times New Roman}

\sloppypar

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

\newbox\trialbox
\newbox\linebox
\global\newcount\maxbad
\newcount\linebad
\newcount\currenthbadness


\makeatletter
\NewEnviron{fitbox}[2]{% \begin{fitbox}{<width>}{<height>} stuff \end{fitbox}
    % Store environment body
    \def\stuff{%
        \BODY%
    }%
    % prepare badness box
    \def\badnessbox{%
        \global\maxbad=0\relax%
        \currenthbadness=\hbadness% save old \hbadness
        \hbadness=10000000\relax% make sure, TeX reports overfull boxes
        \message{Starting measureline recursion with width #1^^J}%
        \setbox\trialbox=\vbox{%
            \hsize#1\relax%
            \fontsize{\fontdim}{1.2\fontdim}%
            \selectfont%
            \stuff\par%
            \measurelines% start recursion
        }%
%       \noindent\usebox\trialbox\par
        \hbadness=\currenthbadness% restore old \hbadness
    }
    % prepare recursion to measure line badness
    \def\measurelines{%
        \message{Iteration of measurelines^^J}%
        \begingroup%
            \setbox\linebox=\lastbox% get the last line
            \setbox0=\hbox to \hsize{\unhcopy\linebox}% put the last line into box0 to provoke badness calculation
            \linebad=\the\badness\relax% \badness now reflects the last typeset box, i.e. box0
            \message{Badness: \the\badness\space\the\linebad\space with max \the\maxbad\space at Fontsize: \the\fontdim\space^^J}%
            \ifnum\linebad>\maxbad% store the maximum badness
                \global\maxbad=\linebad% Uncomment this line to ignore overfull hboxes!
            \fi%
            \ifvoid% end of recursion
                \linebox%
            \else%
                \unskip\unpenalty\measurelines% do the recursion
                \ifhmode%
                    \newline%
                \fi%
                \noindent\box\linebox% do the output
            \fi%
        \endgroup%
    }%
    % Prepare measurement box
    \def\buildbox{%
        \badnessbox% measure badness
        \setbox0\vbox{% measure height
            \hbox{%
                \fontsize{\fontdim}{1.2\fontdim}%
                \selectfont%
                \minipage{#1}%
                    \vbox{%                     
                        \stuff\par%
                    }%
                \endminipage%
            }%
        }%
        \message{Measured badness: \the\maxbad\space at Fontsize: \the\fontdim\space^^J}%
        \dimen@\ht0
        \advance\dimen@\dp0
        \message{Measured box height: \the\dimen@\space^^J}%
    }%
    \def\shrinkheight{%
        \loop
            \fontdim.5\fontdim % Reduce font size by half
            \buildbox
            \message{Shrinking, new box height: \the\dimen@\space at Fontsize: \the\fontdim\space^^J}%
        \ifdim\dimen@>#2 \repeat
        \lowerfontdim\fontdim
        \upperfontdim2\fontdim
        \fontdim1.5\fontdim
    }%
    \def\shrinkwidth{%
        \loop
            \fontdim.5\fontdim % Reduce font size by half
            \buildbox
        \ifnum\maxbad>10000 \repeat
        \lowerfontdim\fontdim
        \upperfontdim2\fontdim
        \fontdim1.5\fontdim
    }%
    \def\growheight{%
        \loop
            \fontdim2\fontdim % Double font size
            \buildbox
            \message{Growing, new box height: \the\dimen@\space at Fontsize: \the\fontdim\space^^J}%
        \ifdim\dimen@<#2 \repeat
        \upperfontdim\fontdim
        \lowerfontdim.5\fontdim
        \fontdim.75\fontdim
    }%
    \buildbox
    % Compute upper and lower bounds
    \ifdim\dimen@>#2
        \message{Need to shrink box height: \the\dimen@\space^^J}%
        \shrinkheight
    \else
        \message{Need to grow box height: \the\dimen@\space to target: #2^^J}%
        \growheight
    \fi
    \message{Max font: \the\upperfontdim\space^^J}%
    \message{Min font: \the\lowerfontdim\space^^J}%
    % Potentially further reduce bounds for overfull box
    \ifnum\maxbad>10000
        \shrinkwidth
    \fi 
    \message{Max font adjusted: \the\upperfontdim\space^^J}%
    \message{Min font adjusted: \the\lowerfontdim\space^^J}%
    % Now try to find the optimum height and width
    \loop
        \buildbox
        \message{Height: \the\dimen@\space^^J}%
        \ifdim\dimen@>#2
            \moreiterationstrue
            \upperfontdim\fontdim
            \advance\fontdim\lowerfontdim
            \fontdim.5\fontdim
        \else
            \ifnum\maxbad>10000
                \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
        \fi
    \ifmoreiterations \repeat
    \message{Selected font: \the\fontdim\space^^J}%
    \vbox to #2{\box0\hbox{}}% Typeset content
}%
\makeatother

\usepackage{parskip}

\setlength\fboxsep{0pt}
\begin{document}

(2cm x 2cm):

\fbox{%
\begin{fitbox}{2cm}{2cm}%
xx
\end{fitbox}
}%
\fbox{\vbox to 2cm{\hbox to 2cm{}}}%
\fbox{\vbox to 2cm{\hbox to 2cm{}}}%
\fbox{\vbox to 2cm{\hbox to 2cm{}}}%

(8cm x 2cm):

\fbox{%
\begin{fitbox}{8cm}{2cm}
xxxxxxxxxx
\end{fitbox}
}%

(8cm x 1cm):

\fbox{%
\begin{fitbox}{8cm}{1cm}
This box should be 8x1 cm and actually, that's exactly the size it has.
\end{fitbox}
}
\end{document}​

This produces the following output:

Fitbox illustration

There is no longer any text running over the edge for the cases discussed in the other answers and the question. Instead, the font shrinks and part of the box remains empty.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.