Programovatelný sázecí systém LuaTeX

19. 7. 2016
Doba čtení: 10 minut

Sdílet

Dnes se seznámíme se základními vlastnostmi sázecího systému nazvaného LuaTeX, což je vylepšená verze TeXu podporující mj. OpenType fonty a umožňující tvorbu maker v programovacím jazyce Lua.

Obsah

1. Programovatelný sázecí systém LuaTeX

2. Z jakého důvodu vlastně LuaTeX vznikl?

3. Původní TeX a jeho systém maker

4. Skriptovací jazyk Lua a LuaTeX

5. Proč zrovna Lua?

6. Příkaz \directlua

7. První demonstrační příklad – použití příkazu \directlua

8. Příkazové závorky luacode

9. Druhý demonstrační příklad – použití příkazových závorek luacode

10. Chování znaku zpětného lomítka

11. Třetí demonstrační příklad – chování znaku zpětného lomítka

12. Odkazy na Internetu

1. Programovatelný sázecí systém LuaTeX

Sázecí systém TeX pravděpodobně není nutné čtenářům Roota podrobně představovat, ostatně vycházel o něm samostatný a poměrně podrobný seriál. Původní Knuthův TeX je sice velmi robustní a taktéž stabilní sázecí systém, ovšem právě jeho stabilita (de facto zmražení kódu) způsobuje, že se do TeXu nedají jednoduše přidávat další vlastnosti či upravovat jeho interní struktury (například způsob kódování znaků; což v současnosti zabraňuje plnohodnotnému použití Unicode). Taktéž makrosystém TeXu je pro mnoho uživatelů těžko uchopitelný. Není tedy divu, že vzniklo hned několik projektů, jejichž snahou bylo zachovat kvality původního TeXu a jeho algoritmů pro sazbu a na stranu druhou uspokojit nově vznikající požadavky. Jedná se například o projekty New Typesetting System (NTS, dnes již prakticky mrtvý), ε-TEX, pdfTEX, Ω (Omega), εχTEX a v neposlední řadě právě LuaTeX.

Hlavním cílem tvůrců projektu LuaTeX bylo vytvoření takové varianty systému TeX, v níž by bylo možné ovlivňovat celý průběh sazby dokumentu s využitím skriptů napsaných v programovacím jazyce Lua. Důležité přitom je, že ono zmíněné ovlivňování sazby se týká prakticky všech kroků, od načítání dokumentu, hledání souborů s fonty či expanze TeXových maker až po modifikování entit a příkazů vkládaných do výsledného souboru PDF. Kromě toho došlo i k několika dalším podstatným úpravám, které sice LuaTeX přibližují dnešním požadavkům, ovšem v některých případech mohou způsobit, že LuaTeX není na 100% zpětně kompatibilní s původním Knuthovým TeXem (jedná se například o plnohodnotnou podporu Unicode v průběhu celého zpracování dokumentu, což je vlastnost, která se objevila v jednom z předchůdců LuaTeXu – systému Omega). S naprostou většinou nekompatibilit se však běžní uživatelé nesetkají a i takové rozsáhlé balíčky, jakým je LaTeX, je možné s LuaTeXem použít.

2. Z jakého důvodu vlastně LuaTeX vznikl?

Uživatelé TeXu či jeho pravděpodobně nejznámější nadstavby LaTeXu se nyní možná ptají, proč vlastně LuaTeX vznikl, když už samotný TeX je programovatelný na úrovni maker. Ostatně existence samotného LaTeXu ukazuje, do jaké míry je TeX programovatelný a rozšiřitelný. Ve skutečnosti jsou ovšem možnosti původního TeXu v této oblasti poněkud omezené, protože s využitím maker je možné ovlivnit průběh vlastní sazby (makra se zpracovávají po tokenizaci zdrojových dokumentů), ovšem například manipulaci s fonty či výstupním formátem nelze jednoduše zařídit (existují pouze specializované nástroje typu PSTricks, které ovšem předpokládají použití konkrétního výstupního formátu – zde konkrétně PostScriptu). Pěkným příkladem, jak lze využít manipulaci s výstupním formátem (PDF) v LuaTeXu, je přímo dokumentace k tomuto systému. Pokud si tento dokument zobrazíte stylem „zobraz dvoustranu“ (view dual) a „zobraz celé stránky “ (fit page), je při stránkování patrné, jak se symbol Měsíce v logu jazyka Lua zobrazeného na spodním okraji stránek postupně otáčí okolo Země.

