static void md_block_external_images(cmark_iter *const iter) { for(;;) { cmark_event_type const event = cmark_iter_next(iter); if(CMARK_EVENT_DONE == event) break; if(CMARK_EVENT_EXIT != event) continue; cmark_node *const node = cmark_iter_get_node(iter); if(CMARK_NODE_IMAGE != cmark_node_get_type(node)) continue; char const *const URI = cmark_node_get_url(node); if(URI) { if(0 == strncasecmp(URI, STR_LEN("hash:"))) continue; if(0 == strncasecmp(URI, STR_LEN("data:"))) continue; } cmark_node *link = cmark_node_new(CMARK_NODE_LINK); cmark_node *text = cmark_node_new(CMARK_NODE_TEXT); cmark_node_set_url(link, URI); for(;;) { cmark_node *child = cmark_node_first_child(node); if(!child) break; cmark_node_append_child(link, child); } if(cmark_node_first_child(link)) { cmark_node_set_literal(text, " (external image)"); } else { cmark_node_set_literal(text, "(external image)"); } cmark_node_append_child(link, text); cmark_node_insert_before(node, link); cmark_node_free(node); } }
static void md_convert_hashes(cmark_iter *const iter) { for(;;) { cmark_event_type const event = cmark_iter_next(iter); if(CMARK_EVENT_DONE == event) break; if(CMARK_EVENT_EXIT != event) continue; cmark_node *const node = cmark_iter_get_node(iter); cmark_node_type const type = cmark_node_get_type(node); if(CMARK_NODE_LINK != type && CMARK_NODE_IMAGE != type) continue; char const *const URI = cmark_node_get_url(node); if(!URI) continue; if(0 != strncasecmp(URI, STR_LEN("hash:"))) continue; cmark_node *sup = superscript("#", HASH_INFO_MSG, URI); cmark_node_insert_after(node, sup); cmark_iter_reset(iter, sup, CMARK_EVENT_EXIT); char *escaped = QSEscape(URI, strlen(URI), true); size_t const elen = strlen(escaped); cmark_strbuf rel[1]; char const qpfx[] = "/sources/"; cmark_strbuf_init(&DEFAULT_MEM_ALLOCATOR, rel, sizeof(qpfx)-1+elen); cmark_strbuf_put(rel, (unsigned char const *)qpfx, sizeof(qpfx)-1); cmark_strbuf_put(rel, (unsigned char const *)escaped, elen); free(escaped); escaped = NULL; cmark_node_set_url(node, cmark_strbuf_cstr(rel)); cmark_strbuf_free(rel); } }
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); if (url && *url == '#') { return INTERNAL_LINK; } url_len = 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 = strlen(title); // if it has a title, we can't treat it as an autolink: if (title_len == 0) { link_text = node->first_child; cmark_consolidate_text_nodes(link_text); if (!link_text) return NO_LINK; realurl = (char *)url; realurllen = (int)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; } } } return NORMAL_LINK; }
static int S_render_node(cmark_renderer *renderer, cmark_node *node, cmark_event_type ev_type, int options) { cmark_node *tmp; int list_number; cmark_delim_type list_delim; int numticks; bool extra_spaces; int i; bool entering = (ev_type == CMARK_EVENT_ENTER); const char *info, *code, *title; char fencechar[2] = {'\0', '\0'}; size_t info_len, code_len; char listmarker[LISTMARKER_SIZE]; char *emph_delim; bool first_in_list_item; bufsize_t marker_width; bool allow_wrap = renderer->width > 0 && !(CMARK_OPT_NOBREAKS & options) && !(CMARK_OPT_HARDBREAKS & options); // Don't adjust tight list status til we've started the list. // Otherwise we loose the blank line between a paragraph and // a following list. if (!(node->type == CMARK_NODE_ITEM && node->prev == NULL && entering)) { tmp = get_containing_block(node); renderer->in_tight_list_item = tmp && // tmp might be NULL if there is no containing block ((tmp->type == CMARK_NODE_ITEM && cmark_node_get_list_tight(tmp->parent)) || (tmp && tmp->parent && tmp->parent->type == CMARK_NODE_ITEM && cmark_node_get_list_tight(tmp->parent->parent))); } switch (node->type) { case CMARK_NODE_DOCUMENT: break; case CMARK_NODE_BLOCK_QUOTE: if (entering) { LIT("> "); renderer->begin_content = true; cmark_strbuf_puts(renderer->prefix, "> "); } else { cmark_strbuf_truncate(renderer->prefix, renderer->prefix->size - 2); BLANKLINE(); } break; case CMARK_NODE_LIST: if (!entering && node->next && (node->next->type == CMARK_NODE_CODE_BLOCK || node->next->type == CMARK_NODE_LIST)) { // this ensures that a following indented code block or list will be // inteprereted correctly. CR(); LIT("<!-- end list -->"); BLANKLINE(); } break; case CMARK_NODE_ITEM: if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { marker_width = 4; } else { list_number = cmark_node_get_list_start(node->parent); list_delim = cmark_node_get_list_delim(node->parent); tmp = node; while (tmp->prev) { tmp = tmp->prev; list_number += 1; } // we ensure a width of at least 4 so // we get nice transition from single digits // to double snprintf(listmarker, LISTMARKER_SIZE, "%d%s%s", list_number, list_delim == CMARK_PAREN_DELIM ? ")" : ".", list_number < 10 ? " " : " "); marker_width = strlen(listmarker); } if (entering) { if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { LIT(" - "); renderer->begin_content = true; } else { LIT(listmarker); renderer->begin_content = true; } for (i = marker_width; i--;) { cmark_strbuf_putc(renderer->prefix, ' '); } } else { cmark_strbuf_truncate(renderer->prefix, renderer->prefix->size - marker_width); CR(); } break; case CMARK_NODE_HEADING: if (entering) { for (i = cmark_node_get_heading_level(node); i > 0; i--) { LIT("#"); } LIT(" "); renderer->begin_content = true; renderer->no_linebreaks = true; } else { renderer->no_linebreaks = false; BLANKLINE(); } break; case CMARK_NODE_CODE_BLOCK: first_in_list_item = node->prev == NULL && node->parent && node->parent->type == CMARK_NODE_ITEM; if (!first_in_list_item) { BLANKLINE(); } info = cmark_node_get_fence_info(node); info_len = strlen(info); fencechar[0] = strchr(info, '`') == NULL ? '`' : '~'; code = cmark_node_get_literal(node); code_len = strlen(code); // use indented form if no info, and code doesn't // begin or end with a blank line, and code isn't // first thing in a list item if (info_len == 0 && (code_len > 2 && !cmark_isspace(code[0]) && !(cmark_isspace(code[code_len - 1]) && cmark_isspace(code[code_len - 2]))) && !first_in_list_item) { LIT(" "); cmark_strbuf_puts(renderer->prefix, " "); OUT(cmark_node_get_literal(node), false, LITERAL); cmark_strbuf_truncate(renderer->prefix, renderer->prefix->size - 4); } else { numticks = longest_backtick_sequence(code) + 1; if (numticks < 3) { numticks = 3; } for (i = 0; i < numticks; i++) { LIT(fencechar); } LIT(" "); OUT(info, false, LITERAL); CR(); OUT(cmark_node_get_literal(node), false, LITERAL); CR(); for (i = 0; i < numticks; i++) { LIT(fencechar); } } BLANKLINE(); break; case CMARK_NODE_HTML_BLOCK: BLANKLINE(); OUT(cmark_node_get_literal(node), false, LITERAL); BLANKLINE(); break; case CMARK_NODE_CUSTOM_BLOCK: BLANKLINE(); OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node), false, LITERAL); BLANKLINE(); break; case CMARK_NODE_THEMATIC_BREAK: BLANKLINE(); LIT("-----"); BLANKLINE(); break; case CMARK_NODE_PARAGRAPH: if (!entering) { BLANKLINE(); } break; case CMARK_NODE_TEXT: OUT(cmark_node_get_literal(node), allow_wrap, NORMAL); break; case CMARK_NODE_LINEBREAK: if (!(CMARK_OPT_HARDBREAKS & options)) { LIT(" "); } CR(); break; case CMARK_NODE_SOFTBREAK: if (CMARK_OPT_HARDBREAKS & options) { LIT(" "); CR(); } else if (!renderer->no_linebreaks && renderer->width == 0 && !(CMARK_OPT_HARDBREAKS & options) && !(CMARK_OPT_NOBREAKS & options)) { CR(); } else { OUT(" ", allow_wrap, LITERAL); } break; case CMARK_NODE_CODE: code = cmark_node_get_literal(node); code_len = strlen(code); numticks = shortest_unused_backtick_sequence(code); extra_spaces = code_len == 0 || code[0] == '`' || code[code_len - 1] == '`' || code[0] == ' ' || code[code_len - 1] == ' '; for (i = 0; i < numticks; i++) { LIT("`"); } if (extra_spaces) { LIT(" "); } OUT(cmark_node_get_literal(node), allow_wrap, LITERAL); if (extra_spaces) { LIT(" "); } for (i = 0; i < numticks; i++) { LIT("`"); } break; case CMARK_NODE_HTML_INLINE: OUT(cmark_node_get_literal(node), false, LITERAL); break; case CMARK_NODE_CUSTOM_INLINE: OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node), false, LITERAL); break; case CMARK_NODE_STRONG: if (entering) { LIT("**"); } else { LIT("**"); } break; case CMARK_NODE_EMPH: // If we have EMPH(EMPH(x)), we need to use *_x_* // because **x** is STRONG(x): if (node->parent && node->parent->type == CMARK_NODE_EMPH && node->next == NULL && node->prev == NULL) { emph_delim = "_"; } else { emph_delim = "*"; } if (entering) { LIT(emph_delim); } else { LIT(emph_delim); } break; case CMARK_NODE_LINK: if (is_autolink(node)) { if (entering) { LIT("<"); if (strncmp(cmark_node_get_url(node), "mailto:", 7) == 0) { LIT((const char *)cmark_node_get_url(node) + 7); } else { LIT((const char *)cmark_node_get_url(node)); } LIT(">"); // return signal to skip contents of node... return 0; } } else { if (entering) { LIT("["); } else { LIT("]("); OUT(cmark_node_get_url(node), false, URL); title = cmark_node_get_title(node); if (strlen(title) > 0) { LIT(" \""); OUT(title, false, TITLE); LIT("\""); } LIT(")"); } } break; case CMARK_NODE_IMAGE: if (entering) { LIT("!["); } else { LIT("]("); OUT(cmark_node_get_url(node), false, URL); title = cmark_node_get_title(node); if (strlen(title) > 0) { OUT(" \"", allow_wrap, LITERAL); OUT(title, false, TITLE); LIT("\""); } LIT(")"); } break; default: assert(false); break; } return 1; }
static int S_render_node(cmark_node *node, cmark_event_type ev_type, struct render_state *state) { cmark_node *tmp; cmark_strbuf *man = state->man; int list_number; bool entering = (ev_type == CMARK_EVENT_ENTER); if (state->plain == node) { // back at original node state->plain = NULL; } if (state->plain != NULL) { switch(node->type) { case CMARK_NODE_TEXT: case CMARK_NODE_CODE: escape_man(man, node->as.literal.data, node->as.literal.len); break; case CMARK_NODE_LINEBREAK: case CMARK_NODE_SOFTBREAK: cmark_strbuf_putc(man, ' '); break; default: break; } return 1; } switch (node->type) { case CMARK_NODE_DOCUMENT: break; case CMARK_NODE_BLOCK_QUOTE: if (entering) { cr(man); cmark_strbuf_puts(man, ".RS"); cr(man); } else { cr(man); cmark_strbuf_puts(man, ".RE"); cr(man); } break; case CMARK_NODE_LIST: break; case CMARK_NODE_ITEM: if (entering) { cr(man); cmark_strbuf_puts(man, ".IP "); if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { cmark_strbuf_puts(man, "\\[bu] 2"); } else { list_number = cmark_node_get_list_start(node->parent); tmp = node; while (tmp->prev) { tmp = tmp->prev; list_number += 1; } cmark_strbuf_printf(man, "\"%d.\" 4", list_number); } cr(man); } else { cr(man); } break; case CMARK_NODE_HEADER: if (entering) { cr(man); cmark_strbuf_puts(man, cmark_node_get_header_level(node) == 1 ? ".SH" : ".SS"); cr(man); } else { cr(man); } break; case CMARK_NODE_CODE_BLOCK: cr(man); cmark_strbuf_puts(man, ".IP\n.nf\n\\f[C]\n"); escape_man(man, node->as.code.literal.data, node->as.code.literal.len); cr(man); cmark_strbuf_puts(man, "\\f[]\n.fi"); cr(man); break; case CMARK_NODE_HTML: break; case CMARK_NODE_HRULE: cr(man); cmark_strbuf_puts(man, ".PP\n * * * * *"); cr(man); break; case CMARK_NODE_PARAGRAPH: if (entering) { // no blank line if first paragraph in list: if (node->parent && node->parent->type == CMARK_NODE_ITEM && node->prev == NULL) { // no blank line or .PP } else { cr(man); cmark_strbuf_puts(man, ".PP\n"); } } else { cr(man); } break; case CMARK_NODE_TEXT: escape_man(man, node->as.literal.data, node->as.literal.len); break; case CMARK_NODE_LINEBREAK: cmark_strbuf_puts(man, ".PD 0\n.P\n.PD"); cr(man); break; case CMARK_NODE_SOFTBREAK: cmark_strbuf_putc(man, '\n'); break; case CMARK_NODE_CODE: cmark_strbuf_puts(man, "\\f[C]"); escape_man(man, node->as.literal.data, node->as.literal.len); cmark_strbuf_puts(man, "\\f[]"); break; case CMARK_NODE_INLINE_HTML: break; case CMARK_NODE_STRONG: if (entering) { cmark_strbuf_puts(man, "\\f[B]"); } else { cmark_strbuf_puts(man, "\\f[]"); } break; case CMARK_NODE_EMPH: if (entering) { cmark_strbuf_puts(man, "\\f[I]"); } else { cmark_strbuf_puts(man, "\\f[]"); } break; case CMARK_NODE_LINK: if (!entering) { cmark_strbuf_printf(man, " (%s)", cmark_node_get_url(node)); } break; case CMARK_NODE_IMAGE: if (entering) { cmark_strbuf_puts(man, "[IMAGE: "); state->plain = node; } else { cmark_strbuf_puts(man, "]"); } break; default: assert(false); break; } // cmark_strbuf_putc(man, 'x'); return 1; }
static int S_render_node(cmark_renderer *renderer, cmark_node *node, cmark_event_type ev_type, int options) { int list_number; int enumlevel; char list_number_string[LIST_NUMBER_STRING_SIZE]; bool entering = (ev_type == CMARK_EVENT_ENTER); cmark_list_type list_type; bool allow_wrap = renderer->width > 0 && !(CMARK_OPT_NOBREAKS & options); // 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) { enumlevel = S_get_enumlevel(node); // latex normally supports only five levels if (enumlevel >= 1 && enumlevel <= 5) { snprintf(list_number_string, LIST_NUMBER_STRING_SIZE, "%d", list_number); LIT("\\setcounter{enum"); switch(enumlevel) { case 1: LIT("i"); break; case 2: LIT("ii"); break; case 3: LIT("iii"); break; case 4: LIT("iv"); break; case 5: LIT("v"); break; default: LIT("i"); break; } 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_HEADING: if (entering) { switch (cmark_node_get_heading_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_BLOCK: break; case CMARK_NODE_CUSTOM_BLOCK: CR(); OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node), false, LITERAL); CR(); break; case CMARK_NODE_THEMATIC_BREAK: 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), allow_wrap, NORMAL); break; case CMARK_NODE_LINEBREAK: LIT("\\\\"); CR(); break; case CMARK_NODE_SOFTBREAK: if (options & CMARK_OPT_HARDBREAKS) { LIT("\\\\"); CR(); } else if (renderer->width == 0 && !(CMARK_OPT_NOBREAKS & options)) { CR(); } else { OUT(" ", allow_wrap, NORMAL); } break; case CMARK_NODE_CODE: LIT("\\texttt{"); OUT(cmark_node_get_literal(node), false, NORMAL); LIT("}"); break; case CMARK_NODE_HTML_INLINE: break; case CMARK_NODE_CUSTOM_INLINE: OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node), false, LITERAL); 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); LIT("}"); return 0; // Don't process further nodes to avoid double-rendering artefacts case EMAIL_AUTOLINK: LIT("\\href{"); OUT(url, false, URL); LIT("}\\nolinkurl{"); break; case NORMAL_LINK: LIT("\\href{"); OUT(url, false, URL); LIT("}{"); break; case INTERNAL_LINK: LIT("\\protect\\hyperlink{"); OUT(url + 1, 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; }
static char* S_convert_link(cmark_node *link, CFCClass *doc_class, int header_level) { cmark_node *child = cmark_node_first_child(link); const char *uri = cmark_node_get_url(link); char *text = S_nodes_to_pod(child, doc_class, header_level); char *retval; if (!CFCUri_is_clownfish_uri(uri)) { retval = S_pod_link(text, uri); FREEMEM(text); return retval; } char *new_uri = NULL; char *new_text = NULL; CFCUri *uri_obj = CFCUri_new(uri, doc_class); CFCUriType type = CFCUri_get_type(uri_obj); switch (type) { case CFC_URI_ERROR: { const char *error = CFCUri_get_error(uri_obj); new_text = CFCUtil_sprintf("[%s]", error); break; } case CFC_URI_NULL: // Change all instances of NULL to 'undef' new_text = CFCUtil_strdup("undef"); break; case CFC_URI_CLASS: { CFCClass *klass = CFCUri_get_class(uri_obj); if (klass != doc_class) { const char *class_name = CFCClass_get_name(klass); new_uri = CFCUtil_strdup(class_name); } if (text[0] == '\0') { const char *src = CFCClass_included(klass) ? CFCClass_get_name(klass) : CFCClass_get_struct_sym(klass); new_text = CFCUtil_strdup(src); } break; } case CFC_URI_FUNCTION: case CFC_URI_METHOD: { CFCClass *klass = CFCUri_get_class(uri_obj); const char *name = CFCUri_get_callable_name(uri_obj); // Convert "Err_get_error" to "Clownfish->error". if (strcmp(CFCClass_full_struct_sym(klass), "cfish_Err") == 0 && strcmp(name, "get_error") == 0 ) { new_text = CFCUtil_strdup("Clownfish->error"); break; } char *perl_name = CFCUtil_strdup(name); for (size_t i = 0; perl_name[i] != '\0'; ++i) { perl_name[i] = CFCUtil_tolower(perl_name[i]); } // The Perl POD only contains sections for novel methods. Link // to the class where the method is declared first. if (type == CFC_URI_METHOD) { CFCClass *parent = CFCClass_get_parent(klass); while (parent && CFCClass_method(parent, name)) { klass = parent; parent = CFCClass_get_parent(klass); } } if (klass == doc_class) { new_uri = CFCUtil_sprintf("/%s", perl_name); } else { const char *class_name = CFCClass_get_name(klass); new_uri = CFCUtil_sprintf("%s/%s", class_name, perl_name); } if (text[0] == '\0') { new_text = CFCUtil_sprintf("%s()", perl_name); } FREEMEM(perl_name); break; } case CFC_URI_DOCUMENT: { CFCDocument *doc = CFCUri_get_document(uri_obj); const char *path_part = CFCDocument_get_path_part(doc); new_uri = CFCUtil_global_replace(path_part, CHY_DIR_SEP, "::"); if (text[0] == '\0') { const char *name = CFCDocument_get_name(doc); new_text = CFCUtil_strdup(name); } break; } } if (new_text) { FREEMEM(text); text = new_text; } if (new_uri) { retval = S_pod_link(text, new_uri); FREEMEM(new_uri); FREEMEM(text); } else { retval = text; } CFCBase_decref((CFCBase*)uri_obj); return retval; }
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; }
static int S_render_node(cmark_renderer *renderer, cmark_node *node, cmark_event_type ev_type, int options) { cmark_node *tmp; int list_number; cmark_delim_type list_delim; int numticks; int i; bool entering = (ev_type == CMARK_EVENT_ENTER); const char *info, *code, *title; size_t info_len, code_len; cmark_strbuf listmarker = GH_BUF_INIT; char *emph_delim; bufsize_t marker_width; // Don't adjust tight list status til we've started the list. // Otherwise we loose the blank line between a paragraph and // a following list. if (!(node->type == CMARK_NODE_ITEM && node->prev == NULL && entering)) { tmp = get_containing_block(node); renderer->in_tight_list_item = (tmp->type == CMARK_NODE_ITEM && cmark_node_get_list_tight(tmp->parent)) || (tmp && tmp->parent && tmp->parent->type == CMARK_NODE_ITEM && cmark_node_get_list_tight(tmp->parent->parent)); } switch (node->type) { case CMARK_NODE_DOCUMENT: break; case CMARK_NODE_BLOCK_QUOTE: if (entering) { LIT("> "); cmark_strbuf_puts(renderer->prefix, "> "); } else { cmark_strbuf_truncate(renderer->prefix, renderer->prefix->size - 2); BLANKLINE(); } break; case CMARK_NODE_LIST: if (!entering && node->next && (node->next->type == CMARK_NODE_CODE_BLOCK || node->next->type == CMARK_NODE_LIST)) { // this ensures 2 blank lines after list, // if before code block or list: LIT("\n"); } break; case CMARK_NODE_ITEM: if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { marker_width = 2; } else { list_number = cmark_node_get_list_start(node->parent); list_delim = cmark_node_get_list_delim(node->parent); tmp = node; while (tmp->prev) { tmp = tmp->prev; list_number += 1; } // we ensure a width of at least 4 so // we get nice transition from single digits // to double cmark_strbuf_printf(&listmarker, "%d%s%s", list_number, list_delim == CMARK_PAREN_DELIM ? ")" : ".", list_number < 10 ? " " : " "); marker_width = listmarker.size; } if (entering) { if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { LIT("* "); cmark_strbuf_puts(renderer->prefix, " "); } else { LIT((char *)listmarker.ptr); for (i = marker_width; i--;) { cmark_strbuf_putc(renderer->prefix, ' '); } } } else { cmark_strbuf_truncate(renderer->prefix, renderer->prefix->size - marker_width); CR(); } cmark_strbuf_free(&listmarker); break; case CMARK_NODE_HEADER: if (entering) { for (int i = cmark_node_get_header_level(node); i > 0; i--) { LIT("#"); } LIT(" "); renderer->no_wrap = true; } else { renderer->no_wrap = false; BLANKLINE(); } break; case CMARK_NODE_CODE_BLOCK: BLANKLINE(); info = cmark_node_get_fence_info(node); info_len = safe_strlen(info); code = cmark_node_get_literal(node); code_len = safe_strlen(code); // use indented form if no info, and code doesn't // begin or end with a blank line, and code isn't // first thing in a list item if (info_len == 0 && (code_len > 2 && !isspace(code[0]) && !(isspace(code[code_len - 1]) && isspace(code[code_len - 2]))) && !(node->prev == NULL && node->parent && node->parent->type == CMARK_NODE_ITEM)) { LIT(" "); cmark_strbuf_puts(renderer->prefix, " "); OUT(cmark_node_get_literal(node), false, LITERAL); cmark_strbuf_truncate(renderer->prefix, renderer->prefix->size - 4); } else { numticks = longest_backtick_sequence(code) + 1; if (numticks < 3) { numticks = 3; } for (i = 0; i < numticks; i++) { LIT("`"); } LIT(" "); OUT(info, false, LITERAL); CR(); OUT(cmark_node_get_literal(node), false, LITERAL); CR(); for (i = 0; i < numticks; i++) { LIT("`"); } } BLANKLINE(); break; case CMARK_NODE_HTML: BLANKLINE(); OUT(cmark_node_get_literal(node), false, LITERAL); BLANKLINE(); break; case CMARK_NODE_HRULE: BLANKLINE(); LIT("-----"); 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: if (!(CMARK_OPT_HARDBREAKS & options)) { LIT("\\"); } CR(); break; case CMARK_NODE_SOFTBREAK: if (renderer->width == 0 && !(CMARK_OPT_HARDBREAKS & options)) { CR(); } else { OUT(" ", true, LITERAL); } break; case CMARK_NODE_CODE: code = cmark_node_get_literal(node); code_len = safe_strlen(code); numticks = shortest_unused_backtick_sequence(code); for (i = 0; i < numticks; i++) { LIT("`"); } if (code_len == 0 || code[0] == '`') { LIT(" "); } OUT(cmark_node_get_literal(node), true, LITERAL); if (code_len == 0 || code[code_len - 1] == '`') { LIT(" "); } for (i = 0; i < numticks; i++) { LIT("`"); } break; case CMARK_NODE_INLINE_HTML: OUT(cmark_node_get_literal(node), false, LITERAL); break; case CMARK_NODE_STRONG: if (entering) { LIT("**"); } else { LIT("**"); } break; case CMARK_NODE_EMPH: // If we have EMPH(EMPH(x)), we need to use *_x_* // because **x** is STRONG(x): if (node->parent && node->parent->type == CMARK_NODE_EMPH && node->next == NULL && node->prev == NULL) { emph_delim = "_"; } else { emph_delim = "*"; } if (entering) { LIT(emph_delim); } else { LIT(emph_delim); } break; case CMARK_NODE_LINK: if (is_autolink(node)) { if (entering) { LIT("<"); if (strncmp(cmark_node_get_url(node), "mailto:", 7) == 0) { LIT((char *)cmark_node_get_url(node) + 7); } else { LIT((char *)cmark_node_get_url(node)); } LIT(">"); // return signal to skip contents of node... return 0; } } else { if (entering) { LIT("["); } else { LIT("]("); OUT(cmark_node_get_url(node), false, URL); title = cmark_node_get_title(node); if (safe_strlen(title) > 0) { LIT(" \""); OUT(title, false, TITLE); LIT("\""); } LIT(")"); } } break; case CMARK_NODE_IMAGE: if (entering) { LIT("!["); } else { LIT("]("); OUT(cmark_node_get_url(node), false, URL); title = cmark_node_get_title(node); if (safe_strlen(title) > 0) { OUT(" \"", true, LITERAL); OUT(title, false, TITLE); LIT("\""); } LIT(")"); } break; default: assert(false); break; } return 1; }
static int S_render_node(cmark_renderer *renderer, cmark_node *node, cmark_event_type ev_type, int options) { cmark_node *tmp; int list_number; bool entering = (ev_type == CMARK_EVENT_ENTER); // avoid unused parameter error: (void)(options); switch (node->type) { case CMARK_NODE_DOCUMENT: break; case CMARK_NODE_BLOCK_QUOTE: if (entering) { CR(); LIT(".RS"); CR(); } else { CR(); LIT(".RE"); CR(); } break; case CMARK_NODE_LIST: break; case CMARK_NODE_ITEM: if (entering) { CR(); LIT(".IP "); if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { LIT("\\[bu] 2"); } else { list_number = cmark_node_get_list_start(node->parent); tmp = node; while (tmp->prev) { tmp = tmp->prev; list_number += 1; } const size_t LIST_NUMBER_SIZE = 20; char list_number_s[LIST_NUMBER_SIZE]; snprintf(list_number_s, LIST_NUMBER_SIZE, "\"%d.\" 4", list_number); LIT(list_number_s); } CR(); } else { CR(); } break; case CMARK_NODE_HEADER: if (entering) { CR(); LIT(cmark_node_get_header_level(node) == 1 ? ".SH" : ".SS"); CR(); } else { CR(); } break; case CMARK_NODE_CODE_BLOCK: CR(); LIT(".IP\n.nf\n\\f[C]\n"); OUT(cmark_node_get_literal(node), false, NORMAL); CR(); LIT("\\f[]\n.fi"); CR(); break; case CMARK_NODE_HTML: break; case CMARK_NODE_HRULE: CR(); LIT(".PP\n * * * * *"); CR(); break; case CMARK_NODE_PARAGRAPH: if (entering) { // no blank line if first paragraph in list: if (node->parent && node->parent->type == CMARK_NODE_ITEM && node->prev == NULL) { // no blank line or .PP } else { CR(); LIT(".PP"); CR(); } } else { CR(); } break; case CMARK_NODE_TEXT: OUT(cmark_node_get_literal(node), true, NORMAL); break; case CMARK_NODE_LINEBREAK: LIT(".PD 0\n.P\n.PD"); CR(); break; case CMARK_NODE_SOFTBREAK: if (renderer->width == 0) { CR(); } else { OUT(" ", true, LITERAL); } break; case CMARK_NODE_CODE: LIT("\\f[C]"); OUT(cmark_node_get_literal(node), true, NORMAL); LIT("\\f[]"); break; case CMARK_NODE_INLINE_HTML: break; case CMARK_NODE_STRONG: if (entering) { LIT("\\f[B]"); } else { LIT("\\f[]"); } break; case CMARK_NODE_EMPH: if (entering) { LIT("\\f[I]"); } else { LIT("\\f[]"); } break; case CMARK_NODE_LINK: if (!entering) { LIT(" ("); OUT(cmark_node_get_url(node), true, URL); LIT(")"); } break; case CMARK_NODE_IMAGE: if (entering) { LIT("[IMAGE: "); } else { LIT("]"); } break; default: assert(false); break; } return 1; }
static char* S_convert_link(CFCClass *klass, cmark_node *link) { cmark_node *child = cmark_node_first_child(link); const char *uri = cmark_node_get_url(link); char *text = S_nodes_to_pod(klass, child); char *retval; if (!CFCUri_is_clownfish_uri(uri)) { retval = S_pod_link(text, uri); FREEMEM(text); return retval; } char *new_uri = NULL; char *new_text = NULL; CFCUri *uri_obj = CFCUri_new(uri, klass); int type = CFCUri_get_type(uri_obj); switch (type) { case CFC_URI_NULL: // Change all instances of NULL to 'undef' new_text = CFCUtil_strdup("undef"); break; case CFC_URI_CLASS: { const char *full_struct_sym = CFCUri_full_struct_sym(uri_obj); CFCClass *uri_class = CFCClass_fetch_by_struct_sym(full_struct_sym); if (!uri_class) { CFCUtil_warn("URI class not found: %s", full_struct_sym); } else if (uri_class != klass) { const char *class_name = CFCClass_get_class_name(uri_class); new_uri = CFCUtil_strdup(class_name); } if (text[0] != '\0') { // Keep text. break; } if (strcmp(CFCUri_get_prefix(uri_obj), CFCClass_get_prefix(klass)) == 0 ) { // Same parcel. const char *struct_sym = CFCUri_get_struct_sym(uri_obj); new_text = CFCUtil_strdup(struct_sym); } else { // Other parcel. if (!uri_class) { new_text = CFCUtil_strdup(full_struct_sym); } else { const char *class_name = CFCClass_get_class_name(uri_class); new_text = CFCUtil_strdup(class_name); } } break; } case CFC_URI_FUNCTION: case CFC_URI_METHOD: { const char *full_struct_sym = CFCUri_full_struct_sym(uri_obj); const char *func_sym = CFCUri_get_func_sym(uri_obj); // Convert "Err_get_error" to "Clownfish->error". if (strcmp(full_struct_sym, "cfish_Err") == 0 && strcmp(func_sym, "get_error") == 0 ) { new_text = CFCUtil_strdup("Clownfish->error"); break; } CFCClass *uri_class = CFCClass_fetch_by_struct_sym(full_struct_sym); // TODO: Link to relevant POD section. This isn't easy because // the section headers for functions also contain a description // of the parameters. if (!uri_class) { CFCUtil_warn("URI class not found: %s", full_struct_sym); } else if (uri_class != klass) { const char *class_name = CFCClass_get_class_name(uri_class); new_uri = CFCUtil_strdup(class_name); } new_text = CFCUtil_sprintf("%s()", func_sym); for (size_t i = 0; new_text[i] != '\0'; ++i) { new_text[i] = tolower(new_text[i]); } break; } } if (new_text) { FREEMEM(text); text = new_text; } if (new_uri) { retval = S_pod_link(text, new_uri); FREEMEM(new_uri); FREEMEM(text); } else { retval = text; } CFCBase_decref((CFCBase*)uri_obj); return retval; }
static void accessors(test_batch_runner *runner) { static const char markdown[] = "## Header\n" "\n" "* Item 1\n" "* Item 2\n" "\n" "2. Item 1\n" "\n" "3. Item 2\n" "\n" "\n" " code\n" "\n" "``` lang\n" "fenced\n" "```\n" "\n" "<div>html</div>\n" "\n" "[link](url 'title')\n"; cmark_node *doc = cmark_parse_document(markdown, sizeof(markdown) - 1, CMARK_OPT_DEFAULT); // Getters cmark_node *header = cmark_node_first_child(doc); INT_EQ(runner, cmark_node_get_header_level(header), 2, "get_header_level"); cmark_node *bullet_list = cmark_node_next(header); INT_EQ(runner, cmark_node_get_list_type(bullet_list), CMARK_BULLET_LIST, "get_list_type bullet"); INT_EQ(runner, cmark_node_get_list_tight(bullet_list), 1, "get_list_tight tight"); cmark_node *ordered_list = cmark_node_next(bullet_list); INT_EQ(runner, cmark_node_get_list_type(ordered_list), CMARK_ORDERED_LIST, "get_list_type ordered"); INT_EQ(runner, cmark_node_get_list_delim(ordered_list), CMARK_PERIOD_DELIM, "get_list_delim ordered"); INT_EQ(runner, cmark_node_get_list_start(ordered_list), 2, "get_list_start"); INT_EQ(runner, cmark_node_get_list_tight(ordered_list), 0, "get_list_tight loose"); cmark_node *code = cmark_node_next(ordered_list); STR_EQ(runner, cmark_node_get_literal(code), "code\n", "get_literal indented code"); cmark_node *fenced = cmark_node_next(code); STR_EQ(runner, cmark_node_get_literal(fenced), "fenced\n", "get_literal fenced code"); STR_EQ(runner, cmark_node_get_fence_info(fenced), "lang", "get_fence_info"); cmark_node *html = cmark_node_next(fenced); STR_EQ(runner, cmark_node_get_literal(html), "<div>html</div>\n", "get_literal html"); cmark_node *paragraph = cmark_node_next(html); INT_EQ(runner, cmark_node_get_start_line(paragraph), 19, "get_start_line"); INT_EQ(runner, cmark_node_get_start_column(paragraph), 1, "get_start_column"); INT_EQ(runner, cmark_node_get_end_line(paragraph), 19, "get_end_line"); cmark_node *link = cmark_node_first_child(paragraph); STR_EQ(runner, cmark_node_get_url(link), "url", "get_url"); STR_EQ(runner, cmark_node_get_title(link), "title", "get_title"); cmark_node *string = cmark_node_first_child(link); STR_EQ(runner, cmark_node_get_literal(string), "link", "get_literal string"); // Setters OK(runner, cmark_node_set_header_level(header, 3), "set_header_level"); OK(runner, cmark_node_set_list_type(bullet_list, CMARK_ORDERED_LIST), "set_list_type ordered"); OK(runner, cmark_node_set_list_delim(bullet_list, CMARK_PAREN_DELIM), "set_list_delim paren"); OK(runner, cmark_node_set_list_start(bullet_list, 3), "set_list_start"); OK(runner, cmark_node_set_list_tight(bullet_list, 0), "set_list_tight loose"); OK(runner, cmark_node_set_list_type(ordered_list, CMARK_BULLET_LIST), "set_list_type bullet"); OK(runner, cmark_node_set_list_tight(ordered_list, 1), "set_list_tight tight"); OK(runner, cmark_node_set_literal(code, "CODE\n"), "set_literal indented code"); OK(runner, cmark_node_set_literal(fenced, "FENCED\n"), "set_literal fenced code"); OK(runner, cmark_node_set_fence_info(fenced, "LANG"), "set_fence_info"); OK(runner, cmark_node_set_literal(html, "<div>HTML</div>\n"), "set_literal html"); OK(runner, cmark_node_set_url(link, "URL"), "set_url"); OK(runner, cmark_node_set_title(link, "TITLE"), "set_title"); OK(runner, cmark_node_set_literal(string, "LINK"), "set_literal string"); char *rendered_html = cmark_render_html(doc, CMARK_OPT_DEFAULT); static const char expected_html[] = "<h3>Header</h3>\n" "<ol start=\"3\">\n" "<li>\n" "<p>Item 1</p>\n" "</li>\n" "<li>\n" "<p>Item 2</p>\n" "</li>\n" "</ol>\n" "<ul>\n" "<li>Item 1</li>\n" "<li>Item 2</li>\n" "</ul>\n" "<pre><code>CODE\n" "</code></pre>\n" "<pre><code class=\"language-LANG\">FENCED\n" "</code></pre>\n" "<div>HTML</div>\n" "<p><a href=\"URL\" title=\"TITLE\">LINK</a></p>\n"; STR_EQ(runner, rendered_html, expected_html, "setters work"); free(rendered_html); // Getter errors INT_EQ(runner, cmark_node_get_header_level(bullet_list), 0, "get_header_level error"); INT_EQ(runner, cmark_node_get_list_type(header), CMARK_NO_LIST, "get_list_type error"); INT_EQ(runner, cmark_node_get_list_start(code), 0, "get_list_start error"); INT_EQ(runner, cmark_node_get_list_tight(fenced), 0, "get_list_tight error"); OK(runner, cmark_node_get_literal(ordered_list) == NULL, "get_literal error"); OK(runner, cmark_node_get_fence_info(paragraph) == NULL, "get_fence_info error"); OK(runner, cmark_node_get_url(html) == NULL, "get_url error"); OK(runner, cmark_node_get_title(header) == NULL, "get_title error"); // Setter errors OK(runner, !cmark_node_set_header_level(bullet_list, 3), "set_header_level error"); OK(runner, !cmark_node_set_list_type(header, CMARK_ORDERED_LIST), "set_list_type error"); OK(runner, !cmark_node_set_list_start(code, 3), "set_list_start error"); OK(runner, !cmark_node_set_list_tight(fenced, 0), "set_list_tight error"); OK(runner, !cmark_node_set_literal(ordered_list, "content\n"), "set_literal error"); OK(runner, !cmark_node_set_fence_info(paragraph, "lang"), "set_fence_info error"); OK(runner, !cmark_node_set_url(html, "url"), "set_url error"); OK(runner, !cmark_node_set_title(header, "title"), "set_title error"); OK(runner, !cmark_node_set_header_level(header, 0), "set_header_level too small"); OK(runner, !cmark_node_set_header_level(header, 7), "set_header_level too large"); OK(runner, !cmark_node_set_list_type(bullet_list, CMARK_NO_LIST), "set_list_type invalid"); OK(runner, !cmark_node_set_list_start(bullet_list, -1), "set_list_start negative"); cmark_node_free(doc); }
static int S_render_node(cmark_renderer *renderer, cmark_node *node, cmark_event_type ev_type, int options) { cmark_node *tmp; int list_number; bool entering = (ev_type == CMARK_EVENT_ENTER); bool allow_wrap = renderer->width > 0 && !(CMARK_OPT_NOBREAKS & options); if (node->extension && node->extension->man_render_func) { node->extension->man_render_func(node->extension, renderer, node, ev_type, options); return 1; } switch (node->type) { case CMARK_NODE_DOCUMENT: if (entering) { /* Define a strikethrough macro */ /* Commenting out because this makes tests fail LIT(".de ST"); CR(); LIT(".nr ww \\w'\\\\$1'"); CR(); LIT("\\Z@\\v'-.25m'\\l'\\\\n[ww]u'@\\\\$1"); CR(); LIT(".."); CR(); */ } break; case CMARK_NODE_BLOCK_QUOTE: if (entering) { CR(); LIT(".RS"); CR(); } else { CR(); LIT(".RE"); CR(); } break; case CMARK_NODE_LIST: break; case CMARK_NODE_ITEM: if (entering) { CR(); LIT(".IP "); if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { LIT("\\[bu] 2"); } else { list_number = cmark_node_get_list_start(node->parent); tmp = node; while (tmp->prev) { tmp = tmp->prev; list_number += 1; } char list_number_s[LIST_NUMBER_SIZE]; snprintf(list_number_s, LIST_NUMBER_SIZE, "\"%d.\" 4", list_number); LIT(list_number_s); } CR(); } else { CR(); } break; case CMARK_NODE_HEADING: if (entering) { CR(); LIT(cmark_node_get_heading_level(node) == 1 ? ".SH" : ".SS"); CR(); } else { CR(); } break; case CMARK_NODE_CODE_BLOCK: CR(); LIT(".IP\n.nf\n\\f[C]\n"); OUT(cmark_node_get_literal(node), false, NORMAL); CR(); LIT("\\f[]\n.fi"); CR(); break; case CMARK_NODE_HTML_BLOCK: break; case CMARK_NODE_CUSTOM_BLOCK: CR(); OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node), false, LITERAL); CR(); break; case CMARK_NODE_THEMATIC_BREAK: CR(); LIT(".PP\n * * * * *"); CR(); break; case CMARK_NODE_PARAGRAPH: if (entering) { // no blank line if first paragraph in list: if (node->parent && node->parent->type == CMARK_NODE_ITEM && node->prev == NULL) { // no blank line or .PP } else { CR(); LIT(".PP"); CR(); } } else { CR(); } break; case CMARK_NODE_TEXT: OUT(cmark_node_get_literal(node), allow_wrap, NORMAL); break; case CMARK_NODE_LINEBREAK: LIT(".PD 0\n.P\n.PD"); CR(); break; case CMARK_NODE_SOFTBREAK: if (options & CMARK_OPT_HARDBREAKS) { LIT(".PD 0\n.P\n.PD"); CR(); } else if (renderer->width == 0 && !(CMARK_OPT_NOBREAKS & options)) { CR(); } else { OUT(" ", allow_wrap, LITERAL); } break; case CMARK_NODE_CODE: LIT("\\f[C]"); OUT(cmark_node_get_literal(node), allow_wrap, NORMAL); LIT("\\f[]"); break; case CMARK_NODE_HTML_INLINE: break; case CMARK_NODE_CUSTOM_INLINE: OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node), false, LITERAL); break; case CMARK_NODE_STRONG: if (entering) { LIT("\\f[B]"); } else { LIT("\\f[]"); } break; case CMARK_NODE_EMPH: if (entering) { LIT("\\f[I]"); } else { LIT("\\f[]"); } break; case CMARK_NODE_LINK: if (!entering) { LIT(" ("); OUT(cmark_node_get_url(node), allow_wrap, URL); LIT(")"); } break; case CMARK_NODE_IMAGE: if (entering) { LIT("[IMAGE: "); } else { LIT("]"); } break; case CMARK_NODE_FOOTNOTE_DEFINITION: case CMARK_NODE_FOOTNOTE_REFERENCE: // TODO break; default: assert(false); break; } return 1; }