| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <ctype.h> |
| |
| #include "config.h" |
| #include "cmark.h" |
| #include "node.h" |
| #include "buffer.h" |
| #include "utf8.h" |
| #include "scanners.h" |
| #include "render.h" |
| |
| #define safe_strlen(s) cmark_strbuf_safe_strlen(s) |
| #define OUT(s, wrap, escaping) renderer->out(renderer, s, wrap, escaping) |
| #define LIT(s) renderer->out(renderer, s, false, LITERAL) |
| #define CR() renderer->cr(renderer) |
| #define BLANKLINE() renderer->blankline(renderer) |
| |
| static inline void outc(cmark_renderer *renderer, cmark_escaping escape, |
| int32_t c, unsigned char nextc) { |
| if (escape == LITERAL) { |
| cmark_render_code_point(renderer, c); |
| return; |
| } |
| |
| switch (c) { |
| case 123: // '{' |
| case 125: // '}' |
| case 35: // '#' |
| case 37: // '%' |
| case 38: // '&' |
| cmark_render_ascii(renderer, "\\"); |
| cmark_render_code_point(renderer, c); |
| break; |
| case 36: // '$' |
| case 95: // '_' |
| if (escape == NORMAL) { |
| cmark_render_ascii(renderer, "\\"); |
| } |
| cmark_render_code_point(renderer, c); |
| break; |
| case 45: // '-' |
| if (nextc == 45) { // prevent ligature |
| cmark_render_ascii(renderer, "\\-"); |
| } else { |
| cmark_render_ascii(renderer, "-"); |
| } |
| break; |
| case 126: // '~' |
| if (escape == NORMAL) { |
| cmark_render_ascii(renderer, "\\textasciitilde{}"); |
| } else { |
| cmark_render_code_point(renderer, c); |
| } |
| break; |
| case 94: // '^' |
| cmark_render_ascii(renderer, "\\^{}"); |
| break; |
| case 92: // '\\' |
| if (escape == URL) { |
| // / acts as path sep even on windows: |
| cmark_render_ascii(renderer, "/"); |
| } else { |
| cmark_render_ascii(renderer, "\\textbackslash{}"); |
| } |
| break; |
| case 124: // '|' |
| cmark_render_ascii(renderer, "\\textbar{}"); |
| break; |
| case 60: // '<' |
| cmark_render_ascii(renderer, "\\textless{}"); |
| break; |
| case 62: // '>' |
| cmark_render_ascii(renderer, "\\textgreater{}"); |
| break; |
| case 91: // '[' |
| case 93: // ']' |
| cmark_render_ascii(renderer, "{"); |
| cmark_render_code_point(renderer, c); |
| cmark_render_ascii(renderer, "}"); |
| break; |
| case 34: // '"' |
| cmark_render_ascii(renderer, "\\textquotedbl{}"); |
| // requires \usepackage[T1]{fontenc} |
| break; |
| case 39: // '\'' |
| cmark_render_ascii(renderer, "\\textquotesingle{}"); |
| // requires \usepackage{textcomp} |
| break; |
| case 160: // nbsp |
| cmark_render_ascii(renderer, "~"); |
| break; |
| case 8230: // hellip |
| cmark_render_ascii(renderer, "\\ldots{}"); |
| break; |
| case 8216: // lsquo |
| if (escape == NORMAL) { |
| cmark_render_ascii(renderer, "`"); |
| } else { |
| cmark_render_code_point(renderer, c); |
| } |
| break; |
| case 8217: // rsquo |
| if (escape == NORMAL) { |
| cmark_render_ascii(renderer, "\'"); |
| } else { |
| cmark_render_code_point(renderer, c); |
| } |
| break; |
| case 8220: // ldquo |
| if (escape == NORMAL) { |
| cmark_render_ascii(renderer, "``"); |
| } else { |
| cmark_render_code_point(renderer, c); |
| } |
| break; |
| case 8221: // rdquo |
| if (escape == NORMAL) { |
| cmark_render_ascii(renderer, "''"); |
| } else { |
| cmark_render_code_point(renderer, c); |
| } |
| break; |
| case 8212: // emdash |
| if (escape == NORMAL) { |
| cmark_render_ascii(renderer, "---"); |
| } else { |
| cmark_render_code_point(renderer, c); |
| } |
| break; |
| case 8211: // endash |
| if (escape == NORMAL) { |
| cmark_render_ascii(renderer, "--"); |
| } else { |
| cmark_render_code_point(renderer, c); |
| } |
| break; |
| default: |
| cmark_render_code_point(renderer, c); |
| } |
| } |
| |
| typedef enum { NO_LINK, URL_AUTOLINK, EMAIL_AUTOLINK, NORMAL_LINK } link_type; |
| |
| static link_type get_link_type(cmark_node *node) { |
| size_t title_len, url_len; |
| cmark_node *link_text; |
| char *realurl; |
| int realurllen; |
| bool isemail = false; |
| |
| if (node->type != CMARK_NODE_LINK) { |
| return NO_LINK; |
| } |
| |
| const char *url = cmark_node_get_url(node); |
| cmark_chunk url_chunk = cmark_chunk_literal(url); |
| |
| url_len = safe_strlen(url); |
| if (url_len == 0 || scan_scheme(&url_chunk, 0) == 0) { |
| return NO_LINK; |
| } |
| |
| const char *title = cmark_node_get_title(node); |
| title_len = safe_strlen(title); |
| // if it has a title, we can't treat it as an autolink: |
| if (title_len > 0) { |
| return NORMAL_LINK; |
| } |
| |
| link_text = node->first_child; |
| cmark_consolidate_text_nodes(link_text); |
| realurl = (char *)url; |
| realurllen = url_len; |
| if (strncmp(realurl, "mailto:", 7) == 0) { |
| realurl += 7; |
| realurllen -= 7; |
| isemail = true; |
| } |
| if (realurllen == link_text->as.literal.len && |
| strncmp(realurl, (char *)link_text->as.literal.data, |
| link_text->as.literal.len) == 0) { |
| if (isemail) { |
| return EMAIL_AUTOLINK; |
| } else { |
| return URL_AUTOLINK; |
| } |
| } else { |
| return NORMAL_LINK; |
| } |
| } |
| |
| static int S_get_enumlevel(cmark_node *node) { |
| int enumlevel = 0; |
| cmark_node *tmp = node; |
| while (tmp) { |
| if (tmp->type == CMARK_NODE_LIST && |
| cmark_node_get_list_type(node) == CMARK_ORDERED_LIST) { |
| enumlevel++; |
| } |
| tmp = tmp->parent; |
| } |
| return enumlevel; |
| } |
| |
| static int S_render_node(cmark_renderer *renderer, cmark_node *node, |
| cmark_event_type ev_type, int options) { |
| int list_number; |
| char list_number_string[20]; |
| bool entering = (ev_type == CMARK_EVENT_ENTER); |
| cmark_list_type list_type; |
| const char *roman_numerals[] = {"", "i", "ii", "iii", "iv", "v", |
| "vi", "vii", "viii", "ix", "x"}; |
| |
| // avoid warning about unused parameter: |
| (void)(options); |
| |
| switch (node->type) { |
| case CMARK_NODE_DOCUMENT: |
| break; |
| |
| case CMARK_NODE_BLOCK_QUOTE: |
| if (entering) { |
| LIT("\\begin{quote}"); |
| CR(); |
| } else { |
| LIT("\\end{quote}"); |
| BLANKLINE(); |
| } |
| break; |
| |
| case CMARK_NODE_LIST: |
| list_type = cmark_node_get_list_type(node); |
| if (entering) { |
| LIT("\\begin{"); |
| LIT(list_type == CMARK_ORDERED_LIST ? "enumerate" : "itemize"); |
| LIT("}"); |
| CR(); |
| list_number = cmark_node_get_list_start(node); |
| if (list_number > 1) { |
| sprintf(list_number_string, "%d", list_number); |
| LIT("\\setcounter{enum"); |
| LIT((char *)roman_numerals[S_get_enumlevel(node)]); |
| LIT("}{"); |
| OUT(list_number_string, false, NORMAL); |
| LIT("}"); |
| CR(); |
| } |
| } else { |
| LIT("\\end{"); |
| LIT(list_type == CMARK_ORDERED_LIST ? "enumerate" : "itemize"); |
| LIT("}"); |
| BLANKLINE(); |
| } |
| break; |
| |
| case CMARK_NODE_ITEM: |
| if (entering) { |
| LIT("\\item "); |
| } else { |
| CR(); |
| } |
| break; |
| |
| case CMARK_NODE_HEADER: |
| if (entering) { |
| switch (cmark_node_get_header_level(node)) { |
| case 1: |
| LIT("\\section"); |
| break; |
| case 2: |
| LIT("\\subsection"); |
| break; |
| case 3: |
| LIT("\\subsubsection"); |
| break; |
| case 4: |
| LIT("\\paragraph"); |
| break; |
| case 5: |
| LIT("\\subparagraph"); |
| break; |
| } |
| LIT("{"); |
| } else { |
| LIT("}"); |
| BLANKLINE(); |
| } |
| break; |
| |
| case CMARK_NODE_CODE_BLOCK: |
| CR(); |
| LIT("\\begin{verbatim}"); |
| CR(); |
| OUT(cmark_node_get_literal(node), false, LITERAL); |
| CR(); |
| LIT("\\end{verbatim}"); |
| BLANKLINE(); |
| break; |
| |
| case CMARK_NODE_HTML: |
| break; |
| |
| case CMARK_NODE_HRULE: |
| BLANKLINE(); |
| LIT("\\begin{center}\\rule{0.5\\linewidth}{\\linethickness}\\end{center}"); |
| BLANKLINE(); |
| break; |
| |
| case CMARK_NODE_PARAGRAPH: |
| if (!entering) { |
| BLANKLINE(); |
| } |
| break; |
| |
| case CMARK_NODE_TEXT: |
| OUT(cmark_node_get_literal(node), true, NORMAL); |
| break; |
| |
| case CMARK_NODE_LINEBREAK: |
| LIT("\\\\"); |
| CR(); |
| break; |
| |
| case CMARK_NODE_SOFTBREAK: |
| if (renderer->width == 0) { |
| CR(); |
| } else { |
| OUT(" ", true, NORMAL); |
| } |
| break; |
| |
| case CMARK_NODE_CODE: |
| LIT("\\texttt{"); |
| OUT(cmark_node_get_literal(node), false, NORMAL); |
| LIT("}"); |
| break; |
| |
| case CMARK_NODE_INLINE_HTML: |
| break; |
| |
| case CMARK_NODE_STRONG: |
| if (entering) { |
| LIT("\\textbf{"); |
| } else { |
| LIT("}"); |
| } |
| break; |
| |
| case CMARK_NODE_EMPH: |
| if (entering) { |
| LIT("\\emph{"); |
| } else { |
| LIT("}"); |
| } |
| break; |
| |
| case CMARK_NODE_LINK: |
| if (entering) { |
| const char *url = cmark_node_get_url(node); |
| // requires \usepackage{hyperref} |
| switch (get_link_type(node)) { |
| case URL_AUTOLINK: |
| LIT("\\url{"); |
| OUT(url, false, URL); |
| break; |
| case EMAIL_AUTOLINK: |
| LIT("\\href{"); |
| OUT(url, false, URL); |
| LIT("}\\nolinkurl{"); |
| break; |
| case NORMAL_LINK: |
| LIT("\\href{"); |
| OUT(url, false, URL); |
| LIT("}{"); |
| break; |
| case NO_LINK: |
| LIT("{"); // error? |
| } |
| } else { |
| LIT("}"); |
| } |
| |
| break; |
| |
| case CMARK_NODE_IMAGE: |
| if (entering) { |
| LIT("\\protect\\includegraphics{"); |
| // requires \include{graphicx} |
| OUT(cmark_node_get_url(node), false, URL); |
| LIT("}"); |
| return 0; |
| } |
| break; |
| |
| default: |
| assert(false); |
| break; |
| } |
| |
| return 1; |
| } |
| |
| char *cmark_render_latex(cmark_node *root, int options, int width) { |
| return cmark_render(root, options, width, outc, S_render_node); |
| } |