/*
 * Copyright (c) Kristaps Dzonsons <kristaps@bsd.lv>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include "config.h"

#if HAVE_SYS_QUEUE
# include <sys/queue.h>
#endif

#include <assert.h>
#include <ctype.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "lowdown.h"
#include "extern.h"

struct latex {
	unsigned int	 oflags; /* same as in lowdown_opts */
	struct hentryq	 headers_used; /* headers we've seen */
	ssize_t		 headers_offs; /* header offset */
	size_t		 footsz; /* current footnote */
	const char	*templ; /* output template */
};

/*
 * Like lowdown_latex_esc() but with a NUL-terminated string.
 */
static int
rndr_escape_string(const struct latex *st, struct lowdown_buf *ob,
    const char *data)
{

	return lowdown_latex_esc(ob, data, strlen(data));
}

/*
 * Like lowdown_latex_esc() but with a buffer.
 */
static int
rndr_escape(const struct latex *st, struct lowdown_buf *ob,
    const struct lowdown_buf *dat)
{
	
	return lowdown_latex_esc(ob, dat->data, dat->size);
}

/*
 * Escape a URL with a sized buffer.  Return FALSE on error (memory),
 * TRUE on success. XXX: this is the same function found in nroff.c.
 */
static int
rndr_url_buf(struct lowdown_buf *ob, const char *data, size_t sz,
    const enum halink_type *type)
{
	size_t	 	 i = 0;
	unsigned char	 ch;

	if (type != NULL && *type == HALINK_EMAIL && sz > 7 &&
	    strncmp(data, "mailto:", 7) == 0)
		i = 7;

	for ( ; i < sz; i++) {
		ch = (unsigned char)data[i];
		if (!isprint(ch) || strchr(" <>\\^`{|}\"", ch) != NULL) {
			if (!hbuf_printf(ob, "\\%%%.2X", ch))
				return 0;
		} else if (!hbuf_putc(ob, ch))
			return 0;
	}

	return 1;
}

/*
 * Escape a URL.  Return FALSE on error (memory), TRUE on success.
 */
static int
rndr_url(struct lowdown_buf *ob, const struct lowdown_buf *link,
    const enum halink_type *type)
{

	return rndr_url_buf(ob, link->data, link->size, type);
}

static int
rndr_autolink(const struct latex *st, struct lowdown_buf *ob,
    const struct rndr_autolink *param)
{

	if (param->link.size == 0)
		return 1;
	if (!HBUF_PUTSL(ob, "\\url{"))
		return 0;
	if (!rndr_url(ob, &param->link, &param->type))
		return 0;
	return HBUF_PUTSL(ob, "}");
}

static int
rndr_entity(const struct latex *st, struct lowdown_buf *ob,
    const struct rndr_entity *param)
{
	const char	*tex;
	unsigned char	 texflags;

	tex = entity_find_tex(&param->text, &texflags);
	if (tex == NULL)
		return rndr_escape(st, ob, &param->text);

	if ((texflags & TEX_ENT_MATH) && (texflags & TEX_ENT_ASCII))
		return hbuf_printf(ob, "$\\mathrm{%s}$", tex);
	if (texflags & TEX_ENT_ASCII)
		return hbuf_puts(ob, tex);
	if (texflags & TEX_ENT_MATH)
		return hbuf_printf(ob, "$\\mathrm{\\%s}$", tex);
	return hbuf_printf(ob, "\\%s", tex);
}

static int
rndr_blockcode(struct lowdown_buf *ob,
    const struct rndr_blockcode *param)
{

	if (ob->size && !HBUF_PUTSL(ob, "\n"))
		return 0;

#if 0
	HBUF_PUTSL(ob, "\\begin{lstlisting}");
	if (lang->size) {
		HBUF_PUTSL(ob, "[language=");
		rndr_escape(ob, lang);
		HBUF_PUTSL(ob, "]\n\n");
	} else
		HBUF_PUTSL(ob, "\n");
#else
	HBUF_PUTSL(ob, "\\begin{verbatim}\n");
#endif
	if (!hbuf_putb(ob, &param->text))
		return 0;
#if 0
	HBUF_PUTSL(ob, "\\end{lstlisting}\n");
#else
	return HBUF_PUTSL(ob, "\\end{verbatim}\n");
#endif
}

