Sazba odstavců do textových oblastí – zdrojový kód, verze 3 Jan Šustek
Program řeší sazbu textu do textových oblastí v TEXu. Pokud mají textové oblasti různou šířku a pokud se v textu vyskytují pružné vertikální mezery, není triviální tento problém řešit.
0. Použití programu 0.1. plain Použití v plainu je následující. \input oblasti3 \threadshape ... \addonepar Text odstavce \addonepar Text odstavce \bye
0.2. LATEX Použití v LATEXu je následující. \documentclass ... ... \input oblasti3 \begin{document} ... \threadshape ... \addonepar Text odstavce \addonepar Text odstavce \forceoutput ... \end{document}
1
1. Úvodní makra 1.1. Definice zkratek Pro často používané konstrukce jsou nadefinovány tyto zkratky. \let\ea\expandafter \def\name#1{\csname#1\endcsname} 3 \def\namedef#1{\ea\def\csname#1\endcsname} 1 2
1.2. Alokace registrů Čtyři čítače pro obecné výpočty není nutné speciálně pojmenovávat. \newcount\tmpcounta \newcount\tmpcountb 6 \newcount\tmpcountc 7 \newcount\tmpcountd 4 5
V čítači \threadpart je uloženo číslo aktuálně sázené oblasti. Počet všech oblastí je uložen v čítači \threadparts. 8 9
\newcount\threadpart \newcount\threadparts
Čítače mající v názvu SPpass se používají při počítání průchodů makra \splitparagraph. Čítač \SPpass označuje číslo aktuálního průchodu. Čítač \maxSPpasses označuje maximální počet průchodů. Po počtu průchodů daném čítačem \softSPpasses se makrem \setsoftpenalties sníží některé penalty. 10 11 12
\newcount\SPpass \newcount\maxSPpasses \maxSPpasses16 \newcount\softSPpasses \softSPpasses8
Pro správné vertikální mezery mezi odstavci je hodnota \prevdepth pro předchozí sázený odstavec uložena do registru \prevprevdepth. 13
\newdimen\prevprevdepth
Následující boxy se používají při práci. Ve vboxu \region je uložena aktuáně sázená oblast. Vbox \pagebox obsahuje všechny dosud vysázené oblasti na stránce. Vboxy \beginbox a \endbox používají při lámání odstavce do oblastí. 14 15 16 17
\newbox\region \newbox\pagebox \newbox\beginbox \newbox\endbox
1.3. Test formátu Program funguje ve formátech plain a LATEX. Některá makra se v jednotlivých formátech liší. Ke zjištění, v jakém formátu je program spuštěn, se používá následující test.
2
18
\def\ifplain{\ifx\documentclass\undefined}
1.4. Informace o průběhu TEX může vypisovat o průběhu programu. Tyto informace může vypisovat na terminál nebo do log souboru. Podle toho se nastaví jeden z následujících řádků. 19 20 21
\def\showinfo#1{} % no info \let\showinfo\wlog % info > log \let\showinfo\message % info > terminal
2. Nastavení vlákna Všechny textové oblasti jako celek budu nazývat vlákno. Tvar vlákna se nastaví makrem \threadshape, které má syntaxi podobnou primitivu \parshape. Po použití \threadshape n x1 y1 w1 h1 . . . xn yn wn hn se bude vlákno skládat z n oblastí a i-tá oblast bude začínat na pozici (xi , yi ) a bude mít šířku wi a výšku hi . Pozice (0 cm, 0 cm) odpovídá v plainu levému hornímu rohu stránky a v LATEXu levému hornímu rohu vboxu vkládaného na stranu. 22 23 24
\def\threadshape#1 {\threadparts#1 \threadpart0 \readthreadshape}
Tvar jedné oblasti se načte pomocným makrem \readthreadshape. Jednotlivé souřadnice se uloží do maker \name{tsxi}, \name{tsyi}, \name{tswi} a \name{tshi}, kde i je číslo oblasti. 25 26 27 28 29 30 31 32 33 34 35
\def\readthreadshape#1 #2 #3 #4 {\advance\threadpart1 \namedef{tsx\the\threadpart}{#1} \namedef{tsy\the\threadpart}{#2} \namedef{tsw\the\threadpart}{#3} \namedef{tsh\the\threadpart}{#4} \ifnum\threadpart<\threadparts \ea\readthreadshape \else \threadpart1 \prevprevdepth0pt \fi}
3
3. Sazba odstavce 3.1. Rozhraní Sazba odstavce do aktuální oblasti je řešena makrem \addonepar.1 Makro vloží text odstavce pro další použití do makra \paragraphtext. Dále opakovaně provede makro \splitparagraph. Po jeho ukončení odstavec naostro vysází s poslední hodnotou \linesinregions. 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
\def\addonepar#1\par{ \def\paragraphtext{#1\unskip} \sejmiprvnislovo#1 \par \hsize\name{tsw\the\threadpart} \initLIR \getnormalpenalties \SPpass0 \splitparagraph \setbox\endbox=\vtop{\unvcopy\region \prevdepth\prevprevdepth \theparshape\paragraphtext\par \global\prevprevdepth\prevdepth} \loop \setbox\region=\vsplit\endbox to\name{tsh\the\threadpart} \ifdim\ht\endbox>0pt \addtopagebox \repeat \setnormalpenalties}
Pokud je třeba vložit jiný vertikální materiál, například vertikální mezeru, použije se makro \addtoregion. 54
\def\addtoregion#1{\setbox\region=\vbox{\unvbox\region#1}}
3.2. Zlom do oblastí Jádrem makra \addonepar je makro \splitparagraph. Toto makro se vykonává opakovaně, dokud se hodnota \linesinregions neustálí. Pokud se tak nestane během \maxSPpasses průchodů, vysází se odstavec podle poslední hodnoty \linesinregions, což bude pravděpodobně špatně. Po \softSPpasses průchodech se makrem \setsoftpenalties sníží hodnoty některých penalt. Během jednoho průchodu makro \splitparagraph pokusně vysází odstavec při vypočtené hodnotě \linesinregions a \parshape a naláme jej do jednotlivých oblastí, počínaje (již částečně zaplněnou) aktuální oblastí. Na základě toho 1 V tuto chvíli je program implementován tak, že se sekvence \addonepar musí napsat na začátek každého odstavce. Řešení tohoto problému pomocí \everypar není jednoduché, protože se odstavce sází opakovaně. Zatím se toto řešení nepodařilo najít.
4
znovu spočítá \linesinregions. Pokud se hodnota \linesinregions liší od předchozí hodnoty, provede se další průchod makra \splitparagraph. 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
\def\splitparagraph{\advance\SPpass1 \let\prevlinesinregions\linesinregions \genparshape \tmpcounta\threadpart \clearLIR \setbox\endbox=\vtop{\unvcopy\region \prevdepth\prevprevdepth \theparshape\paragraphtext\par \vfil}% to avoid underfull vboxes \loop \setbox\beginbox=\vsplit\endbox to\name{tsh\the\tmpcounta} \linesinbox\beginbox\tmpcountb \addtoLIR{\the\tmpcountb} \linesinbox\endbox\tmpcountb \ifnum\tmpcountb>0 \advancemodTP\tmpcounta \repeat \addtoLIR{-1} \countlinesofparagraph \showinfo{^^JParagraph \prvnislovo ..., pass \the\SPpass: [\linesinregions]}% \ifx\linesinregions\prevlinesinregions \let\next\relax \else \ifnum\SPpass=\maxSPpasses \let\next\relax \message{^^J\string\splitparagraph does not converge!^^J} \else \ifnum\SPpass=\softSPpasses \setsoftpenalties \fi \let\next\splitparagraph \fi \fi \next}
Při výpisu činnosti programu je aktuální odstavec identifikován svým prvním slovem. Toto slovo se získá pomocným makrem \sejmiprvnislovo.2 90
\def\sejmiprvnislovo#1 #2\par{\def\prvnislovo{#1}} 2 Proč
jsou názvy maker na řádku 90 v češtině, zjistí čtenář, pokud si je přeloží do angličtiny.
5
4. Tvar odstavce 4.1. Počty řádků Počty řádků aktuálního odstavce, které se vysází do jednotlivých oblastí, jsou uloženy ve frontě \linesinregions. Pokud je číslo aktuální oblasti i a \linesinregions je definováno jako r0 ; r1 ; . . . ; rk ; −1; znamená to, že aktuální odstavec má být rozdělen do oblastí i, i + 1, . . . , i + k, přičemž v oblasti i + j bude jeho rj řádků. Hodnota −1 slouží jako oddělovač. Pro práci s frontou \linesinregions jsou definována následující čtyři makra. 91 92 93
\def\getoneLIR#1;#2\endLIR{\tmpcounta#1 \def\tmpLIR{#2}} \def\clearLIR{\def\linesinregions{}} \def\addtoLIR#1{\edef\linesinregions{\linesinregions#1;}}
Aby mělo makro \splitparagraph vždy alespoň dva průchody, je na začátku fronta \linesinregions nastavena na nesmyslnou hodnotu. 94
\def\initLIR{\def\linesinregions{-2;-1;}}
Pro posun na další oblast se používá makro \advancemodTP. Makro zvýší daný čítač o jedničku. Pokud by hodnota čítače byla větší než počet oblastí, nastaví se čítač na hodnotu 1. 95 96
\def\advancemodTP#1{\ifnum#1=\threadparts #1=1 \else \advance#1by1 \fi}
4.2. Generování tvaru Řádky odstavce v různých oblastech mají různou šířku. Proto je nutné použít primitiv \parshape. Parametry \parshape se generují z fronty \linesinregions pomocí makra \genparshape. Po použití tohoto makra bude primitiv \parshape se všemi parametry uložen do makra \theparshape. \def\genparshape{\def\theparshape{} \tmpcountb0 99 \tmpcountc\threadpart 100 \edef\tmpLIR{\linesinregions} 101 \GPSloop} 97 98
Makro \GPSloop tvoří hlavní cyklus pro makro \genparshape. \def\GPSloop{\ea\getoneLIR\tmpLIR\endLIR \ifnum\tmpcounta=-1 104 \ea\GPSfinal 105 \else 106 \tmpcountd0 102 103
6
107 108 109 110 111 112 113 114 115 116
\loop \ifnum\tmpcountd<\tmpcounta \advance\tmpcountd1 \edef\theparshape{\theparshape0pt \name{tsw\the\tmpcountc} } \repeat \advance\tmpcountb\tmpcounta \advancemodTP\tmpcountc \ea\GPSloop \fi}
Makro \GPSfinal zakončuje generování \parshape. \def\GPSfinal{% \edef\theparshape{\noexpand\parshape 119 \the\tmpcountb\space \theparshape}} 117 118
5. Počítání řádků 5.1. Řádky v boxu Makro \linesinbox spočítá počet řádků ve vboxu #1 a výsledek uloží do čítače #2. Pokud se ve vboxu vyskytuje display matematika, počítá se příslušný řádek jako tři řádky. \def\linesinbox#1#2{\setbox0=\vbox{\global#2=0 \unvcopy#1 122 \loop 123 \unskipdisplay{#2}\unskipdisplay{#2} 124 \setbox2=\lastbox 125 \unskipdisplay{#2}\unskipdisplay{#2}\unskipdisplay{#2} 126 \unpenalty 127 \ifhbox2 128 \global\advance#2by1 129 \repeat} 130 \ifdim\ht0>0pt 131 \message{^^JERROR in \string\linesinbox^^J} 132 {\showboxbreadth10 \showbox0} 133 \fi}
120 121
Test na přítomnost display matematiky se provádí porovnáním velikosti vertikální mezery v boxu s velikostí mezery vkládané nad rovnici, která je při \predisplaypenalty=10000 vždy přítomna, na rozdíl od mezery pod rovnicí. Aby nedošlo k záměně s jinými mezerami, jsou tyto mezery zvětšeny o 0,001 pt. 134 135
\advance\abovedisplayskip0.001pt \advance\abovedisplayshortskip0.001pt
7
Makro \unskipdisplay pracuje jako \unskip s testem na přítomnost display matematiky. \def\unskipdisplay#1{ \ifdim\lastskip=\abovedisplayskip \global\advance#1by2 \fi 138 \ifdim\lastskip=\abovedisplayshortskip\global\advance#1by2 \fi 139 \unskip} 136
137
5.2. Řádky v odstavci Při počítání řádků aktuálního odstavce v jednotlivých oblastech (vboxech) bude první položka nesprávná, protože oblast bude obsahovat i řádky předchozích odstavců. Tuto první položku opraví makro \countlinesofparagraph. \def\countlinesofparagraph{ \setbox0=\vbox{\theparshape\paragraphtext\par 142 \global\tmpcountc\prevgraf} 143 \ea\getoneLIR\linesinregions\endLIR 144 \let\linesinregions\tmpLIR 145 \tmpcountb0 146 \CLOPloop} 140 141
Makro \CLOPloop tvoří hlavní cyklus pro makro \countlinesofparagraph. \def\CLOPloop{\ea\getoneLIR\tmpLIR\endLIR \ifnum\tmpcounta=-1 149 \ea\CLOPfinal 150 \else 151 \advance\tmpcountb\tmpcounta 152 \ea\CLOPloop 153 \fi} 147 148
Makro \CLOPfinal zakončuje práci makra \countlinesofparagraph. Pokud by vyšlo \tmpcounta<0, znamená to, že se předchozí odstavec při zpracovávání současného odstavce rozdělil do více oblastí, než byl rozdělen původně. Tomu je třeba zabránit vložením \penalty-10000. \def\CLOPfinal{\tmpcounta\tmpcountc \advance\tmpcounta-\tmpcountb 156 \edef\linesinregions{\the\tmpcounta;\linesinregions}% 157 \ifnum\tmpcounta<0 158 \addtoregion{\penalty-10000 } 159 \fi} 154 155
6. Nastavení penalt Může se stát, že ani po několika průchodech makra \splitparagraph se hodnota \linesinregions neustálí. To může nastat, pokud se blízko hranice oblasti 8
nachází display matematika nebo hranice odstavce a pokud se do zlomu do oblastí zapojí některá z níže uvedených penalt. Na to zareaguje makro \splitparagraph tím, že tyto penalty sníží. Tím se sníží typografické požadavky za cenu konvergence algoritmu. \newcount\oriclubpenalty \newcount\oriwidowpenalty 162 \newcount\oridisplaywidowpenalty 163 \newcount\oribrokenpenalty 160
161
Makro \getnormalpenalties zjistí původní hodnoty penalt. \def\getnormalpenalties{\oriclubpenalty\clubpenalty \oriwidowpenalty\widowpenalty 166 \oridisplaywidowpenalty\displaywidowpenalty 167 \oribrokenpenalty\brokenpenalty} 164 165
Makro \setnormalpenalties nastaví původní hodnoty penalt. \def\setnormalpenalties{\clubpenalty\oriclubpenalty \widowpenalty\oriwidowpenalty 170 \displaywidowpenalty\oridisplaywidowpenalty 171 \brokenpenalty\oribrokenpenalty} 168 169
Makro \setsoftpenalties sníží penalty. \def\setsoftpenalties{\clubpenalty0 \widowpenalty0 174 \displaywidowpenalty0 175 \brokenpenalty0 } 172 173
7. Umístění na stranu Jednotlivé vboxy s oblastmi jsou na správné pozici umístěny v boxu \pagebox. Do tohoto boxu se box s aktuální oblastí vloží makrem \addtopagebox. Pokud jsou zaplněny všechny oblasti na straně, vysází se \pagebox na aktuální stranu a začne se plnit další strana. \def\addtopagebox{\setbox\pagebox=\vtop{\unvbox\pagebox \dimen2=\name{tsy\the\threadpart} 178 \vskip\dimen2 179 \vtop to 0pt{ 180 \moveright \name{tsx\the\threadpart} \box\region 181 \vss} 182 \vskip-\dimen2 } 183 \showinfo{Exporting region \the\threadpart^^J} 184 \advancemodTP\threadpart 185 \ifnum\threadpart=1 186 \forceoutput 176
177
9
187
\fi}
Makro \forceoutput vysází \pagebox na aktuální stranu. V plainu zároveň zastupuje výstupní rutinu a stranu vloží do dvi/pdf souboru. V LATEXu vloží \pagebox do vboxu výšky \pageboxheight, ten vloží na aktuální stranu a vyvolá výstupní rutinu. \ifplain \def\forceoutput{\shipout\vbox{\vskip-1in 190 \moveright-1in\box\pagebox}% 191 \global\advance\pageno1 } 192 \else 193 \newdimen\pageboxheight 194 \def\forceoutput{\vbox to \pageboxheight{\box\pagebox\vss} 195 \pagebreak} 196 \fi 188
189
Zbývá vyřešit sazbu poslední strany. V plainu program očekává, že se celý dokument sází do oblastí. Proto je sazba poslední strany řešena předefinovaným makra \bye. Aktuální oblast se vloží do boxu \pagebox a tento box je vložen do dvi/pdf souboru. V případě \threadpart=1 byla aktuální oblast poslední na straně a makro \forceoutput již bylo zavoláno z makra \addtopagebox. \ifplain \let\oribye\bye 199 \def\bye{% 200 \setbox\region=\vtop{\unvbox\region}% 201 \addtopagebox 202 \ifnum\threadpart>1 \forceoutput\fi 203 \name{oribye}} 197 198
V LATEXu je vložení strany do dvi/pdf souboru záležitostí výstupní rutiny a očekává se, že dokument bude obsahovat i text, který se nesází do oblastí. Poslední strana s oblastmi se vysází makrem \shipoutlastpage. Pokud se má potlačit stránkový zlom za poslední stranou sázenou do oblastí, stačí před použitím \shipoutlastpage lokálně definovat \let\pagebreak\relax. \else \def\shipoutlastpage{% 206 \setbox\region=\vtop{\unvbox\region}% 207 \addtopagebox 208 \ifnum\threadpart>1 \forceoutput\fi} 209 \fi 204 205
10
Index Numbers written in italic refer to the page where the corresponding entry is described; numbers underlined refer to the code line of the definition; numbers in roman refer to the code lines where the entry is used. A \abovedisplayshortskip . . . . . . . 135, 138 \abovedisplayskip . . . . . . . 134, 137 \addonepar . . . . . . . 36 \addtoLIR . . . 67, 72, 93 \addtopagebox . . . . . 51, 176, 201, 207 \addtoregion . . 54, 158 \advance 25, 55, 96, 109, 113, 128, 134, 135, 137, 138, 151, 155, 191 \advancemodTP . . . . . . 70, 95, 114, 184 B \beginbox . . . 16, 65, 66 \box . . . . . 180, 190, 194 \brokenpenalty . . . . . . . 167, 171, 175 \bye . . . . . . . . . 198, 199 C \clearLIR . . . . . 59, 92 \CLOPfinal . . . 149, 154 \CLOPloop 146, 147, 152 \clubpenalty . . . . . . . . . 164, 168, 172 \countlinesofparagraph . . . . . . . . 73, 140 \csname . . . . . . . . . 2, 3 D \def . . . . . . 2, 3, 18, 19, 22, 25, 36, 37, 54, 55, 90– 95, 97, 102, 117,
120, 136, 140, 147, 154, 164, 168, 172, 176, 189, 194, 199, 205 \dimen . . . 177, 178, 182 \displaywidowpenalty . . . . 166, 170, 174 \documentclass . . . . 18 E \ea . . . . . . 1, 3, 31, 102, 104, 115, 143, 147, 149, 152 \edef . . . . . . . . . 93, 100, 110, 118, 156 \else . 32, 78, 82, 96, 105, 150, 192, 204 \endbox . . . . 17, 44, 49, 50, 60, 65, 68 \endcsname . . . . . . 2, 3 \endLIR 91, 102, 143, 147 \expandafter . . . . . . . 1 F . . . . . . . 35, 85, 87, 88, 96, 116, 133, 137, 138, 153, 159, 187, 196, 202, 208, 209 \forceoutput . . 186, 189, 194, 202, 208 \fi
G \genparshape . . . 57, 97 \getnormalpenalties . . . . . . . . 41, 164 \getoneLIR . . . . . . . 91, 102, 143, 147
\global 47, 120, 128, 137, 138, 142, 191 \GPSfinal . . . . 104, 117 \GPSloop . 101, 102, 115 H \hsize . . . . . . . . . . . 39 \ht . . . . . . . . . 50, 130 I \ifdim 50, 130, 137, 138 \ifhbox . . . . . . . . . 127 \ifnum . . . . . . . . 30, 69, 79, 83, 95, 103, 108, 148, 157, 185, 202, 208 \ifplain . . 18, 188, 197 \ifx . . . . . . . . . . 18, 76 \initLIR . . . . . . 40, 94 L \lastbox . . . . . . . . 124 \lastskip . . . . 137, 138 \let . . . 1, 20, 21, 56, 77, 80, 86, 144, 198 \linesinbox . . . . . . . . 66, 68, 120, 131 \linesinregions 56, 75, 76, 92–94, 100, 143, 144, 156 \loop . . 48, 64, 107, 122 M \maxSPpasses . . . 11, 79 \message . . . 21, 81, 131 \moveright . . . 180, 190 N \name . . 2, 39, 49, 65, 111, 177, 180, 203
11
\namedef \newbox . \newcount \newdimen \next . . . \noexpand
. . . . . 3, 26–29 . . . . . . 14–17 4–12, 160–163 . . . . 13, 193 . 77, 80, 86, 89 . . . . . . . 118
O \oribrokenpenalty . . . . 163, 167, 171 \oribye . . . . . . . . . 198 \oriclubpenalty . . . . . . 160, 164, 168 \oridisplaywidowpenalty . . . . 162, 166, 170 \oriwidowpenalty . . . . . 161, 165, 169 P \pagebox 15, 176, 190, 194 \pageboxheight 193, 194 \pagebreak . . . . . . 195 \pageno . . . . . . . . . 191 \par 36, 38, 46, 62, 90, 141 \paragraphtext . . . . . . 37, 46, 62, 141 \parshape . . . . . . . 118 \penalty . . . . . . . . 158 \prevdepth . . 45, 47, 61 \prevgraf . . . . . . . 142 \prevlinesinregions . . . . . . . . . 56, 76 \prevprevdepth . . . . 13, 34, 45, 47, 61 \prvnislovo . . . . 74, 90 R \readthreadshape . . . . . . . 24, 25, 31
\region . 14, 44, 49, 54, 60, 180, 200, 206 \relax . . . . . . . . 77, 80 \repeat 52, 71, 112, 129 S \sejmiprvnislovo 38, 90 \setbox . 44, 49, 54, 60, 65, 120, 124, 141, 176, 200, 206 \setnormalpenalties . . . . . . . . 53, 168 \setsoftpenalties . . . . . . . . 84, 172 \shipout . . . . . . . . 189 \shipoutlastpage . 205 \showbox . . . . . . . . 132 \showboxbreadth . . 132 \showinfo 19–21, 74, 183 \softSPpasses . . 12, 83 \space . . . . . . . . . . 119 \splitparagraph . . . . . 43, 55, 81, 86 \SPpass . . . . . . . 10, 42, 55, 75, 79, 83 \string . . . . . . 81, 131 T \the 26–29, 39, 49, 65, 67, 75, 111, 119, 156, 177, 180, 183 \theparshape . . . . . . . . 46, 62, 97, 110, 118, 119, 141 \threadpart 8, 23, 25– 30, 33, 39, 49, 58, 99, 177, 180, 183–185, 202, 208 \threadparts . . . . . . . . . 9, 22, 30, 95
\threadshape . . . . . . 22 \tmpcounta . . . . . 4, 58, 65, 70, 91, 103, 108, 113, 148, 151, 154–157 \tmpcountb . . . . . 5, 66–69, 98, 113, 119, 145, 151, 155 \tmpcountc . . 6, 99, 111, 114, 142, 154 \tmpcountd . . . . . . . . 7, 106, 108, 109 \tmpLIR . . . . . . . 91, 100, 102, 144, 147 U \undefined . . . . . . . 18 \unpenalty . . . . . . 126 \unskip . . . . . . 37, 139 \unskipdisplay . . . . . . . 123, 125, 136 \unvbox 54, 176, 200, 206 \unvcopy . . . 44, 60, 121
\vbox . . . . 120, \vfil . . . . \vskip . . . \vsplit . . \vss . . . . . \vtop . . . . 176,
V . . . . . 54, 141, 189, 194 . . . . . . . . 63 178, 182, 189 . . . . . 49, 65 . . . . 181, 194 . . 44, 60, 179, 200, 206
W \widowpenalty . . . . . . . . 165, 169, 173 \wlog . . . . . . . . . . . . 20
Jan Šustek
12