%% OPmac -- Olsak's PlainTeX macros %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Tech-documentation, Petr Olsak, 2012 \input utf8off \clearmubyte % This file is encoded in ISO-8859-2 because % UTF-8 encTeX has a conflict with DocByTeX \chyph \def\projectversion{July 2019} \def\headtitle{OPmac} \widowpenalty=10000 \clubpenalty=10000 \emergencystretch=2em \hbadness=2200 \showboxbreadth=1500 \showboxdepth=2 \def\csplain{\CS plain} \def\CS{CS} \input docby.tex \setlinecomment{\percent} \noactive{\nb\percent} \noactive{\percent\cbrace} \noactive{\nb fontdimen} \noactive{\nb empty} \title OPmac -- rozšiřující makra plain\TeX{}u \author Petr Olšák \centerline{\ulink[http://www.olsak.net/opmac.html]% {www.olsak.net/opmac.html}} \def\db{\dg\nb} \def\du#1{\api{\nb#1}} \let\quotehook=\langleactive \def\insdef#1 {\ifirst{docby.tex}{def\nb#1 }{^^B\cbrace}{++}} \def\inssdef#1 {\ifirst{docby.tex}{def\nb#1}{\empty}{+-}} \bgroup \catcode`\[=1 \catcode`]=2 \catcode`\{=12 \catcode`\}=12 \gdef\obrace[{] \gdef\cbrace[}] \egroup \def\indexhook{% Tučně je označena strana, kde je slovo dokumentováno, pak následuje seznam všech stran, na kterých se slovo vyskytuje. \medskip} \def\nn#1 {\noactive{\nb#1}} \nn insert \nn undefined \def\cnvbookmark#1{\lowercase{\lowercase{#1}}} \def\bookmarkshook{\lo ěe \lo šs \lo čc \lo řr \lo žz \lo ýy \lo áa \lo íi \lo ée \lo úu \lo ůu \lo óo \lo ňn } \def\lo #1#2{\lccode`#1=`#2} \dotoc \bookmarks \sec Úvod %%%%%%%%% OPmac je balík jednoduchých doplňujících maker k plain\TeX{}u umožňující uživatelům základní La\TeX{}ovou funkcionalitu: změny velikosti písma, automatickou tvorbu obsahu a rejstříku, práci s {\tt bib} databázemi, referencemi, možnost proložení referencí hyperlinkovými odkazy atd. \sec Uživatelská dokumentace %%%%%%%%%%%%%%%%%%%%%%%%%%%% Uživatelská dokumentace je zatím v souboru "opmac-u.tex" a "opmac-u.pdf". Do tohoto místa ji zahrnu později a prolinkuji ji s technickou dokumentací. \sec Technická dokumentace %%%%%%%%%%%%%%%%%%%%%%%%%% Tato část dokumentace je určena pro tvůrce maker, kteří se chtějí zde uvedenými makry inspirovat a případně je přizpůsobit svému požadavku. Předpokládá se znalost \TeX{}u, tj. například aspoň zběžná orientace v~\TeX{}booku naruby. Na tuto knihu je na mnoha místech odkazováno pod zkratkou TBN. \subsec Základní makra %%%%%%%%%%%%%%%%%%%%%% Na začátku souboru "opmac.tex" zjistíme, zda není soubor čtený podruhé. V takovém případě čtení odmítneme. Ptáme se na to, zda je definováno makro \db OPmacversion, které vzápětí definujeme. Je-li někdo překvapen, proč jsem nepoužil "\expandafter\endpinput\fi", může si prostudovat TBN, stranu~358, heslo "\endinput". \ifirst {opmac.tex}{OPmacversion}{\empty}{+-} Dva pracovní registry: \dgn \nb tmpnum \dgn \nb tmpdim \inext {newcount}{\empty}{+-} OPmac nebude nikdy hlásit chyby. Často ale bude psát pomocí \db opwarning na terminál varování. \inext {warning}{\empty}{+-} Makro \db addto "{}" přidá na konec "" dané "". \inext {addto}{\empty}{+-} V OPmac budeme pracovat se seznamem \db protectlist, který bude obsahovat makra, jež chceme mít tzv. robustní, tj. chceme, aby se při "\write" v output rutině neexpandovala. Každému makru v~seznamu předchází "\doprotect", takže seznam "\protectlist" vypadá takto: \def\begtthook{\langleactive} \begtt \doprotect \doprotect ... \endtt \def\begtthook{} Seznam budeme spouštět v output rutině s tím, že "\doprotect" tam bude mít význam makra, které zařídí, aby jeho parametr získal význam "\relax". Tím bude zabráněno jeho expanzi. Naprogramujeme \db addprotect "", které zařídí vložení "" do seznamu. \inext {protectlist}{\empty}{+-} OPmac užívá v makrech pro speciální vlastnosti PDF výstupu výhradně primitivy pdf\TeX{}u. Lua\TeX{} nám v roce 2016 přidělal starosti, protože předefinoval pdf\TeX{}ové primitivy. Proto při detekování nového Lua\TeX{}u (to poznáme podle "\pdfextension") nastavíme význam primitivu "\pdfoutput" do původního stavu a dále, na konci souboru maker (viz sekci~\ilink[zaver]{\numref[zaver]}), voláme speciální soubor "opmac-luatex.tex", který nastaví další pdf\TeX{}ové primitivy podle původního významu. \inext {pdfextension}{\empty}{+-} Některá makra budou fungovat jen v pdf\TeX{}u při nastaveném "\pdfoutput=1". Připravíme si tedy test \db ifpdftex, který pak použijeme při čtení souboru "opmac.tex". Test nikdy nebudeme vkládat do maker, takže při čtení souboru "opmac.tex" už musí být jasné, zda bude výstup směrován do DVI nebo PDF. Pozdější změna "\pdfoutput" může způsobit potíže. Xe\TeX{} sice není pdf\TeX, ale po dobu čtení maker jej za pdf\TeX{} budeme považovat a na konci čtení maker (viz sekci~\ilink[zaver]{\numref[zaver]}) to spravíme. \inext {ifpdftex}{\empty}{+-} Makra \db sdef a \db sxdef umožňují pohodlně definovat kontrolní sekvence ohraničené pomocí "\csname...\endcsname". Stejně tak \db slet nastaví význam sekvencí ohraničených pomocí "\csname...\endcsname". \inext {sdef}{\empty}{+-} Makro \db adef umožní nastavit znak na aktivní a rovnou ho definovat, což normálně uvnitř maker není jednoduché (TBN str. 25 a 26). Využijeme toho, že "~" je aktivní znak a pomocí "\lccode" a "\lowercase" jej přepíšeme na požadovaný znak. Dostaneme tím aktivní token s požadovanou ASCII hodnotou a tento token definujeme. "\lccode" nastavíme ve skupině, takže po ukončení skupiny se vrací k výchozí hodnotě. \inext {adef}{\empty}{+-} Makrem \db isdefined "{}\iftrue" se ptáme, zda je definovaná "\csname\endcsname". To závěrečné připojené "\iftrue" makro sežere, ale uživatel ho píše zejména z toho důvodu, aby mu tato konstrukce fungovala uvnitř vnořených "\if..\fi" \inext {isdefined}{^^B\cbrace}{++} Makro \db isinlist "{}\iftrue" zjistí, zda "" jsou (jako string) obsaženy v~makru "". Přitom sežere "\iftrue" ze stejných důvodů, jak je uvedeno před chvílí. \inext {isinlist}{^^B\cbrace}{++} Makro \db isnextchar "{}{}" pracuje poněkud odlišně od předchozích maker. Zjistí, zda následující znak je "" a pokud ano, vykoná vnitřek první závorky, jinak vykoná vnitřek druhé závorky. Pomocí "\futurelet" uloží zkoumaný znak do "\next" a spustí \db isnextcharA. \inext {isnextchar}{\empty}{+-} Makro \db eoldef "\foo#1{}" pracuje jako "\def", ale parametr "#1" je separován koncem řádku. Takže třeba \begtt \eoldef\foo#1{param={#1}} \foo tady je parametr \endtt expanduje na "param={tady je parametr}". Implementace se opírá o to, že při "\eoldef\foo" se definují "\foo" a "\\foo:M". Přitom "\foo" ve skupině pozmění catcode znaku pro konec řádku a spustí \db eoldefA "\foo". Toto makro načte parametr "#2" do konce řádku (po "^^M"), dále ukončí skupinu a spustí "\\foo:M{parametr}". Konečně "\\foo:M" vykoná to, co definoval uživatel. \inext {eoldef}{\empty}{+-} Makro \db maybebreak "" umožní uživateli rozlomit řádek nebo stránku v místě použití. Pomocné marko \db maybebreakA se spustí po načtení parametru. Zlom se uskuteční, chybí-li do konce řádku/stránky zhruba méně než "" místa. Jinak se zlom neuskuteční a nestane se nic. Makro je závislé na módu \TeX{}u (vertikální/horizontální). Chcete-li jím lámat stránky, pište třeba "\par\maybebreak3cm". Makro využívá triku, že přičte a odečte stejnou hodnotu roztažitelnosti mezery, takže tyto dvě mezery těsně za sebou se (při nezlomení v~"\penalty-130") anulují. \inext {maybebreak}{^^B\cbrace}{++} Předefinujeme makro \db uv z \csplain{}u. Tam je toto makro navrženo tak, aby mohlo mít za svůj parametr verbatim text. Důsledkem toho nefunguje správně kerning. Považuji za lepší mít správně kerning a případné uvozování verbatim textů řešit třeba pomocí "\clqq...\crqq". \inext {def\nb uv}{}{+-} Knuth v souboru "plain.tex" zanechal řídicí sekvenci "\\" v provizorním stavu (cvičení: podívejte se v jakém). Domnívám se, že je lepší ji dát jednoznačný význam "\undefined". Některým uživatelům totiž může OPmac připomínat La\TeX{} a není tedy vyloučeno, že je napadne psát "\\". Měli by na to dostat jednoznačnou odpověď: {\tt undefined control sequence}. \inext {undefined}{}{+-} Do pracovního souboru určeného k novému načtení budeme chtít vložit komentáře za znakem procento. K tomu potřebujeme mít procento jako obyčejný znak kategorie 12. Na tento znak se v našem kódu překlopí otazník, Takže \db percent expanduje na znak procento s kategorií 12. \inext {percent}{}{+-} Podobně je naprogramováno makro \db bslash, které vytiskne obyčejné zpětné lomítko: \inext {bslash}{}{+-} Makro plain\TeX{}u "\," funguje jen v matematické sazbě. Uživatel bude chtít makro často použít například mezi číslem a jednotkou v textovém módu: "5\,mm", takže makro předefinujeme. \inext {muskip}{}{+-} Definovaná makra chceme při "\write" do souboru nechat v původním stavu: \inext {addprotect}{}{+-} Makro "\exfont" se vyskytuje v souboru "exchars.tex" z \CS{}plainu. Příkaz "\addprotect\exfont" zaprotektuje všechny znaky deklarované v tomto souboru naráz. Podrobnosti lze nalézt v uvedeném souboru. Makro \db replacestrings "{}{}" vymění v makru "\tmpb" veškeré výskyty "" za "". Pro tento účel definuje pracovní makra \db replacestringsA a \db replacestringsB se separátorem "". Jak to pracuje je ukázáno na příkladu níže. Před spuštěním "\replacestringsA" je třeba nejprve vyvrhnout obsah "\tmpb" do vstupní fronty pomocí "\expandafter". V makru pracujeme s tokeny "!" a "?" kategorie 3, které slouží jako separátory. Předpokládáme, že takové nestandardní tokeny se ve zpracovávaném textu nikdy neobjeví, protože vykřičník a otazník mají normálně kategorii 12. \inext{bgroup}{\empty}{+-} Jak to pracuje si ukážeme na příkladu "\replacestrings{XX}{YY}", pokud máme v "\tmpb" uložen třeba text "ahaXXuffXXkonec". Makra "\replacestringsA" a "\replacestringsB" jsou v takovém případě definována jako: \begtt \def\replacestringsA #1XX{\def\tmpb{#1}\replacestringsB} \def\replacestringsB #1XX{\ifx!#1\relax\else \addto\tmpb{YY#1}\expandafter\replacestringsB\fi}% \endtt % a jednotlivé kroky zpracování probíhají takto: \begtt \replacestringsA ahaXXuffXXkonec?XX!XX #1 = "aha" zbytek fronty = "uffXXkonec?XX!" \def\tmpb{aha} \replacestringsB uffXXkonec?XX!XX #1 = "uff" zbytek fronty = "konec?XX!" \addto\tmpb{YYuff}, tj. \tmpb obsahuje "ahaYYuff". \replacestringsB konec?XX!XX #1 = "konec?" zbytek fronty = "!XX" \addto\tmpb{YYkonec?}, tj. \tmpb obsahuje "ahaYYuffYYkonec?" \replacestringsB !XX #1 = ! zbytek fronty prázdný, rekurze končí \endtt % Dále se předefinuje "\def\replacestrin""gsA#1?{\def\tmpb{#1}}" a provede se \begtt \replacestringsA ahaYYuffYYkonec? #1 = "ahaYYuffYYkonec" \def\tmpb{ahaYYuffYYkonec} \endtt % tedy tímto algoritmem odstraníme koncový otazník. Proč jsme ho tam vlastně dávali? Kdyby tam nebyl, tak by nesprávně fungovalo "\replacestrings{XX}{YY}" při "\tmpb" ve tvaru "ahaX". Makro "\replacestrings" je kompromisem mezi jednoduchostí a přijatelnými možnostmi. Nefunguje nad textem s nespárovanými "\if...\fi" a také při "\def\tmpb{{aha}XX}\replacestrings{XX}{YY}" se bohužel odstraní kučeravé závorky kolem "aha". Můžete třeba přidat před každou dvojici takových závorek "\empty", abyste měli jistotu, že závorky nezmizí. \subsec Globální parametry %%%%%%%%%%%%%%%%%%%%%%%%%% Zakážeme vdovy a sirotky a dále nastavíme registry pro listingy tiskového materiálu na smysluplnější hodnoty, než jsou implicitní. \inext {widowpenalty}{\empty}{+-} Následující makra a registry ovlivní chování klíčových maker OPmac způsobem, jak je popsáno v~komentářích. Mnohé z těchto maker a registrů byly zmíněny v uživatelské dokumentaci. \dgn \nb iindent \dgn \nb ttindent \dgn \nb ttskip \dgn \nb ttpenalty \dgn \nb tthook \dgn \nb intthook \dgn \nb ptthook \dgn \nb iiskip \dgn \nb itemhook \dgn \nb bibskip \dgn \nb tabstrut \dgn \nb tabiteml \dgn \nb tabitemr \dgn \nb vvkern \dgn \nb hhkern \dgn \nb multiskip \dgn \nb colsep \dgn \nb mnoteindent \dgn \nb mnotesize \dgn \nb titskip \dgn \nb picdir \dgn \nb bibtexhook \dgn \nb chaphook \dgn \nb sechook \dgn \nb secchook \dgn \nb cnvhook \dgn \nb prepghook \dgn \nb pghook \dgn \nb toclinehook \dgn \nb fnotehook \dgn \nb mnotehook \dgn \nb captionhook \inext {newdimen}{\count=7 \empty}{+-} \subsec Loga %%%%%%%%%%%% V logu \db OPmac je pomocí "\thefontscale" zvětšeno písmeno O. Logo \db CS je přepsáno beze změny z CS\TeX{}u. Tím snadno vytvoříme i logo \db csplain. \inext {def\nb OPmac}{\empty}{+-} Troufám si tvrdit, že logo \db LaTeX (ačkoli je plain\TeX{}isté asi moc nebudou potřebovat) je v~následujícím kódu daleko lépe řešeno, než v samotném La\TeX{}u. Počítá totiž ve spolupráci s makrem \db slantcorr i se sklonem písma při usazování zmenšeného A. \inext {LaTeX}{\empty}{+-} Loga se občas mohou vyskytnout v nadpisech. Zabezpečíme je tedy proti rozboření při zápisu do REF souboru. \inext {addprotect}{\empty}{+-} \subsec Velikosti fontů, řádkování %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \csplain{} od verze "" definuje makro \db resizefont "", které změní velikost fontu daného svým přepínačem a tento změněný font si ponechá stejný přepínač. Změna velikosti je dána obsahem makra \db sizespec. Tam může být například napsáno "at13pt" nebo "scaled800". Dále CSplain definuje makro \db resizeall, které změní velikost fontů s registrovanými přepínači. Registrování se provádí makrem \db regfont. Implicitně jsou registrovány přepínače "\tenrm", "\tenit", "\tenbf", "\tenbi", a "\tentt". Do nových velikostí tedy půjdeme se starými názvy přepínačů "\ten" a to slovo "ten" budeme chápat jen jako historický relikt, který nám ovšem napoví, že kontrolní sekvence je fontovým přepínačem. OPmac si zjistí, zda je definovaný "\regfont" (tj. je detekován dostatečně nový csplain). Pokud ne, upozorní na nedostupnost vicejazyčné podpory na terminálu a potřebná makra pro fonty si definuje. Je to kopie kódu ze souboru "csfontsm.tex" z balíčku \csplain{}. \inext {regfont}{\empty}{+-} Makra "\typosize", "\fontsizex", "\textfontsize", "\setbaselineskip" požadují zápis parametru bez jednotky. Jednotkou je \db ptunit, která je nastavena na 1pt. Uživatel může jednotku změnit (např. "\ptunit=1mm" při návrhu plakátu). Dále \db fontdim je registr, který udává aktuální velikost písma. \inext {ptunit}{\empty}{+-} Implicitně jsou zavedeny \CS{}fonty, takže k nim přidáme AMS fonty z "ams-math.tex", které vizuálně odpovídají. Později si může uživatel zavést jiné makro (např. "tx-math.tex") a zavede si třeba i jiné textové fonty. To nezmění vlastnosti maker v OPmac, pokud nové soubory maker správně předefinují makra "\setmathsizes[/