4

I'm currently writing a very long document and whenever I switch styles (I know, I shouldn't do this as often) I get tons of bad boxes, mainly due to long, unbreakable pieces of math inside a paragraph that wrapped differently. For example:

We should consider the function $alongfunctiondefinitionthat
cannotbesplitintwolinesandIdon'twanttohaveinitsownenvironment$.

Usually when I need to fix these bad boxes, I end up having to add or remove text from the previous sentences, in order to make this function definition appear in the beginning of a line. But I may have to revert this effort later if I change styles again (and it's a lot of work sometimes).

My question is the following: would it be possible to add an optional piece of text that is only inserted in case it helps avoiding bad boxes? Like:

We should consider the \opt{following continuous} function $alongfunctiondefinitionthat
cannotbesplitintwolinesandIdon'twanttohaveinitsownenvironment$.

Then TeX can decide whether following continuous should be inserted or not depending on the wrapping algorithm.

Or perhaps even better, have two optional texts to be chosen from

We \opt{define the}{would like to introduce the following continuous} function...

I don't know if there is a way to fit this into TeX's wrapping algorithm, but it would be really great!

4
  • Welcome to TeX.SX! Nice question! Please consider adding a small, simple minimal working example (MWE) will save potential answerers the effort of setting up a document for testing their solutions. :-) Mar 13, 2015 at 17:20
  • 1
    Under “classical” TeX the user has no access to line badnesses after a paragraph has been split into lines. This can maybe done with LuaTeX, but it's much easier to wait fixing bad breaks when the text is in final form.
    – egreg
    Mar 13, 2015 at 17:27
  • 1
    Is there a reason why you don't want to use display equations? One should only really use inline maths for small short/breakable mathematical expressions. Mar 13, 2015 at 18:28
  • @MobiusPizza They are somewhat problematic if all your maths needs to get pushed into footnotes, though. The spacing looks wrong most of the time. At least, it tends to look wrong to me if I use display too much.
    – cfr
    Mar 14, 2015 at 4:45

2 Answers 2

4

My solution introduces the macro \testbadnesspar{paragraph text} which calculates the maximum badness of lines in the paragraph and sets this result to \badnesspar register. The \choosepar{par1}{par2}{par3}...{par-n}{} is provided. It chooses the best paragraph form the paragraph list in accordance to the \badnesspar. And the

