The fix2col package^{∗}

David Carlisle^{†}

1997/11/11

1 Introduction

This package makes two independent changes to L^{A}TEX’s two column output routine to fix the following two longstanding ‘features’.

• If the TEX mark system is used (for example using the ‘headings’ page style in the standard L^{A}TEX classes) then any marks that originate on the first column are ‘lost’ as L^{A}TEX constructs the second column. An example document showing how this can result in incorrect page headings may be found in the latex bug database:

     http://www.uni-mainz.de/cgi-bin/ltxbugs2html?pr=latex/2613

• The second feature is documented in the L^{A}TEX book. By default L^{A}TEX does not attempt to keep double and single column floats in sequence, so if ‘Figure 1’ is a double column float produced with figure*, then it may float after ‘Figure 2’ if that is a single column, figure, float. Further correspondence about this may also be found in the bug database:

     http://www.uni-mainz.de/cgi-bin/ltxbugs2html?pr=latex/2346

2 Notes on the Implementation Strategies

2.1 Preserving Marks

The standard L^{A}TEX twocolumn system works internally by making each column a separate ‘page’ that is passed independently to TEX’s pagebreaker. (Unlike say the multicol package, where all columns are gathered together and then split into columns later, using \vsplit.) This means that the primitive TEX marks that are normally used for header information, are globally reset after the first column. By default L^{A}TEX does nothing about this. A good solution is provided by Piet van Oostrum (building on earlier work of Joe Pallas) in his fixmarks package.

After the first column box has been collected the mark information for that box is saved, so that any \firstmark can be ‘artificially’ used to set the pagelevel marks after the second column has been collected. (The second column \firstmark is not normally required.) Unfortunately TEX does not provide a direct way of knowing if any marks are in the page, \firstmark always has a value from previous pages, even if there is no mark in this page. The solution is to make a copy of the box and then \vsplit it so that any marks show up as

∗This file has version number v0.02, last revised 1997/11/11.

†Part one is essentially a copy of the fixmarks package by Piet van Oostrum, itself based on earlier work by Joe Pallas. Part two is loosely based on the fixfloats package, originally by Ed Sznyter, with some modifications by Bil Kleb.

\splitfirstmark.

The use of \vsplit does mean that the output routine will globally change the value of \splitfirstmark and \splitbotmark. The fixmarks package goes to some trouble to save and restore these values so that the output routine does not change the values. This part of fixmarks is not copied here as it is quite costly (having to be run on every page) and there is no reason why anyone writing code using \vsplit should allow the output routine to be triggered before the split marks have been accessed.

2.2 Preserving Float Order

The standard output routine maintains two lists of floats that have been ‘deferred’ for later consideration. One list for single column floats, and one for double column floats (which are always immediately put onto their deferred list). This mechanism means that L^{A}TEX ‘knows’ which type of float is contained in each box by the list that it is processing, but having two lists means that there is no mechanism for preserving the order between the floats in each list.

The solution to this problem consists of two small changes to the output routine.

Firstly, abandon the ‘double column float list’ \@dbldeferlist and change every command where it is used so that instead the same \@deferlist is used as for single column floats. That one change ensures that double and single column floats stay in the same sequence, but as L^{A}TEX no longer ‘knows’ whether a float is double or single column, it will happily insert a double float into a single column, overprinting the other column, or the margin.

The second change is to provide an alternative mechanism for recording the two column floats. L^{A}TEX already has a compact mechanism for recording float information, an integer count register assigned to each float records information about the ‘type’ of float ‘figure’, ‘table’ and the position information ‘htp’ etc.

The type information is stored in the ‘high’ bits, one bit position (above ‘32’) allocated to each float type. The ‘low’ bits store information about the allowed positions, one bit each allocated for h t b p. In the L^{A}TEX2.09 system, the bit corresponding to ‘16’ formed a ‘boundary’ between these two sets of information, and it was never actually used by the system. Ed Sznyter’s fixfloats package not unreasonably used this position to store the double column information, setting the bit for double column floats. Then at each point in the output routine at which a float is committed to a certain region, an additional check must be made to check that the float is (or is not) double column. If it spans the wrong number of columns it is deferred rather than being added.

