Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions api_test/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,30 @@ static void render_plaintext(test_batch_runner *runner) {
"render document without wrapping");
free(plaintext);

static const char multibyte_markdown[] =
"- D\xC3\xA9j\xC3\xA0 vu \xC3\xA0 Z\xC3\xBCrich, \xE6\x97\xA5\xE6\x9C\xAC "
"\xF0\x9F\x98\x80 encore\n"
"\n"
" ```\n"
" code one\n"
" code two\n"
" ```\n";
cmark_node *multibyte_doc = cmark_parse_document(
multibyte_markdown, sizeof(multibyte_markdown) - 1, CMARK_OPT_DEFAULT);
plaintext = cmark_render_plaintext(multibyte_doc, CMARK_OPT_DEFAULT, 12);
STR_EQ(runner, plaintext,
" - D\xC3\xA9j\xC3\xA0 vu\n"
" \xC3\xA0\n"
" Z\xC3\xBCrich,\n"
" \xE6\x97\xA5\xE6\x9C\xAC \xF0\x9F\x98\x80\n"
" encore\n"
" \n"
" code one\n"
" code two\n",
"render multibyte text and code block with wrapping");
free(plaintext);
cmark_node_free(multibyte_doc);

cmark_node *text = cmark_node_new(CMARK_NODE_TEXT);
cmark_node_set_literal(text, "Hi");
plaintext = cmark_render_plaintext(text, CMARK_OPT_DEFAULT, 0);
Expand Down
8 changes: 1 addition & 7 deletions src/plaintext.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,6 @@

// Functions to convert cmark_nodes to plain text strings.

static CMARK_INLINE void outc(cmark_renderer *renderer, cmark_node *node,
cmark_escaping escape,
int32_t c, unsigned char nextc) {
cmark_render_code_point(renderer, c);
}

static int S_render_node(cmark_renderer *renderer, cmark_node *node,
cmark_event_type ev_type, int options) {
int list_number;
Expand Down Expand Up @@ -217,5 +211,5 @@ char *cmark_render_plaintext_with_mem(cmark_node *root, int options, int width,
// a different meaning with OPT_HARDBREAKS
width = 0;
}
return cmark_render(mem, root, options, width, outc, S_render_node);
return cmark_render(mem, root, options, width, NULL, S_render_node);
}
51 changes: 51 additions & 0 deletions src/render.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ static CMARK_INLINE void S_blankline(cmark_renderer *renderer) {
}
}

static void S_outc_verbatim(cmark_renderer *renderer, cmark_node *node,
cmark_escaping escape, int32_t c,
unsigned char nextc) {
cmark_render_code_point(renderer, c);
}

static void S_out(cmark_renderer *renderer, cmark_node *node,
const char *source, bool wrap,
cmark_escaping escape) {
Expand Down Expand Up @@ -57,6 +63,13 @@ static void S_out(cmark_renderer *renderer, cmark_node *node,
renderer->need_cr -= 1;
}

// Characters the renderer writes back unchanged are copied in runs rather
// than decoded and re-encoded one by one. Spaces (when wrapping), newlines
// (in LITERAL), U+FFFE, U+FFFF and invalid UTF-8 still go through the loop
// below.
bool verbatim =
!ext && (escape == LITERAL || renderer->outc == S_outc_verbatim);

while (i < length) {
if (renderer->begin_line) {
cmark_strbuf_put(renderer->buffer, renderer->prefix->ptr,
Expand All @@ -65,6 +78,41 @@ static void S_out(cmark_renderer *renderer, cmark_node *node,
renderer->column = renderer->prefix->size;
}

// With no pending break point, the width check below cannot fire.
if (verbatim && (renderer->width == 0 || renderer->last_breakable == 0)) {
int run_end = i;
int code_points = 0;
bool all_digits = true;
while (run_end < length) {
unsigned char byte = (unsigned char)source[run_end];
if (byte < 0x80) {
c = byte;
len = 1;
} else {
len = cmark_utf8proc_iterate((const uint8_t *)source + run_end,
length - run_end, &c);
// cmark_utf8proc_encode_char writes U+FFFE and U+FFFF as one byte.
if (len == -1 || c == 0xFFFE || c == 0xFFFF)
break;
}
if ((c == ' ' && wrap) || (c == '\n' && escape == LITERAL))
break;
// (char)c truncates, as the per-character path below does.
all_digits = all_digits && cmark_isdigit((char)c);
run_end += len;
code_points += 1;
}
if (run_end > i) {
cmark_strbuf_put(renderer->buffer, (const unsigned char *)source + i,
run_end - i);
renderer->column += code_points;
renderer->begin_line = false;
renderer->begin_content = renderer->begin_content && all_digits;
i = run_end;
continue;
}
}

len = cmark_utf8proc_iterate((const uint8_t *)source + i, length - i, &c);
if (len == -1) { // error condition
return; // return without rendering rest of string
Expand Down Expand Up @@ -169,6 +217,9 @@ char *cmark_render(cmark_mem *mem, cmark_node *root, int options, int width,
char *result;
cmark_iter *iter = cmark_iter_new(root);

if (outc == NULL)
outc = S_outc_verbatim;

cmark_renderer renderer = {mem, &buf, &pref, 0, width,
0, 0, true, true, false,
false, outc, S_cr, S_blankline, S_out,
Expand Down
1 change: 1 addition & 0 deletions src/render.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ void cmark_render_ascii(cmark_renderer *renderer, const char *s);

void cmark_render_code_point(cmark_renderer *renderer, uint32_t c);

// A NULL outc writes every code point unchanged, which lets S_out copy runs.
char *cmark_render(cmark_mem *mem, cmark_node *root, int options, int width,
void (*outc)(cmark_renderer *, cmark_node *,
cmark_escaping, int32_t,
Expand Down