% This is CHBARS.DOC as of 09 Oct 89 %--------------------------------------------------------- % (c) 1989 by J.Schrod. copy conditions see below. % % Macro package for creating changebars in Plain TeX % MAKEPROG is needed % % documented in LaTeX (for Anne and Chris) % % VERSION HISTORY (MSCF -- most significant change first) % % DATE PERSON REMARK % 89-10-09 -js converted to LaTeX (progltx) % 89-09-25 -js repaired \mark processing in horizontal mode % 89-08 -js first version (for EuroTeX89 in Karlsruhe) % % author's current address: % % Detig$\,\cdot\,$Schrod \TeX{}sys % Joachim Schrod % Kranichweg 1 % % D-6074 R\"odermark-Urberach % FR Germany % % Tel. (+6074) 1617 % Bitnet: XITIJSCH@DDATHD21 % should be progtex... %%%% %%%% %%%% These TeX macros were documented with the documentation system %%%% MAKEPROG and automatically converted to the current form. %%%% If you have MAKEPROG available you may transform it back to %%%% the original input: Remove every occurence of three percents %%%% and one optional blank from the beginning of a line and remove %%%% every line which starts with four percents. The following lex %%%% program will do this: %%%% %%%% %% %%%% %%%% ^%%%\ ? ; %%%% ^%%%%.*\n ; %%%% %%%% MAKEPROG may be obtained over the net from the Bitnet-Listserver %%%% LISTSERV@DHDURZ1 (filelist WEBWARE), from tuglib@science.utah.edu, %%%% or via ftp from june.cs.washington.edu. %%%% %%%% %%% \documentstyle[progltx,a4-9]{article} %%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% % %%% % local macros %%% % %%% \let\mc=\small % for names like GNU %%% \def\PS{{\sc PostScript}} %%% \def\DVI{{\tt DVI}} %%% \def\GNU{{\mc GNU}} %%% \chardef\bs=`\\ %%% % %%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% \begin{document} %%% \title{Changebars without {\tt \bs{}special}'s} %%% \author{\sc Joachim Schrod} %%% \maketitle %%% \begin{abstract} %%% It is common practice to use vertical bars in the margins of a %%% document to mark pieces of text which have changed since the last %%% version(s) of this document. Such vertical bars are usually %%% called {\em changebars}. It has often been said that it is %%% impossible to produce changebars with \TeX{} without the usage of %%% |\special| commands (driver directives), which extend the %%% primitives of \TeX{}. This paper presents a \TeX{} macro file %%% which implements changebars without such a usage. The macro file %%% is written for the usage with Plain~\TeX{} but the implementation %%% strategy can be used with \LaTeX{}, too. %%% \end{abstract} %%% \chap Introduction. %%% Changebars are used to mark modified parts in existing documents. For %%% the usage in \TeX{} documents, there exist only solutions that use %%% driver/printer features by the way of inserting |\special| commands in %%% the \TeX{} source, e.g.\ for \PS{} drivers. This results in documents %%% that are no longer as freely interchangeable as the \DVI{} concept would %%% allow---device dependency is problematic especially for this application %%% that is useful for multi-authoring or standards development. %%% This macro package offers a pure \TeX{} solution. Nevertheless, it has %%% its restrictions, too. The page break will no longer be optimal, %%% because no strechability or shrinkability on a page on top of the last %%% mark of a change is supported. But this seems to be acceptable, %%% especially as the change bar feature often will be used for proof %%% reading and not in the final document. This restriction is the reason %%% why no change marks can be used on titlepages or for similar %%% constructions. Changes in floating insertions (footnotes, figures) are %%% not discovered. %%% The demonstrated solution is written in Plain \TeX{}, because it was %%% easier and could be presented better at the Euro\TeX89 conference in %%% Karlsruhe. An adaption to \LaTeX{} is possible, but would require %%% modifications to the \LaTeX{} kernel, i.e.\ the output routine and the %%% layout parameters of the standard styles. %%% \sect This program is free software; you can redistribute it and/or %%% modify it under the terms of the \GNU{} General Public License as %%% published by the Free Software Foundation; either version~1, or (at your %%% option) any later version. %%% This program is distributed in the hope that it will be useful, but %%% {\bf without any warranty\/}; without even the implied warranty of %%% {\bf merchantability\/} or {\bf fitness for a particular purpose}. See %%% the \GNU{} General Public License for more details. %%% %You should have received a copy of the \GNU{} General Public License %%% %along with this program; if not, write to the Free Software Foundation, %%% If you have not received a copy of the \GNU{} General Public License %%% along with this program, write to the Free Software Foundation, %%% Inc., 675~Mass Ave, Cambridge, MA~02139, USA. %%% \sect A changed area is described by two marks, |\beginchange| and %%% |\endchange|. The method for writing a change bar consists of three %%% parts: First, it will be indicated to the output routine that a change %%% has occured. Next, the position of the changed area will be found out %%% and fixed. A list of all changed areas is built, that can at last be %%% marked by the output routine using vertical rules at the right margin. %%% But before we start we declare some shorthands for category codes. By %%% declaring the at sign~(`|@|') as well as the underscore~`(|_|)' as %%% letters we can use them in our macros. (I agree with D.~Knuth that %%% |\identifier_several_words_long| is more readable than %%% |\IdentifierSeveralWordsLong| and in every case better than |\p@@@s|.) %%% With the at sign we can use the ``private'' Plain macros and with the %%% underscore we can make our own macros more readable. But as we have to %%% restore these category codes at the end of this macro file we store %%% their former values in the control sequences |\atcode| and |\uscode|. %%% This method is better than to use a group because not all macros have to %%% be defined global this way. %%% \beginprog \chardef\letter=11 \chardef\atcode=\catcode`\@ \chardef\uscode=\catcode`\_ \catcode`\@=\letter \catcode`\_=\letter %%% \endprog %%% \chap Triggering the Output Routine. %%% \begin{sloppypar} %%% First, the output routine must know that changes occured. This is done %%% by the macros |\beginchange| and |\endchange| that append a reserved %%% penalty value |\break_penalty| to the current list that is below %%% $-10\,000$. The penalty will be in the range $|\change_penalty_group| %%% \cdot 100 - 99 \ldots |\change_penalty_group| \cdot 100$. %%% For the output it must be differentiated if the mark indicates %%% begin or end of the changed area. This is done with the values %%% |\change_penalty_begin| and |\change_penalty_end| that are used as the %%% second-to-last digit of the change penalty. %%% \end{sloppypar} %%% It must also be differentiated between the beginning of a mark being in %%% horizontal or vertical mode. In horizontal mode, the change bar must %%% not begin at the baseline of the actual text position, but on top of the %%% actual line. This is marked in the last digit, an odd digit will be %%% used in horizontal mode. %%% Note that the values mentioned above are used as digits here that can be %%% concatenated. If they are not followed by an other digit they should be %%% terminated by |\space| to stop the look-ahead for digits. %%% These interactions with the output routine adds a lot of dead cycles; we %%% must therefore increase |\maxdeadcycles| to prevent \TeX{} from grumbling. %%% \beginprog \def\change_penalty_group{-101} \def\change_penalty_begin{0} \def\change_penalty_end{1} \maxdeadcycles=100 %%% \endprog %%% \sect The marks set the change penalty value (including the indication %%% of begin or end of mark). The rest (|\trigger_output|) is the same action %%% for both. An end change mark in horizontal mode can be preceeded by %%% glue that could cause a line break, thus including the following line to %%% the change area as well. To avoid this unwanted behaviour, the space is %%% saved in |\save_space|, discarded in front of the mark and restored %%% afterwards. %%% \beginprog \newcount\break_penalty \newskip\save_space \def\beginchange{% \break_penalty=\change_penalty_group\change_penalty_begin0 \trigger_output } \def\endchange{% \ifhmode \save_space=\lastskip \unskip \fi \break_penalty=\change_penalty_group\change_penalty_end0 \trigger_output \ifhmode \hskip\save_space \fi } %%% \endprog %%% \sect In |\trigger_output| the real output trigger is done. We must consider %%% that an |\output|-invocation by our |\break_penalty| can be discarded at %%% the beginning of a page. So we trigger the output routine twice, first %%% with a special penalty value that is 2~less than the correct value %%% (including the code for horizontal or vertical mode). After the first %%% page break it is asserted that the current list is empty. The output %%% routine has to save the former page contents if necessary. %%% Now we set the penalty to the correct value. The second page break does %%% the real work, restores the page contents and handles the split %%% insertions (footnotes, figures,~\dots) %%% In horizontal mode |\spacefactor| must not be destroyed. %%% \beginprog \newcount\save_space_factor \def\trigger_output{% \ifinner \errmessage{Change cannot be marked inside a box}% \else \ifvmode \let\do_in_vmode=\relax \advance \break_penalty by -2 \else \save_space_factor=\spacefactor \let\do_in_vmode=\vadjust \advance \break_penalty by -3 \fi \do_in_vmode{% \penalty\break_penalty \null \advance \break_penalty by 2 \penalty\break_penalty }% \ifhmode \spacefactor\save_space_factor \fi \fi } %%% \endprog %%% \sect The usage of the output routine as an information passing system %%% has always it's difficulties. One of the real hard parts is the %%% handling of the page marks, special token lists that are handled by %%% \TeX{}. These token lists provide informations about text on a page, %%% they are usually used to create head lines etc. The user may access %%% these token lists with three control sequences: |\botmark| is the %%% last page mark given, |\topmark| is the |\botmark| of the previous %%% page, and |\firstmark| is the first page mark on the actual page or %%% |\topmark| if none was given. Here ``page'' is used in the \TeX{} %%% sense, i.e.\ as the material which has been collected between two %%% |\output| invocations. Of course, the page marks must not be %%% destroyed---and that means they must be reinserted after each %%% special use of the output routine. %%% But we have luck: A ``special use'' consists of two |\output| %%% invocations, so we can insert |\topmark| again as a page mark after %%% the first invocation where it will be the only page mark on that %%% \TeX{} page. The second invocation will automatically transform this %%% page mark into the ``last page mark on the previous page,'' i.e.\ in %%% |\topmark|---that's what we need! Furthermore |\firstmark| and %%% |\botmark| are saved in control sequences during the first invocation, %%% they will be inserted again, too. %%% There's one situation where this approach doesn't work: In front of %%% the first page mark |\topmark|, |\firstmark|, and |\botmark| expand %%% to an empty token list. If we save them then and insert their old %%% values we have inserted empty page marks. If other page marks follow %%% on the same ``real'' page |\firstmark| will be empty instead of %%% expanding to the token list of the first page mark. To prevent this %%% we must not save and restore page marks before the first |\mark| has %%% been added to the main vertical list. %%% Well, that can be controlled with a switch---but this switch must be %%% set very carefully. If it is set immediately by the first |\mark| %%% this may be in horizontal mode and special output invocations can %%% occur above this page mark (i.e., there may be a |\beginchange| in the %%% same paragraph in front of the |\mark|). Therefore the setting of the %%% switch must be delayed until the vertical position of the |\mark| %%% (precisely: the position of the |\mark| in the current list) is %%% reached. In horizontal mode this can be done with a |\vadjust| and %%% the output routine! Voil\`a, this is another command group for the %%% output routine with only one command. %%% \beginprog \newif\if@save_mark@ \@save_mark@false \def\mark_penalty_group{-102} %%% \endprog %%% \sect We will redefine |\mark| so that the first page mark either sets %%% the switch to true (in vertical mode all possible special page breaks %%% are already handled) or forces the |\output| routine to do this at an %%% appropriate place. In the last case we can use |\trigger_output| %%% again. Afterwards we restore the original meaning of |\mark| again to %%% reduce the processing overhead (and the dead cycles). %%% This change of |\mark| has the consequence that the first |\mark| in a %%% document cannot be used anymore in horizontal mode inside a vertical %%% box that shall be split afterwards. But this is only sensible if this %%% mark shall be used as |\splitfirstmark| because it will almost never %%% migrate to the outer list---really a rare case! %%% \beginprog \let\@@mark=\mark \def\mark{% \ifvmode \ifinner \else \global\@save_mark@true \fi % split marks! \else \break_penalty=\mark_penalty_group00 % this will corrupt \vsplit \trigger_output \fi \global\let\mark=\@@mark \@@mark } %%% \endprog %%% \sect %%% \begin{sloppypar} %%% If the output routine is triggered with the mark penalty value %%% it will call |\start_saving_page_marks|. %%% \end{sloppypar} %%% \beginprog \def\start_saving_page_marks{% % this may be executed twice \unvbox255 \global\@save_mark@true } %%% \endprog %%% \sect To finish the treatment of page marks we can formulate the two %%% macros which are used at the first resp.\ second invocation of a %%% ``special output,'' the principles have already been explained. %%% \beginprog \def\backup_page_marks{% \if@save_mark@ \mark{\topmark}% \xdef\save_firstmark{\firstmark}% \xdef\save_botmark{\botmark}% \fi } \def\restore_page_marks{% \if@save_mark@ \mark{\save_firstmark}\mark{\save_botmark}% \fi } %%% \endprog %%% \chap Positioning the Change Marks. %%% Now we must handle the positions of the bars. |\change_pos| will hold %%% the position of the actual mark, i.e.\ the distance between top of page %%% and actual mark. |\top_change_pos| will hold the beginning of a changed %%% area; a value of |\maxdimen| indicates that no change is in effect. If %%% a changed area is completed, it is appended to the list |\bar_list| as %%% an element |\bar(\top_change_pos,\change_pos)|. This list contains all %%% changed areas within the current page so that bars can be written later %%% on. A single bar will be produced by |\write_bar|. %%% \noindent The definition of |\bar| to |\relax| allows the concatenation %%% of new elements to |\bar_list| with |\xdef|. %%% \beginprog \newdimen\change_pos \newdimen\top_change_pos \top_change_pos=\maxdimen \let\bar_list=\empty \let\bar=\relax \def\write_bar(#1,#2){% \setbox0=\hbox{\vrule height -#1 depth #2}% \dp0=0pt \ht0=0pt \box0 } %%% \endprog %%% \sect If the output routine was activated by a |\outputpenalty| value %%% within the range of our reserved penalties, the change handling will %%% occur, otherwise standard plain output can be done. %%% \beginprog \newcount\penalty_group \output={% \boxmaxdepth=\maxdepth \penalty_group=\outputpenalty \divide \penalty_group by 100 \ifnum \penalty_group=\change_penalty_group\space \change_handling \else \ifnum \penalty_group=\mark_penalty_group\space \start_saving_page_marks \else \plainoutput \fi \fi } %%% \endprog %%% \sect As explained before, the change handling must differentiate %%% between the kind of the change command (beginning is indicated by %%% $|\change_cmd|=0$, end by~1) and between the mode (horizontal indicated %%% by an odd |\change_mode| value, vertical by an even). A change mode %%% higher than one indicates that we are doing the first page break that %%% has to backup the page as far as it exists already and results in an %%% empty current list of page elements. %%% \beginprog \newcount\change_cmd \newcount\change_mode \def\change_handling{% \change_cmd=-\outputpenalty % ==> absolute value \advance \change_cmd by \change_penalty_group00 % subtraction \change_mode=\change_cmd \divide \change_cmd by 10 % second-to-last digit \advance \change_mode by -\number\change_cmd0 % last digit \ifnum \change_mode>1 \backup_page \else \ifcase \change_cmd \begin_change \or \end_change \else \errmessage{Invalid changepenalty}% \fi \fi } %%% \endprog %%% \sect Processing a mark during the second trigger of the output routine %%% means restoring the page and storing the positions. At the beginning, %%% the begin of the change is saved, at the end, we know the bar already %%% and put it into the bar list. Then the positioning values are %%% reinitialized. %%% As within every output invocation, the box 255 must be unboxed. As we %%% are here in the second invocation of the output routine the |\box255| %%% consists only of the empty |\vbox| we have inserted in %%% |\trigger_output|. We can therefore throw it away. %%% \beginprog \def\begin_change{% \restore_page \setbox0=\box255 \ifdim \top_change_pos=\maxdimen \global\top_change_pos=\change_pos \global\change_pos=0pt \else \errmessage{Nested change bars are not supported}% \fi } \def\end_change{% \restore_page \setbox0=\box255 \ifdim \top_change_pos=\maxdimen \errmessage{No change is in effect}% \else \xdef\bar_list{\bar_list \bar(\the\top_change_pos,\the\change_pos)}% \global\top_change_pos=\maxdimen \global\change_pos=0pt \fi } %%% \endprog %%% \chap Handling the Page Contents. %%% We handle the part of the page that was collected up to now by putting %%% it into a box. This fixes the position of the change mark so that %%% |\change_pos| can be set and stored later on in |\top_change_pos| or as %%% the lower end of a bar in |\bar_list|. %%% To save the page contents in the first output invocation, we have to %%% save the page in |\save_page|. Before that, we store the size of the %%% box (which equals |\pagegoal|!)\ in |\page_goal|. If the unboxing %%% caused an increase of height (i.e.\ if $|\pagetotal|>|\pagegoal|$), %%% we eject the page up to the change mark. Now we have to compute the %%% current position of our mark in |\change_pos|. It is fixed by the size %%% of the |\save_page|, but in case of a begin mark in horizontal mode we %%% must decrease it from the baseline position to the top of the last line. %%% Finally, we must save the values for the allowed insertions and change %%% them to the maximal value so that a rest that is split from an insertion %%% will be appended to the insertion box at the second invocation in every %%% case. %%% The |\vsize| is initialized to |\maxdimen|. This allows to control %%% whether this first output invocation ocurred or if it was discarded. %%% For the same reason |\change_pos| is initialized to~0pt. %%% \beginprog \newbox\save_page \newdimen\page_goal \newdimen\save_vsize \save_vsize=\maxdimen \newdimen\save_dimen_topins \newdimen\save_dimen_footins \def\backup_page{% \global\page_goal=\ht255 \global\setbox\save_page=\vbox{\unvbox255}% \ifdim \ht\save_page>\page_goal \eject_page_so_far \fi \change_pos=\ht\save_page \global\advance \change_pos by \dp\save_page \ifnum \change_cmd=\change_penalty_begin\space \ifodd \change_mode \higher_change_pos \fi \fi \global\save_vsize=\vsize \global\vsize=\maxdimen \global\save_dimen_topins=\dimen\topins \global\dimen\topins=\maxdimen \global\save_dimen_footins=\dimen\footins \global\dimen\footins=\maxdimen \backup_page_marks } \change_pos=0pt %%% \endprog %%% \sect To eject a page as far as it is we restore it from the %%% |\save_page| back to box~255. In horizontal mode and at a begin mark %%% the last line contains the mark and must not be output. So we remove it %%% and the preceding glue from the stored rest, just leaving a single hbox %%% to be on top of the actual page (in |\save_page|) now. Then normal %%% output can be done with box~255. %%% \beginprog \def\eject_page_so_far{% \begingroup \vbadness=20000 % don't complain about underfull vboxes \global\setbox255=\vbox to \page_goal{% \unvbox\save_page \ifnum \change_cmd=\change_penalty_begin\space \ifodd \change_mode \global\setbox\save_page=\lastbox \unskip \fi \fi }% \endgroup \plainoutput } %%% \endprog %%% \sect %%% \begin{sloppypar} %%% In horizontal mode and at a begin mark, we need the position of %%% the mark (|\change_pos|) on the upper boundary of the last line in %%% |\save_page|. If there is just one line left from a recent eject, the %%% height is given by the topskip decreased by the height of this hbox. If %%% the height of the box is larger than |\topskip| the skip will not be %%% inserted and the change position results to~0pt. Otherwise, %%% |\save_page| is a vbox whose last hbox we delete temporarily using %%% box~0. Height and depth of the rest are the actual position on the page. %%% \end{sloppypar} %%% The double of the page we have constructed this way will immediately be %%% fed back to the garbage collector because it could have become %%% reasonably large. %%% \beginprog \def\higher_change_pos{% \ifhbox \save_page % rest of page from \eject_page_so_far \change_pos=\topskip \global\advance \change_pos by -\ht\save_page \ifdim \change_pos<0pt \global\change_pos=0pt \fi \else \setbox0=\vbox{% \unvcopy\save_page \setbox0=\lastbox % delete last line }% \change_pos=\ht0 \global\advance \change_pos by \dp0 \setbox0=\box\voidb@x \fi } %%% \endprog %%% \sect To restore a page during the second output invocation, we first %%% restore the saved values, but only if they were really changed (this can %%% be discovered by the value of |\save_vsize|). Now the |\save_page| is %%% appended to the current list as a box, which stops later usage of its %%% stretch- and shrinkability! Then the collected insertions can be %%% inserted again. The page marks have to be inserted, too. %%% \beginprog \def\restore_page{% \ifdim \save_vsize=\maxdimen \else \global\vsize=\save_vsize \global\dimen\topins=\save_dimen_topins \global\dimen\footins=\save_dimen_footins \global\save_vsize=\maxdimen \restore_page_marks \fi \box\save_page % discards stretch- and shrinkability! \ifvoid \topins \else \insert\topins{\floatingpenalty=0 \unvbox\topins}% \fi \ifvoid \footins \else \insert\footins{\floatingpenalty=20000 \unvbox\footins}% \fi } %%% \endprog %%% \sect {\em Please note, that there is still a problem with this concept %%% of handling the output trigger:} %%% \bigskip %%% If the first output trigger is discarded because a page break has occured %%% just in front, footnote parts may be juggled around. I.e., if a %%% footnote is split in three parts, the first part was just been shipped %%% out, the second part is inserted back into the recent contributions by %%% the output routine but {\em behind\/} the third part which is saved in %%% the ``special place'' (according to the \TeX{}book, p.~125). A solution to %%% this problem might be to insert a |\do_change| again within the second %%% output trigger and finishing the treatmend afterwards. Afterwards a %%% full triggering process (two output invocations) is executed again and %%% alle insertion parts will be accessible in the insertion box. %%% By the way, the almost same problem appears in \LaTeX{}, too. Almost: in %%% \LaTeX{} this can happen every time because at the first output invocation %%% the |\dimen|-values of the footnote insertion is not increased. I leave %%% the problem open to the reader\,\dots %%% \chap Writing the Stuff. %%% The positions of the bars which mark the changed areas are relative to %%% the top of the text, i.e.\ the height of the top insertion is not %%% included. Therefore it is best to write them just after the top %%% insertions before the page text---but to do this we have to change the %%% Plain macro |\pagecontents|. %%% Below is the new definition, I have just rearranged it a little bit so %%% that it is more legible. The line I have inserted is marked with %%% `|%%%%|'. |\insert_current_bar| inserts a last element in |\bar_list| %%% if a changed area is not yet finished, afterwards all bars can be %%% written. %%% \beginprog \def\pagecontents{% \ifvoid \topins \else \unvbox\topins \fi \insert_current_bar \write_all_bars %%%% \dimen@=\dp\@cclv \unvbox\@cclv % open up \box255 \ifvoid \footins \else % footnote info is present \vskip\skip\footins \footnoterule \unvbox\footins \fi \ifr@ggedbottom \kern-\dimen@ \vfil \fi } %%% \endprog %%% \sect If $|\top_change_pos|=|\maxdimen|$ no change is active. Otherwise %%% the current change reaches from the begin mark (|\top_change_pos|) to %%% the end of the page, i.e.\ we insert a virtual end mark. Because the %%% change continues on the next page we insert a virtual begin mark on the %%% top of the page, too. %%% \beginprog \def\insert_current_bar{% \ifdim \top_change_pos=\maxdimen \else \change_pos=\ht255 \advance\change_pos by \dp255 \xdef\bar_list{\bar_list \bar(\the\top_change_pos,\the\change_pos)}% \global\top_change_pos=0pt \fi } %%% \endprog %%% \sect Now we can write all bars---if they exist anyway. It's rather %%% easy, we just have to define |\bar| to |\write_bar| and execute %%% |\bar_list|. The resulting output must not use vertical place. We must %%% not forget to delete the list, or we will get the same bars on the next %%% page again. %%% \noindent |\BarDistance| is the amount of place between the text margin %%% and the change bars. %%% \beginprog \newbox\@bars \newdimen\BarDistance \BarDistance=2cc \def\write_all_bars{% \ifx \bar_list\empty \else % changes exist \setbox\@bars=\hbox to \hsize{% \hskip\hsize \hskip\BarDistance \vbox to 0pt{\offinterlineskip \let\bar=\write_bar \bar_list }% \hss }% \ht\@bars=0pt \dp\@bars=0pt \box\@bars \global\let\bar_list=\empty \fi } %%% \endprog %%% \sect We finish the macro file so that garbage (e.g.\ of exchanges %%% between systems) can come afterwards. %%% \beginprog \catcode`\@=\atcode \catcode`\_=\uscode \endinput %%% \endprog %%% \end{document}