Highest Variable Value on Every Page

I am writing a document where each paragraph is categorized. I'm encoding the category as a simple numeric variable: 1, 2, 3, &c. On each page I want to know and possibly display the highest category value of any content on the page. I'm using a simple counter and wrapping each paragraph in a macro that updates the counter value if the paragraph category is higher than anything encountered so far. I'm resetting the counter every page using either everypage or everyshi. So far it almost works.

Also, I really, really don't want paragraphs to be broken across pages. When I set

\widowpenalties 1 10000


to try to enforce this, the category counter behaves in the strangest way. This strange behavior is my trouble.

The page category counter always resets two paragraphs too late into the next page. I'm completely mystified. Can anyone please shed light on this?

I'll happily accept suggestions of other ways to accomplish what I want, too.

Here is a not-very-minimal working example. It's 9 pages long. There are four pages at the beginning that show the behavior without setting widowpenalties. Then there's a separation page before four more identical pages with widowpenalties turned up to 10,000.

When you run the code, each paragraph is marked with two like 2-1 The first number is the highest category yet encountered on that page and the second number is that paragraph's category. To see the odd behavior, notice that n page 2, you'll see the first two paragraphs marked 1-1 but on page 7 you'll see the exact same two paragraphs marked 3-1.

    \documentclass{article}

\usepackage{xifthen}
\usepackage{lipsum}
\usepackage{etoolbox}
\usepackage{everypage}

