# Adjust font size to fit long words in vbox

Goal:
Typesetting minimalist book covers in ConTeXt, with the title in the largest font size possible while remaining in the box.

Problem:
Long words or lines don't respect the right side of the box and continue off the page.

Minimal Example:
I found this code from Wolfgang on the mailing list, thanks to this answer from @aditya. (Sorry for the silly title but I wanted a word that I knew would continue off the page!)

\newbox\cover
\newdimen\coverheight \coverheight= 4in
\newdimen\coverwidth  \coverwidth = 7in
\newdimen\coverfont   \coverfont  = 12pt
\newdimen\fontstep    \fontstep   = 1pt

\def\startcover
{\dostartbuffer[cover][startcover][stopcover]}

\def\stopcover
{\setups[cover:place]}

\def\boxsize
{\setbox\cover\vbox
{\hsize\coverwidth
\definedfont[Serif at \the\coverfont]\setupinterlinespace
\setupalign[nothyphenated]
\emergencystretch\maxdimen
\getbuffer[cover]}}

\def\covertext
{\boxsize\doloop
{\boxsize
\ifdim\ht\cover>\coverheight
\exitloop
\else
\fi}}

\startsetups[cover:place]
\covertext
\boxsize
\vbox to\coverheight{\box\cover}
\stopsetups

\starttext

\startcover
\strut Mary Poppins Sings Supercalifragilisticexpialidocious
\stopcover

\stoptext


Compiles to:
Notes:
In the context (no pun intended!) of book covers, I'm more concerned about getting the maximum text size horizontally than filling the vertical space. The ideal would be to set a maximum height the text could be and absolute width, and fit the text inside, aligned to the top. If the text doesn't take up the maximum vertical space, that's fine.

• Also, just for the record I know I've been bombarding all you ConTeXt gurus with questions recently. Thanks so much for all the help! – derBingle Apr 25 '17 at 14:41
• Well, at least in the case of your example, the solution is to manually insert [optional] hyphenation points in Supercalifragilisticexpialidocious. – Steven B. Segletes Apr 25 '17 at 14:42
• Haha, right?. But hyphenation doesn't look too good or professional on book covers! – derBingle Apr 25 '17 at 14:44
• Neither does the 5.3pt font required to get Supercalifragilisticexpialidocious to fit on a single line of the book cover. Perhaps, look at this: tex.stackexchange.com/questions/123614/… – Steven B. Segletes Apr 25 '17 at 14:46

The setup is a little bit more convoluted in this case because we have to come up with a completely new algorithm here. The one you posted typesets stuff in a box and loops over the fontsize (increasing) until a certain height is achieved. Here we want to typeset stuff in a box, examine if it is too wide and decrease the fontsize. That means we start from a ridiculously large fontsize (I chose 100pt).