3. Původní TeX a jeho systém maker

I vlastní systém maker TeXu není bez chyby, zejména v porovnání s moderními skriptovacími jazyky – nabízí menší flexibilitu (základní příkazy jsou relativně nízkoúrovňové) a při tvorbě rozsáhlejších balíčků maker či při současném používání většího množství balíčků lze narazit na problémy s oddělením jednotlivých modulů (jmenné prostory atd.). Navíc není prakticky možné odstínit uživatele systému maker (například opět LaTeXu) od vlastního TeXu, což znamená, že při výskytu nějaké chyby v dokumentu se mnohdy vypíše interní chybová zpráva TeXu, které uživatel nemusí rozumět (o tomto problému se zmiňuje i Petr Olšák).

Jen pro ilustraci se podívejme na příklad, jak může vypadat část zdrojového kódu v TeXu. Nejedná se v žádném případě o zcela nečitelný zápis, ovšem ke srozumitelnosti zápisu, jaké nabízí moderní skriptovací jazyky typu Python či Lua, má tento zápis daleko :-):

\newcount\licount
\def\li#{\advance\licount by 1 \bgroup\aftergroup\par
  \noindent\llap{\the\licount.\enspace}\ignorespaces\let\next= }
\def\listend{\smallskip\noindent\ignorespaces}
\def\nlist#{\bgroup\licount=0\par\nobreak
  \advance\leftskip by\parindent \aftergroup\listend\let\next= }

4. Skriptovací jazyk Lua a LuaTeX

Namísto (možná marné) snahy o úpravy makrosystému TeXu se autoři LuaTeXu rozhodli celý původní systém TeX (resp. přesněji řečeno jeho algoritmy) přepsat takovým způsobem, aby uživatelé mohli ovlivnit sazbu vytvářeného dokumentu v programovacím jazyku Lua, což je jazyk, který náleží do poměrně rozsáhlé a stále častěji používané skupiny vysokoúrovňových skriptovacích jazyků, do níž můžeme zařadit například populární Python, Groovy, Ruby, na síle nabývající JavaScript či dnes již poněkud méně populární jazyky Perl a Tcl. Tyto programovací jazyky nabízí vývojářům jednoduchou práci se strukturovanými daty (většinou je použita nějaká forma asociativního pole – slovníku), dynamicky typované proměnné, automatickou správu paměti (garbage collector) a mnohé další vysokoúrovňové techniky zjednodušující a zrychlující vývoj. Jazyk Lua má navíc velmi jednoduchou – a pro mnoho vývojářů důvěrně známou – syntaxi inspirovanou Modulou a Pascalem, zatímco sémantika tohoto jazyka se v mnohém podobá spíše moderním verzím JavaScriptu.

5. Proč zrovna Lua?

V mnoha případech se také využívá další užitečné vlastnosti jazyka Lua – celý překladač i interpretr vygenerovaného bajtkódu je možné velmi snadno zabudovat do jiné aplikace, přičemž se výsledná velikost spustitelného souboru této aplikace zvětší o cca 70 až 100 kB (popř. lze volat dynamickou knihovnu o řádově stejné velikosti), což není mnoho, když si uvědomíme, že dostáváme k dispozici plnohodnotný vysokoúrovňový programovací jazyk. V případě LuaTexu však pro jazyk Lua (a ne například pro konkurenční Python) mluví ještě jedna důležitá věc, kterou si blíže osvětlíme v dalším textu – zdrojové kódy napsané v jazyku Lua jsou platné i ve chvíli, kdy „zmizí“ konce řádků, což je přesně situace, která nastává v LuaTeXu. Samozřejmě najdeme některé výjimky, kdy mají konce řádku svůj význam, například jednořádkové komentáře, ovšem například následující dva kódy jsou totožné, a to i přes absenci středníků či jakéhokoli jiného oddělovače:

a=1
b=10
 
print(a)
 
for i=a,b do
    print(i)
    if i==5 then
        break
    end
end
a=1 b=10 print(a) for i=a,b do print(i) end

6. Příkaz \directlua