\newcommand\settext[3]{\csdef{#1#2}{#3}}
\newcommand\gettext[2]{\csuse{#1#2}}

\newcounter{pageCategoryCntr}
\setcounter{pageCategoryCntr}{1}
%\zmakeperpage{pageCategoryCntr}
%\EveryShipout{\setcounter{pageCategoryCntr}{1}}

\newcommand{\category}[2]{
\ifthenelse{#1 > \value{pageCategoryCntr}}{
\setcounter{pageCategoryCntr}{#1}
}{}
\thepageCategoryCntr\ - #1 -
#2
}

\begin{document}
\category{1}{\lipsum[2]}

\category{1}{\lipsum[3]}

\category{1}{\lipsum[2]}

\category{3}{\lipsum[3]}

\category{1}{\lipsum[2]}

\category{1}{\lipsum[3]}

\category{1}{\lipsum[4]}

\category{1}{\lipsum[5]}

\category{1}{\lipsum[2]}

\category{1}{\lipsum[3]}

\category{1}{\lipsum[2]}

\category{1}{\lipsum[3]}

\category{3}{\lipsum[2]}

\category{1}{\lipsum[3]}

\category{1}{\lipsum[4]}

\category{1}{\lipsum[5]}

\newpage
\widowpenalties 1 10000
\clubpenalties 1 10000
\raggedbottom
Now another copy of the same but we set the widowpenalties and
clubpenalties to avoid breaking paragraphs across pages.
\newpage
\category{1}{\lipsum[2]}

\category{1}{\lipsum[3]}

\category{1}{\lipsum[2]}

\category{3}{\lipsum[3]}

\category{1}{\lipsum[2]}

\category{1}{\lipsum[3]}

\category{1}{\lipsum[4]}

\category{1}{\lipsum[5]}

\category{1}{\lipsum[2]}

\category{1}{\lipsum[3]}

\category{1}{\lipsum[2]}

\category{1}{\lipsum[3]}

\category{3}{\lipsum[2]}

\category{1}{\lipsum[3]}

\category{1}{\lipsum[4]}

\category{1}{\lipsum[5]}
\end{document}

• – touhami Jul 15 '16 at 7:25
• Hm, are you suggesting enclosing each paragraph in one of the absolutely unbreakable environments given in the first answer to that question? Normally that wouldn't be particularly reasonable in my 100-page document... But I do have to categorize every paragraph anyway! I'll give it a try. – Austin A. Jul 15 '16 at 12:35
• One paragraph I understand, since the paragraph is written first then tested to see if it will fit on the current page. If not, it is shipped to the next page. Maybe the second paragraph is due to the club test. Anyway, check out the ifoddpage package. – John Kormylo Jul 15 '16 at 13:08
• BTW, if you would like the absolute highest category per page rather than the highest so far, that also be done using the aux file. – John Kormylo Jul 15 '16 at 13:18
• @John Kormylo, I am ultimately interested in the absolute highest category per page and displaying that value on the page. The specific trouble I'm having is that a high-value page bleeds over into the next page , and0 then the next, & c. If there's another way of getting the information I want using the aux file, please do share! – Austin A. Jul 15 '16 at 13:31

Here is the aux file solution. Interestingly, you need to use at least \leavevmode to put \protected@write INSIDE the paragraph instead of before it.

I really only need one counter (CategoryCntr), but using \setcounter is a easier to understand.

\documentclass{article}

%\usepackage{xifthen}
\usepackage{lipsum}
%\usepackage{etoolbox}
%\usepackage{everypage}

\newcommand\settext[3]{\csdef{#1#2}{#3}}
\newcommand\gettext[2]{\csuse{#1#2}}

\newcounter{CategoryMax}% used to compute max category per page
\newcounter{CategoryCntr}% increment every \category
\newcounter{CategoryPage}% used instead of the page counter

\newcommand{\newcategory}[3]{% #1 = index, #2 = category, #3 = page
\expandafter\gdef\csname CategoryPage#1\endcsname{#3}% save page per index
\ifnum\value{CategoryPage}=#3\relax
\ifnum\value{CategoryMax}<#2\relax \setcounter{CategoryMax}{#2}\fi
\else
\ifnum\value{CategoryPage}>0\relax
\expandafter\xdef\csname CategoryMax\theCategoryPage\endcsname{\theCategoryMax}% save max category
\fi
\setcounter{CategoryMax}{#2}%
\setcounter{CategoryPage}{#3}%
\fi
}
\AtBeginDocument{% save last page
\ifnum\value{CategoryPage}>0\relax
\expandafter\xdef\csname CategoryMax\theCategoryPage\endcsname{\theCategoryMax}%
\fi
}
\makeatletter
\newcommand{\category}[2]{% #1 = category, #2 = text
\par\leavevmode
\stepcounter{CategoryCntr}%
\protected@write\@auxout{}{\string\newcategory{\theCategoryCntr}{#1}{\thepage}}%
%\protected@write\@auxout{\let\arabic\relax}{\string\newcategory{\theCategoryCntr}{#1}{\arabic{page}}}%
\@ifundefined{CategoryPage\theCategoryCntr}{?? - #1 - #2}{%
\setcounter{CategoryPage}{\csname CategoryPage\theCategoryCntr\endcsname}%
{\csname CategoryMax\theCategoryPage\endcsname} - #1 - #2
}%
}
\makeatother

\begin{document}
\widowpenalties 1 10000
\clubpenalties 1 10000
\raggedbottom

\category{1}{\lipsum[2]}

\category{1}{\lipsum[3]}

\category{1}{\lipsum[2]}

\category{3}{\lipsum[3]}

\category{1}{\lipsum[2]}

\category{1}{\lipsum[3]}

\category{1}{\lipsum[4]}

\category{1}{\lipsum[5]}

\category{1}{\lipsum[2]}

\category{1}{\lipsum[3]}

\category{1}{\lipsum[2]}

\category{1}{\lipsum[3]}

\category{3}{\lipsum[2]}

\category{1}{\lipsum[3]}

\category{1}{\lipsum[4]}

\category{1}{\lipsum[5]}

\end{document}

• Thank you for posting this. I was hoping to see what this aux file wizardry would look like! This solution by the way seems to naturally adapt to \newpage commands. – Austin A. Jul 15 '16 at 16:24
• Take a look at the aux file to see what it is doing. – John Kormylo Jul 15 '16 at 16:28
• In my actual application, this solution is the one I went with. Once you get the idea, it extends very easily to categorizing other things such as figures and tables. It's also simple to keep track of an overall maximum category value in the document. This is a really cool trick. Thank you for posting it! – Austin A. Jul 19 '16 at 0:21

Here is a solution.

\documentclass{article}
\usepackage{lipsum}
\usepackage{everypage}

\newcounter{pageCategoryCntr}

\newcommand{\category}[2]{%
\par \penalty-100\begingroup %from needspace package
\setbox0=\vbox{#2}%
\ifdim\ht0>\dimexpr\pagegoal-\pagetotal\relax
\break
\fi\endgroup
\ifnum #1 > \value{pageCategoryCntr}
\setcounter{pageCategoryCntr}{#1}%
\fi
\thepageCategoryCntr\ - #1 - #2
}

\begin{document}

\category{1}{\lipsum[2]}

\category{1}{\lipsum[3]}

\category{1}{\lipsum[2]}

\category{3}{\lipsum[3]}

\category{1}{\lipsum[2]}

\category{1}{\lipsum[3]}

\category{1}{\lipsum[4]}

\category{1}{\lipsum[5]}

\category{1}{\lipsum[2]}

\category{1}{\lipsum[3]}

\category{1}{\lipsum[2]}

\category{1}{\lipsum[3]}

\category{3}{\lipsum[2]}

\category{1}{\lipsum[3]}

\category{1}{\lipsum[4]}

\category{1}{\lipsum[5]}

\end{document}

• Yep, that works perfectly! – Austin A. Jul 15 '16 at 15:10
• Hm, there's an odd behavior: adding a \newpage does not reset the value of pageCategoryCntr. It seems that you have to manually reset the counter with every page break. – Austin A. Jul 15 '16 at 15:32
• Same issue with e.g. new chapters. – Austin A. Jul 15 '16 at 15:39
• @AustinA. answer updated. – touhami Jul 15 '16 at 16:04