/* SPDX-FileCopyrightText: 2026 Project Tick
 * SPDX-FileContributor: Project Tick
 * SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-MeshMC-MMCO-Module-Exception-1.0
 *
 *   MeshMC - A Custom Launcher for Minecraft
 *   Copyright (C) 2026 Project Tick
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version, with the additional permission
 *   described in the MeshMC MMCO Module Exception 1.0.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 *   You should have received a copy of the MeshMC MMCO Module Exception 1.0
 *   along with this program.  If not, see <https://projecttick.org/licenses/>.
 */

#include "hoedown/html.h"

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>

#include "hoedown/escape.h"

#define USE_XHTML(opt) (opt->flags & HOEDOWN_HTML_USE_XHTML)

hoedown_html_tag hoedown_html_is_tag(const uint8_t* data, size_t size,
									 const char* tagname)
{
	size_t i;
	int closed = 0;

	if (size < 3 || data[0] != '<')
		return HOEDOWN_HTML_TAG_NONE;

	i = 1;

	if (data[i] == '/') {
		closed = 1;
		i++;
	}

	for (; i < size; ++i, ++tagname) {
		if (*tagname == 0)
			break;

		if (data[i] != *tagname)
			return HOEDOWN_HTML_TAG_NONE;
	}

	if (i == size)
		return HOEDOWN_HTML_TAG_NONE;

	if (isspace(data[i]) || data[i] == '>')
		return closed ? HOEDOWN_HTML_TAG_CLOSE : HOEDOWN_HTML_TAG_OPEN;

	return HOEDOWN_HTML_TAG_NONE;
}

static void escape_html(hoedown_buffer* ob, const uint8_t* source,
						size_t length)
{
	hoedown_escape_html(ob, source, length, 0);
}

static void escape_href(hoedown_buffer* ob, const uint8_t* source,
						size_t length)
{
	hoedown_escape_href(ob, source, length);
}

/********************
 * GENERIC RENDERER *
 ********************/
static int rndr_autolink(hoedown_buffer* ob, const hoedown_buffer* link,
						 hoedown_autolink_type type,
						 const hoedown_renderer_data* data)
{
	hoedown_html_renderer_state* state = data->opaque;

	if (!link || !link->size)
		return 0;

	HOEDOWN_BUFPUTSL(ob, "<a href=\"");
	if (type == HOEDOWN_AUTOLINK_EMAIL)
		HOEDOWN_BUFPUTSL(ob, "mailto:");
	escape_href(ob, link->data, link->size);

	if (state->link_attributes) {
		hoedown_buffer_putc(ob, '\"');
		state->link_attributes(ob, link, data);
		hoedown_buffer_putc(ob, '>');
	} else {
		HOEDOWN_BUFPUTSL(ob, "\">");
	}

	/*
	 * Pretty printing: if we get an email address as
	 * an actual URI, e.g. `mailto:foo@bar.com`, we don't
	 * want to print the `mailto:` prefix
	 */
	if (hoedown_buffer_prefix(link, "mailto:") == 0) {
		escape_html(ob, link->data + 7, link->size - 7);
	} else {
		escape_html(ob, link->data, link->size);
	}

	HOEDOWN_BUFPUTSL(ob, "</a>");

	return 1;
}

static void rndr_blockcode(hoedown_buffer* ob, const hoedown_buffer* text,
						   const hoedown_buffer* lang,
						   const hoedown_renderer_data* data)
{
	if (ob->size)
		hoedown_buffer_putc(ob, '\n');

	if (lang) {
		HOEDOWN_BUFPUTSL(ob, "<pre><code class=\"language-");
		escape_html(ob, lang->data, lang->size);
		HOEDOWN_BUFPUTSL(ob, "\">");
	} else {
		HOEDOWN_BUFPUTSL(ob, "<pre><code>");
	}

	if (text)
		escape_html(ob, text->data, text->size);

	HOEDOWN_BUFPUTSL(ob, "</code></pre>\n");
}

static void rndr_blockquote(hoedown_buffer* ob, const hoedown_buffer* content,
							const hoedown_renderer_data* data)
{
	if (ob->size)
		hoedown_buffer_putc(ob, '\n');
	HOEDOWN_BUFPUTSL(ob, "<blockquote>\n");
	if (content)
		hoedown_buffer_put(ob, content->data, content->size);
	HOEDOWN_BUFPUTSL(ob, "</blockquote>\n");
}