Unfortunately the bit ‘16’ is not available in L^{A}TEX 2ε. It is used to encode the extra float position possibility ‘!’ that was added in that system. It would be possible to use position ‘32’ and to move the flags for ‘table’, ‘figure’,… up one position, to start at 64, but this would mean that in principle one less float type would be supported, and more importantly is likely to break any other packages that assume anything about the output routine internals. So here I instead use another mechanism for flagging double column floats: By default all floats have depth 0pt. This package arranges that double column ones have depth 1sp. This information may then be used in the same manner as in the fixfloats package, to defer any floats that are not of the correct column spanning type.

3 Implementation

1 ⟨∗package⟩

3.1 Preserving Marks

This is just a change to the single command \@outputdblcol so that it saves mark information for the first column and restores it in the second column.

 2 \def\@outputdblcol{%
 3 \if@firstcolumn
 4  \global\@firstcolumnfalse

Save the left column

 5  \global\setbox\@leftcolumn\copy\@outputbox

Remember the marks from the first column

 6  \splitmaxdepth\maxdimen
 7  \vbadness\maxdimen
 8  \setbox\@outputbox\vsplit\@outputbox to\maxdimen

One minor difference from the current fixmarks, pass the marks through a token register to stop any # tokens causing an error in a \def.

 9  \toks@\expandafter{\topmark}%
10  \xdef\@firstcoltopmark{\the\toks@}%
11  \toks@\expandafter{\splitfirstmark}%
12  \xdef\@firstcolfirstmark{\the\toks@}%

This test does not work if truly empty marks have been inserted, but L^{A}TEX marks should always have (at least) two brace groups. (Except before the first mark is used, when the marks are empty, but that is OK here.)

13  \ifx\@firstcolfirstmark\@empty
14  \global\let\@setmarks\relax
15  \else
16  \gdef\@setmarks{%
17  \let\firstmark\@firstcolfirstmark
18  \let\topmark\@firstcoltopmark}%
19  \fi

End of change

20 \else
21  \global\@firstcolumntrue
22  \setbox\@outputbox\vbox{%
23  \hb@xt@\textwidth{%
24  \hb@xt@\columnwidth{\box\@leftcolumn \hss}%
25  \hfil
26  \vrule \@width\columnseprule
27  \hfil
28  \hb@xt@\columnwidth{\box\@outputbox \hss}}}%
29 \@combinedblfloats

Override current first and top with those of first column if necessary

30  \@setmarks

End of change

31  \@outputpage
32  \begingroup
33  \@dblfloatplacement
34  \@startdblcolumn
35  \@whilesw\if@fcolmade \fi{\@outputpage\@startdblcolumn}%
36  \endgroup
37 \fi}

3.2 Preserving Float Order

Changes \@dbldeferlist to \@deferlist are not explicitly noted but are flagged by blank comment lines around the changed line.

38 \def\end@dblfloat{%
39 \if@twocolumn
40 \@endfloatbox
41 \ifnum\@floatpenalty <\z@
42  \@largefloatcheck

Force the depth of two column float boxes.

43  \global\dp\@currbox1sp %
44  \@cons\@deferlist\@currbox
45 \fi
46  \ifnum \@floatpenalty =-\@Mii \@Esphack\fi
47 \else
48 \end@float
49 \fi

50 }

Test if the float box has the wrong width. (Actually as noted above the test is for a conventional depth setting rather than for the width of the float).

51 \def\@testwrongwidth #1{%
52 \ifdim\dp#1=\f@depth
53 \else
54  \global\@testtrue
55 \fi}

Normally looking for single column floats, which have zero depth.

56 \let\f@depth\z@

but when making two column float area, look for floats with 1sp depth.

