diff options
author | Karl Berry <karl@freefriends.org> | 2013-09-08 22:07:13 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2013-09-08 22:07:13 +0000 |
commit | be662476eb6af4d2ac4265ffdd2c26f9ec0d48ce (patch) | |
tree | b949cd695e0f86d22139a900f34c3f004099fafe /Master/texmf-dist/source/latex/marginfix/marginfix.dtx | |
parent | 40706b96168d4f2b191483c54e851e4c46f8de5a (diff) |
marginfix (8sep13)
git-svn-id: svn://tug.org/texlive/trunk@31598 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/source/latex/marginfix/marginfix.dtx')
-rw-r--r-- | Master/texmf-dist/source/latex/marginfix/marginfix.dtx | 1194 |
1 files changed, 929 insertions, 265 deletions
diff --git a/Master/texmf-dist/source/latex/marginfix/marginfix.dtx b/Master/texmf-dist/source/latex/marginfix/marginfix.dtx index b0686483fd7..1e7b43ea06b 100644 --- a/Master/texmf-dist/source/latex/marginfix/marginfix.dtx +++ b/Master/texmf-dist/source/latex/marginfix/marginfix.dtx @@ -12,10 +12,10 @@ % %<package>\NeedsTeXFormat{LaTeX2e} %<package>\ProvidesPackage{marginfix}% - [2010/08/28 v0.9.1 Fix Margin Paragraphs] + [2013/09/08 v1.1 Fix Margin Paragraphs] %<*driver> \documentclass{ltxdoc} -\CheckSum{546} +\CheckSum{1197} %\OnlyDescription % (un)comment this line to show (hide) source code \RecordChanges \EnableCrossrefs @@ -53,6 +53,23 @@ % {(SDH) Initial CTAN version.} % \changes{v0.9.1}{2010/08/28} % {(SDH) Fix bug where we alternated sides in article.} +% \changes{v1.0}{2013/09/01} +% {(Dario Buttari) Alternate order of \cs{marginpar} arguments to be +% consistent with the original macro.} +% \changes{v1.0}{2013/09/01} +% {(Dario Buttari) Fix bug in deferred note spacing.} +% \changes{v1.0}{2013/09/01} +% {(Dario Buttari) Fix issues with \cs{marginheightadjustment} and +% \cs{extendmargin} not being applied and reset consistently.} +% \changes{v1.0}{2013/09/01} +% {(SDH) Stop gobbling vertical stretch on page. Margin notes will +% not line up properly if page is stretched.} +% \changes{v1.0}{2013/09/01} +% {(SDH) Add margin phantoms.} +% \changes{v1.1}{2013/09/08} +% {(SDH) Globally calculate margin phantoms over 4 passes.} +% \changes{v1.1}{2013/09/08} +% {(SDH) Add \cs{topskip} to notes at the top of the margin.} % % \GetFileInfo{marginfix.dtx} % \title{\Lopt{marginfix} package documentation} @@ -72,14 +89,16 @@ % unsatisfying for short textual notes, and standard marginpars cannot % be mixed with sidebars. This package implements a solution to % make marginpars "just work" by keeping a list of floating inserts and -% arranging them intelligently in the output routine. +% arranging them intelligently in the output routine. The credit for the +% concept behind this algorithm goes to Prof. Andy Ruina, who employed me +% to work on some of his textbook macros in 2007--9. % % \section{Options} % There are currently no options that do anything yet. % % \section{Commands} -% For the most part, this is a drop-in replacement. Simply add -% |\usepackage{marginfix}| to the preamble, use \cs{marginpar} +% For the most part, this is a drop-in replacement. Simply include +% a call to |\usepackage{marginfix}| to the preamble, use \cs{marginpar} % normally and hope for the best. % In the event, however, that it doesn't work exactly as hoped, % there are a number of tweaks that the user can apply. @@ -108,9 +127,9 @@ % % \DescribeMacro{\mparshift} % To adjust the position of a single note, use \cs{mparshift}\marg{length} -% before \cs{marginpar}. Positive lengths move it down the page. This -% essentially shifts the call-out location, so the actual position of the -% note might not change if the margin is sufficiently crowded. +% before a call to \cs{marginpar}. Positive lengths move it down the page. +% This essentially shifts the call-out location, so the actual position of +% the note might not change if the margin is sufficiently crowded. % Multiple calls before the same note are cumulative. % % \DescribeMacro{\marginheightadjustment} @@ -122,15 +141,38 @@ % \cs{extendmargin} on every page. % % \DescribeMacro{\marginposadjustment} -% Similarly, if all the margins are in the wrong place, the callout -% positions can be adjusted by assigning a non-zero value to the dimension -% register \cs{marginposadjustment}. This is effectively the same as a call -% to \cs{mparshift} before every note. This is particularly useful at -% present because the height of the line on which the margin note is called +% Similarly, if all the margin notes are in the wrong place, the callout +% positions can be adjusted globally by assigning a non-zero value to the +% dimension register \cs{marginposadjustment}. This is effectively the same +% as a call to \cs{mparshift} before every note. This is particularly useful +% at present because the height of the line on which the margin note is called % is currently only estimated, and appears to be off by a point or two. % This may get fixed in the future, but until then, the adjustment is % possibly the easiest workaround. % +% \DescribeMacro{\blockmargin}\DescribeMacro{\unblockmargin} +% As of version 1.0, we now support ``margin phantoms'': sections of the +% margin in which no notes will be placed, which can be useful for large +% figures that jut into the margin (note: margins already move out of the +% way of floats, regardless of whether or not they extend into the margin; +% this is mainly for in-place figures or equations). The easiest way to +% block off part of the margin is to call \cs{blockmargin} before the +% extended content and \cs{unblockmargin} afterwards. No margin notes +% will be placed between these two points (though one must be careful: +% if one of these is called in horizontal mode, the toggle will occur at +% the \emph{top} of the current line). Each of these commands takes an +% optional argument: \cs{blockmargin}\oarg{pos} will begin the margin +% block at a position \emph{pos} below the current position (or above +% if \emph{pos} is negative), and \cs{unblockmargin}\oarg{pos} will +% likewise end the block at a position \emph{pos} below the current +% position. +% +% \DescribeMacro{\marginphantom} +% Margin phantoms may also be called out in place with a known size +% using \cs{marginphantom}\oarg{pos}\marg{size}, which is essentially +% equivalent to \cs{blockmargin}\oarg{pos}\cs{unblockmargin}\oarg{pos$+$size}. +% Either argument may be negative to refer upward rather than downward. +% % \section{Interaction with other packages} % \subsection{memoir} % There are no known issues with \Lopt{memoir} at present, provided that @@ -151,10 +193,14 @@ % Here is a list of things to possibly look forward to in a future version. % If any of them are particularly important, please let me know. % \begin{itemize} -% \item Margin phantoms. % \item Use of pdf\TeX's \cs{pdfsavepos} and \cs{pdflastypos} for more % accurate margin placement. % \item \cs{vadjust} to correct inconsistencies with \cs{@pageht}. +% \item Margin note placement is irrespective of vertical stretch. +% Previously we gobbled any vertical stretch, but now that we have +% fixed that bug, there's the possibility of wrong alignment since +% we don't know where the positions will ultimately end up. This +% may be fixed by \cs{pdfsavepos} as well. % \item Better interaction with floats. % (We can set a default one way % or the other and then allow a macro to override it (presumably with a @@ -170,18 +216,11 @@ % \makeatletter % \part*{Implementation} % \section{Initial Setup} -% \begin{macros}{\@ight,\f@ur,\@C} -% Make the |@|-sign into a letter for use in macro names. We also -% define a few other (hopefully) obvious and unambiguous macros that -% other packages won't destructively clobber. +% Make the |@|-sign into a letter for use in macro names. % \begin{macrocode} %<*package> \makeatletter -\chardef\@ight=8 -\chardef\f@ur=4 -\chardef\@c=100 % \end{macrocode} -% \end{macros} % % \begin{macros}{\MFX@debug} % We have some optionally-included code for debugging. \cs{MFX@debug} @@ -189,15 +228,22 @@ % The newline can be suppressed with a |*|. We'll also ask for more % error context in the debug mode. % \begin{macrocode} -%<debug>\def\MFX@debug{\@ifstar\message{\message{^^JMFX:}\message}} -%<debug>\errorcontextlines=20 +%<*debug> +\def\MFX@debug{\message{^^JMFX:}\message} +\errorcontextlines=20 +\def\MFX@mac#1{\expandafter\MFX@@mac\meaning#1>>>} +\def\MFX@@mac#1->{<<<} +\def\MFX@htdp#1{\ht#1=\the\ht#1, \dp#1=\the\dp#1} +\showboxbreadth=100 +\showboxdepth=10 +%</debug> % \end{macrocode} % \end{macros} % % The reader might begin to note at this point a convention we % adopt throughout this package. While we strive to avoid introducing -% new names as much as possible (using the \pkg{inlinedef} package -% whenever we can), any new names we do introduce will be prefixed +% new names as much as possible (with clever usages of \cs{expandafter}), +% any new names we do introduce will be prefixed % by |\MFX@|, |\Mfx@|, or |\mfx@|, depending on the type of name. % The all-capitol |\MFX@| is used for fully-constant macros. The % initial-caps |\Mfx@| is used for control sequences that are @@ -208,6 +254,7 @@ % % \section{Options} % Here we define the various package options. +% \iffalse - CURRENTLY UNIMPLEMENTED % \begin{macros}{\ifmfx@ypos} % The \Lopt{ypos} option signifies that we should use the pdf{\TeX} % primitives \cs{pdfsavepos} and \cs{pdflastypos} to improve positioning @@ -219,6 +266,7 @@ \DeclareOption{ypos}{\mfx@ypostrue} % \end{macrocode} % \end{macros} +% END IFFALSE - \else There are no options yet. \fi % % Now we actually process the options. % \begin{macrocode} @@ -235,6 +283,21 @@ % \end{macrocode} % \end{macros} % +% \begin{macros}{\mfx@inject,\Mfx@inject@insert} +% These are used to hijack \cs{marginpar} to inject arbitrary code +% into the output routine, rather than actually set a note. To +% inject code, we unshift two copies of this dummy insert onto +% \cs{@freelist} for \cs{marginpar} to pull off. We append +% whatever code we want to inject into \cs{mfx@inject} and then +% call \cs{marginpar}. Then our custom \cs{@addmarginpar} will +% recognize the dummy inserts and run the code instead of setting +% a margin note. +% \begin{macrocode} +\let\mfx@injected\@empty +\newinsert\Mfx@inject@insert +% \end{macrocode} +% \end{macros} +% % \begin{macros}{\Mfx@marginbox} % While we're building the margin, we need to put it in a box % before we can attach it to the main columm. @@ -243,24 +306,52 @@ % \end{macrocode} % \end{macros} % -% \begin{macros}{\Mfx@marginboxspace,\Mfx@marginpos} -% While we build up the margin box, we need to keep track of both -% where we are in the margin (\cs{Mfx@marginpos}) and how -% much of that space is incompressible (\cs{Mfx@marginboxspace}). +% \begin{macros}{\Mfx@marginpos@min,\Mfx@marginpos@max,\Mfx@marginspace} +% While we build up the margin piece boxes, we need to keep track of the +% possible range of positions. The pair \cs{Mfx@marginpos@min} and +% \cs{Mfx@marginpos@max} are used to accumulate how much material has +% been added so far, with the difference that \cs{Mfx@marginpos@min} doesn't +% take into account compressible space, while \cs{Mfx@marginpos@max} does. +% Finally, \cs{Mfx@marginspace} is the amount of (incompressible) space +% since the last note, which allows skips to span margin phantoms. % \begin{macrocode} -\newdimen\Mfx@marginboxspace -\newdimen\Mfx@marginpos +\newdimen\Mfx@marginpos@min +\newdimen\Mfx@marginpos@max +\newdimen\Mfx@marginspace % \end{macrocode} % \end{macros} % % \begin{macros}{\Mfx@marginheight} % Because the margin height can be altered by, \cs{extendmargin}, % we must maintain a dimension for the height of the current margin. +% This dimension is reused in several different ways in the shipout-time +% margin building routines, keeping track of how much much space is left +% in (for the global passes) and the end position of the end of (for the +% piecewise passes) the current piece. % \begin{macrocode} \newdimen\Mfx@marginheight % \end{macrocode} % \end{macros} % +% \begin{macros}{\mfx@marginstart,\mfx@marginpieces, +% \Mfx@piece@content,\Mfx@piece@count,\ifmfx@in@phantom} +% These control sequences keep track of the margin phantoms. When the +% margin is unblocked then \cs{mfx@marginstart} is not \cs{relax}. When +% a phantom begins, the current \cs{mfx@marginstart} and page position +% are stored as a pair in \cs{mfx@marginpieces}, which is iterated over +% while building individual pieces of the margin. We also define a box +% to keep the content of each margin piece, and a counter to keep track +% of how many pieces we have. Finally, we define a switch for use in +% the second pass to indicate that we're inside a phantom. +% \begin{macrocode} +\def\mfx@marginstart{0pt} +\let\mfx@marginpieces\@empty +\newbox\Mfx@piece@content +\newcount\Mfx@piece@count +\newif\ifmfx@in@phantom +% \end{macrocode} +% \end{macros} +% % \begin{macros}{\Mfx@mparshift} % We store the current shift in a dimension register. % \begin{macrocode} @@ -372,7 +463,7 @@ % \@combinefloats % add \@texttop and \@textbottom to \@outputbox (default no-op) % \@opcol: -% \@outputpage (of \@outputdblcol in twocolumn mode) +% \@outputpage (or \@outputdblcol in twocolumn mode) % let \@mparbottom := \@textfloatsheight := 0 % \@floatplacement % \@startcolumn: @@ -398,22 +489,29 @@ % output routine so that we can get ahold of the current page % position. Even if we have a better idea of the page position % (e.g. from pdf\TeX), we still might as well do this in the OR. +% In addition to actually setting the margin note, we also use this +% routine to inject arbitrary code into the OR (see \cs{MFX@inject}). % \begin{macrocode} \def\@addmarginpar{% - \@next\@marbox\@currlist{}\MFX@bug -%<debug>\MFX@debug{addmarginpar (running insert) \@marbox/\@currbox at -%<debug> \the\c@page:\the\@pageht, marginlist=\meaning\mfx@marginlist}% + \@next\@marbox\@currlist{}\MFX@AssertionError +%<debug>\MFX@debug{addmarginpar (running insert) \@marbox/ \@currbox at +%<debug> \the\c@page:\the\@pageht, marginlist=\MFX@mac\mfx@marginlist}% +%<debug>\MFX@debug{addmarginpar outputpenalty=\the\outputpenalty}% \MFX@getypos - \MFX@cons\mfx@marginlist{% TODO: later this will be a run@marginlist - \noexpand\mfx@margin@note\@marbox\@currbox{\mfx@ypos}% (^i.e. for phantoms) - \noexpand\mfx@margin@skip{\the\marginparpush}% - }% -%<debug>\MFX@debug{addmarginpar (exit): marginlist=\meaning\mfx@marginlist}% + \expandafter\ifx\@marbox\Mfx@inject@insert + \mfx@injected\global\let\mfx@injected\@empty + \else + \MFX@cons\mfx@marginlist{% + \noexpand\mfx@build@note\@currbox\@marbox{\mfx@ypos}% + \noexpand\mfx@build@skip{\the\marginparpush}% + }% + \fi +%<debug>\MFX@debug{addmarginpar (exit): marginlist=\MFX@mac\mfx@marginlist}% } % \end{macrocode} % \end{macros} % -% \begin{macros}{\MFX@cons,\MFX@snoc} +% \begin{macros}{\MFX@cons,\MFX@snoc,\MFX@run@clear} % In passing we'll define the cons macro, which fully-expands % its second argument, but makes sure to only expand the first % one once, so that any fragile control sequences in it are @@ -435,11 +533,35 @@ \expandafter\expandafter\expandafter{\expandafter\temp@#1}% } % \end{macrocode} +% Finally, \cs{MFX@run@clear} is a quick trick to expand the +% contents of a macro and then clear it (to \cs{@empty}) before +% any of its tokens are consumed. +% \begin{macrocode} +\def\MFX@run@clear#1{% + \expandafter\global\expandafter\let\expandafter#1\expandafter\@empty#1% +} +% \end{macrocode} +% \end{macros} +% +% \begin{macros}{\MFX@inject} +% As mentioned earlier, \cs{@addmarginpar} is also a hook for injecting +% arbitrary code into the output routine, i.e. to get a vertical position +% for blocking the margin. We define \cs{MFX@inject} to facilitate this. +% \begin{macrocode} +\def\MFX@inject#1{ + \expandafter\def\expandafter\@freelist\expandafter{% + \expandafter\@elt\expandafter\Mfx@inject@insert + \expandafter\@elt\expandafter\Mfx@inject@insert + \@freelist}% + \expandafter\def\expandafter\mfx@injected\expandafter{\mfx@injected#1}% + \marginpar{}% +} +% \end{macrocode} % \end{macros} % % \begin{macros}{\MFX@getypos,\mfx@ypos} % We now need to settle on a way to determine the vertical position. -% Ultimately this will be an option, and will depend on a variety +% Someday this may be an option, and will depend on a variety % of factors. But for starters, we define the simplest version. % Note the subtraction of \cs{Mfx@strutheight}. Ideally we would simply % grab a copy of \cs{@holdpg} from the middle of \cs{@specialoutput} @@ -450,14 +572,16 @@ % within \cs{@specialoutput}, and it's not worth the risky job of % performing surgery on it (which is unfortunately brittle if anyone % else has a similar idea), we instead resort to this approximation. -% And since this will ultimately be only a fallback for when -% \cs{pdflastypos} isn't available, it should be good enough. -% NOTE: we might be able to use a \cs{vadjust} instead here? +% And since this should ultimately be only a fallback for when +% \cs{pdflastypos} isn't available, it's good enough. +% (NOTE: we might be able to use a \cs{vadjust} instead here?) % \begin{macrocode} \def\MFX@getypos{% - \edef\mfx@ypos{% - \the\dimexpr\@pageht - \Mfx@strutheight - + \marginposadjustment + \Mfx@mparshift\relax}% + \dimen@\dimexpr\@pageht+\@pagedp+\marginposadjustment+\Mfx@mparshift\relax + \ifnum\outputpenalty=-10002\relax + \advance\dimen@-\Mfx@strutheight + \fi + \edef\mfx@ypos{\the\dimen@}% \global\Mfx@mparshift\z@ } % \end{macrocode} @@ -490,14 +614,17 @@ % % \cs{MFX@combinefloats@before} is then responsible for picking the % needed notes from \cs{mfx@marginlist}, building them into a box, and -% attaching said box onto the correct side of \cs{@outputbox}. This is -% also a convenient place to reset \cs{Mfx@marginheight} to zero (since -% we reuse the register for one-time extensions). +% attaching that box onto the correct side of \cs{@outputbox}. We also +% add any global \cs{marginheightadjustment} to \cs{Mfx@marginheight} +% before building the margin, and then reset it back to zero at the end. +% This allows any calls to \cs{extendmargin} during the page itself to +% work as expected. % \begin{macrocode} \def\MFX@combinefloats@before{% + \advance\Mfx@marginheight\marginheightadjustment \MFX@buildmargin \MFX@attachmargin - \Mfx@marginheight\marginheightadjustment + \global\Mfx@marginheight\z@ } % \end{macrocode} % \end{macros} @@ -512,218 +639,312 @@ % First, we need to make sure that the boxes we're combining are the % same size. % \begin{macrocode} - \ifdim\ht\@outputbox<\ht\Mfx@marginbox - \setbox\@outputbox\vbox to \ht\Mfx@marginbox{% - \box\@outputbox - \vfill - }% - \else - \setbox\Mfx@marginbox\vbox to \ht\@outputbox{% - \unvbox\Mfx@marginbox - \vfill - }% - \fi +%<debug>\MFX@debug{attachmargin: \MFX@htdp\@outputbox, \MFX@htdp\Mfx@marginbox}% + %<debug>\showbox\@outputbox + %<debug>\showbox\Mfx@marginbox + \setbox\Mfx@marginbox\vtop{% + \vskip\z@\unvbox\Mfx@marginbox}% + %% \ifdim\ht\@outputbox<\ht\Mfx@marginbox + %% \setbox\Mfx@marginbox\vbox to \ht\@outputbox{% + %% \unvbox\Mfx@marginbox + %% \vskip\z@ shrink 1filll\relax + %% }% + %% \else + %% \setbox\Mfx@marginbox\vbox to \ht\@outputbox{% + %% \unvbox\Mfx@marginbox + %% \vfill + %% }% + %% \fi % \end{macrocode} % Next we need to figure out which side of \cs{@outputbox} to attach -% the \cs{Mfx@marginbox} on. -% \begin{macrocode} - \setbox\@outputbox\vbox to \ht\@outputbox{% - \hbox to \wd\@outputbox{% - \if\MFX@leftmargin - \llap{\box\Mfx@marginbox\hskip\marginparsep}% - \box\@outputbox - \else - \box\@outputbox - \rlap{\hskip\marginparsep\box\Mfx@marginbox}% - \fi - }}% +% the \cs{Mfx@marginbox} on. We now use \cs{columnwidth} instead of +% \cs{wd}\cs{@outputbox} to set the right-hand margins, since tufte-\LaTeX +% sometimes makes too-wide output boxes. If this becomes a problem, +% we'll need to consider making this configurable elsewhere. We should +% also pay attention to whether adding a box at the top of \cs{@outputbox} +% might have unintended consequences w.r.t. any glue being retained that +% should have been swallowed. This will require further investigation. +% \begin{macrocode} + \setbox\@outputbox\vbox{% to \ht\@outputbox{% + \begingroup + \setbox\@tempboxa\vbox{% to \ht\@outputbox{% + \hbox{% to \wd\@outputbox{% + \if\MFX@leftmargin + \llap{\box\Mfx@marginbox\hskip\marginparsep}% + \else + \hskip\columnwidth + \rlap{\hskip\marginparsep\box\Mfx@marginbox}% + \fi + }}% + \ht\@tempboxa\z@ + \dp\@tempboxa\z@ + \box\@tempboxa + \endgroup + \unskip + \unvbox\@outputbox + }% + %<debug>\showbox\@outputbox } % \end{macrocode} % \end{macros} % % \begin{macros}{\MFX@buildmargin} % When \cs{MFX@buildmargin} is called, we have a list of tokens in -% \cs{mfx@marginlist} that need to be processed. After it's done -% working, \cs{mfx@marginlist} should have the first $n>0$ of these -% removed, and leaving only notes that were deferred to the next -% page. Additionally, \cs{Mfx@marginbox} must contain a -% box the same height as \cs{@outputbox}. We do this in several -% steps. We start with a height of \cs{@colroom} rather than -% \cs{textheight} because \cs{@colroom} has already subtracted -% off the top/bottom floats that have been set. Eventually we -% will want to encroach on these floats, particularly if they -% don't encroach on the margin, but that will be tricky... +% \cs{mfx@marginlist} that need to be processed: combinations of +% \cs{mfx@build@note}, \cs{mfx@build@skip}, and \cs{mfx@build@clear}, +% with various parameters to indicate what material still needs to be +% set in the margin. This macro therefore must pull off the first $n>0$ +% of these commands to set on the current page ($n$ must be positive to +% prevent infinite loops), and leave the rest to be deferred. We do not +% currently support taking notes out of order, though that is a possible +% feature to allow in the future, on an opt-in basis. The typeset +% material will be left in \cs{Mfx@marginbox}, which must have the same +% height as \cs{@outputbox} (although because we \cs{unvbox}\cs{@outputbox} +% in \cs{MFX@attachmargin}, we can't guarantee that this will correctly +% line up the notes with their callouts). This procedure happens in +% four passes. But first, we initialize \cs{Mfx@marginheight} to +% \cs{@colroom}, which is the height of the page minus any floats that +% have been added to the top of bottom (these floats may extend into the +% margins: in the future we may look into detecting this and using the +% whole page, with overwide floats blocked off as phantoms). We add +% \cs{@colroom} rather than assigning it because any global or per-page +% adjustments have already been added to \cs{Mfx@marginheight}. We +% can then close out any still-open margin pieces (this is the typical +% case, where the margin is not blocked across a page boundary, so that +% \cs{mfx@marginstart} will hold a position, rather than \cs{relax}). +% After this, \cs{Mfx@marginheight} is no longer necessary, so we reuse +% it for keeping track of available space in individual margin pieces. % \begin{macrocode} \def\MFX@buildmargin{% \advance\Mfx@marginheight\@colroom -%<debug>\MFX@debug{buildmargin: marginheight=\the\Mfx@marginheight}% - \MFX@buildmargin@down - \MFX@buildmargin@up + \ifx\mfx@marginstart\relax + \else + \MFX@cons\mfx@marginpieces{% + \noexpand\@elt{\mfx@marginstart}{\the\Mfx@marginheight}}% + \gdef\mfx@marginstart{0pt}% + \global\advance\Mfx@piece@count\@ne + \fi +%<debug>\MFX@debug{buildmargin: marginheight=\the\Mfx@marginheight, +%<debug> marginlist=\MFX@mac\mfx@marginlist, +%<debug> marginpieces=\MFX@mac\mfx@marginpieces}% +% \end{macrocode} +% We now execute the four passes. First is a global downward pass, +% whose purpose is to determine the maximum number of notes (and other +% material) that can fit in the margin, taking any phantoms into +% consideration. Every note identified by \cs{MFX@buildmargin@down} +% is guaranteed to show up on this page, so we free its inserts back +% to \cs{@freelist}. The second pass is the global upward pass, in +% which we determine the lowest possible margin piece each note may +% go into without causing lower notes to fall off the bottom. The +% third pass is the piecewise downward pass. For each piece, we +% figure out where in the piece each note will go by inserting +% compressible spaces between the notes. If a note is called out +% past the end of the piece and does not need to go into the piece +% (as determined by pass 2), it will be deferred to a later piece. +% The fourth pass is the piecewise upward pass, in which the compressible +% spaces are shrunk just enough to fit everything into the piece. The +% last two (piecewise) passes both occur in each piece before the next +% piece is addressed. The whole process is bypassed if there are +% no eligible margin pieces. +% \begin{macrocode} + \ifx\mfx@marginpieces\@empty\else + \MFX@buildmargin@down + \MFX@buildmargin@up + \MFX@buildmargin@pieces + \fi } % \end{macrocode} % \end{macros} % -% \subsection{First pass} -% \begin{macros}{\MFX@buildmargin@down} -% The first step is the ``down'' step, in which we move the notes -% that will go on the current page into \cs{mfx@marginout} in reverse order, -% and anything that will be deferred gets put back in -% \cs{mfx@marginlist}. This behavior is configured by changing -% the meaning of \cs{mfx@margin@note}, \cs{mfx@margin@skip}, and -% \cs{mfx@margin@clear}. Note that during the course of processing -% \cs{mfx@marginlist}, these meanings will continue to change. +% \subsection{First pass: global downward} +% \begin{macros}{\MFX@buildmargin@down,\mfx@pieceheights} +% The first step is the global ``down'' step, in which we move the +% notes that will fit on the current page into \cs{mfx@marginout} in +% reverse order (to prepare for the second, upward, pass), and anything +% that doesn't fit is deferred back into \cs{mfx@marginlist}. We do +% this by changing the meaning of \cs{mfx@build@note}, \cs{mfx@build@skip}, +% and \cs{mfx@build@clear}, which delimit the different types of material +% in \cs{mfx@marginlist}. Note that as we continue processing, these +% macros will change from time to time (i.e. changing \cs{mfx@build@skip} +% to actually doing something once we find a note, rather than gobbling +% so as to remove skips at page boundaries; or changing them to save +% material back onto \cs{mfx@marginlist} once the margin fills up). +% The first thing we need to do is iterate over the piece positions +% to get the list of heights. % \begin{macrocode} \def\MFX@buildmargin@down{% -%<debug>\MFX@debug{buildmargin@down: ENTRY}% -%<debug>\MFX@debug{marginlist=\meaning\mfx@marginlist}% - \let\mfx@margin@note\MFX@margin@note@down - \let\mfx@margin@skip\@gobble - \let\mfx@margin@clear\MFX@margin@clear@down - \let\mfx@marginout\@empty + \let\mfx@pieceheights\@empty + \def\@elt##1##2{% + \MFX@cons\mfx@pieceheights{\noexpand\@elt{\the\dimexpr##2-##1}}}% + \mfx@marginpieces + \MFX@popdimen\Mfx@marginheight\mfx@pieceheights % \end{macrocode} -% We do a little bit of \cs{expandafter} trickery here to first expand -% the current meaning of \cs{mfx@marginlist}, then clear it before -% actually executing anything. +% Now we run forwards over the \cs{mfx@marginlist} to actually +% operate on each thing in the margin. % \begin{macrocode} - \expandafter\global\expandafter\let - \expandafter\mfx@marginlist\expandafter\@empty - \mfx@marginlist -%<debug>\MFX@debug{buildmargin@down: RETURN}% -%<debug>\MFX@debug{marginlist=\meaning\mfx@marginlist}% + \let\mfx@build@note\MFX@margin@note@down + \let\mfx@build@skip\@gobble + \let\mfx@build@clear\MFX@build@clear@down + \let\mfx@marginout\@empty + \MFX@run@clear\mfx@marginlist +%<debug>\MFX@debug{buildmargin@down: RETURN +%<debug> marginout=\MFX@mac\mfx@marginout, +%<debug> marginlist=\MFX@mac\mfx@marginlist}% } % \end{macrocode} % \end{macros} % -% We must now define the different meanings for the -% |\mfx@margin@...| macros. -% \begin{macros}{\MFX@margin@note@down,\MFX@whichbox,% -% \MFX@margin@skip@down,\MFX@margin@clear@down} -% We'll dive right into the notes first. When we see a note in the -% \cs{marginlist}, we need to do several things. +% \begin{macros}{\MFX@margin@note@down,\MFX@margin@skip@down, +% \MFX@margin@clear@down} +% We now define the various |\MFX@margin@...@down| macros. +% At this stage in the game, the only difference between notes +% and skips is that we ignore skips before any notes by setting +% \cs{mfx@build@note} initially to \cs{@gobble}. Once we've +% seen the first note, skips are treated exactly the same: as +% fixed-height material. If there is room in the current piece +% for the given height, then we prepend it to \cs{mfx@marginout}, +% decrement the remaining height, and arrange for the boxes to +% be freed. If not, we unshift the next piece height from +% \cs{mfx@pieceheights} and try again, until \cs{mfx@pieceheights} +% is empty and we simply defer everything to later pages. +% +% Upon seeing a note, we must do several things: % \begin{enumerate} -% \item optionally add a \emph{compressible} skip before it, of length -% $|#3| - \cs{Mfx@marginpos}$, to line it up with the callout -% location -% \item figure out which box we need for the current page, not yet freeing -% the allocated boxes, since we may still end up deferring -% \item add the height of the correct box to \cs{Mfx@marginboxspace} and -% \cs{Mfx@marginpos} -% \item if \cs{Mfx@marginboxspace} exceeds \cs{Mfx@marginheight}, then -% we defer this note and change the meaning of \cs{mfx@margin@note} -% to defer all remaining notes -% \item otherwise, we add tokens to construct the box to \cs{toks@} and -% free the allocated inserts. -% \item redefine \cs{mfx@margin@skip} to \cs{MFX@margin@skip@down}, now -% that we've gotten a real note (it starts out as \cs{@gobble} since -% we don't want to apply skips at the very beginning). +% \item determine which box (left or right) is needed for the +% current page, by calling \cs{MFX@whichbox} +% \item if the box fits, free both boxes, prepend \cs{mfx@marginout} +% with a call to \cs{mfx@build@note}, and re-enable skips +% \item otherwise, defer the current note and all future notes % \end{enumerate} +% The latter two steps are taken care of by \cs{MFX@margin@fit}, +% which takes the height and two blocks of material: one to prepend +% to \cs{mfx@marginout} if it fits, the other to append to +% \cs{mfx@marginlist} if it doesn't. % \begin{macrocode} \def\MFX@margin@note@down#1#2#3{% -%<debug>\MFX@debug{note@down: ENTRY: #1/#2 at #3}% - \ifdim#3>\Mfx@marginpos - \dimen@\dimexpr#3-\Mfx@marginpos\relax -%<debug>\MFX@debug{note@down: adding compressible \the\dimen@}% - \MFX@snoc\mfx@marginout{\noexpand\mfx@margin@compressible{\the\dimen@}}% - \advance\Mfx@marginpos\the\dimen@ - \fi - \MFX@whichbox#1#2% - \advance\Mfx@marginboxspace\dimexpr\ht\@marbox+\dp\@marbox\relax - \ifdim\Mfx@marginboxspace>\Mfx@marginheight -% \end{macrocode} -% We've run out of margin space, so we now defer every following box, -% which means appending them back to \cs{mfx@marginlist}, except with -% a callout position of zero, since we want them as high up as possible -% (also, the position should be monotonic). -% \begin{macrocode} -%<debug>\MFX@debug{note@down: out of space: -%<debug> \the\Mfx@marginboxspace>\the\Mfx@marginheight}% - \advance\Mfx@marginboxspace\dimexpr-\ht\@marbox-\dp\@marbox\relax - \mfx@margin@clear - \mfx@margin@note#1#2{#3}% - \else -% \end{macrocode} -% At this point, the box is definitely going onto this page, so we can -% arrange for the boxes to be freed and then add the correct box to the -% output list. -% \begin{macrocode} -%<debug>\MFX@debug{note@down: adding \@marbox -%<debug> ht \the\ht\@marbox dp \the\dp\@marbox}% - \advance\Mfx@marginpos\dimexpr\ht\@marbox+\dp\@marbox\relax +%<debug>\MFX@debug{margin@note@down: ENTRY: #1/ #2 at #3}% + \MFX@whichbox\@marbox#1#2% + \if\MFX@check@fit{}{\ht\@marbox+\dp\@marbox}% \MFX@snoc\mfx@marginout{% - \noexpand\mfx@margin@note\@marbox \noexpand\@cons\noexpand\@freelist#1% \noexpand\@cons\noexpand\@freelist#2% - }% + \noexpand\mfx@build@note\@marbox{#3}}% + \let\mfx@build@skip\MFX@margin@skip@down + \else + \mfx@build@clear + \mfx@build@note{#1}{#2}{#3}% \fi - \let\mfx@margin@skip\MFX@margin@skip@down -%<debug>\MFX@debug{note@down: RETURN space=\the\Mfx@marginboxspace, -%<debug> pos=\the\Mfx@marginpos}% } % \end{macrocode} % -% We need to figure out which box to set. This macro calls -% \cs{MFX@leftmargin} and then sets the correct box into -% \cs{@marbox} for use by \cs{MFX@margin@box@down}. +% Skips are similar. A skip needs only to save itself back into +% \cs{mfx@marginout}, provided it fits. If not, there is no need +% to defer it because it will just get gobbled at the top of the +% next page anyway. % \begin{macrocode} -\def\MFX@whichbox#1#2{% - \if\MFX@leftmargin - \def\@marbox{#1}% +\def\MFX@margin@skip@down#1{% +%<debug>\MFX@debug{margin@skip@down #1}% + \if\MFX@check@fit{}{#1}% + \MFX@snoc\mfx@marginout{\noexpand\mfx@build@skip{#1}}% \else - \def\@marbox{#2}% + \mfx@build@clear \fi -%<debug>\MFX@debug{whichbox: \@marbox}% } % \end{macrocode} % -% The next macro we'll write is the skip. This one just has to save itself -% onto whichever list we're working on (output or deferred) and in the case -% of output, update \cs{@Mfx@marginpos}. +% Finally, \cs{MFX@margin@clear@down} is the only place we actually +% need to handle full-margin clears, since the downward pass does not +% ever push \cs{mfx@build@clear} onto \cs{mfx@marginout}. When we +% see this, we simply redefine all three commands to append themselves +% back to \cs{mfx@marginlist}. % \begin{macrocode} -\def\MFX@margin@skip@down#1{% -%<debug>\MFX@debug{skip@down #1}% - \advance\Mfx@marginpos#1\relax - \advance\Mfx@marginboxspace#1\relax - \MFX@snoc\mfx@marginout{\noexpand\mfx@margin@skip{#1}}% +\def\MFX@build@clear@down{% +%<debug>\MFX@debug{clear@down}% + \def\mfx@build@note##1##2##3{% + \MFX@cons\mfx@marginlist{\noexpand\mfx@build@note##1##2{\MFX@minus@inf}}}% + \def\mfx@build@skip##1{% + \MFX@cons\mfx@marginlist{\noexpand\mfx@build@skip{##1}}}% + \def\mfx@build@clear{% + \MFX@cons\mfx@marginlist{\noexpand\mfx@build@clear}}% } % \end{macrocode} +% \end{macros} % -% Finally, \cs{MFX@margin@clear@down} simply signals the end of this -% margin. All further material will be deferred, so we redefine -% \cs{mfx@margin@note}, \cs{mfx@margin@skip}, and \cs{mfx@margin@clear} -% to defer. +% \begin{macros}{\MFX@check@fit,\MFX@popdimen} +% We factored out some of the common functionality between the +% note and skip routines, so that must now be defined. The +% \cs{MFX@check@fit} macro acts as a conditional and should be +% used as \cs{if}\cs{MFX@check@fit}\marg{piece-hook}\marg{size}. +% It takes care of iterating through the list of heights and +% accumulating the total size of material encountered so far. +% The \emph{piece-hook} is executed each time a new piece height +% is popped. % \begin{macrocode} -\def\MFX@margin@clear@down{% -%<debug>\MFX@debug{clear@down}% - \def\mfx@margin@note##1##2##3{% - \MFX@cons\mfx@marginlist{\noexpand\mfx@margin@note##1##2{\MFX@minus@inf}}}% - \def\mfx@margin@skip##1{% - \MFX@cons\mfx@marginlist{\noexpand\mfx@margin@skip{##1}}}% - \def\mfx@margin@clear{% - \MFX@cons\mfx@marginlist{\noexpand\mfx@margin@clear}}% +\def\MFX@check@fit#1#2{% + 00\fi % close out the \if +%<debug>\MFX@debug{check@fit{\unexpanded{#1}}{#2=\the\dimexpr#2} ENTRY: +%<debug> marginheight=\the\Mfx@marginheight}% + \@tempswafalse + \ifdim\dimexpr#2<\Mfx@marginheight % it fits + \advance\Mfx@marginheight-\dimexpr#2\relax % deduct the size + \@tempswatrue + \else % didn't fit: check the next piece +%<debug>\MFX@debug{check@fit overflow: pieceheights=\MFX@mac\mfx@pieceheights}% + \ifx\mfx@pieceheights\@empty\else % make sure there's anything there + #1% + \MFX@popdimen\Mfx@marginheight\mfx@pieceheights + \if\MFX@check@fit{#1}{#2}\fi + \fi + \fi +%<debug>\MFX@debug{check@fit RETURN \meaning\if@tempswa: +%<debug> marginheight=\the\Mfx@marginheight,}% + \if@tempswa % start a new \if +} +% \end{macrocode} +% Here is a quick convenience routine. \cs{MFX@popdimen}\marg{dimen}% +% \marg{list} removes the first dimension from \emph{list} and stores +% it into \emph{dimen}. +% \begin{macrocode} +\def\MFX@popdimen#1#2{% + \def\@elt##1{% + #1##1\relax + \def\@elt####1{% + \MFX@cons#2{\noexpand\@elt{####1}}% + }% + }% + \MFX@run@clear#2% } % \end{macrocode} % \end{macros} % -% \begin{macros}{\MFX@minus@inf} -% Note that when we added deferred boxes to the list, we put them -% at \cs{MFX@minus@inf}. We'll define that to be a large negative -% dimension. +% \begin{macros}{\MFX@whichbox,\MFX@leftmargin} +% We also need to determine which box should be used, since they +% may have different heights. The macro \cs{MFX@whichbox}% +% \marg{target-box}\marg{left-box}\marg{right-box} checks which margin +% we're setting and stores the correct box into \emph{target-box}. +% Note that \emph{target-box} must be a single control sequence. % \begin{macrocode} -\def\MFX@minus@inf{-4000pt} +\def\MFX@whichbox#1#2#3{% + \if\MFX@leftmargin + \def#1{#2}% + \else + \def#1{#3}% + \fi +%<debug>\MFX@debug{whichbox: \@marbox (\the\dimexpr\ht#1+\dp#1)}% +} % \end{macrocode} -% \end{macros} % -% \begin{macros}{\MFX@leftmargin} -% Here we figure out which box to use based on the page number and -% other flags. This is a conditional that should be used after -% \cs{if}, as in \cs{if}\cs{MFX@leftmargin}\ldots\cs{else}\ldots\cs{fi}. +% And here is the logic to figure out which margin were in, based +% on the page number and other flags. This is another conditional-like +% macro, and should be used after an \cs{if}, as in +% \cs{if}\cs{MFX@leftmargin}\ldots\cs{else}\ldots\cs{fi}. % % This is different from the corresponding code in the {\LaTeX} % routines because we don't support double columns. In addition, we % would ideally allow \cs{if@reversemargin} to work on a per-note % basis (i.e. at callout time) but we also need something working at % shipout time so we can figure out which margin to use. Thus, until -% we figure out how to use multiple margins. +% we figure out how to use multiple margins, this will have to do. % \begin{macrocode} \def\MFX@leftmargin{% 00\fi % close out the \if @@ -742,84 +963,445 @@ % \end{macrocode} % \end{macros} % -% \subsection{Second pass} -% \begin{macros}{\MFX@buildmargin@up} -% Next is the ``up'' step. Here we simply take the reversed -% list in \cs{mfx@marginout} and prepend each item in turn to -% \cs{Mfx@marginbox}. We start by automatically discarding any -% skips, though we can't do this with a simple \cs{@gobble} anymore -% since we need to deduct them from \cs{Mfx@marginpos}. Once we -% hit a note, we'll change the skips to be their normal meanings. -% Note that there are no clears in \cs{mfx@marginout}. +% \begin{macros}{\MFX@minus@inf} +% Finally, note that when deferring notes to the next page, we +% adjust their position to the top of the page, rather than the +% callout position. This is a large negative dimension (near +% \TeX's maximum), but we may reconsider making this zero or even +% a small positive amount, since there seems to be a small amount +% of space before the first paragraph in normal text, though I'm +% not sure where that comes from. +% \begin{macrocode} +\def\MFX@minus@inf{-4000\p@} +% \end{macrocode} +% \end{macros} +% +% \subsection{Second pass: global upward} +% \begin{macros}{\MFX@buildmargin@up,\mfx@phantomheights} +% The next step is the global ``up'' step, in which we figure out the +% lowest piece a note can possibly occupy (without pushing later notes +% off the bottom) and add this information to the \cs{mfx@build@note} in +% \cs{mfx@marginout}. We start similar to \cs{MFX@buildmargin@down}, +% except we need the list of heights to be backwards. We also need a +% list of phantom heights, in order to handle skips properly, which we +% intersperse as negative heights. % \begin{macrocode} \def\MFX@buildmargin@up{% -%<debug>\MFX@debug{buildmargin@up: excess=\the\dimexpr -%<debug> \Mfx@marginpos-\Mfx@marginheight\relax}% -%<debug>\MFX@debug{marginout=\meaning\mfx@marginout} - \let\mfx@margin@note\MFX@margin@note@up - \let\mfx@margin@compressible\MFX@margin@skip@gobble@up - \let\mfx@margin@skip\MFX@margin@skip@gobble@up - \mfx@marginout +%<debug>\MFX@debug{buildmargin@up: ENTRY +%<debug> marginpieces=\MFX@mac\mfx@marginpieces, +%<debug> marginout=\MFX@mac\mfx@marginout}% + \let\mfx@pieceheights\@empty + \let\mfx@phantomheights\@empty + \let\temp@@\relax + \def\@elt##1##2{% +%<debug>\MFX@debug{ -> piece (##1,##2), temp@@=\meaning\temp@@}% + \MFX@snoc\mfx@pieceheights{\noexpand\@elt{\the\dimexpr##2-##1}}% + \ifx\temp@@\relax\else +%<debug>\MFX@debug{ -> phantom (\temp@@,##1)}% + \MFX@snoc\mfx@phantomheights{\noexpand\@elt{\the\dimexpr##1-\temp@@}}% + \fi + \def\temp@@{##2}% + }% + \mfx@in@phantomfalse + \mfx@marginpieces +% \end{macrocode} +% The piece counter, \cs{Mfx@piece@count} has not been touched yet, +% so now we will start decrementing it for each piece to keep track +% of where we are. Note that \cs{mfx@marginout} will never contain +% \cs{mfx@build@clear} so we don't need to reassign it. Since we +% don't do any deferrals here, we don't need to empty out a new target +% list. Instead, we operate ``in-place'' in \cs{mfx@marginout}, after +% popping off the first height. +% \begin{macrocode} + \MFX@popdimen\Mfx@marginheight\mfx@pieceheights + \let\mfx@build@note\MFX@margin@note@up + \let\mfx@build@skip\@gobble + \MFX@run@clear\mfx@marginout +%<debug>\MFX@debug{buildmargin@up: RETURN marginout=\MFX@mac\mfx@marginout}% } % \end{macrocode} % \end{macros} % -% \begin{macros}{\MFX@margin@skip@gobble@up} -% In case we have any skips at the beginning of \cs{mfx@marginout}, -% we'll gobble them and deduct their lenghts from \cs{Mfx@marginpos}. +% \begin{macros}{\MFX@margin@note@up,\MFX@margin@skip@up} +% We must again define the specific behavior of each build command. +% These macros simply reuse \cs{MFX@check@fit}, but ask it to +% decrement the piece counter when a piece runs out of space. +% Aside from that, the only thing that actually happens here is +% that we append the current piece to each note, and also end +% up reversing the contents by again prepending everything. +% We are guaranteed that \cs{MFX@check@fit} will never fail. +% Since we cannot put notes in a phantom, we start by ensuring +% we're not in one. +% \begin{macrocode} +\def\MFX@margin@note@up#1#2{% +%<debug>\MFX@debug{margin@note@up: #1at #2, marginheight=\the\Mfx@marginheight}% + \ifmfx@in@phantom + \MFX@popdimen\Mfx@marginheight\mfx@pieceheights + \advance\Mfx@piece@count\m@ne + \mfx@in@phantomfalse + \fi +% \end{macrocode} +% Now we're guaranteed to be in a piece, rather than a phantom, we +% look for a piece that can fit this note, making sure to decrement +% the piece count and pop off a phantom for each new piece we check. +% Once it's found, we add the note back to \cs{mfx@marginout} with +% the corect piece. % \begin{macrocode} -\def\MFX@margin@skip@gobble@up#1{% -%<debug>\MFX@debug{skip@gobble@up: #1}% - \advance\Mfx@marginpos-#1\relax + \if\MFX@check@fit{\advance\Mfx@piece@count\m@ne + \MFX@popdimen\dimen@\mfx@phantomheights}{\ht#1+\dp#1}% + \MFX@snoc\mfx@marginout{% + \noexpand\mfx@build@note{#1}{#2}{\the\Mfx@piece@count}}% + \let\mfx@build@skip\MFX@margin@skip@up + \else\MFX@AssertionError\fi +} +% \end{macrocode} +% Skips are similar, but we have the added complication of handling +% margin phantoms. When we cross between phantom and piece, we split +% the skip so that we can use the simplest recursion possible. +% \begin{macrocode} +\def\MFX@margin@skip@up#1{% +%<debug>\MFX@debug{margin@skip@up: #1}% + \dimen@#1\relax + \advance\Mfx@marginheight-\dimen@ + \ifdim\Mfx@marginheight<\z@ +% \end{macrocode} +% This skip was bigger than the piece, so we need to split this skip, +% adding the overflow back, and try again. Since we're done with this +% piece, we'll pop the next one and recurse on whatever's left. This +% looks slightly different depending on whether or not we're in a phantom. +% \begin{macrocode} + \advance\dimen@\Mfx@marginheight + \MFX@snoc\mfx@marginout{% + \noexpand\mfx@build@skip{\the\dimen@}{\the\Mfx@piece@count}}% + \dimen@-\Mfx@marginheight + \ifmfx@in@phantom + \MFX@popdimen\Mfx@marginheight\mfx@pieceheights + \advance\Mfx@piece@count\m@ne + \mfx@in@phantomfalse + \else + \MFX@popdimen\Mfx@marginheight\mfx@phantomheights + \mfx@in@phantomtrue + \fi + \mfx@build@skip\dimen@ + \else +% \end{macrocode} +% This skip fit entirely within the phantom, so we simply emit it. +% \begin{macrocode} + \MFX@snoc\mfx@marginout{% + \noexpand\mfx@build@skip{\the\dimen@}{\the\Mfx@piece@count}}% + \fi } % \end{macrocode} % \end{macros} % -% \begin{macros}{\MFX@margin@note@up} -% Once we actually do hit a note, we need to set it in \cs{Mfx@marginbox}. -% We also redefine \cs{mfx@margin@skip} and \cs{mfx@margin@compressible} -% here. Hopefully any skips aren't being dropped by our \cs{unvbox}ing. +% \subsection{Margin pieces} +% \begin{macros}{\MFX@buildmargin@pieces,\MFX@buildmargin@piece} +% Before we can start the third and fourth passes, we need to set up +% a loop over the pieces so that each piece can do these passes at +% one time. In case we didn't use up all the pieces in the second +% phase, we'll reset \cs{Mfx@piece@count} to zero. We also reset +% \cs{Mfx@marginspace} and \cs{Mfx@marginbox}. +% \begin{macrocode} +\def\MFX@buildmargin@pieces{% + \Mfx@piece@count\z@ + \Mfx@marginspace\z@ + \setbox\Mfx@marginbox\vbox{\vskip\z@}% TODO - do we need this? +% \end{macrocode} +% Now we run over the individual margin pieces, clearing it out as we +% build up the contents of \cs{Mfx@marginbox}. Once we're done with +% that, we need to do a bit of clean-up before finishing. % \begin{macrocode} -\def\MFX@margin@note@up#1{% -%<debug>\MFX@debug{note@up: #1}% - \setbox\Mfx@marginbox\vbox{\box#1\unvbox\Mfx@marginbox}% - \let\mfx@margin@skip\MFX@margin@skip@up - \let\mfx@margin@compressible\MFX@margin@compressible@up + \let\@elt\MFX@buildmargin@piece + \MFX@run@clear\mfx@marginpieces + \let\@elt\relax + \Mfx@piece@count\z@ +} +% \end{macrocode} +% +% The \cs{MFX@buildmargin@piece} macro is called for each piece of +% the margin, and is passed the top and bottom positions of the piece. +% Here we need to do a few things. First, if the output so far is +% smaller than the top of the piece (this is generally true, except +% sometimes for the first piece) then we need to insert padding into +% \cs{Mfx@marginbox} before continuing. We accumulate this padding +% into \cs{Mfx@marginspace}, which allows skips to do double-duty +% across phantoms. +% \begin{macrocode} +\def\MFX@buildmargin@piece#1#2{% +%<debug>\MFX@debug{buildmargin@piece: ENTRY (#1, #2)}% + \ifdim\ht\Mfx@marginbox<#1\relax + \dimen@\dimexpr#1-\ht\Mfx@marginbox\relax +%<debug>\MFX@debug{buildmargin@piece: padding \the\dimen@}% + \setbox\Mfx@marginbox\vbox{% + \unvbox\Mfx@marginbox + \vskip\dimen@ + }% + \advance\Mfx@marginspace\dimen@ + \fi +% \end{macrocode} +% Now that \cs{Mfx@marginbox} has been padded, we proceed to set +% things up for our down and up passes, and then run them to build +% \cs{Mfx@piece@content}. +% \begin{macrocode} + \Mfx@marginpos@min#1\relax + \Mfx@marginpos@max#1\relax + \Mfx@marginheight#2\relax + \advance\Mfx@piece@count\@ne + \MFX@buildpiece@down + \MFX@buildpiece@up +% \end{macrocode} +% Once \cs{Mfx@piece@content} has been built, we append it to the +% \cs{Mfx@marginbox}. +% \begin{macrocode} + \setbox\Mfx@marginbox\vbox{% + \unvbox\Mfx@marginbox + \box\Mfx@piece@content + \vskip\z@ + }% } % \end{macrocode} % \end{macros} % -% \begin{macros}{\MFX@margin@skip@up} -% This one is even easier---all we have to do is add a skip to the -% margin output box. We also need to make sure it's not getting dropped, -% which might entail adding some \cs{vbox}\lit\{\lit\}s. +% \subsection{Third pass: piecewise downward} +% \begin{macros}{\MFX@buildpiece@down,\mfx@pieceout} +% The next pass is another downward pass. This is very similar +% to the first (global downward) pass, except we're now moving +% the first several notes from \cs{mfx@marginout} into +% \cs{mfx@pieceout} (again, inverting the order) and deferring +% the rest back into \cs{mfx@marginout}. % \begin{macrocode} -\def\MFX@margin@skip@up#1{% -%<debug>\MFX@debug{skip@up: #1}% - \setbox\Mfx@marginbox\vbox{\vskip#1\relax\unvbox\Mfx@marginbox}% +\def\MFX@buildpiece@down{% +%<debug>\MFX@debug{buildpiece@down: ENTRY piece=\the\Mfx@piece@count, +%<debug> marginpos=(\the\Mfx@marginpos@min, \the\Mfx@marginpos@max), +%<debug> marginspace=\the\Mfx@marginspace, +%<debug> marginout=\MFX@mac\mfx@marginout}% + \let\mfx@build@note\MFX@piece@note@down + \let\mfx@build@skip\MFX@piece@skip@down + \let\mfx@pieceout\@empty + \MFX@run@clear\mfx@marginout +%<debug>\MFX@debug{buildpiece@down: RETURN pieceout=\MFX@mac\mfx@pieceout, +%<debug> marginout=\MFX@mac\mfx@marginout}% } % \end{macrocode} % \end{macros} % -% \begin{macros}{\MFX@margin@compressible@up} -% This token gets put into \cs{mfx@marginout} during the first pass. -% When it executes, it inserts a \cs{vskip} depending on how -% much extra margin space (stored in \cs{Mfx@marginpos}) -% we need to excise. If also updates \cs{Mfx@marginpos}. +% \begin{macros}{\MFX@piece@note@down,\MFX@piece@skip@down,\MFX@piece@clear} +% We again define each of our building macros. First, the note builder. +% When we encounter a note, we first zero out \cs{Mfx@marginspace}. Then +% we need to decide whether to put it in the current piece, or whether +% to defer it to the next piece, based on a few signals. A note will only +% be deferred for one of two reasons. In either case, we store the decision +% to defer in |@tempswa|, so we'll start by clearing it. % \begin{macrocode} -\def\MFX@margin@compressible@up#1{% -%<debug>\MFX@debug{compressible@up: #1, excess=\the\dimexpr -%<debug> \Mfx@marginpos-\Mfx@marginheight\relax}% - \dimen@#1\relax - \ifdim\Mfx@marginpos>\Mfx@marginheight - \advance\dimen@\dimexpr\Mfx@marginheight-\Mfx@marginpos\relax - \ifdim\dimen@<\z@ - \dimen@\z@ +\def\MFX@piece@note@down#1#2#3{% +%<debug>\MFX@debug{piece@note@down: ENTRY: #1at #2, lowest=#3, +%<debug> marginpos=(\the\Mfx@marginpos@min,\the\Mfx@marginpos@max, +%<debug> marginspace=\the\Mfx@marginspace}% + \Mfx@marginspace\z@ + \@tempswafalse +% \end{macrocode} +% The first reason to defer is if its callout position (|#2|) +% is beneath the end of the current piece (\cs{Mfx@marginheight}) \emph{and} +% if its lowest-possible-piece (|#3|, as computed in the second pass) is +% \emph{after} the current one (\cs{Mfx@piece@count}). +% \begin{macrocode} + \ifdim#2>\Mfx@marginheight + \ifnum#3>\Mfx@piece@count + \@tempswatrue \fi - \advance\Mfx@marginpos\dimexpr\dimen@-#1\relax \fi +% \end{macrocode} +% The second possibility is that we've run out of room in this piece. +% In this case, there's no need to check |#3|, since that number was +% computed assuming everything that fit was as low as possible. +% \begin{macrocode} + \ifdim\dimexpr\ht#1+\dp#1+\Mfx@marginpos@min>\Mfx@marginheight + \@tempswatrue + \fi +% \end{macrocode} +% If a note is deferred, then we push it and everything after it +% back onto \cs{mfx@marginout}. +% \begin{macrocode} + \if@tempswa +%<debug>\MFX@debug{piece@note@down: clearing margin}% + \MFX@piece@clear + \mfx@build@note{#1}{#2}{#3}% + \else +% \end{macrocode} +% Otherwise, we have decided the note is going into this piece. In this +% case, we now need to check if any compressible space is needed above it. +% In order to get better alignment for deferred notes, we check for the +% case that the current position is zero and the note's callout position +% is \cs{MFX@minus@inf}. In this case, we change the callout position +% to instead be the greater of 0 or $\cs{topskip}-\cs{ht}\#1$. +% \begin{macrocode} + \dimen@#2\relax + \ifdim\dimen@=\MFX@minus@inf + \ifdim\Mfx@marginpos@max=\z@ + \dimen@\topskip + \advance\dimen@-\ht#1\relax + \ifdim\dimen@<\z@ \dimen@\z@ \fi + \fi + \fi + \advance\dimen@-\Mfx@marginpos@max + \ifdim\dimen@>\z@ +%<debug>\MFX@debug{piece@note@down: adding compressible \the\dimen@}% + \MFX@snoc\mfx@pieceout{\noexpand\mfx@build@compressible{\the\dimen@}}% + \advance\Mfx@marginpos@max\dimen@ + \fi +% \end{macrocode} +% After (maybe) adding the compressible space, we now add the +% (incompressible) box itself, and accumulate its height in both position +% registers. We no longer need the piece index or the position, so we +% only store the box itself here. +% \begin{macrocode} + \MFX@snoc\mfx@pieceout{\noexpand\mfx@build@note{#1}}% + \advance\Mfx@marginpos@min\dimexpr\ht#1+\dp#1\relax + \advance\Mfx@marginpos@max\dimexpr\ht#1+\dp#1\relax + \fi +} +% \end{macrocode} +% +% Skips are a bit more complicated now. We no longer gobble the +% initial skips (since the skips at the top and bottom of the page +% have already been eaten). Instead, we need to look at +% \cs{Mfx@marginspace}: if it's nonzero, we substract it from the +% skip length before adding it incompressibly (if there's any left). +% While we have piece number information here, we just pass it on +% to the upward pass, which can choose to ``late-defer'' initial +% (i.e. the bottom-most) skips in a piece. +% \begin{macrocode} +\def\MFX@piece@skip@down#1#2{% + \dimen@#1\relax + \ifdim\Mfx@marginspace>\z@ + \advance\dimen@-\Mfx@marginspace + \ifdim\dimen@<\z@ \dimen@\z@ \fi + \advance\Mfx@marginspace-\dimen@ + \fi +% \end{macrocode} +% At this point, \cs{dimen@} now stores any further incompressible +% skip we need to add, which is now relative to the top of this piece. +% In that case (note that \cs{Mfx@marginspace} is now necessarily zero), +% we need to check that it actually fits in the current piece, and if +% not, defer it. +% \begin{macrocode} \ifdim\dimen@>\z@ - \MFX@margin@skip@up\dimen@ + \ifdim\dimexpr#1+\Mfx@marginpos@min>\Mfx@marginheight + \MFX@piece@clear + \mfx@build@skip{\the\dimen@}{#2}% + \else +% \end{macrocode} +% If the skip \emph{does} fit, we need to add it to \cs{mfx@pieceout} +% and advance the position registers. +% \begin{macrocode} + \MFX@snoc\mfx@pieceout{\noexpand\mfx@build@skip{\the\dimen@}{#2}}% + \advance\Mfx@marginpos@min\dimen@ + \advance\Mfx@marginpos@max\dimen@ + \fi + \fi +} +% \end{macrocode} +% +% Finally, we need to handle the case of deferring material. By +% analogy with the previous two passes, we'll continue to refer to +% this as clearing. In this case, we need to redefine the note and +% skip macros to save themselves back to \cs{mfx@marginout}. +% \begin{macrocode} +\def\MFX@piece@clear{% +%<debug>\MFX@debug{piece@clear}% + \def\mfx@build@note##1##2##3{% + \MFX@cons\mfx@marginout{\noexpand\mfx@build@note##1{##2}{##3}}}% + \def\mfx@build@skip##1##2{% + \MFX@cons\mfx@marginout{\noexpand\mfx@build@skip{##1}{##2}}}% +} +% \end{macrocode} +% \end{macros} +% +% \subsection{Fourth pass: piecewise upward} +% \begin{macros}{\MFX@buildpiece@up} +% We are now ready for the final pass, where we take the reversed +% list of notes for this piece and stack them up, compressing as much +% compressible space as necessary to make them all fit. Since this +% is the last pass, we can finally prepend all the boxes into +% \cs{Mfx@piece@content}. Because it is still possible for pieces +% to have skips at the bottom, we need to worry about which piece +% these skips belong in. Since we've passed forward the lowest-piece +% information from pass 2, we can choose at the last possible moment +% to defer the bottom-most skips of a piece to the next piece (by +% prepending it to \cs{mfx@marginout}. Note that compressible spaces +% will \emph{never} be at the beginning of \cs{mfx@pieceout}. We +% store the excess space in \cs{Mfx@marginheight}. +% \begin{macrocode} +\def\MFX@buildpiece@up{% + \Mfx@marginheight\dimexpr\Mfx@marginpos@max-\Mfx@marginheight\relax + \ifdim\Mfx@marginheight<\z@\Mfx@marginheight\z@\fi +%<debug>\MFX@debug{buildpiece@up: excess=\the\Mfx@marginheight}% + \let\mfx@build@note\MFX@piece@note@up + \let\mfx@build@compressible\MFX@piece@compressible@up + \let\mfx@build@skip\MFX@piece@skip@maybedefer + \MFX@run@clear\mfx@pieceout\relax +} +% \end{macrocode} +% \end{macros} +% +% \begin{macros}{\MFX@piece@skip@maybedefer} +% As mentioned earlier, the initial skips in a piece may be deferred +% to later pieces, provided they can possibly go there. Here we +% check the |#2| argument against \cs{Mfx@piece@count} and defer if +% it is larger. Otherwise, we switch back to the standard behavior +% defined in \cs{MFX@piece@skip@up}. Deferred skips do not need to +% be subtracted from the excess because they were never compressible +% in the first place. +% \begin{macrocode} +\def\MFX@piece@skip@maybedefer#1#2{% + \ifnum#2>\Mfx@piece@count +%<debug>\MFX@debug{piece@skip deferring: #1, #2 (\the\Mfx@piece@count)}% + \MFX@snoc\mfx@marginout{\noexpand\mfx@build@skip{#1}{#2}}% + \else + \let\mfx@build@skip\MFX@piece@skip@up + \mfx@build@skip{#1}{#2}% + \fi +} +% \end{macrocode} +% \end{macros} +% +% \begin{macros}{\MFX@piece@note@up,\MFX@piece@skip@up} +% Now that we've taken care of late deferrals, we can define the +% standard behavior without worrying as much about that. +% These macros finally set their contents into \cs{Mfx@piece@content}, +% as well as reset \cs{mfx@build@skip} back to its standard value. +% \begin{macrocode} +\def\MFX@piece@note@up#1{% +%<debug>\MFX@debug{piece@note@up: #1}% + \setbox\Mfx@piece@content\vbox{% + \box#1% + \unvbox\Mfx@piece@content}% + \let\mfx@build@skip\MFX@piece@skip@up +} +% \end{macrocode} +% Skips are also straightforward. +% \begin{macrocode} +\def\MFX@piece@skip@up#1#2{% +%<debug>\MFX@debug{skip@up: #1=\the\dimexpr#1\relax (#2)}% + \setbox\Mfx@piece@content\vbox{% + \vskip#1\relax + \unvbox\Mfx@piece@content}% +} +% \end{macrocode} +% \end{macros} +% +% \begin{macros}{\MFX@piece@compressible@up} +% Finally we come to the compressible space. Here, as long as +% there's excess space (\cs{Mfx@marginheight}) we drop the +% compressible space. Once the excess is exhausted, we insert +% them as normal skips. +% \begin{macrocode} +\def\MFX@piece@compressible@up#1{% +%<debug>\MFX@debug{compressible@up: #1, excess=\the\Mfx@marginheight}% + \advance\Mfx@marginheight-#1\relax + \ifdim\Mfx@marginheight<\z@ + \MFX@piece@skip@up{-\Mfx@marginheight}\relax + \Mfx@marginheight\z@ \fi } % \end{macrocode} @@ -840,11 +1422,13 @@ %<debug>\MFX@debug{dumpmargins}% \loop \unless\ifx\mfx@marginlist\@empty +%<debug>\MFX@debug{dumpmargins: marginlist=\MFX@mac\mfx@marginlist}% \let\temp@\mfx@marginlist \vbox{}\clearpage \ifx\temp@\mfx@marginlist \PackageError{marginfix}{lost some margin notes% -%<debug>: \meaning\mfx@marginlist + \ifx\mfx@marginstart\relax\ (missing \noexpand\unblockmargin)\fi +%<debug>: \MFX@mac\mfx@marginlist }\@eha \let\mfx@marginlist\@empty % be nicer by just dropping one? % TODO: also, set an emergency mode to allow oversized notes @@ -859,10 +1443,10 @@ % % \begin{macros}{\marginskip} % Inserting a skip in the margin list is simple. We need only -% append \cs{mfx@margin@skip} to \cs{mfx@marginlist}. +% append \cs{mfx@build@skip} to \cs{mfx@marginlist}. % \begin{macrocode} \def\marginskip#1{% - \MFX@cons\mfx@marginlist{\noexpand\mfx@margin@skip{#1}}% + \MFX@cons\mfx@marginlist{\noexpand\mfx@build@skip{#1}}% } % \end{macrocode} % \end{macros} @@ -871,7 +1455,7 @@ % Likewise, \cs{clearmargin} is easy too. % \begin{macrocode} \def\clearmargin{% - \MFX@cons\mfx@marginlist{\noexpand\mfx@margin@clear}% + \MFX@cons\mfx@marginlist{\noexpand\mfx@build@clear}% } % \end{macrocode} % While we call \cs{softclearmargin} a ``clear margin'', it's @@ -904,6 +1488,86 @@ % \end{macrocode} % \end{macros} % +% \begin{macros}{\blockmargin,\MFX@blockmargin} +% We need two macros to process the optional bracket argument. +% Essentially, \cs{blockmargin} just checks that +% \cs{mfx@marginstart} is defined and then appends a new item to +% the \cs{mfx@marginpieces} list to indicate a piece that starts +% at \cs{mfx@marginstart} and ends at the current position, potentially +% modified by the argument. It then resets \cs{mfx@marginstart} to +% \cs{relax} and optionally adds a \cs{softclearmargin} if there was no +% star, to keep margin material separated on either side of the phantom. +% \begin{macrocode} +\def\blockmargin{% + \@ifnextchar[%] + \MFX@blockmargin + {\MFX@blockmargin[0\p@]}% +} +\def\MFX@blockmargin[#1]{% + \MFX@inject{% + \ifx\mfx@marginstart\relax + \PackageError{marginfix}{two \\blockmargin with no \\unblockmargin}\@eha + \else + \MFX@cons\mfx@marginpieces{\noexpand + \@elt{\mfx@marginstart}{\expandafter\dimexpr\mfx@ypos+#1\relax}}% + \global\let\mfx@marginstart\relax + \global\advance\Mfx@piece@count\@ne + \fi + }% +} +% \end{macrocode} +% \end{macros} +% +% \begin{macros}{\unblockmargin,\MFX@unblockmargin} +% This is the other half of \cs{blockmargin}. It ensures that the +% margin is currently blocked, and if so, sets \cs{mfx@marginstart} +% back to a real dimension (the current page position, plus the optional +% modifier). +% \begin{macrocode} +\def\unblockmargin{% + \@ifnextchar[%] + \MFX@unblockmargin + {\MFX@unblockmargin[0\p@]}% +} +\def\MFX@unblockmargin[#1]{% + \MFX@inject{% + \ifx\mfx@marginstart\relax + \xdef\mfx@marginstart{\dimexpr\mfx@ypos+#1\relax}% + \else + \PackageError{marginfix}{\\unblockmargin with no \\blockmargin}\@eha + \fi + }% +} +% \end{macrocode} +% \end{macros} +% +% \begin{macros}{\marginphantom,\MFX@marginphantom} +% The \cs{marginphantom} command is basically just a concatenation of +% \cs{blockmargin} and \cs{unblockmargin}. We reimplement it from the +% ground up mainly so that the error messages make more sense. +% \begin{macrocode} +\def\marginphantom{% + \@ifnextchar[%] + \MFX@marginphantom + {\MFX@marginphantom[0\p@]}% +} +\def\MFX@marginphantom[#1]#2{% + \ifdim#2<\z@\MFX@marginphantom[#1+#2]{-#2}\else + \MFX@inject{% + \ifx\mfx@marginstart\relax + \PackageError{marginfix}{\\marginphantom while margin blocked}\@eha + \else + \MFX@cons\mfx@marginpieces{\noexpand + \@elt{\mfx@marginstart}{\expandafter\dimexpr\mfx@ypos+#1\relax}}% + \xdef\mfx@marginstart{\dimexpr\mfx@ypos+#1+#2\relax}% + \global\advance\Mfx@piece@count\@ne + \fi + }% + \fi +} +% \end{macrocode} +% \end{macros} +% % \section{Random scribbles} % % Later we'll get fancier with putting notes next to top/bottom @@ -917,7 +1581,7 @@ % now that's all we'll do. % % \section{Parting words} -% Finish it up +% Finish it up. % \begin{macrocode} \makeatother %</package> |