65

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?

3
  • 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. Aug 23, 2011 at 8:45
  • 3
    @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, 2011 at 9:51
  • See tex.stackexchange.com/questions/123614/… for another approach. Jul 16, 2013 at 17:22

9 Answers 9

27
+50

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.

17
  • 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, 2011 at 6:55
  • 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, 2011 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, 2011 at 21:07
  • 2
    Nice! Could you please make a LaTeX package out of it and put it on CTAN? Nov 29, 2011 at 10:11
  • 1
    @kontextify: Globally or locally? For the latter use \mbox{wordnottobreak}.
    – Werner
    Apr 25, 2019 at 20:11
20
+100

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.

3
  • 1
    Thanks for this answer. However, how to make the text vertically centered within the box ? (\vfill doesn't work.)
    – ebosi
    Oct 4, 2015 at 15:23
  • This answer is great! How to get rid of the black outline/border though?
    – qubodup
    Jul 31, 2016 at 2:40
  • I spent hours trying to understand the code, just to realize that to get rid of the box, I have to remove the \fbox{% and corresponding }% lines from inside the document -_-
    – qubodup
    Jul 31, 2016 at 4:27
15

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}
3
  • 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, 2011 at 8:46
  • @kongo: I don't think so. \relscale should scale the fontsize. Aug 18, 2011 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, 2011 at 18:46
7

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.

