From 0d81f5100640c7f961fe6d6e79a6b0d801b3289b Mon Sep 17 00:00:00 2001
From: Case Duckworth
Date: Tue, 2 Aug 2022 09:25:42 -0500
Subject: Initial commit

---
 README.html  | 203 ++++++++++++++
 doc.awk      | 213 ++++++++++++++
 footer.html  |   2 +
 header.html  |   4 +
 htmlsafe.awk |   8 +
 mdown.awk    | 903 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 style.css    |  31 ++
 7 files changed, 1364 insertions(+)
 create mode 100644 README.html
 create mode 100755 doc.awk
 create mode 100644 footer.html
 create mode 100644 header.html
 create mode 100755 htmlsafe.awk
 create mode 100755 mdown.awk
 create mode 100644 style.css

diff --git a/README.html b/README.html
new file mode 100644
index 0000000..94c9c2d
--- /dev/null
+++ b/README.html
@@ -0,0 +1,203 @@
+<!DOCTYPE html>
+<title>doc.awk</title>
+<link type="text/css" rel="stylesheet" href="style.css" />
+<body>
+<h1 id="doc-awk"><a href="#doc-awk" class="header">DOC AWK&nbsp;<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><g transform="rotate(-30, 8, 8)" stroke="#000000" opacity="0.25"><rect fill="none" height="6" width="8" x="2" y="6" rx="1.5"/><rect fill="none" height="6" width="8" x="6" y="4" rx="1.5"/></g></svg></a></h1>
+<p>A quick-and-dirty literate-programming-style documentation generator
+inspired by <a class="normal" href="https://ashkenas.com/docco/" title="">docco</a>.</p>
+<p>by Case Duckworth <a class="normal" href="&#x6D;a&#105;&#108;&#x74;&#x6F;&#58;a&#x63;&#x64;&#x77;&#x40;&#97;&#x63;&#x64;&#x77;&#x2E;&#x6E;&#x65;&#x74;">&#97;&#x63;&#x64;w&#x40;&#97;&#x63;&#100;&#119;&#x2E;&#110;&#101;t</a></p>
+<p>Source available under the <a class="normal" href="https://acdw.casa/gcl" title="">Good Choices License</a>.</p>
+<p>There's a lot of quick-and-dirty "literate programming tools" out there, many
+of which were inspired by, and also borrowed from, docco.  I was particularly
+interested in <a class="normal" href="https://rtomayko.github.io/shocco/" title="">shocco</a>, written in POSIX shell (of which I am a fan).</p>
+<p>Notably missing, however, was a converter of some kind written in AWK.  Thus,
+DOC AWK was born.</p>
+<p>This page is the result of DOC AWK working on itself.  Not bad for &lt; 250 lines
+including commentary!  You can pick up the raw source code of doc.awk <a class="normal" href="https://git.acdw.net/doc.awk" title="">in its
+git repository</a> to use it yourself.</p>
+<h2 id="code"><a href="#code" class="header">Code&nbsp;<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><g transform="rotate(-30, 8, 8)" stroke="#000000" opacity="0.25"><rect fill="none" height="6" width="8" x="2" y="6" rx="1.5"/><rect fill="none" height="6" width="8" x="6" y="4" rx="1.5"/></g></svg></a></h2>
+<pre><code>BEGIN {
+</code></pre>
+<p>All the best awk scripts start with a <code>BEGIN</code> block.  In this one, we
+set a few variables from the environment, with defaults.  I use the
+convenience function <code>getenv</code>, further down this script, to make it
+easier.</p>
+<p>First, the comment regex.  This regex detects a comment <em>line</em>, not an
+inline comment.  By default, it's set up for awk, shell, and other
+languages that use <code>#</code> as a comment delimiter, but you can make it
+whatever you want.</p>
+<pre><code>	COMMENT = getenv("DOCAWK_COMMENT", COMMENT, "^[ \t]*#+[ \t]*")
+</code></pre>
+<p>You can set <code>DOCAWK_TEXTPROC</code> to any text processor you want, but the
+default is the vendored <code>mdown.awk</code> script in this repo.  It's from
+<a class="normal" href="https://github.com/wernsey/d.awk" title="">d.awk</a>.</p>
+<pre><code>	TEXTPROC = getenv("DOCAWK_TEXTPROC", TEXTPROC, "./mdown.awk")
+</code></pre>
+<p>You can also set the processor for code sections of the source file;
+the included <code>htmlsafe.awk</code> simply escapes &lt;, &amp;, and &gt;.</p>
+<pre><code>	CODEPROC = getenv("DOCAWK_CODEPROC", CODEPROC, "./htmlsafe.awk")
+</code></pre>
+<p>Usually, a file header and footer are enough for most documents.  The
+defaults here are the included header.html and footer.html, since the
+default output type is html.</p>
+<p>Each of these documents are actually <em>templates</em>, with keys that can
+expand to variables inside of <code>@@VARIABLE@@</code>.  This is mostly
+for title expansion.</p>
+<pre><code>	HEADER = getenv("DOCAWK_HEADER", HEADER, "./header.html")
+	FOOTER = getenv("DOCAWK_FOOTER", FOOTER, "./footer.html")
+}
+</code></pre>
+<p>Because <code>FILENAME</code> is unset during <code>BEGIN</code>, template expansion that attempts
+to view the filename doesn't work.  Thus, I need a state variable to track
+whether we've started or not (so that I don't print a header with every new
+file).</p>
+<pre><code>! begun {
+</code></pre>
+<p>The template array is initialized with the document's title.</p>
+<pre><code>	TV["TITLE"] = get_title()
+</code></pre>
+<p>Print the header here, since if multiple files are passed to DOC AWK
+they'll all be concatenated anyway.</p>
+<pre><code>	file_print(HEADER)
+}
+</code></pre>
+<p><code>doc.awk</code> is multi-file aware.  It also removes the shebang line from the
+script if it exists, because you probably don't want that in the output.</p>
+<p>It wouldn't be a <em>bad</em> idea to make a heuristic for determining the type of
+source file we're converting here.</p>
+<pre><code>FNR == 1 {
+	begun = 1
+	if ($0 ~ COMMENT) {
+		lt = "text"
+	} else {
+		lt = "code"
+	}
+	if ($0 !~ /^#!/) {
+		bufadd(lt)
+	}
+	next
+}
+</code></pre>
+<p>The main logic is quite simple: if a given line is a comment as defined by
+<code>DOCAWK_COMMENT</code>, it's in a text block and should be treated as such;
+otherwise, it's in a code block.  Accumulate each part in a dedicated buffer,
+and on a switch-over between code and text, print the buffer and reset.</p>
+<pre><code>$0 !~ COMMENT {
+	lt = "code"
+	bufprint("text")
+}
+
+$0 ~ COMMENT {
+	lt = "text"
+	bufprint("code")
+	sub(COMMENT, "", $0)
+}
+
+{
+	bufadd(lt)
+}
+</code></pre>
+<p>Of course, at the end there might be something in either buffer, so print that
+out too.  I've decided to put text last for the possibility of ending commentary.</p>
+<pre><code>END {
+	bufprint("code")
+	bufprint("text")
+	file_print(FOOTER)
+}
+</code></pre>
+<h2 id="functions"><a href="#functions" class="header">Functions&nbsp;<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><g transform="rotate(-30, 8, 8)" stroke="#000000" opacity="0.25"><rect fill="none" height="6" width="8" x="2" y="6" rx="1.5"/><rect fill="none" height="6" width="8" x="6" y="4" rx="1.5"/></g></svg></a></h2>
+<p><em>bufadd</em>: Add a STR to buffer TYPE.  STR defaults to $0, the input record.</p>
+<pre><code>function bufadd(type, str)
+{
+	buf[type] = buf[type] (str ? str : $0) "\n"
+}
+</code></pre>
+<p><em>bufprint</em>: Print a buffer of TYPE.  Automatically wrap the code blocks in a
+little HTML code block.  I could maybe have a DOCAWK_CODE_PRE/POST and maybe
+even one for text too, to make it more extensible (to other markup languages,
+for example).</p>
+<pre><code>function bufprint(type)
+{
+	buf[type] = trim(buf[type])
+	if (buf[type]) {
+		if (type == "code") {
+			printf "&lt;pre&gt;&lt;code&gt;"
+			printf(buf[type]) | CODEPROC
+			close(CODEPROC)
+			print "&lt;/code&gt;&lt;/pre&gt;"
+		} else if (type == "text") {
+			print(buf[type]) | TEXTPROC
+			close(TEXTPROC)
+		}
+		buf[type] = ""
+	}
+}
+</code></pre>
+<p><em>file_print</em>: Print FILE line-by-line.  The <code>&gt; 0</code> check here ensures that it
+bails on error (-1).</p>
+<pre><code>function file_print(file)
+{
+	if (file) {
+		while ((getline l &lt; file) &gt; 0) {
+			print template_expand(l)
+		}
+		close(file)
+	}
+}
+</code></pre>
+<p><em>get_title</em>: get the title of the current script, for the expanded document.
+If variables are set, use those; otherwise try to figure out the title from
+the document's basename.</p>
+<pre><code>function get_title()
+{
+	title = getenv("DOCAWK_TITLE", TITLE)
+	if (! title) {
+		title = FILENAME
+		sub(/.*\//, "", title)
+	}
+	return title
+}
+</code></pre>
+<p><em>getenv</em>: a convenience function for pulling values out of the environment.
+If an environment variable ENV isn't found, test if VAR is set (i.e., <code>doc.awk
+-v var=foo</code>.) and return it if it's set.  Otherwise, return the default value
+DEF.</p>
+<pre><code>function getenv(env, var, def)
+{
+	if (ENVIRON[env]) {
+		return ENVIRON[env]
+	} else if (var) {
+		return var
+	} else {
+		return def
+	}
+}
+</code></pre>
+<p><em>template_expand</em>: expand templates of the form <code>@@template@@</code> in the text.
+Currently it only does variables, and works by line.</p>
+<p>Due to the way awk works, template variables need to live in their own special
+array, <code>TV</code>.  I'd love it if awk had some kind of <code>eval</code> functionality, but at
+least POSIX awk doesn't.</p>
+<pre><code>function template_expand(text)
+{
+	if (match(text, /@@[^@]*@@/)) {
+		var = substr(text, RSTART + 2, RLENGTH - 4)
+		new = substr(text, 1, RSTART - 1)
+		new = new TV[var]
+		new = new substr(text, RSTART + RLENGTH)
+	} else {
+		new = text
+	}
+	return new
+}
+</code></pre>
+<p><em>trim</em>: remove whitespace from either end of a string.</p>
+<pre><code>function trim(str)
+{
+	sub(/^[ \n]*/, "", str)
+	sub(/[ \n]*$/, "", str)
+	return str
+}
+</code></pre>
+</body>
+</html>
diff --git a/doc.awk b/doc.awk
new file mode 100755
index 0000000..4735f9d
--- /dev/null
+++ b/doc.awk
@@ -0,0 +1,213 @@
+#!/bin/awk -f
+# DOC AWK
+# ======
+#
+# A quick-and-dirty literate-programming-style documentation generator
+# inspired by [docco][].
+#
+# by Case Duckworth <acdw@acdw.net>
+#
+# Source available under the [Good Choices License][gcl].
+#
+# [gcl]: https://acdw.casa/gcl Good Choices License
+#
+# There's a lot of quick-and-dirty "literate programming tools" out there, many
+# of which were inspired by, and also borrowed from, docco.  I was particularly
+# interested in [shocco][], written in POSIX shell (of which I am a fan).
+#
+# Notably missing, however, was a converter of some kind written in AWK.  Thus,
+# DOC AWK was born.
+#
+# This page is the result of DOC AWK working on itself.  Not bad for < 250 lines
+# including commentary!  You can pick up the raw source code of doc.awk [in its
+# git repository][git] to use it yourself.
+#
+# [docco]: https://ashkenas.com/docco/
+# [shocco]: https://rtomayko.github.io/shocco/
+# [git]: https://git.acdw.net/docawk
+#
+# Code
+# ----
+BEGIN {
+	# All the best awk scripts start with a `BEGIN` block.  In this one, we
+	# set a few variables from the environment, with defaults.  I use the
+	# convenience function `getenv`, further down this script, to make it
+	# easier.
+	#
+	# First, the comment regex.  This regex detects a comment *line*, not an
+	# inline comment.  By default, it's set up for awk, shell, and other
+	# languages that use `#` as a comment delimiter, but you can make it
+	# whatever you want.
+	COMMENT = getenv("DOCAWK_COMMENT", COMMENT, "^[ \t]*#+[ \t]*")
+	# You can set `DOCAWK_TEXTPROC` to any text processor you want, but the
+	# default is the vendored `mdown.awk` script in this repo.  It's from
+	# [d.awk](https://github.com/wernsey/d.awk).
+	TEXTPROC = getenv("DOCAWK_TEXTPROC", TEXTPROC, "./mdown.awk")
+	# You can also set the processor for code sections of the source file;
+	# the included `htmlsafe.awk` simply escapes <, &, and >.
+	CODEPROC = getenv("DOCAWK_CODEPROC", CODEPROC, "./htmlsafe.awk")
+	# Usually, a file header and footer are enough for most documents.  The
+	# defaults here are the included header.html and footer.html, since the
+	# default output type is html.
+	#
+	# Each of these documents are actually *templates*, with keys that can
+	# expand to variables inside of `@@VARIABLE@@`.  This is mostly
+	# for title expansion.
+	HEADER = getenv("DOCAWK_HEADER", HEADER, "./header.html")
+	FOOTER = getenv("DOCAWK_FOOTER", FOOTER, "./footer.html")
+}
+
+# Because `FILENAME` is unset during `BEGIN`, template expansion that attempts
+# to view the filename doesn't work.  Thus, I need a state variable to track
+# whether we've started or not (so that I don't print a header with every new
+# file).
+! begun {
+	# The template array is initialized with the document's title.
+	TV["TITLE"] = get_title()
+	# Print the header here, since if multiple files are passed to DOC AWK
+	# they'll all be concatenated anyway.
+	file_print(HEADER)
+}
+
+# `doc.awk` is multi-file aware.  It also removes the shebang line from the
+# script if it exists, because you probably don't want that in the output.
+#
+# It wouldn't be a *bad* idea to make a heuristic for determining the type of
+# source file we're converting here.
+FNR == 1 {
+	begun = 1
+	if ($0 ~ COMMENT) {
+		lt = "text"
+	} else {
+		lt = "code"
+	}
+	if ($0 !~ /^#!/) {
+		bufadd(lt)
+	}
+	next
+}
+
+# The main logic is quite simple: if a given line is a comment as defined by
+# `DOCAWK_COMMENT`, it's in a text block and should be treated as such;
+# otherwise, it's in a code block.  Accumulate each part in a dedicated buffer,
+# and on a switch-over between code and text, print the buffer and reset.
+$0 !~ COMMENT {
+	lt = "code"
+	bufprint("text")
+}
+
+$0 ~ COMMENT {
+	lt = "text"
+	bufprint("code")
+	sub(COMMENT, "", $0)
+}
+
+{
+	bufadd(lt)
+}
+
+# Of course, at the end there might be something in either buffer, so print that
+# out too.  I've decided to put text last for the possibility of ending commentary.
+END {
+	bufprint("code")
+	bufprint("text")
+	file_print(FOOTER)
+}
+
+
+# Functions
+# ---------
+#
+# *bufadd*: Add a STR to buffer TYPE.  STR defaults to $0, the input record.
+function bufadd(type, str)
+{
+	buf[type] = buf[type] (str ? str : $0) "\n"
+}
+
+# *bufprint*: Print a buffer of TYPE.  Automatically wrap the code blocks in a
+# little HTML code block.  I could maybe have a DOCAWK_CODE_PRE/POST and maybe
+# even one for text too, to make it more extensible (to other markup languages,
+# for example).
+function bufprint(type)
+{
+	buf[type] = trim(buf[type])
+	if (buf[type]) {
+		if (type == "code") {
+			printf "<pre><code>"
+			printf(buf[type]) | CODEPROC
+			close(CODEPROC)
+			print "</code></pre>"
+		} else if (type == "text") {
+			print(buf[type]) | TEXTPROC
+			close(TEXTPROC)
+		}
+		buf[type] = ""
+	}
+}
+
+# *file_print*: Print FILE line-by-line.  The `> 0` check here ensures that it
+# bails on error (-1).
+function file_print(file)
+{
+	if (file) {
+		while ((getline l < file) > 0) {
+			print template_expand(l)
+		}
+		close(file)
+	}
+}
+
+# *get_title*: get the title of the current script, for the expanded document.
+# If variables are set, use those; otherwise try to figure out the title from
+# the document's basename.
+function get_title()
+{
+	title = getenv("DOCAWK_TITLE", TITLE)
+	if (! title) {
+		title = FILENAME
+		sub(/.*\//, "", title)
+	}
+	return title
+}
+
+# *getenv*: a convenience function for pulling values out of the environment.
+# If an environment variable ENV isn't found, test if VAR is set (i.e., `doc.awk
+# -v var=foo`.) and return it if it's set.  Otherwise, return the default value
+# DEF.
+function getenv(env, var, def)
+{
+	if (ENVIRON[env]) {
+		return ENVIRON[env]
+	} else if (var) {
+		return var
+	} else {
+		return def
+	}
+}
+
+# *template_expand*: expand templates of the form `@@template@@` in the text.
+# Currently it only does variables, and works by line.
+#
+# Due to the way awk works, template variables need to live in their own special
+# array, `TV`.  I'd love it if awk had some kind of `eval` functionality, but at
+# least POSIX awk doesn't.
+function template_expand(text)
+{
+	if (match(text, /@@[^@]*@@/)) {
+		var = substr(text, RSTART + 2, RLENGTH - 4)
+		new = substr(text, 1, RSTART - 1)
+		new = new TV[var]
+		new = new substr(text, RSTART + RLENGTH)
+	} else {
+		new = text
+	}
+	return new
+}
+
+# *trim*: remove whitespace from either end of a string.
+function trim(str)
+{
+	sub(/^[ \n]*/, "", str)
+	sub(/[ \n]*$/, "", str)
+	return str
+}
diff --git a/footer.html b/footer.html
new file mode 100644
index 0000000..308b1d0
--- /dev/null
+++ b/footer.html
@@ -0,0 +1,2 @@
+</body>
+</html>
diff --git a/header.html b/header.html
new file mode 100644
index 0000000..38cba73
--- /dev/null
+++ b/header.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<title>@@TITLE@@</title>
+<link type="text/css" rel="stylesheet" href="style.css" />
+<body>
diff --git a/htmlsafe.awk b/htmlsafe.awk
new file mode 100755
index 0000000..a49fe21
--- /dev/null
+++ b/htmlsafe.awk
@@ -0,0 +1,8 @@
+#!/bin/awk -f
+{
+	gsub(/&/, "\\&amp;", $0)
+	gsub(/</, "\\&lt;", $0)
+	gsub(/>/, "\\&gt;", $0)
+	print
+}
+
diff --git a/mdown.awk b/mdown.awk
new file mode 100755
index 0000000..ca39b09
--- /dev/null
+++ b/mdown.awk
@@ -0,0 +1,903 @@
+#! /usr/bin/awk -f
+#
+# Markdown processor in AWK. It is simplified from d.awk
+# https://github.com/wernsey/d.awk
+#
+# (c) 2016 Werner Stoop
+# Slight modifications (c) 2022 Case Duckworth
+# Copying and distribution of this file, with or without modification,
+# are permitted in any medium without royalty provided the copyright
+# notice and this notice are preserved. This file is offered as-is,
+# without any warranty.
+BEGIN {
+	# Configuration options
+	if (Title == "") {
+		Title = "Documentation"
+	}
+	if (Theme == "") {
+		Theme = 1
+	}
+	if (HideToCLevel == "") {
+		HideToCLevel = 3
+	}
+	if (Lang == "") {
+		Lang = "en"
+	}
+	if (Tables == "") {
+		Tables = 1
+		#TopLinks = 1;
+		#classic_underscore = 1;
+	}
+	if (MaxWidth == "") {
+		MaxWidth = "1080px"
+	}
+	if (NumberHeadings == "") {
+		NumberHeadings = 1
+	}
+	if (NumberH1s == "") {
+		NumberH1s = 0
+	}
+	Mode = "p"
+	ToC = ""
+	ToCLevel = 1
+	CSS = init_css(Theme)
+	for (i = 0; i < 128; i++) {
+		_ord[sprintf("%c", i)] = i
+	}
+	srand()
+}
+
+{
+	gsub(/\r/, "", $0)
+}
+
+{
+	Out = Out filter($0)
+}
+
+END {
+	if (Mode == "ul" || Mode == "ol") {
+		while (ListLevel > 1) {
+			Buf = Buf "\n</" Open[ListLevel--] ">"
+		}
+		Out = Out tag(Mode, Buf "\n")
+	} else if (Mode == "pre") {
+		while (ListLevel > 1) {
+			Buf = Buf "\n</" Open[ListLevel--] ">"
+		}
+		Out = Out tag(Mode, Buf "\n")
+	} else if (Mode == "table") {
+		Out = Out end_table()
+	} else {
+		Buf = trim(scrub(Buf))
+		if (Buf) {
+			Out = Out tag(Mode, Buf)
+		}
+	}
+	if (ToC && match(Out, /!\[toc[-+]?\]/)) {
+		print "<script><!--\n" "function toggle_toc(n) {\n" "    var toc=document.getElementById('table-of-contents-' + n);\n" "    var btn=document.getElementById('btn-text-' + n);\n" "    toc.style.display=(toc.style.display=='none')?'block':'none';\n" "    btn.innerHTML=(toc.style.display=='none')?'&#x25BA;':'&#x25BC;';\n" "}\n" "function toggle_toc_ul(n) {   \n" "    var toc=document.getElementById('toc-ul-' + n);   \n" "    var btn=document.getElementById('toc-btn-' + n);   \n" "    if(toc) {\n" "        toc.style.display=(toc.style.display=='none')?'block':'none';   \n" "        btn.innerHTML=(toc.style.display=='none')?'&#x25BA;':'&#x25BC;';\n" "    }\n" "}\n" "//-->\n</script>"
+	}
+	if (Out) {
+		Out = fix_footnotes(Out)
+		Out = fix_links(Out)
+		Out = fix_abbrs(Out)
+		Out = make_toc(Out)
+		print trim(Out)
+		if (footnotes) {
+			footnotes = fix_links(footnotes)
+			print "<hr><ol class=\"footnotes\">\n" footnotes "</ol>"
+		}
+	}
+}
+
+
+function end_table(r, c, t, a, s)
+{
+	for (r = 1; r < Row; r++) {
+		t = IsHeaders[r] ? "th" : "td"
+		s = s "<tr>"
+		for (c = 1; c <= NCols[r]; c++) {
+			a = Align[c]
+			if (a) {
+				s = s "<" t " align=\"" a "\">" scrub(Table[r, c]) "</" t ">"
+			} else {
+				s = s "<" t ">" scrub(Table[r, c]) "</" t ">"
+			}
+		}
+		s = s "</tr>\n"
+	}
+	return tag("table", s, "class=\"da\"")
+}
+
+function escape(st)
+{
+	gsub(/&/, "\\&amp;", st)
+	gsub(/</, "\\&lt;", st)
+	gsub(/>/, "\\&gt;", st)
+	return st
+}
+
+function filter(st, res, tmp, linkdesc, url, delim, edelim, name, def, plang, mmaid, cols, i)
+{
+	if (Mode == "p") {
+		if (match(st, /^[[:space:]]*\[[-._[:alnum:][:space:]]+\]:/)) {
+			linkdesc = ""
+			LastLink = 0
+			match(st, /\[.*\]/)
+			LinkRef = tolower(substr(st, RSTART + 1, RLENGTH - 2))
+			st = substr(st, RSTART + RLENGTH + 2)
+			match(st, /[^[:space:]]+/)
+			url = substr(st, RSTART, RLENGTH)
+			st = substr(st, RSTART + RLENGTH + 1)
+			if (match(url, /^<.*>/)) {
+				url = substr(url, RSTART + 1, RLENGTH - 2)
+			}
+			if (match(st, /["'(]/)) {
+				delim = substr(st, RSTART, 1)
+				edelim = (delim == "(") ? ")" : delim
+				if (match(st, delim ".*" edelim)) {
+					linkdesc = substr(st, RSTART + 1, RLENGTH - 2)
+				}
+			}
+			LinkUrls[LinkRef] = escape(url)
+			if (! linkdesc) {
+				LastLink = 1
+			}
+			LinkDescs[LinkRef] = escape(linkdesc)
+			return
+		} else if (LastLink && match(st, /^[[:space:]]*["'(]/)) {
+			match(st, /["'(]/)
+			delim = substr(st, RSTART, 1)
+			edelim = (delim == "(") ? ")" : delim
+			st = substr(st, RSTART)
+			if (match(st, delim ".*" edelim)) {
+				LinkDescs[LinkRef] = escape(substr(st, RSTART + 1, RLENGTH - 2))
+			}
+			LastLink = 0
+			return
+		} else if (match(st, /^[[:space:]]*\[\^[-._[:alnum:][:space:]]+\]:[[:space:]]*/)) {
+			match(st, /\[\^[[:alnum:]]+\]:/)
+			name = substr(st, RSTART + 2, RLENGTH - 4)
+			def = substr(st, RSTART + RLENGTH + 1)
+			Footnote[tolower(name)] = scrub(def)
+			return
+		} else if (match(st, /^[[:space:]]*\*\[[[:alnum:]]+\]:[[:space:]]*/)) {
+			match(st, /\[[[:alnum:]]+\]/)
+			name = substr(st, RSTART + 1, RLENGTH - 2)
+			def = substr(st, RSTART + RLENGTH + 2)
+			Abbrs[toupper(name)] = def
+			return
+		} else if (match(st, /^((    )| *\t)/) || match(st, /^[[:space:]]*```+[[:alnum:]]*/)) {
+			Preterm = trim(substr(st, RSTART, RLENGTH))
+			st = substr(st, RSTART + RLENGTH)
+			if (Buf) {
+				res = tag("p", scrub(Buf))
+			}
+			Buf = st
+			push("pre")
+		} else if (! trim(Prev) && match(st, /^[[:space:]]*[*-][[:space:]]*[*-][[:space:]]*[*-][-*[:space:]]*$/)) {
+			if (Buf) {
+				res = tag("p", scrub(Buf))
+			}
+			Buf = ""
+			res = res "<hr>\n"
+		} else if (match(st, /^[[:space:]]*===+[[:space:]]*$/)) {
+			Buf = trim(substr(Buf, 1, length(Buf) - length(Prev) - 1))
+			if (Buf) {
+				res = tag("p", scrub(Buf))
+			}
+			if (Prev) {
+				res = res heading(1, scrub(Prev))
+			}
+			Buf = ""
+		} else if (match(st, /^[[:space:]]*---+[[:space:]]*$/)) {
+			Buf = trim(substr(Buf, 1, length(Buf) - length(Prev) - 1))
+			if (Buf) {
+				res = tag("p", scrub(Buf))
+			}
+			if (Prev) {
+				res = res heading(2, scrub(Prev))
+			}
+			Buf = ""
+		} else if (match(st, /^[[:space:]]*#+/)) {
+			sub(/#+[[:space:]]*$/, "", st)
+			match(st, /#+/)
+			ListLevel = RLENGTH
+			tmp = substr(st, RSTART + RLENGTH)
+			if (Buf) {
+				res = tag("p", scrub(Buf))
+			}
+			res = res heading(ListLevel, scrub(trim(tmp)))
+			Buf = ""
+		} else if (match(st, /^[[:space:]]*>/)) {
+			if (Buf) {
+				res = tag("p", scrub(Buf))
+			}
+			Buf = scrub(trim(substr(st, RSTART + RLENGTH)))
+			push("blockquote")
+		} else if (Tables && match(st, /.*\|(.*\|)+/)) {
+			if (Buf) {
+				res = tag("p", scrub(Buf))
+			}
+			Row = 1
+			for (i = 1; i <= MaxCols; i++) {
+				Align[i] = ""
+			}
+			process_table_row(st)
+			push("table")
+		} else if (match(st, /^[[:space:]]*([*+-]|[[:digit:]]+\.)[[:space:]]/)) {
+			if (Buf) {
+				res = tag("p", scrub(Buf))
+			}
+			Buf = ""
+			match(st, /^[[:space:]]*/)
+			ListLevel = 1
+			indent[ListLevel] = RLENGTH
+			Open[ListLevel] = match(st, /^[[:space:]]*[*+-][[:space:]]*/) ? "ul" : "ol"
+			push(Open[ListLevel])
+			res = res filter(st)
+		} else if (match(st, /^[[:space:]]*$/)) {
+			if (trim(Buf)) {
+				res = tag("p", scrub(trim(Buf)))
+				Buf = ""
+			}
+		} else {
+			Buf = Buf st "\n"
+		}
+		LastLink = 0
+	} else if (Mode == "blockquote") {
+		if (match(st, /^[[:space:]]*>[[:space:]]*$/)) {
+			Buf = Buf "\n</p><p>"
+		} else if (match(st, /^[[:space:]]*>/)) {
+			Buf = Buf "\n" scrub(trim(substr(st, RSTART + RLENGTH)))
+		} else if (match(st, /^[[:space:]]*$/)) {
+			res = tag("blockquote", tag("p", trim(Buf)))
+			pop()
+			res = res filter(st)
+		} else {
+			Buf = Buf st
+		}
+	} else if (Mode == "table") {
+		if (match(st, /.*\|(.*\|)+/)) {
+			process_table_row(st)
+		} else {
+			res = end_table()
+			pop()
+			res = res filter(st)
+		}
+	} else if (Mode == "pre") {
+		if (! Preterm && match(st, /^((    )| *\t)/) || Preterm && ! match(st, /^[[:space:]]*```+/)) {
+			Buf = Buf ((Buf) ? "\n" : "") substr(st, RSTART + RLENGTH)
+		} else {
+			gsub(/\t/, "    ", Buf)
+			if (length(trim(Buf)) > 0) {
+				plang = ""
+				mmaid = 0
+				if (match(Preterm, /^[[:space:]]*```+/)) {
+					plang = trim(substr(Preterm, RSTART + RLENGTH))
+					if (plang) {
+						HasPretty = 1
+						if (plang == "auto") {
+							plang = "class=\"prettyprint\""
+						} else {
+							plang = "class=\"prettyprint lang-" plang "\""
+						}
+					}
+				}
+				if (mmaid && Mermaid) {
+					res = tag("div", Buf, "class=\"mermaid\"")
+				} else {
+					res = tag("pre", tag("code", escape(Buf), plang))
+				}
+			}
+			pop()
+			if (Preterm) {
+				sub(/^[[:space:]]*```+[[:alnum:]]*/, "", st)
+			}
+			res = res filter(st)
+		}
+	} else if (Mode == "ul" || Mode == "ol") {
+		if (ListLevel == 0 || match(st, /^[[:space:]]*$/) && (RLENGTH <= indent[1])) {
+			while (ListLevel > 1) {
+				Buf = Buf "\n</" Open[ListLevel--] ">"
+			}
+			res = tag(Mode, "\n" Buf "\n")
+			pop()
+		} else if (match(st, /^[[:space:]]*([*+-]|[[:digit:]]+\.)/)) {
+			tmp = substr(st, RLENGTH + 1)
+			match(st, /^[[:space:]]*/)
+			if (RLENGTH > indent[ListLevel]) {
+				indent[++ListLevel] = RLENGTH
+				if (match(st, /^[[:space:]]*[*+-]/)) {
+					Open[ListLevel] = "ul"
+				} else {
+					Open[ListLevel] = "ol"
+				}
+				Buf = Buf "\n<" Open[ListLevel] ">"
+			} else {
+				while (RLENGTH < indent[ListLevel]) {
+					Buf = Buf "\n</" Open[ListLevel--] ">"
+				}
+			}
+			if (match(tmp, /^[[:space:]]*\[[xX[:space:]]\]/)) {
+				st = substr(tmp, RLENGTH + 1)
+				tmp = tolower(substr(tmp, RSTART, RLENGTH))
+				Buf = Buf "<li><input type=\"checkbox\" " (index(tmp, "x") ? "checked" : "") " disabled>" scrub(st)
+			} else {
+				Buf = Buf "<li>" scrub(tmp)
+			}
+		} else if (match(st, /^[[:space:]]*$/)) {
+			Buf = Buf "<br>\n"
+		} else {
+			sub(/^[[:space:]]+/, "", st)
+			Buf = Buf "\n" scrub(st)
+		}
+	}
+	Prev = st
+	return res
+}
+
+function fix_abbrs(str, st, k, r, p)
+{
+	for (k in Abbrs) {
+		r = ""
+		st = str
+		t = escape(Abbrs[toupper(k)])
+		gsub(/&/, "\\&", t)
+		p = match(st, "[^[:alnum:]]" k "[^[:alnum:]]")
+		while (p) {
+			r = r substr(st, 1, RSTART)
+			r = r "<abbr title=\"" t "\">" k "</abbr>"
+			st = substr(st, RSTART + RLENGTH - 1)
+			p = match(st, "[^[:alnum:]]" k "[^[:alnum:]]")
+		}
+		str = r st
+	}
+	return str
+}
+
+function fix_footnotes(st, r, p, n, i, d, fn, fc)
+{
+	p = match(st, /\[\^[^\]]+\]/)
+	while (p) {
+		if (substr(st, RSTART - 2, 1) == "\\") {
+			r = r substr(st, 1, RSTART - 3) substr(st, RSTART, RLENGTH)
+			st = substr(st, RSTART + RLENGTH)
+			p = match(st, /\[\^[^\]]+\]/)
+			continue
+		}
+		r = r substr(st, 1, RSTART - 1)
+		d = substr(st, RSTART + 2, RLENGTH - 3)
+		n = tolower(d)
+		st = substr(st, RSTART + RLENGTH)
+		if (Footnote[tolower(n)]) {
+			if (! fn[n]) {
+				fn[n] = ++fc
+			}
+			d = Footnote[n]
+		} else {
+			Footnote[n] = scrub(d)
+			if (! fn[n]) {
+				fn[n] = ++fc
+			}
+		}
+		footname[fc] = n
+		d = strip_tags(d)
+		if (length(d) > 20) {
+			d = substr(d, 1, 20) "&hellip;"
+		}
+		r = r "<sup title=\"" d "\"><a href=\"#footnote-" fn[n] "\" id=\"footnote-pos-" fn[n] "\" class=\"footnote\">[" fn[n] "]</a></sup>"
+		p = match(st, /\[\^[^\]]+\]/)
+	}
+	for (i = 1; i <= fc; i++) {
+		footnotes = footnotes "<li id=\"footnote-" i "\">" Footnote[footname[i]] "<a title=\"Return to Document\" class=\"footnote-back\" href=\"#footnote-pos-" i "\">&nbsp;&nbsp;&#8630;&nbsp;Back</a></li>\n"
+	}
+	return (r st)
+}
+
+function fix_links(st, lt, ld, lr, url, img, res, rx, pos, pre)
+{
+	do {
+		pre = match(st, /<(pre|code)>/)	# Don't substitute in <pre> or <code> blocks
+		pos = match(st, /\[[^\]]+\]/)
+		if (! pos) {
+			break
+		}
+		if (pre && pre < pos) {
+			match(st, /<\/(pre|code)>/)
+			res = res substr(st, 1, RSTART + RLENGTH)
+			st = substr(st, RSTART + RLENGTH + 1)
+			continue
+		}
+		img = substr(st, RSTART - 1, 1) == "!"
+		if (substr(st, RSTART - (img ? 2 : 1), 1) == "\\") {
+			res = res substr(st, 1, RSTART - (img ? 3 : 2))
+			if (img && substr(st, RSTART, RLENGTH) == "[toc]") {
+				res = res "\\"
+			}
+			res = res substr(st, RSTART - (img ? 1 : 0), RLENGTH + (img ? 1 : 0))
+			st = substr(st, RSTART + RLENGTH)
+			continue
+		}
+		res = res substr(st, 1, RSTART - (img ? 2 : 1))
+		rx = substr(st, RSTART, RLENGTH)
+		st = substr(st, RSTART + RLENGTH)
+		if (match(st, /^[[:space:]]*\([^)]+\)/)) {
+			lt = substr(rx, 2, length(rx) - 2)
+			match(st, /\([^)]+\)/)
+			url = substr(st, RSTART + 1, RLENGTH - 2)
+			st = substr(st, RSTART + RLENGTH)
+			ld = ""
+			if (match(url, /[[:space:]]+["']/)) {
+				ld = url
+				url = substr(url, 1, RSTART - 1)
+				match(ld, /["']/)
+				delim = substr(ld, RSTART, 1)
+				if (match(ld, delim ".*" delim)) {
+					ld = substr(ld, RSTART + 1, RLENGTH - 2)
+				}
+			} else {
+				ld = ""
+			}
+			if (img) {
+				res = res "<img src=\"" url "\" title=\"" ld "\" alt=\"" lt "\">"
+			} else {
+				res = res "<a class=\"normal\" href=\"" url "\" title=\"" ld "\">" lt "</a>"
+			}
+		} else if (match(st, /^[[:space:]]*\[[^\]]*\]/)) {
+			lt = substr(rx, 2, length(rx) - 2)
+			match(st, /\[[^\]]*\]/)
+			lr = trim(tolower(substr(st, RSTART + 1, RLENGTH - 2)))
+			if (! lr) {
+				lr = tolower(trim(lt))
+				if (LinkDescs[lr]) {
+					lt = LinkDescs[lr]
+				}
+			}
+			st = substr(st, RSTART + RLENGTH)
+			url = LinkUrls[lr]
+			ld = LinkDescs[lr]
+			if (img) {
+				res = res "<img src=\"" url "\" title=\"" ld "\" alt=\"" lt "\">"
+			} else if (url) {
+				res = res "<a class=\"normal\" href=\"" url "\" title=\"" ld "\">" lt "</a>"
+			} else {
+				res = res "[" lt "][" lr "]"
+			}
+		} else {
+			res = res (img ? "!" : "") rx
+		}
+	} while (pos > 0)
+	return (res st)
+}
+
+function heading(level, st, res, href, u, text, svg)
+{
+	if (level > 6) {
+		level = 6
+	}
+	st = trim(st)
+	href = tolower(st)
+	href = strip_tags(href)
+	gsub(/[^-_ [:alnum:]]+/, "", href)
+	gsub(/[[:space:]]/, "-", href)
+	if (TitleUrls[href]) {
+		for (u = 1; TitleUrls[href "-" u]; u++) {
+		}
+		href = href "-" u
+	}
+	TitleUrls[href] = "#" href
+	svg = "<svg width=\"16\" height=\"16\" xmlns=\"http://www.w3.org/2000/svg\"><g transform=\"rotate(-30, 8, 8)\" stroke=\"#000000\" opacity=\"0.25\"><rect fill=\"none\" height=\"6\" width=\"8\" x=\"2\" y=\"6\" rx=\"1.5\"/><rect fill=\"none\" height=\"6\" width=\"8\" x=\"6\" y=\"4\" rx=\"1.5\"/></g></svg>"
+	text = "<a href=\"#" href "\" class=\"header\">" st "&nbsp;" svg "</a>" (TopLinks ? "&nbsp;&nbsp;<a class=\"top\" title=\"Return to top\" href=\"#\">&#8593;&nbsp;Top</a>" : "")
+	res = tag("h" level, text, "id=\"" href "\"")
+	for (; ToCLevel < level; ToCLevel++) {
+		ToC_ID++
+		if (ToCLevel < HideToCLevel) {
+			ToC = ToC "<a class=\"toc-button\" id=\"toc-btn-" ToC_ID "\" onclick=\"toggle_toc_ul('" ToC_ID "')\">&#x25BC;</a>"
+			ToC = ToC "<ul class=\"toc toc-" ToCLevel "\" id=\"toc-ul-" ToC_ID "\">"
+		} else {
+			ToC = ToC "<a class=\"toc toc-button\" id=\"toc-btn-" ToC_ID "\" onclick=\"toggle_toc_ul('" ToC_ID "')\">&#x25BA;</a>"
+			ToC = ToC "<ul style=\"display:none;\" class=\"toc toc-" ToCLevel "\" id=\"toc-ul-" ToC_ID "\">"
+		}
+	}
+	for (; ToCLevel > level; ToCLevel--) {
+		ToC = ToC "</ul>"
+	}
+	ToC = ToC "<li class=\"toc-" level "\"><a class=\"toc toc-" level "\" href=\"#" href "\">" st "</a>\n"
+	ToCLevel = level
+	return res
+}
+
+function init_css(Theme, css, ss, hr, c1, c2, c3, c4, c5, bg1, bg2, bg3, bg4, ff, fs, i)
+{
+	if (Theme == "0") {
+		return ""
+	}
+	css["body"] = "color:%color1%;font-family:%font-family%;font-size:%font-size%;line-height:1.5em;" "padding:1em 2em;width:80%;max-width:%maxwidth%;margin:0 auto;min-height:100%;float:none;"
+	css["h1"] = "border-bottom:1px solid %color1%;padding:0.3em 0.1em;"
+	css["h1 a"] = "color:%color1%;"
+	css["h2"] = "color:%color2%;border-bottom:1px solid %color2%;padding:0.2em 0.1em;"
+	css["h2 a"] = "color:%color2%;"
+	css["h3"] = "color:%color3%;border-bottom:1px solid %color3%;padding:0.1em 0.1em;"
+	css["h3 a"] = "color:%color3%;"
+	css["h4,h5,h6"] = "padding:0.1em 0.1em;"
+	css["h4 a,h5 a,h6 a"] = "color:%color4%;"
+	css["h1,h2,h3,h4,h5,h6"] = "font-weight:bolder;line-height:1.2em;"
+	css["h4"] = "border-bottom:1px solid %color4%"
+	css["p"] = "margin:0.5em 0.1em;"
+	css["hr"] = "background:%color1%;height:1px;border:0;"
+	css["a.normal, a.toc"] = "color:%color2%;"
+	#css["a.normal:visited"] = "color:%color2%;";
+	#css["a.normal:active"] = "color:%color4%;";
+	css["a.normal:hover, a.toc:hover"] = "color:%color4%;"
+	css["a.top"] = "font-size:x-small;text-decoration:initial;float:right;"
+	css["a.header svg"] = "opacity:0;"
+	css["a.header:hover svg"] = "opacity:1;"
+	css["a.header"] = "text-decoration: none;"
+	css["strong,b"] = "color:%color1%"
+	css["code"] = "color:%color2%;font-weight:bold;"
+	css["blockquote"] = "margin-left:1em;color:%color2%;border-left:0.2em solid %color3%;padding:0.25em 0.5em;overflow-x:auto;"
+	css["pre"] = "color:%color2%;background:%color5%;border:1px solid;border-radius:2px;line-height:1.25em;margin:0.25em 0.5em;padding:0.75em;overflow-x:auto;"
+	css["table.dawk-ex"] = "border-collapse:collapse;margin:0.5em;"
+	css["th.dawk-ex,td.dawk-ex"] = "padding:0.5em 0.75em;border:1px solid %color4%;"
+	css["th.dawk-ex"] = "color:%color2%;border:1px solid %color3%;border-bottom:2px solid %color3%;"
+	css["tr.dawk-ex:nth-child(odd)"] = "background-color:%color5%;"
+	css["table.da"] = "border-collapse:collapse;margin:0.5em;"
+	css["table.da th,td"] = "padding:0.5em 0.75em;border:1px solid %color4%;"
+	css["table.da th"] = "color:%color2%;border:1px solid %color3%;border-bottom:2px solid %color3%;"
+	css["table.da tr:nth-child(odd)"] = "background-color:%color5%;"
+	css["div.dawk-ex"] = "padding:0.5em;"
+	css["caption.dawk-ex"] = "padding:0.5em;font-style:italic;"
+	css["dl.dawk-ex"] = "margin:0.5em;"
+	css["dt.dawk-ex"] = "font-weight:bold;"
+	css["dd.dawk-ex"] = "padding:0.3em;"
+	css["mark.dawk-ex"] = "color:%color5%;background-color:%color4%;"
+	css["del.dawk-ex,s.dawk-ex"] = "color:%color4%;"
+	css["a.toc-button"] = "color:%color2%;cursor:pointer;font-size:small;padding: 0.3em 0.5em 0.5em 0.5em;font-family:monospace;border-radius:3px;"
+	css["a.toc-button:hover"] = "color:%color4%;background:%color5%;"
+	css["div#table-of-contents"] = "padding:0;font-size:smaller;"
+	css["abbr"] = "cursor:help;"
+	css["ol.footnotes"] = "font-size:small;color:%color4%"
+	css["a.footnote"] = "font-size:smaller;text-decoration:initial;"
+	css["a.footnote-back"] = "text-decoration:initial;font-size:x-small;"
+	css[".fade"] = "color:%color5%;"
+	css[".highlight"] = "color:%color2%;background-color:%color5%;"
+	css["summary"] = "cursor:pointer;"
+	css["ul.toc"] = "list-style-type:none;"
+	if (NumberHeadings) {
+		if (NumberH1s) {
+			css["body"] = css["body"] "counter-reset: h1 toc1;"
+			css["h1"] = css["h1"] "counter-reset: h2 h3 h4;"
+			css["h2"] = css["h2"] "counter-reset: h3 h4;"
+			css["h3"] = css["h3"] "counter-reset: h4;"
+			css["h1::before"] = "content: counter(h1) \" \"; counter-increment: h1; margin-right: 10px;"
+			css["h2::before"] = "content: counter(h1) \".\"counter(h2) \" \";counter-increment: h2; margin-right: 10px;"
+			css["h3::before"] = "content: counter(h1) \".\"counter(h2) \".\"counter(h3) \" \";counter-increment: h3; margin-right: 10px;"
+			css["h4::before"] = "content: counter(h1) \".\"counter(h2) \".\"counter(h3)\".\"counter(h4) \" \";counter-increment: h4; margin-right: 10px;"
+			css["li.toc-1"] = "counter-reset: toc2 toc3 toc4;"
+			css["li.toc-2"] = "counter-reset: toc3 toc4;"
+			css["li.toc-3"] = "counter-reset: toc4;"
+			css["a.toc-1::before"] = "content: counter(h1) \"  \";counter-increment: toc1;"
+			css["a.toc-2::before"] = "content: counter(h1) \".\" counter(toc2) \"  \";counter-increment: toc2;"
+			css["a.toc-3::before"] = "content: counter(h1) \".\" counter(toc2) \".\" counter(toc3) \"  \";counter-increment: toc3;"
+			css["a.toc-4::before"] = "content: counter(h1) \".\" counter(toc2) \".\" counter(toc3) \".\" counter(toc4) \"  \";counter-increment: toc4;"
+		} else {
+			css["h1"] = css["h1"] "counter-reset: h2 h3 h4;"
+			css["h2"] = css["h2"] "counter-reset: h3 h4;"
+			css["h3"] = css["h3"] "counter-reset: h4;"
+			css["h2::before"] = "content: counter(h2) \" \";counter-increment: h2; margin-right: 10px;"
+			css["h3::before"] = "content: counter(h2) \".\"counter(h3) \" \";counter-increment: h3; margin-right: 10px;"
+			css["h4::before"] = "content: counter(h2) \".\"counter(h3)\".\"counter(h4) \" \";counter-increment: h4; margin-right: 10px;"
+			css["li.toc-1"] = "counter-reset: toc2 toc3 toc4;"
+			css["li.toc-2"] = "counter-reset: toc3 toc4;"
+			css["li.toc-3"] = "counter-reset: toc4;"
+			css["a.toc-2::before"] = "content: counter(toc2) \"  \";counter-increment: toc2;"
+			css["a.toc-3::before"] = "content: counter(toc2) \".\" counter(toc3) \"  \";counter-increment: toc3;"
+			css["a.toc-4::before"] = "content: counter(toc2) \".\" counter(toc3) \".\" counter(toc4) \"  \";counter-increment: toc4;"
+		}
+	}
+	# Colors:
+	#c1="#314070";c2="#465DA6";c3="#6676A8";c4="#A88C3F";c5="#E8E4D9";
+	c1 = "#314070"
+	c2 = "#384877"
+	c3 = "#6676A8"
+	c4 = "#738FD0"
+	c5 = "#FBFCFF"
+	# Font Family:
+	ff = "sans-serif"
+	fs = "11pt"
+	# Alternative color scheme suggestions:
+	#c1="#303F9F";c2="#0449CC";c3="#2162FA";c4="#4B80FB";c5="#EDF2FF";
+	#ff="\"Trebuchet MS\", Helvetica, sans-serif";
+	#c1="#430005";c2="#740009";c3="#A6373F";c4="#c55158";c5="#fbf2f2";
+	#ff="Verdana, Geneva, sans-serif";
+	#c1="#083900";c2="#0D6300";c3="#3C8D2F";c4="#50be3f";c5="#f2faf1";
+	#ff="Georgia, serif";
+	#c1="#35305D";c2="#646379";c3="#7A74A5";c4="#646392";c5="#fafafa";
+	for (i = 0; i <= 255; i++) {
+		_hex[sprintf("%02X", i)] = i
+	}
+	for (k in css) {
+		ss = ss "\n" k "{" css[k] "}"
+	}
+	gsub(/%maxwidth%/, MaxWidth, ss)
+	gsub(/%color1%/, c1, ss)
+	gsub(/%color2%/, c2, ss)
+	gsub(/%color3%/, c3, ss)
+	gsub(/%color4%/, c4, ss)
+	gsub(/%color5%/, c5, ss)
+	gsub(/%font-family%/, ff, ss)
+	gsub(/%font-size%/, fs, ss)
+	gsub(/%hr%/, hr, ss)
+	return ss
+}
+
+function itag(t, body)
+{
+	return ("<" t ">" body "</" t ">")
+}
+
+function make_toc(st, r, p, dis, t, n)
+{
+	if (! ToC) {
+		return st
+	}
+	for (; ToCLevel > 1; ToCLevel--) {
+		ToC = ToC "</ul>"
+	}
+	p = match(st, /!\[toc[-+]?\]/)
+	while (p) {
+		if (substr(st, RSTART - 1, 1) == "\\") {
+			r = r substr(st, 1, RSTART - 2) substr(st, RSTART, RLENGTH)
+			st = substr(st, RSTART + RLENGTH)
+			p = match(st, /!\[toc[-+]?\]/)
+			continue
+		}
+		++n
+		dis = index(substr(st, RSTART, RLENGTH), "+")
+		t = "<div>\n<a id=\"toc-button-" n "\" class=\"toc-button\" onclick=\"toggle_toc(" n ")\"><span id=\"btn-text-" n "\">" (dis ? "&#x25BC;" : "&#x25BA;") "</span>&nbsp;Contents</a>\n" "<div id=\"table-of-contents-" n "\" style=\"display:" (dis ? "block" : "none") ";\">\n<ul class=\"toc toc-1\">" ToC "</ul>\n</div>\n</div>"
+		r = r substr(st, 1, RSTART - 1)
+		r = r t
+		st = substr(st, RSTART + RLENGTH)
+		p = match(st, /!\[toc[-+]?\]/)
+	}
+	return (r st)
+}
+
+function obfuscate(e, r, i, t, o)
+{
+	for (i = 1; i <= length(e); i++) {
+		t = substr(e, i, 1)
+		r = int(rand() * 100)
+		if (r > 50) {
+			o = o sprintf("&#x%02X;", _ord[t])
+		} else if (r > 10) {
+			o = o sprintf("&#%d;", _ord[t])
+		} else {
+			o = o t
+		}
+	}
+	return o
+}
+
+function pop()
+{
+	Mode = Stack[--StackTop]
+	Buf = ""
+	return Mode
+}
+
+function process_table_row(st, cols, i)
+{
+	if (match(st, /^[[:space:]]*\|/)) {
+		st = substr(st, RSTART + RLENGTH)
+	}
+	if (match(st, /\|[[:space:]]*$/)) {
+		st = substr(st, 1, RSTART - 1)
+	}
+	st = trim(st)
+	if (match(st, /^([[:space:]:|]|---+)*$/)) {
+		IsHeaders[Row - 1] = 1
+		cols = split(st, A, /[[:space:]]*\|[[:space:]]*/)
+		for (i = 1; i <= cols; i++) {
+			if (match(A[i], /^:-*:$/)) {
+				Align[i] = "center"
+			} else if (match(A[i], /^-*:$/)) {
+				Align[i] = "right"
+			} else if (match(A[i], /^:-*$/)) {
+				Align[i] = "left"
+			}
+		}
+		return
+	}
+	cols = split(st, A, /[[:space:]]*\|[[:space:]]*/)
+	for (i = 1; i <= cols; i++) {
+		Table[Row, i] = A[i]
+	}
+	NCols[Row] = cols
+	if (cols > MaxCols) {
+		MaxCols = cols
+	}
+	IsHeaders[Row] = 0
+	Row++
+}
+
+function push(newmode)
+{
+	Stack[StackTop++] = Mode
+	Mode = newmode
+}
+
+function scrub(st, mp, ms, me, r, p, tg, a)
+{
+	sub(/  $/, "<br>\n", st)
+	gsub(/(  |[[:space:]]+\\)\n/, "<br>\n", st)
+	gsub(/(  |[[:space:]]+\\)$/, "<br>\n", st)
+	while (match(st, /(__?|\*\*?|~~|`+|[&><\\])/)) {
+		a = substr(st, 1, RSTART - 1)
+		mp = substr(st, RSTART, RLENGTH)
+		ms = substr(st, RSTART - 1, 1)
+		me = substr(st, RSTART + RLENGTH, 1)
+		p = RSTART + RLENGTH
+		if (! classic_underscore && match(mp, /_+/)) {
+			if (match(ms, /[[:alnum:]]/) && match(me, /[[:alnum:]]/)) {
+				tg = substr(st, 1, index(st, mp))
+				r = r tg
+				st = substr(st, index(st, mp) + 1)
+				continue
+			}
+		}
+		st = substr(st, p)
+		r = r a
+		ms = ""
+		if (mp == "\\") {
+			if (match(st, /^!?\[/)) {
+				r = r "\\" substr(st, RSTART, RLENGTH)
+				st = substr(st, 2)
+			} else if (match(st, /^(\*\*|__|~~|`+)/)) {
+				r = r substr(st, 1, RLENGTH)
+				st = substr(st, RLENGTH + 1)
+			} else {
+				r = r substr(st, 1, 1)
+				st = substr(st, 2)
+			}
+			continue
+		} else if (mp == "_" || mp == "*") {
+			if (match(me, /[[:space:]]/)) {
+				r = r mp
+				continue
+			}
+			p = index(st, mp)
+			while (p && match(substr(st, p - 1, 1), /[\\[:space:]]/)) {
+				ms = ms substr(st, 1, p - 1) mp
+				st = substr(st, p + length(mp))
+				p = index(st, mp)
+			}
+			if (! p) {
+				r = r mp ms
+				continue
+			}
+			ms = ms substr(st, 1, p - 1)
+			r = r itag("em", scrub(ms))
+			st = substr(st, p + length(mp))
+		} else if (mp == "__" || mp == "**") {
+			if (match(me, /[[:space:]]/)) {
+				r = r mp
+				continue
+			}
+			p = index(st, mp)
+			while (p && match(substr(st, p - 1, 1), /[\\[:space:]]/)) {
+				ms = ms substr(st, 1, p - 1) mp
+				st = substr(st, p + length(mp))
+				p = index(st, mp)
+			}
+			if (! p) {
+				r = r mp ms
+				continue
+			}
+			ms = ms substr(st, 1, p - 1)
+			r = r itag("strong", scrub(ms))
+			st = substr(st, p + length(mp))
+		} else if (mp == "~~") {
+			p = index(st, mp)
+			if (! p) {
+				r = r mp
+				continue
+			}
+			while (p && substr(st, p - 1, 1) == "\\") {
+				ms = ms substr(st, 1, p - 1) mp
+				st = substr(st, p + length(mp))
+				p = index(st, mp)
+			}
+			ms = ms substr(st, 1, p - 1)
+			r = r itag("del", scrub(ms))
+			st = substr(st, p + length(mp))
+		} else if (match(mp, /`+/)) {
+			p = index(st, mp)
+			if (! p) {
+				r = r mp
+				continue
+			}
+			ms = substr(st, 1, p - 1)
+			r = r itag("code", escape(ms))
+			st = substr(st, p + length(mp))
+		} else if (mp == ">") {
+			r = r "&gt;"
+		} else if (mp == "<") {
+			p = index(st, ">")
+			if (! p) {
+				r = r "&lt;"
+				continue
+			}
+			tg = substr(st, 1, p - 1)
+			if (match(tg, /^[[:alpha:]]+[[:space:]]/)) {
+				a = trim(substr(tg, RSTART + RLENGTH - 1))
+				tg = substr(tg, 1, RLENGTH - 1)
+			} else {
+				a = ""
+			}
+			if (match(tolower(tg), "^/?(a|abbr|div|span|blockquote|pre|img|code|p|em|strong|sup|sub|del|ins|s|u|b|i|br|hr|ul|ol|li|table|thead|tfoot|tbody|tr|th|td|caption|column|col|colgroup|figure|figcaption|dl|dd|dt|mark|cite|q|var|samp|small|details|summary)$")) {
+				if (! match(tg, /\//)) {
+					if (match(a, /class="/)) {
+						sub(/class="/, "class=\"dawk-ex ", a)
+					} else if (a) {
+						a = a " class=\"dawk-ex\""
+					} else {
+						a = "class=\"dawk-ex\""
+					}
+					r = r "<" tg " " a ">"
+				} else {
+					r = r "<" tg ">"
+				}
+			} else if (match(tg, "^[[:alpha:]]+://[[:graph:]]+$")) {
+				if (! a) {
+					a = tg
+				}
+				r = r "<a class=\"normal\" href=\"" tg "\">" a "</a>"
+			} else if (match(tg, "^[[:graph:]]+@[[:graph:]]+$")) {
+				if (! a) {
+					a = tg
+				}
+				r = r "<a class=\"normal\" href=\"" obfuscate("mailto:" tg) "\">" obfuscate(a) "</a>"
+			} else {
+				r = r "&lt;"
+				continue
+			}
+			st = substr(st, p + 1)
+		} else if (mp == "&") {
+			if (match(st, /^[#[:alnum:]]+;/)) {
+				r = r "&" substr(st, 1, RLENGTH)
+				st = substr(st, RLENGTH + 1)
+			} else {
+				r = r "&amp;"
+			}
+		}
+	}
+	return (r st)
+}
+
+function strip_tags(st)
+{
+	gsub(/<\/?[^>]+>/, "", st)
+	return st
+}
+
+function tag(t, body, attr)
+{
+	if (attr) {
+		attr = " " trim(attr)
+		# https://www.w3.org/TR/html5/grouping-content.html#the-p-element
+	}
+	if (t == "p" && (match(body, /<\/?(div|table|blockquote|dl|ol|ul|h[[:digit:]]|hr|pre)[>[:space:]]/)) || (match(body, /!\[toc\]/) && substr(body, RSTART - 1, 1) != "\\")) {
+		return ("<" t attr ">" body "\n")
+	} else {
+		return ("<" t attr ">" body "</" t ">\n")
+	}
+}
+
+function trim(st)
+{
+	sub(/^[[:space:]]+/, "", st)
+	sub(/[[:space:]]+$/, "", st)
+	return st
+}
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..eb059d9
--- /dev/null
+++ b/style.css
@@ -0,0 +1,31 @@
+body {
+	margin: auto;
+	font: 18px/1.4 serif;
+	max-width: 70ch;
+	background-color: papayawhip;
+	color: navy;
+}
+code {
+	background-color: #ffe;
+	color: black;
+}
+pre > code {
+	display: block;
+}
+img {
+	max-width: 100%;
+}
+a:link {
+	text-decoration: none;
+	background: #eef;
+	color: inherit;
+}
+a:visted {
+	color: inherit;
+}
+a:active {
+	border-bottom: 1px solid;
+}
+a:hover {
+	border-bottom: 1px solid;
+}
-- 
cgit 1.4.1-21-gabe81