Hello world!
" 53 | } 54 | Heading(level: 2) { 55 | "Chapter 1" 56 | } 57 | ThematicBreak() 58 | } 59 | 60 | // when 61 | let result = build() 62 | 63 | // then 64 | XCTAssertEqual( 65 | [ 66 | .paragraph(.init(text: [.text("This is the first paragraph.")])), 67 | .paragraph( 68 | .init( 69 | text: [ 70 | .text("This is the "), 71 | .strong(.init(children: [.text("second")])), 72 | .text(" paragraph."), 73 | ] 74 | ) 75 | ), 76 | .blockQuote( 77 | .init( 78 | items: [ 79 | .paragraph(.init(text: [.text("You say hello.")])), 80 | .blockQuote( 81 | .init( 82 | items: [ 83 | .paragraph(.init(text: [.text("I say goodbye.")])) 84 | ] 85 | ) 86 | ), 87 | ] 88 | ) 89 | ), 90 | .orderedList( 91 | .init( 92 | items: [ 93 | .init(blocks: [.paragraph(.init(text: [.text("One")]))]), 94 | .init( 95 | blocks: [ 96 | .paragraph(.init(text: [.text("Two")])), 97 | .bulletList( 98 | .init( 99 | items: [ 100 | .init(blocks: [.paragraph(.init(text: [.text("Two 1")]))]), 101 | .init(blocks: [.paragraph(.init(text: [.text("Two 2")]))]), 102 | ], 103 | tight: false 104 | ) 105 | ), 106 | ] 107 | ), 108 | ], 109 | start: 1, 110 | tight: true 111 | ) 112 | ), 113 | .bulletList( 114 | .init( 115 | items: [ 116 | .init( 117 | blocks: [ 118 | .paragraph(.init(text: [.text("First paragraph.")])), 119 | .paragraph(.init(text: [.text("Second paragraph.")])), 120 | ] 121 | ), 122 | .init( 123 | blocks: [ 124 | .paragraph(.init(text: [.text("Two")])), 125 | .bulletList( 126 | .init( 127 | items: [ 128 | .init(blocks: [.paragraph(.init(text: [.text("Two 1")]))]), 129 | .init(blocks: [.paragraph(.init(text: [.text("Two 2")]))]), 130 | ], 131 | tight: true 132 | ) 133 | ), 134 | ] 135 | ), 136 | ], 137 | tight: false 138 | ) 139 | ), 140 | .code(.init(language: "swift", code: { "let a = 5\nlet b = 42" })), 141 | .html(.init(html: "Hello world!
")), 142 | .heading(.init(text: [.text("Chapter 1")], level: 2)), 143 | .thematicBreak, 144 | ], 145 | result 146 | ) 147 | } 148 | 149 | func testForLoops() { 150 | // given 151 | @BlockArrayBuilder func build() -> [Block] { 152 | for i in 0...3 { 153 | "\(i)" 154 | } 155 | } 156 | 157 | // when 158 | let result = build() 159 | 160 | // then 161 | XCTAssertEqual( 162 | [ 163 | .paragraph(.init(text: [.text("0")])), 164 | .paragraph(.init(text: [.text("1")])), 165 | .paragraph(.init(text: [.text("2")])), 166 | .paragraph(.init(text: [.text("3")])), 167 | ], 168 | result 169 | ) 170 | } 171 | 172 | func testIf() { 173 | @BlockArrayBuilder func build() -> [Block] { 174 | "Something is:" 175 | if true { 176 | BlockQuote { 177 | "true" 178 | } 179 | } 180 | } 181 | 182 | // when 183 | let result = build() 184 | 185 | // then 186 | XCTAssertEqual( 187 | [ 188 | .paragraph(.init(text: [.text("Something is:")])), 189 | .blockQuote( 190 | .init( 191 | items: [.paragraph(.init(text: [.text("true")]))] 192 | ) 193 | ), 194 | ], 195 | result 196 | ) 197 | } 198 | 199 | func testIfElse() { 200 | @BlockArrayBuilder func build(_ value: Bool) -> [Block] { 201 | "Something is:" 202 | if value { 203 | BlockQuote { 204 | "true" 205 | } 206 | } else { 207 | "false" 208 | } 209 | } 210 | 211 | // when 212 | let result1 = build(true) 213 | let result2 = build(false) 214 | 215 | // then 216 | XCTAssertEqual( 217 | [ 218 | .paragraph(.init(text: [.text("Something is:")])), 219 | .blockQuote( 220 | .init( 221 | items: [.paragraph(.init(text: [.text("true")]))] 222 | ) 223 | ), 224 | ], 225 | result1 226 | ) 227 | XCTAssertEqual( 228 | [ 229 | .paragraph(.init(text: [.text("Something is:")])), 230 | .paragraph(.init(text: [.text("false")])), 231 | ], 232 | result2 233 | ) 234 | } 235 | } 236 | #endif 237 | -------------------------------------------------------------------------------- /Sources/cmark/render.c: -------------------------------------------------------------------------------- 1 | #include\n"); 88 | } else { 89 | cr(html); 90 | cmark_strbuf_puts(html, "\n"); 91 | } 92 | break; 93 | 94 | case CMARK_NODE_LIST: { 95 | cmark_list_type list_type = (cmark_list_type)node->as.list.list_type; 96 | int start = node->as.list.start; 97 | 98 | if (entering) { 99 | cr(html); 100 | if (list_type == CMARK_BULLET_LIST) { 101 | cmark_strbuf_puts(html, "
");
153 | } else {
154 | bufsize_t first_tag = 0;
155 | while (node->as.code.info[first_tag] &&
156 | !cmark_isspace(node->as.code.info[first_tag])) {
157 | first_tag += 1;
158 | }
159 |
160 | cmark_strbuf_puts(html, "as.code.info, first_tag);
164 | cmark_strbuf_puts(html, "\">");
165 | }
166 |
167 | escape_html(html, node->data, node->len);
168 | cmark_strbuf_puts(html, "
\n");
169 | break;
170 |
171 | case CMARK_NODE_HTML_BLOCK:
172 | cr(html);
173 | if (!(options & CMARK_OPT_UNSAFE)) {
174 | cmark_strbuf_puts(html, "");
175 | } else {
176 | cmark_strbuf_put(html, node->data, node->len);
177 | }
178 | cr(html);
179 | break;
180 |
181 | case CMARK_NODE_CUSTOM_BLOCK: {
182 | unsigned char *block = entering ? node->as.custom.on_enter :
183 | node->as.custom.on_exit;
184 | cr(html);
185 | if (block) {
186 | cmark_strbuf_puts(html, (char *)block);
187 | }
188 | cr(html);
189 | break;
190 | }
191 |
192 | case CMARK_NODE_THEMATIC_BREAK:
193 | cr(html);
194 | cmark_strbuf_puts(html, "
\n");
197 | break;
198 |
199 | case CMARK_NODE_PARAGRAPH:
200 | parent = cmark_node_parent(node);
201 | grandparent = cmark_node_parent(parent);
202 | if (grandparent != NULL && grandparent->type == CMARK_NODE_LIST) {
203 | tight = grandparent->as.list.tight;
204 | } else {
205 | tight = false;
206 | }
207 | if (!tight) {
208 | if (entering) {
209 | cr(html);
210 | cmark_strbuf_puts(html, "');
213 | } else {
214 | cmark_strbuf_puts(html, "
\n");
215 | }
216 | }
217 | break;
218 |
219 | case CMARK_NODE_TEXT:
220 | escape_html(html, node->data, node->len);
221 | break;
222 |
223 | case CMARK_NODE_LINEBREAK:
224 | cmark_strbuf_puts(html, "
\n");
225 | break;
226 |
227 | case CMARK_NODE_SOFTBREAK:
228 | if (options & CMARK_OPT_HARDBREAKS) {
229 | cmark_strbuf_puts(html, "
\n");
230 | } else if (options & CMARK_OPT_NOBREAKS) {
231 | cmark_strbuf_putc(html, ' ');
232 | } else {
233 | cmark_strbuf_putc(html, '\n');
234 | }
235 | break;
236 |
237 | case CMARK_NODE_CODE:
238 | cmark_strbuf_puts(html, "");
239 | escape_html(html, node->data, node->len);
240 | cmark_strbuf_puts(html, "");
241 | break;
242 |
243 | case CMARK_NODE_HTML_INLINE:
244 | if (!(options & CMARK_OPT_UNSAFE)) {
245 | cmark_strbuf_puts(html, "");
246 | } else {
247 | cmark_strbuf_put(html, node->data, node->len);
248 | }
249 | break;
250 |
251 | case CMARK_NODE_CUSTOM_INLINE: {
252 | unsigned char *block = entering ? node->as.custom.on_enter :
253 | node->as.custom.on_exit;
254 | if (block) {
255 | cmark_strbuf_puts(html, (char *)block);
256 | }
257 | break;
258 | }
259 |
260 | case CMARK_NODE_STRONG:
261 | if (entering) {
262 | cmark_strbuf_puts(html, "");
263 | } else {
264 | cmark_strbuf_puts(html, "");
265 | }
266 | break;
267 |
268 | case CMARK_NODE_EMPH:
269 | if (entering) {
270 | cmark_strbuf_puts(html, "");
271 | } else {
272 | cmark_strbuf_puts(html, "");
273 | }
274 | break;
275 |
276 | case CMARK_NODE_LINK:
277 | if (entering) {
278 | cmark_strbuf_puts(html, "as.link.url && ((options & CMARK_OPT_UNSAFE) ||
280 | !(_scan_dangerous_url(node->as.link.url)))) {
281 | houdini_escape_href(html, node->as.link.url,
282 | strlen((char *)node->as.link.url));
283 | }
284 | if (node->as.link.title) {
285 | cmark_strbuf_puts(html, "\" title=\"");
286 | escape_html(html, node->as.link.title,
287 | strlen((char *)node->as.link.title));
288 | }
289 | cmark_strbuf_puts(html, "\">");
290 | } else {
291 | cmark_strbuf_puts(html, "");
292 | }
293 | break;
294 |
295 | case CMARK_NODE_IMAGE:
296 | if (entering) {
297 | cmark_strbuf_puts(html, "
as.link.url && ((options & CMARK_OPT_UNSAFE) ||
299 | !(_scan_dangerous_url(node->as.link.url)))) {
300 | houdini_escape_href(html, node->as.link.url,
301 | strlen((char *)node->as.link.url));
302 | }
303 | cmark_strbuf_puts(html, "\" alt=\"");
304 | state->plain = node;
305 | } else {
306 | if (node->as.link.title) {
307 | cmark_strbuf_puts(html, "\" title=\"");
308 | escape_html(html, node->as.link.title,
309 | strlen((char *)node->as.link.title));
310 | }
311 |
312 | cmark_strbuf_puts(html, "\" />");
313 | }
314 | break;
315 |
316 | default:
317 | assert(false);
318 | break;
319 | }
320 |
321 | // cmark_strbuf_putc(html, 'x');
322 | return 1;
323 | }
324 |
325 | char *cmark_render_html(cmark_node *root, int options) {
326 | char *result;
327 | cmark_strbuf html = CMARK_BUF_INIT(root->mem);
328 | cmark_event_type ev_type;
329 | cmark_node *cur;
330 | struct render_state state = {&html, NULL};
331 | cmark_iter *iter = cmark_iter_new(root);
332 |
333 | while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
334 | cur = cmark_iter_get_node(iter);
335 | S_render_node(cur, ev_type, &state, options);
336 | }
337 | result = (char *)cmark_strbuf_detach(&html);
338 |
339 | cmark_iter_free(iter);
340 | return result;
341 | }
342 |
--------------------------------------------------------------------------------
/Sources/cmark/scanners.re:
--------------------------------------------------------------------------------
1 | #include
2 | #include "chunk.h"
3 | #include "scanners.h"
4 |
5 | bufsize_t _scan_at(bufsize_t (*scanner)(const unsigned char *), cmark_chunk *c, bufsize_t offset)
6 | {
7 | bufsize_t res;
8 | unsigned char *ptr = (unsigned char *)c->data;
9 |
10 | if (ptr == NULL || offset > c->len) {
11 | return 0;
12 | } else {
13 | unsigned char lim = ptr[c->len];
14 |
15 | ptr[c->len] = '\0';
16 | res = scanner(ptr + offset);
17 | ptr[c->len] = lim;
18 | }
19 |
20 | return res;
21 | }
22 |
23 | /*!re2c
24 | re2c:define:YYCTYPE = "unsigned char";
25 | re2c:define:YYCURSOR = p;
26 | re2c:define:YYMARKER = marker;
27 | re2c:define:YYCTXMARKER = marker;
28 | re2c:yyfill:enable = 0;
29 |
30 | wordchar = [^\x00-\x20];
31 |
32 | spacechar = [ \t\v\f\r\n];
33 |
34 | reg_char = [^\\()\x00-\x20];
35 |
36 | escaped_char = [\\][!"#$%&'()*+,./:;<=>?@[\\\]^_`{|}~-];
37 |
38 | tagname = [A-Za-z][A-Za-z0-9-]*;
39 |
40 | blocktagname = 'address'|'article'|'aside'|'base'|'basefont'|'blockquote'|'body'|'caption'|'center'|'col'|'colgroup'|'dd'|'details'|'dialog'|'dir'|'div'|'dl'|'dt'|'fieldset'|'figcaption'|'figure'|'footer'|'form'|'frame'|'frameset'|'h1'|'h2'|'h3'|'h4'|'h5'|'h6'|'head'|'header'|'hr'|'html'|'iframe'|'legend'|'li'|'link'|'main'|'menu'|'menuitem'|'nav'|'noframes'|'ol'|'optgroup'|'option'|'p'|'param'|'section'|'source'|'title'|'summary'|'table'|'tbody'|'td'|'tfoot'|'th'|'thead'|'title'|'tr'|'track'|'ul';
41 |
42 | attributename = [a-zA-Z_:][a-zA-Z0-9:._-]*;
43 |
44 | unquotedvalue = [^ \t\r\n\v\f"'=<>`\x00]+;
45 | singlequotedvalue = ['][^'\x00]*['];
46 | doublequotedvalue = ["][^"\x00]*["];
47 |
48 | attributevalue = unquotedvalue | singlequotedvalue | doublequotedvalue;
49 |
50 | attributevaluespec = spacechar* [=] spacechar* attributevalue;
51 |
52 | attribute = spacechar+ attributename attributevaluespec?;
53 |
54 | opentag = tagname attribute* spacechar* [/]? [>];
55 | closetag = [/] tagname spacechar* [>];
56 |
57 | htmlcomment = "--->" | ("-" ([-]? [^\x00>-]) ([-]? [^\x00-])* "-->");
58 |
59 | processinginstruction = ([^?>\x00]+ | [?][^>\x00] | [>])+;
60 |
61 | declaration = [A-Z]+ spacechar+ [^>\x00]*;
62 |
63 | cdata = "CDATA[" ([^\]\x00]+ | "]" [^\]\x00] | "]]" [^>\x00])*;
64 |
65 | htmltag = opentag | closetag;
66 |
67 | in_parens_nosp = [(] (reg_char|escaped_char|[\\])* [)];
68 |
69 | in_double_quotes = ["] (escaped_char|[^"\x00])* ["];
70 | in_single_quotes = ['] (escaped_char|[^'\x00])* ['];
71 | in_parens = [(] (escaped_char|[^)\x00])* [)];
72 |
73 | scheme = [A-Za-z][A-Za-z0-9.+-]{1,31};
74 | */
75 |
76 | // Try to match a scheme including colon.
77 | bufsize_t _scan_scheme(const unsigned char *p)
78 | {
79 | const unsigned char *marker = NULL;
80 | const unsigned char *start = p;
81 | /*!re2c
82 | scheme [:] { return (bufsize_t)(p - start); }
83 | * { return 0; }
84 | */
85 | }
86 |
87 | // Try to match URI autolink after first <, returning number of chars matched.
88 | bufsize_t _scan_autolink_uri(const unsigned char *p)
89 | {
90 | const unsigned char *marker = NULL;
91 | const unsigned char *start = p;
92 | /*!re2c
93 | scheme [:][^\x00-\x20<>]*[>] { return (bufsize_t)(p - start); }
94 | * { return 0; }
95 | */
96 | }
97 |
98 | // Try to match email autolink after first <, returning num of chars matched.
99 | bufsize_t _scan_autolink_email(const unsigned char *p)
100 | {
101 | const unsigned char *marker = NULL;
102 | const unsigned char *start = p;
103 | /*!re2c
104 | [a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+
105 | [@]
106 | [a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?
107 | ([.][a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*
108 | [>] { return (bufsize_t)(p - start); }
109 | * { return 0; }
110 | */
111 | }
112 |
113 | // Try to match an HTML tag after first <, returning num of chars matched.
114 | bufsize_t _scan_html_tag(const unsigned char *p)
115 | {
116 | const unsigned char *marker = NULL;
117 | const unsigned char *start = p;
118 | /*!re2c
119 | htmltag { return (bufsize_t)(p - start); }
120 | * { return 0; }
121 | */
122 | }
123 |
124 | bufsize_t _scan_html_comment(const unsigned char *p)
125 | {
126 | const unsigned char *marker = NULL;
127 | const unsigned char *start = p;
128 | /*!re2c
129 | htmlcomment { return (bufsize_t)(p - start); }
130 | * { return 0; }
131 | */
132 | }
133 |
134 | bufsize_t _scan_html_pi(const unsigned char *p)
135 | {
136 | const unsigned char *marker = NULL;
137 | const unsigned char *start = p;
138 | /*!re2c
139 | processinginstruction { return (bufsize_t)(p - start); }
140 | * { return 0; }
141 | */
142 | }
143 |
144 | bufsize_t _scan_html_declaration(const unsigned char *p)
145 | {
146 | const unsigned char *marker = NULL;
147 | const unsigned char *start = p;
148 | /*!re2c
149 | declaration { return (bufsize_t)(p - start); }
150 | * { return 0; }
151 | */
152 | }
153 |
154 | bufsize_t _scan_html_cdata(const unsigned char *p)
155 | {
156 | const unsigned char *marker = NULL;
157 | const unsigned char *start = p;
158 | /*!re2c
159 | cdata { return (bufsize_t)(p - start); }
160 | * { return 0; }
161 | */
162 | }
163 |
164 | // Try to match an HTML block tag start line, returning
165 | // an integer code for the type of block (1-6, matching the spec).
166 | // #7 is handled by a separate function, below.
167 | bufsize_t _scan_html_block_start(const unsigned char *p)
168 | {
169 | const unsigned char *marker = NULL;
170 | /*!re2c
171 | [<] ('script'|'pre'|'textarea'|'style') (spacechar | [>]) { return 1; }
172 | '' { return (bufsize_t)(p - start); }
210 | * { return 0; }
211 | */
212 | }
213 |
214 | // Try to match an HTML block end line of type 3
215 | bufsize_t _scan_html_block_end_3(const unsigned char *p)
216 | {
217 | const unsigned char *marker = NULL;
218 | const unsigned char *start = p;
219 | /*!re2c
220 | [^\n\x00]* '?>' { return (bufsize_t)(p - start); }
221 | * { return 0; }
222 | */
223 | }
224 |
225 | // Try to match an HTML block end line of type 4
226 | bufsize_t _scan_html_block_end_4(const unsigned char *p)
227 | {
228 | const unsigned char *marker = NULL;
229 | const unsigned char *start = p;
230 | /*!re2c
231 | [^\n\x00]* '>' { return (bufsize_t)(p - start); }
232 | * { return 0; }
233 | */
234 | }
235 |
236 | // Try to match an HTML block end line of type 5
237 | bufsize_t _scan_html_block_end_5(const unsigned char *p)
238 | {
239 | const unsigned char *marker = NULL;
240 | const unsigned char *start = p;
241 | /*!re2c
242 | [^\n\x00]* ']]>' { return (bufsize_t)(p - start); }
243 | * { return 0; }
244 | */
245 | }
246 |
247 | // Try to match a link title (in single quotes, in double quotes, or
248 | // in parentheses), returning number of chars matched. Allow one
249 | // level of internal nesting (quotes within quotes).
250 | bufsize_t _scan_link_title(const unsigned char *p)
251 | {
252 | const unsigned char *marker = NULL;
253 | const unsigned char *start = p;
254 | /*!re2c
255 | ["] (escaped_char|[^"\x00])* ["] { return (bufsize_t)(p - start); }
256 | ['] (escaped_char|[^'\x00])* ['] { return (bufsize_t)(p - start); }
257 | [(] (escaped_char|[^()\x00])* [)] { return (bufsize_t)(p - start); }
258 | * { return 0; }
259 | */
260 | }
261 |
262 | // Match space characters, including newlines.
263 | bufsize_t _scan_spacechars(const unsigned char *p)
264 | {
265 | const unsigned char *start = p; \
266 | /*!re2c
267 | [ \t\v\f\r\n]+ { return (bufsize_t)(p - start); }
268 | * { return 0; }
269 | */
270 | }
271 |
272 | // Match ATX heading start.
273 | bufsize_t _scan_atx_heading_start(const unsigned char *p)
274 | {
275 | const unsigned char *marker = NULL;
276 | const unsigned char *start = p;
277 | /*!re2c
278 | [#]{1,6} ([ \t]+|[\r\n]) { return (bufsize_t)(p - start); }
279 | * { return 0; }
280 | */
281 | }
282 |
283 | // Match setext heading line. Return 1 for level-1 heading,
284 | // 2 for level-2, 0 for no match.
285 | bufsize_t _scan_setext_heading_line(const unsigned char *p)
286 | {
287 | const unsigned char *marker = NULL;
288 | /*!re2c
289 | [=]+ [ \t]* [\r\n] { return 1; }
290 | [-]+ [ \t]* [\r\n] { return 2; }
291 | * { return 0; }
292 | */
293 | }
294 |
295 | // Scan an opening code fence.
296 | bufsize_t _scan_open_code_fence(const unsigned char *p)
297 | {
298 | const unsigned char *marker = NULL;
299 | const unsigned char *start = p;
300 | /*!re2c
301 | [`]{3,} / [^`\r\n\x00]*[\r\n] { return (bufsize_t)(p - start); }
302 | [~]{3,} / [^\r\n\x00]*[\r\n] { return (bufsize_t)(p - start); }
303 | * { return 0; }
304 | */
305 | }
306 |
307 | // Scan a closing code fence with length at least len.
308 | bufsize_t _scan_close_code_fence(const unsigned char *p)
309 | {
310 | const unsigned char *marker = NULL;
311 | const unsigned char *start = p;
312 | /*!re2c
313 | [`]{3,} / [ \t]*[\r\n] { return (bufsize_t)(p - start); }
314 | [~]{3,} / [ \t]*[\r\n] { return (bufsize_t)(p - start); }
315 | * { return 0; }
316 | */
317 | }
318 |
319 | // Scans an entity.
320 | // Returns number of chars matched.
321 | bufsize_t _scan_entity(const unsigned char *p)
322 | {
323 | const unsigned char *marker = NULL;
324 | const unsigned char *start = p;
325 | /*!re2c
326 | [&] ([#] ([Xx][A-Fa-f0-9]{1,6}|[0-9]{1,7}) |[A-Za-z][A-Za-z0-9]{1,31} ) [;]
327 | { return (bufsize_t)(p - start); }
328 | * { return 0; }
329 | */
330 | }
331 |
332 | // Returns positive value if a URL begins in a way that is potentially
333 | // dangerous, with javascript:, vbscript:, file:, or data:, otherwise 0.
334 | bufsize_t _scan_dangerous_url(const unsigned char *p)
335 | {
336 | const unsigned char *marker = NULL;
337 | const unsigned char *start = p;
338 | /*!re2c
339 | 'data:image/' ('png'|'gif'|'jpeg'|'webp') { return 0; }
340 | 'javascript:' | 'vbscript:' | 'file:' | 'data:' { return (bufsize_t)(p - start); }
341 | * { return 0; }
342 | */
343 | }
344 |
345 |
--------------------------------------------------------------------------------
/Sources/cmark/utf8.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "cmark_ctype.h"
6 | #include "utf8.h"
7 |
8 | static const int8_t utf8proc_utf8class[256] = {
9 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
10 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
11 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
12 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
13 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
14 | 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
15 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
16 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
17 | 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
18 | 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
19 | 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0};
20 |
21 | static void encode_unknown(cmark_strbuf *buf) {
22 | static const uint8_t repl[] = {239, 191, 189};
23 | cmark_strbuf_put(buf, repl, 3);
24 | }
25 |
26 | static int utf8proc_charlen(const uint8_t *str, bufsize_t str_len) {
27 | int length, i;
28 |
29 | if (!str_len)
30 | return 0;
31 |
32 | length = utf8proc_utf8class[str[0]];
33 |
34 | if (!length)
35 | return -1;
36 |
37 | if (str_len >= 0 && (bufsize_t)length > str_len)
38 | return -str_len;
39 |
40 | for (i = 1; i < length; i++) {
41 | if ((str[i] & 0xC0) != 0x80)
42 | return -i;
43 | }
44 |
45 | return length;
46 | }
47 |
48 | // Validate a single UTF-8 character according to RFC 3629.
49 | static int utf8proc_valid(const uint8_t *str, bufsize_t str_len) {
50 | int length = utf8proc_utf8class[str[0]];
51 |
52 | if (!length)
53 | return -1;
54 |
55 | if ((bufsize_t)length > str_len)
56 | return -str_len;
57 |
58 | switch (length) {
59 | case 2:
60 | if ((str[1] & 0xC0) != 0x80)
61 | return -1;
62 | if (str[0] < 0xC2) {
63 | // Overlong
64 | return -length;
65 | }
66 | break;
67 |
68 | case 3:
69 | if ((str[1] & 0xC0) != 0x80)
70 | return -1;
71 | if ((str[2] & 0xC0) != 0x80)
72 | return -2;
73 | if (str[0] == 0xE0) {
74 | if (str[1] < 0xA0) {
75 | // Overlong
76 | return -length;
77 | }
78 | } else if (str[0] == 0xED) {
79 | if (str[1] >= 0xA0) {
80 | // Surrogate
81 | return -length;
82 | }
83 | }
84 | break;
85 |
86 | case 4:
87 | if ((str[1] & 0xC0) != 0x80)
88 | return -1;
89 | if ((str[2] & 0xC0) != 0x80)
90 | return -2;
91 | if ((str[3] & 0xC0) != 0x80)
92 | return -3;
93 | if (str[0] == 0xF0) {
94 | if (str[1] < 0x90) {
95 | // Overlong
96 | return -length;
97 | }
98 | } else if (str[0] >= 0xF4) {
99 | if (str[0] > 0xF4 || str[1] >= 0x90) {
100 | // Above 0x10FFFF
101 | return -length;
102 | }
103 | }
104 | break;
105 | }
106 |
107 | return length;
108 | }
109 |
110 | void cmark_utf8proc_check(cmark_strbuf *ob, const uint8_t *line,
111 | bufsize_t size) {
112 | bufsize_t i = 0;
113 |
114 | while (i < size) {
115 | bufsize_t org = i;
116 | int charlen = 0;
117 |
118 | while (i < size) {
119 | if (line[i] < 0x80 && line[i] != 0) {
120 | i++;
121 | } else if (line[i] >= 0x80) {
122 | charlen = utf8proc_valid(line + i, size - i);
123 | if (charlen < 0) {
124 | charlen = -charlen;
125 | break;
126 | }
127 | i += charlen;
128 | } else if (line[i] == 0) {
129 | // ASCII NUL is technically valid but rejected
130 | // for security reasons.
131 | charlen = 1;
132 | break;
133 | }
134 | }
135 |
136 | if (i > org) {
137 | cmark_strbuf_put(ob, line + org, i - org);
138 | }
139 |
140 | if (i >= size) {
141 | break;
142 | } else {
143 | // Invalid UTF-8
144 | encode_unknown(ob);
145 | i += charlen;
146 | }
147 | }
148 | }
149 |
150 | int cmark_utf8proc_iterate(const uint8_t *str, bufsize_t str_len,
151 | int32_t *dst) {
152 | int length;
153 | int32_t uc = -1;
154 |
155 | *dst = -1;
156 | length = utf8proc_charlen(str, str_len);
157 | if (length < 0)
158 | return -1;
159 |
160 | switch (length) {
161 | case 1:
162 | uc = str[0];
163 | break;
164 | case 2:
165 | uc = ((str[0] & 0x1F) << 6) + (str[1] & 0x3F);
166 | if (uc < 0x80)
167 | uc = -1;
168 | break;
169 | case 3:
170 | uc = ((str[0] & 0x0F) << 12) + ((str[1] & 0x3F) << 6) + (str[2] & 0x3F);
171 | if (uc < 0x800 || (uc >= 0xD800 && uc < 0xE000))
172 | uc = -1;
173 | break;
174 | case 4:
175 | uc = ((str[0] & 0x07) << 18) + ((str[1] & 0x3F) << 12) +
176 | ((str[2] & 0x3F) << 6) + (str[3] & 0x3F);
177 | if (uc < 0x10000 || uc >= 0x110000)
178 | uc = -1;
179 | break;
180 | }
181 |
182 | if (uc < 0)
183 | return -1;
184 |
185 | *dst = uc;
186 | return length;
187 | }
188 |
189 | void cmark_utf8proc_encode_char(int32_t uc, cmark_strbuf *buf) {
190 | uint8_t dst[4];
191 | bufsize_t len = 0;
192 |
193 | assert(uc >= 0);
194 |
195 | if (uc < 0x80) {
196 | dst[0] = (uint8_t)(uc);
197 | len = 1;
198 | } else if (uc < 0x800) {
199 | dst[0] = (uint8_t)(0xC0 + (uc >> 6));
200 | dst[1] = 0x80 + (uc & 0x3F);
201 | len = 2;
202 | } else if (uc == 0xFFFF) {
203 | dst[0] = 0xFF;
204 | len = 1;
205 | } else if (uc == 0xFFFE) {
206 | dst[0] = 0xFE;
207 | len = 1;
208 | } else if (uc < 0x10000) {
209 | dst[0] = (uint8_t)(0xE0 + (uc >> 12));
210 | dst[1] = 0x80 + ((uc >> 6) & 0x3F);
211 | dst[2] = 0x80 + (uc & 0x3F);
212 | len = 3;
213 | } else if (uc < 0x110000) {
214 | dst[0] = (uint8_t)(0xF0 + (uc >> 18));
215 | dst[1] = 0x80 + ((uc >> 12) & 0x3F);
216 | dst[2] = 0x80 + ((uc >> 6) & 0x3F);
217 | dst[3] = 0x80 + (uc & 0x3F);
218 | len = 4;
219 | } else {
220 | encode_unknown(buf);
221 | return;
222 | }
223 |
224 | cmark_strbuf_put(buf, dst, len);
225 | }
226 |
227 | void cmark_utf8proc_case_fold(cmark_strbuf *dest, const uint8_t *str,
228 | bufsize_t len) {
229 | int32_t c;
230 |
231 | #define bufpush(x) cmark_utf8proc_encode_char(x, dest)
232 |
233 | while (len > 0) {
234 | bufsize_t char_len = cmark_utf8proc_iterate(str, len, &c);
235 |
236 | if (char_len >= 0) {
237 | #include "case_fold_switch.inc"
238 | } else {
239 | encode_unknown(dest);
240 | char_len = -char_len;
241 | }
242 |
243 | str += char_len;
244 | len -= char_len;
245 | }
246 | }
247 |
248 | // matches anything in the Zs class, plus LF, CR, TAB, FF.
249 | int cmark_utf8proc_is_space(int32_t uc) {
250 | return (uc == 9 || uc == 10 || uc == 12 || uc == 13 || uc == 32 ||
251 | uc == 160 || uc == 5760 || (uc >= 8192 && uc <= 8202) || uc == 8239 ||
252 | uc == 8287 || uc == 12288);
253 | }
254 |
255 | // matches anything in the P[cdefios] classes.
256 | int cmark_utf8proc_is_punctuation(int32_t uc) {
257 | return (
258 | (uc < 128 && cmark_ispunct((char)uc)) || uc == 161 || uc == 167 ||
259 | uc == 171 || uc == 182 || uc == 183 || uc == 187 || uc == 191 ||
260 | uc == 894 || uc == 903 || (uc >= 1370 && uc <= 1375) || uc == 1417 ||
261 | uc == 1418 || uc == 1470 || uc == 1472 || uc == 1475 || uc == 1478 ||
262 | uc == 1523 || uc == 1524 || uc == 1545 || uc == 1546 || uc == 1548 ||
263 | uc == 1549 || uc == 1563 || uc == 1566 || uc == 1567 ||
264 | (uc >= 1642 && uc <= 1645) || uc == 1748 || (uc >= 1792 && uc <= 1805) ||
265 | (uc >= 2039 && uc <= 2041) || (uc >= 2096 && uc <= 2110) || uc == 2142 ||
266 | uc == 2404 || uc == 2405 || uc == 2416 || uc == 2800 || uc == 3572 ||
267 | uc == 3663 || uc == 3674 || uc == 3675 || (uc >= 3844 && uc <= 3858) ||
268 | uc == 3860 || (uc >= 3898 && uc <= 3901) || uc == 3973 ||
269 | (uc >= 4048 && uc <= 4052) || uc == 4057 || uc == 4058 ||
270 | (uc >= 4170 && uc <= 4175) || uc == 4347 || (uc >= 4960 && uc <= 4968) ||
271 | uc == 5120 || uc == 5741 || uc == 5742 || uc == 5787 || uc == 5788 ||
272 | (uc >= 5867 && uc <= 5869) || uc == 5941 || uc == 5942 ||
273 | (uc >= 6100 && uc <= 6102) || (uc >= 6104 && uc <= 6106) ||
274 | (uc >= 6144 && uc <= 6154) || uc == 6468 || uc == 6469 || uc == 6686 ||
275 | uc == 6687 || (uc >= 6816 && uc <= 6822) || (uc >= 6824 && uc <= 6829) ||
276 | (uc >= 7002 && uc <= 7008) || (uc >= 7164 && uc <= 7167) ||
277 | (uc >= 7227 && uc <= 7231) || uc == 7294 || uc == 7295 ||
278 | (uc >= 7360 && uc <= 7367) || uc == 7379 || (uc >= 8208 && uc <= 8231) ||
279 | (uc >= 8240 && uc <= 8259) || (uc >= 8261 && uc <= 8273) ||
280 | (uc >= 8275 && uc <= 8286) || uc == 8317 || uc == 8318 || uc == 8333 ||
281 | uc == 8334 || (uc >= 8968 && uc <= 8971) || uc == 9001 || uc == 9002 ||
282 | (uc >= 10088 && uc <= 10101) || uc == 10181 || uc == 10182 ||
283 | (uc >= 10214 && uc <= 10223) || (uc >= 10627 && uc <= 10648) ||
284 | (uc >= 10712 && uc <= 10715) || uc == 10748 || uc == 10749 ||
285 | (uc >= 11513 && uc <= 11516) || uc == 11518 || uc == 11519 ||
286 | uc == 11632 || (uc >= 11776 && uc <= 11822) ||
287 | (uc >= 11824 && uc <= 11842) || (uc >= 12289 && uc <= 12291) ||
288 | (uc >= 12296 && uc <= 12305) || (uc >= 12308 && uc <= 12319) ||
289 | uc == 12336 || uc == 12349 || uc == 12448 || uc == 12539 || uc == 42238 ||
290 | uc == 42239 || (uc >= 42509 && uc <= 42511) || uc == 42611 ||
291 | uc == 42622 || (uc >= 42738 && uc <= 42743) ||
292 | (uc >= 43124 && uc <= 43127) || uc == 43214 || uc == 43215 ||
293 | (uc >= 43256 && uc <= 43258) || uc == 43310 || uc == 43311 ||
294 | uc == 43359 || (uc >= 43457 && uc <= 43469) || uc == 43486 ||
295 | uc == 43487 || (uc >= 43612 && uc <= 43615) || uc == 43742 ||
296 | uc == 43743 || uc == 43760 || uc == 43761 || uc == 44011 || uc == 64830 ||
297 | uc == 64831 || (uc >= 65040 && uc <= 65049) ||
298 | (uc >= 65072 && uc <= 65106) || (uc >= 65108 && uc <= 65121) ||
299 | uc == 65123 || uc == 65128 || uc == 65130 || uc == 65131 ||
300 | (uc >= 65281 && uc <= 65283) || (uc >= 65285 && uc <= 65290) ||
301 | (uc >= 65292 && uc <= 65295) || uc == 65306 || uc == 65307 ||
302 | uc == 65311 || uc == 65312 || (uc >= 65339 && uc <= 65341) ||
303 | uc == 65343 || uc == 65371 || uc == 65373 ||
304 | (uc >= 65375 && uc <= 65381) || (uc >= 65792 && uc <= 65794) ||
305 | uc == 66463 || uc == 66512 || uc == 66927 || uc == 67671 || uc == 67871 ||
306 | uc == 67903 || (uc >= 68176 && uc <= 68184) || uc == 68223 ||
307 | (uc >= 68336 && uc <= 68342) || (uc >= 68409 && uc <= 68415) ||
308 | (uc >= 68505 && uc <= 68508) || (uc >= 69703 && uc <= 69709) ||
309 | uc == 69819 || uc == 69820 || (uc >= 69822 && uc <= 69825) ||
310 | (uc >= 69952 && uc <= 69955) || uc == 70004 || uc == 70005 ||
311 | (uc >= 70085 && uc <= 70088) || uc == 70093 ||
312 | (uc >= 70200 && uc <= 70205) || uc == 70854 ||
313 | (uc >= 71105 && uc <= 71113) || (uc >= 71233 && uc <= 71235) ||
314 | (uc >= 74864 && uc <= 74868) || uc == 92782 || uc == 92783 ||
315 | uc == 92917 || (uc >= 92983 && uc <= 92987) || uc == 92996 ||
316 | uc == 113823);
317 | }
318 |
--------------------------------------------------------------------------------
/Sources/cmark/latex.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 |
6 | #include "config.h"
7 | #include "cmark.h"
8 | #include "node.h"
9 | #include "buffer.h"
10 | #include "utf8.h"
11 | #include "scanners.h"
12 | #include "render.h"
13 |
14 | #define OUT(s, wrap, escaping) renderer->out(renderer, s, wrap, escaping)
15 | #define LIT(s) renderer->out(renderer, s, false, LITERAL)
16 | #define CR() renderer->cr(renderer)
17 | #define BLANKLINE() renderer->blankline(renderer)
18 | #define LIST_NUMBER_STRING_SIZE 20
19 |
20 | static CMARK_INLINE void outc(cmark_renderer *renderer, cmark_escaping escape,
21 | int32_t c, unsigned char nextc) {
22 | if (escape == LITERAL) {
23 | cmark_render_code_point(renderer, c);
24 | return;
25 | }
26 |
27 | switch (c) {
28 | case 123: // '{'
29 | case 125: // '}'
30 | case 35: // '#'
31 | case 37: // '%'
32 | case 38: // '&'
33 | cmark_render_ascii(renderer, "\\");
34 | cmark_render_code_point(renderer, c);
35 | break;
36 | case 36: // '$'
37 | case 95: // '_'
38 | if (escape == NORMAL) {
39 | cmark_render_ascii(renderer, "\\");
40 | }
41 | cmark_render_code_point(renderer, c);
42 | break;
43 | case 45: // '-'
44 | if (nextc == 45) { // prevent ligature
45 | cmark_render_ascii(renderer, "-{}");
46 | } else {
47 | cmark_render_ascii(renderer, "-");
48 | }
49 | break;
50 | case 126: // '~'
51 | if (escape == NORMAL) {
52 | cmark_render_ascii(renderer, "\\textasciitilde{}");
53 | } else {
54 | cmark_render_code_point(renderer, c);
55 | }
56 | break;
57 | case 94: // '^'
58 | cmark_render_ascii(renderer, "\\^{}");
59 | break;
60 | case 92: // '\\'
61 | if (escape == URL) {
62 | // / acts as path sep even on windows:
63 | cmark_render_ascii(renderer, "/");
64 | } else {
65 | cmark_render_ascii(renderer, "\\textbackslash{}");
66 | }
67 | break;
68 | case 124: // '|'
69 | cmark_render_ascii(renderer, "\\textbar{}");
70 | break;
71 | case 60: // '<'
72 | cmark_render_ascii(renderer, "\\textless{}");
73 | break;
74 | case 62: // '>'
75 | cmark_render_ascii(renderer, "\\textgreater{}");
76 | break;
77 | case 91: // '['
78 | case 93: // ']'
79 | cmark_render_ascii(renderer, "{");
80 | cmark_render_code_point(renderer, c);
81 | cmark_render_ascii(renderer, "}");
82 | break;
83 | case 34: // '"'
84 | cmark_render_ascii(renderer, "\\textquotedbl{}");
85 | // requires \usepackage[T1]{fontenc}
86 | break;
87 | case 39: // '\''
88 | cmark_render_ascii(renderer, "\\textquotesingle{}");
89 | // requires \usepackage{textcomp}
90 | break;
91 | case 160: // nbsp
92 | cmark_render_ascii(renderer, "~");
93 | break;
94 | case 8230: // hellip
95 | cmark_render_ascii(renderer, "\\ldots{}");
96 | break;
97 | case 8216: // lsquo
98 | if (escape == NORMAL) {
99 | cmark_render_ascii(renderer, "`");
100 | } else {
101 | cmark_render_code_point(renderer, c);
102 | }
103 | break;
104 | case 8217: // rsquo
105 | if (escape == NORMAL) {
106 | cmark_render_ascii(renderer, "\'");
107 | } else {
108 | cmark_render_code_point(renderer, c);
109 | }
110 | break;
111 | case 8220: // ldquo
112 | if (escape == NORMAL) {
113 | cmark_render_ascii(renderer, "``");
114 | } else {
115 | cmark_render_code_point(renderer, c);
116 | }
117 | break;
118 | case 8221: // rdquo
119 | if (escape == NORMAL) {
120 | cmark_render_ascii(renderer, "''");
121 | } else {
122 | cmark_render_code_point(renderer, c);
123 | }
124 | break;
125 | case 8212: // emdash
126 | if (escape == NORMAL) {
127 | cmark_render_ascii(renderer, "---");
128 | } else {
129 | cmark_render_code_point(renderer, c);
130 | }
131 | break;
132 | case 8211: // endash
133 | if (escape == NORMAL) {
134 | cmark_render_ascii(renderer, "--");
135 | } else {
136 | cmark_render_code_point(renderer, c);
137 | }
138 | break;
139 | default:
140 | cmark_render_code_point(renderer, c);
141 | }
142 | }
143 |
144 | typedef enum {
145 | NO_LINK,
146 | URL_AUTOLINK,
147 | EMAIL_AUTOLINK,
148 | NORMAL_LINK,
149 | INTERNAL_LINK
150 | } link_type;
151 |
152 | static link_type get_link_type(cmark_node *node) {
153 | size_t title_len, url_len;
154 | cmark_node *link_text;
155 | char *realurl;
156 | int realurllen;
157 | bool isemail = false;
158 |
159 | if (node->type != CMARK_NODE_LINK) {
160 | return NO_LINK;
161 | }
162 |
163 | const char *url = cmark_node_get_url(node);
164 | cmark_chunk url_chunk = cmark_chunk_literal(url);
165 |
166 | if (url && *url == '#') {
167 | return INTERNAL_LINK;
168 | }
169 |
170 | url_len = strlen(url);
171 | if (url_len == 0 || scan_scheme(&url_chunk, 0) == 0) {
172 | return NO_LINK;
173 | }
174 |
175 | const char *title = cmark_node_get_title(node);
176 | title_len = strlen(title);
177 | // if it has a title, we can't treat it as an autolink:
178 | if (title_len == 0) {
179 |
180 | link_text = node->first_child;
181 | cmark_consolidate_text_nodes(link_text);
182 |
183 | if (!link_text)
184 | return NO_LINK;
185 |
186 | realurl = (char *)url;
187 | realurllen = (int)url_len;
188 | if (strncmp(realurl, "mailto:", 7) == 0) {
189 | realurl += 7;
190 | realurllen -= 7;
191 | isemail = true;
192 | }
193 | if (realurllen == link_text->len &&
194 | strncmp(realurl, (char *)link_text->data,
195 | link_text->len) == 0) {
196 | if (isemail) {
197 | return EMAIL_AUTOLINK;
198 | } else {
199 | return URL_AUTOLINK;
200 | }
201 | }
202 | }
203 |
204 | return NORMAL_LINK;
205 | }
206 |
207 | static int S_get_enumlevel(cmark_node *node) {
208 | int enumlevel = 0;
209 | cmark_node *tmp = node;
210 | while (tmp) {
211 | if (tmp->type == CMARK_NODE_LIST &&
212 | cmark_node_get_list_type(node) == CMARK_ORDERED_LIST) {
213 | enumlevel++;
214 | }
215 | tmp = tmp->parent;
216 | }
217 | return enumlevel;
218 | }
219 |
220 | static int S_render_node(cmark_renderer *renderer, cmark_node *node,
221 | cmark_event_type ev_type, int options) {
222 | int list_number;
223 | int enumlevel;
224 | char list_number_string[LIST_NUMBER_STRING_SIZE];
225 | bool entering = (ev_type == CMARK_EVENT_ENTER);
226 | cmark_list_type list_type;
227 | bool allow_wrap = renderer->width > 0 && !(CMARK_OPT_NOBREAKS & options);
228 |
229 | // avoid warning about unused parameter:
230 | (void)(options);
231 |
232 | switch (node->type) {
233 | case CMARK_NODE_DOCUMENT:
234 | break;
235 |
236 | case CMARK_NODE_BLOCK_QUOTE:
237 | if (entering) {
238 | LIT("\\begin{quote}");
239 | CR();
240 | } else {
241 | LIT("\\end{quote}");
242 | BLANKLINE();
243 | }
244 | break;
245 |
246 | case CMARK_NODE_LIST:
247 | list_type = cmark_node_get_list_type(node);
248 | if (entering) {
249 | LIT("\\begin{");
250 | LIT(list_type == CMARK_ORDERED_LIST ? "enumerate" : "itemize");
251 | LIT("}");
252 | CR();
253 | list_number = cmark_node_get_list_start(node);
254 | if (list_number > 1) {
255 | enumlevel = S_get_enumlevel(node);
256 | // latex normally supports only five levels
257 | if (enumlevel >= 1 && enumlevel <= 5) {
258 | snprintf(list_number_string, LIST_NUMBER_STRING_SIZE, "%d",
259 | list_number);
260 | LIT("\\setcounter{enum");
261 | switch (enumlevel) {
262 | case 1: LIT("i"); break;
263 | case 2: LIT("ii"); break;
264 | case 3: LIT("iii"); break;
265 | case 4: LIT("iv"); break;
266 | case 5: LIT("v"); break;
267 | default: LIT("i"); break;
268 | }
269 | LIT("}{");
270 | OUT(list_number_string, false, NORMAL);
271 | LIT("}");
272 | }
273 | CR();
274 | }
275 | } else {
276 | LIT("\\end{");
277 | LIT(list_type == CMARK_ORDERED_LIST ? "enumerate" : "itemize");
278 | LIT("}");
279 | BLANKLINE();
280 | }
281 | break;
282 |
283 | case CMARK_NODE_ITEM:
284 | if (entering) {
285 | LIT("\\item ");
286 | } else {
287 | CR();
288 | }
289 | break;
290 |
291 | case CMARK_NODE_HEADING:
292 | if (entering) {
293 | switch (cmark_node_get_heading_level(node)) {
294 | case 1:
295 | LIT("\\section");
296 | break;
297 | case 2:
298 | LIT("\\subsection");
299 | break;
300 | case 3:
301 | LIT("\\subsubsection");
302 | break;
303 | case 4:
304 | LIT("\\paragraph");
305 | break;
306 | case 5:
307 | LIT("\\subparagraph");
308 | break;
309 | }
310 | LIT("{");
311 | } else {
312 | LIT("}");
313 | BLANKLINE();
314 | }
315 | break;
316 |
317 | case CMARK_NODE_CODE_BLOCK:
318 | CR();
319 | LIT("\\begin{verbatim}");
320 | CR();
321 | OUT(cmark_node_get_literal(node), false, LITERAL);
322 | CR();
323 | LIT("\\end{verbatim}");
324 | BLANKLINE();
325 | break;
326 |
327 | case CMARK_NODE_HTML_BLOCK:
328 | break;
329 |
330 | case CMARK_NODE_CUSTOM_BLOCK:
331 | CR();
332 | OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node),
333 | false, LITERAL);
334 | CR();
335 | break;
336 |
337 | case CMARK_NODE_THEMATIC_BREAK:
338 | BLANKLINE();
339 | LIT("\\begin{center}\\rule{0.5\\linewidth}{\\linethickness}\\end{center}");
340 | BLANKLINE();
341 | break;
342 |
343 | case CMARK_NODE_PARAGRAPH:
344 | if (!entering) {
345 | BLANKLINE();
346 | }
347 | break;
348 |
349 | case CMARK_NODE_TEXT:
350 | OUT(cmark_node_get_literal(node), allow_wrap, NORMAL);
351 | break;
352 |
353 | case CMARK_NODE_LINEBREAK:
354 | LIT("\\\\");
355 | CR();
356 | break;
357 |
358 | case CMARK_NODE_SOFTBREAK:
359 | if (options & CMARK_OPT_HARDBREAKS) {
360 | LIT("\\\\");
361 | CR();
362 | } else if (renderer->width == 0 && !(CMARK_OPT_NOBREAKS & options)) {
363 | CR();
364 | } else {
365 | OUT(" ", allow_wrap, NORMAL);
366 | }
367 | break;
368 |
369 | case CMARK_NODE_CODE:
370 | LIT("\\texttt{");
371 | OUT(cmark_node_get_literal(node), false, NORMAL);
372 | LIT("}");
373 | break;
374 |
375 | case CMARK_NODE_HTML_INLINE:
376 | break;
377 |
378 | case CMARK_NODE_CUSTOM_INLINE:
379 | OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node),
380 | false, LITERAL);
381 | break;
382 |
383 | case CMARK_NODE_STRONG:
384 | if (entering) {
385 | LIT("\\textbf{");
386 | } else {
387 | LIT("}");
388 | }
389 | break;
390 |
391 | case CMARK_NODE_EMPH:
392 | if (entering) {
393 | LIT("\\emph{");
394 | } else {
395 | LIT("}");
396 | }
397 | break;
398 |
399 | case CMARK_NODE_LINK:
400 | if (entering) {
401 | const char *url = cmark_node_get_url(node);
402 | // requires \usepackage{hyperref}
403 | switch (get_link_type(node)) {
404 | case URL_AUTOLINK:
405 | LIT("\\url{");
406 | OUT(url, false, URL);
407 | LIT("}");
408 | return 0; // Don't process further nodes to avoid double-rendering artefacts
409 | case EMAIL_AUTOLINK:
410 | LIT("\\href{");
411 | OUT(url, false, URL);
412 | LIT("}\\nolinkurl{");
413 | break;
414 | case NORMAL_LINK:
415 | LIT("\\href{");
416 | OUT(url, false, URL);
417 | LIT("}{");
418 | break;
419 | case INTERNAL_LINK:
420 | LIT("\\protect\\hyperlink{");
421 | OUT(url + 1, false, URL);
422 | LIT("}{");
423 | break;
424 | case NO_LINK:
425 | LIT("{"); // error?
426 | }
427 | } else {
428 | LIT("}");
429 | }
430 |
431 | break;
432 |
433 | case CMARK_NODE_IMAGE:
434 | if (entering) {
435 | LIT("\\protect\\includegraphics{");
436 | // requires \include{graphicx}
437 | OUT(cmark_node_get_url(node), false, URL);
438 | LIT("}");
439 | return 0;
440 | }
441 | break;
442 |
443 | default:
444 | assert(false);
445 | break;
446 | }
447 |
448 | return 1;
449 | }
450 |
451 | char *cmark_render_latex(cmark_node *root, int options, int width) {
452 | return cmark_render(root, options, width, outc, S_render_node);
453 | }
454 |
--------------------------------------------------------------------------------