blob: 146062e20c70a949e625006e63596dac5b0b2a53 [file] [log] [blame]
#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);
}