render.c 6.2 KB

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