\chooseoptpar {paragraph text with the \opt{one}{two} macro}` 

is provided. It selects the better paragraph from two variants generated by two variants of \opt expansion.

\newif\ifboxrepeat   \boxrepeattrue
\newcount\badnesspar
\newcount\tmpnum

\def\testbadnesspar#1{\par
   \setbox0=\vbox{#1\par
   \global\badnesspar=0
   \loop
      \setbox0=\lastbox
      \ifvoid0 \boxrepeatfalse \else
         \unskip \unpenalty \setbox0=\hbox to\hsize{\unhbox0}
         \ifnum\badness > \badnesspar \global\badnesspar=\badness \fi
      \fi
      \ifboxrepeat \repeat
   }
}
\def\choosepar{\par\def\tmp{}\tmpnum=2000000 \chooseparA}
\def\chooseparA#1{\ifx^#1^\tmp \else
   \testbadnesspar{#1}
   \ifnum\tmpnum>\badnesspar \tmpnum=\badnesspar \def\tmp{#1}\fi
   \expandafter\chooseparA \fi
}
\def\chooseoptpar#1{%
   \def\opt##1##2{##1}
   \testbadnesspar{#1}\tmpnum=\badnesspar
   \def\opt##1##2{##2}
       \testbadnesspar{#1}
   \ifnum\badnesspar>\tmpnum \def\opt##1##2{##1}\fi
   #1\par
}   

%%% test
\hsize=8cm

\chooseoptpar{
   We \opt{define}{would like to introduce the following continuous}
   function $alongfunctiondefinitionthat$.
}

\bye
2
  • Great answer, but one more thing: would it be possible in addition to avoid the warnings one gets when the bad choice is tested?
    – Augusto T.
    Mar 16, 2015 at 17:42
  • 1
    @AugustoT. The reports about overfull boxes cannot be removed. But reports about underfull boxes can be removed by setting \hbadness=10000 in the \testbadnesspar macro (after the line \global\badnesspar=0 and before \loop). And you can minimalize the occurrence of overfull boxes by setting \emergencystretch to sufficient positive value (this work does the \sloppy macro from LaTeX). The \emergencystretch setting must be done globally (not only in the \testbadnesspar) because the setting influences the result.
    – wipet
    Mar 16, 2015 at 21:07
2

You can reduce/eliminate overfull boxes by going \sloppy or with the \sloppypar environment (though \sloppy will still produce underfull boxes). Of course, it eliminates the overfull problem by possibly introducing excessive space.

EDITED to introduce the \opt syntax requested by the OP, though the macro \optpar must still operate on the whole paragraph. Multiple instances of \opt are permitted in the argument to \optpar. The use of an optional argument to \optpar produces a diagnostic result that shows what the two choices would have looked like without sloppy correction.

\optpar employs an auxiliary macro \choosepar{}{} which takes two complete paragraph alternatives as its input. It sets them both \sloppy in a temporary \parbox and compares the total length of text (modulo \textwidth) with the same text set in a simple temporary box (which will do no space stretching).

It picks the paragraph that minimizes the space stretching, and sets it in a sloppypar to eliminate the overfull box, while choosing the one that is minimally bad.

This MWE employs the diagnostic option (shown in red!90) to show the choices at hand.

\documentclass{article}
\usepackage{tabto,ifthen,xcolor}
\def\compressbias{0pt}

\newcommand\savemypos[1]{%
  \tabto*{0in}\xdef#1{\TabPrevPos}\tabto{\TabPrevPos}{}%
}
\newcommand\testpar[1]{%
  \xdef\myparskip{\the\parindent}%
  \setbox2=\hbox{\parbox{\textwidth}{\sloppy%
    \hspace*{\myparskip}#1\savemypos{\finalparloc}}}
  \setbox4=\hbox{#1}%
  \wd4=\dimexpr\wd4-\compressbias\relax%
  \def\continuecheck{T}%
  \whiledo{\equal{T}{\continuecheck}}{%
    \wd4=\dimexpr\wd4-\textwidth\relax%
    \ifdim\wd4<\finalparloc\def\continuecheck{F}\fi%
  }%
  \def\thedelta{\the\dimexpr\finalparloc-\wd4\relax}%
}
\newcommand\choosepar[2]{%
  \begin{sloppypar}%
  \testpar{#1}\edef\deltaA{\thedelta}%
  \testpar{#2}\edef\deltaB{\thedelta}%
  \ifdim\deltaA>\deltaB\relax#2\else#1\fi%
  \end{sloppypar}%
}
\newcommand\optpar[2][\relax]{\choosepar{%
  \def\opt##1##2{##1}#2}{\def\opt##1##2{##2}#2}%
% THE FOLLOWING CODE USES THE OPTIONAL ARGUMENT FOR DIAGNOSTICS
  {\color{red!90}\ifx\relax#1\else%
  The options had been:\par
  \def\opt##1##2{##1}#2\par versus\par
  \def\opt##1##2{##2}#2\par
  \fi}%
}
\parskip 1ex
\begin{document}
\noindent\hrulefill

\optpar[d]{This \opt{particular}{\ignorespaces} sentence is being 
  tested, because it has a long box, 
  \mbox{$\int_0^x A + b\xi + C\xi^2 \,d\xi$} 
  in the middle of it.}

\noindent\hrulefill

\optpar[d]{This \opt{particular}{small} sentence is
  tested with \opt{several}{two} opt calls because it
  has a box, 
  \mbox{$\int_0^x A + b\xi + C\xi^2 \,d\xi$} 
  in the middle of it.}

\noindent\hrulefill

\optpar[d]{This \opt{particular}{small} sentence is being 
  tested, because it has a long box, 
  \mbox{$\int_0^x A + b\xi + C\xi^2 \,d\xi$} 
  in the middle of it.}

Both choices were bad.  But the first option was chosen 
because the second choice,
when addressed with sloppy, would introduce more space.

\end{document}

enter image description here

ORIGINAL (pre \optpar) SOLUTION SHOWING SOLUTION ELEMENTS:

In the MWE, I define the two paragraphs, and individually show the badness and their appearance as typeset (I do this to illustrate...it is not required of the method). I then employ \choosepar to select the best one. I then perform in on a second example that contains just one large chuck of boxed material surrounded by normal text.

EDITED to allow the user to select a certain length of what I call \compressbias, so that the modulo math I am using doesn't perceive a small space compression (which is acceptable) as a large space expansion (which has large badness)

\documentclass{article}
\usepackage{tabto,ifthen}
\def\compressbias{0pt}

\newcommand\savemypos[1]{%
  \tabto*{0in}\xdef#1{\TabPrevPos}\tabto{\TabPrevPos}{}%
}
\newcommand\testpar[1]{%
  \xdef\myparskip{\the\parindent}%
  \setbox2=\hbox{\parbox{\textwidth}{\sloppy%
    \hspace*{\myparskip}#1\savemypos{\finalparloc}}}
  \setbox4=\hbox{#1}%
  \wd4=\dimexpr\wd4-\compressbias\relax%
  \def\continuecheck{T}%
  \whiledo{\equal{T}{\continuecheck}}{%
    \wd4=\dimexpr\wd4-\textwidth\relax%
    \ifdim\wd4<\finalparloc\def\continuecheck{F}\fi%
  }%
  \def\thedelta{\the\dimexpr\finalparloc-\wd4\relax}%
}
\newcommand\choosepar[2]{%
  \begin{sloppypar}%
  \testpar{#1}\edef\deltaA{\thedelta}%
  \testpar{#2}\edef\deltaB{\thedelta}%
  \ifdim\deltaA>\deltaB\relax#2\else#1\fi%
  \end{sloppypar}%
}
\parskip 1ex
\begin{document}

\def\tmpA{%
verylongwordthathasnospace 
verylongwordthathasnospace
verylongwordthathasnospace
verylongwordthathasnospace
verylongwordthathasnospace
verylongwordthathasnospace
verylongwordthathasnospace.}%

\testpar{\tmpA}
added space = \thedelta

\begin{sloppypar}
\tmpA
\end{sloppypar}

\def\tmpB{%
very longword that has no space 
very longword that has no space
very longword that has no space
very longword that has no space
very longword that has no space
very longword that has no space
very longword that has no space.}%
\testpar{\tmpB}

added space = \thedelta
\begin{sloppypar}
\tmpB
\end{sloppypar}

Now let choosepar pick:

\choosepar{\tmpA}{\tmpB}

EXAMPLE 2:

\choosepar{%
I want to check the quality of this algorithm on a 
\mbox{verylongwordthathasnospace} 
to see if it will work.}{%
I want to check whether 
\mbox{verylongwordthathasnospace} 
will fit nicely in this space.}

This was the one rejected:

\begin{sloppypar}
I want to check the quality of this algorithm on a 
\mbox{verylongwordthathasnospace} 
to see if it will work.
\end{sloppypar}

\end{document}

enter image description here

You must log in to answer this question.

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