diff options
Diffstat (limited to 'macros/latex/contrib/versonotes/versonotes.dtx')
-rw-r--r-- | macros/latex/contrib/versonotes/versonotes.dtx | 658 |
1 files changed, 658 insertions, 0 deletions
diff --git a/macros/latex/contrib/versonotes/versonotes.dtx b/macros/latex/contrib/versonotes/versonotes.dtx new file mode 100644 index 0000000000..afcdc70719 --- /dev/null +++ b/macros/latex/contrib/versonotes/versonotes.dtx @@ -0,0 +1,658 @@ +% \iffalse +% This is versonotes.dtx, which supports adding notes on verso pages, +% opposite annotation on the recto pages. Still sketchy +%% +%% Release version 0.4, 2019 July 6. +%% +%% Copyright 2014, 2015, 2019 Norman Gray <https://nxg.me.uk> +%% +%% This work may be distributed and/or modified under the +%% conditions of the LaTeX Project Public License, either version 1.3 +%% of this license or (at your option) any later version. +%% The latest version of this license is in +%% http://www.latex-project.org/lppl.txt +%% and version 1.3 or later is part of all distributions of LaTeX +%% version 2005/12/01 or later. +%% +%% This work has the LPPL maintenance status `maintained'. +%% +%% The Current Maintainer of this work is Norman Gray +%% +%% This work consists of the files versonotes.dtx and versonotes.ins +%% and the derived file versonotes.sty. +%% +%%%% File: versonotes.dtx + +%<+package|driver|example>%%%% Source: 7d4f5fb7b19e, 2019-07-06T19:09:45+01:00 +%% +%<+package>\NeedsTeXFormat{LaTeX2e} +%<+package>\ProvidesPackage{versonotes}[2019/07/06 v0.4] +% +%<*driver> +\documentclass{ltxdoc} +\title{versonotes: Notes on verso pages} +\author{Norman Gray\\\url{https://nxg.me.uk}} +\date{Release 0.4, 2019 July 6} +\usepackage{url} +\newcommand\Lopt[1]{\textsf {#1}} % options +\newcommand\file[1]{\texttt {#1}} +\newcommand\Lcount[1]{\textsl {\small#1}} +\newcommand\Lenv[1]{\texttt{\{#1\}}} %environments +\newcommand\Lpackage[1]{\{\textsf{#1}\}} %packages +%Make command strings easier to write +%{\catcode`\<=\active \gdef<#1>{\meta{#1}}} +{\catcode`\<=\active + \gdef<#1>{{\ensuremath\langle\normalfont\textsl{#1}\ensuremath\rangle}}} +\def\cmd{\begingroup + \catcode`\\=12 \catcode`\{=12 \catcode`\}=12 + \catcode`\<=\active \catcode`\|=12 + \docmd} +\def\docmd|#1|{\texttt{#1}\endgroup} +%%% \url macro (url.sty does this better) +%\def\setpathdots{\discretionary{.}{}{.}} +%\def\setpathslash{\discretionary{/}{}{/}} +%{\catcode`\.=\active +% \catcode`\/=\active +% \gdef\pathcats{% +% \catcode`\%=12 \catcode`\~=12 +% \catcode`\.=\active \let.\setpathdots +% \catcode`\/=\active \let/\setpathslash +% \catcode`\#=12 \catcode`\_=12}% +% } +%\def\setpath#1{\ttfamily <\nobreak #1\nobreak>\endgroup} +%\def\url{\begingroup\pathcats\setpath} + +\def\bs{$\backslash$} + +\begin{document} +\maketitle +%\tableofcontents +%\bigskip +\DocInput{versonotes.dtx} +\end{document} +%</driver> +% +% \fi +% +% \noindent This package allows you to place notes on the verso pages +% of an otherwise single-sided document. If, in the run +% of text, you include a call to the macro +% |\versonote{This is a remark}|, +% then that text will be placed on the opposite (ie, `verso') page, +% lined up with the macro call. +% The notes may contain more than one paragraph. +% +% All the pages in the document appear as recto pages +% (ie, right-hand pages), +% and are numbered as such; +% the document should be formatted with that in mind. +% +% Since there is no page opposite the first page, any notes which are +% positioned to appear opposite page 1 are silently discarded. +% +% To invoke the package, use the command |\usepackage{versonotes}|. +% +% It is possible to start the document other than on page one. To do +% this, you can call |\setcounter{page}{|\emph{n}|}| as usual, but it +% must be done \emph{before} the line |\usepackage{versonotes}|. The +% package may get confused if the page number is changed at other times. +% +% If two notes come too close after one another, the second would +% overprint the first if we did not take steps to avoid this. By +% default, they are separated from each other by a short horizontal +% line, but if the \Lopt{mergecollisions} option is provided when +% loading the package, then such notes are instead merged into a +% single block of text. If a sequence of notes needs to be merged, +% then they will be added to the merged block one at a time in +% successive \LaTeX\ runs, since merging one note may make it +% unnecessary to merge the next. If a large block of such notes need +% to be merged, it will therefore take several runs before the package +% finds a stable state. If you find yourself inconvenienced by this, +% it sounds as if you are preparing a critical edition, and you might +% want to venture into a closer relationship with the +% \Lpackage{eledmac} +% package.\footnote{\texttt{http://www.ctan.org/pkg/eledmac}} +% +% The dimensions |\versoleftmargin| and |\versotextwidth| specify the +% position of the text block on the verso page. The defaults for +% these are based on the |\textwidth| and |\evensidemargin| of the +% document \emph{at the time the package is loaded}. +% By default, verso paragraphs are set ragged-left; with the +% declarations in |\versolayout| setting the paragraph parameters. +% You may reset the first two dimensions with for example +% |\versoleftmargin=4cm|, and use |\renewcommand\versolayout{...}| to +% override the paragraph formatting (this will typically require +% changing |\rightskip|, |\leftskip| and |\parfillskip|). For more +% elaborate effects (for example changing the note font size and +% spacing), you will need to redefine |\versolayout|. +% +% \subsection*{Recto notes} +% +% If the package is invoked with the \Lopt{rectonotes} option, then it +% runs in a complementary mode in which the notes are placed on the +% recto page (the right-hand one) instead of the verso. The default +% |\versolayout| in this case (which of course is really a recto +% layout) is slightly different, and is based on the preexisting +% document |\oddsidemargin|. All the pages in the document (apart +% from the front page) are then formatted as verso pages, though still +% only the non-note pages are numbered. +% +% In this mode, the `add a note' command is still called +% |\versonote|. You may find it convenient to rename that with +% |\let\rectonote = \versonote| or |\let\sidenote = \versonote|. +% +% \subsection*{Notes} +% \begin{itemize} +% \item The package uses the |\pdfsavepos| primitive, and so requires +% \texttt{pdflatex}, \texttt{xelatex}, or \texttt{luatex}. +% +% \item The notes are handled using the |.aux| file, and so (i) they will +% only appear on the second and subsequent runs; and (ii) they will be +% located opposite the position where the |\versonote| macro was +% called on the \emph{previous} run. +% +% \item If notes collide, you may well have to re-run \LaTeX\ several +% times. There is quite a lot of state in the |.aux| file. +% +% \item The package is available on CTAN at +% \texttt{macros/latex/contrib/versonotes/}. The source is hosted at +% \url{https://bitbucket.org/nxg/versonotes}, where you can also find +% an issues list. +% +% \end{itemize} +% +% \subsection*{Acknowledgements, and release notes} +% +% \iffalse @RELEASENOTES@ \fi +% +% +% Thanks to Thomas H. Luxon for permission to include the +% ‘Paradise Lost’ notes in the sample text. +% +% \begin{description} +% \item[Version 0.4, 2019 July 6]\relax +% Support changing the initial page number +% (thanks to P J Couch for the suggestion). +% \item[Version 0.3, 2015 December 8]\relax +% \leavevmode\begin{itemize} +% \item Minor documentation adjustments. +% \item Added the [rectonotes] option +% (thanks to Axel Berger for this suggestion). +% \end{itemize} +% \item[Version 0.2, 2015 February 16]\relax +% Initial public release. +% \end{description} +% +% +% \StopEventually{} + +% \newpage +% \section{Implementation} +% +% \begin{macrocode} +%<*package> +% \end{macrocode} +% +% The package contains a single user-visible command, and three +% configuration parameters. +% +% The normal behaviour of this package is to place notes on the verso +% page (ahem, hence the name). But we can swap that around. +% \begin{macrocode} +\newif\if@verso@notesonleft +\@verso@notesonlefttrue +% \end{macrocode} +% +% Configuration options: +% \begin{macrocode} +\def\verso@resetmergestate{\verso@mergestate=2} +\DeclareOption{mergecollisions}{\def\verso@resetmergestate{\verso@mergestate=0}} +\DeclareOption{rectonotes}{\@verso@notesonleftfalse} +\ProcessOptions +% \end{macrocode} +% +% The single user-visible command is |\versonote|. +% Use |\unexpanded| (from e-\TeX) so that macros in the argument are +% not expanded into the aux file. +% \begin{macrocode} +\long\def\versonote#1{\@bsphack + \pdfsavepos + \write\@auxout{\string\verso@note{\the\c@page}{\the\pdflastypos sp}\unexpanded{{#1}}}% + \@esphack} +% \end{macrocode} +% +% Now the layout parameters, and defaults. +% The dimensions |\versoleftmargin| and |\versotextwidth| specify the +% position of the text block on the verso page, and the declarations +% in |\versolayout| set the paragraph parameters. +% \begin{macrocode} +\newdimen\versotextwidth + \versotextwidth=\textwidth +\newdimen\versoleftmargin + \versoleftmargin=\paperwidth + \advance\versoleftmargin -\textwidth + \advance\versoleftmargin -\hoffset + \advance\versoleftmargin -1in +\if@verso@notesonleft + \advance\versoleftmargin -\evensidemargin + \def\versolayout{ + \leftskip=0pt plus 1fil + \rightskip=0pt + \parfillskip=0pt + \parindent=0pt + \parskip=\medskipamount + } +\else + \advance\versoleftmargin -\oddsidemargin + \def\versolayout{ + \leftskip=0pt + \rightskip=0pt plus 4em + \parindent=0pt + \parskip=\medskipamount + } +\fi +% \end{macrocode} +% +% Add a sentinel at the end of the document +% \begin{macrocode} +\def\end@versonote{% + \write\@auxout{\string\verso@note{\the\c@page}{-1pt}{}}} +\AtEndDocument{\end@versonote} +% \end{macrocode} +% +% Define various bits of working storage. +% +% The |\verso@pages| token list needs to have an extra leading `page' +% since the logic in |\verso@processonepage@| can skip it. +% \begin{macrocode} +\newtoks\verso@pages + \if@verso@notesonleft + \verso@pages={} + \else + \verso@pages={{}} + \fi +% \end{macrocode} +% Notes on a single page (which is kept track of by +% |\verso@currentpagenum| are assembled into |\verso@currentpage| +% before being transferred as a unit into |\verso@pages|. +% \begin{macrocode} +\newtoks\verso@currentpage +% \end{macrocode} +% We keep track of page numbers when invoking |\verso@note| below. We +% initialise this to be the current page number (typically~1). Doing +% this this way means that if the document sets the current page +% number using |\setcounter{page}{n}| \emph{before} loading the +% package, then we will respect that. We will get confused if the +% page number is changed at other points. +% \begin{macrocode} +\newcount\verso@currentpagenum + \verso@currentpagenum=\c@page +\newcount\verso@currentnotenum + \verso@currentnotenum=0 +\newdimen\verso@spacerskip + \verso@spacerskip=10pt +% \end{macrocode} +% Flag |\if@verso@processversonotes| is set true if there are any +% |\verso@note|s found in the aux file. If there are none -- so that +% this is still false at the first |\shipout| -- then the package +% carefully does nothing. +% \begin{macrocode} +\newif\if@verso@processversonotes + \@verso@processversonotesfalse +% \end{macrocode} +% +% We need to be able to calculate our position on the page. +% The |\verso@pagebottom| is the position of the bottom of the page, +% in the same coordinates as |\verso@currentposition|, and is +% (textheight + top margin + \dots). +% \begin{macrocode} +\newdimen\verso@pagebottom + \verso@pagebottom=1in + \advance\verso@pagebottom \topskip + \advance\verso@pagebottom \topmargin + \advance\verso@pagebottom \headheight + \advance\verso@pagebottom \headsep + \advance\verso@pagebottom \textheight +\newdimen\verso@currentposition +% \end{macrocode} +% The paper size options to class files set |\paperheight| but not (or +% not necessarily) |\pdfpageheight|. If we don't set this, then +% output vertical positions will come out incorrectly if the latter +% parameter doesn't default to the right value. The code in this package is +% specific to pdf\TeX and friends, so we don't have to conditionalise. +% \begin{macrocode} +\pdfpageheight=\paperheight +% \end{macrocode} +% +% Macro |\verso@note| is what's written to the aux file, and so is +% what we process on the next run, to discover the set of verso-notes +% we need to produce. +% \begin{macrocode} +\long\def\verso@note#1#2#3{% + \global\@verso@processversonotestrue +% \end{macrocode} +% Store the target page number. It would be nice to issue a warning if this is +% indicated to appear on the (ignored) first page; such notes are +% silently discarded below. However detecting this here requires that +% |\@tempcnta| is equal to~1 on the call of this, and we can't +% guarantee this if we support calling |\setcounter{page}{x}| before +% loading the package. +% \begin{macrocode} + \@tempcnta=#1 +% \end{macrocode} +% Gather pages from the .aux file, assembling them into a single list +% in |\verso@pages| with one group per page, each containing a +% sequence of |\\{...}| note specifications. This inserts empty +% groups for pages which have no verso-notes. +% \begin{macrocode} + \@tempdima=#2 % #2 < 0pt is the end-of-document flag + \loop + \@tempswafalse + \ifnum\@tempcnta > \verso@currentpagenum + \@tempswatrue + \fi + \ifdim\@tempdima < 0pt + \@tempswatrue + \@tempdima=0pt + \fi + \if@tempswa + \edef\@tempa{\the\verso@pages {\the\verso@currentpage}} + \global\verso@pages=\expandafter{\@tempa} + \advance\verso@currentpagenum 1 + \verso@currentpage={} + \repeat + \verso@currentpage=\expandafter{\the\verso@currentpage \\{{#1}{#2}{#3}}} +% \end{macrocode} +% The last time around, add an extra pair of tokens at the end of the +% |\verso@pages| list. These provide the arguments to +% |\verso@processonepage@| on the last time it's called. +% \begin{macrocode} + \ifdim\@tempdima=0pt + \global\verso@pages=\expandafter{\the\verso@pages {}{}} + \fi +} +% \end{macrocode} +% +% Disable |\verso@note| in time for the end-document aux reading. +% This isn't quite necessary, but it's tidy. +% \begin{macrocode} +\AtEndDocument{\long\def\verso@note#1#2#3{}} +% \end{macrocode} +% +% Test whether we want to merge the current note with the following +% one. The arguments to this macro are the contents of +% |\verso@mergenotelist|, which is assembled from the +% |\verso@mergenote| commands read from the aux file. +% \begin{macrocode} +\def\verso@testmergenote#1,#2\@nil{ + \def\@tempa{#1} + \ifx\@tempa\@empty + \@tempcnta=-1 + \else + \@tempcnta=#1 + \fi + \advance\@tempcnta -1 + \ifnum\@tempcnta=\verso@currentnotenum +% \end{macrocode} +% Set |\if@tempswa| true, set |\verso@mergenotelist| to the tail of +% the list, and ensure that this note is merged next time round. +% \begin{macrocode} + \@tempswatrue + \xdef\verso@mergenotelist{#2} + \immediate\write\@auxout{\string\verso@mergenote{#1}} + \else + \@tempswafalse + \fi +} +% \end{macrocode} +% +% The macro |\verso@setnote| is the one which does the work of +% positioning a single note on the (verso) page. We potentially work +% quite hard to avoid notes overwriting each other here. If +% |\verso@mergestate| is greater than one (by default, it's +% initialised to~2, but any number greater than~1 would be equivalent) +% then when two notes collide, we simply put a horizontal bar between +% them, and hope for the best. Each time we have a collision we +% increment this number, and if we find it has a value~1 (ie, the +% first collision) we we record in the aux file that this note should +% be merged with its predecessor, by writing a |\verso@mergenote| +% command. Next time round, when we are about to set the note +% \emph{before} that one, we avoid setting it, and instead start +% accumulating the text of such merged notes, in the token register +% |\verso@accumulatenote|. We only add a single note to the list of +% merged notes on each \LaTeX\ run, since merging one note may make it +% unnecessary to merge any more. The only way to delete the +% collections of merged notes is to delete the aux file. +% \begin{macrocode} +\newcount\verso@mergestate + \verso@resetmergestate +\newtoks\verso@accumulatenote + \verso@accumulatenote={} +% \end{macrocode} +% +% Set the note. The arguments are (1) the page number, (2) the +% position on the page, and (3) the text of the note. +% \begin{macrocode} +\long\def\verso@setnote#1#2#3{ + \@tempdima=\paperheight \advance\@tempdima -#2 + % target position; will become skip to add + \@tempdimb=\verso@currentposition + \global\advance\verso@currentnotenum 1 +% \end{macrocode} +% Check whether we're to merge the following note into this one. +% \begin{macrocode} + \expandafter\verso@testmergenote\verso@mergenotelist,\@nil + \if@tempswa + \verso@accumulatenote=\expandafter{% + \the\verso@accumulatenote + #3% + \quad\P~\nobreak} + %\PackageInfo{versonotes}{\typeout{Merging notes on p#1}} + \else +% \end{macrocode} +% No merging: set the current note, possibly along with any +% accumulated notes, in a box, and then start to work out where to put it. +% \begin{macrocode} + \setbox0=\vtop{ + \versolayout + \the\verso@accumulatenote + #3\par} + \verso@accumulatenote={} +% \end{macrocode} +% We now know how big this box is (|\ht0|), how far down the page the +% accumulated boxes are (|\verso@currentposition|), and where the box +% is to appear, as an offset from the top of the paper (|\@tempdima|). +% Work how how much of a skip we have to add to get there. +% \begin{macrocode} + \ifdim\@tempdima>\verso@currentposition +% \end{macrocode} +% The target position is below the current position; add a skip. This +% is the straightforward case, and leaves the skip in |\@tempdima| and +% the new current position in |\@tempdimb|. +% \begin{macrocode} + \advance\@tempdima -\verso@currentposition + \advance\@tempdima -\ht0 + \advance\@tempdimb \@tempdima + \verso@resetmergestate + \else +% \end{macrocode} +% Or the target position is above the current position; +% thus the new material will have to be moved down +% (I'm not sure how best to handle the case where this skip results +% in the note hitting the bottom of the page. Should it be merged +% with that case below, somehow?). +% \begin{macrocode} + % note: \PackageWarning seems to produce odd effects here + \message{^^Jversonotes warning: Moved versonote text on p.#1.} + \advance\verso@mergestate 1 + \ifnum\verso@mergestate=1 + \immediate\write\@auxout{\string\verso@mergenote{\the\verso@currentnotenum}} + \fi + \nointerlineskip + \vbox to \verso@spacerskip{\vfil + \hbox to \hsize{\hfil\vrule height 0.4pt width 10em} + \vfil} + \advance\@tempdimb \verso@spacerskip + \@tempdima=0pt % ...so don't add any more skip + \fi + \advance\@tempdimb \ht0 + \advance\@tempdimb \dp0 +% \end{macrocode} +% Parameter |\@tempdimb| now holds the expected new value of +% |\verso@currentposition|. Check to see if this would be beyond the bottom +% of the page, and move the text upwards in that case. +% \begin{macrocode} + \ifdim\@tempdimb>\verso@pagebottom + \message{^^Jversonotes warning: versonote text on p.#1 hit bottom of page.} + \advance\@tempdima -\@tempdimb + \advance\@tempdima \verso@pagebottom + \@tempdimb=\verso@pagebottom + \fi +% \end{macrocode} +% Finally, do the skip and add the box. +% \begin{macrocode} + \vskip\@tempdima + \verso@currentposition=\@tempdimb + \nointerlineskip + \box0 + \fi +} +% \end{macrocode} +% +% Log notes where a previous run has discovered they collide. +% \begin{macrocode} +\def\verso@mergenotelist{} +\def\verso@mergenote#1{\xdef\verso@mergenotelist{\verso@mergenotelist #1,}} +% \end{macrocode} +% +% Save the original definition of |\shipout|, prior to our +% redefining it below. We could do this perfectly well using the +% \Lpackage{everyshi} package, but we do it by hand in order to avoid +% the extra dependency. +% \begin{macrocode} +\let\verso@orig@shipout\shipout +% \end{macrocode} +% +% +% Process a single page of verso-notes. +% +% Macro |\verso@processonepage@| consumes the next group in the +% |\verso@pages| list. This is invoked as part of the redefined +% |\shipout|. +% +% Macro |\verso@processonepage| is called just before each shipout of +% a `real' page. If we are putting notes on verso pages (the usual +% case), then \emph{before} we ship out page~2, we must construct and +% ship out the notes for page~2; we will have a set of notes for +% page~1 (which we expect will be empty), which we must discard; we +% achieve this by simply doing nothing if the current page is page~1. +% If, on the other hand, we are putting notes on recto pages, then we +% must instead ship out the notes for page~2 just before shipping out page~3, and +% must similarly do nothing on pages~1 and~2. This works because +% in the latter case we have initialised |\verso@pages| with an extra +% leading |{{}}|. +% +% We manage the page skipping by decrementing a `pages to skip' +% counter. The more obvious way of doing that it to test the value of +% |\@tempcnta|, which at this point holds the current page number, but +% this produces the wrong answer if the document starts at other than +% page~1 (perhaps because the document includes |\setcounter{page}{99}|). +% \begin{macrocode} +\newcount\verso@pagestoskip +\if@verso@notesonleft + \verso@pagestoskip=1 +\else + \verso@pagestoskip=2 +\fi +% \end{macrocode} +% +% Define |\verso@processonepage@|. +% \begin{macrocode} +\long\def\verso@processonepage@#1#2\@nil{ + \def\@tempa{#1} +% \end{macrocode} +% Do nothing on page~1 (in the verso case), or pages~1 and~2 (in the +% recto case). Any notes which would appear in such a position are +% silently discarded. +% \begin{macrocode} + \ifnum\verso@pagestoskip=0 +% \end{macrocode} +% If there are no verso-notes on this page, then ship out an empty box. +% \begin{macrocode} + \ifx\@tempa\@empty + \verso@orig@shipout\vbox{} +% \end{macrocode} +% This is the normal case: +% create a box containing the |\\{...}| contents of the +% current page's verso-notes +% \begin{macrocode} + \else + \begingroup +% \end{macrocode} +% Within the box we're about to create, we have to `un-protect' things. +% Robust commands (including for example |\emph| and other font-changing commands) are +% defined using |\protect|, which in the present context, inside +% |\@outputpage|, is |\let| equal to |\noexpand|. +% We must set this to be simply |\relax|, +% as it is in the normal run of text elsewhere +% (see \url{http://tex.stackexchange.com/questions/206673/}). +% \begin{macrocode} + \let\protect\relax +% \end{macrocode} +% Set everything up for setting the verso page. +% \begin{macrocode} + \hoffset=-1in \advance\hoffset \versoleftmargin + \voffset=-1in + \hsize=\versotextwidth + \topskip=0pt + \topmargin=0pt + \headheight=0pt + \headsep=0pt + \vsize=1000mm % larger than any actual note + \normalfont\normalsize +% \end{macrocode} +% Construct and ship out the page contents. +% \begin{macrocode} + \long\def\\##1{\verso@setnote ##1} + \verso@orig@shipout\vbox{ + \verso@currentposition=0pt + #1} + \endgroup + \fi + \else +% \end{macrocode} +% The |\verso@pagestoskip| counter is still positive, so decrement it. +% \begin{macrocode} + \global\advance\verso@pagestoskip -1 + \fi +% \end{macrocode} +% Finally, set |\verso@pages| to the tail of the list. +% \begin{macrocode} + \global\verso@pages={#2}} +% \end{macrocode} +% +% The command |\verso@processonepage| is invoked once on every |\shipout|. +% \begin{macrocode} +\def\verso@processonepage{ + \if@verso@processversonotes + \expandafter\verso@processonepage@\the\verso@pages\@nil + \else +% \end{macrocode} +% We don't seem to have any versonotes in this file, so don't come here again. +% \begin{macrocode} + \global\let\verso@processonepage\relax + \fi +} +% \end{macrocode} +% +% And finally, redefine |\shipout|. +% \begin{macrocode} +\def\shipout{\verso@processonepage \verso@orig@shipout} +% \end{macrocode} +% +% \begin{macrocode} +%</package> +% \end{macrocode} +% \Finale +% +\endinput |