V LuaTeXu se dokumenty zapisují naprosto stejným způsobem, jako je tomu v TeXu (popř. v LaTeXu jakožto nadstavbě TeXu). Jedním z viditelných rozdílů je přímá podpora Unicode, přičemž se předpokládá, že vstupní soubory budou používat kódování UTF-8. Do LuaTeXu byly přidány nové příkazy, z nichž důležitý je především příkaz nazvaný \directlua, který umožňuje, aby se do dokumentu zapsal skript či část skriptu vytvořeného v jazyce Lua. Příkaz \directlua pracuje následujícím způsobem:

  1. Text je nejdříve expandován, což znamená, že se zpracují případná TeXovská makra (skript v Lua tedy už „uvidí“ jejich expandovanou podobu, ovšem toto chování lze ovlivnit).
  2. Konce řádků jsou převedeny na mezery (pozor tedy na jednořádkové komentáře zapisované do Lua skriptu).
  3. Posléze je takto zpracovaný text předán interpretru jazyka Lua.
  4. Skript napsaný v jazyce Lua může například tisknout zprávy na standardní výstup (uloží se do běžného logu), ovlivnit interní datové struktury LuaTeXu (pro tento účel je však mnohem lepší použít speciální callback funkce popsané příště) a taktéž použít funkce nazvané tex.print() a tex.sprint()
  5. Výstup (řetězec) produkovaný funkcemi tex.print() a tex.sprint() bude dále zpracován TeXem, protože tento výstup bude vložen do vstupního bufferu. TeX vlastně nijak nerozpozná, že výstup byl vytvořen skriptem a nikoli zapsán přímo do vstupního dokumentu.
  6. Každý příkaz \directlua je skriptem vykonán samostatně, což mj. znamená, že skripty mezi sebou nesdílí lokální proměnné.

Poznámka: tex.sprint() se od tex.print() odlišuje tím, že neignoruje mezery na začátku řetězce a taktéž nepřidává automaticky příkaz \endlinechar.

7. První demonstrační příklad – použití příkazu \directlua

Podívejme se nyní na způsob použití výše zmíněného nového příkazu \directlua. Tento příkaz využijeme v jednoduchém LaTeXovém dokumentu, do něhož je na druhém řádku importován balíček nazvaný luacode (ten zde sice není nutný, ovšem v dalších příkladech ho využijeme). První volání příkazu \directlua způsobí výpis náhodného čísla vygenerovaného funkcí math.random() do výsledného dokumentu (tato funkce je součástí standardní knihovny jazyka Lua), druhé volání vypíše do dokumentu hodnotu čísla π s využitím konstanty math.pi, třetí volání vypíše aktuální datum a čas a konečně čtvrté volání vypíše do výsledného dokumentu odrážku. Toto volání je nejzajímavější neboť zde můžeme vidět, že se mezi znaky před a za příkazem \directlua nemusí psát mezery (zde je ve výsledném dokumentu nechceme, ekvivalentem je totiž zápis a–b):

\documentclass{article}
\usepackage{luacode}
 
\begin{document}
 
\section*{Random number generator}
 
A random number:
\directlua{tex.print(math.random())}
 
\section*{$\pi$}
 
$\pi = \directlua{tex.print(math.pi)}$
 
\section*{Date}
 
Date:
\directlua{tex.print(os.date())}
 
\section*{Combination}
 
a\directlua{tex.print("--")}b
 
\end{document}

Pro překlad tohoto příkladu použijte následující příkaz (předpokládá se samozřejmě, že je nainstalován jak LuaTeX, tak i další podpůrné balíčky):

lualatex example1.tex

Obrázek 1: Dokument vygenerovaný prvním demonstračním příkladem.

8. Příkazové závorky luacode

Pro zápis delších skriptů je možné použít i „příkazové závorky“ luacode, které se do zdrojových dokumentů zapisují následujícím způsobem:

\begin{luacode}
    skript v jazyce Lua
    skript v jazyce Lua
    skript v jazyce Lua
\end{luacode}

I přesto, že by se mohlo zdát, že skripty zapsané do těchto „závorek“ jsou čitelnější, je pro delší kódy mnohem výhodnější spíše zavolat externí skript:

\directlua{ dofile('jmeno_souboru.lua')}

Přednost tohoto způsobu spočívá v tom, že se nemusí složitě řešit zpracování speciálních znaků typu zpětného lomítka, znaku pro procenta atd.

Další způsob spočívá v použití „příkazových závorek“ luacode*:

\begin{luacode*}
    skript v jazyce Lua
    skript v jazyce Lua
    skript v jazyce Lua
\end{luacode*}

Zde nedochází k případné expanzi TeXovských maker, čehož je možné v některých případech využít (přesné rozdíly mezi luacode a luacode* si vysvětlíme příště).

9. Druhý demonstrační příklad – použití příkazových závorek luacode

Podívejme se nyní na způsob použití \begin{luacode} a \end{luacode}. Je to ve skutečnosti velmi jednoduché (pokud ovšem nedojde na makra :-), jak to ostatně ukazuje zdrojový kód tohoto příkladu:

\documentclass{article}
\usepackage{luacode}
 
\begin{document}
 
\section*{Random number generator}
 
A random number:
\begin{luacode}
tex.print(math.random())
\end{luacode}
 
\section*{Date}
 
Date:
\begin{luacode}
tex.print(os.date())
\end{luacode}
 
\end{document}

Obrázek 2: Dokument vygenerovaný druhým demonstračním příkladem.

10. Chování znaku zpětného lomítka

Poměrně problematická je práce se znakem procenta a taktéž se znakem zpětného lomítka. U zpětného lomítka je tomu ze dvou důvodů:

  1. Tento znak má speciální význam pro TeX (příkladem je makro\TeX)
  2. Současně má tento znak speciální význam i pro jazyk Lua (například v řetězcích typu Hello World\n)

Vzhledem k tomu, že nejdříve dochází k expanzi TeXovských maker, může být práce se zpětnými lomítky v příkazu \directlua poměrně nešikovná. Jedním z možných řešení je uložení řetězců do závorek [[ a ]]. Ty ruší význam \n a neznámé sekvence \T:

\directlua{tex.sprint([[\noexpand\TeX]])}

Druhý způsob spočívá ve využití luacode* popsané výše. Následující příkaz vytiskne logo TeXu (lomítko je zde zdvojené z toho důvodu, že se v řetězci jedná o escape sekvenci, samotný TeX však uvidí pouze \TeX):

\begin{luacode*}
tex.print("\\TeX")
\end{luacode*}

Samotné zpětné lomítko se vytiskne takto:

\begin{luacode*}
tex.print("\\textbackslash")
\end{luacode*}

popř. v matematickém režimu (může vypadat odlišně):

tex.print("$\\backslash$")

Pozor si dejte taktéž na příkaz pro vložení nového řádku, který v nejjednodušším případě vypadá takto:

\\

V Lua skriptu se ovšem musí každé zpětné lomítko zdvojit, takže namísto pouhých dvou lomítek napíšeme lomítka čtyři:

\begin{luacode*}
tex.print("\\\\")
\end{luacode*}

Ve skutečnosti, stejně jako v předchozím příkladu, uvidí TeX ve výsledku pouze dvojici zpětných lomítek \\, což již dokáže správně interpretovat (viz též LaTeX Line and Page Breaking.

11. Třetí demonstrační příklad – chování znaku zpětného lomítka

Některá chování zpětného lomítka v Lua skriptu (konkrétně v řetězcích) si ukážeme ve třetím demonstračním příkladu. Povšimněte si zakomentovaného příkazu, který nelze použít (schválně si můžete zkusit si tento příkaz odkomentovat a spustit překlad znovu):

ict ve školství 24

\directlua{--tex.sprint("\noexpand\TeX")}

Úplný zdrojový kód tohoto příkladu vypadá takto:

\documentclass{article}
\usepackage{luacode}
 
\begin{document}
 
\section*{Backslash behaviour}
 
\subsection*{luacode}
 
\begin{luacode*}
tex.print("\\TeX")
tex.print("\\\\")
tex.print("\\textbackslash")
tex.print("\\\\")
tex.print("$\\backslash$")
\end{luacode*}
 
\subsection*{directlua}
 
\directlua{--tex.sprint("\noexpand\TeX")}
 
\directlua{tex.sprint([[\noexpand\TeX]])}
 
\end{document}

Obrázek 3: Dokument vygenerovaný třetím demonstračním příkladem.

12. 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. The Latin Modern (LM) Family of Fonts
    http://www.gust.org.pl/projects/e-foundry/latin-modern
  9. Sazeci system TeX
    https://www.phil.muni.cz/~letty/tex/
  10. CSTeX – česká a slovenská podpora TeXu
    http://petr.olsak.net/cstex.html
  11. Proč nerad používám LaTeX
    http://petr.olsak.net/ftp/ol­sak/bulletin/nolatex.pdf
  12. εχTEX
    http://www.extex.org/index.html

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.