TeX pro každého - makra pro sazbu

2. 12. 2002
Doba čtení: 8 minut

Sdílet

Zatímco v minulém dílu jsme se zabývali makry, která demonstrují způsob používání a sílu TeXového makrojazyka, dnes se podíváme na makra, která přímo zasahují do sazby a rozhodují o vzhledu dokumentu – makra pro umístění textu, změnu počtu sloupců a obtékané obrázky.

Dnešní díl využijí spíš uživatelé plain TEXu. Většina konstrukcí, které zde najdete, je totiž v LaTEXu již hotová. Dal jsem záměrně přednost věcem, které v TEXu nejsou zcela jednoduché, abych demonstroval možnost nadefinovat si vlastní makra i pro komplikovanější činnosti.

Jako obvykle přikládám soubor se všemi ukázkami z tohoto dílu (předpokládá se formát csplain; obrázek Tuxe najdete zde a za domácí úkol si jej převedete na EPS).

Do čtvrtiny a tří čtvrtin

Začněme něčím velmi jednoduchým – vyrobíme si makro \minitab. Mělo by sloužit k sázení jednoduché „tabulky“ – ve čtvrtině šířky stránky budou na střed položky prvního sloupce, ve třech čtvtinách pak položky třetího sloupce.

Nezkušeného TEXistu by možná napadlo jednoduché makro s pružnými výplňky:

\def\minitab#1#2{\line{\hss#1\hss\hss#2\hss}}

(poznámka: Makro \line má v LaTEXu jiný význam.)

Malá zkouška však ukáže, že je to špatně – text se sice nachází zhruba tam, kde chceme, ale kolem té správné polohy ošklivě „courá“:

\minitab {pes}{štěká}
\minitab {kočka}{mňouká}
\minitab {slepice}{kdáká}

Zamyslíme-li se nad tím, dojde nám, že od šířky, o kterou se pružné výplňky dělí, se odečítá šířka zadaných položek, a proto se jejich poloha posouvá tak, aby pouze zbylé místo mezi nimi bylo v poměru 1 : 2 : 1. To však lze snadno napravit – prostor, o který se výplňky budou dělit, bude celý řádek. Další výplňky pak zajistí, že položky budou na středu:

\def\minitab#1#2{\line{%
  \hss\hbox to0pt{\hss#1\hss}\hss
  \hss\hbox to0pt{\hss#2\hss}\hss
  }}

U vnitřních boxů jsme při tom využili skutečnosti, že výplňky \hss mohou nabývat i záporných velikostí.

Makro \PAR

Občas bychom potřebovali, aby makro pro úpravu zlomu odstavce (např. – \looseness nebo\parshape) – fungovalo na více odstavců najednou. Pokusíme se proto vytvořit makro, které se co nejvíce přiblíží chování primitiva \par, ale přitom nedá TEXu pokyn k zalomení:

\def\PAR{%
  \ifhmode
    \unskip \hskip\parfillskip
    \vadjust{\vskip\parskip}%
    \break
  \fi
  \indent}

Makro nejdříve vyplní východový řádek, poté do vertikálního seznamu vloží odstavcový skok (často bývá nulový), pak přeskočí na nový řádek a tam vloží odstavcovou zarážku.

Má však i své nectnosti – nejviditelnější je nemožnost použít \noindent (ledaže bychom si napsali makro \PARnoindent). Další jsou již méně viditelné – TEX takto vytvořený zlom nepovažuje za zlom odstavce, a proto nefunguje vše, co se k tomu vztahuje (\widowpenalty,\club­penalty a \finalhyphende­merits, naproti tomu zafunguje \adjdemerits).

Vizuální výstup

Nyní si ukážeme jednoduchý zásah do výstupní rutiny. Pokud jste důkladně prostudovali soubory s ukázkovými příklady z minulých dílů, jistě jste si všimli makra \visualized. To mělo za úkol zviditelnit hranice boxu. Nyní si zviditelníme hranice výstupního boxu.

Protože výstupní rutina se liší podle nadstavby, následující příklad lze použít pouze v plain TEXu.

Po prostudování souboru plain.tex najdeme zajímavé makro:

\def\pagebody{%
  \vbox to\vsize{%
    \boxmaxdepth\maxdepth \pagecontents}}

Zkusíme jej tedy pozměnit:

\def\pagebody{\visualized{%
  \vbox to\vsize{%
    \boxmaxdepth\maxdepth \pagecontents}}}

Vidíme, že rámeček se nachází kolem hlavního boxu, ale číslo stránky je mimo něj.

Obrátíme se tedy k primitivu \shipout, které posílá výstup do DVI souboru. Předefinujeme jej, abychom viděli, co do něj přichází:

