summaryrefslogtreecommitdiff
path: root/src/render.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/render.c')
-rw-r--r--src/render.c186
1 files changed, 186 insertions, 0 deletions
diff --git a/src/render.c b/src/render.c
new file mode 100644
index 0000000..0fec3c4
--- /dev/null
+++ b/src/render.c
@@ -0,0 +1,186 @@
+#include <stdlib.h>
+#include "buffer.h"
+#include "chunk.h"
+#include "cmark.h"
+#include "utf8.h"
+#include "render.h"
+
+static inline
+cmark_render_state
+cmark_initialize_render_state(int options, int width,
+ void (*outc)(cmark_render_state*,
+ cmark_escaping,
+ int32_t,
+ unsigned char))
+{
+ cmark_strbuf *pref = (cmark_strbuf*)malloc(sizeof(cmark_strbuf));
+ cmark_strbuf *buf = (cmark_strbuf*)malloc(sizeof(cmark_strbuf));
+ cmark_strbuf_init(pref, 16);
+ cmark_strbuf_init(buf, 1024);
+ cmark_render_state state = { options, buf, pref, 0, width,
+ 0, 0, 0, true, false, false, outc };
+ return state;
+}
+
+static inline
+char *
+cmark_finalize_render_state(cmark_render_state *state)
+{
+ char * result;
+ result = (char *)cmark_strbuf_detach(state->buffer);
+ cmark_strbuf_free(state->prefix);
+ cmark_strbuf_free(state->buffer);
+ free(state->prefix);
+ free(state->buffer);
+ return result;
+}
+
+void cr(cmark_render_state *state)
+{
+ if (state->need_cr < 1) {
+ state->need_cr = 1;
+ }
+}
+
+void blankline(cmark_render_state *state)
+{
+ if (state->need_cr < 2) {
+ state->need_cr = 2;
+ }
+}
+
+void out(cmark_render_state *state,
+ cmark_chunk str,
+ bool wrap,
+ cmark_escaping escape)
+{
+ unsigned char* source = str.data;
+ int length = str.len;
+ unsigned char nextc;
+ int32_t c;
+ int i = 0;
+ int len;
+ cmark_chunk remainder = cmark_chunk_literal("");
+ int k = state->buffer->size - 1;
+
+ wrap = wrap && !state->no_wrap;
+
+ if (state->in_tight_list_item && state->need_cr > 1) {
+ state->need_cr = 1;
+ }
+ while (state->need_cr) {
+ if (k < 0 || state->buffer->ptr[k] == '\n') {
+ k -= 1;
+ } else {
+ cmark_strbuf_putc(state->buffer, '\n');
+ if (state->need_cr > 1) {
+ cmark_strbuf_put(state->buffer, state->prefix->ptr,
+ state->prefix->size);
+ }
+ }
+ state->column = 0;
+ state->begin_line = true;
+ state->need_cr -= 1;
+ }
+
+ while (i < length) {
+ if (state->begin_line) {
+ cmark_strbuf_put(state->buffer, state->prefix->ptr,
+ state->prefix->size);
+ // note: this assumes prefix is ascii:
+ state->column = state->prefix->size;
+ }
+
+ len = utf8proc_iterate(source + i, length - i, &c);
+ if (len == -1) { // error condition
+ return; // return without rendering rest of string
+ }
+ nextc = source[i + len];
+ if (c == 32 && wrap) {
+ if (!state->begin_line) {
+ cmark_strbuf_putc(state->buffer, ' ');
+ state->column += 1;
+ state->begin_line = false;
+ state->last_breakable = state->buffer->size -
+ 1;
+ // skip following spaces
+ while (source[i + 1] == ' ') {
+ i++;
+ }
+ }
+
+ } else if (c == 10) {
+ cmark_strbuf_putc(state->buffer, '\n');
+ state->column = 0;
+ state->begin_line = true;
+ state->last_breakable = 0;
+ } else {
+ (state->outc)(state, escape, c, nextc);
+ }
+
+ // If adding the character went beyond width, look for an
+ // earlier place where the line could be broken:
+ if (state->width > 0 &&
+ state->column > state->width &&
+ !state->begin_line &&
+ state->last_breakable > 0) {
+
+ // copy from last_breakable to remainder
+ cmark_chunk_set_cstr(&remainder, (char *) state->buffer->ptr + state->last_breakable + 1);
+ // truncate at last_breakable
+ cmark_strbuf_truncate(state->buffer, state->last_breakable);
+ // add newline, prefix, and remainder
+ cmark_strbuf_putc(state->buffer, '\n');
+ cmark_strbuf_put(state->buffer, state->prefix->ptr,
+ state->prefix->size);
+ cmark_strbuf_put(state->buffer, remainder.data, remainder.len);
+ state->column = state->prefix->size + remainder.len;
+ cmark_chunk_free(&remainder);
+ state->last_breakable = 0;
+ state->begin_line = false;
+ }
+
+ i += len;
+ }
+}
+
+void lit(cmark_render_state *state, char *s, bool wrap)
+{
+ cmark_chunk str = cmark_chunk_literal(s);
+ out(state, str, wrap, LITERAL);
+}
+
+char*
+cmark_render(cmark_node *root,
+ int options,
+ int width,
+ void (*outc)(cmark_render_state*,
+ cmark_escaping,
+ int32_t,
+ unsigned char),
+ int (*render_node)(cmark_node *node,
+ cmark_event_type ev_type,
+ cmark_render_state *state))
+{
+ if (CMARK_OPT_HARDBREAKS & options) {
+ width = 0;
+ }
+ cmark_render_state state = cmark_initialize_render_state(options, width, outc);
+ cmark_node *cur;
+ cmark_event_type ev_type;
+ cmark_iter *iter = cmark_iter_new(root);
+
+ while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
+ cur = cmark_iter_get_node(iter);
+ if (!render_node(cur, ev_type, &state)) {
+ // a false value causes us to skip processing
+ // the node's contents. this is used for
+ // autolinks.
+ cmark_iter_reset(iter, cur, CMARK_EVENT_EXIT);
+ }
+ }
+
+ cmark_iter_free(iter);
+
+ return cmark_finalize_render_state(&state);
+}