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
2 changes: 1 addition & 1 deletion cmarkly
8 changes: 1 addition & 7 deletions ext/markly/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 ext/markly/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 ext/markly/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
4 changes: 4 additions & 0 deletions releases.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Releases

## Unreleased

- Speed up `Node#to_plaintext` by copying runs of unchanged text in one append instead of decoding and re-encoding each character (`parse` + `to_plaintext` 1.3 to 1.7 times faster).

## v0.19.0

- Add `Markly::HTML_BLOCK_BLANK_LINES` for keeping consistently indented HTML content together across blank lines.
Expand Down
29 changes: 29 additions & 0 deletions test/markly/node/to_plaintext.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,33 @@
it "can generate plaintext" do
expect(document.to_plaintext).to be == PLAINTEXT
end

with "text the renderer copies in runs" do
it "wraps at the width inside a list item" do
document = Markly.parse("- Les lunettes Xpand X105-RF-X1 sont des lunettes 3D actives.")

expect(document.to_plaintext(width: 20)).to be == " - Les lunettes\n Xpand X105-RF-X1\n sont des\n lunettes 3D\n actives.\n"
end

it "keeps multibyte characters intact" do
document = Markly.parse("> Déjà vu: l'été à Zürich 日本 😀")

expect(document.to_plaintext).to be == "Déjà vu: l'été à Zürich 日本 😀\n"
end

it "indents every line of a code block in a list item" do
document = Markly.parse("1. item\n\n ```\n code line one\n code line two\n ```")

expect(document.to_plaintext).to be == "1. item\n \n code line one\n code line two\n"
end

it "stops at invalid UTF-8 in a text node built through the API" do
document = Markly.parse("- item")
text = Markly::Node.new(:text)
text.string_content = "valid \xFF lost".b.force_encoding(Encoding::UTF_8)
document.first_child.first_child.first_child.append_child(text)

expect(document.to_plaintext).to be == " - itemvalid \n"
end
end
end