static int
rndr_definition_title(struct lowdown_buf *ob,
    const struct lowdown_buf *content)
{

	if (!HBUF_PUTSL(ob, "\\item ["))
		return 0;
	if (!hbuf_putb(ob, content))
		return 0;
	return HBUF_PUTSL(ob, "] ");
}

static int
rndr_definition(struct lowdown_buf *ob,
    const struct lowdown_buf *content)
{

	if (!HBUF_PUTSL(ob, "\\begin{description}\n"))
		return 0;
	if (!hbuf_putb(ob, content))
		return 0;
	return HBUF_PUTSL(ob, "\\end{description}\n");
}

static int
rndr_blockquote(struct lowdown_buf *ob,
    const struct lowdown_buf *content)
{

	if (ob->size && !HBUF_PUTSL(ob, "\n"))
		return 0;
	if (!HBUF_PUTSL(ob, "\\begin{quotation}\n"))
		return 0;
	if (!hbuf_putb(ob, content))
		return 0;
	return HBUF_PUTSL(ob, "\\end{quotation}\n");
}

static int
rndr_codespan(const struct latex *st, struct lowdown_buf *ob,
    const struct rndr_codespan *param)
{
#if 0
	HBUF_PUTSL(ob, "\\lstinline{");
	hbuf_putb(ob, text);
#else
	if (!HBUF_PUTSL(ob, "\\texttt{"))
		return 0;
	if (!rndr_escape(st, ob, &param->text))
		return 0;
#endif
	return HBUF_PUTSL(ob, "}");
}

static int
rndr_triple_emphasis(struct lowdown_buf *ob,
    const struct lowdown_buf *content)
{

	if (!HBUF_PUTSL(ob, "\\textbf{\\emph{"))
		return 0;
	if (!hbuf_putb(ob, content))
		return 0;
	return HBUF_PUTSL(ob, "}}");
}

static int
rndr_double_emphasis(struct lowdown_buf *ob,
    const struct lowdown_buf *content)
{

	if (!HBUF_PUTSL(ob, "\\textbf{"))
		return 0;
	if (!hbuf_putb(ob, content))
		return 0;
	return HBUF_PUTSL(ob, "}");
}

static int
rndr_emphasis(struct lowdown_buf *ob,
    const struct lowdown_buf *content)
{

	if (!HBUF_PUTSL(ob, "\\emph{"))
		return 0;
	if (!hbuf_putb(ob, content))
		return 0;
	return HBUF_PUTSL(ob, "}");
}

static int
rndr_highlight(struct lowdown_buf *ob,
    const struct lowdown_buf *content)
{

	if (!HBUF_PUTSL(ob, "\\underline{"))
		return 0;
	if (!hbuf_putb(ob, content))
		return 0;
	return HBUF_PUTSL(ob, "}");
}

static int
rndr_linebreak(struct lowdown_buf *ob)
{

	return HBUF_PUTSL(ob, "\\linebreak\n");
}

static int
rndr_header(struct latex *st, struct lowdown_buf *ob,
    const struct lowdown_buf *content, const struct lowdown_node *n)
{
	const char			*type;
	ssize_t				 level;
	struct lowdown_buf		*buf = NULL;
	const struct lowdown_buf	*id;
	int				 rc = 0;

	if (n->rndr_header.attr_id.size) {
		if ((buf = hbuf_new(32)) == NULL)
			goto out;
		if (!rndr_escape(st, buf, &n->rndr_header.attr_id))
			goto out;
		id = buf;
	} else {
		id = hbuf_id(NULL, n, &st->headers_used);
		if (id == NULL)
			goto out;
	}

	if (ob->size && !HBUF_PUTSL(ob, "\n"))
		goto out;

	if (!HBUF_PUTSL(ob, "\\hypertarget{"))
		goto out;
	if (!hbuf_putb(ob, id))
		goto out;
	if (!HBUF_PUTSL(ob, "}{%\n"))
		goto out;

	level = (ssize_t)n->rndr_header.level + st->headers_offs;
	if (level < 1)
		level = 1;

	switch (level) {
	case 1:
		type = "\\section";
		break;
	case 2:
		type = "\\subsection";
		break;
	case 3:
		type = "\\subsubsection";
		break;
	case 4:
		type = "\\paragraph";
		break;
	default:
		type = "\\subparagraph";
		break;
	}

	if (!hbuf_puts(ob, type))
		goto out;
	if (!(st->oflags & LOWDOWN_LATEX_NUMBERED) &&
  	    !HBUF_PUTSL(ob, "*"))
		goto out;
	if (!HBUF_PUTSL(ob, "{"))
		goto out;
	if (!hbuf_putb(ob, content))
		goto out;
	if (!HBUF_PUTSL(ob, "}\\label{"))
		goto out;
	if (!hbuf_putb(ob, id))
		goto out;
	if (!HBUF_PUTSL(ob, "}}\n"))
		goto out;
	rc = 1;
out:
	hbuf_free(buf);
	return rc;
}

static int
rndr_link(const struct latex *st, struct lowdown_buf *ob,
    const struct lowdown_buf *content, const struct rndr_link *param)
{
	int	loc;

	loc = param->link.size > 0 &&
		param->link.data[0] == '#';

	if (param->attr_id.size > 0) {
		if (!HBUF_PUTSL(ob, "\\hypertarget{"))
			return 0;
		if (!hbuf_putb(ob, &param->attr_id))
			return 0;
		if (!HBUF_PUTSL(ob, "}{%\n"))
			return 0;
	}

	if (loc && !HBUF_PUTSL(ob, "\\hyperlink{"))
		return 0;
	else if (!loc && !HBUF_PUTSL(ob, "\\href{"))
		return 0;
	if (loc && !rndr_url_buf(ob, &param->link.data[1],
	    param->link.size - 1, NULL))
		return 0;
	else if (!loc && !rndr_url( ob, &param->link, NULL))
		return 0;
	if (!HBUF_PUTSL(ob, "}{"))
		return 0;
	if (!hbuf_putb(ob, content))
		return 0;
	if (param->attr_id.size > 0 && !HBUF_PUTSL(ob, "}"))
		return 0;
	return HBUF_PUTSL(ob, "}");
}

static int
rndr_list(struct lowdown_buf *ob, const struct lowdown_buf *content,
    const struct rndr_list *param)
{
	const char	*type;

	if (ob->size && !hbuf_putc(ob, '\n'))
		return 0;

	/* TODO: HLIST_FL_ORDERED and param->start */

	type = (param->flags & HLIST_FL_ORDERED) ?
		"enumerate" : "itemize";

	if (!hbuf_printf(ob, "\\begin{%s}\n", type))
		return 0;
	if (!(param->flags & HLIST_FL_BLOCK) &&
	    !HBUF_PUTSL(ob, "\\itemsep -0.2em\n"))
		return 0;
	if (!hbuf_putb(ob, content))
		return 0;
	return hbuf_printf(ob, "\\end{%s}\n", type);
}

static int
rndr_listitem(struct lowdown_buf *ob, const struct lowdown_buf *content,
    const struct rndr_listitem *param)
{
	size_t	 size;

	/* Only emit \item if we're not a definition list. */

	if (!(param->flags & HLIST_FL_DEF)) {
		if (!HBUF_PUTSL(ob, "\\item"))
			return 0;
		if ((param->flags & HLIST_FL_CHECKED) &&
		    !HBUF_PUTSL(ob, "[$\\rlap{$\\checkmark$}\\square$]"))
			return 0;
		if ((param->flags & HLIST_FL_UNCHECKED) &&
		    !HBUF_PUTSL(ob, "[$\\square$]"))
			return 0;
		if (!HBUF_PUTSL(ob, " "))
			return 0;
	}

	/* Cut off any trailing space. */

	if ((size = content->size) > 0) {
		while (size && content->data[size - 1] == '\n')
			size--;
		if (!hbuf_put(ob, content->data, size))
			return 0;
	}

	return HBUF_PUTSL(ob, "\n");
}

static int
rndr_paragraph(struct lowdown_buf *ob,
    const struct lowdown_buf *content)
{
	size_t	i = 0;

	if (content->size == 0)
		return 1;
	while (i < content->size &&
	       isspace((unsigned char)content->data[i])) 
		i++;
	if (i == content->size)
		return 1;

	if (!HBUF_PUTSL(ob, "\n"))
		return 0;
	if (!hbuf_put(ob, content->data + i, content->size - i))
		return 0;
	return HBUF_PUTSL(ob, "\n");
}

static int
rndr_raw_block(struct lowdown_buf *ob,
    const struct rndr_blockhtml *param, const struct latex *st)
{
	size_t	org = 0, sz = param->text.size;

	if (st->oflags & LOWDOWN_SKIP_HTML)
		return 1;
	while (sz > 0 && param->text.data[sz - 1] == '\n')
		sz--;
	while (org < sz && param->text.data[org] == '\n')
		org++;
	if (org >= sz)
		return 1;

	if (ob->size && !HBUF_PUTSL(ob, "\n"))
		return 0;
	if (!HBUF_PUTSL(ob, "\\begin{verbatim}\n"))
		return 0;
	if (!hbuf_put(ob, param->text.data + org, sz - org))
		return 0;
	return HBUF_PUTSL(ob, "\\end{verbatim}\n");
}

static int
rndr_hrule(struct lowdown_buf *ob)
{

	if (ob->size && !hbuf_putc(ob, '\n'))
		return 0;
	return HBUF_PUTSL(ob, "\\noindent\\hrulefill\n");
}

static int
rndr_image(const struct latex *st, struct lowdown_buf *ob,
    const struct rndr_image *param)
{
	const char	*cp;
	char		 dimbuf[32];
	unsigned int	 x, y;
	float		 pct;
	int		 rc = 0;

	/*
	 * Scan in our dimensions, if applicable.
	 * It's unreasonable for them to be over 32 characters, so use
	 * that as a cap to the size.
	 */

	if (param->dims.size && 
	    param->dims.size < sizeof(dimbuf) - 1) {
		memset(dimbuf, 0, sizeof(dimbuf));
		memcpy(dimbuf, param->dims.data, param->dims.size);
		rc = sscanf(dimbuf, "%ux%u", &x, &y);
	}

	/* Extended attributes override dimensions. */

	if (!HBUF_PUTSL(ob, "\\includegraphics["))
		return 0;
	if (param->attr_width.size || param->attr_height.size) {
		if (param->attr_width.size &&
		    param->attr_width.size < sizeof(dimbuf) - 1) {
			memset(dimbuf, 0, sizeof(dimbuf));
			memcpy(dimbuf, param->attr_width.data, 
				param->attr_width.size);

			/* Try to parse as a percentage. */

			if (sscanf(dimbuf, "%e%%", &pct) == 1) {
				if (!hbuf_printf(ob, "width=%.2f"
				     "\\linewidth", pct / 100.0))
					return 0;
			} else {
				if (!hbuf_printf(ob, "width=%.*s", 
				    (int)param->attr_width.size, 
				    param->attr_width.data))
					return 0;
			}
		}
		if (param->attr_height.size &&
		    param->attr_height.size < sizeof(dimbuf) - 1) {
			if (param->attr_width.size && 
			    !HBUF_PUTSL(ob, ", "))
				return 0;
			if (!hbuf_printf(ob, "height=%.*s", 
			    (int)param->attr_height.size, 
			    param->attr_height.data))
				return 0;
		}
	} else if (rc > 0) {
		if (!hbuf_printf(ob, "width=%upx", x))
			return 0;
		if (rc > 1 && !hbuf_printf(ob, ", height=%upx", y))
			return 0;
	}

	if (!HBUF_PUTSL(ob, "]{"))
		return 0;
	cp = memrchr(param->link.data, '.', param->link.size);
	if (cp != NULL) {
		if (!HBUF_PUTSL(ob, "{"))
			return 0;
		if (!lowdown_latex_esc
		    (ob, param->link.data, cp - param->link.data))
			return 0;
		if (!HBUF_PUTSL(ob, "}"))
			return 0;
		if (!lowdown_latex_esc
		    (ob, cp, param->link.size - (cp - param->link.data)))
			return 0;
	} else {
		if (!rndr_escape(st, ob, &param->link))
			return 0;
	}
	return HBUF_PUTSL(ob, "}");
}

static int
rndr_raw_html(const struct latex *st, struct lowdown_buf *ob,
    const struct rndr_raw_html *param)
{

	if (st->oflags & LOWDOWN_SKIP_HTML)
		return 1;
	return rndr_escape(st, ob, &param->text);
}

static int
rndr_table(struct lowdown_buf *ob, const struct lowdown_buf *content)
{

	/* Open the table in rndr_table_header. */

	if (ob->size && !hbuf_putc(ob, '\n'))
		return 0;
	if (!hbuf_putb(ob, content))
		return 0;
	return HBUF_PUTSL(ob, "\\end{longtable}\n");
}

static int
rndr_table_header(struct lowdown_buf *ob,
    const struct lowdown_buf *content, 
    const struct rndr_table_header *param)
{
	size_t	 i;
	char	 align;
	int	 fl;

	if (!HBUF_PUTSL(ob, "\\begin{longtable}[]{"))
		return 0;

	for (i = 0; i < param->columns; i++) {
		fl = param->flags[i] & HTBL_FL_ALIGNMASK;
		if (fl == HTBL_FL_ALIGN_CENTER)
			align = 'c';
		else if (fl == HTBL_FL_ALIGN_RIGHT)
			align = 'r';
		else
			align = 'l';
		if (!hbuf_putc(ob, align))
			return 0;
	}
	if (!HBUF_PUTSL(ob, "}\n"))
		return 0;
	return hbuf_putb(ob, content);
}

static int
rndr_tablecell(struct lowdown_buf *ob,
    const struct lowdown_buf *content, 
    const struct rndr_table_cell *param)
{

	if (!hbuf_putb(ob, content))
		return 0;
	return (param->col < param->columns - 1) ?
		HBUF_PUTSL(ob, " & ") :
		HBUF_PUTSL(ob, "  \\\\\n");
}

static int
rndr_superscript(struct lowdown_buf *ob,
    const struct lowdown_buf *content, enum lowdown_rndrt type)
{
	const char	*elem;

	elem = (type == LOWDOWN_SUPERSCRIPT) ? "super" : "sub";

	return hbuf_printf(ob, "\\text%sscript{", elem) &&
	    hbuf_putb(ob, content) &&
	    HBUF_PUTSL(ob, "}");
}

static int
rndr_normal_text(const struct latex *st, struct lowdown_buf *ob,
    const struct rndr_normal_text *param)
{

	return rndr_escape(st, ob, &param->text);
}

static int
rndr_footnote_ref(struct lowdown_buf *ob,
    const struct lowdown_buf *content, struct latex *st)
{

	if (!hbuf_printf(ob, "\\footnote[%zu]{", ++st->footsz))
		return 0;
	if (!hbuf_putb(ob, content))
		return 0;
	return HBUF_PUTSL(ob, "}");
}

static int
rndr_math(struct lowdown_buf *ob, const struct rndr_math *param)
{

	if (param->blockmode && !HBUF_PUTSL(ob, "\\["))
		return 0;
	else if (!param->blockmode && !HBUF_PUTSL(ob, "\\("))
		return 0;
	if (!hbuf_putb(ob, &param->text))
		return 0;
	if (param->blockmode && !HBUF_PUTSL(ob, "\\]"))
		return 0;
	else if (!param->blockmode && !HBUF_PUTSL(ob, "\\)"))
		return 0;
	return 1;
}

static int
rndr_root(const struct latex *st, struct lowdown_buf *ob,
    const struct lowdown_metaq *mq, const struct lowdown_buf *content)
{
	const struct lowdown_meta	*m;
	const char			*author = NULL, *title = NULL,
					*affil = NULL, *date = NULL,
					*rcsauthor = NULL, 
					*rcsdate = NULL, *header = NULL;

	if (!(st->oflags & LOWDOWN_STANDALONE))
		return hbuf_putb(ob, content);
	if (st->templ != NULL)
		return lowdown_template(st->templ, content, ob, mq, 0);

	TAILQ_FOREACH(m, mq, entries)
		if (strcasecmp(m->key, "author") == 0)
			author = m->value;
		else if (strcasecmp(m->key, "affiliation") == 0)
			affil = m->value;
		else if (strcasecmp(m->key, "date") == 0)
			date = m->value;
		else if (strcasecmp(m->key, "rcsauthor") == 0)
			rcsauthor = rcsauthor2str(m->value);
		else if (strcasecmp(m->key, "rcsdate") == 0)
			rcsdate = rcsdate2str(m->value);
		else if (strcasecmp(m->key, "title") == 0)
			title = m->value;
		else if (strcasecmp(m->key, "latexheader") == 0)
			header = m->value;

	/* Overrides. */

	if (rcsauthor != NULL)
		author = rcsauthor;
	if (rcsdate != NULL)
		date = rcsdate;

	/* Standard header. */

	if (!HBUF_PUTSL(ob, 
	    "% Options for packages loaded elsewhere\n"
	    "\\PassOptionsToPackage{unicode}{hyperref}\n"
	    "\\PassOptionsToPackage{hyphens}{url}\n"
	    "%\n"
	    "\\documentclass[11pt,a4paper]{article}\n"
	    "\\usepackage{amsmath,amssymb}\n"
	    "\\usepackage{lmodern}\n"
	    "\\usepackage{iftex}\n"
	    "\\ifPDFTeX\n"
	    "  \\usepackage[T1]{fontenc}\n"
	    "  \\usepackage[utf8]{inputenc}\n"
	    "  \\usepackage{textcomp} % provide euro and other symbols\n"
	    "\\else % if luatex or xetex\n"
	    "  \\usepackage{unicode-math}\n"
	    "  \\defaultfontfeatures{Scale=MatchLowercase}\n"
	    "  \\defaultfontfeatures[\\rmfamily]{Ligatures=TeX,Scale=1}\n"
	    "\\fi\n"
	    "\\usepackage{xcolor}\n"
	    "\\usepackage{graphicx}\n"
	    "\\usepackage{longtable}\n"
	    "\\usepackage{hyperref}\n"))
	    	return 0;

	/* Optional raw LaTeX header. */

	if (header != NULL) {
		if (!hbuf_puts(ob, header))
			return 0;
		if (header[strlen(header) - 1] != '\n' &&
		    !HBUF_PUTSL(ob, "\n"))
			return 0;
	}

	if (!HBUF_PUTSL(ob, "\\begin{document}\n"))
		return 0;

	/*
	 * Title, author, and date are not required.  However, if any of
	 * them are specified, we need the title even if empty.
	 */

	if (title != NULL || author != NULL || date != NULL) {
		if (!HBUF_PUTSL(ob, "\\title{"))
			return 0;
		if (title != NULL && !rndr_escape_string(st, ob, title))
			return 0;
		if (!HBUF_PUTSL(ob, "}\n"))
			return 0;
	}
	if (author != NULL) {
		if (!HBUF_PUTSL(ob, "\\author{"))
			return 0;
		if (!rndr_escape_string(st, ob, author))
			return 0;
		if (affil != NULL) {
			if (!HBUF_PUTSL(ob, " \\\\ "))
				return 0;
			if (!rndr_escape_string(st, ob, affil))
				return 0;
		}
		if (!HBUF_PUTSL(ob, "}\n"))
			return 0;
	}

	if (date != NULL) {
		if (!HBUF_PUTSL(ob, "\\date{"))
			return 0;
		if (!rndr_escape_string(st, ob, date))
			return 0;
		if (!HBUF_PUTSL(ob, "}\n"))
			return 0;
	}

	/* Only construct the title if there are elements for it. */

	if ((title != NULL || author != NULL || date != NULL) &&
	    !HBUF_PUTSL(ob, "\\maketitle\n"))
		return 0;

	if (!hbuf_putb(ob, content))
		return 0;

	return HBUF_PUTSL(ob, "\\end{document}\n");
}

