13

I want to make a command that works like

\CalcNumber{<the text>}{<width>}{<cs>}

And stores the number of lines (i. e. height in \baselineskip rounded up) needed for <the text> in <cs> if it was typeset in a box of a width <width>.

For example \CalcNumber{A}{\columwith}{\AHeight}\AHeight should yield 1, while something like \CalcNumber{M M M}{1em}{\MMMHeight}\MMMHeight should give smth like 3.

How can I do that?

P. S. A LuaLaTeX solution is also welcomed.

  • Have a look at get height on a block of latex output. – dexteritas Nov 23 '17 at 17:45
  • \newbox\calcbox \setbox\calcbox\vbox{\hsize#2\relax#1\par} \edef#3{\the\dimexpr(\dp\calcbox+\ht\calcbox)/(\baselineskip)\relax} Sorry, haven't tested and probably doesn't work, because of limitations of \dimexpr and may be because of problems with the syntax (I don't know much about boxes), but something like that would work. – Manuel Nov 23 '17 at 17:48
  • Count Lines – SLx64 Nov 23 '17 at 17:54
  • tex.stackexchange.com/q/242003/21930 – Manuel Nov 23 '17 at 17:56
  • 2
    IMO the question is not well-defined. If you can have large display math in it, what does it even mean to talk of the number of lines? Does all the math count as “one line”? And do you want the number of lines, or the height? And if your actual use-case is “I need to calculate the height to put it inside a minipage of that height” why not ask that as the question (because there are other solutions to that, that do not involve this complicated workaround of calculating the height or the number of lines)? – ShreevatsaR Dec 19 '17 at 3:19
2
+50

You can use adjustbox to box the content in a minipage of the given width and store its total height, then calculate the factor of the baselineskip using eTeX (and finally discard the content).

This is similar to other answers which using boxing, but a more out-of-the-box solution (no pun intended).

\documentclass{article}
\usepackage{adjustbox}

\newlength\mycalclength

\newcommand\CalcNumber[3]{%
    \adjustbox{minipage={#2}, gstore totalheight=\mycalclength, discard}{#1}%
    \edef#3{\the\numexpr\dimexpr \mycalclength/\baselineskip\relax\relax}%
}

\begin{document}

\CalcNumber{A}{\columnwidth}{\AHeight}\AHeight

\CalcNumber{M M M M M M M}{1em}{\MMMHeight}\MMMHeight

\end{document}

If you rearrange the order of the arguments and have the content last, you can use the feature of \adjustbox to read the last argument as box, not as argument, allowing for verbatim and other special content. Note that the assignment is then global as it happens in a group.

\documentclass{article}
\usepackage{adjustbox}

\newlength\mycalclength

\newcommand\CalcNumber[2]{%
    \adjustbox{minipage={#1}, gstore totalheight=\mycalclength, execute={\gdef#2{\the\numexpr\dimexpr \mycalclength/\baselineskip\relax\relax}}, discard}
}

\begin{document}

\CalcNumber{\columnwidth}{\AHeight}{A}\AHeight

\CalcNumber{1em}{\MMMHeight}{M M M M M M M}\MMMHeight

\end{document}
6

There are two ways that you can do this: one is with \prevgraf, and the other by calculating the lines manually.

If you do it with \prevgraf, that gives you the amount of lines in the previous paragraph. This will only work if your input is meant to be only one paragraph. What we do is we use \setbox0 to put the text into a vbox which we don't print. In the vbox, we set the hsize to your width, the second argument, and then print the first argument as a paragraph. We manually end the paragraph with \par, and the use \xdef to save the current value of \prevgraf into the cs you passed as the third argument.

\def\CalcNumber#1#2#3{%
  \setbox0\vbox{\hsize=#2 \noindent#1\par\xdef#3{\the\prevgraf}}}

If your input is more than one paragraph, then you can use the same setup as follows:

\makeatletter
\long\def\CalcNumber#1#2#3{%
  \setbox0\vbox{\hsize=#2 \noindent#1}}
\@tempcnta=\dimexpr(\ht0+\dp0)/\baselineskip
\xdef#3{\the\@tempcnta}}
\makeatother

In a similar fashion to our other approach, we typeset our text in a box. We need to define our \CalcNumber as \long, so it can accept text of more than one paragraph. Then, we add the height and depth of the box, and divide by \baselineskip, saving it in a LaTeX temporary count register (this will round the number for us). We then store this in our give control sequence, as above.

A caveat here is that this will use whatever the baselineskip is when you call the macro. You'd need to account for variations in font sizes, depending on what you plan on using this for.

4

FWIW, ConTeXt provides a macro \determinenofline{...} that determines the number of lines of the content (when typeset in a \vbox). The result is stored in the counter: \noflines.

This counts the number of typeset lines (not the height in the multiple of \lineheight (or what is called \baselineskip in LaTeX). Here is one example from supp-box.mkiv to show that:

\starttext

\determinenoflines{test\\test}
\the\noflines

\determinenoflines{\bfd test\\test}
\the\noflines

\determinenoflines{\definedfont[Sans at 40pt]test\\test}
\the\noflines
\stoptext

All three of these tests give 2.

To count the number of lines for a content of a specific width, you can simply set \hsize for the content. For example:

\starttext
\vbox{\hsize=3cm \input ward\relax}

\determinenoflines{\hsize=3cm \input ward\relax}
\the\noflines
\stoptext

which gives (the first \vbox is just to verify the result).

enter image description here

2

This is an attempt based on the lineno package.

\documentclass{article}
\usepackage[running]{lineno}
\usepackage{lipsum}
\newcounter{mylines}
\newsavebox{\marmot}
\newcommand{\CalcNumber}[3]{
\savebox{\marmot}{\begin{minipage}{#2}\linenumbers*
\begin{internallinenumbers}
#1 \par
\setcounter{mylines}{\thelinenumber}\addtocounter{mylines}{-1}
\end{internallinenumbers}
\end{minipage}}
\let#3=\themylines}
\begin{document}

\CalcNumber{\lipsum[1]}{8cm}{\myx}

\myx

\CalcNumber{A}{8cm}{\myy}

\myy


\end{document}
2

This uses \vsplit to count the lines. The only surprise was from intemise which adds a blank line (and some negative \vspace) at the start.

One can return the number of lines by adding \theNLcount to the end of the macro. In this case, I wanted to show the number using \marginpar and it was using \marginparwidth as the default width.

Since you want to split off only one line at a time, it is better so request as little as possible, hence the 0pt.

\documentclass{article}
\usepackage{amsmath}
\usepackage{blindtext}

\newcounter{NLcount}

\newcommand{\numberoflines}[2][\linewidth]% #1=width (optional), #2=text
{\bgroup
  \hsize=#1\relax
  \clubpenalty=0
  \widowpenalty=0
  \displaywidowpenalty=0
  \predisplaypenalty=0
  \postdisplaypenalty=0
  \interlinepenalty=0
  \setbox0=\vbox{#2}%
  \setcounter{NLcount}{0}%
  \loop\ifdim\ht0>0pt
    \stepcounter{NLcount}%
    \setbox1=\vsplit0 to 0pt
  \repeat
\egroup}% \theNLcount

\begin{document}

\numberoflines{above \[x=a\] more text \begin{equation} y=b \end{equation} below}

\hrule\leavevmode\marginpar{\theNLcount{} lines}
above \[x=a\] more text \begin{equation} y=b \end{equation} below
\hrule

\numberoflines{above\newline \rule{1in}{1in}\newline below}

\hrule\leavevmode\marginpar{\theNLcount{} lines}
above\newline \rule{1in}{1in}\newline below
\hrule

\numberoflines{\begin{itemize}
  \item One
  \item Two
\end{itemize}}

\hrule\leavevmode\marginpar{\theNLcount{} lines}
\begin{itemize}
  \item One
  \item Two
\end{itemize}
\hrule

\numberoflines{\blindtext}

\hrule\leavevmode\marginpar{\theNLcount{} lines}
\blindtext
\hrule

\numberoflines{\tiny\blindtext}

\hrule\leavevmode\marginpar{\theNLcount{} lines}
{\tiny\blindtext}
\hrule

\newpage
\numberoflines[2in]{\blindtext}
\hrule\noindent\marginpar{\theNLcount{} lines}
\parbox{2in}{\strut\blindtext}
\hrule

\end{document}
1

A LaTeX solution, maybe a bit more complicated than necessary, but it is working:

\documentclass[12pt]{article}

\makeatletter

\def\RoundLines#1\relax{%
  \@tempdimb=#1\relax
  \@tempcnta=0
  \loop%
  \ifdim\@tempdimb>0\p@%
    \global\@tempdimb\dimexpr\@tempdimb-\baselineskip\relax
    \global\advance\@tempcnta\@ne
  \repeat
  \xdef\result{\@tempcnta}%
  }

\newbox\calcbox
\def\CalcNumber#1#2#3{%
  \edef\@argii{#2}%
  \expandafter\@tempdima\@argii\relax
  \setbox\calcbox\vbox{\hsize\@tempdima\relax\noindent#1\par}%
  \@tempdima=\dimexpr(\dp\calcbox+\ht\calcbox)\relax
  \RoundLines\@tempdima\relax
  \expandafter\xdef\csname#3\endcsname{\the\result}%
  % \copy\calcbox%% for testing
}
\makeatother

\begin{document}
\CalcNumber{A}{20mm}{AHeight}
has {\AHeight} lines

\CalcNumber{M M M}{1mm}{MMMHeight}
has {\MMMHeight} lines
\end{document}

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.