diff --git a/cbits/autolink.c b/cbits/autolink.c
--- a/cbits/autolink.c
+++ b/cbits/autolink.c
@@ -22,7 +22,7 @@
 #include <ctype.h>
 
 int
-sd_autolink_issafe(const char *link, size_t link_len)
+sd_autolink_issafe(const uint8_t *link, size_t link_len)
 {
 	static const size_t valid_uris_count = 4;
 	static const char *valid_uris[] = {
@@ -35,7 +35,7 @@
 		size_t len = strlen(valid_uris[i]);
 
 		if (link_len > len &&
-			strncasecmp(link, valid_uris[i], len) == 0 &&
+			strncasecmp((char *)link, valid_uris[i], len) == 0 &&
 			isalnum(link[len]))
 			return 1;
 	}
@@ -44,9 +44,9 @@
 }
 
 static size_t
-autolink_delim(char *data, size_t link_end, size_t offset, size_t size)
+autolink_delim(uint8_t *data, size_t link_end, size_t offset, size_t size)
 {
-	char cclose, copen = 0;
+	uint8_t cclose, copen = 0;
 	size_t i;
 
 	for (i = 0; i < link_end; ++i)
@@ -128,7 +128,7 @@
 }
 
 static size_t
-check_domain(char *data, size_t size)
+check_domain(uint8_t *data, size_t size)
 {
 	size_t i, np = 0;
 
@@ -147,7 +147,7 @@
 }
 
 size_t
-sd_autolink__www(size_t *rewind_p, struct buf *link, char *data, size_t offset, size_t size)
+sd_autolink__www(size_t *rewind_p, struct buf *link, uint8_t *data, size_t offset, size_t size)
 {
 	size_t link_end;
 
@@ -177,13 +177,13 @@
 }
 
 size_t
-sd_autolink__email(size_t *rewind_p, struct buf *link, char *data, size_t offset, size_t size)
+sd_autolink__email(size_t *rewind_p, struct buf *link, uint8_t *data, size_t offset, size_t size)
 {
 	size_t link_end, rewind;
 	int nb = 0, np = 0;
 
 	for (rewind = 0; rewind < offset; ++rewind) {
-		char c = data[-rewind - 1];
+		uint8_t c = data[-rewind - 1];
 
 		if (isalnum(c))
 			continue;
@@ -198,7 +198,7 @@
 		return 0;
 
 	for (link_end = 0; link_end < size; ++link_end) {
-		char c = data[link_end];
+		uint8_t c = data[link_end];
 
 		if (isalnum(c))
 			continue;
@@ -226,7 +226,7 @@
 }
 
 size_t
-sd_autolink__url(size_t *rewind_p, struct buf *link, char *data, size_t offset, size_t size)
+sd_autolink__url(size_t *rewind_p, struct buf *link, uint8_t *data, size_t offset, size_t size)
 {
 	size_t link_end, rewind = 0, domain_len;
 
diff --git a/cbits/autolink.h b/cbits/autolink.h
--- a/cbits/autolink.h
+++ b/cbits/autolink.h
@@ -20,16 +20,16 @@
 #include "buffer.h"
 
 extern int
-sd_autolink_issafe(const char *link, size_t link_len);
+sd_autolink_issafe(const uint8_t *link, size_t link_len);
 
 extern size_t
-sd_autolink__www(size_t *rewind_p, struct buf *link, char *data, size_t offset, size_t size);
+sd_autolink__www(size_t *rewind_p, struct buf *link, uint8_t *data, size_t offset, size_t size);
 
 extern size_t
-sd_autolink__email(size_t *rewind_p, struct buf *link, char *data, size_t offset, size_t size);
+sd_autolink__email(size_t *rewind_p, struct buf *link, uint8_t *data, size_t offset, size_t size);
 
 extern size_t
-sd_autolink__url(size_t *rewind_p, struct buf *link, char *data, size_t offset, size_t size);
+sd_autolink__url(size_t *rewind_p, struct buf *link, uint8_t *data, size_t offset, size_t size);
 
 #endif
 
diff --git a/cbits/buffer.c b/cbits/buffer.c
--- a/cbits/buffer.c
+++ b/cbits/buffer.c
@@ -30,59 +30,7 @@
 #	define _buf_vsnprintf vsnprintf
 #endif
 
-/* bufcmp: buffer comparison */
 int
-bufcmp(const struct buf *a, const struct buf *b)
-{
-	size_t i = 0;
-	size_t cmplen;
-
-	if (a == b)
-		return 0;
-
-	if (!a)
-		return -1;
-
-	if (!b)
-		return 1;
-
-	cmplen = (a->size < b->size) ? a->size : b->size;
-
-	while (i < cmplen && a->data[i] == b->data[i])
-		++i;
-
-	if (i < a->size) {
-		if (i < b->size) return a->data[i] - b->data[i];
-		return 1;
-	} else {
-		if (i < b->size) return -1;
-		return 0;
-	}
-}
-
-/* bufcmps: comparison of a string to a buffer */
-int
-bufcmps(const struct buf *a, const char *b)
-{
-	const size_t len = strlen(b);
-	size_t cmplen = len;
-	int r;
-
-	if (!a || !a->size)
-		return b ? 0 : -1;
-
-	if (len < a->size)
-		cmplen = a->size;
-
-	r = strncmp(a->data, b, cmplen);
-
-	if (r) return r;
-	else if (a->size == len) return 0;
-	else if (a->size < len) return -1;
-	else return 1;
-}
-
-int
 bufprefix(const struct buf *buf, const char *prefix)
 {
 	size_t i;
@@ -98,42 +46,6 @@
 	return 0;
 }
 
-
-/* bufdup: buffer duplication */
-struct buf *
-bufdup(const struct buf *src, size_t dupunit)
-{
-	size_t blocks;
-	struct buf *ret;
-	if (src == 0)
-		return 0;
-
-	ret = malloc(sizeof (struct buf));
-	if (ret == 0)
-		return 0;
-
-	ret->unit = dupunit;
-	ret->size = src->size;
-	ret->ref = 1;
-	if (!src->size) {
-		ret->asize = 0;
-		ret->data = 0;
-		return ret;
-	}
-
-	blocks = (src->size + dupunit - 1) / dupunit;
-	ret->asize = blocks * dupunit;
-	ret->data = malloc(ret->asize);
-
-	if (ret->data == 0) {
-		free(ret);
-		return 0;
-	}
-
-	memcpy(ret->data, src->data, src->size);
-	return ret;
-}
-
 /* bufgrow: increasing the allocated size to the given value */
 int
 bufgrow(struct buf *buf, size_t neosz)
@@ -170,7 +82,6 @@
 	if (ret) {
 		ret->data = 0;
 		ret->size = ret->asize = 0;
-		ret->ref = 1;
 		ret->unit = unit;
 	}
 	return ret;
@@ -184,11 +95,11 @@
 		return NULL;
 
 	if (buf->size < buf->asize && buf->data[buf->size] == 0)
-		return buf->data;
+		return (char *)buf->data;
 
 	if (buf->size + 1 <= buf->asize || bufgrow(buf, buf->size + 1) == 0) {
 		buf->data[buf->size] = 0;
-		return buf->data;
+		return (char *)buf->data;
 	}
 
 	return NULL;
@@ -229,9 +140,9 @@
 }
 
 
-/* bufputc: appends a single char to a buffer */
+/* bufputc: appends a single uint8_t to a buffer */
 void
-bufputc(struct buf *buf, char c)
+bufputc(struct buf *buf, int c)
 {
 	if (!buf)
 		return;
@@ -250,10 +161,8 @@
 	if (!buf)
 		return;
 
-	if (--buf->ref == 0) {
-		free(buf->data);
-		free(buf);
-	}
+	free(buf->data);
+	free(buf);
 }
 
 
@@ -269,20 +178,6 @@
 	buf->size = buf->asize = 0;
 }
 
-
-/* bufset: safely assigns a buffer to another */
-void
-bufset(struct buf **dest, struct buf *src)
-{
-	if (src) {
-		if (!src->asize) src = bufdup(src, 1);
-		else src->ref += 1;
-	}
-
-	bufrelease(*dest);
-	*dest = src;
-}
-
 /* bufslurp: removes a given number of bytes from the head of the array */
 void
 bufslurp(struct buf *buf, size_t len)
@@ -308,7 +203,7 @@
 	if (buf == 0 || (buf->size >= buf->asize && bufgrow(buf, buf->size + 1)) < 0)
 		return;
 
-	n = _buf_vsnprintf(buf->data + buf->size, buf->asize - buf->size, fmt, ap);
+	n = _buf_vsnprintf((char *)buf->data + buf->size, buf->asize - buf->size, fmt, ap);
 
 	if (n < 0) {
 #ifdef _MSC_VER
@@ -322,7 +217,7 @@
 		if (bufgrow(buf, buf->size + n + 1) < 0)
 			return;
 
-		n = _buf_vsnprintf(buf->data + buf->size, buf->asize - buf->size, fmt, ap);
+		n = _buf_vsnprintf((char *)buf->data + buf->size, buf->asize - buf->size, fmt, ap);
 	}
 
 	if (n < 0)
diff --git a/cbits/buffer.h b/cbits/buffer.h
--- a/cbits/buffer.h
+++ b/cbits/buffer.h
@@ -20,6 +20,7 @@
 
 #include <stddef.h>
 #include <stdarg.h>
+#include <stdint.h>
 
 #if defined(_MSC_VER)
 #define __attribute__(x)
@@ -33,36 +34,23 @@
 
 /* struct buf: character array buffer */
 struct buf {
-	char *data;		/* actual character data */
+	uint8_t *data;		/* actual character data */
 	size_t size;	/* size of the string */
 	size_t asize;	/* allocated size (0 = volatile buffer) */
 	size_t unit;	/* reallocation unit size (0 = read-only buffer) */
-	int	ref;		/* reference count */
-};	
+};
 
 /* CONST_BUF: global buffer from a string litteral */
 #define BUF_STATIC(string) \
-	{ string, sizeof string -1, sizeof string, 0, 0 }
+	{ (uint8_t *)string, sizeof string -1, sizeof string, 0, 0 }
 
 /* VOLATILE_BUF: macro for creating a volatile buffer on the stack */
 #define BUF_VOLATILE(strname) \
-	{ strname, strlen(strname), 0, 0, 0 }
+	{ (uint8_t *)strname, strlen(strname), 0, 0, 0 }
 
 /* BUFPUTSL: optimized bufputs of a string litteral */
-#define BUFPUTSL(output, litteral) \
-	bufput(output, litteral, sizeof litteral - 1)
-
-/* bufcmp: case-sensitive buffer comparison */
-int bufcmp(const struct buf *, const struct buf *);
-
-/* bufcmps: case-sensitive comparison of a string to a buffer */
-int bufcmps(const struct buf *, const char *); 
-
-/* bufprefix: compare the beginning of a buffer with a string */
-int bufprefix(const struct buf *buf, const char *prefix); 
-
-/* bufdup: buffer duplication */
-struct buf *bufdup(const struct buf *, size_t) __attribute__ ((malloc));
+#define BUFPUTSL(output, literal) \
+	bufput(output, literal, sizeof literal - 1)
 
 /* bufgrow: increasing the allocated size to the given value */
 int bufgrow(struct buf *, size_t);
@@ -73,17 +61,17 @@
 /* bufnullterm: NUL-termination of the string array (making a C-string) */
 const char *bufcstr(struct buf *);
 
-/* bufprintf: formatted printing to a buffer */
-void bufprintf(struct buf *, const char *, ...) __attribute__ ((format (printf, 2, 3)));
+/* bufprefix: compare the beginning of a buffer with a string */
+int bufprefix(const struct buf *buf, const char *prefix);
 
 /* bufput: appends raw data to a buffer */
-void bufput(struct buf *, const void*, size_t);
+void bufput(struct buf *, const void *, size_t);
 
 /* bufputs: appends a NUL-terminated string to a buffer */
-void bufputs(struct buf *, const char*);
+void bufputs(struct buf *, const char *);
 
 /* bufputc: appends a single char to a buffer */
-void bufputc(struct buf *, char);
+void bufputc(struct buf *, int);
 
 /* bufrelease: decrease the reference count and free the buffer if needed */
 void bufrelease(struct buf *);
@@ -91,13 +79,13 @@
 /* bufreset: frees internal data of the buffer */
 void bufreset(struct buf *);
 
-/* bufset: safely assigns a buffer to another */
-void bufset(struct buf **, struct buf *);
-
 /* bufslurp: removes a given number of bytes from the head of the array */
 void bufslurp(struct buf *, size_t);
 
+/* bufprintf: formatted printing to a buffer */
+void bufprintf(struct buf *, const char *, ...) __attribute__ ((format (printf, 2, 3)));
+
 /* vbufprintf: stdarg variant of formatted printing into a buffer */
-void vbufprintf(struct buf *, const char*, va_list);
+void vbufprintf(struct buf *, const char * , va_list);
 
 #endif
