Obsah
1. Programovatelný sázecí systém LuaTeX: interní stav LuaTeXu a využití mechanismu callback funkcí
2. Použití standardního výstupu a logovacího souboru pro jednoduché ladění skriptů
3. Devátý demonstrační příklad: využití funkce debugprint()
4. Expanze TeXovských maker podruhé
5. Desátý demonstrační příklad: výpis expandovaného a neexpandovaného makra \TeX
6. Čtení čítačů nadstavby LaTeX ve skriptech
7. Jedenáctý demonstrační příklad: přečtení hodnot čítačů „page“, „section“ a „subsection“
8. Přístup ke stavovým proměnným systému LuaTeX i ke knihovním modulům
9. Dvanáctý demonstrační příklad: výpis všech stavových proměnných systému LuaTeX
10. Mechanismus callback funkcí systému LuaTeX
11. Třináctý demonstrační příklad: výpis všech callback funkcí
12. Čtrnáctý demonstrační příklad: setříděný výpis všech callback funkcí
13. Příklad callback funkce – „hyphenate“
14. Patnáctý demonstrační příklad: realizace prázdné callback funkce volané při dělení slov
15. Repositář s popsanými demonstračními příklady
1. Programovatelný sázecí systém LuaTeX: interní stav LuaTeXu a využití mechanismu callback funkcí
V předchozích dvou částech [1] [2] tohoto seriálu jsme se seznámili zejména se způsobem použití příkazu \directlua i s novým LaTeXovským prostředím luacode. Taktéž jsme si ukázali, jak je možné oddělit vlastní dokument od skriptů psaných v programovacím jazyce Lua, což je v mnoha ohledech poměrně užitečná vlastnost. Nicméně prozatím celá interakce mezi TeXem (resp. jeho upraveným jádrem LuaTeX) a interpretrem jazyka Lua spočívala v použití funkcí tex.print() a tex.sprint(), které dokázaly do vstupního bufferu TeXu přidat nový řetězec, který TeX následně zpracoval stejným způsobem, jakoby byl tento text zapsán přímo do vstupního dokumentu.
Velká síla LuaTeXu však ve skutečnosti spočívá v tom, že pro zpracovávaný dokument se v paměti vytváří datová struktura složená z takzvaných uzlů (nodes), které je možné vhodným způsobem modifikovat a tak přímo ovlivňovat způsob sazby dokumentu. Je například možné do všech míst, v nichž se algoritmy TeXu snažily o rozdělení slova, vložit nějakou značku, vykreslit do dokumentu na určené místo graf atd. Navíc LuaTeX podporuje koncept takzvaných callback funkcí, které do značné míry zjednodušují práci s uzly, protože callback funkce jsou zavolány v přesně specifikovaných okamžicích. Vhodnou kombinací uživatelsky definovaných callback funkcí a úpravou uzlů je tak možné dosáhnout mnohdy i velmi komplikovaných efektů.
2. Použití standardního výstupu a logovacího souboru pro jednoduché ladění skriptů
Ještě předtím, než si ukážeme další možnosti interakce mezi algoritmy TeXu a uživatelskými skripty naprogramovanými v jazyku Lua, si ukažme poměrně užitečnou funkci nazvanou debugprint(), jejíž poslední verzi naleznete v souboru debugprint.lua. Tato funkce je ve skutečnosti až triviálně jednoduchá – řetězec, který je jí předán jako jediný parametr, je nejdříve vytištěn na standardní výstup s využitím standardní funkce print() a posléze vložen do vstupního bufferu TeXu další v LuaTeXu standardní funkcí tex.print(). Společně s tím, že je řetězec vypsán na standardní výstup, dojde k jeho uložení do logovacího souboru, který má jméno shodné se zpracovávaným dokumentem, samozřejmě až na odlišnou příponu (.log namísto .tex):
function debugprint(str) print(str) tex.print(str) end
Poznámka: pokud je nutné zapisovat nějaká data do odlišného souboru, lze použít příkaz \openout, který umožňuje otevření až 127 dalších souborů. Alternativně je samozřejmě možné využít možností nabízených samotným interpretrem jazyka Lua a jeho knihovnami – modul io a popř. os.execute (s tím, že mohou nastat problémy s přenositelností, pokud se bude nějakým způsobem pracovat s adresářovou strukturou).
3. Devátý demonstrační příklad: využití funkce debugprint()
Ukažme si nyní dva způsoby použití nově definované funkce debugprint(). V souboru pojmenovaném test9.lua je umístěna deklarace dvou dalších uživatelských funkcí naprogramovaných ve skriptovacím jazyku Lua. První z těchto funkcí se jmenuje factorial() a setkali jsme se s ní již v předchozí části tohoto seriálu ve dvou demonstračních příkladech. Druhá funkce se jmenuje poweroftwo(). Tato funkce slouží pro vytvoření tabulky (resp. přesněji řečeno interní části tabulky – jejích řádků, ovšem ne již hlavičky), a to právě s využitím volání uživatelsky definované debugprint(). To mj. znamená, že se seznam druhých mocnin dvojky objeví jak ve vysázeném dokumentu, tak i v logovacím souboru generovaném LuaTeXem (a samozřejmě též na standardním výstupu):
function factorial(n) if n <= 1 then return 1 else return n * factorial(n-1) end end function poweroftwo(from,to) for n = from,to do debugprint(n .. "&" .. math.pow(2, n)) debugprint("\\\\") end end
Dokument, který tento soubor se skriptem načte a následně použije v něm definované funkce, vypadá následovně:
\documentclass{article} \usepackage{luacode} \directlua{dofile("debugprint.lua")} \directlua{dofile("test9.lua")} \newcommand*{\factorial}[1]{% \directlua{debugprint(factorial(#1))}% } \begin{document} \section*{Faktorial} \subsection*{verze 3} \begin{tabular}{|r|r|} \hline $n$ & $n!$ \\ \hline 1 & \factorial{1} \\ 10 & \factorial{10} \\ 100 & \factorial{100} \\ \hline \end{tabular} \section*{Mocniny dvou} \subsection*{verze 4} \begin{tabular}{|r|r|} \hline $n$ & $2^n$ \\ \hline \directlua{poweroftwo(1,16)} \hline \end{tabular} \end{document}

Obrázek 1: První část dokumentu obsahující tabulku s faktoriály 1!, 10! a 100!.
Po spuštění LuaTeXu (v našem konkrétním případě LuaLaTeXu) by se na standardní výstup a současně i do logovacího souboru měly vypsat přibližně následující řádky. Konkrétní čísla verzí, cesty ke stylům apod. se sice mohou lišit, ale část s výpočtem faktoriálu a tabulky s druhými mocninami dvojky by měla vypadat stejně, jako je tomu ve výpisu:
~$ lualatex test9.tex This is LuaTeX, Version beta-0.76.0-2013061217 (rev 4627) restricted \write18 enabled. (./test9.tex LaTeX2e <2011/06/27> Babel <3.9f> and hyphenation patterns for 2 languages loaded. (/usr/share/texlive/texmf-dist/tex/latex/base/article.cls Document Class: article 2007/10/19 v1.4h Standard LaTeX document class (/usr/share/texlive/texmf-dist/tex/latex/base/size10.clo)) (/usr/share/texlive/texmf-dist/tex/lualatex/luacode/luacode.sty (/usr/share/texlive/texmf-dist/tex/generic/oberdiek/ifluatex.sty) (/usr/share/texlive/texmf-dist/tex/luatex/luatexbase/luatexbase.sty (/usr/share/texlive/texmf-dist/tex/generic/oberdiek/luatex.sty (/usr/share/texlive/texmf-dist/tex/generic/oberdiek/infwarerr.sty) (/usr/share/texlive/texmf-dist/tex/latex/etex-pkg/etex.sty) (/usr/share/texlive/texmf-dist/tex/generic/oberdiek/luatex-loader.sty (/usr/share/texlive/texmf-dist/scripts/oberdiek/oberdiek.luatex.lua))) (/usr/share/texlive/texmf-dist/tex/luatex/luatexbase/luatexbase-compat.sty) (/usr/share/texlive/texmf-dist/tex/luatex/luatexbase/luatexbase-modutils.sty (/usr/share/texlive/texmf-dist/tex/luatex/luatexbase/luatexbase-loader.sty (/usr/share/texlive/texmf-dist/tex/luatex/luatexbase/luatexbase.loader.lua)) (/usr/share/texlive/texmf-dist/tex/luatex/luatexbase/modutils.lua)) (/usr/share/texlive/texmf-dist/tex/luatex/luatexbase/luatexbase-regs.sty) (/usr/share/texlive/texmf-dist/tex/luatex/luatexbase/luatexbase-attr.sty (/usr/share/texlive/texmf-dist/tex/luatex/luatexbase/attr.lua)) (/usr/share/texlive/texmf-dist/tex/luatex/luatexbase/luatexbase-cctb.sty (/usr/share/texlive/texmf-dist/tex/luatex/luatexbase/cctb.lua)) (/usr/share/texlive/texmf-dist/tex/luatex/luatexbase/luatexbase-mcb.sty (/usr/share/texlive/texmf-dist/tex/luatex/luatexbase/mcb.lua)))) No file test9.aux. 1 3628800 9.3326215443944e+157 1&2 \\ 2&4 \\ 3&8 \\ 4&16 \\ 5&32 \\ 6&64 \\ 7&128 \\ 8&256 \\ 9&512 \\ 10&1024 \\ 11&2048 \\ 12&4096 \\ 13&8192 \\ 14&16384 \\ 15&32768 \\ 16&65536 \\ [1{/usr/share/texlive/texmf-dist/fonts/map/pdftex/updmap/pdftex.map}] (./test9.aux) ) 265 words of node memory still in use: 2 hlist, 1 vlist, 1 rule, 2 glue, 40 glue_spec, 1 write nodes avail lists: 2:13,3:13,4:705,5:6,6:159,7:91,9:82,10:3 </usr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmbx12.pfb></usr/s hare/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi10.pfb></usr/share/te xlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi7.pfb></usr/share/texlive/te xmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb> Output written on test9.pdf (1 page, 37960 bytes). Transcript written on test9.log.

Obrázek 2: Druhá část dokumentu obsahující tabulku s mocninami čísla 2.
4. Expanze TeXovských maker podruhé
Ještě jednou se na chvíli vraťme k problematice expanze TeXovských maker. Vzhledem k tomu, že nyní již máme k dispozici pomocnou funkci debugprint(), můžeme mnohem lépe prozkoumat, jak přesně se LuaTeX při expanzi maker chová. Nejprve si uvedeme variantu desátého demonstračního příkladu bez použití funkce debugprint(). Při sazbě dokumentu se na standardní výstup (a současně i do příslušného logovacího souboru) vypíše makro \TeX takovým způsobem, jak ho „vidí“ skript naprogramovaný v jazyku Lua. Posléze se vypíše výsledek příkazu \noexpand\TeX, opět stylem, jak ho vidí skript vytvořený v programovacím jazyku Lua. Vzhledem k tomu, že jak expandovaná, tak i neexpandovaná makra se do skriptů předávají formou řetězce, lze snadno zjistit délku těchto řetězců (ve skutečnosti je funkce pro výpočet délky řetězce nepatrně upravena pro správné chování v případě vstupu v UTF-8):
\documentclass{article} \usepackage{luacode} \begin{document} \section*{Macro expansion} \directlua{print("------------------")} \directlua{print([[\TeX]])} \directlua{print("------------------")} \directlua{print([[\noexpand\TeX]])} \directlua{print("------------------")} \directlua{tex.print(string.len([[\TeX]]))} \\ \directlua{tex.print(string.len([[\noexpand\TeX]]))} \end{document}
Rozdíl mezi vysázeným dokumentem a řetězcem, který je skutečně předán do vstupního bufferu LuaTeXu, asi nejlépe ilustruje tato část logovacího souboru:
------------------ T\kern -.1667em\lower .5ex\hbox {E}\kern -.125emX\spacefactor \@m ------------------ \TeX ------------------
Vidíme, že první řetězec předaný do Lua skriptu již obsahoval expandované makro (základní příkazy TeXu), zatímco ve druhém případě se do Lua skriptu předalo původní makro, ovšem již bez prefixu \noexpand.

Obrázek 3: Dokument s vysázenou délkou řetězce představujícího expandované a neexpandované makro \TeX. Vidíme, že expandované makro má délku 66 znaků, neexpandované pak pouhých pět znaků.
5. Desátý demonstrační příklad: výpis expandovaného a neexpandovaného makra \TeX
Druhá verze tohoto (v pořadí již desátého) demonstračního příkladu může využít novou funkci debugprint(), a to konkrétně následujícím způsobem:
\documentclass{article} \usepackage{luacode} \directlua{dofile("debugprint.lua")} \begin{document} \section*{Macro expansion} \directlua{print("------------------")} \directlua{debugprint([[\TeX]])} \\ \directlua{print("------------------")} \directlua{debugprint([[\noexpand\TeX]])} \\ \directlua{print("------------------")} \directlua{tex.print(string.len([[\TeX]]))} \\ \directlua{tex.print(string.len([[\noexpand\TeX]]))} \end{document}
V logovacím souboru bude podle očekávání zobrazena stejná informace:
------------------ T\kern -.1667em\lower .5ex\hbox {E}\kern -.125emX\spacefactor \@m ------------------ \TeX ------------------
Poznámka: ukázku vygenerovaného dokumentu zde záměrně neuvádím, protože se jedná o malý kvíz pro čtenáře – dokážete bez spuštění LuaTeXu popsat, jak bude vysázený dokument vypadat a proč?
6. Čtení čítačů nadstavby LaTeX ve skriptech
V některých případech může být velmi užitečné mít možnost přistoupit k čítačům používaným nadstavbou LaTeX. V LaTeXu jsou čítače využívány ve chvílích, kdy je zapotřebí nějakým způsobem označit (či očíslovat) určitý textový objekt či část dokumentu. Typickým příkladem jsou kapitoly a podkapitoly, protože zde se hodnoty čítačů přímo vkládají do sázených dokumentů. Podobně je tomu v případě seznamů, číslovaných vzorců či poznámek pod čarou. Ovšem ve skutečnosti je možné vytvářet i nové čítače, přičemž každému čítači je přiřazeno unikátní jméno a současně i styl výpisu hodnoty čítače (arabské číslice, římské číslice atd.). Mezi základní příkazy pro deklaraci čítačů a změnu jejich hodnoty patří:
Zápis | Význam |
---|---|
\newcounter{JménoČítače} | vytvoření nového čítače |
\stepcounter{JménoČítače} | zvýšení hodnoty čítače o jedničku (často používáno) |
\setcounter{JménoČítače}{hodnota} | nastavení hodnoty čítače na specifikovanou hodnotu |
\addtocounter{JménoČítače}{hodnota} | zvýšení hodnoty čítače o specifikovanou hodnotu |
Pro přístup k hodnotám čítačů existují tyto příkazy:
Zápis | Význam |
---|---|
\value{JménoČítače} | hodnota čítače bez formátování („čisté“ číslo) |
\theJménoČítače | hodnota čítače se vrátí jako naformátovaný řetězec (vše se píše jako jedno slovo) |
\arabic{JménoČítače} | hodnota čítače ve formě řetězce obsahujícího arabské číslo |
Mezi podporované formáty čítače patří především:
Výchozí formát | Ukázka |
---|---|
\arabic | 1, 2, 3 … |
\alph | a, b, c … |
\Alph | A, B, C … |
\roman | i, ii, iii … |
\Roman | I, II, III … |
\fnsymbol | sekvence symbolů, použito například pro poznámky pod čarou |
V LaTeXu jsou již předdefinovány následující čítače, jejichž význam je zřejmý už z jejich jména:
- part
- chapter
- section
- subsection
- subsubsection
- paragraph
- subparagraph
- page
- figure
- table
- footnote
- mpfootnote
Pro prostředí enumerate existují tyto čtyři čítače:
- enumi
- enumii
- enumiii
- enumiv
Pro prostředí eqnarray ještě jeden čítač navíc:
- equation
K hodnotám čítačů lze ze skriptu přistupovat (při čtení) velmi jednoduše, což si ostatně ukážeme na demonstračním příkladu popsaném v navazující kapitole.
7. Jedenáctý demonstrační příklad: přečtení hodnot čítačů „page“, „section“ a „subsection“
K hodnotě vybraného čítače se ve skriptu sice nedá přímo přistoupit (alespoň ne zcela jednoduše a přímočaře), ovšem můžeme použít malý trik a číst či zapisovat do čítačů nepřímo – pokud se Lua funkci předá parametr pojmenovaný například \thepage, je tento parametr ještě před zavoláním Lua funkce expandován a předá se tak ve skutečnosti již hodnota čítače. O nastavení čítače na jinou hodnotu se postará volání tex.print(„\setcounter{JménoČítače}{hodnota}“) popř. tex.print(„\addtocounter{JménoČítače}{hodnota}“). Toto volání lze snadno implementovat v uživatelské funkci. Podívejme se na první případ – čtení hodnot čítačů nazvaných „page“, „section“ a „subsection“:
\documentclass{article} \usepackage{luacode} \begin{document} \section{First section} \subsection{First subsection} \directlua{tex.print(\thepage)} \\ \directlua{tex.print(\thesection)} \\ \directlua{tex.print(\thesubsection)} \\ \subsection{Second subsection} \directlua{tex.print(\thepage)} \\ \directlua{tex.print(\thesection)} \\ \directlua{tex.print(\thesubsection)} \\ \section{Second section} \directlua{tex.print(\thepage)} \\ \directlua{tex.print(\thesection)} \\ \directlua{tex.print(\thesubsection)} \\ \end{document}

Obrázek 4: Dokument, do něhož se postupně vypisovaly hodnoty čítačů nazvaných „page“, „section“ a „subsection“.
8. Přístup ke stavovým proměnným systému LuaTeX i ke knihovním modulům
Kromě základních modulů (knihoven) dostupných ve všech variantách interpretru programovacího jazyka Lua se v LuaTeXu používají i další moduly, především pak moduly tex, callback a lua. Díky tomu, že jazyk Lua ukládá prakticky všechny údaje do tabulek a metatabulek, není nic jednoduššího, než si obsah všech těchto modulů/knihoven nechat vypsat. Povšimněte si, že se následující sekvencí tří programových smyček vypíšou i odkazy na funkce, ovšem ne již těla samotných funkcí. Je tomu tak z jednoduchého důvodu – samotný interpret má totiž po překladu funkcí k dispozici pouze jejich bajtkód a případné ladicí informace (čísla řádků atd.):
\directlua{ print("------------------") for k,v in pairs(tex) do print(k, v) end print("------------------") for k,v in pairs(callback) do print(k, v) end print("------------------") for k,v in pairs(lua) do print(k,v) end print("------------------") }
Pokud zmíněnou trojici programových smyček skutečně použijete v testovacím dokumentu, měly by se v průběhu sazby na standardní výstup vypsat mj. i tyto řádky (konkrétní reference se opět budou odlišovat):
------------------ catcode table: 0x24f88c0 setnest function: 0x4a12d0 setlist function: 0x4a5f30 gettoks function: 0x4a5620 getnest function: 0x4a5b50 pdfxformname function: 0x4a15d0 settoks function: 0x4a56a0 setmathcode function: 0x4a3c20 write function: 0x4a4610 count table: 0x24f9a90 getbox function: 0x4a4f10 tprint function: 0x4a4620 sprint function: 0x4a45f0 setbox function: 0x4a4f70 ... ... ... fontname function: 0x4a1660 ------------------ find function: 0x47eff0 register function: 0x2496910 list function: 0x47ef60 ------------------ bytecode table: 0x2460d40 setbytecode function: 0x48f6b0 setluaname function: 0x48fa80 version Lua 5.2 name table: 0x24ffef0 getbytecode function: 0x48f930 getluaname function: 0x48f570 ------------------
Důležitější jsou však hodnoty dostupné s využitím modulu nazvaného jednoduše status. Jedná se jak o konfigurační hodnoty, z nichž mnohé jsou nastavené při překladu LuaTeXu a další se nastavují v průběhu iniTeXu, tak i o hodnoty, které se mění v průběhu sazby. Těchto konfiguračních hodnot existuje poměrně velké množství a postupně se s nimi seznámíme. Následující tabulka není úplná, ale měla by postačovat pro základní orientaci:
Klíč | Význam |
---|---|
filename | jméno právě zpracovávaného vstupního souboru (dokumentu) |
linenumber | číslo načítaného řádku ve vstupním souboru (dokumentu) |
output_file_name | jméno výstupního souboru |
total_pages | počet již vysázených stránek |
pdf_gone | počet vygenerovaných a zapsaných bajtů ve výsledném PDF souboru |
pdf_ptr | počet nezapsaných bajtů do PDF |
dvi_gone | počet vygenerovaných a zapsaných bajtů ve výsledném DVI souboru |
dvi_ptr | počet nezapsaných bajtů do DVI |
log_name | jméno logovacího souboru |
str_ptr | počet řetězců (aktuální hodnota) |
init_str_ptr | počet řetězců v iniTeXu |
max_string | maximální povolený (nastavený) počet řetězců |
node_mem_usage | řetězec s naformátovanými informacemi o využití paměti |
font_ptr | počet aktivně použitých fontů |
stack_size | aktuální velikosti interních zásobníků a bufferů |
nest_size | aktuální velikosti interních zásobníků a bufferů |
buf_size | aktuální velikosti interních zásobníků a bufferů |
save_size | aktuální velikosti interních zásobníků a bufferů |
param_size | aktuální velikosti interních zásobníků a bufferů |
max_in_stack | maximální velikosti interních zásobníků a bufferů |
max_nest | maximální velikosti interních zásobníků a bufferů |
max_buf | maximální velikosti interních zásobníků a bufferů |
max_save | maximální velikosti interních zásobníků a bufferů |
max_param | maximální velikosti interních zásobníků a bufferů |
9. Dvanáctý demonstrační příklad: výpis všech stavových proměnných systému LuaTeX
Seznam všech interních konstant a proměnných popisujících stav LuaTeXu (viz též předchozí kapitolu), je možné si nechat vypsat například následujícím jednoduchým programem využívajícím funkci status.list(). Vzhledem k tomu, že se má vypsat poměrně dlouhá tabulka, která přesahuje rámec jednoho listu A4, používá se zde prostředí longtable (někdy je nutné příslušný balíček doinstalovat). Ve výpisu zdrojového kódu dokumentu si povšimněte, že znaky použité v některých hodnotách by bylo zapotřebí lépe ošetřit (hodnota proměnné best_page_break je dobrým příkladem).

Obrázek 5: První část tabulky s konfiguračními hodnotami.
Podívejme se na úplný zdrojový kód tohoto příkladu:
\documentclass{article} \usepackage{luacode} \usepackage{longtable} \begin{document} \section*{Status} \begin{luacode*} tex.print("\\begin{longtable}{|l|p{7cm}|}\\hline") tex.print("key&value\\\\") tex.print("\\hline") local stat = status.list() for key,val in pairs(stat) do tex.print(key:gsub("_", "\\_") .. "&" .. tostring(val):gsub("_", "\\_")) tex.print("\\\\") end tex.print("\\hline") tex.print("\\end{longtable}") \end{luacode*} \end{document}

Obrázek 6: Další část tabulky s konfiguračními hodnotami.
10. Mechanismus callback funkcí systému LuaTeX
Jak jsme si již řekli v úvodní kapitole, spočívá velká síla LuaTeXu v tom, že pro zpracovávaný dokument se v paměti vytváří datová struktura složená z takzvaných uzlů (nodes), které je možné vhodným způsobem modifikovat a tak přímo ovlivňovat způsob sazby dokumentu. Tato modifikace se provádí v takzvaných callback funkcích volaných v přesně specifikovaných okamžicích. Existují dva typy callback funkcí – jeden typ je určen pro rozšíření původní funkcionality, druhý typ pro náhradu původní funkcionality jiným algoritmem. Některé callback funkce se volají ve chvíli, kdy se hledají či otevírají různé typy souborů (typicky soubory s fonty), další callback funkce se volají při sazbě dokumentu a existuje i několik typů callback funkcí volaných při vytváření PDF výstupu.
Callback funkce, které mohou modifikovat interní podobu struktury dokumentu, jsou volány s parametrem či parametry odkazujícími na takzvané uzly (nodes). Každý uzel je vlastně objektem, jemuž je nastaven typ a hodnoty (například text na řádku či odstavci). Typ uzlu je specifikován celým číslem:
Typ uzlu | Číslo |
---|---|
hlist | (0) |
vlist | (1) |
rule | (2) |
ins | (3) |
mark | (4) |
adjust | (5) |
boundary | (6) |
disc | (7) |
whatsit | (8) |
local_par | (9) |
dir | (10) |
math | (11) |
glue | (12) |
kern | (13) |
penalty | (14) |
unset | (15) |
style | (16) |
choice | (17) |
noad | (18) |
radical | (19) |
fraction | (20) |
accent | (21) |
fence | (22) |
math_char | (23) |
sub_box | (24) |
sub_mlist | (25) |
math_text_char | (26) |
delim | (27) |
margin_kern | (28) |
glyph | (29) |
align_record | (30) |
pseudo_file | (31) |
pseudo_line | (32) |
page_insert | (33) |
split_insert | (34) |
expr_stack | (35) |
nested_list | (36) |
span | (37) |
attribute | (38) |
glue_spec | (39) |
attribute_list | (40) |
temp | (41) |
align_stack | (42) |
movement_stack | (43) |
if_stack | (44) |
unhyphenated | (45) |
hyphenated | (46) |
delta | (47) |
passive | (48) |
shape | (49) |
11. Třináctý demonstrační příklad: výpis všech callback funkcí
Následující demonstrační příklad slouží k vytvoření dokumentu, v němž se do tabulky (resp. přesněji řečeno do prostředí longtable) vypíšou jména všech událostí, pro něž je možné zaregistrovat uživatelsky definované callback funkce. Získaná jména nejsou žádným způsobem setříděna, takže se do vysázeného dokumentu vypíšou v takovém pořadí, jaké odpovídá použité hašovací funkci interpretru programovacího jazyka Lua (teoreticky se tedy pořadí může na vašem systému lišit, prakticky je však dnes použit v LuaTeXu již stabilní interpret jazyka Lua, takže je to nepravděpodobné).

Obrázek 7: První část tabulky s callback funkcemi.
Podívejme se na zdrojový kód tohoto příkladu:
\documentclass{article} \usepackage{luacode} \usepackage{longtable} \begin{document} \section*{Callback functions} \begin{luacode*} tex.print("\\begin{longtable}{|l|l|}\\hline") tex.print("key&value\\\\") tex.print("\\hline") local callbacks = callback.list() for name,val in pairs(callbacks) do val = callbacks[name] tex.print(name:gsub("_", "\\_") .. "&" .. tostring(val):gsub("_", "\\_")) tex.print("\\\\") end tex.print("\\hline") tex.print("\\end{longtable}") \end{luacode*} \end{document}

Obrázek 8: Druhá část tabulky s callback funkcemi.
12. Čtrnáctý demonstrační příklad: setříděný výpis všech callback funkcí
Následující demonstrační příklad vznikl nepatrnou úpravou příkladu předchozího. Ukazují se v něm některé možnosti programovacího jazyka Lua a taktéž jeho jediné datové struktury – pole. Nejdříve je získán seznam všech možných volání callback funkcí (stejně, jako tomu bylo v předchozím příkladu):
local callbacks = callback.list()
Následně je seznam jmen vložen do pole. To se zde chová jako běžná tabulka, nikoli jako asociativní pole; je tedy zaručeno pořadí vložení prvků atd.:
local callbacks = callback.list() local names = {} for key,val in pairs(callbacks) do table.insert(names, key) end
Pole se jmény se seřazeno standardní funkcí table.sort():
table.sort(names)
Seřazený seznam jmen je následně použit při výpisu tabulky. Jména zde slouží jako klíč do původního pole callbacks:
tex.print("\\begin{longtable}{|r|l|l|}\\hline") tex.print("i&key&value\\\\") tex.print("\\hline") for i,name in ipairs(names) do val = callbacks[name] tex.print(i .. "&", name:gsub("_", "\\_") .. "&" .. tostring(val):gsub("_", "\\_")) tex.print("\\\\") end tex.print("\\hline") tex.print("\\end{longtable}")
Povšimněte si způsobu náhrady znaku „_“, který má v TeXu speciální význam, za dvojici znaků „\_“ (první zpětné lomítko je zde uvedeno proto, že tento znak má v jazyku Lua speciální význam; to již ostatně víme z předchozích dvou částí tohoto seriálu).

Obrázek 9: První část tabulky s callback funkcemi.
Úplný zdrojový kód dokumentu obsahujícího tento demonstrační příklad vypadá následovně:
\documentclass{article} \usepackage{luacode} \usepackage{longtable} \begin{document} \section*{Callback functions} \begin{luacode*} tex.print("\\begin{longtable}{|r|l|l|}\\hline") tex.print("i&key&value\\\\") tex.print("\\hline") local callbacks = callback.list() local names = {} for key,val in pairs(callbacks) do table.insert(names, key) end table.sort(names) for i,name in ipairs(names) do val = callbacks[name] tex.print(i .. "&", name:gsub("_", "\\_") .. "&" .. tostring(val):gsub("_", "\\_")) tex.print("\\\\") end tex.print("\\hline") tex.print("\\end{longtable}") \end{luacode*} \end{document}

Obrázek 10: Druhá část tabulky s callback funkcemi.
13. Příklad callback funkce – „hyphenate“
Vyzkoušejme si nyní vytvořit si vlastní callback funkci, která bude volaná ve chvíli, kdy interní algoritmy TeXu zjistí, že by bylo vhodné rozdělit nějaké slovo. V tento okamžik se zavolá námi deklarovaná callback funkce, která může (ale také nemusí!) modifikovat interní datové uzly LuaTeXu popisující zpracovávaný dokument. Samotná callback funkce v první verzi pouze vypíše informace o dvou uzlech, které jsou jí předány. V praxi to znamená, že k žádnému rozdělení slova ve skutečnosti nedojde, neboť obsah uzlů zůstane nezměněn:
function x(head,tail) print() print("hyphenation:") print(head) print(tail) end
Tuto funkci je nutné do LuaTeXu zaregistrovat, a to konkrétně takto:
luatexbase.add_to_callback("hyphenate", x, "description", 0)
Poznámka: v dokumentaci můžete najít odlišný kód (knihovní funkci) určený pro zaregistrování callback funkce:
callback.register("hyphenate", x)
Tento kód však již pravděpodobně nebude správně interpretován; uvádím ho zde jen kvůli úplnosti.
14. Patnáctý demonstrační příklad: realizace prázdné callback funkce volané při dělení slov
Mechanismus registrace uživatelsky definované callback funkce je ukázán v dnešním posledním demonstračním příkladu. Jedná se o dokument obsahující delší text, který by byl při použití původních algoritmů TeXu na několika místech zalomen. Ovšem „díky“ tomu, že jsme zaregistrovali vlastní callback funkci volanou ve chvíli, kdy má dojít k dělení slova a tato funkce nijak neovlivňuje strukturu dokumentu, nebude ve skutečnosti rozdělení nikdy provedeno, o čemž se lze snadno přesvědčit při pohledu na vysázený dokument:

Obrázek 11: Povšimněte si, že ve vysázeném dokumentu nedošlo k rozdělení ani jednoho slova, protože to naše callback funkce x neumí.
Celý zdrojový kód dokumentu s textem i příslušnou callback funkcí vypadá následovně:
\documentclass{article} \usepackage{luacode} \begin{document} \section*{Callback functions} \begin{luacode*} function x(head,tail) print() print("hyphenation:") print(head) print(tail) end luatexbase.add_to_callback("hyphenate", x, "description", 0) \end{luacode*} This section will guide you through the formatting techniques of the text. Formatting tends to refer to most things to do with appearance, so it makes the list of possible topics quite eclectic: text style, spacing, etc. If formatting may also refer to paragraphs and to the page layout, we will focus on the customization of words and sentences for now. A lot of formatting techniques are required to differentiate certain elements from the rest of the text. It is often necessary to add emphasis to key words or phrases. Footnotes are useful for providing extra information or clarification without interrupting the main flow of text. So, for these reasons, formatting is very important. However, it is also very easy to abuse, and a document that has been over-done can look and read worse than one with none at all. \LaTeX is so flexible that we will actually only skim the surface, as you can have much more control over the presentation of your document if you wish. Having said that, one of the purposes of LaTeX is to take away the stress of having to deal with the physical presentation yourself, so you need not get too carried away! \end{document}
V průběhu sazby se funkce pro rozdělení slov volá hned několikrát, o čemž se můžeme snadno přesvědčit pohledem do logovacího souboru:
No file test15.aux. hyphenation: <node nil < 30 > 485 : temp 0> <node 2534 < 2544 > nil : glue 0> hyphenation: <node nil < 30 > 2685 : temp 0> <node 5432 < 5442 > nil : glue 0> hyphenation: <node nil < 74 > 5770 : temp 0> <node 74 < 5770 > nil : glyph 1> hyphenation: <node nil < 138 > 234 : temp 0> <node 138 < 234 > nil : rule 0> hyphenation: <node nil < 76 > 5800 : temp 0> <node 76 < 5800 > nil : glyph 1> hyphenation: <node nil < 74 > 5785 : temp 0> <node 74 < 5785 > nil : glyph 1> hyphenation: <node nil < 30 > 5647 : temp 0> <node 7633 < 7643 > nil : glue 0> hyphenation: <node nil < 140 > 234 : temp 0> <node 140 < 234 > nil : rule 0> hyphenation: <node nil < 142 > 8069 : temp 0> <node 8023 < 8073 > nil : glue 0> [1{/usr/share/texlive/texmf-dist/fonts/map/pdftex/updmap/pdftex.map}] (./test15.aux) ) 266 words of node memory still in use: 2 hlist, 1 vlist, 1 rule, 2 glue, 40 glue_spec, 1 write nodes avail lists: 1:1,2:13,3:12,4:284,5:74,6:976,7:14,9:27,10:7 </usr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmbx12.pfb></usr/s hare/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb></usr/share/tex live/texmf-dist/fonts/type1/public/amsfonts/cm/cmr7.pfb> Output written on test15.pdf (1 page, 36532 bytes). Transcript written on test15.log.
15. Repositář s popsanými demonstračními příklady
Demonstrační příklady popsané v dnešním článku byly uloženy do veřejného Git repositáře, z něhož si je můžete snadno stáhnout a otestovat ve své instalaci LuaTeXu (nezapomeňte přitom na to, že se v některých příkladech používá prostředí longtable):
Poznámka: některé dokumenty budou pravděpodobně na vaší instalaci LuaTeXu vysázeny nepatrné odlišným způsobem. Jedná se především o dokumenty zobrazující interní konstanty a proměnné popisující stav LuaTeXu atd.
16. Odkazy na Internetu
- LuaTex
http://www.luatex.org/ - LuaTex: dokumentace
http://www.luatex.org/documentation.html - LuaTex Wiki
http://wiki.luatex.org/index.php/Main_Page - LuaTeX (Wikipedia)
https://en.wikipedia.org/wiki/LuaTeX - Paper o LuaTeXu
https://www.tug.org/TUGboat/tb28–3/tb90hoekwater-luatex.pdf - TeX (Wikibooks)
https://en.wikibooks.org/wiki/TeX - LaTeX (Wikibooks)
https://en.wikibooks.org/wiki/LaTeX - LaTeX (Wikibooks, PDF verze)
https://upload.wikimedia.org/wikipedia/commons/2/2d/LaTeX.pdf - The Latin Modern (LM) Family of Fonts
http://www.gust.org.pl/projects/e-foundry/latin-modern - Sázecí system TeX
https://www.phil.muni.cz/~letty/tex/ - CSTeX – česká a slovenská podpora TeXu
http://petr.olsak.net/cstex.html - Proč nerad používám LaTeX
http://petr.olsak.net/ftp/olsak/bulletin/nolatex.pdf - εχTEX
http://www.extex.org/index.html - PlainTeX (Wikipedia)
https://cs.wikipedia.org/wiki/PlainTeX - What is the difference between \def and \newcommand? (SO)
http://tex.stackexchange.com/questions/655/what-is-the-difference-between-def-and-newcommand#658 - LaTeX/Macros
https://en.wikibooks.org/wiki/LaTeX/Macros - TeX (StackExchange)
http://tex.stackexchange.com/ - LaTeX/Counters
https://en.wikibooks.org/wiki/LaTeX/Counters - LuaTeX: Questions and Answers (kniha)
https://www.amazon.com/LuaTeX-Questions-Answers-George-Duckett/dp/1534601163?ie=UTF8&SubscriptionId=AKIAILSHYYTFIVPWUY6Q&camp=2025&creative=165953&creativeASIN=1534601163&linkCode=xm2&&tag=duckduckgo-d-20