1#include <stdlib.h>
2#include "buffer.h"
3#include "cmark.h"
4#include "utf8.h"
5#include "render.h"
6#include "node.h"
7#include "cmark_ctype.h"
8
9static CMARK_INLINE void S_cr(cmark_renderer *renderer) {
10 if (renderer->need_cr < 1) {
11 renderer->need_cr = 1;
12 }
13}
14
15static CMARK_INLINE void S_blankline(cmark_renderer *renderer) {
16 if (renderer->need_cr < 2) {
17 renderer->need_cr = 2;
18 }
19}
20
21static void S_out(cmark_renderer *renderer, const char *source, bool wrap,
22 cmark_escaping escape) {
23 int length = strlen(source);
24 unsigned char nextc;
25 int32_t c;
26 int i = 0;
27 int last_nonspace;
28 int len;
29 int k = renderer->buffer->size - 1;
30
31 wrap = wrap && !renderer->no_linebreaks;
32
33 if (renderer->in_tight_list_item && renderer->need_cr > 1) {
34 renderer->need_cr = 1;
35 }
36 while (renderer->need_cr) {
37 if (k < 0 || renderer->buffer->ptr[k] == '\n') {
38 k -= 1;
39 } else {
40 cmark_strbuf_putc(renderer->buffer, '\n');
41 if (renderer->need_cr > 1) {
42 cmark_strbuf_put(renderer->buffer, renderer->prefix->ptr,
43 renderer->prefix->size);
44 }
45 }
46 renderer->column = 0;
47 renderer->last_breakable = 0;
48 renderer->begin_line = true;
49 renderer->begin_content = true;
50 renderer->need_cr -= 1;
51 }
52
53 while (i < length) {
54 if (renderer->begin_line) {
55 cmark_strbuf_put(renderer->buffer, renderer->prefix->ptr,
56 renderer->prefix->size);
57 // note: this assumes prefix is ascii:
58 renderer->column = renderer->prefix->size;
59 }
60
61 len = cmark_utf8proc_iterate((const uint8_t *)source + i, length - i, &c);
62 if (len == -1) { // error condition
63 return; // return without rendering rest of string
64 }
65 nextc = source[i + len];
66 if (c == 32 && wrap) {
67 if (!renderer->begin_line) {
68 last_nonspace = renderer->buffer->size;
69 cmark_strbuf_putc(renderer->buffer, ' ');
70 renderer->column += 1;
71 renderer->begin_line = false;
72 renderer->begin_content = false;
73 // skip following spaces
74 while (source[i + 1] == ' ') {
75 i++;
76 }
77 // We don't allow breaks that make a digit the first character
78 // because this causes problems with commonmark output.
79 if (!cmark_isdigit(source[i + 1])) {
80 renderer->last_breakable = last_nonspace;
81 }
82 }
83
84 } else if (escape == LITERAL) {
85 if (c == 10) {
86 cmark_strbuf_putc(renderer->buffer, '\n');
87 renderer->column = 0;
88 renderer->begin_line = true;
89 renderer->begin_content = true;
90 renderer->last_breakable = 0;
91 } else {
92 cmark_render_code_point(renderer, c);
93 renderer->begin_line = false;
94 // we don't set 'begin_content' to false til we've
95 // finished parsing a digit. Reason: in commonmark
96 // we need to escape a potential list marker after
97 // a digit:
98 renderer->begin_content =
99 renderer->begin_content && cmark_isdigit(c) == 1;
100 }
101 } else {
102 (renderer->outc)(renderer, escape, c, nextc);
103 renderer->begin_line = false;
104 renderer->begin_content =
105 renderer->begin_content && cmark_isdigit(c) == 1;
106 }
107
108 // If adding the character went beyond width, look for an
109 // earlier place where the line could be broken:
110 if (renderer->width > 0 && renderer->column > renderer->width &&
111 !renderer->begin_line && renderer->last_breakable > 0) {
112
113 // copy from last_breakable to remainder
114 unsigned char *src = renderer->buffer->ptr +
115 renderer->last_breakable + 1;
116 bufsize_t remainder_len = renderer->buffer->size -
117 renderer->last_breakable - 1;
118 unsigned char *remainder =
119 (unsigned char *)renderer->mem->realloc(NULL, remainder_len);
120 memcpy(remainder, src, remainder_len);
121 // truncate at last_breakable
122 cmark_strbuf_truncate(renderer->buffer, renderer->last_breakable);
123 // add newline, prefix, and remainder
124 cmark_strbuf_putc(renderer->buffer, '\n');
125 cmark_strbuf_put(renderer->buffer, renderer->prefix->ptr,
126 renderer->prefix->size);
127 cmark_strbuf_put(renderer->buffer, remainder, remainder_len);
128 renderer->column = renderer->prefix->size + remainder_len;
129 renderer->mem->free(remainder);
130 renderer->last_breakable = 0;
131 renderer->begin_line = false;
132 renderer->begin_content = false;
133 }
134
135 i += len;
136 }
137}
138
139// Assumes no newlines, assumes ascii content:
140void cmark_render_ascii(cmark_renderer *renderer, const char *s) {
141 int origsize = renderer->buffer->size;
142 cmark_strbuf_puts(renderer->buffer, s);
143 renderer->column += renderer->buffer->size - origsize;
144}
145
146void cmark_render_code_point(cmark_renderer *renderer, uint32_t c) {
147 cmark_utf8proc_encode_char(c, renderer->buffer);
148 renderer->column += 1;
149}
150
151char *cmark_render(cmark_node *root, int options, int width,
152 void (*outc)(cmark_renderer *, cmark_escaping, int32_t,
153 unsigned char),
154 int (*render_node)(cmark_renderer *renderer,
155 cmark_node *node,
156 cmark_event_type ev_type, int options)) {
157 cmark_mem *mem = root->mem;
158 cmark_strbuf pref = CMARK_BUF_INIT(mem);
159 cmark_strbuf buf = CMARK_BUF_INIT(mem);
160 cmark_node *cur;
161 cmark_event_type ev_type;
162 char *result;
163 cmark_iter *iter = cmark_iter_new(root);
164
165 cmark_renderer renderer = {options,
166 mem, &buf, &pref, 0, width,
167 0, 0, true, true, false,
168 false, outc, S_cr, S_blankline, S_out};
169
170 while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
171 cur = cmark_iter_get_node(iter);
172 if (!render_node(&renderer, cur, ev_type, options)) {
173 // a false value causes us to skip processing
174 // the node's contents. this is used for
175 // autolinks.
176 cmark_iter_reset(iter, cur, CMARK_EVENT_EXIT);
177 }
178 }
179
180 // ensure final newline
181 if (renderer.buffer->size == 0 || renderer.buffer->ptr[renderer.buffer->size - 1] != '\n') {
182 cmark_strbuf_putc(renderer.buffer, '\n');
183 }
184
185 result = (char *)cmark_strbuf_detach(renderer.buffer);
186
187 cmark_iter_free(iter);
188 cmark_strbuf_free(renderer.prefix);
189 cmark_strbuf_free(renderer.buffer);
190
191 return result;
192}
193