LuaTeX: interní stav a využití mechanismu callback funkcí

Pavel Tišnovský 2. 8. 2016

Ve třetí části seriálu o sázecím systému LuaTeX se budeme zabývat interními datovými strukturami a také mechanismem callback funkcí, které je možné použít pro ovlivnění sazby i generování PDF dokumentu.

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

16. Odkazy na Internetu

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(„\addtocou­nter{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:

widgety

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):

# Soubor Stručný popis Odkaz
1 test9.tex jednoduché logování https://github.com/tisnik/pre­sentations/blob/master/lu­atex/slides/test9.tex
2 test9.lua funkce factorial a poweroftwo https://github.com/tisnik/pre­sentations/blob/master/lu­atex/slides/test9.lua
3 debugprint.lua funkce pro ladicí výpisy https://github.com/tisnik/pre­sentations/blob/master/lu­atex/slides/debugprint.lua
4 test9.pdf PDF soubor vysázený LuaTeXem https://github.com/tisnik/pre­sentations/blob/master/lu­atex/slides/test9.pdf
       
5 test10.tex výpis expandovaného a neexpandovaného makra https://github.com/tisnik/pre­sentations/blob/master/lu­atex/slides/test10.tex
6 test10.pdf PDF soubor vysázený LuaTeXem https://github.com/tisnik/pre­sentations/blob/master/lu­atex/slides/test10.pdf
       
7 test11.tex přečtení hodnot vybraných čítačů https://github.com/tisnik/pre­sentations/blob/master/lu­atex/slides/test11.tex
8 test11.pdf PDF soubor vysázený LuaTeXem https://github.com/tisnik/pre­sentations/blob/master/lu­atex/slides/test11.pdf
       
9 test12.tex výpis všech stavových proměnných systému LuaTeX https://github.com/tisnik/pre­sentations/blob/master/lu­atex/slides/test12.tex
10 test12.pdf PDF soubor vysázený LuaTeXem https://github.com/tisnik/pre­sentations/blob/master/lu­atex/slides/test12.pdf
       
11 test13.tex výpis všech callback funkcí https://github.com/tisnik/pre­sentations/blob/master/lu­atex/slides/test13.tex
12 test13.pdf PDF soubor vysázený LuaTeXem https://github.com/tisnik/pre­sentations/blob/master/lu­atex/slides/test13.pdf
       
13 test14.tex setříděný výpis všech callback funkcí https://github.com/tisnik/pre­sentations/blob/master/lu­atex/slides/test14.tex
14 test14.pdf PDF soubor vysázený LuaTeXem https://github.com/tisnik/pre­sentations/blob/master/lu­atex/slides/test14.pdf
       
15 test15.tex realizace prázdné callback funkce volané při dělení slov https://github.com/tisnik/pre­sentations/blob/master/lu­atex/slides/test15.tex
16 test15.pdf PDF soubor vysázený LuaTeXem https://github.com/tisnik/pre­sentations/blob/master/lu­atex/slides/test15.pdf

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

  1. LuaTex
    http://www.luatex.org/
  2. LuaTex: dokumentace
    http://www.luatex.org/docu­mentation.html
  3. LuaTex Wiki
    http://wiki.luatex.org/in­dex.php/Main_Page
  4. LuaTeX (Wikipedia)
    https://en.wikipedia.org/wiki/LuaTeX
  5. Paper o LuaTeXu
    https://www.tug.org/TUGboat/tb28–3/tb90hoekwater-luatex.pdf
  6. TeX (Wikibooks)
    https://en.wikibooks.org/wiki/TeX
  7. LaTeX (Wikibooks)
    https://en.wikibooks.org/wiki/LaTeX
  8. LaTeX (Wikibooks, PDF verze)
    https://upload.wikimedia.or­g/wikipedia/commons/2/2d/La­TeX.pdf
  9. The Latin Modern (LM) Family of Fonts
    http://www.gust.org.pl/projects/e-foundry/latin-modern
  10. Sázecí system TeX
    https://www.phil.muni.cz/~letty/tex/
  11. CSTeX – česká a slovenská podpora TeXu
    http://petr.olsak.net/cstex.html
  12. Proč nerad používám LaTeX
    http://petr.olsak.net/ftp/ol­sak/bulletin/nolatex.pdf
  13. εχTEX
    http://www.extex.org/index.html
  14. PlainTeX (Wikipedia)
    https://cs.wikipedia.org/wi­ki/PlainTeX
  15. What is the difference between \def and \newcommand? (SO)
    http://tex.stackexchange.com/qu­estions/655/what-is-the-difference-between-def-and-newcommand#658
  16. LaTeX/Macros
    https://en.wikibooks.org/wi­ki/LaTeX/Macros
  17. TeX (StackExchange)
    http://tex.stackexchange.com/
  18. LaTeX/Counters
    https://en.wikibooks.org/wi­ki/LaTeX/Counters
  19. LuaTeX: Questions and Answers (kniha)
    https://www.amazon.com/LuaTeX-Questions-Answers-George-Duckett/dp/1534601163?ie=UT­F8&SubscriptionId=AKIAILSHY­YTFIVPWUY6Q&camp=2025&cre­ative=165953&creativeASIN=1534601163&lin­kCode=xm2&&tag=duckduckgo-d-20
Našli jste v článku chybu?
Lupa.cz: Jak levné procesory změnily svět?

Jak levné procesory změnily svět?

Vitalia.cz: Tahák, jak vyzrát nad zápachem z úst

Tahák, jak vyzrát nad zápachem z úst

Podnikatel.cz: Udělali jsme velkou chybu, napsal Čupr

Udělali jsme velkou chybu, napsal Čupr

Vitalia.cz: Tohle jsou nejlepší česká piva podle odborníků

Tohle jsou nejlepší česká piva podle odborníků

Vitalia.cz: Opuncie je plod kaktusu. Pozor na trny

Opuncie je plod kaktusu. Pozor na trny

Lupa.cz: Aukro.cz mění majitele. Vrací se do českých rukou

Aukro.cz mění majitele. Vrací se do českých rukou

Vitalia.cz: Test dětských svačinek: Tyhle ne!

Test dětských svačinek: Tyhle ne!

DigiZone.cz: DVB-T2 ověřeno: seznam TV zveřejněn

DVB-T2 ověřeno: seznam TV zveřejněn

120na80.cz: Hrbatá prsa aneb mýty o implantátech

Hrbatá prsa aneb mýty o implantátech

Vitalia.cz: Jaký je rozdíl mezi brambůrky a chipsy?

Jaký je rozdíl mezi brambůrky a chipsy?

Lupa.cz: Hackeři mají data z půlmiliardy účtů Yahoo

Hackeři mají data z půlmiliardy účtů Yahoo

Vitalia.cz: Inspekce našla nelegální sklad v SAPĚ. Zase

Inspekce našla nelegální sklad v SAPĚ. Zase

Vitalia.cz: Kterou dýni můžete jíst za syrova?

Kterou dýni můžete jíst za syrova?

Podnikatel.cz: Babišovi se nedá věřit, stěžovali si hospodští

Babišovi se nedá věřit, stěžovali si hospodští

Podnikatel.cz: Byla finanční manažerka, teď cvičí jógu

Byla finanční manažerka, teď cvičí jógu

DigiZone.cz: Mordparta: trochu podchlazený 87. revír

Mordparta: trochu podchlazený 87. revír

Vitalia.cz: 5 pravidel proti infekci močových cest

5 pravidel proti infekci močových cest

Vitalia.cz: Jak Ondra o astma přišel

Jak Ondra o astma přišel

DigiZone.cz: Numan Two: rozhlasový přijímač s CD

Numan Two: rozhlasový přijímač s CD

Podnikatel.cz: EET pro e-shopy? Postavené na hlavu

EET pro e-shopy? Postavené na hlavu