Now the problem is, that we set stuff in a vbox at a fixed width (\textwidth for simplicity). How do we know whether a line is too wide? Vic­tor Ei­jkhout presented a method for that in his book TeX by Topic which he called \eatlines. I adapted this to measure the width of the line and set a boolean value depending on whether the width is met or not. The only thing left to do is loop over the fontsize (here in steps of 1pt, decreasing). I also handle the case that the fontsize decreases below 0pt, in which case the content can never be fit in the box (which shouldn't happen), but leaving out this case would then result in an infinite loop.

\newdimen\coverwidth  \coverwidth = \textwidth
\newdimen\coverfont   \coverfont  = 100pt
\newdimen\fontstep    \fontstep   = 1pt
\newconditional\widthmet

\def\startcover
{\dostartbuffer[cover][startcover][stopcover]}

\def\stopcover
{\setups[cover:place]}

\def\eatlines{%
\global\settrue\widthmet
\setbox\scratchboxone=\lastbox
\ifvoid\scratchboxone\else
\unskip\unpenalty
{\eatlines}%
\copy\scratchboxone
\setbox\scratchboxtwo=\hbox{\unhbox\scratchboxone}%
\ifdim\wd\scratchboxtwo>\coverwidth
\global\setfalse\widthmet
\fi
\fi
}

\startsetups cover:place
\start\dontcomplain
\doloop{%
\setbox\scratchbox=\vbox{\hsize=\coverwidth
\definedfont[Serif at \the\coverfont]\setupinterlinespace
\setupalign[nothyphenated]\emergencystretch\maxdimen
\getbuffer[cover]%
\par\eatlines
}%
\ifconditional\widthmet
\box\scratchbox
\exitloop
\else
\fi
\ifdim\coverfont<0pt
\exitloop
\fi
}
\stop
\stopsetups

\starttext

\startcover
Mary Poppins Sings Supercalifragilisticexpialidocious
\stopcover

\stoptext


Output (with \showboxes):

ConTeXt makes it quite easy to add a comprehensive key-value interface to this so you could use things like

\startcover[width=2in]
Mary Poppins Sings Supercalifragilisticexpialidocious
\stopcover

\definecover[whatever][maxsize=200pt]
\setupcover[whatever][stepsize=5pt]
\startwhatever[width=4in]
Mary Poppins Sings Supercalifragilisticexpialidocious
\stopwhatever


This might seem a little exaggerated, but one could even go further and put the stuff in a module, so you'd only have to \usemodule[cover] and you are set. Save the following as t-cover.mkiv in your working directory.

\startmodule[cover]
\unprotect

\installcorenamespace{cover}
\installcommandhandler \??cover {cover} \??cover

\startinterface all
\setinterfaceconstant {maxsize}  {maxsize}
\setinterfaceconstant {stepsize} {stepsize}
\stopinterface

\appendtoks
\setuevalue{\e!start\currentcover}{\cover_start[\currentcover]}%
\setuvalue {\e!stop\currentcover}{\cover_process}%
\to \everydefinecover

\setupcover[
\c!width=\textwidth,
\c!maxsize=100pt,
\c!stepsize=1pt,
]

\unexpanded\def\cover_start
{\bgroup\obeylines\dodoubleargument\cover_start_indeed}

\starttexdefinition cover_start_indeed [#1][#2]
\egroup
\edef\currentcover{#1}
\setupcover[#1][#2]
\grabbufferdata[coverbuffer][start#1][stop#1]
\stoptexdefinition

\unexpanded\def\cover_process{%
\start\dontcomplain
\scratchdimen=\coverparameter\c!maxsize
\doloop{%
\setbox\scratchbox=\vbox{\hsize=\coverparameter\c!width
\definedfont[Serif at \the\scratchdimen]\setupinterlinespace
\setupalign[nothyphenated]\emergencystretch\maxdimen
\getbuffer[coverbuffer]%
\par\eatlines
}%
\ifconditional\scratchcounter
\box\scratchbox
\exitloop
\else
\fi
\ifdim\scratchdimen<0pt
\exitloop
\fi
}%
\stop
}

\def\eatlines{%
\global\settrue\scratchcounter
\setbox\scratchboxone=\lastbox
\ifvoid\scratchboxone\else
\unskip\unpenalty
{\eatlines}%
\copy\scratchboxone
\setbox\scratchboxtwo=\hbox{\unhbox\scratchboxone}%
\ifdim\wd\scratchboxtwo>\coverparameter\c!width
\global\setfalse\scratchcounter
\fi
\fi
}

\definecover[cover]

\protect
\stopmodule


\usemodule[cover]

\starttext

\startcover
Mary Poppins Sings Supercalifragilisticexpialidocious
\stopcover

\stoptext

• 🔥🔥🔥 What an amazing solution - thanks so much @Henri! I knew that the solution would involve checking if the line width matched a given value, but just had no clue where to begin—Tex is such a challenging language for me, I'm trying to get a better handle on it. TeX by Topic is downloaded, and I'm going to go through your code understand what's happening in each line. Thanks again! – derBingle Apr 26 '17 at 13:45
• One my Linux the fire emojis look a little bit like turds. – Henri Menke Apr 26 '17 at 21:13

If you don't mind breaking lines manually (should not be a big deal for cover pages), and if you don't mind the wrong optical size being used (very few fonts change glyphs according to the fontsize), you can simply typeset the box and scaling it appropriately. For example:

\define[1]\FitBox
{\scale[width=10cm,height=5cm,factor=max]{\framed[align=normal]{#1}}}

\starttext
\FitBox{Mary \\ Poppins Sings \\ Supercalifragilisticexpialidocious}

\FitBox{A \\ B \\ C \\ D \\ E \\ F \\ G \\ H \\ I \\ J \\ K \\ L \\ M \\ N}

\stoptext