187 lines
6.1 KiB
C
187 lines
6.1 KiB
C
#include <stdlib.h>
|
|
#include "buffer.h"
|
|
#include "chunk.h"
|
|
#include "cmark.h"
|
|
#include "utf8.h"
|
|
#include "render.h"
|
|
#include "node.h"
|
|
|
|
static CMARK_INLINE void S_cr(cmark_renderer *renderer) {
|
|
if (renderer->need_cr < 1) {
|
|
renderer->need_cr = 1;
|
|
}
|
|
}
|
|
|
|
static CMARK_INLINE void S_blankline(cmark_renderer *renderer) {
|
|
if (renderer->need_cr < 2) {
|
|
renderer->need_cr = 2;
|
|
}
|
|
}
|
|
|
|
static void S_out(cmark_renderer *renderer, const char *source, bool wrap,
|
|
cmark_escaping escape) {
|
|
int length = strlen(source);
|
|
unsigned char nextc;
|
|
int32_t c;
|
|
int i = 0;
|
|
int last_nonspace;
|
|
int len;
|
|
cmark_chunk remainder = cmark_chunk_literal("");
|
|
int k = renderer->buffer->size - 1;
|
|
|
|
wrap = wrap && !renderer->no_linebreaks;
|
|
|
|
if (renderer->in_tight_list_item && renderer->need_cr > 1) {
|
|
renderer->need_cr = 1;
|
|
}
|
|
while (renderer->need_cr) {
|
|
if (k < 0 || renderer->buffer->ptr[k] == '\n') {
|
|
k -= 1;
|
|
} else {
|
|
cmark_strbuf_putc(renderer->buffer, '\n');
|
|
if (renderer->need_cr > 1) {
|
|
cmark_strbuf_put(renderer->buffer, renderer->prefix->ptr,
|
|
renderer->prefix->size);
|
|
}
|
|
}
|
|
renderer->column = 0;
|
|
renderer->last_breakable = 0;
|
|
renderer->begin_line = true;
|
|
renderer->begin_content = true;
|
|
renderer->need_cr -= 1;
|
|
}
|
|
|
|
while (i < length) {
|
|
if (renderer->begin_line) {
|
|
cmark_strbuf_put(renderer->buffer, renderer->prefix->ptr,
|
|
renderer->prefix->size);
|
|
// note: this assumes prefix is ascii:
|
|
renderer->column = renderer->prefix->size;
|
|
}
|
|
|
|
len = cmark_utf8proc_iterate((const uint8_t *)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 (!renderer->begin_line) {
|
|
last_nonspace = renderer->buffer->size;
|
|
cmark_strbuf_putc(renderer->buffer, ' ');
|
|
renderer->column += 1;
|
|
renderer->begin_line = false;
|
|
renderer->begin_content = false;
|
|
// skip following spaces
|
|
while (source[i + 1] == ' ') {
|
|
i++;
|
|
}
|
|
// We don't allow breaks that make a digit the first character
|
|
// because this causes problems with commonmark output.
|
|
if (!cmark_isdigit(source[i + 1])) {
|
|
renderer->last_breakable = last_nonspace;
|
|
}
|
|
}
|
|
|
|
} else if (c == 10) {
|
|
cmark_strbuf_putc(renderer->buffer, '\n');
|
|
renderer->column = 0;
|
|
renderer->begin_line = true;
|
|
renderer->begin_content = true;
|
|
renderer->last_breakable = 0;
|
|
} else if (escape == LITERAL) {
|
|
cmark_render_code_point(renderer, c);
|
|
renderer->begin_line = false;
|
|
// we don't set 'begin_content' to false til we've
|
|
// finished parsing a digit. Reason: in commonmark
|
|
// we need to escape a potential list marker after
|
|
// a digit:
|
|
renderer->begin_content =
|
|
renderer->begin_content && cmark_isdigit(c) == 1;
|
|
} else {
|
|
(renderer->outc)(renderer, escape, c, nextc);
|
|
renderer->begin_line = false;
|
|
renderer->begin_content =
|
|
renderer->begin_content && cmark_isdigit(c) == 1;
|
|
}
|
|
|
|
// If adding the character went beyond width, look for an
|
|
// earlier place where the line could be broken:
|
|
if (renderer->width > 0 && renderer->column > renderer->width &&
|
|
!renderer->begin_line && renderer->last_breakable > 0) {
|
|
|
|
// copy from last_breakable to remainder
|
|
cmark_chunk_set_cstr(renderer->mem, &remainder,
|
|
(char *)renderer->buffer->ptr +
|
|
renderer->last_breakable + 1);
|
|
// truncate at last_breakable
|
|
cmark_strbuf_truncate(renderer->buffer, renderer->last_breakable);
|
|
// add newline, prefix, and remainder
|
|
cmark_strbuf_putc(renderer->buffer, '\n');
|
|
cmark_strbuf_put(renderer->buffer, renderer->prefix->ptr,
|
|
renderer->prefix->size);
|
|
cmark_strbuf_put(renderer->buffer, remainder.data, remainder.len);
|
|
renderer->column = renderer->prefix->size + remainder.len;
|
|
cmark_chunk_free(renderer->mem, &remainder);
|
|
renderer->last_breakable = 0;
|
|
renderer->begin_line = false;
|
|
renderer->begin_content = false;
|
|
}
|
|
|
|
i += len;
|
|
}
|
|
}
|
|
|
|
// Assumes no newlines, assumes ascii content:
|
|
void cmark_render_ascii(cmark_renderer *renderer, const char *s) {
|
|
int origsize = renderer->buffer->size;
|
|
cmark_strbuf_puts(renderer->buffer, s);
|
|
renderer->column += renderer->buffer->size - origsize;
|
|
}
|
|
|
|
void cmark_render_code_point(cmark_renderer *renderer, uint32_t c) {
|
|
cmark_utf8proc_encode_char(c, renderer->buffer);
|
|
renderer->column += 1;
|
|
}
|
|
|
|
char *cmark_render(cmark_node *root, int options, int width,
|
|
void (*outc)(cmark_renderer *, cmark_escaping, int32_t,
|
|
unsigned char),
|
|
int (*render_node)(cmark_renderer *renderer,
|
|
cmark_node *node,
|
|
cmark_event_type ev_type, int options)) {
|
|
cmark_mem *mem = cmark_node_mem(root);
|
|
cmark_strbuf pref = CMARK_BUF_INIT(mem);
|
|
cmark_strbuf buf = CMARK_BUF_INIT(mem);
|
|
cmark_node *cur;
|
|
cmark_event_type ev_type;
|
|
char *result;
|
|
cmark_iter *iter = cmark_iter_new(root);
|
|
|
|
cmark_renderer renderer = {mem, &buf, &pref, 0, width,
|
|
0, 0, true, true, false,
|
|
false, outc, S_cr, S_blankline, S_out};
|
|
|
|
while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
|
|
cur = cmark_iter_get_node(iter);
|
|
if (!render_node(&renderer, cur, ev_type, options)) {
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
// ensure final newline
|
|
if (renderer.buffer->ptr[renderer.buffer->size - 1] != '\n') {
|
|
cmark_strbuf_putc(renderer.buffer, '\n');
|
|
}
|
|
|
|
result = (char *)cmark_strbuf_detach(renderer.buffer);
|
|
|
|
cmark_iter_free(iter);
|
|
cmark_strbuf_free(renderer.prefix);
|
|
cmark_strbuf_free(renderer.buffer);
|
|
|
|
return result;
|
|
}
|