57 \def\@dblfloatplacement{\global\@dbltopnum\c@dbltopnumber
58 \global\@dbltoproom \dbltopfraction\@colht
59 \@textmin \@colht
60 \advance \@textmin -\@dbltoproom
61 \@fpmin \dblfloatpagefraction\textheight
62 \@fptop \@dblfptop
63 \@fpsep \@dblfpsep
64 \@fpbot \@dblfpbot
65 \def\f@depth{1sp}}

All the remaining changes are replacing the double column defer list or insering the extra test \@testwrongwidth{⟨box⟩} at suitable places. That is at plces where a box is taken off the deferlist.

66 \def \@doclearpage {%
67  \ifvoid\footins
68  \setbox\@tempboxa\vsplit\@cclv to\z@ \unvbox\@tempboxa
69  \setbox\@tempboxa\box\@cclv
70  \xdef\@deferlist{\@toplist\@botlist\@deferlist}%
71  \global \let \@toplist \@empty
72  \global \let \@botlist \@empty
73  \global \@colroom \@colht
74  \ifx \@currlist\@empty
75  \else
76  \@latexerr{Float(s) lost}\@ehb
77  \global \let \@currlist \@empty
78  \fi
79  \@makefcolumn\@deferlist
80  \@whilesw\if@fcolmade \fi{\@opcol\@makefcolumn\@deferlist}%
81  \if@twocolumn
82  \if@firstcolumn
83  \xdef\@deferlist{\@dbltoplist\@deferlist}%
84  \global \let \@dbltoplist \@empty
85  \global \@colht \textheight
86  \begingroup
87  \@dblfloatplacement
88  \@makefcolumn\@deferlist
89  \@whilesw\if@fcolmade \fi{\@outputpage
90  \@makefcolumn\@deferlist}%
91  \endgroup
92  \else
93  \vbox{}\clearpage
94  \fi
95  \fi
96  \else
97  \setbox\@cclv\vbox{\box\@cclv\vfil}%
98  \@makecol\@opcol
99  \clearpage
100  \fi

101 }

102 \def \@startdblcolumn {%
103 \@tryfcolumn \@deferlist
104 \if@fcolmade
105 \else
106  \begingroup
107  \let \reserved@b \@deferlist
108  \global \let \@deferlist \@empty
109  \let \@elt \@sdblcolelt
110  \reserved@b
111  \endgroup
112 \fi

113 }

114 \def\@addtonextcol{%
115 \begingroup
116 \@insertfalse
117 \@setfloattypecounts
118 \ifnum \@fpstype=8
119 \else
120  \ifnum \@fpstype=24
121  \else
122  \@flsettextmin
123  \@reqcolroom \ht\@currbox
124  \advance \@reqcolroom \@textmin
125  \ifdim \@colroom>\@reqcolroom
126  \@flsetnum \@colnum
127  \ifnum\@colnum>\z@
128  \@bitor\@currtype\@deferlist
129  \@testwrongwidth\@currbox
130  \if@test
131  \else
132  \@addtotoporbot
133  \fi
134  \fi
135  \fi
136  \fi
137 \fi
138 \if@insert
139 \else
140  \@cons\@deferlist\@currbox
141 \fi
142 \endgroup

143 }

144 \def\@addtodblcol{%
145 \begingroup
146 \@insertfalse
147 \@setfloattypecounts
148 \@getfpsbit \tw@
149 \ifodd\@tempcnta
150  \@flsetnum \@dbltopnum
151  \ifnum \@dbltopnum>\z@
152  \@tempswafalse
153  \ifdim \@dbltoproom>\ht\@currbox
154  \@tempswatrue
155  \else
156  \ifnum \@fpstype<\sixt@@n
157  \advance \@dbltoproom \@textmin
158  \ifdim \@dbltoproom>\ht\@currbox
159  \@tempswatrue
160  \fi
161  \advance \@dbltoproom -\@textmin
162  \fi
163  \fi
164  \if@tempswa
165  \@bitor \@currtype \@deferlist

not in fixfloats?

166  \@testwrongwidth\@currbox
167  \if@test
168  \else
169  \@tempdima -\ht\@currbox
170  \advance\@tempdima
171  -\ifx \@dbltoplist\@empty \dbltextfloatsep \else
172  \dblfloatsep \fi
173  \global \advance \@dbltoproom \@tempdima
174  \global \advance \@colht \@tempdima
175  \global \advance \@dbltopnum \m@ne
176  \@cons \@dbltoplist \@currbox
177  \@inserttrue
178  \fi
179  \fi
180  \fi
181 \fi
182 \if@insert
183 \else
184  \@cons\@deferlist\@currbox
185 \fi
186 \endgroup

187 }

