2

Question

Is it possible to get the width of a multi-paragraph piece of text? For example, if I want to get the width of a single piece of text, I can do the following:

\documentclass[a4paper]{article}
\begin{document}

\newlength{\mytextwidth}
\settowidth{\mytextwidth}{some text}

\end{document}

However, if that text has multiple paragraphs, that doesn't work. E.g. If I do something like the following I get the error Paragraph ended before \@settodim was complete. <to be read again>:

\documentclass[a4paper]{article}
\begin{document}

\newlength{\testlen}
\settowidth{\testlen}{
    some text

    some longer text
}

\end{document}

What I'm wondering is if there is a way to get the widest width of text content (even if it's multi-paragraph text). So, for the first example it would still give the length of some text and for the second example, it would give the length of some longer text.


Context

Above is what I think I need for what I'm trying to do, but in case it's helpful, I'll also add the context for why I'm interested in getting this.

I'm attempting to write a command that will take in text content for a left side and right side. The content will be split into two columns, with the left side content being flush left and the right side content being flush right. The columns will adjust themselves to fit as much text on a line as they can before wrapping to the next line.

So if the left side content is much longer than the right side content, the right column will be much shorter in width than the left column and vice-versa. If neither side's content requires half the line width, each column's width will simply be half the line width. Similarly, if both side's content requires at least half the line width, each column's width will just be half the line width.

Here's what I have, which is working for single paragraph text:

\documentclass[a4paper]{article}
\begin{document}

% length variables for the command below
\newlength{\leftwidth}
\newlength{\rightwidth}
\newlength{\halfline}
\newlength{\leftexcess}
\newlength{\rightexcess}