static int
rndr_meta(struct latex *st, const struct lowdown_node *n, 
    struct lowdown_metaq *mq)
{
	struct lowdown_meta	*m;
	ssize_t			 val;
	const char		*ep;

	if ((m = lowdown_get_meta(n, mq)) == NULL)
		return 0;

	if (strcmp(m->key, "shiftheadinglevelby") == 0) {
		val = (ssize_t)strtonum(m->value, -100, 100, &ep);
		if (ep == NULL)
			st->headers_offs = val + 1;
	} else if (strcmp(m->key, "baseheaderlevel") == 0) {
		val = (ssize_t)strtonum(m->value, 1, 100, &ep);
		if (ep == NULL)
			st->headers_offs = val;
	}

	return 1;
}

static int
rndr(struct lowdown_buf *ob, struct lowdown_metaq *mq, void *arg, 
    const struct lowdown_node *n)
{
	struct lowdown_buf		*tmp;
	struct latex			*st = arg;
	const struct lowdown_node	*child;
	int				 ret = 0;

	if ((tmp = hbuf_new(64)) == NULL)
		return 0;

	TAILQ_FOREACH(child, &n->children, entries)
		if (!rndr(tmp, mq, st, child))
			goto out;

	/*
	 * These elements can be put in either a block or an inline
	 * context, so we're safe to just use them and forget.
	 */

	if (n->chng == LOWDOWN_CHNG_INSERT && 
	    !HBUF_PUTSL(ob, "{\\color{blue} "))
		goto out;
	if (n->chng == LOWDOWN_CHNG_DELETE &&
	    !HBUF_PUTSL(ob, "{\\color{red} "))
		goto out;

	switch (n->type) {
	case LOWDOWN_BLOCKCODE:
		if (!rndr_blockcode(ob, &n->rndr_blockcode))
			return 0;
		break;
	case LOWDOWN_DOC_HEADER:
		/* Don't output anything for this. */
		break;
	case LOWDOWN_BLOCKQUOTE:
		if (!rndr_blockquote(ob, tmp))
			return 0;
		break;
	case LOWDOWN_DEFINITION:
		if (!rndr_definition(ob, tmp))
			return 0;
		break;
	case LOWDOWN_DEFINITION_TITLE:
		if (!rndr_definition_title(ob, tmp))
			return 0;
		break;
	case LOWDOWN_META:
		if (n->chng != LOWDOWN_CHNG_DELETE &&
		    !rndr_meta(st, n, mq))
			return 0;
		break;
	case LOWDOWN_HEADER:
		if (!rndr_header(st, ob, tmp, n))
			return 0;
		break;
	case LOWDOWN_HRULE:
		if (!rndr_hrule(ob))
			return 0;
		break;
	case LOWDOWN_LIST:
		if (!rndr_list(ob, tmp, &n->rndr_list))
			return 0;
		break;
	case LOWDOWN_LISTITEM:
		if (!rndr_listitem(ob, tmp, &n->rndr_listitem))
			return 0;
		break;
	case LOWDOWN_PARAGRAPH:
		if (!rndr_paragraph(ob, tmp))
			return 0;
		break;
	case LOWDOWN_TABLE_BLOCK:
		if (!rndr_table(ob, tmp))
			return 0;
		break;
	case LOWDOWN_TABLE_HEADER:
		if (!rndr_table_header(ob, tmp, &n->rndr_table_header))
			return 0;
		break;
	case LOWDOWN_TABLE_CELL:
		if (!rndr_tablecell(ob, tmp, &n->rndr_table_cell))
			return 0;
		break;
	case LOWDOWN_BLOCKHTML:
		if (!rndr_raw_block(ob, &n->rndr_blockhtml, st))
			return 0;
		break;
	case LOWDOWN_LINK_AUTO:
		if (!rndr_autolink(st, ob, &n->rndr_autolink))
			return 0;
		break;
	case LOWDOWN_CODESPAN:
		if (!rndr_codespan(st, ob, &n->rndr_codespan))
			return 0;
		break;
	case LOWDOWN_DOUBLE_EMPHASIS:
		if (!rndr_double_emphasis(ob, tmp))
			return 0;
		break;
	case LOWDOWN_EMPHASIS:
		if (!rndr_emphasis(ob, tmp))
			return 0;
		break;
	case LOWDOWN_HIGHLIGHT:
		if (!rndr_highlight(ob, tmp))
			return 0;
		break;
	case LOWDOWN_IMAGE:
		if (!rndr_image(st, ob, &n->rndr_image))
			return 0;
		break;
	case LOWDOWN_LINEBREAK:
		if (!rndr_linebreak(ob))
			return 0;
		break;
	case LOWDOWN_LINK:
		if (!rndr_link(st, ob, tmp, &n->rndr_link))
			return 0;
		break;
	case LOWDOWN_TRIPLE_EMPHASIS:
		if (!rndr_triple_emphasis(ob, tmp))
			return 0;
		break;
	case LOWDOWN_SUBSCRIPT:
		if (!rndr_superscript(ob, tmp, n->type))
			return 0;
		break;
	case LOWDOWN_SUPERSCRIPT:
		if (!rndr_superscript(ob, tmp, n->type))
			return 0;
		break;
	case LOWDOWN_FOOTNOTE:
		if (!rndr_footnote_ref(ob, tmp, st))
			return 0;
		break;
	case LOWDOWN_MATH_BLOCK:
		if (!rndr_math(ob, &n->rndr_math))
			return 0;
		break;
	case LOWDOWN_RAW_HTML:
		if (!rndr_raw_html(st, ob, &n->rndr_raw_html))
			return 0;
		break;
	case LOWDOWN_NORMAL_TEXT:
		if (!rndr_normal_text(st, ob, &n->rndr_normal_text))
			return 0;
		break;
	case LOWDOWN_ENTITY:
		if (!rndr_entity(st, ob, &n->rndr_entity))
			return 0;
		break;
	case LOWDOWN_ROOT:
		if (!rndr_root(st, ob, mq, tmp))
			return 0;
		break;
	default:
		if (!hbuf_putb(ob, tmp))
			return 0;
		break;
	}

	if ((n->chng == LOWDOWN_CHNG_INSERT ||
	     n->chng == LOWDOWN_CHNG_DELETE) && !HBUF_PUTSL(ob, "}"))
		goto out;

	ret = 1;
out:
	hbuf_free(tmp);
	return ret;
}

int
lowdown_latex_rndr(struct lowdown_buf *ob, void *arg,
    const struct lowdown_node *n)
{
	struct latex		*st = arg;
	struct lowdown_metaq	 metaq;
	int			 rc;

	/* Reset header identifiers, metadata, and footnotes. */

	TAILQ_INIT(&st->headers_used);
	TAILQ_INIT(&metaq);
	st->headers_offs = 1;
	st->footsz = 0;

	/* Actually perform rendering. */

	rc = rndr(ob, &metaq, st, n);

	/* Clean up header identifiers and metadata. */

	lowdown_metaq_free(&metaq);
	hentryq_clear(&st->headers_used);
	return rc;
}

void *
lowdown_latex_new(const struct lowdown_opts *opts)
{
	struct latex	*p;

	if ((p = calloc(1, sizeof(struct latex))) == NULL)
		return NULL;

	p->oflags = opts == NULL ? 0 : opts->oflags;
	p->templ = opts == NULL ? NULL : opts->templ;
	return p;
}

void
lowdown_latex_free(void *arg)
{

	free(arg);
}
