2

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
        \global\advance\coverfont-\fontstep
        \exitloop
      \else
        \global\advance\coverfont\fontstep
      \fi}}

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

\starttext

\startcover
\strut Mary Poppins Sings Supercalifragilisticexpialidocious
\stopcover

\stoptext

Compiles to:
enter image description here 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.

4

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
      \global\advance\coverfont by -\fontstep
    \fi
    \ifdim\coverfont<0pt
      \exitloop
    \fi
  }
  \stop
\stopsetups

\starttext

\startcover
Mary Poppins Sings Supercalifragilisticexpialidocious
\stopcover

\stoptext

Output (with \showboxes):

enter image description here

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
      \global\advance\scratchdimen by -\coverparameter\c!stepsize
    \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

Then your main file would read

\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
  • 1
    One my Linux the fire emojis look a little bit like turds. – Henri Menke Apr 26 '17 at 21:13
4

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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