diff --git a/archiv/202503-liedblaetter-mit-typst/.htaccess b/archiv/202503-liedblaetter-mit-typst/.htaccess
new file mode 100644
index 0000000..e07af09
--- /dev/null
+++ b/archiv/202503-liedblaetter-mit-typst/.htaccess
@@ -0,0 +1 @@
+AddCharset UTF-8 .html
diff --git a/archiv/202503-liedblaetter-mit-typst/01-der_hase_augustin.pdf b/archiv/202503-liedblaetter-mit-typst/01-der_hase_augustin.pdf
new file mode 100644
index 0000000..2a893a4
Binary files /dev/null and b/archiv/202503-liedblaetter-mit-typst/01-der_hase_augustin.pdf differ
diff --git a/archiv/202503-liedblaetter-mit-typst/01-der_hase_augustin.typst b/archiv/202503-liedblaetter-mit-typst/01-der_hase_augustin.typst
new file mode 100644
index 0000000..5f465e3
--- /dev/null
+++ b/archiv/202503-liedblaetter-mit-typst/01-der_hase_augustin.typst
@@ -0,0 +1,68 @@
+#import "@preview/conchord:0.3.0": new-chordgen, smart-chord, chordify
+
+#set text(lang: "de")
+
+#set page(margin: (
+ top: 1.5cm,
+ bottom: 0cm,
+))
+
+#let chord = new-chordgen()
+#show: chordify
+
+#place(right, dx: -1em, {
+ set align(left)
+ // Make all text in chord graph bold
+ show text: strong
+ // List of used chords there
+ box([
+ #box(smart-chord("C")) \
+ #box(chord("320033", name: "G")) \
+ #box(smart-chord("Dm")) \
+ #box(smart-chord("Am")) \
+ ])
+
+})
+= Der Hase Augustin
+Frederik Vahle (GEMA!)
+
+=== #raw("[[Strophe 1]]")
+
+Es [C] war einmal ein Hase, der hieß Augustin und [G] lief unglaublich schnell. \
+Wenn [Dm] der so durch die [G] Gegend lief und [C] seine fixen [Am] Haken schlug, \
+dann [Dm] blieben alle [G] Leute stehn, um Augustin zu [C] sehn.
+
+=== #raw("[[Refrain A]]")
+
+𝄆 [C] Seht mal wer da [G] rennt, [Dm] seht mal [G] wer da [C] rennt, \
+[C] ist das nicht der [G] Augustin, [Dm] das Na[G] turta[C] lent. 𝄇 \
+
+=== #raw("[[Strophe 2]]")
+
+[C] Augustin, der flitzte, sprang über manche Pfütze und [G] aß gern Rosenkohl. \
+Doch [Dm] kam der Gärtner [G] angerannt, schon [C] war der schnelle [Am] Hase weg, \
+der [Dm] Gärtner stand im [G] Rosenkohl und staunte gar nicht [C] schlecht.
+
+=== #raw("[[Refrain B]]")
+
+𝄆 [C] Dreimal Sapper[G] ment! [Dm] Dreimal [G] Sapper[C] ment! \
+[C] Das ist wohl der [G] Augustin, [Dm] das Na[G] turta[C] lent. 𝄇
+
+
+=== #raw("[[Strophe 3]]")
+
+[C] Einmal kam ein Jäger, ein dicker fetter Jäger. Herr [G] Schlamm aus Düsseldorf. \
+Der [Dm] hatte sich 'ne [G] Jagd gekauft und [C] wollte jetzt auf [Am] Hasen gehn. \
+Da [Dm] kommt schon einer [G] angerannt, Herr Schlamm hat ihn [C] erkannt: #raw("[[Refrain B]]") \
+
+=== #raw("[[Strophe 4]]")
+
+Herr [C] Schlamm nahm seine Flinte, die knallte los und stank. Schon [G] flitzt der Hase weg, \
+die [Dm] Kugel hinter[G] her. Doch der [C] Hase war zu [Am] schnell, \
+die [Dm] Kugel fiel in'n [G] Dreck und Herr Schlamm, der schimpfte [C] sehr: #raw("[[Refrain B]]") \
+
+=== #raw("[[Strophe 5]]")
+
+[C] Augustin war stolz, er trug die Nase hoch und einen [G] Orden auch. \
+Er [Dm] wurde Landes[G] meister gar im [C] großen Zickzack[Am] dauerlauf, \
+und [Dm] bei der Ehren[G] runde sangen alle Mann ganz [C] laut: #raw("[[Refrain A]]") \
diff --git a/archiv/202503-liedblaetter-mit-typst/02-montag_und_dienstag.pdf b/archiv/202503-liedblaetter-mit-typst/02-montag_und_dienstag.pdf
new file mode 100644
index 0000000..4406989
Binary files /dev/null and b/archiv/202503-liedblaetter-mit-typst/02-montag_und_dienstag.pdf differ
diff --git a/archiv/202503-liedblaetter-mit-typst/02-montag_und_dienstag.typst b/archiv/202503-liedblaetter-mit-typst/02-montag_und_dienstag.typst
new file mode 100644
index 0000000..5fcc588
--- /dev/null
+++ b/archiv/202503-liedblaetter-mit-typst/02-montag_und_dienstag.typst
@@ -0,0 +1,97 @@
+#import "@preview/conchord:0.3.0": new-chordgen, smart-chord, chordify
+
+#set text(lang: "de")
+
+#let chord = new-chordgen()
+#show: chordify
+
+#set align(center)
+
+= Montag und Dienstag und Mittwoch... wie gehts?
+
+#v(2em)
+
+//Music
+/*
+treble time34 keyE e4 e h, | e e h, | e d e | f2. | f8 f f4 h, | f8 f f4 h, | f e f | g2. |
+treble a4 b c' | f2 f4 | g a b | e2. | e4 d c | b, b, g | a g f | e2. |.
+*/
+
+#block(
+[
+#set text(size: 20pt , font: "MusiQwik")
+&=3=¤=T=T=Q=!=T=T=Q=!=T=S=T=!=eµ=!=E=E=U=Q=!=E=E=U=Q=!=U=T=U=!=f¶=! \
+&=¤=W=X=Y=!=e=U=!=V=W=X=!=d´=!=T=S=R=!=Q=Q=V=!=W=V=U=!=d´=.
+]
+)
+
+#set align(left)
+
+#v(2em)
+
+== In E
+
+#place(right, dx: 4em, {
+ set align(left)
+ // Make all text in chord graph bold
+ show text: strong
+ // List of used chords there
+ box([
+#box(smart-chord("E"))
+#box(smart-chord("A"))
+#box(chord("x21202", name: "H7"))
+ ])
+})
+
+
+
+[E] Montag und Dienstag und Mittwoch, wie [H7] gehts? \
+[H7] Donnerstag und Freitag und Samstag, wie [E] stehts? \
+[A] Kommst du am Sonntag [E] mit mir nach Haus? \
+[E] Wir essen [H7] Kuchen, die Woche ist [E] aus!
+
+#v(2em)
+
+== In D
+
+#place(right, dx: 4em, dy: -2em, {
+ set align(left)
+ // Make all text in chord graph bold
+ show text: strong
+ // List of used chords there
+ box([
+#box(smart-chord("D"))
+#box(smart-chord("G")) \
+#box(smart-chord("A"))
+#box(smart-chord("A7"))
+ ])
+})
+
+
+[D] Montag und Dienstag und Mittwoch, wie [A] gehts? \
+[A7] Donnerstag und Freitag und Samstag, wie [D] stehts? \
+[G] Kommst du am Sonntag [D] mit mir nach Haus? \
+[D] Wir essen [A7] Kuchen, die Woche ist [D] aus! \
+
+#v(4em)
+
+#set text(8pt)
+
+== Anleitung
+
+Alle Kinder sitzen im Stuhlkreis. Alle singen mit.
+Ein Kind darf in der Mitte im Kreis entlanggehen. Bei Montag,
+Dienstag, Donnerstag und Freitag heben die sitzenden Kinder die Hand und die Person in der Mitte
+klatscht beim vorbeigehen ab. Bei Mittwoch und Samstag geben sich die beiden die Hand, wie zur Begrüßung.
+Für den Sonntag zieht das Herumgehende das Kind aus dem Stuhlkreis in die Mitte und die beiden tanzen zusammen.
+
+Jetzt setzt sich das erste Kind wieder und das neue macht weiter, das Lied geht von vorne los.
+Wer schon getanz hat und erneut an die Reihe käme wird übersprungen.
+
+Textvariante: Das Kind, das neu im Kreis ist ruft schnell, bevor es losgeht, ein Essen in den Kreis, was dann am Sonntag alle statt "Kuchen" singen.
+
+=== Variante für größere Gruppen:
+
+Es werden zwei Stehkreise gebildet, einer innen und einer außen, je mit der gleichen Anzahl an Kindern Man steht am Anfang rechte Schulter an rechter Schulter.
+Der äußere Kreis geht im Uhrzeigersinn, der innere dagegen.
+Jetzt klatschen alle gleichzeitig ab, gehen eine Person weiter (jeweils geradeaus), geben sich die Hände und tanzen am "Sonntag" mit der Person, bei der sie gerade angekommen sind.
diff --git a/archiv/202503-liedblaetter-mit-typst/2025-03-11-turnarounds.pdf b/archiv/202503-liedblaetter-mit-typst/2025-03-11-turnarounds.pdf
new file mode 100644
index 0000000..c67c4e2
Binary files /dev/null and b/archiv/202503-liedblaetter-mit-typst/2025-03-11-turnarounds.pdf differ
diff --git a/archiv/202503-liedblaetter-mit-typst/2025-03-11-turnarounds.typst b/archiv/202503-liedblaetter-mit-typst/2025-03-11-turnarounds.typst
new file mode 100644
index 0000000..5376c0d
--- /dev/null
+++ b/archiv/202503-liedblaetter-mit-typst/2025-03-11-turnarounds.typst
@@ -0,0 +1,33 @@
+#import "@preview/conchord:0.3.0": new-chordgen, smart-chord, chordify
+#let chord = new-chordgen()
+#show: chordify
+
+= Akkordwechsel (Turnarounds)
+
+Die folgenden Akkordfolgen eignen sich sehr zum Üben der Akkordwechsel.
+
+Sie sind gleichzeitig typische Akkordverbindungen, die in vielen Liedern benutzt werden.
+
+== Übungen
+
+Jede der Übungen soll mit je 8, 4, 2 und 1 Schlag pro Akkord gespielt werden.
+D.h. erst Akkordwechsel nach 8 Schlägen, so dass man viel Zeit hat. Danach eine Runde mit 4 Schlägen pro Akkord, dann mit 2 und schließlich nur ein Schlag pro Akkord.
+Wenn man besser wird sollte man die Runden mit 8 und 4 Schlägen weg lassen, da diese sonst zu langweilig werden.
+
+
+Die 7er Akkorde sind hier alle zu spielen und nicht freiwillig.
+
+- C Am Dm G
+- Em D G H7
+- Am E F C
+- D G E7 A7
+- G C A D7
+- A E E7 A | D A H7 E
+
+
+= Akkordtabelle
+
+#box(smart-chord("C")) #box(smart-chord("D")) #box(smart-chord("D7")) #box(smart-chord("Dm")) \
+#box(smart-chord("E")) #box(smart-chord("E7")) #box(smart-chord("Em"))
+#box(smart-chord("F")) #box(chord("320033", name: "G")) \
+#box(smart-chord("A")) #box(smart-chord("A7")) #box(smart-chord("Am")) #box(chord("x21202", name: "H7"))
diff --git a/archiv/202503-liedblaetter-mit-typst/content.md b/archiv/202503-liedblaetter-mit-typst/content.md
new file mode 100644
index 0000000..8ca96bc
--- /dev/null
+++ b/archiv/202503-liedblaetter-mit-typst/content.md
@@ -0,0 +1,44 @@
+
+Gitarren-Liedblätter mit Typst
+_Falls Sie über eine Suchmaschine hierher gelangt sind: Dieses Dokument dient lediglich als Begleitmaterial und Zusammenfassung eines mündlichen Vortrags._
+
+## Typst
+
+Typeset und Layout System für Papier, PDF und (irgendwann) Web.
+* https://typst.app/
+* "So wie LaTeX, aber neuer und einfacher"
+ * Klar, nicht das erste Projekt, dass das versucht und verspricht.
+ * Aber: Für mich persönlich Sweet Spot Funktionalität / Komplexität und Community drumherum.
+ * Liegt auch daran, dass es Syntax und Konzepte benutzt, die ich oft benutze, im Gegensatz zu LaTeX: Markdown, Skriptsprachen etc.
+* Open Source, Linux, Commandline
+ * Nicht verwirren lassen von "Pricing" etc. da steckt wie so oft heute noch eine Webapp&Service oben drauf zum Geld verdienen.
+* Community Hosting: https://typst.app/universe/
+ * Templates (z.B. DIN 5008 Brief)
+ * Packages (Akkordsymbole)
+
+Terminal Beispiele:
+```
+typst compile document.typst # -> document.pdf
+
+# bei jeder Änderung neu, man kann den PDF Viewer offen lassen
+typst watch document.tpyst
+```
+
+## Conchord
+
+Typst kann "Packages": Module, Extension, Plugins; wie man das auch immer nennen möchte. Es lädt die sogar automatisch herunter (und dann werden sie offline verwendet).
+
+Gitarrenakkorde o.ä. gehen gut mir https://typst.app/universe/package/conchord/
+
+>Write up a song with chords into Typst format in quite a pretty and simple way.
+>Explore all the possible chord variants with your special tuning of your special string instrument (like ukulele, bass guitar, mandolin or anything else).
+
+
+## Beispiele
+
+* [Typst: Turarounds](./2025-03-11-turnarounds.typst)
+* [PDF: Turarounds](./2025-03-11-turnarounds.pdf)
+* [Typst: Der Hase Augustin](./01-der_hase_augustin.typst)
+* [PDF: Der Hase Augustin](./01-der_hase_augustin.pdf)
+* [Typst: Montag und Dienstag](./02-montag_und_dienstag.typst)
+* [PDF: Montag und Dienstag](./02-montag_und_dienstag.pdf)
diff --git a/archiv/202503-liedblaetter-mit-typst/index.php b/archiv/202503-liedblaetter-mit-typst/index.php
new file mode 100644
index 0000000..2fdde38
--- /dev/null
+++ b/archiv/202503-liedblaetter-mit-typst/index.php
@@ -0,0 +1,2324 @@
+text(substr($fileContent, $firstLineBreakAt));
+
+?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+DefinitionData = array();
+
+ # standardize line breaks
+ $text = str_replace(array("\r\n", "\r"), "\n", $text);
+
+ # remove surrounding line breaks
+ $text = trim($text, "\n");
+
+ # split text into lines
+ $lines = explode("\n", $text);
+
+ # iterate through lines to identify blocks
+ $markup = $this->lines($lines);
+
+ # trim line breaks
+ $markup = trim($markup, "\n");
+
+ return $markup;
+ }
+
+ #
+ # Setters
+ #
+
+ function setBreaksEnabled($breaksEnabled)
+ {
+ $this->breaksEnabled = $breaksEnabled;
+
+ return $this;
+ }
+
+ protected $breaksEnabled;
+
+ function setMarkupEscaped($markupEscaped)
+ {
+ $this->markupEscaped = $markupEscaped;
+
+ return $this;
+ }
+
+ protected $markupEscaped;
+
+ function setUrlsLinked($urlsLinked)
+ {
+ $this->urlsLinked = $urlsLinked;
+
+ return $this;
+ }
+
+ protected $urlsLinked = true;
+
+ function setSafeMode($safeMode)
+ {
+ $this->safeMode = (bool) $safeMode;
+
+ return $this;
+ }
+
+ protected $safeMode;
+
+ protected $safeLinksWhitelist = array(
+ 'http://',
+ 'https://',
+ 'ftp://',
+ 'ftps://',
+ 'mailto:',
+ 'data:image/png;base64,',
+ 'data:image/gif;base64,',
+ 'data:image/jpeg;base64,',
+ 'irc:',
+ 'ircs:',
+ 'git:',
+ 'ssh:',
+ 'news:',
+ 'steam:',
+ );
+
+ #
+ # Lines
+ #
+
+ protected $BlockTypes = array(
+ '#' => array('Header'),
+ '*' => array('Rule', 'List'),
+ '+' => array('List'),
+ '-' => array('SetextHeader', 'Table', 'Rule', 'List'),
+ '0' => array('List'),
+ '1' => array('List'),
+ '2' => array('List'),
+ '3' => array('List'),
+ '4' => array('List'),
+ '5' => array('List'),
+ '6' => array('List'),
+ '7' => array('List'),
+ '8' => array('List'),
+ '9' => array('List'),
+ ':' => array('Table'),
+ '<' => array('Comment', 'Markup'),
+ '=' => array('SetextHeader'),
+ '>' => array('Quote'),
+ '[' => array('Reference'),
+ '_' => array('Rule'),
+ '`' => array('FencedCode'),
+ '|' => array('Table'),
+ '~' => array('FencedCode'),
+ );
+
+ # ~
+
+ protected $unmarkedBlockTypes = array(
+ 'Code',
+ );
+
+ #
+ # Blocks
+ #
+
+ protected function lines(array $lines)
+ {
+ $CurrentBlock = null;
+
+ foreach ($lines as $line)
+ {
+ if (chop($line) === '')
+ {
+ if (isset($CurrentBlock))
+ {
+ $CurrentBlock['interrupted'] = true;
+ }
+
+ continue;
+ }
+
+ if (strpos($line, "\t") !== false)
+ {
+ $parts = explode("\t", $line);
+
+ $line = $parts[0];
+
+ unset($parts[0]);
+
+ foreach ($parts as $part)
+ {
+ $shortage = 4 - mb_strlen($line, 'utf-8') % 4;
+
+ $line .= str_repeat(' ', $shortage);
+ $line .= $part;
+ }
+ }
+
+ $indent = 0;
+
+ while (isset($line[$indent]) and $line[$indent] === ' ')
+ {
+ $indent ++;
+ }
+
+ $text = $indent > 0 ? substr($line, $indent) : $line;
+
+ # ~
+
+ $Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
+
+ # ~
+
+ if (isset($CurrentBlock['continuable']))
+ {
+ $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock);
+
+ if (isset($Block))
+ {
+ $CurrentBlock = $Block;
+
+ continue;
+ }
+ else
+ {
+ if ($this->isBlockCompletable($CurrentBlock['type']))
+ {
+ $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
+ }
+ }
+ }
+
+ # ~
+
+ $marker = $text[0];
+
+ # ~
+
+ $blockTypes = $this->unmarkedBlockTypes;
+
+ if (isset($this->BlockTypes[$marker]))
+ {
+ foreach ($this->BlockTypes[$marker] as $blockType)
+ {
+ $blockTypes []= $blockType;
+ }
+ }
+
+ #
+ # ~
+
+ foreach ($blockTypes as $blockType)
+ {
+ $Block = $this->{'block'.$blockType}($Line, $CurrentBlock);
+
+ if (isset($Block))
+ {
+ $Block['type'] = $blockType;
+
+ if ( ! isset($Block['identified']))
+ {
+ $Blocks []= $CurrentBlock;
+
+ $Block['identified'] = true;
+ }
+
+ if ($this->isBlockContinuable($blockType))
+ {
+ $Block['continuable'] = true;
+ }
+
+ $CurrentBlock = $Block;
+
+ continue 2;
+ }
+ }
+
+ # ~
+
+ if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted']))
+ {
+ $CurrentBlock['element']['text'] .= "\n".$text;
+ }
+ else
+ {
+ $Blocks []= $CurrentBlock;
+
+ $CurrentBlock = $this->paragraph($Line);
+
+ $CurrentBlock['identified'] = true;
+ }
+ }
+
+ # ~
+
+ if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
+ {
+ $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
+ }
+
+ # ~
+
+ $Blocks []= $CurrentBlock;
+
+ unset($Blocks[0]);
+
+ # ~
+
+ $markup = '';
+
+ foreach ($Blocks as $Block)
+ {
+ if (isset($Block['hidden']))
+ {
+ continue;
+ }
+
+ $markup .= "\n";
+ $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']);
+ }
+
+ $markup .= "\n";
+
+ # ~
+
+ return $markup;
+ }
+
+ protected function isBlockContinuable($Type)
+ {
+ return method_exists($this, 'block'.$Type.'Continue');
+ }
+
+ protected function isBlockCompletable($Type)
+ {
+ return method_exists($this, 'block'.$Type.'Complete');
+ }
+
+ #
+ # Code
+
+ protected function blockCode($Line, $Block = null)
+ {
+ if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if ($Line['indent'] >= 4)
+ {
+ $text = substr($Line['body'], 4);
+
+ $Block = array(
+ 'element' => array(
+ 'name' => 'pre',
+ 'handler' => 'element',
+ 'text' => array(
+ 'name' => 'code',
+ 'text' => $text,
+ ),
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockCodeContinue($Line, $Block)
+ {
+ if ($Line['indent'] >= 4)
+ {
+ if (isset($Block['interrupted']))
+ {
+ $Block['element']['text']['text'] .= "\n";
+
+ unset($Block['interrupted']);
+ }
+
+ $Block['element']['text']['text'] .= "\n";
+
+ $text = substr($Line['body'], 4);
+
+ $Block['element']['text']['text'] .= $text;
+
+ return $Block;
+ }
+ }
+
+ protected function blockCodeComplete($Block)
+ {
+ $text = $Block['element']['text']['text'];
+
+ $Block['element']['text']['text'] = $text;
+
+ return $Block;
+ }
+
+ #
+ # Comment
+
+ protected function blockComment($Line)
+ {
+ if ($this->markupEscaped or $this->safeMode)
+ {
+ return;
+ }
+
+ if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!')
+ {
+ $Block = array(
+ 'markup' => $Line['body'],
+ );
+
+ if (preg_match('/-->$/', $Line['text']))
+ {
+ $Block['closed'] = true;
+ }
+
+ return $Block;
+ }
+ }
+
+ protected function blockCommentContinue($Line, array $Block)
+ {
+ if (isset($Block['closed']))
+ {
+ return;
+ }
+
+ $Block['markup'] .= "\n" . $Line['body'];
+
+ if (preg_match('/-->$/', $Line['text']))
+ {
+ $Block['closed'] = true;
+ }
+
+ return $Block;
+ }
+
+ #
+ # Fenced Code
+
+ protected function blockFencedCode($Line)
+ {
+ if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([^`]+)?[ ]*$/', $Line['text'], $matches))
+ {
+ $Element = array(
+ 'name' => 'code',
+ 'text' => '',
+ );
+
+ if (isset($matches[1]))
+ {
+ /**
+ * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes
+ * Every HTML element may have a class attribute specified.
+ * The attribute, if specified, must have a value that is a set
+ * of space-separated tokens representing the various classes
+ * that the element belongs to.
+ * [...]
+ * The space characters, for the purposes of this specification,
+ * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab),
+ * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and
+ * U+000D CARRIAGE RETURN (CR).
+ */
+ $language = substr($matches[1], 0, strcspn($matches[1], " \t\n\f\r"));
+
+ $class = 'language-'.$language;
+
+ $Element['attributes'] = array(
+ 'class' => $class,
+ );
+ }
+
+ $Block = array(
+ 'char' => $Line['text'][0],
+ 'element' => array(
+ 'name' => 'pre',
+ 'handler' => 'element',
+ 'text' => $Element,
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockFencedCodeContinue($Line, $Block)
+ {
+ if (isset($Block['complete']))
+ {
+ return;
+ }
+
+ if (isset($Block['interrupted']))
+ {
+ $Block['element']['text']['text'] .= "\n";
+
+ unset($Block['interrupted']);
+ }
+
+ if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text']))
+ {
+ $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1);
+
+ $Block['complete'] = true;
+
+ return $Block;
+ }
+
+ $Block['element']['text']['text'] .= "\n".$Line['body'];
+
+ return $Block;
+ }
+
+ protected function blockFencedCodeComplete($Block)
+ {
+ $text = $Block['element']['text']['text'];
+
+ $Block['element']['text']['text'] = $text;
+
+ return $Block;
+ }
+
+ #
+ # Header
+
+ protected function blockHeader($Line)
+ {
+ if (isset($Line['text'][1]))
+ {
+ $level = 1;
+
+ while (isset($Line['text'][$level]) and $Line['text'][$level] === '#')
+ {
+ $level ++;
+ }
+
+ if ($level > 6)
+ {
+ return;
+ }
+
+ $text = trim($Line['text'], '# ');
+
+ $Block = array(
+ 'element' => array(
+ 'name' => 'h' . min(6, $level),
+ 'text' => $text,
+ 'handler' => 'line',
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ #
+ # List
+
+ protected function blockList($Line)
+ {
+ list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]');
+
+ if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches))
+ {
+ $Block = array(
+ 'indent' => $Line['indent'],
+ 'pattern' => $pattern,
+ 'element' => array(
+ 'name' => $name,
+ 'handler' => 'elements',
+ ),
+ );
+
+ if($name === 'ol')
+ {
+ $listStart = stristr($matches[0], '.', true);
+
+ if($listStart !== '1')
+ {
+ $Block['element']['attributes'] = array('start' => $listStart);
+ }
+ }
+
+ $Block['li'] = array(
+ 'name' => 'li',
+ 'handler' => 'li',
+ 'text' => array(
+ $matches[2],
+ ),
+ );
+
+ $Block['element']['text'] []= & $Block['li'];
+
+ return $Block;
+ }
+ }
+
+ protected function blockListContinue($Line, array $Block)
+ {
+ if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches))
+ {
+ if (isset($Block['interrupted']))
+ {
+ $Block['li']['text'] []= '';
+
+ $Block['loose'] = true;
+
+ unset($Block['interrupted']);
+ }
+
+ unset($Block['li']);
+
+ $text = isset($matches[1]) ? $matches[1] : '';
+
+ $Block['li'] = array(
+ 'name' => 'li',
+ 'handler' => 'li',
+ 'text' => array(
+ $text,
+ ),
+ );
+
+ $Block['element']['text'] []= & $Block['li'];
+
+ return $Block;
+ }
+
+ if ($Line['text'][0] === '[' and $this->blockReference($Line))
+ {
+ return $Block;
+ }
+
+ if ( ! isset($Block['interrupted']))
+ {
+ $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
+
+ $Block['li']['text'] []= $text;
+
+ return $Block;
+ }
+
+ if ($Line['indent'] > 0)
+ {
+ $Block['li']['text'] []= '';
+
+ $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
+
+ $Block['li']['text'] []= $text;
+
+ unset($Block['interrupted']);
+
+ return $Block;
+ }
+ }
+
+ protected function blockListComplete(array $Block)
+ {
+ if (isset($Block['loose']))
+ {
+ foreach ($Block['element']['text'] as &$li)
+ {
+ if (end($li['text']) !== '')
+ {
+ $li['text'] []= '';
+ }
+ }
+ }
+
+ return $Block;
+ }
+
+ #
+ # Quote
+
+ protected function blockQuote($Line)
+ {
+ if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
+ {
+ $Block = array(
+ 'element' => array(
+ 'name' => 'blockquote',
+ 'handler' => 'lines',
+ 'text' => (array) $matches[1],
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockQuoteContinue($Line, array $Block)
+ {
+ if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
+ {
+ if (isset($Block['interrupted']))
+ {
+ $Block['element']['text'] []= '';
+
+ unset($Block['interrupted']);
+ }
+
+ $Block['element']['text'] []= $matches[1];
+
+ return $Block;
+ }
+
+ if ( ! isset($Block['interrupted']))
+ {
+ $Block['element']['text'] []= $Line['text'];
+
+ return $Block;
+ }
+ }
+
+ #
+ # Rule
+
+ protected function blockRule($Line)
+ {
+ if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text']))
+ {
+ $Block = array(
+ 'element' => array(
+ 'name' => 'hr'
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ #
+ # Setext
+
+ protected function blockSetextHeader($Line, array $Block = null)
+ {
+ if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if (chop($Line['text'], $Line['text'][0]) === '')
+ {
+ $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
+
+ return $Block;
+ }
+ }
+
+ #
+ # Markup
+
+ protected function blockMarkup($Line)
+ {
+ if ($this->markupEscaped or $this->safeMode)
+ {
+ return;
+ }
+
+ if (preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
+ {
+ $element = strtolower($matches[1]);
+
+ if (in_array($element, $this->textLevelElements))
+ {
+ return;
+ }
+
+ $Block = array(
+ 'name' => $matches[1],
+ 'depth' => 0,
+ 'markup' => $Line['text'],
+ );
+
+ $length = strlen($matches[0]);
+
+ $remainder = substr($Line['text'], $length);
+
+ if (trim($remainder) === '')
+ {
+ if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
+ {
+ $Block['closed'] = true;
+
+ $Block['void'] = true;
+ }
+ }
+ else
+ {
+ if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
+ {
+ return;
+ }
+
+ if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
+ {
+ $Block['closed'] = true;
+ }
+ }
+
+ return $Block;
+ }
+ }
+
+ protected function blockMarkupContinue($Line, array $Block)
+ {
+ if (isset($Block['closed']))
+ {
+ return;
+ }
+
+ if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open
+ {
+ $Block['depth'] ++;
+ }
+
+ if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close
+ {
+ if ($Block['depth'] > 0)
+ {
+ $Block['depth'] --;
+ }
+ else
+ {
+ $Block['closed'] = true;
+ }
+ }
+
+ if (isset($Block['interrupted']))
+ {
+ $Block['markup'] .= "\n";
+
+ unset($Block['interrupted']);
+ }
+
+ $Block['markup'] .= "\n".$Line['body'];
+
+ return $Block;
+ }
+
+ #
+ # Reference
+
+ protected function blockReference($Line)
+ {
+ if (preg_match('/^\[(.+?)\]:[ ]*(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches))
+ {
+ $id = strtolower($matches[1]);
+
+ $Data = array(
+ 'url' => $matches[2],
+ 'title' => null,
+ );
+
+ if (isset($matches[3]))
+ {
+ $Data['title'] = $matches[3];
+ }
+
+ $this->DefinitionData['Reference'][$id] = $Data;
+
+ $Block = array(
+ 'hidden' => true,
+ );
+
+ return $Block;
+ }
+ }
+
+ #
+ # Table
+
+ protected function blockTable($Line, array $Block = null)
+ {
+ if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '')
+ {
+ $alignments = array();
+
+ $divider = $Line['text'];
+
+ $divider = trim($divider);
+ $divider = trim($divider, '|');
+
+ $dividerCells = explode('|', $divider);
+
+ foreach ($dividerCells as $dividerCell)
+ {
+ $dividerCell = trim($dividerCell);
+
+ if ($dividerCell === '')
+ {
+ continue;
+ }
+
+ $alignment = null;
+
+ if ($dividerCell[0] === ':')
+ {
+ $alignment = 'left';
+ }
+
+ if (substr($dividerCell, - 1) === ':')
+ {
+ $alignment = $alignment === 'left' ? 'center' : 'right';
+ }
+
+ $alignments []= $alignment;
+ }
+
+ # ~
+
+ $HeaderElements = array();
+
+ $header = $Block['element']['text'];
+
+ $header = trim($header);
+ $header = trim($header, '|');
+
+ $headerCells = explode('|', $header);
+
+ foreach ($headerCells as $index => $headerCell)
+ {
+ $headerCell = trim($headerCell);
+
+ $HeaderElement = array(
+ 'name' => 'th',
+ 'text' => $headerCell,
+ 'handler' => 'line',
+ );
+
+ if (isset($alignments[$index]))
+ {
+ $alignment = $alignments[$index];
+
+ $HeaderElement['attributes'] = array(
+ 'style' => 'text-align: '.$alignment.';',
+ );
+ }
+
+ $HeaderElements []= $HeaderElement;
+ }
+
+ # ~
+
+ $Block = array(
+ 'alignments' => $alignments,
+ 'identified' => true,
+ 'element' => array(
+ 'name' => 'table',
+ 'handler' => 'elements',
+ ),
+ );
+
+ $Block['element']['text'] []= array(
+ 'name' => 'thead',
+ 'handler' => 'elements',
+ );
+
+ $Block['element']['text'] []= array(
+ 'name' => 'tbody',
+ 'handler' => 'elements',
+ 'text' => array(),
+ );
+
+ $Block['element']['text'][0]['text'] []= array(
+ 'name' => 'tr',
+ 'handler' => 'elements',
+ 'text' => $HeaderElements,
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockTableContinue($Line, array $Block)
+ {
+ if (isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if ($Line['text'][0] === '|' or strpos($Line['text'], '|'))
+ {
+ $Elements = array();
+
+ $row = $Line['text'];
+
+ $row = trim($row);
+ $row = trim($row, '|');
+
+ preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches);
+
+ foreach ($matches[0] as $index => $cell)
+ {
+ $cell = trim($cell);
+
+ $Element = array(
+ 'name' => 'td',
+ 'handler' => 'line',
+ 'text' => $cell,
+ );
+
+ if (isset($Block['alignments'][$index]))
+ {
+ $Element['attributes'] = array(
+ 'style' => 'text-align: '.$Block['alignments'][$index].';',
+ );
+ }
+
+ $Elements []= $Element;
+ }
+
+ $Element = array(
+ 'name' => 'tr',
+ 'handler' => 'elements',
+ 'text' => $Elements,
+ );
+
+ $Block['element']['text'][1]['text'] []= $Element;
+
+ return $Block;
+ }
+ }
+
+ #
+ # ~
+ #
+
+ protected function paragraph($Line)
+ {
+ $Block = array(
+ 'element' => array(
+ 'name' => 'p',
+ 'text' => $Line['text'],
+ 'handler' => 'line',
+ ),
+ );
+
+ return $Block;
+ }
+
+ #
+ # Inline Elements
+ #
+
+ protected $InlineTypes = array(
+ '"' => array('SpecialCharacter'),
+ '!' => array('Image'),
+ '&' => array('SpecialCharacter'),
+ '*' => array('Emphasis'),
+ ':' => array('Url'),
+ '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'),
+ '>' => array('SpecialCharacter'),
+ '[' => array('Link'),
+ '_' => array('Emphasis'),
+ '`' => array('Code'),
+ '~' => array('Strikethrough'),
+ '\\' => array('EscapeSequence'),
+ );
+
+ # ~
+
+ protected $inlineMarkerList = '!"*_&[:<>`~\\';
+
+ #
+ # ~
+ #
+
+ public function line($text, $nonNestables=array())
+ {
+ $markup = '';
+
+ # $excerpt is based on the first occurrence of a marker
+
+ while ($excerpt = strpbrk($text, $this->inlineMarkerList))
+ {
+ $marker = $excerpt[0];
+
+ $markerPosition = strpos($text, $marker);
+
+ $Excerpt = array('text' => $excerpt, 'context' => $text);
+
+ foreach ($this->InlineTypes[$marker] as $inlineType)
+ {
+ # check to see if the current inline type is nestable in the current context
+
+ if ( ! empty($nonNestables) and in_array($inlineType, $nonNestables))
+ {
+ continue;
+ }
+
+ $Inline = $this->{'inline'.$inlineType}($Excerpt);
+
+ if ( ! isset($Inline))
+ {
+ continue;
+ }
+
+ # makes sure that the inline belongs to "our" marker
+
+ if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
+ {
+ continue;
+ }
+
+ # sets a default inline position
+
+ if ( ! isset($Inline['position']))
+ {
+ $Inline['position'] = $markerPosition;
+ }
+
+ # cause the new element to 'inherit' our non nestables
+
+ foreach ($nonNestables as $non_nestable)
+ {
+ $Inline['element']['nonNestables'][] = $non_nestable;
+ }
+
+ # the text that comes before the inline
+ $unmarkedText = substr($text, 0, $Inline['position']);
+
+ # compile the unmarked text
+ $markup .= $this->unmarkedText($unmarkedText);
+
+ # compile the inline
+ $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']);
+
+ # remove the examined text
+ $text = substr($text, $Inline['position'] + $Inline['extent']);
+
+ continue 2;
+ }
+
+ # the marker does not belong to an inline
+
+ $unmarkedText = substr($text, 0, $markerPosition + 1);
+
+ $markup .= $this->unmarkedText($unmarkedText);
+
+ $text = substr($text, $markerPosition + 1);
+ }
+
+ $markup .= $this->unmarkedText($text);
+
+ return $markup;
+ }
+
+ #
+ # ~
+ #
+
+ protected function inlineCode($Excerpt)
+ {
+ $marker = $Excerpt['text'][0];
+
+ if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(? strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'code',
+ 'text' => $text,
+ ),
+ );
+ }
+ }
+
+ protected function inlineEmailTag($Excerpt)
+ {
+ if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches))
+ {
+ $url = $matches[1];
+
+ if ( ! isset($matches[2]))
+ {
+ $url = 'mailto:' . $url;
+ }
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'a',
+ 'text' => $matches[1],
+ 'attributes' => array(
+ 'href' => $url,
+ ),
+ ),
+ );
+ }
+ }
+
+ protected function inlineEmphasis($Excerpt)
+ {
+ if ( ! isset($Excerpt['text'][1]))
+ {
+ return;
+ }
+
+ $marker = $Excerpt['text'][0];
+
+ if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
+ {
+ $emphasis = 'strong';
+ }
+ elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
+ {
+ $emphasis = 'em';
+ }
+ else
+ {
+ return;
+ }
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => $emphasis,
+ 'handler' => 'line',
+ 'text' => $matches[1],
+ ),
+ );
+ }
+
+ protected function inlineEscapeSequence($Excerpt)
+ {
+ if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
+ {
+ return array(
+ 'markup' => $Excerpt['text'][1],
+ 'extent' => 2,
+ );
+ }
+ }
+
+ protected function inlineImage($Excerpt)
+ {
+ if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
+ {
+ return;
+ }
+
+ $Excerpt['text']= substr($Excerpt['text'], 1);
+
+ $Link = $this->inlineLink($Excerpt);
+
+ if ($Link === null)
+ {
+ return;
+ }
+
+ $Inline = array(
+ 'extent' => $Link['extent'] + 1,
+ 'element' => array(
+ 'name' => 'img',
+ 'attributes' => array(
+ 'src' => $Link['element']['attributes']['href'],
+ 'alt' => $Link['element']['text'],
+ ),
+ ),
+ );
+
+ $Inline['element']['attributes'] += $Link['element']['attributes'];
+
+ unset($Inline['element']['attributes']['href']);
+
+ return $Inline;
+ }
+
+ protected function inlineLink($Excerpt)
+ {
+ $Element = array(
+ 'name' => 'a',
+ 'handler' => 'line',
+ 'nonNestables' => array('Url', 'Link'),
+ 'text' => null,
+ 'attributes' => array(
+ 'href' => null,
+ 'title' => null,
+ ),
+ );
+
+ $extent = 0;
+
+ $remainder = $Excerpt['text'];
+
+ if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches))
+ {
+ $Element['text'] = $matches[1];
+
+ $extent += strlen($matches[0]);
+
+ $remainder = substr($remainder, $extent);
+ }
+ else
+ {
+ return;
+ }
+
+ if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches))
+ {
+ $Element['attributes']['href'] = $matches[1];
+
+ if (isset($matches[2]))
+ {
+ $Element['attributes']['title'] = substr($matches[2], 1, - 1);
+ }
+
+ $extent += strlen($matches[0]);
+ }
+ else
+ {
+ if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
+ {
+ $definition = strlen($matches[1]) ? $matches[1] : $Element['text'];
+ $definition = strtolower($definition);
+
+ $extent += strlen($matches[0]);
+ }
+ else
+ {
+ $definition = strtolower($Element['text']);
+ }
+
+ if ( ! isset($this->DefinitionData['Reference'][$definition]))
+ {
+ return;
+ }
+
+ $Definition = $this->DefinitionData['Reference'][$definition];
+
+ $Element['attributes']['href'] = $Definition['url'];
+ $Element['attributes']['title'] = $Definition['title'];
+ }
+
+ return array(
+ 'extent' => $extent,
+ 'element' => $Element,
+ );
+ }
+
+ protected function inlineMarkup($Excerpt)
+ {
+ if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false)
+ {
+ return;
+ }
+
+ if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*[ ]*>/s', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'markup' => $matches[0],
+ 'extent' => strlen($matches[0]),
+ );
+ }
+
+ if ($Excerpt['text'][1] === '!' and preg_match('/^/s', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'markup' => $matches[0],
+ 'extent' => strlen($matches[0]),
+ );
+ }
+
+ if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'markup' => $matches[0],
+ 'extent' => strlen($matches[0]),
+ );
+ }
+ }
+
+ protected function inlineSpecialCharacter($Excerpt)
+ {
+ if ($Excerpt['text'][0] === '&' and ! preg_match('/^?\w+;/', $Excerpt['text']))
+ {
+ return array(
+ 'markup' => '&',
+ 'extent' => 1,
+ );
+ }
+
+ $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
+
+ if (isset($SpecialCharacter[$Excerpt['text'][0]]))
+ {
+ return array(
+ 'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';',
+ 'extent' => 1,
+ );
+ }
+ }
+
+ protected function inlineStrikethrough($Excerpt)
+ {
+ if ( ! isset($Excerpt['text'][1]))
+ {
+ return;
+ }
+
+ if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'del',
+ 'text' => $matches[1],
+ 'handler' => 'line',
+ ),
+ );
+ }
+ }
+
+ protected function inlineUrl($Excerpt)
+ {
+ if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
+ {
+ return;
+ }
+
+ if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE))
+ {
+ $url = $matches[0][0];
+
+ $Inline = array(
+ 'extent' => strlen($matches[0][0]),
+ 'position' => $matches[0][1],
+ 'element' => array(
+ 'name' => 'a',
+ 'text' => $url,
+ 'attributes' => array(
+ 'href' => $url,
+ ),
+ ),
+ );
+
+ return $Inline;
+ }
+ }
+
+ protected function inlineUrlTag($Excerpt)
+ {
+ if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches))
+ {
+ $url = $matches[1];
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'a',
+ 'text' => $url,
+ 'attributes' => array(
+ 'href' => $url,
+ ),
+ ),
+ );
+ }
+ }
+
+ # ~
+
+ protected function unmarkedText($text)
+ {
+ if ($this->breaksEnabled)
+ {
+ $text = preg_replace('/[ ]*\n/', " \n", $text);
+ }
+ else
+ {
+ $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', " \n", $text);
+ $text = str_replace(" \n", "\n", $text);
+ }
+
+ return $text;
+ }
+
+ #
+ # Handlers
+ #
+
+ protected function element(array $Element)
+ {
+ if ($this->safeMode)
+ {
+ $Element = $this->sanitiseElement($Element);
+ }
+
+ $markup = '<'.$Element['name'];
+
+ if (isset($Element['attributes']))
+ {
+ foreach ($Element['attributes'] as $name => $value)
+ {
+ if ($value === null)
+ {
+ continue;
+ }
+
+ $markup .= ' '.$name.'="'.self::escape($value).'"';
+ }
+ }
+
+ $permitRawHtml = false;
+
+ if (isset($Element['text']))
+ {
+ $text = $Element['text'];
+ }
+ // very strongly consider an alternative if you're writing an
+ // extension
+ elseif (isset($Element['rawHtml']))
+ {
+ $text = $Element['rawHtml'];
+ $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode'];
+ $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode;
+ }
+
+ if (isset($text))
+ {
+ $markup .= '>';
+
+ if (!isset($Element['nonNestables']))
+ {
+ $Element['nonNestables'] = array();
+ }
+
+ if (isset($Element['handler']))
+ {
+ $markup .= $this->{$Element['handler']}($text, $Element['nonNestables']);
+ }
+ elseif (!$permitRawHtml)
+ {
+ $markup .= self::escape($text, true);
+ }
+ else
+ {
+ $markup .= $text;
+ }
+
+ $markup .= ''.$Element['name'].'>';
+ }
+ else
+ {
+ $markup .= ' />';
+ }
+
+ return $markup;
+ }
+
+ protected function elements(array $Elements)
+ {
+ $markup = '';
+
+ foreach ($Elements as $Element)
+ {
+ $markup .= "\n" . $this->element($Element);
+ }
+
+ $markup .= "\n";
+
+ return $markup;
+ }
+
+ # ~
+
+ protected function li($lines)
+ {
+ $markup = $this->lines($lines);
+
+ $trimmedMarkup = trim($markup);
+
+ if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '')
+ {
+ $markup = $trimmedMarkup;
+ $markup = substr($markup, 3);
+
+ $position = strpos($markup, "
");
+
+ $markup = substr_replace($markup, '', $position, 4);
+ }
+
+ return $markup;
+ }
+
+ #
+ # Deprecated Methods
+ #
+
+ function parse($text)
+ {
+ $markup = $this->text($text);
+
+ return $markup;
+ }
+
+ protected function sanitiseElement(array $Element)
+ {
+ static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/';
+ static $safeUrlNameToAtt = array(
+ 'a' => 'href',
+ 'img' => 'src',
+ );
+
+ if (isset($safeUrlNameToAtt[$Element['name']]))
+ {
+ $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]);
+ }
+
+ if ( ! empty($Element['attributes']))
+ {
+ foreach ($Element['attributes'] as $att => $val)
+ {
+ # filter out badly parsed attribute
+ if ( ! preg_match($goodAttribute, $att))
+ {
+ unset($Element['attributes'][$att]);
+ }
+ # dump onevent attribute
+ elseif (self::striAtStart($att, 'on'))
+ {
+ unset($Element['attributes'][$att]);
+ }
+ }
+ }
+
+ return $Element;
+ }
+
+ protected function filterUnsafeUrlInAttribute(array $Element, $attribute)
+ {
+ foreach ($this->safeLinksWhitelist as $scheme)
+ {
+ if (self::striAtStart($Element['attributes'][$attribute], $scheme))
+ {
+ return $Element;
+ }
+ }
+
+ $Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]);
+
+ return $Element;
+ }
+
+ #
+ # Static Methods
+ #
+
+ protected static function escape($text, $allowQuotes = false)
+ {
+ return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8');
+ }
+
+ protected static function striAtStart($string, $needle)
+ {
+ $len = strlen($needle);
+
+ if ($len > strlen($string))
+ {
+ return false;
+ }
+ else
+ {
+ return strtolower(substr($string, 0, $len)) === strtolower($needle);
+ }
+ }
+
+ static function instance($name = 'default')
+ {
+ if (isset(self::$instances[$name]))
+ {
+ return self::$instances[$name];
+ }
+
+ $instance = new static();
+
+ self::$instances[$name] = $instance;
+
+ return $instance;
+ }
+
+ private static $instances = array();
+
+ #
+ # Fields
+ #
+
+ protected $DefinitionData;
+
+ #
+ # Read-Only
+
+ protected $specialCharacters = array(
+ '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|',
+ );
+
+ protected $StrongRegex = array(
+ '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s',
+ '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us',
+ );
+
+ protected $EmRegex = array(
+ '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
+ '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
+ );
+
+ protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?';
+
+ protected $voidElements = array(
+ 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
+ );
+
+ protected $textLevelElements = array(
+ 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
+ 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
+ 'i', 'rp', 'del', 'code', 'strike', 'marquee',
+ 'q', 'rt', 'ins', 'font', 'strong',
+ 's', 'tt', 'kbd', 'mark',
+ 'u', 'xm', 'sub', 'nobr',
+ 'sup', 'ruby',
+ 'var', 'span',
+ 'wbr', 'time',
+ );
+}
diff --git a/archiv/202503-liedblaetter-mit-typst/metadata.ini b/archiv/202503-liedblaetter-mit-typst/metadata.ini
new file mode 100644
index 0000000..cb8d615
--- /dev/null
+++ b/archiv/202503-liedblaetter-mit-typst/metadata.ini
@@ -0,0 +1,8 @@
+[Meta]
+#YYYY-MM
+Date = 2025-03
+
+#e.g. CC-by-sa, CC-by, All Rights Reserved
+License = CC-by-sa
+PrettyName = Gitarren-Liedblätter mit Typst erstellen
+Author = nils
diff --git a/stuff/bingo.pdf b/stuff/bingo.pdf
new file mode 100644
index 0000000..52fbc86
Binary files /dev/null and b/stuff/bingo.pdf differ
diff --git a/stuff/bingo.typst b/stuff/bingo.typst
new file mode 100644
index 0000000..b9dd4ec
--- /dev/null
+++ b/stuff/bingo.typst
@@ -0,0 +1,63 @@
+#set page(flipped: true) // A4 paper format
+
+// Bingo Title
+= OSAMC BINGO 2025-03
+
+Spielregeln: Schreibe je einen Begriff in ein Feld. Höre während des Treffens zu – sobald jemand den Begriff nennt, markiere das Feld.
+Übersetzte Begriffe (Ton = Sound o. Beamer = Projektor) zählen auch.
+
+Gewonnen hat, wer eine durchgehende Reihe horizontal, vertikal oder diagonal markiert.
+
+#let words = (
+ "Ardour", "JACK", "Pipewire", "Plugin",
+ "MIDI", "Gitarre", "Ton", "Chat",
+ "Latenz", "Terminal", "Lizenz", "Akkord",
+ "Platte", "Uhr", "Projektor", "Stecker"
+)
+
+
+#place(right, dy: 2em, dx: -32em, {
+ set align(left)
+ show text: strong
+ [
+ = Begriffe
+ #for word in words.sorted() [
+ - #word
+ ]
+
+ ]
+})
+
+#h(1cm) // Spacer
+
+#grid(
+ columns: 4,
+ rows: 4,
+ row-gutter: 1em,
+ column-gutter: 1em,
+
+ [#rect(width: 2cm, height: 2cm) #rect(width: 2cm, height: 2cm) #rect(width: 2cm, height: 2cm) #rect(width: 2cm, height: 2cm) ],
+ [#rect(width: 2cm, height: 2cm) #rect(width: 2cm, height: 2cm) #rect(width: 2cm, height: 2cm) #rect(width: 2cm, height: 2cm) ],
+ [#rect(width: 2cm, height: 2cm) #rect(width: 2cm, height: 2cm) #rect(width: 2cm, height: 2cm) #rect(width: 2cm, height: 2cm) ],
+ [#rect(width: 2cm, height: 2cm) #rect(width: 2cm, height: 2cm) #rect(width: 2cm, height: 2cm) #rect(width: 2cm, height: 2cm) ],
+
+)
+
+#place(top, dy: 8em, dx: 35em, {
+ set align(left)
+ show text: strong
+
+ [
+ #grid(
+ columns: 4,
+ rows: 4,
+ row-gutter: 1em,
+ column-gutter: 1em,
+
+ [#rect(width: 2cm, height: 2cm) #rect(width: 2cm, height: 2cm) #rect(width: 2cm, height: 2cm) #rect(width: 2cm, height: 2cm) ],
+ [#rect(width: 2cm, height: 2cm) #rect(width: 2cm, height: 2cm) #rect(width: 2cm, height: 2cm) #rect(width: 2cm, height: 2cm) ],
+ [#rect(width: 2cm, height: 2cm) #rect(width: 2cm, height: 2cm) #rect(width: 2cm, height: 2cm) #rect(width: 2cm, height: 2cm) ],
+ [#rect(width: 2cm, height: 2cm) #rect(width: 2cm, height: 2cm) #rect(width: 2cm, height: 2cm) #rect(width: 2cm, height: 2cm) ],
+ )
+ ]
+})
diff --git a/stuff/osamc.svg b/stuff/osamc.svg
new file mode 100644
index 0000000..05df2be
--- /dev/null
+++ b/stuff/osamc.svg
@@ -0,0 +1,215 @@
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+ Sturkopf Grotesk Font CAPS Schriftgröße 100 1px Abstand zwischen den Buchstaben 50% Height (nicht breite skalieren) Unterschrift: normale schrift, kein extra Space, keine height. Auf gleiche Breite wie das Logo gemacht, zentral unten drunter Position der Unterschrift nach Augenmaß Export als transparent SVG Dann Farben und Schatten in Gimp, je nach Einsatzzweck
+ OSAMC
+ Open Source Audio Meeting Cologne
+ OSAMC
+ Open Source Audio Meeting Cologne
+ Im Erdgeschoss Gerade aus durch zum Chaos Computer Club
+
+