static int rndr_codespan(hoedown_buffer* ob, const hoedown_buffer* text,
						 const hoedown_renderer_data* data)
{
	HOEDOWN_BUFPUTSL(ob, "<code>");
	if (text)
		escape_html(ob, text->data, text->size);
	HOEDOWN_BUFPUTSL(ob, "</code>");
	return 1;
}

static int rndr_strikethrough(hoedown_buffer* ob, const hoedown_buffer* content,
							  const hoedown_renderer_data* data)
{
	if (!content || !content->size)
		return 0;

	HOEDOWN_BUFPUTSL(ob, "<del>");
	hoedown_buffer_put(ob, content->data, content->size);
	HOEDOWN_BUFPUTSL(ob, "</del>");
	return 1;
}

static int rndr_double_emphasis(hoedown_buffer* ob,
								const hoedown_buffer* content,
								const hoedown_renderer_data* data)
{
	if (!content || !content->size)
		return 0;

	HOEDOWN_BUFPUTSL(ob, "<strong>");
	hoedown_buffer_put(ob, content->data, content->size);
	HOEDOWN_BUFPUTSL(ob, "</strong>");

	return 1;
}

static int rndr_emphasis(hoedown_buffer* ob, const hoedown_buffer* content,
						 const hoedown_renderer_data* data)
{
	if (!content || !content->size)
		return 0;
	HOEDOWN_BUFPUTSL(ob, "<em>");
	if (content)
		hoedown_buffer_put(ob, content->data, content->size);
	HOEDOWN_BUFPUTSL(ob, "</em>");
	return 1;
}

static int rndr_underline(hoedown_buffer* ob, const hoedown_buffer* content,
						  const hoedown_renderer_data* data)
{
	if (!content || !content->size)
		return 0;

	HOEDOWN_BUFPUTSL(ob, "<u>");
	hoedown_buffer_put(ob, content->data, content->size);
	HOEDOWN_BUFPUTSL(ob, "</u>");

	return 1;
}

static int rndr_highlight(hoedown_buffer* ob, const hoedown_buffer* content,
						  const hoedown_renderer_data* data)
{
	if (!content || !content->size)
		return 0;

	HOEDOWN_BUFPUTSL(ob, "<mark>");
	hoedown_buffer_put(ob, content->data, content->size);
	HOEDOWN_BUFPUTSL(ob, "</mark>");

	return 1;
}

static int rndr_quote(hoedown_buffer* ob, const hoedown_buffer* content,
					  const hoedown_renderer_data* data)
{
	if (!content || !content->size)
		return 0;

	HOEDOWN_BUFPUTSL(ob, "<q>");
	hoedown_buffer_put(ob, content->data, content->size);
	HOEDOWN_BUFPUTSL(ob, "</q>");

	return 1;
}

static int rndr_linebreak(hoedown_buffer* ob, const hoedown_renderer_data* data)
{
	hoedown_html_renderer_state* state = data->opaque;
	hoedown_buffer_puts(ob, USE_XHTML(state) ? "<br/>\n" : "<br>\n");
	return 1;
}

static void rndr_header(hoedown_buffer* ob, const hoedown_buffer* content,
						int level, const hoedown_renderer_data* data)
{
	hoedown_html_renderer_state* state = data->opaque;

	if (ob->size)
		hoedown_buffer_putc(ob, '\n');

	if (level <= state->toc_data.nesting_level)
		hoedown_buffer_printf(ob, "<h%d id=\"toc_%d\">", level,
							  state->toc_data.header_count++);
	else
		hoedown_buffer_printf(ob, "<h%d>", level);

	if (content)
		hoedown_buffer_put(ob, content->data, content->size);
	hoedown_buffer_printf(ob, "</h%d>\n", level);
}

static int rndr_link(hoedown_buffer* ob, const hoedown_buffer* content,
					 const hoedown_buffer* link, const hoedown_buffer* title,
					 const hoedown_renderer_data* data)
{
	hoedown_html_renderer_state* state = data->opaque;

	HOEDOWN_BUFPUTSL(ob, "<a href=\"");

	if (link && link->size)
		escape_href(ob, link->data, link->size);

	if (title && title->size) {
		HOEDOWN_BUFPUTSL(ob, "\" title=\"");
		escape_html(ob, title->data, title->size);
	}

	if (state->link_attributes) {
		hoedown_buffer_putc(ob, '\"');
		state->link_attributes(ob, link, data);
		hoedown_buffer_putc(ob, '>');
	} else {
		HOEDOWN_BUFPUTSL(ob, "\">");
	}

	if (content && content->size)
		hoedown_buffer_put(ob, content->data, content->size);
	HOEDOWN_BUFPUTSL(ob, "</a>");
	return 1;
}

static void rndr_list(hoedown_buffer* ob, const hoedown_buffer* content,
					  hoedown_list_flags flags,
					  const hoedown_renderer_data* data)
{
	if (ob->size)
		hoedown_buffer_putc(ob, '\n');
	hoedown_buffer_put(
		ob,
		(const uint8_t*)(flags & HOEDOWN_LIST_ORDERED ? "<ol>\n" : "<ul>\n"),
		5);
	if (content)
		hoedown_buffer_put(ob, content->data, content->size);
	hoedown_buffer_put(
		ob,
		(const uint8_t*)(flags & HOEDOWN_LIST_ORDERED ? "</ol>\n" : "</ul>\n"),
		6);
}

static void rndr_listitem(hoedown_buffer* ob, const hoedown_buffer* content,
						  hoedown_list_flags flags,
						  const hoedown_renderer_data* data)
{
	HOEDOWN_BUFPUTSL(ob, "<li>");
	if (content) {
		size_t size = content->size;
		while (size && content->data[size - 1] == '\n')
			size--;

		hoedown_buffer_put(ob, content->data, size);
	}
	HOEDOWN_BUFPUTSL(ob, "</li>\n");
}

static void rndr_paragraph(hoedown_buffer* ob, const hoedown_buffer* content,
						   const hoedown_renderer_data* data)
{
	hoedown_html_renderer_state* state = data->opaque;
	size_t i = 0;

	if (ob->size)
		hoedown_buffer_putc(ob, '\n');

	if (!content || !content->size)
		return;

	while (i < content->size && isspace(content->data[i]))
		i++;

	if (i == content->size)
		return;

	HOEDOWN_BUFPUTSL(ob, "<p>");
	if (state->flags & HOEDOWN_HTML_HARD_WRAP) {
		size_t org;
		while (i < content->size) {
			org = i;
			while (i < content->size && content->data[i] != '\n')
				i++;

			if (i > org)
				hoedown_buffer_put(ob, content->data + org, i - org);

			/*
			 * do not insert a line break if this newline
			 * is the last character on the paragraph
			 */
			if (i >= content->size - 1)
				break;

			rndr_linebreak(ob, data);
			i++;
		}
	} else {
		hoedown_buffer_put(ob, content->data + i, content->size - i);
	}
	HOEDOWN_BUFPUTSL(ob, "</p>\n");
}

static void rndr_raw_block(hoedown_buffer* ob, const hoedown_buffer* text,
						   const hoedown_renderer_data* data)
{
	size_t org, sz;

	if (!text)
		return;

	/* FIXME: Do we *really* need to trim the HTML? How does that make a
	 * difference? */
	sz = text->size;
	while (sz > 0 && text->data[sz - 1] == '\n')
		sz--;

	org = 0;
	while (org < sz && text->data[org] == '\n')
		org++;

	if (org >= sz)
		return;

	if (ob->size)
		hoedown_buffer_putc(ob, '\n');

	hoedown_buffer_put(ob, text->data + org, sz - org);
	hoedown_buffer_putc(ob, '\n');
}

static int rndr_triple_emphasis(hoedown_buffer* ob,
								const hoedown_buffer* content,
								const hoedown_renderer_data* data)
{
	if (!content || !content->size)
		return 0;
	HOEDOWN_BUFPUTSL(ob, "<strong><em>");
	hoedown_buffer_put(ob, content->data, content->size);
	HOEDOWN_BUFPUTSL(ob, "</em></strong>");
	return 1;
}

static void rndr_hrule(hoedown_buffer* ob, const hoedown_renderer_data* data)
{
	hoedown_html_renderer_state* state = data->opaque;
	if (ob->size)
		hoedown_buffer_putc(ob, '\n');
	hoedown_buffer_puts(ob, USE_XHTML(state) ? "<hr/>\n" : "<hr>\n");
}

static int rndr_image(hoedown_buffer* ob, const hoedown_buffer* link,
					  const hoedown_buffer* title, const hoedown_buffer* alt,
					  const hoedown_renderer_data* data)
{
	hoedown_html_renderer_state* state = data->opaque;
	if (!link || !link->size)
		return 0;

	HOEDOWN_BUFPUTSL(ob, "<img src=\"");
	escape_href(ob, link->data, link->size);
	HOEDOWN_BUFPUTSL(ob, "\" alt=\"");

	if (alt && alt->size)
		escape_html(ob, alt->data, alt->size);

	if (title && title->size) {
		HOEDOWN_BUFPUTSL(ob, "\" title=\"");
		escape_html(ob, title->data, title->size);
	}

	hoedown_buffer_puts(ob, USE_XHTML(state) ? "\"/>" : "\">");
	return 1;
}

static int rndr_raw_html(hoedown_buffer* ob, const hoedown_buffer* text,
						 const hoedown_renderer_data* data)
{
	hoedown_html_renderer_state* state = data->opaque;

	/* ESCAPE overrides SKIP_HTML. It doesn't look to see if
	 * there are any valid tags, just escapes all of them. */
	if ((state->flags & HOEDOWN_HTML_ESCAPE) != 0) {
		escape_html(ob, text->data, text->size);
		return 1;
	}

	if ((state->flags & HOEDOWN_HTML_SKIP_HTML) != 0)
		return 1;

	hoedown_buffer_put(ob, text->data, text->size);
	return 1;
}

static void rndr_table(hoedown_buffer* ob, const hoedown_buffer* content,
					   const hoedown_renderer_data* data)
{
	if (ob->size)
		hoedown_buffer_putc(ob, '\n');
	HOEDOWN_BUFPUTSL(ob, "<table>\n");
	hoedown_buffer_put(ob, content->data, content->size);
	HOEDOWN_BUFPUTSL(ob, "</table>\n");
}

static void rndr_table_header(hoedown_buffer* ob, const hoedown_buffer* content,
							  const hoedown_renderer_data* data)
{
	if (ob->size)
		hoedown_buffer_putc(ob, '\n');
	HOEDOWN_BUFPUTSL(ob, "<thead>\n");
	hoedown_buffer_put(ob, content->data, content->size);
	HOEDOWN_BUFPUTSL(ob, "</thead>\n");
}

static void rndr_table_body(hoedown_buffer* ob, const hoedown_buffer* content,
							const hoedown_renderer_data* data)
{
	if (ob->size)
		hoedown_buffer_putc(ob, '\n');
	HOEDOWN_BUFPUTSL(ob, "<tbody>\n");
	hoedown_buffer_put(ob, content->data, content->size);
	HOEDOWN_BUFPUTSL(ob, "</tbody>\n");
}

static void rndr_tablerow(hoedown_buffer* ob, const hoedown_buffer* content,
						  const hoedown_renderer_data* data)
{
	HOEDOWN_BUFPUTSL(ob, "<tr>\n");
	if (content)
		hoedown_buffer_put(ob, content->data, content->size);
	HOEDOWN_BUFPUTSL(ob, "</tr>\n");
}

static void rndr_tablecell(hoedown_buffer* ob, const hoedown_buffer* content,
						   hoedown_table_flags flags,
						   const hoedown_renderer_data* data)
{
	if (flags & HOEDOWN_TABLE_HEADER) {
		HOEDOWN_BUFPUTSL(ob, "<th");
	} else {
		HOEDOWN_BUFPUTSL(ob, "<td");
	}

	switch (flags & HOEDOWN_TABLE_ALIGNMASK) {
		case HOEDOWN_TABLE_ALIGN_CENTER:
			HOEDOWN_BUFPUTSL(ob, " style=\"text-align: center\">");
			break;

		case HOEDOWN_TABLE_ALIGN_LEFT:
			HOEDOWN_BUFPUTSL(ob, " style=\"text-align: left\">");
			break;

		case HOEDOWN_TABLE_ALIGN_RIGHT:
			HOEDOWN_BUFPUTSL(ob, " style=\"text-align: right\">");
			break;

		default:
			HOEDOWN_BUFPUTSL(ob, ">");
	}

	if (content)
		hoedown_buffer_put(ob, content->data, content->size);

	if (flags & HOEDOWN_TABLE_HEADER) {
		HOEDOWN_BUFPUTSL(ob, "</th>\n");
	} else {
		HOEDOWN_BUFPUTSL(ob, "</td>\n");
	}
}

static int rndr_superscript(hoedown_buffer* ob, const hoedown_buffer* content,
							const hoedown_renderer_data* data)
{
	if (!content || !content->size)
		return 0;
	HOEDOWN_BUFPUTSL(ob, "<sup>");
	hoedown_buffer_put(ob, content->data, content->size);
	HOEDOWN_BUFPUTSL(ob, "</sup>");
	return 1;
}

static void rndr_normal_text(hoedown_buffer* ob, const hoedown_buffer* content,
							 const hoedown_renderer_data* data)
{
	if (content)
		escape_html(ob, content->data, content->size);
}

static void rndr_footnotes(hoedown_buffer* ob, const hoedown_buffer* content,
						   const hoedown_renderer_data* data)
{
	hoedown_html_renderer_state* state = data->opaque;

	if (ob->size)
		hoedown_buffer_putc(ob, '\n');
	HOEDOWN_BUFPUTSL(ob, "<div class=\"footnotes\">\n");
	hoedown_buffer_puts(ob, USE_XHTML(state) ? "<hr/>\n" : "<hr>\n");
	HOEDOWN_BUFPUTSL(ob, "<ol>\n");

	if (content)
		hoedown_buffer_put(ob, content->data, content->size);

	HOEDOWN_BUFPUTSL(ob, "\n</ol>\n</div>\n");
}

static void rndr_footnote_def(hoedown_buffer* ob, const hoedown_buffer* content,
							  unsigned int num,
							  const hoedown_renderer_data* data)
{
	size_t i = 0;
	int pfound = 0;

	/* insert anchor at the end of first paragraph block */
	if (content) {
		while ((i + 3) < content->size) {
			if (content->data[i++] != '<')
				continue;
			if (content->data[i++] != '/')
				continue;
			if (content->data[i++] != 'p' && content->data[i] != 'P')
				continue;
			if (content->data[i] != '>')
				continue;
			i -= 3;
			pfound = 1;
			break;
		}
	}

	hoedown_buffer_printf(ob, "\n<li id=\"fn%d\">\n", num);
	if (pfound) {
		hoedown_buffer_put(ob, content->data, i);
		hoedown_buffer_printf(
			ob, "&nbsp;<a href=\"#fnref%d\" rev=\"footnote\">&#8617;</a>", num);
		hoedown_buffer_put(ob, content->data + i, content->size - i);
	} else if (content) {
		hoedown_buffer_put(ob, content->data, content->size);
	}
	HOEDOWN_BUFPUTSL(ob, "</li>\n");
}

static int rndr_footnote_ref(hoedown_buffer* ob, unsigned int num,
							 const hoedown_renderer_data* data)
{
	hoedown_buffer_printf(
		ob,
		"<sup id=\"fnref%d\"><a href=\"#fn%d\" rel=\"footnote\">%d</a></sup>",
		num, num, num);
	return 1;
}

static int rndr_math(hoedown_buffer* ob, const hoedown_buffer* text,
					 int displaymode, const hoedown_renderer_data* data)
{
	hoedown_buffer_put(ob, (const uint8_t*)(displaymode ? "\\[" : "\\("), 2);
	escape_html(ob, text->data, text->size);
	hoedown_buffer_put(ob, (const uint8_t*)(displaymode ? "\\]" : "\\)"), 2);
	return 1;
}

static void toc_header(hoedown_buffer* ob, const hoedown_buffer* content,
					   int level, const hoedown_renderer_data* data)
{
	hoedown_html_renderer_state* state = data->opaque;

	if (level <= state->toc_data.nesting_level) {
		/* set the level offset if this is the first header
		 * we're parsing for the document */
		if (state->toc_data.current_level == 0)
			state->toc_data.level_offset = level - 1;

		level -= state->toc_data.level_offset;

		if (level > state->toc_data.current_level) {
			while (level > state->toc_data.current_level) {
				HOEDOWN_BUFPUTSL(ob, "<ul>\n<li>\n");
				state->toc_data.current_level++;
			}
		} else if (level < state->toc_data.current_level) {
			HOEDOWN_BUFPUTSL(ob, "</li>\n");
			while (level < state->toc_data.current_level) {
				HOEDOWN_BUFPUTSL(ob, "</ul>\n</li>\n");
				state->toc_data.current_level--;
			}
			HOEDOWN_BUFPUTSL(ob, "<li>\n");
		} else {
			HOEDOWN_BUFPUTSL(ob, "</li>\n<li>\n");
		}

		hoedown_buffer_printf(ob, "<a href=\"#toc_%d\">",
							  state->toc_data.header_count++);
		if (content)
			hoedown_buffer_put(ob, content->data, content->size);
		HOEDOWN_BUFPUTSL(ob, "</a>\n");
	}
}

