Setting content a length below the top; and \marginpar

I was trying to style the headings in my document such that the ascenders of differently-sized sections are a certain amount of space below the upper edge of the text area. I came up with this command to use when printing headings:

\newcommand*{\@titlemargins}{% is used inside groups so the altered \topskip doesn't leak
\setlength{\topskip}{\z@}% "first line bounding box flush with top of the text area"
\vspace*{\z@}% "first line baseline is \baselineskip below the top of the textblock"
\vspace*{-\@leadings}% "ascender of the first line is flush with top of the text area"
}
% and then do \@titlemargins \vspace*{n pt} in section definitions


For a moment, that seemed to produce desired results. Then I hit a snag when I noticed that on pages with a \marginpar the heading was too far down. It somehow seems to mess with the top of the page?

Here is a minimal example which omits all the finetuning and exhibits the unexpected \marginpar behavior:

\documentclass{report}

\begin{document}
\begingroup
\setlength{\topskip}{0pt}
\vspace*{8pt}
\endgroup
I'm supposed to be 8 points below the top.

Another paragraph.

\clearpage
\begingroup
\setlength{\topskip}{0pt}
\vspace*{8pt}
\endgroup
I'm supposed to be 8 points below the top.

Another paragraph.\marginpar{And then there's a note.}
\end{document}


Against expectations, the paragraph on the second page is lower than that on the first page.

Is there something I can do about \marginpar, or do I need to use some other kind of approach for \@titlemargins?

The explanation lies in the fact that \marginpar causes the output routine to be invoked and the page-so-far to be subsequently sent back to the page builder; the latter then inserts again the \topskip glue, which, at this time, has resumed its default value (10 points in your example). You can check that the same thing happens, for instance, on any page that contains a float, since floats invoke the output routine in a similar fashion.

These claims can be verified by asking TeX to show the lists it’s working on, as in the following test code:

\documentclass{report}

\showboxdepth = 10
% \tracingonline = 1

\begin{document}
\begingroup
\setlength{\topskip}{0pt}
\vspace*{8pt}
\endgroup

I'm supposed to be 8 points below the top.

Another paragraph.

% \showlists

\clearpage
\begingroup
\setlength{\topskip}{0pt}
\vspace*{8pt}
\endgroup
I'm supposed to be 8 points below the top.

Another paragraph.\marginpar{And then there's a note.}\showlists

\showlists

\end{document}


This is the trace produced by the first (uncommented) \showlists, taken from the transcript file:

### horizontal mode entered at line 26
\hbox(0.0+0.0)x15.0
\OT1/cmr/m/n/10 A
\OT1/cmr/m/n/10 n
\OT1/cmr/m/n/10 o
\OT1/cmr/m/n/10 t
\OT1/cmr/m/n/10 h
\OT1/cmr/m/n/10 e
\OT1/cmr/m/n/10 r
\glue 3.33333 plus 1.66666 minus 1.11111
\OT1/cmr/m/n/10 p
\OT1/cmr/m/n/10 a
\OT1/cmr/m/n/10 r
\OT1/cmr/m/n/10 a
\OT1/cmr/m/n/10 g
\OT1/cmr/m/n/10 r
\OT1/cmr/m/n/10 a
\OT1/cmr/m/n/10 p
\OT1/cmr/m/n/10 h
\OT1/cmr/m/n/10 .
.\penalty -10004
.\vbox(0.0+0.0)x0.0
.\penalty -10002
spacefactor 3000
### vertical mode entered at line 0
### current page:
\write-{}
\glue(\topskip) 0.0
\rule(0.0+0.0)x*
\penalty 10000
\glue 8.0
\glue 0.0
\glue(\parskip) 0.0 plus 1.0
\glue(\baselineskip) 5.05556
\hbox(6.94444+1.94444)x345.0, glue set 141.77744fil
.\hbox(0.0+0.0)x15.0
.\OT1/cmr/m/n/10 I
.\OT1/cmr/m/n/10 '
.\OT1/cmr/m/n/10 m
.\glue 3.33333 plus 1.66666 minus 1.11111
.\OT1/cmr/m/n/10 s
.\OT1/cmr/m/n/10 u
.\OT1/cmr/m/n/10 p
.\OT1/cmr/m/n/10 p
.\kern0.27779
.\OT1/cmr/m/n/10 o
.\OT1/cmr/m/n/10 s
.\OT1/cmr/m/n/10 e
.\OT1/cmr/m/n/10 d
.\glue 3.33333 plus 1.66666 minus 1.11111
.\OT1/cmr/m/n/10 t
.\OT1/cmr/m/n/10 o
.\glue 3.33333 plus 1.66666 minus 1.11111
.\OT1/cmr/m/n/10 b
.\kern0.27779
.\OT1/cmr/m/n/10 e
.\glue 3.33333 plus 1.66666 minus 1.11111
.\OT1/cmr/m/n/10 8
.\glue 3.33333 plus 1.66666 minus 1.11111
.\OT1/cmr/m/n/10 p
.\kern0.27779
.\OT1/cmr/m/n/10 o
.\OT1/cmr/m/n/10 i
.\OT1/cmr/m/n/10 n
.\kern-0.27779
.\OT1/cmr/m/n/10 t
.\OT1/cmr/m/n/10 s
.\glue 3.33333 plus 1.66666 minus 1.11111
.\OT1/cmr/m/n/10 b
.\kern0.27779
.\OT1/cmr/m/n/10 e
.\OT1/cmr/m/n/10 l
.\OT1/cmr/m/n/10 o
.\kern-0.27779
.\OT1/cmr/m/n/10 w
.\glue 3.33333 plus 1.66666 minus 1.11111
.\OT1/cmr/m/n/10 t
.\OT1/cmr/m/n/10 h
.\OT1/cmr/m/n/10 e
.\glue 3.33333 plus 1.66666 minus 1.11111
.\OT1/cmr/m/n/10 t
.\OT1/cmr/m/n/10 o
.\OT1/cmr/m/n/10 p
.\OT1/cmr/m/n/10 .
.\penalty 10000
.\glue(\parfillskip) 0.0 plus 1.0fil
.\glue(\rightskip) 0.0
\glue(\parskip) 0.0 plus 1.0
total height 21.94444 plus 2.0
goal height 550.0
prevdepth 1.94444


You can see, in the section that begins with

### vertical mode entered at line 0
### current page:


that the \topskip glue that had originally been inserted, before the call to the output routine triggered by \marginpar, was indeed

\glue(\topskip) 0.0


By the way, in the section headed

### horizontal mode entered at line 26


you can see how the \marginpar commands triggers that call: it inserts

\vadjust
.\penalty -10004
.\vbox(0.0+0.0)x0.0
.\penalty -10002


in the horizontal list of the paragraph being formed. When the paragraph terminates, it is broken into lines, and the contents of \vadjust are inserted in the main vertical list under the line on which the \marginpar winds up: this will call the output routine (actually, twice) as the two \penalty nodes are subsequently moved from the “recent contributions” to the “current page”.

On the contrary, the result of the second (uncommented) \showlists is the following:

### vertical mode entered at line 0
### current page:
\write-{}
\glue(\topskip) 10.0
\rule(0.0+0.0)x*
\penalty 10000
\glue 8.0
\glue 0.0
\glue(\parskip) 0.0 plus 1.0
\glue(\baselineskip) 5.05556
\hbox(6.94444+1.94444)x345.0, glue set 141.77744fil
.\hbox(0.0+0.0)x15.0
.\OT1/cmr/m/n/10 I
.\OT1/cmr/m/n/10 '
.\OT1/cmr/m/n/10 m
.\glue 3.33333 plus 1.66666 minus 1.11111
.\OT1/cmr/m/n/10 s
.\OT1/cmr/m/n/10 u
.\OT1/cmr/m/n/10 p
.\OT1/cmr/m/n/10 p
.\kern0.27779
.\OT1/cmr/m/n/10 o
.\OT1/cmr/m/n/10 s
.\OT1/cmr/m/n/10 e
.\OT1/cmr/m/n/10 d
.\glue 3.33333 plus 1.66666 minus 1.11111
.\OT1/cmr/m/n/10 t
.\OT1/cmr/m/n/10 o
.\glue 3.33333 plus 1.66666 minus 1.11111
.\OT1/cmr/m/n/10 b
.\kern0.27779
.\OT1/cmr/m/n/10 e
.\glue 3.33333 plus 1.66666 minus 1.11111
.\OT1/cmr/m/n/10 8
.\glue 3.33333 plus 1.66666 minus 1.11111
.\OT1/cmr/m/n/10 p
.\kern0.27779
.\OT1/cmr/m/n/10 o
.\OT1/cmr/m/n/10 i
.\OT1/cmr/m/n/10 n
.\kern-0.27779
.\OT1/cmr/m/n/10 t
.\OT1/cmr/m/n/10 s
.\glue 3.33333 plus 1.66666 minus 1.11111
.\OT1/cmr/m/n/10 b
.\kern0.27779
.\OT1/cmr/m/n/10 e
.\OT1/cmr/m/n/10 l
.\OT1/cmr/m/n/10 o
.\kern-0.27779
.\OT1/cmr/m/n/10 w
.\glue 3.33333 plus 1.66666 minus 1.11111
.\OT1/cmr/m/n/10 t
.\OT1/cmr/m/n/10 h
.\OT1/cmr/m/n/10 e
.\glue 3.33333 plus 1.66666 minus 1.11111
.\OT1/cmr/m/n/10 t
.\OT1/cmr/m/n/10 o
.\OT1/cmr/m/n/10 p
.\OT1/cmr/m/n/10 .
.\penalty 10000
.\glue(\parfillskip) 0.0 plus 1.0fil
.\glue(\rightskip) 0.0
\glue(\parskip) 0.0 plus 1.0
\glue(\baselineskip) 3.11111
\hbox(6.94444+1.94444)x345.0, glue set 243.52757fil
.\hbox(0.0+0.0)x15.0
.\OT1/cmr/m/n/10 A
.\OT1/cmr/m/n/10 n
.\OT1/cmr/m/n/10 o
.\OT1/cmr/m/n/10 t
.\OT1/cmr/m/n/10 h
.\OT1/cmr/m/n/10 e
.\OT1/cmr/m/n/10 r
.\glue 3.33333 plus 1.66666 minus 1.11111
.\OT1/cmr/m/n/10 p
.\OT1/cmr/m/n/10 a
.\OT1/cmr/m/n/10 r
.\OT1/cmr/m/n/10 a
.\OT1/cmr/m/n/10 g
.\OT1/cmr/m/n/10 r
.\OT1/cmr/m/n/10 a
.\OT1/cmr/m/n/10 p
.\OT1/cmr/m/n/10 h
.\OT1/cmr/m/n/10 .
.\penalty 10000
.\glue(\parfillskip) 0.0 plus 1.0fil
.\glue(\rightskip) 0.0
\kern -1.94444
\hbox(0.0+0.0)x345.0, glue set - 76.0fil
.\glue 345.0
.\glue 11.0
.\vbox(0.0+0.0)x65.0
..\glue -6.94444
..\vbox(6.94444+12.0)x65.0
...\hbox(6.94444+0.0)x65.0, glue set 14.1667
....\hbox(0.0+0.0)x0.0
....\OT1/cmr/m/n/10 A
....\OT1/cmr/m/n/10 n
....\OT1/cmr/m/n/10 d
....\glue 3.33333 plus 1.66666 minus 1.11111
....\OT1/cmr/m/n/10 t
....\OT1/cmr/m/n/10 h
....\OT1/cmr/m/n/10 e
....\OT1/cmr/m/n/10 n
....\glue(\rightskip) 0.0
...\penalty 300
...\glue(\baselineskip) 5.05556
...\hbox(6.94444+0.0)x65.0, glue set 2.69435fil
....\OT1/cmr/m/n/10 t
....\OT1/cmr/m/n/10 h
....\OT1/cmr/m/n/10 e
....\OT1/cmr/m/n/10 r
....\OT1/cmr/m/n/10 e
....\OT1/cmr/m/n/10 '
....\OT1/cmr/m/n/10 s
....\glue 3.33333 plus 1.66666 minus 1.11111
....\OT1/cmr/m/n/10 a
....\glue 3.33333 plus 1.66666 minus 1.11111
....\OT1/cmr/m/n/10 n
....\OT1/cmr/m/n/10 o
....\OT1/cmr/m/n/10 t
....\OT1/cmr/m/n/10 e
....\OT1/cmr/m/n/10 .
....\penalty 10000
....\glue(\parfillskip) 0.0 plus 1.0fil
....\glue(\rightskip) 0.0
.\glue 0.0 plus 1.0fil minus 1.0fil
\hbox(0.0+1.94444)x0.0
.\rule(0.0+1.94444)x0.0
\penalty 0
\penalty 10000
total height 42.0 plus 2.0
goal height 550.0
prevdepth 1.94444, prevgraf 1 line


As you can see, the \topskip glue has been re-inserted, with its default value of 10.0 points, for the reason explained formerly.

It should be noted that the above transcripts also show that the normal interline glue is inserted between the \vspace* command and the “I’m supposed to be…” boxes: this means that neither of them has its upper edge running at 8 points from the top of the textblock. You should precede them with \nointerlineskip.

• Wow, thanks for the really in-depth answer. I did previously encounter problems where ending a paragraph just before ending a group led to the paragraph skip outside the group affecting the \par I thought I was putting inside the group; this seems to be a lot more complicated case of the same phenomenon. – timo_k Jan 24 '18 at 18:25
• @timo_k: No, it is a completely different affair: here, as said, the output routine is involved, and the fact that the main vertical list is sent back and forth between the output routine and the page builder. – GuM Jan 26 '18 at 23:56
• I meant conceptually, in the sense of scopes. In both cases, I thought that the action in question (producing a paragraph skip, or building the top of the page) is “done inside the group”, when in fact the value outside the group is active when performing the action. – timo_k Jan 28 '18 at 19:27

Seems that doing \vspace*{-\topskip} instead of \setlength{\topskip}{0pt}\vspace*{0} fixes the problem on pages with \marginpar while producing exactly the same output everywhere else. I would still like to hear what it is about \marginpar that causes this behavior.

• Now it’s too late in the night for me to check the details (and that’s why I’m writing a comment and not an answer), but I’d say that the explanation lies in the fact that \marginpar causes the output routine to be invoked and the page-so-far to be subsequently sent back to the page buider; the page builder then inserts again the \topskip glue, which at this time, is no longer null. Note, however, that neither your first “I’m supposed to be…” box has its upper edge at 8pt from the top of the textblock: the normal interline glue is inserted under \vspace*. – GuM Jan 20 '18 at 1:42