188 \def \@addtocurcol {%
189 \@insertfalse
190 \@setfloattypecounts
191 \ifnum \@fpstype=8
192 \else
193  \ifnum \@fpstype=24
194  \else
195  \@flsettextmin
196  \advance \@textmin \@textfloatsheight
197  \@reqcolroom \@pageht
198  \ifdim \@textmin>\@reqcolroom
199  \@reqcolroom \@textmin
200  \fi
201  \advance \@reqcolroom \ht\@currbox
202  \ifdim \@colroom>\@reqcolroom
203  \@flsetnum \@colnum
204  \ifnum \@colnum>\z@
205  \@bitor\@currtype\@deferlist
206  \if@test
207  \else
208  \@bitor\@currtype\@botlist
209  \@testwrongwidth\@currbox
210  \if@test
211  \@addtobot
212  \else
213  \ifodd \count\@currbox
214  \advance \@reqcolroom \intextsep
215  \ifdim \@colroom>\@reqcolroom
216  \global \advance \@colnum \m@ne
217  \global \advance \@textfloatsheight \ht\@currbox
218  \global \advance \@textfloatsheight 2\intextsep

219  \@cons \@midlist \@currbox
220  \if@nobreak
221  \nobreak
222  \@nobreakfalse
223  \everypar{}%
224  \else
225  \addpenalty \interlinepenalty
226  \fi
227  \vskip \intextsep
228  \box\@currbox
229  \penalty\interlinepenalty
230  \vskip\intextsep
231  \ifnum\outputpenalty <-\@Mii \vskip -\parskip\fi
232  \outputpenalty \z@
233  \@inserttrue
234  \fi
235  \fi
236  \if@insert
237  \else
238  \@addtotoporbot
239  \fi
240  \fi
241  \fi
242  \fi
243  \fi
244  \fi
245 \fi
246 \if@insert
247 \else
248  \@resethfps
249  \@cons\@deferlist\@currbox
250 \fi

251 }

252 \def\@xtryfc #1{%
253 \@next\reserved@a\@trylist{}{}%
254 \@currtype \count #1%
255 \divide\@currtype\@xxxii
256 \multiply\@currtype\@xxxii
257 \@bitor \@currtype \@failedlist
258 \@testfp #1%
259 \@testwrongwidth #1%
260 \ifdim \ht #1>\@colht
261  \@testtrue
262 \fi
263 \if@test
264  \@cons\@failedlist #1%
265 \else
266  \@ytryfc #1%
267 \fi}
268 \def\@ztryfc #1{%
269 \@tempcnta\count #1%
270 \divide\@tempcnta\@xxxii
271 \multiply\@tempcnta\@xxxii

272 \@bitor \@tempcnta {\@failedlist \@flfail}%
273 \@testfp #1%

not in fixfloats?

274 \@testwrongwidth #1%
275 \@tempdimb\@tempdima
276 \advance\@tempdimb\ht #1%
277 \advance\@tempdimb\@fpsep
278 \ifdim \@tempdimb >\@colht
279  \@testtrue
280 \fi
281 \if@test
282  \@cons\@flfail #1%
283 \else
284  \@cons\@flsucceed #1%
285  \@tempdima\@tempdimb
286 \fi}

287 ⟨/package⟩