cmark
My personal build of CMark ✏️
render.c (6491B)
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 9 static CMARK_INLINE void S_cr(cmark_renderer *renderer) { 10 if (renderer->need_cr < 1) { 11 renderer->need_cr = 1; 12 } 13 } 14 15 static CMARK_INLINE void S_blankline(cmark_renderer *renderer) { 16 if (renderer->need_cr < 2) { 17 renderer->need_cr = 2; 18 } 19 } 20 21 static 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: 140 void 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 146 void cmark_render_code_point(cmark_renderer *renderer, uint32_t c) { 147 cmark_utf8proc_encode_char(c, renderer->buffer); 148 renderer->column += 1; 149 } 150 151 char *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 }