7
  • 1
    -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, 2011 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.
    – yannisl
    Aug 29, 2011 at 16:32
  • Well, it turns out there is. Please see my answer below.
    – kongo09
    Aug 31, 2011 at 16:07
  • @kongo09 Try this \fbox{% \begin{fitbox}{8cm}{4cm} \"UBERCODERSE \"UBERCODE \"UBERCODE \end{fitbox} }%
    – yannisl
    Sep 1, 2011 at 11:59
  • 2
    @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.
    – yannisl
    Sep 4, 2011 at 20:32
4

The most stable way (in my eyes) is to declare the adjusted fontsize/baselineheight pair directly. As both have to stay proportional to each other you can scale down both by the same factor, which is approximately the square root of the quotient from measured and target height. (The complete example below smoothes down this factor to gain a little more presision.)

Luckily, the graphicx package provides the \scalebox command, so one can feed the calculated factor to this command directly to get the right result.

\documentclass{article}
\usepackage{lipsum}
\renewcommand{\rmdefault}{ppl}
\usepackage[T1]{fontenc}
\usepackage{microtype}
\usepackage{fp,graphicx}
  \setlength\unitlength{1cm}

\makeatletter

\def\accur@cy{0.999}

\newcommand{\fitbox}[3][\textwidth]{%
  \@tempdima#2
  \edef\@wd{\strip@pt\dimexpr#1\relax}
  \def\r@tio{1}
  \@temptokena={\scalebox{\r@tio}{\parbox{\@wd pt}{{#3}}}}
  \setbox0=\vbox{\the\@temptokena}
  \@tempdimb=\dimexpr\ht0+\dp0\relax
  \FPdiv\r@tio{\strip@pt\@tempdima}{\strip@pt\@tempdimb}
  \FProot\r@tio{\r@tio}{2}
  \FPdiv\@wd{\@wd}{\r@tio}
  \fitbox@adjust
  \setbox0=\vbox{\the\@temptokena}
  \box0
}
\newcommand{\fitbox@adjust}{%
  \@tempcnta\z@
  \def\rel@rror@rec{0}
  \fitbox@adjust@
}
\newcommand{\fitbox@adjust@}{%
  \advance\@tempcnta by 1
  \ifnum\@tempcnta<10
   \FPiflt\rel@rror@rec\accur@cy
    \setbox0=\vbox{\the\@temptokena}
    \@tempdimb=\dimexpr\ht0+\dp0\relax
    \FPdiv\rel@rror@rec{\strip@pt\@tempdimb}{\strip@pt\@tempdima}
    \FPdiv\r@tio{\r@tio}{\rel@rror@rec}
    \FPmul\@wd{\@wd}{\rel@rror@rec}
    \fitbox@adjust@
   \fi
  \fi
}

\newcommand{\fitboxdemo}[1]{%
  \framebox(5,#1)[r]{%
    \parbox{5cm}{%
      \fitbox[5cm]{#1cm}{%
        Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
        sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
        sed diam voluptua.
        At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,
        no sea takimata sanctus est Lorem ipsum dolor sit amet.
      }%
    }%
  }%
  \vskip1em
}

\makeatother

\begin{document}
%\fitbox[8cm]{1cm}{XXXXXXXXXX}
\fitboxdemo{1}
\fitboxdemo{2}
\fitboxdemo{3}
\fitboxdemo{4}
\fitboxdemo{5}
\end{document}

output

3

Yet, the most accurate way seems to consist in using the anyfontsize package. The algorithm for finding the target font size and baselineheight is: first shrink the baseline by the ratio of target height and actual height and the fontsize proportionally (similarly to the suggestion of @MartinScharrer) and then factor in the relative difference consecutively til a certain accuracy (which I defined to 0.0001 in the below example).

Note: As well as this method is accurate it is also very fragile. For many cases it will in fact yield good results but it will fail miserabely in some extremer cases.

\documentclass{article}
\usepackage{microtype}
\usepackage{fp,anyfontsize}
\usepackage{graphicx}
  \setlength\unitlength{1cm}

\makeatletter

\newdimen\t@rgetht
\newcount\ht@meas
\newcount\ht@trgt
\newcount\ht@rszd

\def\accur@cy{0.0001}

\newcommand*{\fitbox}[3][\textwidth]{%
  \@tempcnta\z@
  \@tempdima#1
  \t@rgetht#2
  \ht@trgt=\t@rgetht\relax
  \setbox0=\vbox{\parbox[t]{\@tempdima}{#3}}
  \@tempdimb=\dimexpr\ht0+\dp0\relax
  \ht@meas=\@tempdimb\relax
  \edef\blsk@default{\strip@pt\baselineskip}
  \FPdiv\r@tio{\the\ht@trgt}{\the\ht@meas}
  \FPmul\blsk@target{\blsk@default}{\r@tio}
  \FPdiv\fnts@target{\blsk@target}{1.2}
  \def\rel@rror{1}
  \ifdim\t@rgetht>\textheight
    \@latex@warning{Aborting fit algorithm: Rescaling would exceed page limits}
  \else
    \fitbox@process{#3}
  \fi
  \box0
}
\newcommand*{\fitbox@process}[1]{%
  \@temptokena=
    {\parbox[t]{\@tempdima}{\fontsize{\fnts@target}{\blsk@target}\selectfont#1}}
  \FPiflt\rel@rror\accur@cy\else
    \setbox0=\vbox{\the\@temptokena}
    \@tempdimb=\dimexpr\ht0+\dp0\relax
    \ht@rszd=\@tempdimb\relax%
    \FPdiv\rel@rror{\the\ht@rszd}{\the\ht@trgt}%
    \FPsub\rel@rror{1}{\rel@rror}%
    \FPabs\rel@rror{\rel@rror}%
    \FPmul\corr@ht{\r@tio}{\rel@rror}
    \ifdim\@tempdimb<\t@rgetht
      \FPadd\r@tio{\r@tio}{\corr@ht}
    \else
      \FPsub\r@tio{\r@tio}{\corr@ht}
    \fi
    \FPmul\blsk@target{\blsk@default}{\r@tio}
    \FPdiv\fnts@target{\blsk@target}{1.2}
    \ifnum\@tempcnta<20
      \advance\@tempcnta by 1
      \fitbox@process{#1}
    \else
      \setbox0=\vbox{\the\@temptokena}
    \fi
  \fi
}

\newcommand{\fitboxdemo}[1]{%
  \framebox(5,#1)[r]{%
    \parbox{5cm}{%
      \fitbox[5cm]{#1cm}{%
        Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
        sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
        sed diam voluptua.
        At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,
        no sea takimata sanctus est Lorem ipsum dolor sit amet.
      }%
    }%
  }%
  \vskip1em
}

\makeatother

\begin{document}
%\fitbox[8cm]{1cm}{XXXXXXXXXX}
\fitboxdemo{1}
\fitboxdemo{2}
\fitboxdemo{3}
\fitboxdemo{4}
\fitboxdemo{5}
\end{document}

output

1
  • I really liked this solution, but it assumes the text will use multiple lines. If the original text fits into the first line (e.g., it is just “Lorem ipsum”), it doesn't work. Can you help with a fix? Nov 29, 2021 at 16:58
3

Many answers to this question are quite old. At least since 2021 there is a comfortable solution available via the tcolorbox package.

tcolorbox has a fitting library which can do exactly what the question asks. After loading the fitting library and setting up the boxes, all that is needed amounts to:

\begin{tcolorbox}[fit to=WIDTH and HEIGHT]
     MULTIPLE PARAGRAPH TEXT HERE
\end{tcolorbox}

Here is a full MWE:

\documentclass[11pt]{article}

% Not needed but useful for demonstration:
\usepackage[a4paper, margin={2cm, 3cm}]{geometry}
\usepackage{lipsum}
\pagestyle{empty}

% Loading tcolorbox including the fitting library
\usepackage[fitting]{tcolorbox}


% Setting the boxes globally. This can be done
% locally and on the fly. The are many more 
% options. Please see the doc of tcolorbox
\tcbset{sharp corners, 
        colback=white, 
        colframe=black,
        top=2mm,
        bottom=2mm,
        left=2mm,
        right=2mm,
        fit algorithm=hybrid*
    }


\begin{document}
    
  % First box constrained to 9 times 9 cm
  % Only the height has to be given. The
  % width is optional.
  \begin{tcolorbox}[fit to=9cm and 9cm]
    \lipsum[1]\\
    
    \lipsum[2]
  \end{tcolorbox}

  % Second box: 3 times 9 cm
  \begin{tcolorbox}[fit to=3cm and 9cm]
    \lipsum[1]\\
    
    \lipsum[2]
  \end{tcolorbox}

  % Third box: Here we only specify the height
  % of the box as 3cm.
  \begin{tcolorbox}[fit, height=7cm]
    \lipsum[1]\\
    
    \lipsum[2]
  \end{tcolorbox}

\end{document}

First box:

First box

Second box:

Second box

Third box:

Third box

tcolorbox in general has an incredible amount of options not even counting the possibility to integrate tikz code. And even the fitting library has more options, then can be reasonably described here. But I'd like to highlight at least the option of dynamic growth of the box.

It is possible not only to specify a height (and/or width) but also to specify additional width and height up to which the box can grow, before scaling of the font is applied. Here is a MWE:

\documentclass[11pt]{article}

% Not needed but usefull for demonstration:
\usepackage[a4paper, margin={2cm, 3cm}]{geometry}
\usepackage{lipsum}
\pagestyle{empty}

% Loading tcolorbox including the fitting library
\usepackage[fitting]{tcolorbox}


% Setting the boxes globally. This can be done
% locally and on the fly. The are many more 
% options. Please see the doc of tcolorbox
\tcbset{sharp corners, 
        colback=white, 
        colframe=black,
        top=2mm,
        bottom=2mm,
        left=2mm,
        right=2mm,
        fit algorithm=hybrid*
    }


\begin{document}
    
  % Fourth box contrained to a minimum of 3 times 6cm
  % But if the content is to big, the box can grow 
  % to becom 15 times 10cm, before scaling is applied.
  \begin{tcolorbox}[fit width from=3cm to 15cm, fit height from=6cm to 10cm]
    \lipsum[1]
  \end{tcolorbox}

  % Fith box demonstrating the growth of the box, without scaling
  \begin{tcolorbox}[fit width from=3cm to 15cm, fit height from=6cm to 10cm]
        \lipsum[1-2]
  \end{tcolorbox}

  % Sixth box demonstrating the growth of the box and additional scaling
  \begin{tcolorbox}[fit width from=3cm to 15cm, fit height from=6cm to 10cm]
    \lipsum[1-5]
  \end{tcolorbox}

  % Seventh box demonstrating an extreme case
  \begin{tcolorbox}[fit width from=3cm to 15cm, fit height from=6cm to 10cm]
    \lipsum[1-20]
  \end{tcolorbox}

\end{document}

Which gives the following boxes:

Fourth box:

Fourth box

Fifth box:

Fifth box

Sixth box:

enter image description here

Seventh box:

Seventh box

I hope this answer helps someone still searching for a solution after 2023 ;)

1
  • 1
    "I hope this answer helps someone still searching for a solution after 2023 ;)" -- It did ;)
    – MaxD
    Mar 24, 2023 at 14:19
2

A bullet-proof solution would be to use the standard fontsize switches. The algorithm I propose is simple brute force: if the text exceeds the borders of the box use the relatively next small font, else use the next higher one unless it would blow up the borders of the bounding box.

This method is safe as its termination is guaranteed and the "complexity" is bounded by 5. The only limitation is that the text can't get smaller then \tiny. Hence, if the ratio between actual and target height exceeds some constant c>1 the content can't be fitted completely anymore (which I find a reasonable limit as \tiny text is alredy not that easy to read in general).

The below code contains the complete implementation with a small test for which I defined a wrapper command \fitboxdemo{<n>} (n in cm). The actual command is \fitbox and its synopsis: \fitbox[<width>]{<height>}{<content>}

\documentclass{article}
\usepackage[T1]{fontenc}
\renewcommand{\rmdefault}{ppl}
\usepackage{graphicx}
  \setlength\unitlength{1cm}

\makeatletter

\newif\if@nlarge
\newdimen\m@xheight
\newcount\size@selector

\newcommand{\select@size}[1]{%
  \ifcase#1
        \tiny
    \or \scriptsize
    \or \footnotesize
    \or \small
    \or \normalsize
    \or \large
    \or \Large
    \or \LARGE
    \or \huge
    \or \Huge
  \fi
}
\newcommand{\@shrinkbox}[1]{%
  \loop
  \ifdim\@tempdimb>\m@xheight
    \advance\size@selector by -1
    \setbox0=\vbox{\the\@temptokena}
    \@tempdimb=\dimexpr\ht0+\dp0\relax
  \unless\ifnum\size@selector=0
  \repeat
}
\newcommand{\@nlargebox}[1]{%
  \loop
  \ifdim\@tempdimb<\m@xheight
    \advance\size@selector by 1
    \setbox0=\vbox{\the\@temptokena}
    \@tempdimb=\dimexpr\ht0+\dp0\relax
  \unless\ifnum\size@selector=10
  \repeat
}
\newcommand{\fitbox}[3][\textwidth]{%
  \@tempdima#1
  \m@xheight#2
  \size@selector=4
  \@temptokena={\parbox[t]{\@tempdima}{\select@size{\size@selector}#3}}
  \setbox0=\vbox{\the\@temptokena}
  \@tempdimb=\dimexpr\ht0+\dp0\relax
  \ifdim\@tempdimb>\m@xheight
    \@nlargefalse
    \@shrinkbox{#3}
  \else
    \@nlargetrue
    \@nlargebox{#3}
  \fi
  \if@nlarge\advance\size@selector by -1\fi
  \setbox0=\vbox{\the\@temptokena}
  \@tempdimb=\dimexpr\ht0+\dp0\relax
  \ifdim\@tempdimb>\m@xheight
    \@latex@warning{This box was too high and can not be resized entirely to #2}\fi
  \box0
}
\newcommand{\fitboxdemo}[1]{%
  \framebox(5,#1)[r]{%
    \parbox{5cm}{%
      \fitbox[5cm]{#1cm}{%
        Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
        sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
        sed diam voluptua.

        At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,
        no sea takimata sanctus est Lorem ipsum dolor sit amet.
      }%
    }%
  }%
  \vskip1em
}

\makeatother

\begin{document}
\fitboxdemo{1}
\fitboxdemo{2}
%\fitboxdemo{2.25}
\fitboxdemo{3}
\fitboxdemo{4}
\fitboxdemo{5}
\end{document}

output

1

I fixed the single paragraph limitation of kongo09's solution by replacing the definition of \badnessbox with the following:

\newbox\scratchbox
\def\badnessbox{%
    \global\maxbad=0\relax%
    \message{Starting measureline recursion with width \the\fitboxwidth^^J}%
    \bgroup
    \hbadness=10000000\relax% prevent TeX from reporting overfull boxes
    \everypar{\setbox\trialbox=\vbox\bgroup\everypar{}
        \hsize\fitboxwidth\relax%
        \fontsize{\fontdim}{1.2\fontdim}%
        \selectfont%
        \def\par{\endgraf\measurelines\egroup\endgraf}}
    \setbox\scratchbox=\vbox{
        \stuff\par
    }
    \egroup
    \message{Measured badness: \the\maxbad\space at Fontsize: \the\fontdim\space^^J}%
}

Also, the badness is not monotonic in the fontsize so doing a binary search to reduce the badness isn't really warranted (I found an example where it behaves chaotically). I modified the code to do a binary search to find the font size that fits the text into the box as neatly as possible, and then if the badness exceeds a threshold, do a linear search to find a smaller font size with the best badness. I could put it here if anyone is interested.

1
  • I would be interested. Can you post your solution? Or share it with me directly? Thanks? Nov 29, 2021 at 21:46

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .