Skip to content
Snippets Groups Projects
Commit 143dfddf authored by Martin Mareš's avatar Martin Mareš
Browse files

The first light

parents
No related branches found
No related tags found
No related merge requests found
*.aux
*.bbl
*.blg
*.fmt
*.log
*.pdf
*.pid
/chapters.tex
TOP=..
include ../Makerules
\ifx\chapter\undefined
\input adsmac.tex
\singlechapter{1}
\fi
\chapter[intro]{Úvodem: Příhody Alice a Boba}
V~této kapitole si představíme čtyři základní \em{kryptografická primitiva} --
\uv{krabičky}, ze kterých je vystavěna většina kryptografických protokolů:
symetrické a asymetrické šifry, hešovací funkce a náhodné generátory. Zatím je
budeme popisovat neformálně (detaily ještě pár kapitol počkají), ale vyzkoušíme
si, jak se o~nich přemýšlí.
\section{Symetrické šifry}
Začneme jednoduchou úlohou. Alice chce svého kamaráda Boba pozvat do čajovny.
Chce mu proto po síti poslat \em{zprávu} -- nějaký řetězec znaků, pro jednoduchost
předpokládejme, že jedniček a nul. Jenže na síti nejsou sami: vždycky se najde někdo,
kdo zvědavě poslouchá, co se šustne, a~pak šíří drby. Říkejme mu třeba Eva\foot{V~angličtině
to funguje lépe: je to Eve podle slova \em{eavesdrop} s~významem \em{potají naslouchat.}}
nebo CIA.\foot{Autor zajisté ví, že americká třípísmenková agentura starající se o~odposlech
zpráv není CIA, ale NSA. Jenže copak se tady dá odolat zkratce začínající na~C?}
Alice s~Bobem se proto dohodli, že zprávy budou \em{šifrovat.} Alice zprávu zakóduje
nějakým algoritmem, který je známý jenom jí a Bobovi. Eva stále může zprávu odposlechnout,
jen už jí nebude dávat žádný smysl.
Utajovat (potenciálně složitý) algoritmus je ovšem docela nepraktické.
Místo toho algoritmus parametrizujeme \em{klíčem} -- nějakým dalším řetězcem, který ovlivňuje
šifrování a~který utajíme místo algoritmu. Alice a Bob se dopředu bezpečnou cestou shodnou
na klíči. Cokoliv Alice pomocí klíče zašifruje, Bob pomocí téhož klíče dešifruje. Ale Eva klíč
nezná, takže jí zpráva nebude srozumitelná. Tomu říkáme \df{symetrická šifra,} protože Alice a Bob
používají stejný klíč.
Obecně bychom se na symetrickou šifru mohli dívat jako na dvojici \uv{krabiček} $E$ a~$D$.
\em{Šifrovací krabička}~$E$ (\em{encryption box}) dostane \em{otevřenou zprávu (plain text)}
a~klíč a spočítá z~nich \em{zašifrovanou zprávu (cipher text)}.
\em{Dešifrovací krabička}~$D$ (\em{decription box}) dostane zašifrovanou zprávu a klíč
a spočítá původní otevřenou zprávu.
Většinou chceme, aby šifrování zachovávalo délku zprávy.
\examplen{Caesarova šifra}{
Podívejme se na triviální příklad symetrické šifry pocházející prý od Julia Caesara.
Alice chce poslat zprávu složenou z~písmen abecedy {\tt ABC}\dots{\tt Z}.
Zvolí si klíč~$k\in\{0,\ldots,25\}$ a její šifrovací krabička místo každého písmena
pošle takové, které je v~abecedě o~$k$ pozic dál (cyklicky).
Takže zprávu {\tt AHOY} při $k=3$ zašifruje na {\tt DKRB}.
Bobova dešifrovací krabička posouvá o~$k$~písmen v~opačném směru.
}
Pravidlu, že utajujeme klíč a~nikoliv algoritmus, se říká \df{Kerckhoffsův princip.}\foot{
Přišel s~ním koncem 19. století nizozemský kryptograf Auguste Kerckhoffs.}
Proč je tak důležitý?
\list{o}
\:Dobrých šifer je známých málo a je těžké je vytvořit.
\:U~prakticky žádné šifry neumíme dokázat, že je bezpečná. Je tedy lepší použít
veřejně známou šifru, kterou už hodně kryptografů studovalo a nenašli v~ní žádnou
slabinu.
\:Pokud se protivník dozví část našeho tajemství, je mnohem snazší vyměnit zkompromitovaný
klíč než algoritmus.
\endlist
\note{
Ještě dodejme, že Alice a Bob nemusí být konkrétní osoby. Jsou to \em{role} v~protokolu.
Chceme-li na zprávu odpovědět, postavy si role prohodí.
Pokud chceme bezpečně zálohovat data, Bob bude Alice někdy v~budoucnosti.
A~tak dále.
}
\section{Asymetrické šifry}
Představte si, že bychom místo dvojice Alice a Bob měli $N$~lidí, kteří si chtějí
navzájem vyměňovat šifrované zprávy. Každé zprávě přitom smí rozumět jenom odesílatel
a adresát. Použití symetrické šifry by vyžadovalo, aby se každá dvojice lidí předem
shodla na svém klíči. To znamená vytvořit a bezpečně distribuovat řádově $N^2$ klíčů,
což je moc.
Pořídíme si \df{asymetrickou šifru.} Ta má dva klíče: \em{šifrovací} a \em{dešifrovací.}
Ty lze vyrobit jen jako pár (máme-li jen jeden, nepomůže nám to k~získání druhého).
Co zašifrujeme šifrovacím klíčem, dá se dešifrovat jen příslušným dešifrovacím klíčem.
Pak stačí, aby každý z~$N$~účastníků protokolu zveřejnil svůj šifrovací klíč
(třeba v~nějakém veřejném adresáři) a ponechal si ten dešifrovací. Tím pádem
Alici stačí najít v~adresáři Bobův šifrovací klíč, zašifrovat pomocí něj zprávu
a tu pak umí dešifrovat jen Bob.
Pozor na to, že klíče mají dva druhy vlastností:
\em{veřejný} vs. \em{soukromý} a \em{šifrovací} vs. \em{dešifrovací}.
V~našem protokolu je šifrovací klíč veřejný a dešifrovací soukromý.
Zajímavá je i~opačná kombinace: zveřejníme-li dešifrovací klíč a ponecháme si šifrovací,
získáme něco, co se chová jako \em{podpis.} Každý si může zprávu přečíst, ale jenom majitel
příslušného soukromého klíče ji umí vytvořit. Všimněte si rozdílu: šifra zajišťuje \em{utajení}
obsahu zprávy (nepovolané osoby ji neumí přečíst), zatímco podpis zajišťuje \em{integritu}
(pokud nepovolaná osoba zprávu změní, přijde se na to). Často budeme chtít kombinovat obojí.
Časem se ukáže, že naše triviální použití veřejných klíčů má zásadní slabinu:
Nic nebrání útočníkovi podstrčit do veřejného adresáře svůj klíč místo klíče oběti.
Později tento problém vyřeší certifikační autority.
\section{Hešovací funkce}
\section{Náhodné generátory}
\endchapter
\pseudochapter[literatura]{Literatura}
{
\raggedright
\def\bblhook{\parskip=2pt plus 1pt minus 0.5pt}
\def\bblsc{\csc}
\def\bbland{a}
\def\bblvolume{svazek}
\def\bbledition{vydání}
\def\bblnewblock{\hskip .11em minus .07em} % Neroztahuje se, máme \raggedright
\def\urlprefix{Dostupné online na }
% Hack: BibTeX does not support UTF-8 properly
\def\EE{É}
\bibliography{bibliography}
}
\pseudochapter{Nápovědy k~cvičením}
\def\currentchapter{nápovědy}
{
\parindent=0pt
\parskip=0.5\interitemskip
\leftskip=4em
\long\def\posthint#1#2{%
{
\leavevmode
\llap{#1.~~}#2
\endgraf
}
}
\immediate\closeout\hints
\input hints.aux
}
\null
\vskip 2cm
\centerline{\setfonts[/20]\bf Úvod do kryptografie}
\bigskip
\bigskip
\centerline{\setfonts[/16]Martin Mareš}
\bigskip
\centerline{\setfonts[/12]\it mares@kam.mff.cuni.cz}
\bigskip
\bigskip
\centerline{Katedra aplikované matematiky}
\smallskip
\centerline{Matematicko-fyzikální fakulta}
\smallskip
\centerline{Univerzita Karlova, Praha}
\vskip 1.5cm
{
\leftskip=0.1\hsize
\rightskip=\leftskip
\noindent
Tento text uvádí čtenáře do základů kryptografie, a~to jak teoretické,
tak ji aplikované. Vzniká jako skripta k~předmětu Úvod do kryptografie
na MFF UK. Jistě by si zasloužil hezčí předmluvu, leč tu dosud nikdo nenapsal.
Milý čtenáři, buď varován, že se jedná o~pracovní verzi, která jistě není dokonalá.
Najdeš-li libovolnou chybu (což zahrnuje i těžko srozumitelné pasáže), dej prosím
vědět autorovi. Díky!
}
{
\headline={}
\vfill\eject
}
\fakechapter{Obsah}
\def\tocchaplabel#1#2^^X{\if0#1\else#1#2~\fi}
\def\tocchapter#1#2#3#4{\bigskip
\line{\bf\tocchaplabel#2^^X\toclink{#2}{#4}\hfil\pagelink{#1}}
\tocoutline{#2}{#4}
}
\def\tocsection#1#2#3#4{%
\line{\qquad#2#3~~\toclink{#2}{#4}\hfil\pagelink{#1}}
\tocoutline{#2}{#4}
}
\def\toclink#1#2{%
\ifprint
#2
\else
\pdfextension startlink\commonlinkargs goto name {s#1}\relax #2\pdfextension endlink
\fi
}
\def\tocoutline#1#2{
\ifprint\else
\directlua{pdfmeta.add_outline('\luaescapestring{#1}', '\luaescapestring{s#1}', '\luaescapestring{#2}')}
\fi
}
\def\tocstar{\rlap{*}}
{
\catcode`@=11
\catcode`*=\active
\let*=\tocstar
\input toc.aux
\catcode`@=12
}
\bigskip
Sections and exercises marked with one or two asterisks contain advanced material.
We suggest that the reader skips them first time.
\vfill\eject
Makefile 0 → 100644
MAIN=crypto
CUSTOM_UPLOAD=1
all: $(MAIN).pdf sync-view
$(MAIN).pdf: $(MAIN).tex chapters
ebook: chapters
include Makerules
CHAPTERS= \
01-intro
chapters:
for ch in $(CHAPTERS) ; do $(MAKE) -C $$ch pics ; done
for ch in $(CHAPTERS) ; do echo "\\includechapter{$$ch}" ; done >chapters.tex
clean::
for ch in $(CHAPTERS) $(EXTRAS) ; do $(MAKE) -C $$ch clean ; done
rm -f chapters.tex
upload-all:
for ch in $(CHAPTERS) ; do $(MAKE) -C $$ch upload ; done
#upload: $(MAIN).pdf
# rsync $(MAIN).pdf jabberwock:www/static/vyuka/dsnotes/
# ssh jabberwock 'cd web && make'
.PHONY: chapters upload upload-all
Makerules 0 → 100644
ifdef TOP
export TEXINPUTS=.:$(TOP)/tex//:
# Full name: XX-YYY
FULL:=$(shell basename `pwd`)
# Main file name: only the YYY part
MAIN:=$(shell echo $(FULL) | cut -d- -f2)
all: $(MAIN).pdf sync-view
else
export TOP=.
export TEXINPUTS=.//:
FULL_BOOK=1
endif
export TEXFORMATS=$(TOP)/tex:
export ASYMPTOTE_HOME=$(TOP)/pics
TOOLS=$(TOP)/tools
SHELL=/bin/bash
LUATEX?=luatex
TEXFMT=luaplain
TEXFMTPATH=$(TOP)/tex/$(TEXFMT).fmt
export LUAINPUTS=.:$(TOP)/tex//:
export OSFONTDIR=$(TOP)/tex/fonts
export BIBINPUTS=$(TOP)/bib
export BSTINPUTS=$(TOP)/bib
MACROS:=$(wildcard $(TOP)/tex/*.tex) $(wildcard $(TOP)/tex/*.lua)
ifdef FULL_BOOK
# When typesetting the whole book, call TeX three times to calculate all references properly
%.pdf: %.tex $(MACROS) $(TEXFMTPATH) $(TOP)/bib/bibliography.bib pics
rm -f *.aux && >toc.aux
$(LUATEX) -file-line-error -fmt $(TEXFMT) $<
$(TOOLS)/gen-index <index-raw.aux >index-sorted.aux
mv toc-new.aux toc.aux
# [ ! -f $*.aux ] || bibtex $*
$(LUATEX) -file-line-error -fmt $(TEXFMT) $<
$(TOOLS)/gen-index <index-raw.aux >index-sorted.aux
mv toc-new.aux toc.aux
$(LUATEX) -file-line-error -fmt $(TEXFMT) $<
EBOOK_MAIN=ebook
else
# Simplified typesetting of a single chapter: call TeX just twice
# gen-index is still called to check syntax, but its output is not used
%.pdf: %.tex $(MACROS) $(TEXFMTPATH) pics
rm -f *.aux && >toc.aux
$(LUATEX) -file-line-error -fmt $(TEXFMT) $<
$(LUATEX) -file-line-error -fmt $(TEXFMT) $<
$(TOOLS)/gen-index <index-raw.aux >index-sorted.aux
EBOOK_MAIN=$(MAIN)
endif
## EB=$(TOP)/80-ebook
## EBCONFIG=$(EB)/epub.ini
## EBTEXOPTS=\let\modeebook=\relax\def\ebookconfig{$(EBCONFIG)}
##
## ebook-test: $(EBOOK_MAIN).tex $(MACROS) $(TEXFMTPATH) pics
## $(TOOLS)/eb-init $(EBCONFIG)
## cp $(EB)/default.css epub/style/
## rm -f *.aux && >toc.aux
## $(LUATEX) -file-line-error -fmt $(TEXFMT) '$(EBTEXOPTS)\input{$<}'
##
## ebook: $(EBOOK_MAIN).tex $(MACROS) $(TEXFMTPATH) pics
## $(TOOLS)/eb-init $(EBCONFIG)
## cp $(EB)/default.css epub/style/
## $(TOOLS)/eb-fonts $(EBCONFIG) lmroman10-{regular,bold,italic}.otf
## $(TOOLS)/eb-fonts $(EBCONFIG) lmmono10-regular.otf lmromancaps10-regular.otf lmromandemi10-regular.otf
## $(TOOLS)/eb-fonts $(EBCONFIG) latinmodernmath-regular-hacked.otf
## $(TOOLS)/eb-fonts $(EBCONFIG) LiberationSerif-{Regular,Bold,Italic}.ttf
## $(TOOLS)/eb-fonts $(EBCONFIG) Montserrat-{Light,SemiBold}.otf
## $(TOOLS)/eb-fonthack $(EBCONFIG)
## $(TOOLS)/eb-pdfimages $(EBCONFIG) pics/obalka-predni.epdf pics/cznic-logo.epdf
## rm -f *.aux && >toc.aux
## $(LUATEX) -file-line-error -fmt $(TEXFMT) '$(EBTEXOPTS)\input{$<}'
## [ ! -f $(EBOOK_MAIN).aux ] || bibtex $(EBOOK_MAIN)
## mv toc-new.aux toc.aux
## $(TOOLS)/gen-index <index-raw.aux >index-sorted.aux
## $(LUATEX) -file-line-error -fmt $(TEXFMT) '$(EBTEXOPTS)\input{$<}'
## $(TOOLS)/eb-images $(EBCONFIG) $(EBOOK_MAIN).pdf
## xmllint --noout epub/html/*.html
## $(TOOLS)/eb-epub $(EBCONFIG)
##
## ebook-for-mobi: ebook
## ebook-for-mobi: EBCONFIG=$(EB)/mobi.ini
## ebook-for-mobi: EBTEXOPTS+=\let\modemobi=\relax
##
## ebook-kindle: ebook-for-mobi
## kindlegen pruvodce.epub -o pruvodce.mobi -verbose -gif
##
## pics/%.png: pics/%.epdf
## pdftocairo -png -r 200 -singlefile $< $*
pics: $(addsuffix .pdf, $(PICS))
%.pdf: %.asy $(TOP)/pics/ads.asy $(TEXFMTPATH)
PATH=$(TOP)/pics:$(PATH) asy -f pdf -tex pdftex $<
%-booklet.pdf: %-uncompressed.pdf
pspdftool 'book nup(2,paper=a4)' $< $@
%-2in1.pdf: %-uncompressed.pdf
pspdftool 'nup(2,paper=a4)' $< $@
%-uncompressed.pdf: %.pdf
qpdf --object-streams=disable --stream-data=uncompress $< $@
$(TEXFMTPATH):
@echo 'Generating $@'
cd $(dir $@) && $(LUATEX) -jobname $(TEXFMT) -ini $(TEXFMT).ini
clean::
rm -f *~ *.log *.dvi *.ps *.pdf *.aux *.bbl *.blg *.epub
rm -f view.pid
rm -f $(TEXFMTPATH) $(TOP)/tex/$(TEXFMT).log
rm -rf epub
view: $(MAIN).pdf
( /usr/lib/mupdf/mupdf-x11 $(MAIN).pdf & echo $$! >view.pid ; wait $$! ; rm -f view.pid ) &
sync-view: $(MAIN).pdf
@if [ -f view.pid ] ; then echo "Syncing viewer" ; kill -HUP `cat view.pid` ; fi
ifndef CUSTOM_UPLOAD
upload::
make $(MAIN).pdf
rsync $(MAIN).pdf jabberwock:www/static/vyuka/dsnotes/$(FULL).pdf
ssh jabberwock 'cd web && make'
endif
.PHONY: all upload clean view sync-view pics ebook ebook-test ebook-kindle
.SECONDARY:
TODO 0 → 100644
Úvod:
- co je cílem kurzu
- co od čtenáře očekáváme
- odkazy do dalších kapitol
This diff is collapsed.
This diff is collapsed.
@book { mm:ga,
author = "Martin Mareš",
title = "{Krajinou grafových algoritmů}",
series = "{ITI Series}",
volume = 330,
year = 2007,
publisher = "{Institut teoretické informatiky MFF UK Praha}",
isbn = "978-80-239-9049-2",
url = "http://mj.ucw.cz/vyuka/ga/"
}
\let\wholebook=\relax
\input adsmac.tex
%\debuggrid
%\debugindex
\input 98-misc/intro.tex
% \input 98-misc/toc.tex
\def\includechapaux#1-#2@{\input #1-#2/#2.tex}
\def\includechapter#1{\includechapaux#1@}
\input chapters.tex
\bye
/*
* Makra pro Asymptote společná pro všechny obrázky
*/
void ads_init_pic() {
unitsize(1cm);
}
ads_init_pic();
/*** Základní typy čar a velikosti ***/
pen thin = black + roundcap + 0.2mm;
pen hair = black + roundcap + 0.1mm;
pen quarterthick = thin + 0.2mm;
pen halfthick = thin + 0.4mm;
pen almostthick = thin + 0.5mm;
pen thick = thin + 0.6mm;
defaultpen(thin);
pen shortdashed = linetype(new real[] { 1, 4 });
pen middashed = linetype(new real[] { 4, 4 });
pen finedotted = thin + 0.3mm + linetype(new real[] { 0, 2.5 });
pen agray = gray(0.6);
pen area_gray = gray(0.75);
// Šířka čáry v uživatelských souřadnicích
real line_width_user(pen p)
{
pair lw = inverse(currentpicture.scaling()) * (0, linewidth(p));
return lw.y;
}
texpreamble("\font\eightrm=cmr8\font\ninerm=cmr9\font\seventt=cmtt8 at 7pt");
/*** Kreslení grafů ***/
real vertex_size = 0.06;
arrowbar e_arrow = Arrow(size=7);
arrowbar e_biarrow = Arrows(size=7);
arrowbar e_smallarrow = Arrow(size=5);
arrowbar e_smallbiarrow = Arrows(size=5);
arrowbar e_miniarrow = Arrow(size=3);
int v_invisible = -1;
int v_black = 0;
int v_white = 1;
int v_gray = 2;
int v_dashed = 3;
int v_lightgray = 4;
int v_bold = 5;
int v_shadow = 6;
void draw_in_mode(path p, int mode=0)
{
if (mode == v_black)
filldraw(p);
else if (mode == v_white)
filldraw(p, white, black);
else if (mode == v_gray)
filldraw(p, agray, black);
else if (mode == v_dashed)
filldraw(p, white, black + shortdashed);
else if (mode == v_lightgray)
filldraw(p, area_gray, black);
else if (mode == v_bold)
filldraw(p, white, black + thick);
else if (mode == v_shadow)
filldraw(p, white, agray);
}
void vertex(pair p, int mode=0)
{
if (p.x > 100 || p.y > 100) {
return;
}
draw_in_mode(circle(p, vertex_size), mode);
}
pair current_graph[];
void graph(pair g[], int mode=0)
{
current_graph = g;
for (pair p: g) {
vertex(p, mode);
}
}
void labeled_graph(pair g[])
{
vertex_size = 0.17;
graph(g, 1);
for (int i=0; i<g.length; ++i) {
if (g[i].x < 100 && g[i].y < 100) {
label("\eightrm " + (string) i, g[i]);
}
}
}
void vertex(int v, int mode=0)
{
vertex(current_graph[v], mode);
}
void edge(pair v, pair w, pen p = thin)
{
pair d = unit(w-v);
draw((v+d*vertex_size) -- (w-d*vertex_size), p + squarecap);
}
void edge(int v, int w, pen p = thin)
{
edge(current_graph[v], current_graph[w], p);
}
void edged(pair v, pair w, pair dv, pair dw, pen p = thin)
{
draw(v + vertex_size*dv {dv} .. {dw} w - vertex_size*dw, p + squarecap);
}
void edged(int v, int w, pair dv, pair dw, pen p = thin)
{
edged(current_graph[v], current_graph[w], dv, dw, p);
}
void arc(pair v, pair w, pen p = thin, arrowbar a = e_arrow)
{
pair d = unit(w-v);
draw((v+d*vertex_size) -- (w-d*vertex_size), p + squarecap, a);
}
void arc(int v, int w, pen p = thin, arrowbar a = e_arrow)
{
arc(current_graph[v], current_graph[w], p, a);
}
void arcd(pair v, pair w, pair dv, pair dw, pen p = thin, arrowbar a = e_arrow)
{
draw(v + vertex_size*dv {dv} .. {dw} w - vertex_size*dw, p + squarecap, a);
}
void arcd(int v, int w, pair dv, pair dw, pen p = thin, arrowbar a = e_arrow)
{
arcd(current_graph[v], current_graph[w], dv, dw, p, a);
}
void selfloop(pair v, int d, pen p = thin)
{
draw(v + vertex_size*dir(d+45) {dir(d+45)} .. v + 3*vertex_size*dir(d) .. {-dir(d-45)} v + vertex_size*dir(d-45), p + squarecap, e_arrow);
}
void selfloop(int v, int d, pen p = thin)
{
selfloop(current_graph[v], d, p);
}
/*** Geometrie ***/
void fpoint(pair p)
{
fill(circle(p, 0.08));
}
void epoint(pair p)
{
filldraw(circle(p, 0.08), white, black);
}
/*** Dílčí obrázky ***/
picture ads_sub_pic()
{
picture p = new picture;
currentpicture = p;
ads_init_pic();
return p;
}
frame centered(picture p)
{
frame f = p.fit();
return align(f, (0,0));
}
#!/bin/bash
# pdftex wrapper to make Asymptote use csplain
exec /usr/bin/pdftex -output-format pdf -fmt pdfcsplain "$@"
/* Společné funkce pro kreslení stromů všeho druhu */
import ads;
pair bst_edge = (0, -1);
pair bst_mini = (0, 0.5);
real tree_node_size = 0.2;
real tree_sub_size = 0.23;
real tree_sub_aspect = 1;
pair tree_label_offset = (0,0);
void tree_init(pair g[])
{
current_graph = g;
vertex_size = tree_node_size;
}
void tree_node(int i, string name = "", int mode=1)
{
vertex(i, mode);
label("\strut $" + name + "$", current_graph[i] + tree_label_offset);
}
void tree_num(int i, int x, int mode=1)
{
vertex(i, mode);
label("$" + (string) x + "$", current_graph[i] + tree_label_offset);
}
void tree_num_inverse(int i, int x)
{
vertex(i, 0);
label("$" + (string) x + "$", current_graph[i] + tree_label_offset, white);
}
void tree_ext(int i, real size=0.5, int mode=1)
{
pair p = current_graph[i];
path v = shift(p - size*(vertex_size, vertex_size)) * scale(size*2*vertex_size) * unitsquare;
draw_in_mode(v, mode);
}
void tree_sub(int i, string name = "")
{
pair p = current_graph[i];
real vsize = tree_sub_size * tree_sub_aspect;
draw(p -- p + (-1.5*tree_sub_size, -3*vsize) -- p + (1.5*tree_sub_size, -3*vsize) -- cycle);
label("\ninerm " + name, p + (0, -2*vsize));
}
void tree_elliptic_node(int i, string name = "", real elong=1, int mode=1)
{
pair p = current_graph[i];
real h = vertex_size;
real w = elong*vertex_size;
draw_in_mode(
arc(p-(w,0), h, 90, 270) --
arc(p+(w,0), h, -90, 90) --
cycle,
mode
);
label("\strut $" + name + "$", p - (0, 0.02) + tree_label_offset);
}
void tree_edge(int i, int j, pen p = thin)
{
draw(current_graph[i] -- current_graph[j], p);
}
/* AVL stromy */
void lbracket(real x, real ytop, real ybot, string name, real lpos = 0.5)
{
pair t = (x, ytop);
pair b = (x, ybot);
draw(t -- t-(0.1,0) -- b-(0.1,0) -- b);
label("$" + name + "$", point(t--b, lpos), 2*W);
}
void rbracket(real x, real ytop, real ybot, string name, real lpos = 0.5)
{
pair t = (x, ytop);
pair b = (x, ybot);
draw(t -- t+(0.1,0) -- b+(0.1,0) -- b);
label("$" + name + "$", point(t--b, lpos), 2*E);
}
real br_top_vertex(int i)
{
return current_graph[i].y + 1.2*vertex_size;
}
real br_bot_sub(int i)
{
return current_graph[i].y - 3.5*tree_sub_size;
}
void tree_sub_ht(int i, string name = "", string ht)
{
tree_sub(i, name);
label("$" + ht + "$", current_graph[i] + 3*tree_sub_size*tree_sub_aspect*S, S);
}
/* (a,b)-stromy */
void ab_edge(int i, int j, real delta = 0, pen p = thin)
{
draw(current_graph[i] + (delta,0) -- current_graph[j], p);
}
/* LLRB stromy */
pen rb_red = cmyk(0, 0.9, 0.7, 0.2) + colorless(halfthick);
pen rb_black = gray(0) + halfthick;
pen rb_any = agray + halfthick;
/* Úplné binarní stromy */
pair [] complete_binary_tree(real width, real spread, int depth)
{
pair u[];
u[0] = (0,0);
u[1] = (0,0);
int r = 1;
int w = 2;
for (int i=1; i<depth; ++i) {
real d = width / 2**i;
for (int j=0; j<2**(i-1); ++j) {
u[w] = u[r] - (d,spread);
u[w+1] = u[r] + (d,-spread);
w += 2;
++r;
}
}
return u;
}
/* Pohodlnější konstrukce stromů */
struct tnode {
int id;
pair pos;
pair direction;
tnode parent;
tnode sons[];
int size;
int mode; // Drawing mode
void draw_vertex(tnode w) { vertex(w.id, mode=this.mode); }
void draw_edge(tnode w) { if (w.parent != null) tree_edge(w.parent.id, this.id); }
};
tnode tnodes[];
pair tn_pos[];
pair tn_ref;
real tn_edge_len = 1;
void tn_init(pair refpos=(0,0))
{
tnodes = new tnode[];
tn_pos = new pair[];
tn_ref = refpos;
tree_init(tn_pos);
}
tnode tn_add(int parent_id, real angle)
{
tnode v;
v.id = tnodes.length;
v.sons = new tnode[];
v.mode = v_white;
tnodes.push(v);
pair d = (0,1) * dir(angle);
d = d * (-tn_edge_len/d.y);
if (parent_id < 0) {
v.pos = tn_ref;
} else {
v.parent = tnodes[parent_id];
v.parent.sons.push(v);
v.pos = v.parent.pos + d;
}
tn_pos.push(v.pos);
return v;
}
void tn_draw()
{
for (tnode v: tnodes)
v.draw_edge(v);
for (tnode v: tnodes)
v.draw_vertex(v);
}
This diff is collapsed.
![nodetree](https://raw.githubusercontent.com/Josef-Friedrich/nodetree/master/graphics/packagename.png)
# Abstract
`nodetree` is a development package that visualizes the structure of
node lists. `nodetree` shows its debug informations in the consoles’
output when you compile a LuaTeX file. It uses a similar visual
representation for node lists as the UNIX `tree` command uses for a
folder structure.
Node lists are the main building blocks of each document generated by
the TeX engine LuaTeX. The package `nodetree` doesn‘t change
the rendered document. The tree view can only be seen when using a
terminal to generate the document.
`nodetree` is inspired by a
[gist from Patrick Gundlach](https://gist.github.com/pgundlach/556247).
# License
Copyright (C) 2016 by Josef Friedrich <josef@friedrich.rocks>
------------------------------------------------------------------------
This work may be distributed and/or modified under the conditions of
the LaTeX Project Public License, either version 1.3 of this license
or (at your option) any later version. The latest version of this
license is in:
http://www.latex-project.org/lppl.txt
and version 1.3 or later is part of all distributions of LaTeX
version 2005/12/01 or later.
# CTAN
Since July 2016 the cloze package is included in the Comprehensive TeX
Archive Network (CTAN).
* TeX archive: http://mirror.ctan.org/tex-archive/macros/luatex/generic/nodetree
* Package page: http://www.ctan.org/pkg/nodetree
# Repository
https://github.com/Josef-Friedrich/nodetree
# Installation
Get source:
git clone git@github.com:Josef-Friedrich/nodetree.git
cd nodetree
Compile:
make
or manually:
luatex nodetree.ins
lualatex nodetree.dtx
makeindex -s gglo.ist -o nodetree.gls nodetree.glo
makeindex -s gind.ist -o nodetree.ind nodetree.idx
lualatex nodetree.dtx
# Examples
## The node list of the package name
```latex
\documentclass{article}
\usepackage{nodetree}
\begin{document}
nodetree
\end{document}
```
![nodetree](graphics/packagename.png)
## The node list of a mathematical formula
```latex
\documentclass{article}
\usepackage[callback={mhlist}]{nodetree}
\begin{document}
\[\left(a\right)\left[\frac{b}{a}\right]=a\,\]
\end{document}
```
![nodetree](https://raw.githubusercontent.com/Josef-Friedrich/nodetree/master/graphics/math.png)
## The node list of the word 'Office'
The characters 'ffi' are deeply nested in a discretionary node.
```latex
\documentclass{article}
\usepackage{nodetree}
\begin{document}
Office
\end{document}
```
![nodetree](https://raw.githubusercontent.com/Josef-Friedrich/nodetree/master/graphics/ligatures.png)
local nodex = {}
local tpl = {}
local tree = {}
tree.state = {}
local callbacks = {}
local base = {}
local options = {}
function nodex.node_id(n)
return string.gsub(tostring(n), '^<node%s+%S+%s+<%s+(%d+).*', '%1')
end
function nodex.subtype(n)
local typ = node.type(n.id)
local subtypes = {
hlist = {
[0] = 'unknown',
[1] = 'line',
[2] = 'box',
[3] = 'indent',
[4] = 'alignment',
[5] = 'cell',
[6] = 'equation',
[7] = 'equationnumber',
},
vlist = {
[0] = 'unknown',
[4] = 'alignment',
[5] = 'cell',
},
rule = {
[0] = 'unknown',
[1] = 'box',
[2] = 'image',
[3] = 'empty',
[4] = 'user',
},
adjust = {
[0] = 'normal',
[1] = 'pre',
},
boundary = {
[0] = 'cancel',
[1] = 'user',
[2] = 'protrusion',
[3] = 'word',
},
disc = {
[0] = 'discretionary',
[1] = 'explicit',
[2] = 'automatic',
[3] = 'regular',
[4] = 'first',
[5] = 'second',
},
math = {
[0] = 'beginmath',
[1] = 'endmath',
},
glue = {
[0] = 'userskip',
[1] = 'lineskip',
[2] = 'baselineskip',
[3] = 'parskip',
[4] = 'abovedisplayskip',
[5] = 'belowdisplayskip',
[6] = 'abovedisplayshortskip',
[7] = 'belowdisplayshortskip',
[8] = 'leftskip',
[9] = 'rightskip',
[10] = 'topskip',
[11] = 'splittopskip',
[12] = 'tabskip',
[13] = 'spaceskip',
[14] = 'xspaceskip',
[15] = 'parfillskip',
[16] = 'mathskip',
[17] = 'thinmuskip',
[18] = 'medmuskip',
[19] = 'thickmuskip',
[98] = 'conditionalmathskip',
[99] = 'muglue',
[100] = 'leaders',
[101] = 'cleaders',
[102] = 'xleaders',
[103] = 'gleaders',
},
kern = {
[0] = 'fontkern',
[1] = 'userkern',
[2] = 'accentkern',
[3] = 'italiccorrection',
},
noad = {
[0] = 'ord',
[1] = 'opdisplaylimits',
[2] = 'oplimits',
[3] = 'opnolimits',
[4] = 'bin',
[5] = 'rel',
[6] = 'open',
[7] = 'close',
[8] = 'punct',
[9] = 'inner',
[10] = 'under',
[11] = 'over',
[12] = 'vcenter',
},
radical = {
[0] = 'radical',
[1] = 'uradical',
[2] = 'uroot',
[3] = 'uunderdelimiter',
[4] = 'uoverdelimiter',
[5] = 'udelimiterunder',
[6] = 'udelimiterover',
},
accent = {
[0] = 'bothflexible',
[1] = 'fixedtop',
[2] = 'fixedbottom',
[3] = 'fixedboth',
},
fence = {
[0] = 'unset',
[1] = 'left',
[2] = 'middle',
[3] = 'right',
},
glyph = {
[0] = 'character',
[1] = 'ligature',
[2] = 'ghost',
[3] = 'left',
[4] = 'right',
},
}
subtypes.whatsit = node.whatsits()
local out = ''
if subtypes[typ] and subtypes[typ][n.subtype] then
out = subtypes[typ][n.subtype]
if options.verbosity > 1 then
out = out .. tpl.type_id(n.subtype)
end
return out
else
return tostring(n.subtype)
end
assert(false)
end
function tpl.round(number)
local mult = 10^(options.decimalplaces or 0)
return math.floor(number * mult + 0.5) / mult
end
function tpl.length(input)
input = tonumber(input)
input = input / tex.sp('1' .. options.unit)
return string.format('%g%s', tpl.round(input), options.unit)
end
function tpl.fill(number, order, field)
if order ~= nil and order ~= 0 then
if field == 'stretch' then
out = '+'
else
out = '-'
end
return out .. string.format(
'%gfi%s', number / 2^16,
string.rep('l', order - 1)
)
else
return tpl.length(number)
end
end
tpl.node_colors = {
hlist = {'red', 'bright'},
vlist = {'green', 'bright'},
rule = {'blue', 'bright'},
ins = {'blue'},
mark = {'magenta'},
adjust = {'cyan'},
boundary = {'red', 'bright'},
disc = {'green', 'bright'},
whatsit = {'yellow', 'bright'},
local_par = {'blue', 'bright'},
dir = {'magenta', 'bright'},
math = {'cyan', 'bright'},
glue = {'magenta', 'bright'},
kern = {'green', 'bright'},
penalty = {'yellow', 'bright'},
unset = {'blue'},
style = {'magenta'},
choice = {'cyan'},
noad = {'red'},
radical = {'green'},
fraction = {'yellow'},
accent = {'blue'},
fence = {'magenta'},
math_char = {'cyan'},
sub_box = {'red', 'bright'},
sub_mlist = {'green', 'bright'},
math_text_char = {'yellow', 'bright'},
delim = {'blue', 'bright'},
margin_kern = {'magenta', 'bright'},
glyph = {'cyan', 'bright'},
align_record = {'red'},
pseudo_file = {'green'},
pseudo_line = {'yellow'},
page_insert = {'blue'},
split_insert = {'magenta'},
expr_stack = {'cyan'},
nested_list = {'red'},
span = {'green'},
attribute = {'yellow'},
glue_spec = {'magenta'},
attribute_list = {'cyan'},
temp = {'magenta'},
align_stack = {'red', 'bright'},
movement_stack = {'green', 'bright'},
if_stack = {'yellow', 'bright'},
unhyphenated = {'magenta', 'bright'},
hyphenated = {'cyan', 'bright'},
delta = {'red'},
passive = {'green'},
shape = {'yellow'},
}
function tpl.color_code(code)
return string.char(27) .. '[' .. tostring(code) .. 'm'
end
function tpl.color(color, mode, background)
if options.color ~= 'colored' then
return ''
end
local out = ''
local code = ''
if mode == 'bright' then
out = tpl.color_code(1)
elseif mode == 'dim' then
out = tpl.color_code(2)
end
if not background then
if color == 'reset' then code = 0
elseif color == 'red' then code = 31
elseif color == 'green' then code = 32
elseif color == 'yellow' then code = 33
elseif color == 'blue' then code = 34
elseif color == 'magenta' then code = 35
elseif color == 'cyan' then code = 36
else code = 37 end
else
if color == 'black' then code = 40
elseif color == 'red' then code = 41
elseif color == 'green' then code = 42
elseif color == 'yellow' then code = 43
elseif color == 'blue' then code = 44
elseif color == 'magenta' then code = 45
elseif color == 'cyan' then code = 46
elseif color == 'white' then code = 47
else code = 40 end
end
return out .. tpl.color_code(code)
end
function tpl.key_value(key, value)
local out = tpl.color('yellow') .. key .. ': '
if value then
out = out .. tpl.color('white') .. value .. '; '
end
return out .. tpl.color('reset')
end
function tpl.char(input)
return string.format('%q', unicode.utf8.char(input))
end
function tpl.type(type, id)
local out = tpl.color(
tpl.node_colors[type][1],
tpl.node_colors[type][2]
)
.. string.upper(type)
if options.verbosity > 1 then
out = out .. tpl.type_id(id)
end
return out .. tpl.color('reset') .. ' '
end
function tpl.callback_variable(variable_name, variable)
if variable ~= nil and variable ~= '' then
tpl.print(variable_name .. ': ' .. tostring(variable))
end
end
function tpl.line(length)
if length == 'long' then
return '------------------------------------------'
else
return '-----------------------'
end
end
function tpl.callback(callback_name, variables)
tpl.print('\n\n')
tpl.print('Callback: ' .. tpl.color('red', '', true) ..
callback_name .. tpl.color('reset')
)
if variables then
for name, value in pairs(variables) do
if value ~= nil and value ~= '' then
tpl.print(' - ' .. name .. ': ' .. tostring(value))
end
end
end
tpl.print(tpl.line('long'))
end
function tpl.type_id(id)
return '[' .. tostring(id) .. ']'
end
function tpl.branch(connection_type, connection_state, last)
local c = connection_type
local s = connection_state
local l = last
if c == 'list' and s == 'stop' and l == false then
return ' '
elseif c == 'field' and s == 'stop' and l == false then
return ' '
elseif c == 'list' and s == 'continue' and l == false then
return '│ '
elseif c == 'field' and s == 'continue' and l == false then
return '║ '
elseif c == 'list' and s == 'continue' and l == true then
return '├─'
elseif c == 'field' and s == 'continue' and l == true then
return '╠═'
elseif c == 'list' and s == 'stop' and l == true then
return '└─'
elseif c == 'field' and s == 'stop' and l == true then
return '╚═'
end
end
function tpl.branches(level, connection_type)
local out = ''
for i = 1, level - 1 do
out = out .. tpl.branch('list', tree.state[i]['list'], false)
out = out .. tpl.branch('field', tree.state[i]['field'], false)
end
if connection_type == 'list' then
out = out .. tpl.branch('list', tree.state[level]['list'], true)
else
out = out .. tpl.branch('list', tree.state[level]['list'], false)
out = out .. tpl.branch('field', tree.state[level]['field'], true)
end
return out
end
function tpl.print(text)
if options.channel == 'log' then
if not log then
log = io.open(tex.jobname .. '_nodetree.log', 'a')
end
log:write(text, '\n')
else
print(' ' .. text)
end
end
function tree.format_field(head, field)
local out = ''
if not head[field] or head[field] == 0 then
return ''
end
if options.verbosity < 2 and
-- glyph
field == 'font' or
field == 'left' or
field == 'right' or
field == 'uchyph' or
-- hlist
field == 'dir' or
field == 'glue_order' or
field == 'glue_sign' or
field == 'glue_set' or
-- glue
field == 'stretch_order' then
return ''
elseif options.verbosity < 3 and
field == 'prev' or
field == 'next' or
field == 'id'
then
return ''
end
if field == 'prev' or field == 'next' then
out = nodex.node_id(head[field])
elseif field == 'subtype' then
out = nodex.subtype(head)
elseif
field == 'width' or
field == 'height' or
field == 'depth' or
field == 'kern' or
field == 'shift' then
out = tpl.length(head[field])
elseif field == 'char' then
out = tpl.char(head[field])
elseif field == 'glue_set' then
out = tpl.round(head[field])
elseif field == 'stretch' or field == 'shrink' then
out = tpl.fill(head[field], head[field .. '_order'], field)
else
out = tostring(head[field])
end
return tpl.key_value(field, out)
end
function tree.set_state(level, connection_type, connection_state)
if not tree.state[level] then
tree.state[level] = {}
end
tree.state[level][connection_type] = connection_state
end
function tree.analyze_fields(fields, level)
local max = 0
local connection_state = ''
for _ in pairs(fields) do
max = max + 1
end
local count = 0
for field_name, recursion_node in pairs(fields) do
count = count + 1
if count == max then
connection_state = 'stop'
else
connection_state = 'continue'
end
tree.set_state(level, 'field', connection_state)
tpl.print(tpl.branches(level, 'field') .. tpl.key_value(field_name))
tree.analyze_list(recursion_node, level + 1)
end
end
function tree.analyze_node(head, level)
local connection_state
local out = ''
if head.next then
connection_state = 'continue'
else
connection_state = 'stop'
end
tree.set_state(level, 'list', connection_state)
out = tpl.branches(level, 'list')
.. tpl.type(node.type(head.id), head.id)
if options.verbosity > 1 then
out = out .. tpl.key_value('no', nodex.node_id(head))
end
local fields = {}
for field_id, field_name in pairs(node.fields(head.id, head.subtype)) do
if field_name ~= 'next' and
field_name ~= 'prev' and
node.is_node(head[field_name]) then
fields[field_name] = head[field_name]
else
out = out .. tree.format_field(head, field_name)
end
end
tpl.print(out)
tree.analyze_fields(fields, level)
end
function tree.analyze_list(head, level)
while head do
tree.analyze_node(head, level)
head = head.next
end
end
function tree.analyze_callback(head)
tree.analyze_list(head, 1)
tpl.print(tpl.line('short') .. '\n')
end
function callbacks.contribute_filter(extrainfo)
tpl.callback('contribute_filter', {extrainfo = extrainfo})
return true
end
function callbacks.buildpage_filter(extrainfo)
tpl.callback('buildpage_filter', {extrainfo = extrainfo})
return true
end
function callbacks.pre_linebreak_filter(head, groupcode)
tpl.callback('pre_linebreak_filter', {groupcode = groupcode})
tree.analyze_callback(head)
return true
end
function callbacks.linebreak_filter(head, is_display)
tpl.callback('linebreak_filter', {is_display = is_display})
tree.analyze_callback(head)
return true
end
function callbacks.append_to_vlist_filter(head, locationcode, prevdepth, mirrored)
local variables = {
locationcode = locationcode,
prevdepth = prevdepth,
mirrored = mirrored,
}
tpl.callback('append_to_vlist_filter', variables)
tree.analyze_callback(head)
return true
end
function callbacks.post_linebreak_filter(head, groupcode)
tpl.callback('post_linebreak_filter', {groupcode = groupcode})
tree.analyze_callback(head)
return true
end
function callbacks.hpack_filter(head, groupcode, size, packtype, direction, attributelist)
local variables = {
groupcode = groupcode,
size = size,
packtype = packtype,
direction = direction,
attributelist = attributelist,
}
tpl.callback('hpack_filter', variables)
tree.analyze_callback(head)
return true
end
function callbacks.vpack_filter(head, groupcode, size, packtype, maxdepth, direction, attributelist)
local variables = {
groupcode = groupcode,
size = size,
packtype = packtype,
maxdepth = tpl.length(maxdepth),
direction = direction,
attributelist = attributelist,
}
tpl.callback('vpack_filter', variables)
tree.analyze_callback(head)
return true
end
function callbacks.hpack_quality(incident, detail, head, first, last)
local variables = {
incident = incident,
detail = detail,
first = first,
last = last,
}
tpl.callback('hpack_quality', variables)
tree.analyze_callback(head)
end
function callbacks.vpack_quality(incident, detail, head, first, last)
local variables = {
incident = incident,
detail = detail,
first = first,
last = last,
}
tpl.callback('vpack_quality', variables)
tree.analyze_callback(head)
end
function callbacks.process_rule(head, width, height)
local variables = {
width = width,
height = height,
}
tpl.callback('process_rule', variables)
tree.analyze_callback(head)
return true
end
function callbacks.pre_output_filter(head, groupcode, size, packtype, maxdepth, direction)
local variables = {
groupcode = groupcode,
size = size,
packtype = packtype,
maxdepth = maxdepth,
direction = direction,
}
tpl.callback('pre_output_filter', variables)
tree.analyze_callback(head)
return true
end
function callbacks.hyphenate(head, tail)
tpl.callback('hyphenate')
tpl.print('head:')
tree.analyze_callback(head)
tpl.print('tail:')
tree.analyze_callback(tail)
end
function callbacks.ligaturing(head, tail)
tpl.callback('ligaturing')
tpl.print('head:')
tree.analyze_callback(head)
tpl.print('tail:')
tree.analyze_callback(tail)
end
function callbacks.kerning(head, tail)
tpl.callback('kerning')
tpl.print('head:')
tree.analyze_callback(head)
tpl.print('tail:')
tree.analyze_callback(tail)
end
function callbacks.insert_local_par(local_par, location)
tpl.callback('insert_local_par', {location = location})
tree.analyze_callback(local_par)
return true
end
function callbacks.mlist_to_hlist(head, display_type, need_penalties)
local variables = {
display_type = display_type,
need_penalties = need_penalties,
}
tpl.callback('mlist_to_hlist', variables)
tree.analyze_callback(head)
return node.mlist_to_hlist(head, display_type, need_penalties)
end
function base.normalize_options()
options.verbosity = tonumber(options.verbosity)
options.decimalplaces = tonumber(options.decimalplaces)
end
function base.set_default_options()
local defaults = {
verbosity = 1,
callback = 'postlinebreak',
engine = 'luatex',
color = 'colored',
decimalplaces = 2,
unit = 'pt',
channel = 'term',
}
if not options then
options = {}
end
for key, value in pairs(defaults) do
if not options[key] then
options[key] = value
end
end
base.normalize_options()
end
function base.set_option(key, value)
if not options then
options = {}
end
options[key] = value
base.normalize_options()
end
function base.get_option(key)
if not options then
options = {}
end
if options[key] then
return options[key]
end
end
function base.get_callback_name(alias)
if alias == 'contribute' or alias == 'contributefilter' then
return 'contribute_filter'
elseif alias == 'buildpage' or alias == 'buildpagefilter' then
return 'buildpage_filter'
elseif alias == 'preline' or alias == 'prelinebreakfilter' then
return 'pre_linebreak_filter'
elseif alias == 'line' or alias == 'linebreakfilter' then
return 'linebreak_filter'
elseif alias == 'append' or alias == 'appendtovlistfilter' then
return 'append_to_vlist_filter'
elseif alias == 'postline' or alias == 'postlinebreakfilter' then
return 'post_linebreak_filter'
elseif alias == 'hpack' or alias == 'hpackfilter' then
return 'hpack_filter'
elseif alias == 'vpack' or alias == 'vpackfilter' then
return 'vpack_filter'
elseif alias == 'hpackq' or alias == 'hpackquality' then
return 'hpack_quality'
elseif alias == 'vpackq' or alias == 'vpackquality' then
return 'vpack_quality'
elseif alias == 'process' or alias == 'processrule' then
return 'process_rule'
elseif alias == 'preout' or alias == 'preoutputfilter' then
return 'pre_output_filter'
elseif alias == 'hyph' or alias == 'hyphenate' then
return 'hyphenate'
elseif alias == 'liga' or alias == 'ligaturing' then
return 'ligaturing'
elseif alias == 'kern' or alias == 'kerning' then
return 'kerning'
elseif alias == 'insert' or alias == 'insertlocalpar' then
return 'insert_local_par'
elseif alias == 'mhlist' or alias == 'mlisttohlist' then
return 'mlist_to_hlist'
else
return 'post_linebreak_filter'
end
end
function base.register(cb)
if options.engine == 'lualatex' then
luatexbase.add_to_callback(cb, callbacks[cb], 'nodetree')
else
id, error = callback.register(cb, callbacks[cb])
end
end
function base.register_callbacks()
for alias in string.gmatch(options.callback, '([^,]+)') do
base.register(base.get_callback_name(alias))
end
end
function base.unregister(cb)
if options.engine == 'lualatex' then
luatexbase.remove_from_callback(cb, 'nodetree')
else
id, error = callback.register(cb, nil)
end
end
function base.unregister_callbacks()
for alias in string.gmatch(options.callback, '([^,]+)') do
base.unregister(base.get_callback_name(alias))
end
end
function base.execute()
local c = base.get_callback()
if options.engine == 'lualatex' then
luatexbase.add_to_callback(c, callbacks.post_linebreak_filter, 'nodetree')
else
id, error = callback.register(c, callbacks.post_linebreak_filter)
end
end
function base.analyze(head)
tpl.print('\n')
tree.analyze_list(head, 1)
end
return base
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment