Here is a pure LaTeX solution to the problem, using my tokcycle
package (I even offer a pure TeX solution at the end!!). You will need the new version from 2021-03-10, because I define my environment using the new \xtokcycleenvironment
feature, which allows not only the definition of the token-cycle directives, but additional "set-up" and "close-out" code to be executed before and after the token-cycle.
I note that even in the more extensively answered question, Is there any way to do a correct word count of a LaTeX document?, there are no other pure-latex solutions to the problem.
This approach introduces the pseudoenvironment \countem...\endcountem
which will count both words (defined as strings of cat-11 tokens bounded by anything other than a cat-11 token---thus, hyphenated-compound-words are each counted separately) as well as letters (only cat-11 tokens count as letters). Because it is an environment, you can apply it to a portion of a document, and apply it several times within a document.
In it's simplest usage, the environment will populate two counters, wordcount
and lettercount
with values that you can test, typeset, or whatever. But it offers several advanced features.
If you set the counter wordlimit
to a positive number, it will change the color of the counted text to \limitcolor
(default red
) when the running word count exceeds \value{wordlimit}
If you set \runningcounttrue
, you will get a superscripted word and letter count typeset after each word.
If you set \summarycounttrue
, you will get the total word and letter counts typeset at the conclusion of the environment.
The environment can digest and process macros in the input stream, essentially echoing them to the output (and not counting the macro token as a letter or word). But beware! Macro arguments containing cat-11 tokens will count as letters and words. Furthermore, if \runningcountrue
, the added superscripts will pollute the macro argument. The solution to these problems is to use the tokcycle
escape feature, |...|
, which will echo the ...
tokens to the output without processing them through the token cycle directives. Example: counted input |\rule{2ex}{1ex}| more counted input
.
A very nice feature (enable by the tokcycle
logic) is that groups pose no problem to the features. For example, if you exceed wordlimit
inside of a \textit
argument, the color will change there and continue changed even as the \textit
argument is exited.
If one wanted a count for the whole document, one would merely start \countem
immediately after \begin{document}
and then \endcountem
immediately prior to \end{document}
.
\documentclass{article}
\usepackage{tokcycle}[2021-03-10]
\usepackage{xcolor}
\newcounter{wordcount}
\newcounter{lettercount}
\newcounter{wordlimit}
\newif\ifinword
% USER PARAMETERS
\newif\ifrunningcount
\newif\ifsummarycount
\def\limitcolor{red}
\setcounter{wordlimit}{0}
%%
\makeatletter
% \tc@defx is like \def, but expands the replacement text once prior to assignment
\newcommand\addtomacro[2]{\tc@defx#1{#1#2}}
\newcommand\changecolor[1]{\tctestifx{.#1}{}{\addcytoks{\color{#1}{}}%
\tc@defx\currentcolor{#1}}}
\makeatother
\newcommand\dumpword{%
\addcytoks[1]{\accumword}%
\ifinword\stepcounter{wordcount}
\ifrunningcount\addcytoks[x]{$^{\thewordcount,\thelettercount}$}\fi
\ifnum\thewordcount=\value{wordlimit}\relax\changecolor{\limitcolor}\fi
\fi%
\inwordfalse
\def\accumword{}}
\newcommand\addletter[1]{%
\tctestifcatnx A#1{\stepcounter{lettercount}\inwordtrue}{\dumpword}%
\addtomacro\accumword{#1}}
\xtokcycleenvironment\countem
{\addletter{##1}}
{\dumpword\groupedcytoks{\processtoks{##1}\dumpword\expandafter}\expandafter
\changecolor\expandafter{\currentcolor}}
{\dumpword\addcytoks{##1}}
{\dumpword\addcytoks{##1}}
{\stripgroupingtrue\def\accumword{}\def\currentcolor{.}
\setcounter{wordcount}{0}\setcounter{lettercount}{0}}
{\dumpword\ifsummarycount\tcafterenv{%
\par(Wordcount=\thewordcount, Lettercount=\thelettercount)}\fi}
\begin{document}
\countem
This is a test if word counting occurs.
\endcountem
I know there were \thewordcount{} words and \thelettercount{} letters in the
prior sentence.
\bigskip\setcounter{wordlimit}{7}
\countem
This is a test if color changes after seven words.
\endcountem
\bigskip\runningcounttrue
\countem
This is a running count.
But...punctuation does not count as characters.
\endcountem
\bigskip
\countem
This \textbf{is a} running count. |\textsc{Skipping macros
\rule{1ex}{1.5ex}/other text}|
But...\textit{punctuation does} not count as characters.
\endcountem
\bigskip\runningcountfalse\summarycounttrue\setcounter{wordlimit}{125}
\countem
As any dedicated reader can clearly see, the Ideal of
practical reason is a representation of, as far as I know, the things
in themselves; as I have shown elsewhere, the phenomena should only be
used as a canon for our understanding. The paralogisms of practical
reason are what first give rise to the architectonic of practical
reason. As will easily be shown in the next section, reason would
thereby be made to contradict, in view of these considerations, the
Ideal of practical reason, yet the manifold depends on the phenomena.
Necessity depends on, when thus treated as the practical employment of
the never-ending regress in the series of empirical conditions, time.
Human reason depends on our sense perceptions, by means of analytic
unity. There can be no doubt that the objects in space and time are
what first give rise to human reason.
Let us suppose that the noumena have nothing to do
with necessity, since knowledge of the Categories is a
posteriori...
\endcountem
\end{document}
In the MWE, the first example is the basic one. The words and letters of the sentence are counted and made available in wordcount
and lettercount
.
In the 2nd example, wordlimit
is set, and we observe the color of the text to change after 7 words.
In the 3rd example, the running count is enabled. One can observe that spaces and punctuation are not counted as letters but can serve to separate words (in the case of ...
).
In the 4th example, several additional features are demonstrated: text containing a macro sequence is escaped between |...|
delimiters, to avoid polluting the macro arguments. Additionally, we note the color changes inside an italicized group of text. Nonetheless, the group-ending behavior is as expected, and there is no disruption to the word/letter counts nor the limitcolor
.
In the 5th and last example, because the text block is longer, we raise the wordlimit
and turn off the running count, and instead employ a summary count of the \countem
text block.
BONUS: PURE TeX SOLUTION!!
Here is the above answer adapted for Plain TeX. (Thanks to David for chat help with \input color
)
%%%
% MAKE COLOR AVAILABLE
\input color
%%%
%%% COLOR INPUT COMPLETE, NOW LET"S CREATE A TOKEN-CYCLE
%
\input tokcycle
\newcount\wordcount
\newcount\lettercount
\newcount\wordlimit
\newif\ifinword
% USER PARAMETERS
\newif\ifrunningcount
\newif\ifsummarycount
\def\limitcolor{red}
\wordlimit=0\relax
%%
\catcode`@=11
% \tc@defx is like \def, but expands the replacement text once prior to assignment
\def\addtomacro#1#2{\tc@defx#1{#1#2}}
\def\changecolor#1{\tctestifx{.#1}{}{\addcytoks{\color{#1}{}}%
\tc@defx\countemcurrentcolor{#1}}}
\catcode`@=12
\def\dumpword{%
\addcytoks[1]{\accumword}%
\ifinword\global\advance\wordcount 1\relax
\ifrunningcount\addcytoks[x]{$^{\the\wordcount,\the\lettercount}$}\fi
\ifnum\wordcount=\wordlimit\relax\changecolor{\limitcolor}\fi
\fi%
\inwordfalse
\def\accumword{}}
\def\addletter#1{%
\tctestifcatnx A#1{\global\advance\lettercount 1\relax\inwordtrue}{\dumpword}%
\addtomacro\accumword{#1}}
\xtokcycleenvironment\countem
{\addletter{##1}}
{\dumpword\groupedcytoks{\processtoks{##1}\dumpword\expandafter}\expandafter
\changecolor\expandafter{\countemcurrentcolor}}
{\dumpword\addcytoks{##1}}
{\dumpword\addcytoks{##1}}
{\stripgroupingtrue\def\accumword{}\def\countemcurrentcolor{.}
\global\wordcount=0\relax\global\lettercount 0\relax}
{\dumpword\ifsummarycount\tcafterenv{%
\par(Wordcount=\the\wordcount, Lettercount=\the\lettercount)}\fi}
% SETUP COMPLETE. NOW FOR THE DOCUMENT
\countem
This is a test if word counting occurs.
\endcountem
I know there were \the\wordcount{} words and \the\lettercount{} letters in the
prior sentence.
\bigskip\wordlimit=7\relax
\countem
This is a test if color changes after seven words.
\endcountem
\bigskip\runningcounttrue
\countem
This is a running count.
But...punctuation does not count as characters.
\endcountem
\bigskip
\countem
This {\bf is a} running count. |{Skipping macros
rule{1ex}{1.5ex}/other text}|
But...{\it punctuation does} not count as characters.
\endcountem
\bigskip\runningcountfalse\summarycounttrue\wordlimit=125\relax
\countem
As any dedicated reader can clearly see, the Ideal of
practical reason is a representation of, as far as I know, the things
in themselves; as I have shown elsewhere, the phenomena should only be
used as a canon for our understanding. The paralogisms of practical
reason are what first give rise to the architectonic of practical
reason. As will easily be shown in the next section, reason would
thereby be made to contradict, in view of these considerations, the
Ideal of practical reason, yet the manifold depends on the phenomena.
Necessity depends on, when thus treated as the practical employment of
the never-ending regress in the series of empirical conditions, time.
Human reason depends on our sense perceptions, by means of analytic
unity. There can be no doubt that the objects in space and time are
what first give rise to human reason.
Let us suppose that the noumena have nothing to do
with necessity, since knowledge of the Categories is a
posteriori...
\endcountem
\bye