static int toc_link(hoedown_buffer* ob, const hoedown_buffer* content,
					const hoedown_buffer* link, const hoedown_buffer* title,
					const hoedown_renderer_data* data)
{
	if (content && content->size)
		hoedown_buffer_put(ob, content->data, content->size);
	return 1;
}

static void toc_finalize(hoedown_buffer* ob, int inline_render,
						 const hoedown_renderer_data* data)
{
	hoedown_html_renderer_state* state;

	if (inline_render)
		return;

	state = data->opaque;

	while (state->toc_data.current_level > 0) {
		HOEDOWN_BUFPUTSL(ob, "</li>\n</ul>\n");
		state->toc_data.current_level--;
	}

	state->toc_data.header_count = 0;
}

hoedown_renderer* hoedown_html_toc_renderer_new(int nesting_level)
{
	static const hoedown_renderer cb_default = {NULL,

												NULL,
												NULL,
												toc_header,
												NULL,
												NULL,
												NULL,
												NULL,
												NULL,
												NULL,
												NULL,
												NULL,
												NULL,
												NULL,
												NULL,
												NULL,

												NULL,
												rndr_codespan,
												rndr_double_emphasis,
												rndr_emphasis,
												rndr_underline,
												rndr_highlight,
												rndr_quote,
												NULL,
												NULL,
												toc_link,
												rndr_triple_emphasis,
												rndr_strikethrough,
												rndr_superscript,
												NULL,
												NULL,
												NULL,

												NULL,
												rndr_normal_text,

												NULL,
												toc_finalize};

	hoedown_html_renderer_state* state;
	hoedown_renderer* renderer;

	/* Prepare the state pointer */
	state = hoedown_malloc(sizeof(hoedown_html_renderer_state));
	memset(state, 0x0, sizeof(hoedown_html_renderer_state));

	state->toc_data.nesting_level = nesting_level;

	/* Prepare the renderer */
	renderer = hoedown_malloc(sizeof(hoedown_renderer));
	memcpy(renderer, &cb_default, sizeof(hoedown_renderer));

	renderer->opaque = state;
	return renderer;
}

hoedown_renderer* hoedown_html_renderer_new(hoedown_html_flags render_flags,
											int nesting_level)
{
	static const hoedown_renderer cb_default = {NULL,

												rndr_blockcode,
												rndr_blockquote,
												rndr_header,
												rndr_hrule,
												rndr_list,
												rndr_listitem,
												rndr_paragraph,
												rndr_table,
												rndr_table_header,
												rndr_table_body,
												rndr_tablerow,
												rndr_tablecell,
												rndr_footnotes,
												rndr_footnote_def,
												rndr_raw_block,

												rndr_autolink,
												rndr_codespan,
												rndr_double_emphasis,
												rndr_emphasis,
												rndr_underline,
												rndr_highlight,
												rndr_quote,
												rndr_image,
												rndr_linebreak,
												rndr_link,
												rndr_triple_emphasis,
												rndr_strikethrough,
												rndr_superscript,
												rndr_footnote_ref,
												rndr_math,
												rndr_raw_html,

												NULL,
												rndr_normal_text,

												NULL,
												NULL};

	hoedown_html_renderer_state* state;
	hoedown_renderer* renderer;

	/* Prepare the state pointer */
	state = hoedown_malloc(sizeof(hoedown_html_renderer_state));
	memset(state, 0x0, sizeof(hoedown_html_renderer_state));

	state->flags = render_flags;
	state->toc_data.nesting_level = nesting_level;

	/* Prepare the renderer */
	renderer = hoedown_malloc(sizeof(hoedown_renderer));
	memcpy(renderer, &cb_default, sizeof(hoedown_renderer));

	if (render_flags & HOEDOWN_HTML_SKIP_HTML ||
		render_flags & HOEDOWN_HTML_ESCAPE)
		renderer->blockhtml = NULL;

	renderer->opaque = state;
	return renderer;
}

void hoedown_html_renderer_free(hoedown_renderer* renderer)
{
	free(renderer->opaque);
	free(renderer);
}