% arg1: flush left minipage content
% arg2: flush right minipage content
\newcommand{\mycmd}[2]{
    \settowidth{\leftwidth}{#1}
    \settowidth{\rightwidth}{#2}
    \setlength{\halfline}{0.5\linewidth}

    % if the left side content doesn't need at least half of the 
    % line width, store the excess leftover space
    \ifdim\leftwidth<\halfline\relax
        \setlength{\leftexcess}{\dimexpr(\halfline-\leftwidth)}
    \else
        \setlength{\leftexcess}{0pt}
    \fi

    % if the right side content doesn't need at least half of the 
    % line width, store the excess leftover space
    \ifdim\rightwidth<\halfline\relax
        \setlength{\rightexcess}{\dimexpr(\halfline-\rightwidth)}
    \else
        \setlength{\rightexcess}{0pt}
    \fi

    % neither side has any leftover space, so each column just gets half the line
    \ifdim\rightexcess=0pt\relax
        \ifdim\leftexcess=0pt\relax
            \setlength{\leftwidth}{\halfline}
            \setlength{\rightwidth}{\halfline}
        \fi
    \fi

    % neither side needs any extra space to fit, so each column just gets half the line
    \ifdim\leftexcess>0pt\relax
        \ifdim\rightexcess>0pt\relax
            \setlength{\leftwidth}{\halfline}
            \setlength{\rightwidth}{\halfline}
        \fi
    \fi

    % if the right side has space to spare and the left side needs more space than
    % just half the line, give the left side the excess space from the right side.
    \ifdim\rightexcess>0pt\relax
        \ifdim\leftwidth>\halfline\relax
            \setlength{\leftwidth}{\dimexpr(\halfline+\rightexcess)}
        \fi 
    \fi

    % if the left side has space to spare and the right side needs more space than
    % just half the line, give the right side the excess space from the left side.
    \ifdim\leftexcess>0pt\relax
        \ifdim\rightwidth>\halfline\relax
            \setlength{\rightwidth}{\dimexpr(\halfline+\leftexcess)}
        \fi
    \fi

    % command output
    \par\noindent%
    \begin{minipage}[t]{\leftwidth}%
        \begin{flushleft}%
            #1
        \end{flushleft}%
    \end{minipage}%
    \begin{minipage}[t]{\rightwidth}%
        \begin{flushright}%
            #2
        \end{flushright}%
    \end{minipage}%
}

% works fine
\mycmd
{
    left left left left left left left left left left left left left left left
}
{
    right right
}

% errors with: Paragraph ended before \@settodim was complete.
% \mycmd
% {
%     left left left left left left left left left left left left left left left

%     left left left
% }
% {
%     right right
% }

\end{document}
9
  • 2
    Consider using varwidth: \usepackage{varwidth} \newbox{\mybox} ... \begin{lrbox}{\mybox}\begin{varwidth}{\maxdimen} <paragraph text> \end{varwidth}\end{lrbox}. Now \wd\mybox provides the widest line length.
    – Werner
    Apr 20 at 22:26
  • 2
    varwidth or a tabulary with columns {LR} would work. Apr 20 at 22:49
  • paragraphs are broken in to lines to a pre-specified width so the width of lines is known in advance, however you can do trial settings and choose the width subject to constraints as done (with different constraints) by tabularx tabulary or varwidth packages. tabulary is probably closest to what you want. Apr 20 at 22:58
  • Thanks Werner and David! Didn't know about these packages, gonna try them out a bit and see if I can get to what I want with them.
    – tdashroy
    Apr 20 at 23:15
  • I tried varwidth first and it ended up working perfectly. I haven't tried tabulary yet, and it's possible it's a better solution for my overall goal (I'll definitely look to it first next time I'm trying to do something like this), but I think the varwidth solution better answers the specific question I posted here. @Werner if you want to post your comment as a solution, I'd be happy to accept it.
    – tdashroy
    Apr 21 at 17:51
1

The varwidth package provides a similarly-named varwidth environment that acts like minipage. It takes a width argument like minipage, but instead of fixing the output to this width, it reduces the width of the resulting box to something smaller if the contents can fit within this smaller width. So you can specify the maximum width possible (\maxdimen) and varwidth should shrink that down to what you need - the maximum width of any paragraph within the environment body.

enter image description here

\documentclass{article}

\usepackage{varwidth}

\newsavebox{\mybox}
\newlength{\mylen}
\begin{document}

\settowidth{\mylen}{left left left left left left left left left left left left left left left}%
1: \the\mylen

\begin{lrbox}{\mybox}
\begin{varwidth}{\maxdimen}
left left left left left left left left left left left left left left left

right right
\end{varwidth}
\end{lrbox}%
2: \the\wd\mybox

\end{document}
0

tabulary can be used to get a similar effect.

enter image description here

\documentclass{article}

\usepackage{tabulary}
\newcommand\mycmd[2]{%
  \begin{center}
    \begin{tabulary}{\textwidth}{@{}LR@{}}
     #1&#2
    \end{tabulary}
  \end{center}}
\begin{document}

\mycmd
 {
     left left left left left left left left left left left left left left left

     left left left
 }
 {
     right right
 }


\mycmd
 {
     left left left left left

     left left left
 }
 {
     right right
 }

\mycmd
 {
     left left left left left left left left left left left left left left left
     left left left left left left left left left left left left left left left

     left left left
 }
 {
     right right
 }


\mycmd
 {
     left left left left left left left left left left left left left left left

     left left left
 }
 {
     right right right right right right right right right right right right right right
     right right right right right right right right right right right right right right
     right right right right right right right right right right right right right right
     right right right right right right right right right right right right right right
     right right right right right right right right right right right right right right
 }


\end{document}
5
  • If there's a way to get the second example, the one with the short text on both the left and right sides, to be flush left and right with the page, then this would work (and be much simpler than my current solution) for the context of the reason I asked the question.
    – tdashroy
    Apr 22 at 20:23
  • 1
    @tdashroy yes I think the idea that tabulary shouldn't stretch isn't right in all cases originally it was designed for an automated "choose the best table layout" so the idea was not to mess up and over stretch small tables, but to keep large tables on page, but it was all a long time ago (last century...) I think there is an answer on site already that drops that, I'll see if I can find it.... Apr 22 at 20:36
  • 1
    @tdashroy this? tex.stackexchange.com/a/87543/1090 Apr 22 at 20:40
  • I tried out the patch and it works sometimes. Other times I get an Arithmetic overflow. error. Couldn't really nail down when it works and when it doesn't. Something to do with the lengths of the content for of the two sides.
    – tdashroy
    Apr 23 at 19:05
  • I could probably use a better arithemetic in tabulary these days (space for tex calculations was tight in the 90's when I wrote that). If you have an example that overflows that would help. (you could send to me if you don't want to post it here @tdashroy Apr 23 at 19:58
0

Here, I count the width of all cat-11 and 12 tokens, as well as \parindent associated with paragraphs (I bypass any expansion of macros and ignore their width). In \balance, I do the math to calculate the ratio of each width to the total width.

I create minipages, using a specified usable fraction of linewidth to set the parts in minipages based on those ratios. Not knowing how much blank space is available at the end of a paragraph, I add .25\usablewidth as a very approximate average. So if the two sides vary in numbers of paragraphs, that can screw things up a little.

I also, at this point do not account for extremely short entries, less than a line in width...I'm not sure how a general algorithm should handle that case anyhow, if one entry is very short and the other very long.

\documentclass{article}
\usepackage{tokcycle}
\newlength\Xwd
\newlength\Ywd
\edef\svparindent{\the\parindent}
\newcommand\addtowd[2]{\sbox0{#2}\addtolength #1{\wd0}}
\newcommand\countwidth[3]{%
  \def#1{#3}%
  \setlength#2{0pt}%
  \tokcycle
  {\tctestifcatnx A##1{\addtowd{#2}{##1}}
    {\tctestifcatnx.##1{\addtowd{#2}{##1}}{}}\addcytoks{##1}}
  {\processtoks{##1}}
  {\tctestifx\par##1{\addtolength{#2}{%
  \dimexpr\parindent+.25\usablewidth}}{}\addcytoks{##1}}
  {\addtowd{#2}{##1}\addcytoks{##1}}
  {#3}%
} 
\newlength\Awd
\newlength\Bwd
\newlength\ABwd
\newlength\usablewidth
\usablewidth=.9\textwidth
\def\truncdec#1.#2\relax{#1}
\makeatletter
\newcommand\balance[4]{%
  \Awd=#2\relax
  \Bwd=#4\relax
  \ABwd=\dimexpr\Awd+\Bwd\relax
  \edef\ABcnt{\strip@pt\ABwd} 
  \divide\Awd by \expandafter\truncdec\ABcnt\relax
  \divide\Bwd by \expandafter\truncdec\ABcnt\relax
  \edef\Apct{\strip@pt\Awd}%
  \edef\Bpct{\strip@pt\Bwd}%
  \par\noindent
  \begin{minipage}[t]{\Apct\usablewidth}
    \parindent=\svparindent#1\end{minipage}%
  \hfill
  \begin{minipage}[t]{\Bpct\usablewidth}
    \parindent=\svparindent#3\end{minipage}%
  \bigskip
}
\makeatother
\begin{document}   
\countwidth{\X}{\Xwd}{This is a test of blah bla blah.
This is a test of blah bla blah.

This is a test of blah bla blah.
This is a test of blah bla blah.
This is a test of blah bla blah.
This is a test of blah bla blah.} 

\countwidth{\Y}{\Ywd}{This is a test of blah bla blah.
This is a test of blah bla blah.

This is a test of blah bla blah.}

\balance{\X}{\Xwd}{\Y}{\Ywd}

\countwidth{\Y}{\Ywd}{This is a test of blah bla blah.
This is a test of blah bla blah.

This is a test of blah bla blah.
This is a test of blah bla blah.
This is a test of blah bla blah.
This is a test of blah bla blah.
This is a test of blah bla blah.
This is a test of blah bla blah.
This is a test of blah bla blah.
This is a test of blah bla blah.

This is a test of blah bla blah.
This is a test of blah bla blah.
This is a test of blah bla blah.
This is a test of blah bla blah.
This is a test of blah bla blah.
This is a test of blah bla blah.
This is a test of blah bla blah.
This is a test of blah bla blah.}

\balance{\X}{\Xwd}{\Y}{\Ywd}

\countwidth{\Y}{\Ywd}{This is a test of blah bla blah.
This is a test of blah bla blah.

This is a test of blah bla blah.
This is a test of blah bla blah.
This is a test of blah bla blah.
This is a test of blah bla blah.

This is a test of blah bla blah.
This is a test of blah bla blah.

This is a test of blah bla blah.

This is a test of blah bla blah.
This is a test of blah bla blah.
This is a test of blah bla blah.}

\balance{\X}{\Xwd}{\Y}{\Ywd}

\end{document}

enter image description here

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.