\let\oldshipout=\shipout
\def\shipout#1#2{%
  \oldshipout\vbox{\visualized{#1{#2}}}}

Vidíme, že tentokrát je již obsahem boxu vše na stránce.

Poznámka: Uvedená předefinice primitiva \shipout je velmi omezená, neboť parametry \shipout nejsou dva, ale jeden box. Naše předefinice v plainu náhodou funguje, neboť volání tam vypadá zhruba takto: \shipout\vbox{ob­sah}.

Dvousloupcová sazba

Mezi časté požadavky patří vícesloupcová sazba. Je možné ji zajistit v principu dvěma způsoby:

  1. Sázíme do úzkého sloupce dvojnásobné výšky. Ve výstupní rutině přelomíme sloupec pomocí \vsplit na dvě poloviny, ty vložíme vedle sebe a vyrobíme výstup. Výhodou tohoto postupu je možnost snadného vybalancování délek odstavců na poslední straně, nevýhodou nemožnost použití běžných konstrukcí pro řízení přechodu na nový sloupec.
  2. Sázíme do úzkého sloupce běžné výšky. Při prvním volání výstupní rutiny si pouze uschováme obsah výstupního boxu. Teprve při druhém volání ze získaných boxů zkompletujeme výstup. Výhodou je možnost použití běžných konstrukcí pro řízení přechodu na nový sloupec, nevýhodou nemožnost vybalancování délek odstavců na poslední straně.

V praxi se tedy tyto postupy často poměrně komplikovaně kombinují.

Naše jednoduché makro předvede druhý postup:

Nejdřive si nadefinujeme veřejně použitelnou dimenzi \intercolskip (pro mezeru mezi odstavci) a pomocné proměnné:

\newskip\intercolskip
\intercolskip=5mm
\newbox\prvnisloupec
\newbox\druhysloupec
\newdimen\orighsize
\newif\ifprvnisloupec

A můžeme začít nastavením pomocných proměnných a vypočtením nové šířky sloupce:

\def\dvasloupce{%
  \prvnisloupectrue
  \orighsize\hsize
  \advance\hsize-\intercolskip
  \divide\hsize by2%

Nyní nastavíme novou výstupní rutinu. Použijeme při tom \box255, kam se před vyvoláním výstupní rutiny uloží odesílaný obsah strany. Abychom nemuseli vytvářet množství maker, pomůžeme si původním \plainoutput (ten potřebuje ke své činnosti původní šířku strany; tu jsme si však schovali). Povšimněte si dvou drobností: \output není makro, ale syntaxí připomíná toks registr. Vše, co je uvnitř, se však vyvolává uvnitř skupiny, a pokud nepoužijeme \global, je to po skončení výstupní rutiny zapomenuto (pokud nepoužijeme triky s \aftergroup).

  \output{%
    \ifprvnisloupec
      \global\prvnisloupecfalse
      \global\setbox\prvnisloupec\box255%
    \else
      \global\prvnisloupectrue
      \hsize\orighsize
      \plainoutput
    \fi
  }%

Prohlédneme-li si výstupní makra v plain TEXu, zjistíme, že nám opět přijde vhod makro \pagebody. Potřebujeme jej předefinovat a zároveň použijeme i jeho původní obsah. Z toho je vyvoláváno makro \pagecontents natvrdo používající box 255. Abychom jej nemuseli celé opisovat, původní box 255 si uschováme a přiřadíme do něj to, co potřebujeme zpracovat. Pokud bychom celé makro psali sami, takové triky bychom asi nepoužili.

  \let\origpagebody=\pagebody
  \def\pagebody{\hbox{%
    \setbox\druhysloupec=\box255%
    \setbox255\box\prvnisloupec
    \origpagebody
    \hskip\intercolskip
    \setbox255\box\druhysloupec
    \origpagebody
    }%
  }%

A to je vše! Takto jednoduché makro však neřeší opuštění dvousloupcového režimu.

}

Uvedený postup s předefinováním původních maker sice vyžaduje některé triky, ale zato jsme bez problémů adoptovali původní systém plovoucích objektů a poznámek pod čarou (pravda, poznámka pod čarou bude zasahovat jen přes jeden sloupec). Pokud bychom vše řešili od začátku, museli bychom se s tím vypořádat sami. Pro autory větších nadstaveb je to samozřejmé – rozsáhlejší výstupní makra nám dovolí vložit plovoucí objekt či poznámku pod čarou přes dva odstavce či libovolně přecházet mezi různými počty sloupců atd.

Nyní můžete zkombinovat vizualizační makra z předchozí ukázky a sledovat, k jakým změnám došlo.

Obtékaný obrázek

TEX nemá pro tvarování odstavce širokou paletu primitiv – jednak jsou to \hangindent a\hangafter, které jsou vhodné spíš pro odsazení, a potom je to makro \parshape:

\parshape počet_řádků levý_okraj šířka
  levý_okraj šířka ...

Zalomení dalších nespecifikovaných řádků se řídí podle poslední položky.

Vkládáme-li obdélníkový obrázek, je toto primitivum značně nepraktické.

Napíšeme si makro, které to provede za nás:

\vlozobrazek {hloubka}{kód pro obrázek}

Hloubka je uvedená od aktuální pozice, kód pro obrázek bude obsahovat makra pro vložení. Navíc přidáme rozhodovací makro \ifvlevo (abychom vše nemuseli psát dvakrát) a dimenzi\okoli (pro velikost bílého okolí obrázku).

Při definici využijeme makra \totokse z minulého dílu a makrobalíku epsf.tex pro vkládání EPS obrázků:

\input epsf
\newdimen\okoli \okoli=2mm
\newif\ifvlevo

Budeme potřebovat několik pomocných proměnných. Můžeme si je nadefinovat (což vypadá přehledněji) anebo použít registry číslo 0–9 – tyto registry jsou určeny na pomocné proměnné (zvykem bývá pro globálně předefinované proměnné použít lichá čísla a pro lokálně předefinované sudá). Toto pravidlo neplatí pro čítače (zde jsou registy 0–9 vkládány do DVI souboru jako číslování stránek (volný je pouze \count255 – viz plain.tex).

\newcount\obrcount
\newtoks\obrtoks
\newdimen\obrvlevo
\newdimen\obrsirka

Definici makra se dvěma proměnnými začínáme (pro jistotu) odřádkováním:

% \vlozobrazek {hloubka}{kód pro obrázek}
\def\vlozobrazek#1#2{\par

Do boxu si uložíme obrázek:

  \setbox0=\hbox{#2}%

Nyní provedeme vlastní vložení obrázku do předepsané vzdálenosti od aktuální pozice. Vše uzavřeme do boxu s nulovou výškou a hloubkou.

  \vtop to0pt{%
  \vskip#1%
  \ifvlevo
    \line{\copy0\hss}%
  \else
    \line{\hss\copy0}%
  \fi
  \vss}%

Přestože box měl nulovou výšku, v souladu s pravidly meziřádkových mezer bude následující řádek posunut. Nyní tento posun vykompenzujeme (pokud bychom chtěli vytvořit dokonalé makro, museli bychom uložit a obnovit i hodnotu \prevdepth).

  \vskip-\baselineskip

Nyní inicializujeme čítač řádků a toks registr. Proč inicializujeme \obrcount na hodnotu 1, se dozvíme později.

  \obrcount=1%
  \obrtoks={}%

Nyní vypočteme šířku a levý okraj odstavce v místech, kde bude vložen obrázek. Pomocnou hodnotu \obrdimen přitom nastavíme na šířku obrázku zvětšenou o prázdné místo.

  \obrsirka=\hsize
  \obrvlevo=0pt%
  \obrdimen=\wd0\advance\obrdimen by\okoli
  \advance\obrsirka by-\obrdimen
  \ifvlevo
    \advance\obrvlevo by\obrdimen
  \fi

Nyní pomocnou hodnotu \obrdimen použijeme jako naši vzdálenost od aktuální pozice na stránce a pomocnou hodnotu\dimen0 jako vzdálenost horního okraje obrázku (nad bílým místem). Pak můžeme ve smyčce vkládat do toks registru hodnoty pro standardně zalomený řádek, dokud horního okraje nedosáhneme. Při tom si počítáme, kolik řádků jsme již do toks registru vložili:

  \obrdimen=0pt%
  \dimen0=#1\advance\dimen0by-\okoli
  \loop\ifdim\obrdimen<\dimen0%
    \totokse\obrtoks{0pt\hsize}%
    \advance\obrcount by1%
    \advance\obrdimen by\baselineskip
  \repeat

Nyní vypočteme vzdálenost spodního okraje obrázku (včetně bílých míst) a pokračujeme ve stejném duchu. Jediný rozdíl spočívá v tom, že vkládáme do toks registru hodnoty pro zúženou sazbu:

  \advance\dimen0by\ht0%
  \advance\dimen0by2\okoli
  \loop\ifdim\obrdimen<\dimen0%
    \totokse\obrtoks{\obrvlevo\obrsirka}%
    \advance\obrcount by1%
    \advance\obrdimen by\baselineskip
  \repeat

A nyní máme parametry pro \parshape kompletní. Posledním parametrem, který musíme přidat, je návrat na původní šířku sazby. To také vysvětluje, proč jsme na začátku inicializovali počet argumentů na 1. A tím makro končí:

bitcoin_skoleni

  \parshape\obrcount\the\obrtoks 0pt\hsize
}

Takto jednoduché makro není bez problémů – nepozná, že na stránce již není dostatek místa na obrázek. Neumí vkládat obrázek přes více odstavců, pokud si nevypomůžeme makrem, jako je \PAR. Neumí vložit obrázek do horního rohu, pokud sem odstavec přetekl z předchozí stránky. A nakonec, lze pomocí něj vložit nejvýše jeden obrázek.

Všechna popisovaná makra (s výjimkou \PAR) byla napsána speciálně pro tento článek.