diff --git a/cbits/houdini.h b/cbits/houdini.h
new file mode 100644
--- /dev/null
+++ b/cbits/houdini.h
@@ -0,0 +1,28 @@
+#ifndef __HOUDINI_H__
+#define __HOUDINI_H__
+
+#include "buffer.h"
+
+#ifdef HOUDINI_USE_LOCALE
+#	define _isxdigit(c) isxdigit(c)
+#	define _isdigit(c) isdigit(c)
+#else
+/*
+ * Helper _isdigit methods -- do not trust the current locale
+ * */
+#	define _isxdigit(c) (strchr("0123456789ABCDEFabcdef", (c)) != NULL)
+#	define _isdigit(c) ((c) >= '0' && (c) <= '9')
+#endif
+
+extern void houdini_escape_html(struct buf *ob, const uint8_t *src, size_t size);
+extern void houdini_escape_html0(struct buf *ob, const uint8_t *src, size_t size, int secure);
+extern void houdini_unescape_html(struct buf *ob, const uint8_t *src, size_t size);
+extern void houdini_escape_uri(struct buf *ob, const uint8_t *src, size_t size);
+extern void houdini_escape_url(struct buf *ob, const uint8_t *src, size_t size);
+extern void houdini_escape_href(struct buf *ob, const uint8_t *src, size_t size);
+extern void houdini_unescape_uri(struct buf *ob, const uint8_t *src, size_t size);
+extern void houdini_unescape_url(struct buf *ob, const uint8_t *src, size_t size);
+extern void houdini_escape_js(struct buf *ob, const uint8_t *src, size_t size);
+extern void houdini_unescape_js(struct buf *ob, const uint8_t *src, size_t size);
+
+#endif
diff --git a/cbits/houdini_href_e.c b/cbits/houdini_href_e.c
new file mode 100644
--- /dev/null
+++ b/cbits/houdini_href_e.c
@@ -0,0 +1,108 @@
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "houdini.h"
+
+#define ESCAPE_GROW_FACTOR(x) (((x) * 12) / 10)
+
+/*
+ * The following characters will not be escaped:
+ *
+ *		-_.+!*'(),%#@?=;:/,+&$ alphanum
+ *
+ * Note that this character set is the addition of:
+ *
+ *	- The characters which are safe to be in an URL
+ *	- The characters which are *not* safe to be in
+ *	an URL because they are RESERVED characters.
+ *
+ * We asume (lazily) that any RESERVED char that
+ * appears inside an URL is actually meant to
+ * have its native function (i.e. as an URL 
+ * component/separator) and hence needs no escaping.
+ *
+ * There are two exceptions: the chacters & (amp)
+ * and ' (single quote) do not appear in the table.
+ * They are meant to appear in the URL as components,
+ * yet they require special HTML-entity escaping
+ * to generate valid HTML markup.
+ *
+ * All other characters will be escaped to %XX.
+ *
+ */
+static const char HREF_SAFE[] = {
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+	0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 
+	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+void
+houdini_escape_href(struct buf *ob, const uint8_t *src, size_t size)
+{
+	static const char hex_chars[] = "0123456789ABCDEF";
+	size_t  i = 0, org;
+	char hex_str[3];
+
+	bufgrow(ob, ESCAPE_GROW_FACTOR(size));
+	hex_str[0] = '%';
+
+	while (i < size) {
+		org = i;
+		while (i < size && HREF_SAFE[src[i]] != 0)
+			i++;
+
+		if (i > org)
+			bufput(ob, src + org, i - org);
+
+		/* escaping */
+		if (i >= size)
+			break;
+
+		switch (src[i]) {
+		/* amp appears all the time in URLs, but needs
+		 * HTML-entity escaping to be inside an href */
+		case '&': 
+			BUFPUTSL(ob, "&amp;");
+			break;
+
+		/* the single quote is a valid URL character
+		 * according to the standard; it needs HTML
+		 * entity escaping too */
+		case '\'':
+			BUFPUTSL(ob, "&#x27;");
+			break;
+		
+		/* the space can be escaped to %20 or a plus
+		 * sign. we're going with the generic escape
+		 * for now. the plus thing is more commonly seen
+		 * when building GET strings */
+#if 0
+		case ' ':
+			bufputc(ob, '+');
+			break;
+#endif
+
+		/* every other character goes with a %XX escaping */
+		default:
+			hex_str[1] = hex_chars[(src[i] >> 4) & 0xF];
+			hex_str[2] = hex_chars[src[i] & 0xF];
+			bufput(ob, hex_str, 3);
+		}
+
+		i++;
+	}
+}
diff --git a/cbits/houdini_html_e.c b/cbits/houdini_html_e.c
new file mode 100644
--- /dev/null
+++ b/cbits/houdini_html_e.c
@@ -0,0 +1,84 @@
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "houdini.h"
+
+#define ESCAPE_GROW_FACTOR(x) (((x) * 12) / 10) /* this is very scientific, yes */
+
+/**
+ * According to the OWASP rules:
+ *
+ * & --> &amp;
+ * < --> &lt;
+ * > --> &gt;
+ * " --> &quot;
+ * ' --> &#x27;     &apos; is not recommended
+ * / --> &#x2F;     forward slash is included as it helps end an HTML entity
+ *
+ */
+static const char HTML_ESCAPE_TABLE[] = {
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+	0, 0, 1, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 4, 
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 0, 
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static const char *HTML_ESCAPES[] = {
+        "",
+        "&quot;",
+        "&amp;",
+        "&#39;",
+        "&#47;",
+        "&lt;",
+        "&gt;"
+};
+
+void
+houdini_escape_html0(struct buf *ob, const uint8_t *src, size_t size, int secure)
+{
+	size_t  i = 0, org, esc;
+
+	bufgrow(ob, ESCAPE_GROW_FACTOR(size));
+
+	while (i < size) {
+		org = i;
+		while (i < size && (esc = HTML_ESCAPE_TABLE[src[i]]) == 0)
+			i++;
+
+		if (i > org)
+			bufput(ob, src + org, i - org);
+
+		/* escaping */
+		if (i >= size)
+			break;
+
+		/* The forward slash is only escaped in secure mode */
+		if (src[i] == '/' && !secure) {
+			bufputc(ob, '/');
+		} else {
+			bufputs(ob, HTML_ESCAPES[esc]);
+		}
+
+		i++;
+	}
+}
+
+void
+houdini_escape_html(struct buf *ob, const uint8_t *src, size_t size)
+{
+	houdini_escape_html0(ob, src, size, 1);
+}
+
diff --git a/cbits/html.c b/cbits/html.c
--- a/cbits/html.c
+++ b/cbits/html.c
@@ -23,43 +23,12 @@
 #include <stdio.h>
 #include <ctype.h>
 
-#define USE_XHTML(opt) (opt->flags & HTML_USE_XHTML)
-
-static inline void
-put_scaped_char(struct buf *ob, char c)
-{
-	switch (c) {
-		case '<': BUFPUTSL(ob, "&lt;"); break;
-		case '>': BUFPUTSL(ob, "&gt;"); break;
-		case '&': BUFPUTSL(ob, "&amp;"); break;
-		case '"': BUFPUTSL(ob, "&quot;"); break;
-		default: bufputc(ob, c); break;
-	}
-}
-
-/* sdhtml_escape • copy the buffer entity-escaping '<', '>', '&' and '"' */
-void
-sdhtml_escape(struct buf *ob, const char *src, size_t size)
-{
-	size_t  i = 0, org;
-	while (i < size) {
-		/* copying directly unescaped characters */
-		org = i;
-		while (i < size && src[i] != '<' && src[i] != '>'
-		&& src[i] != '&' && src[i] != '"')
-			i += 1;
-		if (i > org) bufput(ob, src + org, i - org);
-
-		/* escaping */
-		if (i >= size) break;
+#include "houdini.h"
 
-		put_scaped_char(ob, src[i]);
-		i++;
-	}
-}
+#define USE_XHTML(opt) (opt->flags & HTML_USE_XHTML)
 
 int
-sdhtml_tag(const char *tag_data, size_t tag_size, const char *tagname)
+sdhtml_is_tag(const uint8_t *tag_data, size_t tag_size, const char *tagname)
 {
 	size_t i;
 	int closed = 0;
@@ -95,7 +64,7 @@
  * GENERIC RENDERER *
  ********************/
 static int
-rndr_autolink(struct buf *ob, struct buf *link, enum mkd_autolink type, void *opaque)
+rndr_autolink(struct buf *ob, const struct buf *link, enum mkd_autolink type, void *opaque)
 {
 	struct html_renderopt *options = opaque;
 
@@ -110,7 +79,7 @@
 	BUFPUTSL(ob, "<a href=\"");
 	if (type == MKDA_EMAIL)
 		BUFPUTSL(ob, "mailto:");
-	sdhtml_escape(ob, link->data, link->size);
+	houdini_escape_href(ob, link->data, link->size);
 
 	if (options->link_attributes) {
 		bufputc(ob, '\"');
@@ -126,9 +95,9 @@
 	 * want to print the `mailto:` prefix
 	 */
 	if (bufprefix(link, "mailto:") == 0) {
-		sdhtml_escape(ob, link->data + 7, link->size - 7);
+		houdini_escape_html(ob, link->data + 7, link->size - 7);
 	} else {
-		sdhtml_escape(ob, link->data, link->size);
+		houdini_escape_html(ob, link->data, link->size);
 	}
 
 	BUFPUTSL(ob, "</a>");
@@ -137,7 +106,7 @@
 }
 
 static void
-rndr_blockcode(struct buf *ob, struct buf *text, struct buf *lang, void *opaque)
+rndr_blockcode(struct buf *ob, const struct buf *text, const struct buf *lang, void *opaque)
 {
 	if (ob->size) bufputc(ob, '\n');
 
@@ -158,7 +127,7 @@
 					org++;
 
 				if (cls) bufputc(ob, ' ');
-				sdhtml_escape(ob, lang->data + org, i - org);
+				houdini_escape_html(ob, lang->data + org, i - org);
 			}
 		}
 
@@ -167,58 +136,13 @@
 		BUFPUTSL(ob, "<pre><code>");
 
 	if (text)
-		sdhtml_escape(ob, text->data, text->size);
-
-	BUFPUTSL(ob, "</code></pre>\n");
-}
-
-/*
- * GitHub style code block:
- *
- *		<pre lang="LANG"><code>
- *		...
- *		</pre></code>
- *
- * Unlike other parsers, we store the language identifier in the <pre>,
- * and don't let the user generate custom classes.
- *
- * The language identifier in the <pre> block gets postprocessed and all
- * the code inside gets syntax highlighted with Pygments. This is much safer
- * than letting the user specify a CSS class for highlighting.
- *
- * Note that we only generate HTML for the first specifier.
- * E.g.
- *		~~~~ {.python .numbered}	=>	<pre lang="python"><code>
- */
-static void
-rndr_blockcode_github(struct buf *ob, struct buf *text, struct buf *lang, void *opaque)
-{
-	if (ob->size) bufputc(ob, '\n');
-
-	if (lang && lang->size) {
-		size_t i = 0;
-		BUFPUTSL(ob, "<pre lang=\"");
-
-		while (i < lang->size && !isspace(lang->data[i]))
-			i++;
-
-		if (lang->data[0] == '.')
-			sdhtml_escape(ob, lang->data + 1, i - 1);
-		else
-			sdhtml_escape(ob, lang->data, i);
-
-		BUFPUTSL(ob, "\"><code>");
-	} else
-		BUFPUTSL(ob, "<pre><code>");
-
-	if (text)
-		sdhtml_escape(ob, text->data, text->size);
+		houdini_escape_html(ob, text->data, text->size);
 
 	BUFPUTSL(ob, "</code></pre>\n");
 }
 
 static void
-rndr_blockquote(struct buf *ob, struct buf *text, void *opaque)
+rndr_blockquote(struct buf *ob, const struct buf *text, void *opaque)
 {
 	if (ob->size) bufputc(ob, '\n');
 	BUFPUTSL(ob, "<blockquote>\n");
@@ -227,16 +151,16 @@
 }
 
 static int
-rndr_codespan(struct buf *ob, struct buf *text, void *opaque)
+rndr_codespan(struct buf *ob, const struct buf *text, void *opaque)
 {
 	BUFPUTSL(ob, "<code>");
-	if (text) sdhtml_escape(ob, text->data, text->size);
+	if (text) houdini_escape_html(ob, text->data, text->size);
 	BUFPUTSL(ob, "</code>");
 	return 1;
 }
 
 static int
-rndr_strikethrough(struct buf *ob, struct buf *text, void *opaque)
+rndr_strikethrough(struct buf *ob, const struct buf *text, void *opaque)
 {
 	if (!text || !text->size)
 		return 0;
@@ -248,7 +172,7 @@
 }
 
 static int
-rndr_double_emphasis(struct buf *ob, struct buf *text, void *opaque)
+rndr_double_emphasis(struct buf *ob, const struct buf *text, void *opaque)
 {
 	if (!text || !text->size)
 		return 0;
@@ -261,7 +185,7 @@
 }
 
 static int
-rndr_emphasis(struct buf *ob, struct buf *text, void *opaque)
+rndr_emphasis(struct buf *ob, const struct buf *text, void *opaque)
 {
 	if (!text || !text->size) return 0;
 	BUFPUTSL(ob, "<em>");
@@ -273,16 +197,16 @@
 static int
 rndr_linebreak(struct buf *ob, void *opaque)
 {
-	struct html_renderopt *options = opaque;	
+	struct html_renderopt *options = opaque;
 	bufputs(ob, USE_XHTML(options) ? "<br/>\n" : "<br>\n");
 	return 1;
 }
 
 static void
-rndr_header(struct buf *ob, struct buf *text, int level, void *opaque)
+rndr_header(struct buf *ob, const struct buf *text, int level, void *opaque)
 {
 	struct html_renderopt *options = opaque;
-	
+
 	if (ob->size)
 		bufputc(ob, '\n');
 
@@ -296,21 +220,21 @@
 }
 
 static int
-rndr_link(struct buf *ob, struct buf *link, struct buf *title, struct buf *content, void *opaque)
+rndr_link(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque)
 {
 	struct html_renderopt *options = opaque;
-	
+
 	if (link != NULL && (options->flags & HTML_SAFELINK) != 0 && !sd_autolink_issafe(link->data, link->size))
 		return 0;
 
 	BUFPUTSL(ob, "<a href=\"");
 
 	if (link && link->size)
-		sdhtml_escape(ob, link->data, link->size);
+		houdini_escape_href(ob, link->data, link->size);
 
 	if (title && title->size) {
 		BUFPUTSL(ob, "\" title=\"");
-		sdhtml_escape(ob, title->data, title->size);
+		houdini_escape_html(ob, title->data, title->size);
 	}
 
 	if (options->link_attributes) {
@@ -327,7 +251,7 @@
 }
 
 static void
-rndr_list(struct buf *ob, struct buf *text, int flags, void *opaque)
+rndr_list(struct buf *ob, const struct buf *text, int flags, void *opaque)
 {
 	if (ob->size) bufputc(ob, '\n');
 	bufput(ob, flags & MKD_LIST_ORDERED ? "<ol>\n" : "<ul>\n", 5);
@@ -336,18 +260,21 @@
 }
 
 static void
-rndr_listitem(struct buf *ob, struct buf *text, int flags, void *opaque)
+rndr_listitem(struct buf *ob, const struct buf *text, int flags, void *opaque)
 {
 	BUFPUTSL(ob, "<li>");
 	if (text) {
-		while (text->size && text->data[text->size - 1] == '\n')
-			text->size -= 1;
-		bufput(ob, text->data, text->size); }
+		size_t size = text->size;
+		while (size && text->data[size - 1] == '\n')
+			size--;
+
+		bufput(ob, text->data, size);
+	}
 	BUFPUTSL(ob, "</li>\n");
 }
 
 static void
-rndr_paragraph(struct buf *ob, struct buf *text, void *opaque)
+rndr_paragraph(struct buf *ob, const struct buf *text, void *opaque)
 {
 	struct html_renderopt *options = opaque;
 	size_t i = 0;
@@ -379,7 +306,7 @@
 			 */
 			if (i >= text->size - 1)
 				break;
-			
+
 			rndr_linebreak(ob, opaque);
 			i++;
 		}
@@ -390,7 +317,7 @@
 }
 
 static void
-rndr_raw_block(struct buf *ob, struct buf *text, void *opaque)
+rndr_raw_block(struct buf *ob, const struct buf *text, void *opaque)
 {
 	size_t org, sz;
 	if (!text) return;
@@ -405,7 +332,7 @@
 }
 
 static int
-rndr_triple_emphasis(struct buf *ob, struct buf *text, void *opaque)
+rndr_triple_emphasis(struct buf *ob, const struct buf *text, void *opaque)
 {
 	if (!text || !text->size) return 0;
 	BUFPUTSL(ob, "<strong><em>");
@@ -417,44 +344,50 @@
 static void
 rndr_hrule(struct buf *ob, void *opaque)
 {
-	struct html_renderopt *options = opaque;	
+	struct html_renderopt *options = opaque;
 	if (ob->size) bufputc(ob, '\n');
 	bufputs(ob, USE_XHTML(options) ? "<hr/>\n" : "<hr>\n");
 }
 
 static int
-rndr_image(struct buf *ob, struct buf *link, struct buf *title, struct buf *alt, void *opaque)
+rndr_image(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *alt, void *opaque)
 {
-	struct html_renderopt *options = opaque;	
+	struct html_renderopt *options = opaque;
 	if (!link || !link->size) return 0;
+
 	BUFPUTSL(ob, "<img src=\"");
-	sdhtml_escape(ob, link->data, link->size);
+	houdini_escape_href(ob, link->data, link->size);
 	BUFPUTSL(ob, "\" alt=\"");
+
 	if (alt && alt->size)
-		sdhtml_escape(ob, alt->data, alt->size);
+		houdini_escape_html(ob, alt->data, alt->size);
+
 	if (title && title->size) {
 		BUFPUTSL(ob, "\" title=\"");
-		sdhtml_escape(ob, title->data, title->size); }
+		houdini_escape_html(ob, title->data, title->size); }
 
 	bufputs(ob, USE_XHTML(options) ? "\"/>" : "\">");
 	return 1;
 }
 
 static int
-rndr_raw_html(struct buf *ob, struct buf *text, void *opaque)
+rndr_raw_html(struct buf *ob, const struct buf *text, void *opaque)
 {
-	struct html_renderopt *options = opaque;	
+	struct html_renderopt *options = opaque;
 
 	if ((options->flags & HTML_SKIP_HTML) != 0)
 		return 1;
 
-	if ((options->flags & HTML_SKIP_STYLE) != 0 && sdhtml_tag(text->data, text->size, "style"))
+	if ((options->flags & HTML_SKIP_STYLE) != 0 &&
+		sdhtml_is_tag(text->data, text->size, "style"))
 		return 1;
 
-	if ((options->flags & HTML_SKIP_LINKS) != 0 && sdhtml_tag(text->data, text->size, "a"))
+	if ((options->flags & HTML_SKIP_LINKS) != 0 &&
+		sdhtml_is_tag(text->data, text->size, "a"))
 		return 1;
 
-	if ((options->flags & HTML_SKIP_IMAGES) != 0 && sdhtml_tag(text->data, text->size, "img"))
+	if ((options->flags & HTML_SKIP_IMAGES) != 0 &&
+		sdhtml_is_tag(text->data, text->size, "img"))
 		return 1;
 
 	bufput(ob, text->data, text->size);
@@ -462,7 +395,7 @@
 }
 
 static void
-rndr_table(struct buf *ob, struct buf *header, struct buf *body, void *opaque)
+rndr_table(struct buf *ob, const struct buf *header, const struct buf *body, void *opaque)
 {
 	if (ob->size) bufputc(ob, '\n');
 	BUFPUTSL(ob, "<table><thead>\n");
@@ -475,7 +408,7 @@
 }
 
 static void
-rndr_tablerow(struct buf *ob, struct buf *text, void *opaque)
+rndr_tablerow(struct buf *ob, const struct buf *text, void *opaque)
 {
 	BUFPUTSL(ob, "<tr>\n");
 	if (text)
@@ -484,7 +417,7 @@
 }
 
 static void
-rndr_tablecell(struct buf *ob, struct buf *text, int flags, void *opaque)
+rndr_tablecell(struct buf *ob, const struct buf *text, int flags, void *opaque)
 {
 	if (flags & MKD_TABLE_HEADER) {
 		BUFPUTSL(ob, "<th");
@@ -520,7 +453,7 @@
 }
 
 static int
-rndr_superscript(struct buf *ob, struct buf *text, void *opaque)
+rndr_superscript(struct buf *ob, const struct buf *text, void *opaque)
 {
 	if (!text || !text->size) return 0;
 	BUFPUTSL(ob, "<sup>");
@@ -530,14 +463,14 @@
 }
 
 static void
-rndr_normal_text(struct buf *ob, struct buf *text, void *opaque)
+rndr_normal_text(struct buf *ob, const struct buf *text, void *opaque)
 {
 	if (text)
-		sdhtml_escape(ob, text->data, text->size);
+		houdini_escape_html(ob, text->data, text->size);
 }
 
 static void
-toc_header(struct buf *ob, struct buf *text, int level, void *opaque)
+toc_header(struct buf *ob, const struct buf *text, int level, void *opaque)
 {
 	struct html_renderopt *options = opaque;
 
@@ -667,7 +600,4 @@
 
 	if (render_flags & HTML_SKIP_HTML)
 		callbacks->blockhtml = NULL;
-
-	if (render_flags & HTML_GITHUB_BLOCKCODE)
-		callbacks->blockcode = rndr_blockcode_github;
 }
diff --git a/cbits/html.h b/cbits/html.h
--- a/cbits/html.h
+++ b/cbits/html.h
@@ -30,7 +30,7 @@
 	unsigned int flags;
 
 	/* extra callbacks */
-	void (*link_attributes)(struct buf *ob, struct buf *url, void *self);
+	void (*link_attributes)(struct buf *ob, const struct buf *url, void *self);
 };
 
 typedef enum {
@@ -38,12 +38,11 @@
 	HTML_SKIP_STYLE = (1 << 1),
 	HTML_SKIP_IMAGES = (1 << 2),
 	HTML_SKIP_LINKS = (1 << 3),
-	HTML_EXPAND_TABS = (1 << 5),
-	HTML_SAFELINK = (1 << 7),
-	HTML_TOC = (1 << 8),
-	HTML_HARD_WRAP = (1 << 9),
-	HTML_GITHUB_BLOCKCODE = (1 << 10),
-	HTML_USE_XHTML = (1 << 11),
+	HTML_EXPAND_TABS = (1 << 4),
+	HTML_SAFELINK = (1 << 5),
+	HTML_TOC = (1 << 6),
+	HTML_HARD_WRAP = (1 << 7),
+	HTML_USE_XHTML = (1 << 8),
 } html_render_mode;
 
 typedef enum {
@@ -52,11 +51,8 @@
 	HTML_TAG_CLOSE,
 } html_tag;
 
-void
-sdhtml_escape(struct buf *ob, const char *src, size_t size);
-
 int
-sdhtml_tag(const char *tag_data, size_t tag_size, const char *tagname);
+sdhtml_is_tag(const uint8_t *tag_data, size_t tag_size, const char *tagname);
 
 extern void
 sdhtml_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options_ptr, unsigned int render_flags);
@@ -65,7 +61,7 @@
 sdhtml_toc_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options_ptr);
 
 extern void
-sdhtml_smartypants(struct buf *ob, struct buf *text);
+sdhtml_smartypants(struct buf *ob, const uint8_t *text, size_t size);
 
 #endif
 
diff --git a/cbits/html_smartypants.c b/cbits/html_smartypants.c
--- a/cbits/html_smartypants.c
+++ b/cbits/html_smartypants.c
@@ -27,19 +27,19 @@
 	int in_dquote;
 };
 
-static size_t smartypants_cb__ltag(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size);
-static size_t smartypants_cb__dquote(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size);
-static size_t smartypants_cb__amp(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size);
-static size_t smartypants_cb__period(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size);
-static size_t smartypants_cb__number(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size);
-static size_t smartypants_cb__dash(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size);
-static size_t smartypants_cb__parens(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size);
-static size_t smartypants_cb__squote(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size);
-static size_t smartypants_cb__backtick(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size);
-static size_t smartypants_cb__escape(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size);
+static size_t smartypants_cb__ltag(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
+static size_t smartypants_cb__dquote(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
+static size_t smartypants_cb__amp(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
+static size_t smartypants_cb__period(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
+static size_t smartypants_cb__number(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
+static size_t smartypants_cb__dash(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
+static size_t smartypants_cb__parens(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
+static size_t smartypants_cb__squote(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
+static size_t smartypants_cb__backtick(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
+static size_t smartypants_cb__escape(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
 
 static size_t (*smartypants_cb_ptrs[])
-	(struct buf *, struct smartypants_data *, char, const char *, size_t) = 
+	(struct buf *, struct smartypants_data *, uint8_t, const uint8_t *, size_t) =
 {
 	NULL,					/* 0 */
 	smartypants_cb__dash,	/* 1 */
@@ -54,7 +54,7 @@
 	smartypants_cb__escape, /* 10 */
 };
 
-static const char smartypants_cb_chars[] = {
+static const uint8_t smartypants_cb_chars[] = {
 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 	0, 0, 4, 0, 0, 0, 5, 3, 2, 0, 0, 0, 0, 1, 6, 0,
@@ -74,13 +74,13 @@
 };
 
 static inline int
-word_boundary(char c)
+word_boundary(uint8_t c)
 {
 	return c == 0 || isspace(c) || ispunct(c);
 }
 
 static int
-smartypants_quotes(struct buf *ob, char previous_char, char next_char, char quote, int *is_open)
+smartypants_quotes(struct buf *ob, uint8_t previous_char, uint8_t next_char, uint8_t quote, int *is_open)
 {
 	char ent[8];
 
@@ -97,10 +97,10 @@
 }
 
 static size_t
-smartypants_cb__squote(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size)
+smartypants_cb__squote(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
 {
 	if (size >= 2) {
-		char t1 = tolower(text[1]);
+		uint8_t t1 = tolower(text[1]);
 
 		if (t1 == '\'') {
 			if (smartypants_quotes(ob, previous_char, size >= 3 ? text[2] : 0, 'd', &smrt->in_dquote))
@@ -114,7 +114,7 @@
 		}
 
 		if (size >= 3) {
-			char t2 = tolower(text[2]);
+			uint8_t t2 = tolower(text[2]);
 
 			if (((t1 == 'r' && t2 == 'e') ||
 				(t1 == 'l' && t2 == 'l') ||
@@ -134,11 +134,11 @@
 }
 
 static size_t
-smartypants_cb__parens(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size)
+smartypants_cb__parens(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
 {
 	if (size >= 3) {
-		char t1 = tolower(text[1]);
-		char t2 = tolower(text[2]);
+		uint8_t t1 = tolower(text[1]);
+		uint8_t t2 = tolower(text[2]);
 
 		if (t1 == 'c' && t2 == ')') {
 			BUFPUTSL(ob, "&copy;");
@@ -155,13 +155,13 @@
 			return 3;
 		}
 	}
-	
+
 	bufputc(ob, text[0]);
 	return 0;
 }
 
 static size_t
-smartypants_cb__dash(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size)
+smartypants_cb__dash(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
 {
 	if (size >= 3 && text[1] == '-' && text[2] == '-') {
 		BUFPUTSL(ob, "&mdash;");
@@ -178,7 +178,7 @@
 }
 
 static size_t
-smartypants_cb__amp(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size)
+smartypants_cb__amp(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
 {
 	if (size >= 6 && memcmp(text, "&quot;", 6) == 0) {
 		if (smartypants_quotes(ob, previous_char, size >= 7 ? text[6] : 0, 'd', &smrt->in_dquote))
@@ -193,7 +193,7 @@
 }
 
 static size_t
-smartypants_cb__period(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size)
+smartypants_cb__period(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
 {
 	if (size >= 3 && text[1] == '.' && text[2] == '.') {
 		BUFPUTSL(ob, "&hellip;");
@@ -210,7 +210,7 @@
 }
 
 static size_t
-smartypants_cb__backtick(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size)
+smartypants_cb__backtick(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
 {
 	if (size >= 2 && text[1] == '`') {
 		if (smartypants_quotes(ob, previous_char, size >= 3 ? text[2] : 0, 'd', &smrt->in_dquote))
@@ -221,7 +221,7 @@
 }
 
 static size_t
-smartypants_cb__number(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size)
+smartypants_cb__number(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
 {
 	if (word_boundary(previous_char) && size >= 3) {
 		if (text[0] == '1' && text[1] == '/' && text[2] == '2') {
@@ -253,7 +253,7 @@
 }
 
 static size_t
-smartypants_cb__dquote(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size)
+smartypants_cb__dquote(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
 {
 	if (!smartypants_quotes(ob, previous_char, size > 0 ? text[1] : 0, 'd', &smrt->in_dquote))
 		BUFPUTSL(ob, "&quot;");
@@ -262,7 +262,7 @@
 }
 
 static size_t
-smartypants_cb__ltag(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size)
+smartypants_cb__ltag(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
 {
 	static const char *skip_tags[] = {"pre", "code", "kbd", "script"};
 	static const size_t skip_tags_count = 4;
@@ -273,7 +273,7 @@
 		i++;
 
 	for (tag = 0; tag < skip_tags_count; ++tag) {
-		if (sdhtml_tag(text, size, skip_tags[tag]) == HTML_TAG_OPEN)
+		if (sdhtml_is_tag(text, size, skip_tags[tag]) == HTML_TAG_OPEN)
 			break;
 	}
 
@@ -285,7 +285,7 @@
 			if (i == size)
 				break;
 
-			if (sdhtml_tag(text + i, size - i, skip_tags[tag]) == HTML_TAG_CLOSE)
+			if (sdhtml_is_tag(text + i, size - i, skip_tags[tag]) == HTML_TAG_CLOSE)
 				break;
 
 			i++;
@@ -300,7 +300,7 @@
 }
 
 static size_t
-smartypants_cb__escape(struct buf *ob, struct smartypants_data *smrt, char previous_char, const char *text, size_t size)
+smartypants_cb__escape(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
 {
 	if (size < 2)
 		return 0;
@@ -323,9 +323,9 @@
 
 #if 0
 static struct {
-    char c0;
-    const char *pattern;
-    const char *entity;
+    uint8_t c0;
+    const uint8_t *pattern;
+    const uint8_t *entity;
     int skip;
 } smartypants_subs[] = {
     { '\'', "'s>",      "&rsquo;",  0 },
@@ -352,7 +352,7 @@
 #endif
 
 void
-sdhtml_smartypants(struct buf *ob, struct buf *text)
+sdhtml_smartypants(struct buf *ob, const uint8_t *text, size_t size)
 {
 	size_t i;
 	struct smartypants_data smrt = {0, 0};
@@ -360,22 +360,22 @@
 	if (!text)
 		return;
 
-	bufgrow(ob, text->size);
+	bufgrow(ob, size);
 
-	for (i = 0; i < text->size; ++i) {
+	for (i = 0; i < size; ++i) {
 		size_t org;
-		char action = 0;
+		uint8_t action = 0;
 
 		org = i;
-		while (i < text->size && (action = smartypants_cb_chars[(unsigned char)text->data[i]]) == 0)
+		while (i < size && (action = smartypants_cb_chars[text[i]]) == 0)
 			i++;
 
 		if (i > org)
-			bufput(ob, text->data + org, i - org);
+			bufput(ob, text + org, i - org);
 
-		if (i < text->size) {
+		if (i < size) {
 			i += smartypants_cb_ptrs[(int)action]
-				(ob, &smrt, i ? text->data[i - 1] : 0, text->data + i, text->size - i);
+				(ob, &smrt, i ? text[i - 1] : 0, text + i, size - i);
 		}
 	}
 }
diff --git a/cbits/markdown.c b/cbits/markdown.c
--- a/cbits/markdown.c
+++ b/cbits/markdown.c
@@ -45,8 +45,8 @@
 struct link_ref {
 	unsigned int id;
 
-	struct buf link;
-	struct buf title;
+	struct buf *link;
+	struct buf *title;
 
 	struct link_ref *next;
 };
@@ -57,19 +57,19 @@
 /*   offset is the number of valid chars before data */
 struct sd_markdown;
 typedef size_t
-(*char_trigger)(struct buf *ob, struct sd_markdown *rndr, char *data, size_t offset, size_t size);
+(*char_trigger)(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
 
-static size_t char_emphasis(struct buf *ob, struct sd_markdown *rndr, char *data, size_t offset, size_t size);
-static size_t char_linebreak(struct buf *ob, struct sd_markdown *rndr, char *data, size_t offset, size_t size);
-static size_t char_codespan(struct buf *ob, struct sd_markdown *rndr, char *data, size_t offset, size_t size);
-static size_t char_escape(struct buf *ob, struct sd_markdown *rndr, char *data, size_t offset, size_t size);
-static size_t char_entity(struct buf *ob, struct sd_markdown *rndr, char *data, size_t offset, size_t size);
-static size_t char_langle_tag(struct buf *ob, struct sd_markdown *rndr, char *data, size_t offset, size_t size);
-static size_t char_autolink_url(struct buf *ob, struct sd_markdown *rndr, char *data, size_t offset, size_t size);
-static size_t char_autolink_email(struct buf *ob, struct sd_markdown *rndr, char *data, size_t offset, size_t size);
-static size_t char_autolink_www(struct buf *ob, struct sd_markdown *rndr, char *data, size_t offset, size_t size);
-static size_t char_link(struct buf *ob, struct sd_markdown *rndr, char *data, size_t offset, size_t size);
-static size_t char_superscript(struct buf *ob, struct sd_markdown *rndr, char *data, size_t offset, size_t size);
+static size_t char_emphasis(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
+static size_t char_linebreak(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
+static size_t char_codespan(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
+static size_t char_escape(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
+static size_t char_entity(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
+static size_t char_langle_tag(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
+static size_t char_autolink_url(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
+static size_t char_autolink_email(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
+static size_t char_autolink_www(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
+static size_t char_link(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
+static size_t char_superscript(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
 
 enum markdown_char_t {
 	MD_CHAR_NONE = 0,
@@ -107,7 +107,7 @@
 	void *opaque;
 
 	struct link_ref *refs[REF_TABLE_SIZE];
-	char active_char[256];
+	uint8_t active_char[256];
 	struct stack work_bufs[2];
 	unsigned int ext_flags;
 	size_t max_nesting;
@@ -163,7 +163,7 @@
 }
 
 static unsigned int
-hash_link_ref(char *link_ref, size_t length)
+hash_link_ref(const uint8_t *link_ref, size_t length)
 {
 	size_t i;
 	unsigned int hash = 0;
@@ -174,31 +174,25 @@
 	return hash;
 }
 
-static void add_link_ref(
+static struct link_ref *
+add_link_ref(
 	struct link_ref **references,
-	char *name, size_t name_size,
-	char *link, size_t link_size,
-	char *title, size_t title_size)
+	const uint8_t *name, size_t name_size)
 {
 	struct link_ref *ref = calloc(1, sizeof(struct link_ref));
 
 	if (!ref)
-		return;
+		return NULL;
 
 	ref->id = hash_link_ref(name, name_size);
 	ref->next = references[ref->id % REF_TABLE_SIZE];
 
-	ref->link.data = link;
-	ref->link.size = link_size;
-
-	ref->title.data = title;
-	ref->title.size = title_size;
-
 	references[ref->id % REF_TABLE_SIZE] = ref;
+	return ref;
 }
 
 static struct link_ref *
-find_link_ref(struct link_ref **references, char *name, size_t length)
+find_link_ref(struct link_ref **references, uint8_t *name, size_t length)
 {
 	unsigned int hash = hash_link_ref(name, length);
 	struct link_ref *ref = NULL;
@@ -225,11 +219,32 @@
 		struct link_ref *next;
 
 		while (r) {
-			next = r->next; free(r); r = next;
+			next = r->next;
+			bufrelease(r->link);
+			bufrelease(r->title);
+			free(r);
+			r = next;
 		}
 	}
 }
 
+/*
+ * Check whether a char is a Markdown space.
+
+ * Right now we only consider spaces the actual
+ * space and a newline: tabs and carriage returns
+ * are filtered out during the preprocessing phase.
+ *
+ * If we wanted to actually be UTF-8 compliant, we
+ * should instead extract an Unicode codepoint from
+ * this character and check for space properties.
+ */
+static inline int
+_isspace(int c)
+{
+	return c == ' ' || c == '\n';
+}
+
 /****************************
  * INLINE PARSING FUNCTIONS *
  ****************************/
@@ -237,7 +252,7 @@
 /* is_mail_autolink • looks for the address part of a mail autolink and '>' */
 /* this is less strict than the original markdown e-mail address matching */
 static size_t
-is_mail_autolink(char *data, size_t size)
+is_mail_autolink(uint8_t *data, size_t size)
 {
 	size_t i = 0, nb = 0;
 
@@ -268,7 +283,7 @@
 
 /* tag_length • returns the length of the given tag, or 0 is it's not valid */
 static size_t
-tag_length(char *data, size_t size, enum mkd_autolink *autolink)
+tag_length(uint8_t *data, size_t size, enum mkd_autolink *autolink)
 {
 	size_t i, j;
 
@@ -311,7 +326,8 @@
 		while (i < size) {
 			if (data[i] == '\\') i += 2;
 			else if (data[i] == '>' || data[i] == '\'' ||
-					data[i] == '"' || isspace(data[i])) break;
+					data[i] == '"' || data[i] == ' ' || data[i] == '\n')
+					break;
 			else i++;
 		}
 
@@ -329,19 +345,19 @@
 
 /* parse_inline • parses inline markdown elements */
 static void
-parse_inline(struct buf *ob, struct sd_markdown *rndr, char *data, size_t size)
+parse_inline(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size)
 {
 	size_t i = 0, end = 0;
-	char action = 0;
-	struct buf work = { 0, 0, 0, 0, 0 };
+	uint8_t action = 0;
+	struct buf work = { 0, 0, 0, 0 };
 
 	if (rndr->work_bufs[BUFFER_SPAN].size +
-		rndr->work_bufs[BUFFER_BLOCK].size > (int)rndr->max_nesting)
+		rndr->work_bufs[BUFFER_BLOCK].size > rndr->max_nesting)
 		return;
 
 	while (i < size) {
 		/* copying inactive chars into the output */
-		while (end < size && (action = rndr->active_char[(unsigned char)data[end]]) == 0) {
+		while (end < size && (action = rndr->active_char[data[end]]) == 0) {
 			end++;
 		}
 
@@ -367,9 +383,9 @@
 	}
 }
 
-/* find_emph_char • looks for the next emph char, skipping other constructs */
+/* find_emph_char • looks for the next emph uint8_t, skipping other constructs */
 static size_t
-find_emph_char(char *data, size_t size, char c)
+find_emph_char(uint8_t *data, size_t size, uint8_t c)
 {
 	size_t i = 1;
 
@@ -414,7 +430,7 @@
 		/* skipping a link */
 		else if (data[i] == '[') {
 			size_t tmp_i = 0;
-			char cc;
+			uint8_t cc;
 
 			i++;
 			while (i < size && data[i] != ']') {
@@ -423,7 +439,7 @@
 			}
 
 			i++;
-			while (i < size && (data[i] == ' ' || data[i] == '\t' || data[i] == '\n'))
+			while (i < size && (data[i] == ' ' || data[i] == '\n'))
 				i++;
 
 			if (i >= size)
@@ -462,7 +478,7 @@
 /* parse_emph1 • parsing single emphase */
 /* closed by a symbol not preceded by whitespace and not followed by symbol */
 static size_t
-parse_emph1(struct buf *ob, struct sd_markdown *rndr, char *data, size_t size, char c)
+parse_emph1(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size, uint8_t c)
 {
 	size_t i = 0, len;
 	struct buf *work = 0;
@@ -479,10 +495,10 @@
 		i += len;
 		if (i >= size) return 0;
 
-		if (data[i] == c && !isspace(data[i - 1])) {
+		if (data[i] == c && !_isspace(data[i - 1])) {
 
 			if (rndr->ext_flags & MKDEXT_NO_INTRA_EMPHASIS) {
-				if (!(i + 1 == size || isspace(data[i + 1]) || ispunct(data[i + 1])))
+				if (!(i + 1 == size || _isspace(data[i + 1]) || ispunct(data[i + 1])))
 					continue;
 			}
 
@@ -499,9 +515,9 @@
 
 /* parse_emph2 • parsing single emphase */
 static size_t
-parse_emph2(struct buf *ob, struct sd_markdown *rndr, char *data, size_t size, char c)
+parse_emph2(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size, uint8_t c)
 {
-	int (*render_method)(struct buf *ob, struct buf *text, void *opaque);
+	int (*render_method)(struct buf *ob, const struct buf *text, void *opaque);
 	size_t i = 0, len;
 	struct buf *work = 0;
 	int r;
@@ -516,7 +532,7 @@
 		if (!len) return 0;
 		i += len;
 
-		if (i + 1 < size && data[i] == c && data[i + 1] == c && i && !isspace(data[i - 1])) {
+		if (i + 1 < size && data[i] == c && data[i + 1] == c && i && !_isspace(data[i - 1])) {
 			work = rndr_newbuf(rndr, BUFFER_SPAN);
 			parse_inline(work, rndr, data, i);
 			r = render_method(ob, work, rndr->opaque);
@@ -531,7 +547,7 @@
 /* parse_emph3 • parsing single emphase */
 /* finds the first closing tag, and delegates to the other emph */
 static size_t
-parse_emph3(struct buf *ob, struct sd_markdown *rndr, char *data, size_t size, char c)
+parse_emph3(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size, uint8_t c)
 {
 	size_t i = 0, len;
 	int r;
@@ -542,7 +558,7 @@
 		i += len;
 
 		/* skip whitespace preceded symbols */
-		if (data[i] != c || isspace(data[i - 1]))
+		if (data[i] != c || _isspace(data[i - 1]))
 			continue;
 
 		if (i + 2 < size && data[i + 1] == c && data[i + 2] == c && rndr->cb.triple_emphasis) {
@@ -572,29 +588,29 @@
 
 /* char_emphasis • single and double emphasis parsing */
 static size_t
-char_emphasis(struct buf *ob, struct sd_markdown *rndr, char *data, size_t offset, size_t size)
+char_emphasis(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size)
 {
-	char c = data[0];
+	uint8_t c = data[0];
 	size_t ret;
 
 	if (size > 2 && data[1] != c) {
 		/* whitespace cannot follow an opening emphasis;
 		 * strikethrough only takes two characters '~~' */
-		if (c == '~' || isspace(data[1]) || (ret = parse_emph1(ob, rndr, data + 1, size - 1, c)) == 0)
+		if (c == '~' || _isspace(data[1]) || (ret = parse_emph1(ob, rndr, data + 1, size - 1, c)) == 0)
 			return 0;
 
 		return ret + 1;
 	}
 
 	if (size > 3 && data[1] == c && data[2] != c) {
-		if (isspace(data[2]) || (ret = parse_emph2(ob, rndr, data + 2, size - 2, c)) == 0)
+		if (_isspace(data[2]) || (ret = parse_emph2(ob, rndr, data + 2, size - 2, c)) == 0)
 			return 0;
 
 		return ret + 2;
 	}
 
 	if (size > 4 && data[1] == c && data[2] == c && data[3] != c) {
-		if (c == '~' || isspace(data[3]) || (ret = parse_emph3(ob, rndr, data + 3, size - 3, c)) == 0)
+		if (c == '~' || _isspace(data[3]) || (ret = parse_emph3(ob, rndr, data + 3, size - 3, c)) == 0)
 			return 0;
 
 		return ret + 3;
@@ -606,7 +622,7 @@
 
 /* char_linebreak • '\n' preceded by two spaces (assuming linebreak != 0) */
 static size_t
-char_linebreak(struct buf *ob, struct sd_markdown *rndr, char *data, size_t offset, size_t size)
+char_linebreak(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size)
 {
 	if (offset < 2 || data[-1] != ' ' || data[-2] != ' ')
 		return 0;
@@ -621,7 +637,7 @@
 
 /* char_codespan • '`' parsing a code span (assuming codespan != 0) */
 static size_t
-char_codespan(struct buf *ob, struct sd_markdown *rndr, char *data, size_t offset, size_t size)
+char_codespan(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size)
 {
 	size_t end, nb = 0, i, f_begin, f_end;
 
@@ -641,16 +657,16 @@
 
 	/* trimming outside whitespaces */
 	f_begin = nb;
-	while (f_begin < end && (data[f_begin] == ' ' || data[f_begin] == '\t'))
+	while (f_begin < end && data[f_begin] == ' ')
 		f_begin++;
 
 	f_end = end - nb;
-	while (f_end > nb && (data[f_end-1] == ' ' || data[f_end-1] == '\t'))
+	while (f_end > nb && data[f_end-1] == ' ')
 		f_end--;
 
 	/* real code span */
 	if (f_begin < f_end) {
-		struct buf work = { data + f_begin, f_end - f_begin, 0, 0, 0 };
+		struct buf work = { data + f_begin, f_end - f_begin, 0, 0 };
 		if (!rndr->cb.codespan(ob, &work, rndr->opaque))
 			end = 0;
 	} else {
@@ -664,10 +680,10 @@
 
 /* char_escape • '\\' backslash escape */
 static size_t
-char_escape(struct buf *ob, struct sd_markdown *rndr, char *data, size_t offset, size_t size)
+char_escape(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size)
 {
 	static const char *escape_chars = "\\`*_{}[]()#+-.!:|&<>";
-	struct buf work = { 0, 0, 0, 0, 0 };
+	struct buf work = { 0, 0, 0, 0 };
 
 	if (size > 1) {
 		if (strchr(escape_chars, data[1]) == NULL)
@@ -687,10 +703,10 @@
 /* char_entity • '&' escaped when it doesn't belong to an entity */
 /* valid entities are assumed to be anything matching &#?[A-Za-z0-9]+; */
 static size_t
-char_entity(struct buf *ob, struct sd_markdown *rndr, char *data, size_t offset, size_t size)
+char_entity(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size)
 {
 	size_t end = 1;
-	struct buf work;
+	struct buf work = { 0, 0, 0, 0 };
 
 	if (end < size && data[end] == '#')
 		end++;
@@ -715,11 +731,11 @@
 
 /* char_langle_tag • '<' when tags or autolinks are allowed */
 static size_t
-char_langle_tag(struct buf *ob, struct sd_markdown *rndr, char *data, size_t offset, size_t size)
+char_langle_tag(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size)
 {
 	enum mkd_autolink altype = MKDA_NOT_AUTOLINK;
 	size_t end = tag_length(data, size, &altype);
-	struct buf work = { data, end, 0, 0, 0 };
+	struct buf work = { data, end, 0, 0 };
 	int ret = 0;
 
 	if (end > 2) {
@@ -740,7 +756,7 @@
 }
 
 static size_t
-char_autolink_www(struct buf *ob, struct sd_markdown *rndr, char *data, size_t offset, size_t size)
+char_autolink_www(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size)
 {
 	struct buf *link, *link_url;
 	size_t link_len, rewind;
@@ -765,7 +781,7 @@
 }
 
 static size_t
-char_autolink_email(struct buf *ob, struct sd_markdown *rndr, char *data, size_t offset, size_t size)
+char_autolink_email(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size)
 {
 	struct buf *link;
 	size_t link_len, rewind;
@@ -785,7 +801,7 @@
 }
 
 static size_t
-char_autolink_url(struct buf *ob, struct sd_markdown *rndr, char *data, size_t offset, size_t size)
+char_autolink_url(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size)
 {
 	struct buf *link;
 	size_t link_len, rewind;
@@ -806,7 +822,7 @@
 
 /* char_link • '[': parsing a link or an image */
 static size_t
-char_link(struct buf *ob, struct sd_markdown *rndr, char *data, size_t offset, size_t size)
+char_link(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size)
 {
 	int is_img = (offset && data[-1] == '!'), level;
 	size_t i = 1, txt_e, link_b = 0, link_e = 0, title_b = 0, title_e = 0;
@@ -847,7 +863,7 @@
 
 	/* skip any amount of whitespace or newline */
 	/* (this is much more laxist than original markdown syntax) */
-	while (i < size && isspace(data[i]))
+	while (i < size && _isspace(data[i]))
 		i++;
 
 	/* inline style link */
@@ -855,7 +871,7 @@
 		/* skipping initial whitespace */
 		i++;
 
-		while (i < size && isspace(data[i]))
+		while (i < size && _isspace(data[i]))
 			i++;
 
 		link_b = i;
@@ -885,7 +901,7 @@
 
 			/* skipping whitespaces after title */
 			title_e = i - 1;
-			while (title_e > title_b && isspace(data[title_e]))
+			while (title_e > title_b && _isspace(data[title_e]))
 				title_e--;
 
 			/* checking for closing quote presence */
@@ -896,7 +912,7 @@
 		}
 
 		/* remove whitespace at the end of the link */
-		while (link_e > link_b && isspace(data[link_e - 1]))
+		while (link_e > link_b && _isspace(data[link_e - 1]))
 			link_e--;
 
 		/* remove optional angle brackets around the link */
@@ -919,7 +935,7 @@
 
 	/* reference style link */
 	else if (i < size && data[i] == '[') {
-		struct buf id = { 0, 0, 0, 0, 0 };
+		struct buf id = { 0, 0, 0, 0 };
 		struct link_ref *lr;
 
 		/* looking for the id */
@@ -958,15 +974,14 @@
 			goto cleanup;
 
 		/* keeping link and title from link_ref */
-		link = &lr->link;
-		if (lr->title.size)
-			title = &lr->title;
+		link = lr->link;
+		title = lr->title;
 		i++;
 	}
 
 	/* shortcut reference style link */
 	else {
-		struct buf id = { 0, 0, 0, 0, 0 };
+		struct buf id = { 0, 0, 0, 0 };
 		struct link_ref *lr;
 
 		/* crafting the id */
@@ -994,9 +1009,8 @@
 			goto cleanup;
 
 		/* keeping link and title from link_ref */
-		link = &lr->link;
-		if (lr->title.size)
-			title = &lr->title;
+		link = lr->link;
+		title = lr->title;
 
 		/* rewinding the whitespace */
 		i = txt_e + 1;
@@ -1031,7 +1045,7 @@
 }
 
 static size_t
-char_superscript(struct buf *ob, struct sd_markdown *rndr, char *data, size_t offset, size_t size)
+char_superscript(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size)
 {
 	size_t sup_start, sup_len;
 	struct buf *sup;
@@ -1053,7 +1067,7 @@
 	} else {
 		sup_start = sup_len = 1;
 
-		while (sup_len < size && !isspace(data[sup_len]))
+		while (sup_len < size && !_isspace(data[sup_len]))
 			sup_len++;
 	}
 
@@ -1074,20 +1088,23 @@
 
 /* is_empty • returns the line length when it is empty, 0 otherwise */
 static size_t
-is_empty(char *data, size_t size)
+is_empty(uint8_t *data, size_t size)
 {
 	size_t i;
+
 	for (i = 0; i < size && data[i] != '\n'; i++)
-		if (data[i] != ' ' && data[i] != '\t') return 0;
+		if (data[i] != ' ')
+			return 0;
+
 	return i + 1;
 }
 
 /* is_hrule • returns whether a line is a horizontal rule */
 static int
-is_hrule(char *data, size_t size)
+is_hrule(uint8_t *data, size_t size)
 {
 	size_t i = 0, n = 0;
-	char c;
+	uint8_t c;
 
 	/* skipping initial spaces */
 	if (size < 3) return 0;
@@ -1095,7 +1112,7 @@
 	if (data[1] == ' ') { i++;
 	if (data[2] == ' ') { i++; } } }
 
-	/* looking at the hrule char */
+	/* looking at the hrule uint8_t */
 	if (i + 2 >= size
 	|| (data[i] != '*' && data[i] != '-' && data[i] != '_'))
 		return 0;
@@ -1104,19 +1121,21 @@
 	/* the whole line must be the char or whitespace */
 	while (i < size && data[i] != '\n') {
 		if (data[i] == c) n++;
-		else if (data[i] != ' ' && data[i] != '\t')
+		else if (data[i] != ' ')
 			return 0;
-		i++; }
 
+		i++;
+	}
+
 	return n >= 3;
 }
 
 /* check if a line is a code fence; return its size if it is */
 static size_t
-is_codefence(char *data, size_t size, struct buf *syntax)
+is_codefence(uint8_t *data, size_t size, struct buf *syntax)
 {
 	size_t i = 0, n = 0;
-	char c;
+	uint8_t c;
 
 	/* skipping initial spaces */
 	if (size < 3) return 0;
@@ -1124,13 +1143,13 @@
 	if (data[1] == ' ') { i++;
 	if (data[2] == ' ') { i++; } } }
 
-	/* looking at the hrule char */
+	/* looking at the hrule uint8_t */
 	if (i + 2 >= size || !(data[i] == '~' || data[i] == '`'))
 		return 0;
 
 	c = data[i];
 
-	/* the whole line must be the char or whitespace */
+	/* the whole line must be the uint8_t or whitespace */
 	while (i < size && data[i] == c) {
 		n++; i++;
 	}
@@ -1141,7 +1160,7 @@
 	if (syntax != NULL) {
 		size_t syn = 0;
 
-		while (i < size && (data[i] == ' ' || data[i] == '\t'))
+		while (i < size && data[i] == ' ')
 			i++;
 
 		syntax->data = data + i;
@@ -1158,16 +1177,16 @@
 
 			/* strip all whitespace at the beginning and the end
 			 * of the {} block */
-			while (syn > 0 && isspace(syntax->data[0])) {
+			while (syn > 0 && _isspace(syntax->data[0])) {
 				syntax->data++; syn--;
 			}
 
-			while (syn > 0 && isspace(syntax->data[syn - 1]))
+			while (syn > 0 && _isspace(syntax->data[syn - 1]))
 				syn--;
 
 			i++;
 		} else {
-			while (i < size && !isspace(data[i])) {
+			while (i < size && !_isspace(data[i])) {
 				syn++; i++;
 			}
 		}
@@ -1176,7 +1195,7 @@
 	}
 
 	while (i < size && data[i] != '\n') {
-		if (!isspace(data[i]))
+		if (!_isspace(data[i]))
 			return 0;
 
 		i++;
@@ -1187,7 +1206,7 @@
 
 /* is_atxheader • returns whether the line is a hash-prefixed header */
 static int
-is_atxheader(struct sd_markdown *rndr, char *data, size_t size)
+is_atxheader(struct sd_markdown *rndr, uint8_t *data, size_t size)
 {
 	if (data[0] != '#')
 		return 0;
@@ -1198,7 +1217,7 @@
 		while (level < size && level < 6 && data[level] == '#')
 			level++;
 
-		if (level < size && data[level] != ' ' && data[level] != '\t')
+		if (level < size && data[level] != ' ')
 			return 0;
 	}
 
@@ -1207,30 +1226,30 @@
 
 /* is_headerline • returns whether the line is a setext-style hdr underline */
 static int
-is_headerline(char *data, size_t size)
+is_headerline(uint8_t *data, size_t size)
 {
 	size_t i = 0;
 
 	/* test of level 1 header */
 	if (data[i] == '=') {
 		for (i = 1; i < size && data[i] == '='; i++);
-		while (i < size && (data[i] == ' ' || data[i] == '\t')) i++;
+		while (i < size && data[i] == ' ') i++;
 		return (i >= size || data[i] == '\n') ? 1 : 0; }
 
 	/* test of level 2 header */
 	if (data[i] == '-') {
 		for (i = 1; i < size && data[i] == '-'; i++);
-		while (i < size && (data[i] == ' ' || data[i] == '\t')) i++;
+		while (i < size && data[i] == ' ') i++;
 		return (i >= size || data[i] == '\n') ? 2 : 0; }
 
 	return 0;
 }
 
 static int
-is_next_headerline(char *data, size_t size)
+is_next_headerline(uint8_t *data, size_t size)
 {
 	size_t i = 0;
-	
+
 	while (i < size && data[i] != '\n')
 		i++;
 
@@ -1242,32 +1261,36 @@
 
 /* prefix_quote • returns blockquote prefix length */
 static size_t
-prefix_quote(char *data, size_t size)
+prefix_quote(uint8_t *data, size_t size)
 {
 	size_t i = 0;
 	if (i < size && data[i] == ' ') i++;
 	if (i < size && data[i] == ' ') i++;
 	if (i < size && data[i] == ' ') i++;
+
 	if (i < size && data[i] == '>') {
-		if (i + 1 < size && (data[i + 1] == ' ' || data[i+1] == '\t'))
+		if (i + 1 < size && data[i + 1] == ' ')
 			return i + 2;
-		else return i + 1; }
-	else return 0;
+
+		return i + 1;
+	}
+
+	return 0;
 }
 
 /* prefix_code • returns prefix length for block code*/
 static size_t
-prefix_code(char *data, size_t size)
+prefix_code(uint8_t *data, size_t size)
 {
-	if (size > 0 && data[0] == '\t') return 1;
 	if (size > 3 && data[0] == ' ' && data[1] == ' '
-			&& data[2] == ' ' && data[3] == ' ') return 4;
+		&& data[2] == ' ' && data[3] == ' ') return 4;
+
 	return 0;
 }
 
 /* prefix_oli • returns ordered list item prefix */
 static size_t
-prefix_oli(char *data, size_t size)
+prefix_oli(uint8_t *data, size_t size)
 {
 	size_t i = 0;
 
@@ -1281,9 +1304,7 @@
 	while (i < size && data[i] >= '0' && data[i] <= '9')
 		i++;
 
-	if (i + 1 >= size ||
-		data[i] != '.' ||
-		(data[i + 1] != ' ' && data[i + 1] != '\t'))
+	if (i + 1 >= size || data[i] != '.' || data[i + 1] != ' ')
 		return 0;
 
 	if (is_next_headerline(data + i, size - i))
@@ -1294,7 +1315,7 @@
 
 /* prefix_uli • returns ordered list item prefix */
 static size_t
-prefix_uli(char *data, size_t size)
+prefix_uli(uint8_t *data, size_t size)
 {
 	size_t i = 0;
 
@@ -1302,9 +1323,9 @@
 	if (i < size && data[i] == ' ') i++;
 	if (i < size && data[i] == ' ') i++;
 
-	if (i + 1 >= size || 
+	if (i + 1 >= size ||
 		(data[i] != '*' && data[i] != '+' && data[i] != '-') ||
-		(data[i + 1] != ' ' && data[i + 1] != '\t'))
+		data[i + 1] != ' ')
 		return 0;
 
 	if (is_next_headerline(data + i, size - i))
@@ -1314,17 +1335,17 @@
 }
 
 
-/* parse_block • parsing of one block, returning next char to parse */
+/* parse_block • parsing of one block, returning next uint8_t to parse */
 static void parse_block(struct buf *ob, struct sd_markdown *rndr,
-			char *data, size_t size);
+			uint8_t *data, size_t size);
 
 
 /* parse_blockquote • handles parsing of a blockquote fragment */
 static size_t
-parse_blockquote(struct buf *ob, struct sd_markdown *rndr, char *data, size_t size)
+parse_blockquote(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size)
 {
 	size_t beg, end = 0, pre, work_size = 0;
-	char *work_data = 0;
+	uint8_t *work_data = 0;
 	struct buf *out = 0;
 
 	out = rndr_newbuf(rndr, BUFFER_BLOCK);
@@ -1362,15 +1383,15 @@
 }
 
 static size_t
-parse_htmlblock(struct buf *ob, struct sd_markdown *rndr, char *data, size_t size, int do_render);
+parse_htmlblock(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size, int do_render);
 
 /* parse_blockquote • handles parsing of a regular paragraph */
 static size_t
-parse_paragraph(struct buf *ob, struct sd_markdown *rndr, char *data, size_t size)
+parse_paragraph(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size)
 {
 	size_t i = 0, end = 0;
 	int level = 0;
-	struct buf work = { data, 0, 0, 0, 0 }; /* volatile working buffer */
+	struct buf work = { data, 0, 0, 0 };
 
 	while (i < size) {
 		for (end = i + 1; end < size && data[end - 1] != '\n'; end++) /* empty */;
@@ -1446,11 +1467,11 @@
 
 /* parse_fencedcode • handles parsing of a block-level code fragment */
 static size_t
-parse_fencedcode(struct buf *ob, struct sd_markdown *rndr, char *data, size_t size)
+parse_fencedcode(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size)
 {
 	size_t beg, end;
 	struct buf *work = 0;
-	struct buf lang = { 0, 0, 0, 0, 0 };
+	struct buf lang = { 0, 0, 0, 0 };
 
 	beg = is_codefence(data, size, &lang);
 	if (beg == 0) return 0;
@@ -1489,7 +1510,7 @@
 }
 
 static size_t
-parse_blockcode(struct buf *ob, struct sd_markdown *rndr, char *data, size_t size)
+parse_blockcode(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size)
 {
 	size_t beg, end, pre;
 	struct buf *work = 0;
@@ -1532,7 +1553,7 @@
 /* parse_listitem • parsing of a single list item */
 /*	assuming initial prefix is already removed */
 static size_t
-parse_listitem(struct buf *ob, struct sd_markdown *rndr, char *data, size_t size, int *flags)
+parse_listitem(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size, int *flags)
 {
 	struct buf *work = 0, *inter = 0;
 	size_t beg = 0, end, pre, sublist = 0, orgpre = 0, i;
@@ -1582,7 +1603,6 @@
 			i++;
 
 		pre = i;
-		if (data[beg] == '\t') { i = 1; pre = 8; }
 
 		/* checking for a new item */
 		if ((prefix_uli(data + beg + i, end - beg - i) &&
@@ -1598,7 +1618,7 @@
 				sublist = work->size;
 		}
 		/* joining only indented stuff after empty lines */
-		else if (in_empty && i < 4 && data[beg] != '\t') {
+		else if (in_empty && i < 4) {
 				*flags |= MKD_LI_END;
 				break;
 		}
@@ -1648,7 +1668,7 @@
 
 /* parse_list • parsing ordered or unordered list block */
 static size_t
-parse_list(struct buf *ob, struct sd_markdown *rndr, char *data, size_t size, int flags)
+parse_list(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size, int flags)
 {
 	struct buf *work = 0;
 	size_t i = 0, j;
@@ -1671,7 +1691,7 @@
 
 /* parse_atxheader • parsing of atx-style headers */
 static size_t
-parse_atxheader(struct buf *ob, struct sd_markdown *rndr, char *data, size_t size)
+parse_atxheader(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size)
 {
 	size_t level = 0;
 	size_t i, end, skip;
@@ -1679,7 +1699,7 @@
 	while (level < size && level < 6 && data[level] == '#')
 		level++;
 
-	for (i = level; i < size && (data[i] == ' ' || data[i] == '\t'); i++);
+	for (i = level; i < size && data[i] == ' '; i++);
 
 	for (end = i; end < size && data[end] != '\n'; end++);
 	skip = end;
@@ -1687,7 +1707,7 @@
 	while (end && data[end - 1] == '#')
 		end--;
 
-	while (end && (data[end - 1] == ' ' || data[end - 1] == '\t'))
+	while (end && data[end - 1] == ' ')
 		end--;
 
 	if (end > i) {
@@ -1708,13 +1728,13 @@
 /* htmlblock_end • checking end of HTML block : </tag>[ \t]*\n[ \t*]\n */
 /*	returns the length on match, 0 otherwise */
 static size_t
-htmlblock_end(const char *tag, size_t tag_len, struct sd_markdown *rndr, char *data, size_t size)
+htmlblock_end(const char *tag, size_t tag_len, struct sd_markdown *rndr, uint8_t *data, size_t size)
 {
 	size_t i, w;
 
 	/* checking if tag is a match */
 	if (tag_len + 3 >= size ||
-		strncasecmp(data + 2, tag, tag_len) != 0 ||
+		strncasecmp((char *)data + 2, tag, tag_len) != 0 ||
 		data[tag_len + 2] != '>')
 		return 0;
 
@@ -1740,17 +1760,24 @@
 
 /* parse_htmlblock • parsing of inline HTML block */
 static size_t
-parse_htmlblock(struct buf *ob, struct sd_markdown *rndr, char *data, size_t size, int do_render)
+parse_htmlblock(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size, int do_render)
 {
 	size_t i, j = 0;
-	const char *curtag;
+	const char *curtag = NULL;
 	int found;
-	struct buf work = { data, 0, 0, 0, 0 };
+	struct buf work = { data, 0, 0, 0 };
 
 	/* identification of the opening tag */
-	if (size < 2 || data[0] != '<') return 0;
-	curtag = find_block_tag(data + 1, size - 1);
+	if (size < 2 || data[0] != '<')
+		return 0;
 
+	i = 1;
+	while (i < size && data[i] != '>' && data[i] != ' ')
+		i++;
+
+	if (i < size && data[i] == '>')
+		curtag = find_block_tag((char *)data + 1, i - 1);
+
 	/* handling of special cases */
 	if (!curtag) {
 
@@ -1835,7 +1862,7 @@
 }
 
 static void
-parse_table_row(struct buf *ob, struct sd_markdown *rndr, char *data, size_t size, size_t columns, int *col_data)
+parse_table_row(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size, size_t columns, int *col_data)
 {
 	size_t i = 0, col;
 	struct buf *row_work = 0;
@@ -1854,7 +1881,7 @@
 
 		cell_work = rndr_newbuf(rndr, BUFFER_SPAN);
 
-		while (i < size && isspace(data[i]))
+		while (i < size && _isspace(data[i]))
 			i++;
 
 		cell_start = i;
@@ -1864,7 +1891,7 @@
 
 		cell_end = i - 1;
 
-		while (cell_end > cell_start && isspace(data[cell_end]))
+		while (cell_end > cell_start && _isspace(data[cell_end]))
 			cell_end--;
 
 		parse_inline(cell_work, rndr, data + cell_start, 1 + cell_end - cell_start);
@@ -1875,7 +1902,7 @@
 	}
 
 	for (; col < columns; ++col) {
-		struct buf empty_cell = {0, 0, 0, 0, 0};
+		struct buf empty_cell = { 0, 0, 0, 0 };
 		rndr->cb.table_cell(row_work, &empty_cell, col_data[col], rndr->opaque);
 	}
 
@@ -1885,7 +1912,7 @@
 }
 
 static size_t
-parse_table_header(struct buf *ob, struct sd_markdown *rndr, char *data, size_t size, size_t *columns, int **column_data)
+parse_table_header(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size, size_t *columns, int **column_data)
 {
 	int pipes;
 	size_t i = 0, col, header_end, under_end;
@@ -1923,7 +1950,7 @@
 
 		(*column_data)[col] |= MKD_TABLE_HEADER;
 
-		while (i < under_end && (data[i] == ' ' || data[i] == '\t'))
+		while (i < under_end && data[i] == ' ')
 			i++;
 
 		if (data[i] == ':') {
@@ -1940,7 +1967,7 @@
 			dashes++;
 		}
 
-		while (i < under_end && (data[i] == ' ' || data[i] == '\t'))
+		while (i < under_end && data[i] == ' ')
 			i++;
 
 		if (i < under_end && data[i] != '|')
@@ -1960,7 +1987,7 @@
 }
 
 static size_t
-parse_table(struct buf *ob, struct sd_markdown *rndr, char *data, size_t size)
+parse_table(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size)
 {
 	size_t i;
 
@@ -2005,16 +2032,16 @@
 	return i;
 }
 
-/* parse_block • parsing of one block, returning next char to parse */
+/* parse_block • parsing of one block, returning next uint8_t to parse */
 static void
-parse_block(struct buf *ob, struct sd_markdown *rndr, char *data, size_t size)
+parse_block(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size)
 {
 	size_t beg, end, i;
-	char *txt_data;
+	uint8_t *txt_data;
 	beg = 0;
 
 	if (rndr->work_bufs[BUFFER_SPAN].size +
-		rndr->work_bufs[BUFFER_BLOCK].size > (int)rndr->max_nesting)
+		rndr->work_bufs[BUFFER_BLOCK].size > rndr->max_nesting)
 		return;
 
 	while (beg < size) {
@@ -2074,7 +2101,7 @@
 
 /* is_ref • returns whether a line is a reference or not */
 static int
-is_ref(char *data, size_t beg, size_t end, size_t *last, struct link_ref **refs)
+is_ref(const uint8_t *data, size_t beg, size_t end, size_t *last, struct link_ref **refs)
 {
 /*	int n; */
 	size_t i = 0;
@@ -2104,23 +2131,27 @@
 	i++;
 	if (i >= end || data[i] != ':') return 0;
 	i++;
-	while (i < end && (data[i] == ' ' || data[i] == '\t')) i++;
+	while (i < end && data[i] == ' ') i++;
 	if (i < end && (data[i] == '\n' || data[i] == '\r')) {
 		i++;
 		if (i < end && data[i] == '\r' && data[i - 1] == '\n') i++; }
-	while (i < end && (data[i] == ' ' || data[i] == '\t')) i++;
+	while (i < end && data[i] == ' ') i++;
 	if (i >= end) return 0;
 
 	/* link: whitespace-free sequence, optionally between angle brackets */
-	if (data[i] == '<') i++;
+	if (data[i] == '<')
+		i++;
+
 	link_offset = i;
-	while (i < end && data[i] != ' ' && data[i] != '\t'
-			&& data[i] != '\n' && data[i] != '\r') i++;
+
+	while (i < end && data[i] != ' ' && data[i] != '\n' && data[i] != '\r')
+		i++;
+
 	if (data[i - 1] == '>') link_end = i - 1;
 	else link_end = i;
 
 	/* optional spacer: (space | tab)* (newline | '\'' | '"' | '(' ) */
-	while (i < end && (data[i] == ' ' || data[i] == '\t')) i++;
+	while (i < end && data[i] == ' ') i++;
 	if (i < end && data[i] != '\n' && data[i] != '\r'
 			&& data[i] != '\'' && data[i] != '"' && data[i] != '(')
 		return 0;
@@ -2133,7 +2164,7 @@
 	/* optional (space|tab)* spacer after a newline */
 	if (line_end) {
 		i = line_end + 1;
-		while (i < end && (data[i] == ' ' || data[i] == '\t')) i++; }
+		while (i < end && data[i] == ' ') i++; }
 
 	/* optional title: any non-newline sequence enclosed in '"()
 					alone on its line */
@@ -2149,7 +2180,7 @@
 		else	title_end = i;
 		/* stepping back */
 		i -= 1;
-		while (i > title_offset && (data[i] == ' ' || data[i] == '\t'))
+		while (i > title_offset && data[i] == ' ')
 			i -= 1;
 		if (i > title_offset
 		&& (data[i] == '\'' || data[i] == '"' || data[i] == ')')) {
@@ -2164,27 +2195,23 @@
 		*last = line_end;
 
 	if (refs) {
-		char *title_str = NULL;
-		size_t title_size = 0;
+		struct link_ref *ref;
 
+		ref = add_link_ref(refs, data + id_offset, id_end - id_offset);
+
+		ref->link = bufnew(link_end - link_offset);
+		bufput(ref->link, data + link_offset, link_end - link_offset);
+
 		if (title_end > title_offset) {
-			title_str = data + title_offset;
-			title_size = title_end - title_offset;
+			ref->title = bufnew(title_end - title_offset);
+			bufput(ref->title, data + title_offset, title_end - title_offset);
 		}
-
-		add_link_ref( refs,
-			data + id_offset, /* identifier */
-			id_end - id_offset,
-			data + link_offset, /* link url */
-			link_end - link_offset,
-			title_str, /* title (optional) */
-			title_size);
 	}
 
 	return 1;
 }
 
-static void expand_tabs(struct buf *ob, const char *line, size_t size)
+static void expand_tabs(struct buf *ob, const uint8_t *line, size_t size)
 {
 	size_t  i = 0, tab = 0;
 
@@ -2273,7 +2300,7 @@
 }
 
 void
-sd_markdown_render(struct buf *ob, const struct buf *ib, struct sd_markdown *md)
+sd_markdown_render(struct buf *ob, const uint8_t *document, size_t doc_size, struct sd_markdown *md)
 {
 	static const float MARKDOWN_GROW_FACTOR = 1.4f;
 
@@ -2285,28 +2312,28 @@
 		return;
 
 	/* Preallocate enough space for our buffer to avoid expanding while copying */
-	bufgrow(text, ib->size);
+	bufgrow(text, doc_size);
 
 	/* reset the references table */
 	memset(&md->refs, 0x0, REF_TABLE_SIZE * sizeof(void *));
 
 	/* first pass: looking for references, copying everything else */
 	beg = 0;
-	while (beg < ib->size) /* iterating over lines */
-		if (is_ref(ib->data, beg, ib->size, &end, md->refs))
+	while (beg < doc_size) /* iterating over lines */
+		if (is_ref(document, beg, doc_size, &end, md->refs))
 			beg = end;
 		else { /* skipping to the next line */
 			end = beg;
-			while (end < ib->size && ib->data[end] != '\n' && ib->data[end] != '\r')
+			while (end < doc_size && document[end] != '\n' && document[end] != '\r')
 				end++;
 
 			/* adding the line body if present */
 			if (end > beg)
-				expand_tabs(text, ib->data + beg, end - beg);
+				expand_tabs(text, document + beg, end - beg);
 
-			while (end < ib->size && (ib->data[end] == '\n' || ib->data[end] == '\r')) {
+			while (end < doc_size && (document[end] == '\n' || document[end] == '\r')) {
 				/* add one \n per newline */
-				if (ib->data[end] == '\n' || (end + 1 < ib->size && ib->data[end + 1] != '\n'))
+				if (document[end] == '\n' || (end + 1 < doc_size && document[end + 1] != '\n'))
 					bufputc(text, '\n');
 				end++;
 			}
diff --git a/cbits/markdown.h b/cbits/markdown.h
--- a/cbits/markdown.h
+++ b/cbits/markdown.h
@@ -60,35 +60,35 @@
 /* sd_callbacks - functions for rendering parsed data */
 struct sd_callbacks {
 	/* block level callbacks - NULL skips the block */
-	void (*blockcode)(struct buf *ob, struct buf *text, struct buf *lang, void *opaque);
-	void (*blockquote)(struct buf *ob, struct buf *text, void *opaque);
-	void (*blockhtml)(struct buf *ob, struct buf *text, void *opaque);
-	void (*header)(struct buf *ob, struct buf *text, int level, void *opaque);
+	void (*blockcode)(struct buf *ob, const struct buf *text, const struct buf *lang, void *opaque);
+	void (*blockquote)(struct buf *ob, const struct buf *text, void *opaque);
+	void (*blockhtml)(struct buf *ob,const  struct buf *text, void *opaque);
+	void (*header)(struct buf *ob, const struct buf *text, int level, void *opaque);
 	void (*hrule)(struct buf *ob, void *opaque);
-	void (*list)(struct buf *ob, struct buf *text, int flags, void *opaque);
-	void (*listitem)(struct buf *ob, struct buf *text, int flags, void *opaque);
-	void (*paragraph)(struct buf *ob, struct buf *text, void *opaque);
-	void (*table)(struct buf *ob, struct buf *header, struct buf *body, void *opaque);
-	void (*table_row)(struct buf *ob, struct buf *text, void *opaque);
-	void (*table_cell)(struct buf *ob, struct buf *text, int flags, void *opaque);
+	void (*list)(struct buf *ob, const struct buf *text, int flags, void *opaque);
+	void (*listitem)(struct buf *ob, const struct buf *text, int flags, void *opaque);
+	void (*paragraph)(struct buf *ob, const struct buf *text, void *opaque);
+	void (*table)(struct buf *ob, const struct buf *header, const struct buf *body, void *opaque);
+	void (*table_row)(struct buf *ob, const struct buf *text, void *opaque);
+	void (*table_cell)(struct buf *ob, const struct buf *text, int flags, void *opaque);
 
 
 	/* span level callbacks - NULL or return 0 prints the span verbatim */
-	int (*autolink)(struct buf *ob, struct buf *link, enum mkd_autolink type, void *opaque);
-	int (*codespan)(struct buf *ob, struct buf *text, void *opaque);
-	int (*double_emphasis)(struct buf *ob, struct buf *text, void *opaque);
-	int (*emphasis)(struct buf *ob, struct buf *text, void *opaque);
-	int (*image)(struct buf *ob, struct buf *link, struct buf *title, struct buf *alt, void *opaque);
+	int (*autolink)(struct buf *ob, const struct buf *link, enum mkd_autolink type, void *opaque);
+	int (*codespan)(struct buf *ob, const struct buf *text, void *opaque);
+	int (*double_emphasis)(struct buf *ob, const struct buf *text, void *opaque);
+	int (*emphasis)(struct buf *ob, const struct buf *text, void *opaque);
+	int (*image)(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *alt, void *opaque);
 	int (*linebreak)(struct buf *ob, void *opaque);
-	int (*link)(struct buf *ob, struct buf *link, struct buf *title, struct buf *content, void *opaque);
-	int (*raw_html_tag)(struct buf *ob, struct buf *tag, void *opaque);
-	int (*triple_emphasis)(struct buf *ob, struct buf *text, void *opaque);
-	int (*strikethrough)(struct buf *ob, struct buf *text, void *opaque);
-	int (*superscript)(struct buf *ob, struct buf *text, void *opaque);
+	int (*link)(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque);
+	int (*raw_html_tag)(struct buf *ob, const struct buf *tag, void *opaque);
+	int (*triple_emphasis)(struct buf *ob, const struct buf *text, void *opaque);
+	int (*strikethrough)(struct buf *ob, const struct buf *text, void *opaque);
+	int (*superscript)(struct buf *ob, const struct buf *text, void *opaque);
 
 	/* low level callbacks - NULL copies input directly into the output */
-	void (*entity)(struct buf *ob, struct buf *entity, void *opaque);
-	void (*normal_text)(struct buf *ob, struct buf *text, void *opaque);
+	void (*entity)(struct buf *ob, const struct buf *entity, void *opaque);
+	void (*normal_text)(struct buf *ob, const struct buf *text, void *opaque);
 
 	/* header and footer */
 	void (*doc_header)(struct buf *ob, void *opaque);
@@ -114,10 +114,10 @@
 	unsigned int extensions,
 	size_t max_nesting,
 	const struct sd_callbacks *callbacks,
-	void *opaque); 
+	void *opaque);
 
 extern void
-sd_markdown_render(struct buf *ob, const struct buf *ib, struct sd_markdown *md);
+sd_markdown_render(struct buf *ob, const uint8_t *document, size_t doc_size, struct sd_markdown *md);
 
 extern void
 sd_markdown_free(struct sd_markdown *md);
diff --git a/cbits/stack.c b/cbits/stack.c
--- a/cbits/stack.c
+++ b/cbits/stack.c
@@ -57,7 +57,7 @@
 	if (!st->size)
 		return NULL;
 
-	return st->item[st->size--];
+	return st->item[--st->size];
 }
 
 int
diff --git a/src/Text/Sundown/Buffer/Foreign.hsc b/src/Text/Sundown/Buffer/Foreign.hsc
--- a/src/Text/Sundown/Buffer/Foreign.hsc
+++ b/src/Text/Sundown/Buffer/Foreign.hsc
@@ -2,25 +2,25 @@
 
 module Text.Sundown.Buffer.Foreign
        ( Buffer (..)
+       , getBufferData
        , c_bufnew
        , c_bufputs
        , c_bufgrow
        , c_bufrelease
        ) where
 
-import Foreign
-import Foreign.C.Types
-import Foreign.C.String
 import Data.ByteString         (ByteString)
 import qualified Data.ByteString as BS
+import Foreign
+import Foreign.C.String
+import Foreign.C.Types
 
 #include "buffer.h"
 
-data Buffer = Buffer { bufData  :: ByteString
-                     , bufSize  :: Int
-                     , bufASize :: Int
-                     , bufUnit  :: Int
-                     , bufRef   :: Int
+data Buffer = Buffer { bufData  :: CString
+                     , bufSize  :: CSize
+                     , bufASize :: CSize
+                     , bufUnit  :: CSize
                      }
 
 instance Storable Buffer where
@@ -29,14 +29,14 @@
   peek ptr = do
     d  <- (#peek struct buf, data) ptr
     s  <- (#peek struct buf, size) ptr
-    dbs <- if d == nullPtr
-           then return $ BS.pack [0]
-           else BS.packCStringLen (d, s)
     as <- (#peek struct buf, asize) ptr
     u  <- (#peek struct buf, unit) ptr
-    r  <- (#peek struct buf, ref) ptr
-    return $ Buffer dbs s as u r
+    return $ Buffer d s as u
   poke _ _ = error "Buffer.poke not implemented."
+
+getBufferData :: Buffer -> IO ByteString
+getBufferData (Buffer d s _ _) | d == nullPtr = return $ BS.pack [0]
+                               | otherwise = BS.packCStringLen (d, fromIntegral s)
 
 foreign import ccall "buffer.h bufnew"
   c_bufnew :: CSize -> IO (Ptr Buffer)
diff --git a/src/Text/Sundown/Markdown.hs b/src/Text/Sundown/Markdown.hs
--- a/src/Text/Sundown/Markdown.hs
+++ b/src/Text/Sundown/Markdown.hs
@@ -8,8 +8,8 @@
 
 -- | All 'Extensions' disabled
 noExtensions :: Extensions
-noExtensions = Extensions False False False False False False 
+noExtensions = Extensions False False False False False False False False
 
 -- | All 'Extensions' enabled
 allExtensions :: Extensions
-allExtensions = Extensions True True True True True True
+allExtensions = Extensions True True True True True True True True
diff --git a/src/Text/Sundown/Markdown/Foreign.hsc b/src/Text/Sundown/Markdown/Foreign.hsc
--- a/src/Text/Sundown/Markdown/Foreign.hsc
+++ b/src/Text/Sundown/Markdown/Foreign.hsc
@@ -9,20 +9,29 @@
        ) where
 
 import Foreign
+import Foreign.C.String
 import Foreign.C.Types
-
 import Text.Sundown.Buffer.Foreign
 import Text.Sundown.Flag
 
 #include "markdown.h"
 
 -- | A set of switches to enable or disable markdown features.
-data Extensions = Extensions { extNoIntraEmphasis :: Bool -- ^ Turn off underscores insode a word does designating emphasis.
-                             , extTables          :: Bool
-                             , extFencedCode      :: Bool -- ^ Turns on a non-indentation form of code-blocks, by blocking off a region with ~ or \`.
-                             , extAutolink        :: Bool -- ^ Turn things that look like URLs and email addresses into links
-                             , extStrikethrough   :: Bool -- ^ Surround text with `~` to designate it as struck through
-                             , extLaxHtmlBlocks   :: Bool -- ^ Allow HTML markup inside of paragraphs, instead requireing tags to be on separate lines
+data Extensions = Extensions { extNoIntraEmphasis :: Bool -- ^ Turn off underscores insode a word
+                                                         --   does designating emphasis.
+                             , extTables :: Bool
+                             , extFencedCode :: Bool -- ^ Turns on a non-indentation form of
+                                                    -- code-blocks, by blocking off a regionwith ~
+                                                    -- or \`.
+                             , extAutolink :: Bool -- ^ Turn things that look like URLs and email
+                                                  -- addresses into links
+                             , extStrikethrough :: Bool -- ^ Surround text with `~` to designate it
+                                                       -- as struck through
+                             , extLaxHtmlBlocks :: Bool -- ^ Allow HTML markup inside of paragraphs,
+                                                       -- instead requireing tags to be on separate
+                                                       -- lines
+                             , extSpaceHeaders :: Bool
+                             , extSuperscript :: Bool
                              }
 
 instance Flag Extensions where
@@ -32,6 +41,8 @@
                      , (3, extAutolink exts)
                      , (4, extStrikethrough exts)
                      , (5, extLaxHtmlBlocks exts)
+                     , (6, extSpaceHeaders exts)
+                     , (7, extSuperscript exts)
                      ]
 
 
@@ -58,7 +69,7 @@
   c_sd_markdown_new' :: CUInt -> CSize -> Ptr Callbacks -> Ptr () -> IO (Ptr Markdown)
 
 foreign import ccall "markdown.h sd_markdown_render"
-  c_sd_markdown_render :: Ptr Buffer -> Ptr Buffer -> Ptr Markdown -> IO ()
+  c_sd_markdown_render :: Ptr Buffer -> CString -> CSize -> Ptr Markdown -> IO ()
 
 foreign import ccall "markdown.h sd_markdown_free"
   c_sd_markdown_free :: Ptr Markdown -> IO ()
diff --git a/src/Text/Sundown/Renderers/Html.hs b/src/Text/Sundown/Renderers/Html.hs
--- a/src/Text/Sundown/Renderers/Html.hs
+++ b/src/Text/Sundown/Renderers/Html.hs
@@ -44,13 +44,17 @@
     
     -- Do the markdown
     c_sdhtml_renderer callbacks options mode
+    
     let maxNesting = fromIntegral $ fromMaybe defaultMaxNesting maxNestingM
     markdown <- c_sd_markdown_new exts maxNesting callbacks (castPtr options)
-    c_sd_markdown_render ob ib markdown
+
+    Buffer {bufData = cs, bufSize = size} <- peek ib
+    c_sd_markdown_render ob cs size markdown
+
     c_sd_markdown_free markdown
     
     -- Get the result
-    Buffer {bufData = output} <- peek ob
+    output <- peek ob >>= getBufferData
     
     c_bufrelease ib
     c_bufrelease ob    
@@ -59,11 +63,11 @@
 
 -- | All the 'HtmlRenderMode' disabled
 noHtmlModes :: HtmlRenderMode
-noHtmlModes = HtmlRenderMode False False False False False False False False False False
+noHtmlModes = HtmlRenderMode False False False False False False False False False
 
 -- | All the 'HtmlRenderMode' enabled
 allHtmlModes :: HtmlRenderMode
-allHtmlModes = HtmlRenderMode True True True True True True True True True True
+allHtmlModes = HtmlRenderMode True True True True True True True True True
 
 -- | Converts punctuation in Html entities,
 -- <http://daringfireball.net/projects/smartypants/>
@@ -75,10 +79,11 @@
     ib <- c_bufnew $ fromInteger . toInteger $ BS.length input
     
     c_bufputs ib input
-    
-    c_sdhtml_smartypants ob ib
+
+    Buffer {bufData = cs, bufSize = size} <- peek ib
+    c_sdhtml_smartypants ob cs size
     
-    Buffer {bufData = output} <- peek ob
+    output <- peek ob >>= getBufferData
     
     c_bufrelease ib
     c_bufrelease ob
diff --git a/src/Text/Sundown/Renderers/Html/Foreign.hsc b/src/Text/Sundown/Renderers/Html/Foreign.hsc
--- a/src/Text/Sundown/Renderers/Html/Foreign.hsc
+++ b/src/Text/Sundown/Renderers/Html/Foreign.hsc
@@ -9,38 +9,39 @@
 
 
 import Foreign
+import Foreign.C.String
 import Foreign.C.Types
-
 import Text.Sundown.Buffer.Foreign
-import Text.Sundown.Markdown.Foreign
 import Text.Sundown.Flag
+import Text.Sundown.Markdown.Foreign
 
 #include "html.h"
 
-data HtmlRenderMode = HtmlRenderMode { htmlSkipHtml :: Bool -- ^ Drop in-line HTML tags from the output
-                                     , htmlSkipStyle :: Bool -- ^ Don't add any style tags to the output
+data HtmlRenderMode = HtmlRenderMode { htmlSkipHtml :: Bool -- ^ Drop in-line HTML tags from the
+                                                           -- output
+                                     , htmlSkipStyle :: Bool -- ^ Don't add any style tags to the
+                                                            -- output
                                      , htmlSkipImages :: Bool -- ^ Don't include images in the output
                                      , htmlSkipLinks :: Bool -- ^ Don't include links in the output
                                      , htmlExpandTabs :: Bool
-                                     , htmlSafelink :: Bool -- ^ Sanity check links for known URL schemes
+                                     , htmlSafelink :: Bool -- ^ Sanity check links for known URL
+                                                           -- schemes
                                      , htmlToc :: Bool -- ^ Include a table of contents in the output
                                      , htmlHardWrap :: Bool
-                                     , htmlGithubBlockcode :: Bool
                                      , htmlUseXhtml :: Bool -- ^ Produce XHTML output instead of HTML
                                      }
 
 
 instance Flag HtmlRenderMode where
-  flagIndexes mode = [ (0,  htmlSkipHtml mode)
-                     , (1,  htmlSkipStyle mode)
-                     , (2,  htmlSkipImages mode)
-                     , (3,  htmlSkipLinks mode)
-                     , (5,  htmlExpandTabs mode)
-                     , (7,  htmlSafelink mode)
-                     , (8,  htmlToc mode)
-                     , (9,  htmlHardWrap mode)
-                     , (10, htmlGithubBlockcode mode)
-                     , (11, htmlUseXhtml mode)
+  flagIndexes mode = [ (0, htmlSkipHtml mode)
+                     , (1, htmlSkipStyle mode)
+                     , (2, htmlSkipImages mode)
+                     , (3, htmlSkipLinks mode)
+                     , (4, htmlExpandTabs mode)
+                     , (5, htmlSafelink mode)
+                     , (6, htmlToc mode)
+                     , (7, htmlHardWrap mode)
+                     , (8, htmlUseXhtml mode)
                      ]
 
 data HtmlRenderOptions
@@ -60,4 +61,4 @@
   c_sdhtml_toc_renderer :: Ptr Callbacks -> Ptr HtmlRenderOptions -> IO ()
 
 foreign import ccall "html.h sdhtml_smartypants"
-  c_sdhtml_smartypants :: Ptr Buffer -> Ptr Buffer -> IO ()
+  c_sdhtml_smartypants :: Ptr Buffer -> CString -> CSize -> IO ()
diff --git a/sundown.cabal b/sundown.cabal
--- a/sundown.cabal
+++ b/sundown.cabal
@@ -1,6 +1,6 @@
 Cabal-version:          >= 1.6
 Name:                   sundown
-Version:                0.2.3
+Version:                0.3
 Author:                 Francesco Mazzoli (f@mazzo.li)
 Maintainer:             Francesco Mazzoli (f@mazzo.li)
 Build-Type:             Simple
@@ -36,14 +36,12 @@
                       , Text.Sundown.Flag
                       , Text.Sundown.Markdown.Foreign
 
-  C-Sources:            cbits/autolink.c, cbits/buffer.c, cbits/html.c,
-                        cbits/html_smartypants.c, cbits/markdown.c,
-                        cbits/stack.c
+  C-Sources:            cbits/autolink.c, cbits/buffer.c, cbits/houdini_href_e.c,
+                        cbits/houdini_html_e.c, cbits/html.c, cbits/html_smartypants.c,
+                        cbits/markdown.c, cbits/stack.c
   
   Include-Dirs:         cbits
   
-  Includes:             autolink.h, buffer.h, html.h, html_blocks.h, markdown.h,
-                        stack.h
+  Includes:             autolink.h, buffer.h, html.h, html_blocks.h, markdown.h, stack.h
 
-  Install-includes:     autolink.h, buffer.h, html.h, html_blocks.h, markdown.h,
-                        stack.h
+  Install-includes:     autolink.h, buffer.h, houdini.h, html.h, html_blocks.h, markdown.h, stack.h
