diff options
Diffstat (limited to 'src/render.c')
| -rw-r--r-- | src/render.c | 186 | 
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); +} | 
