To render a Markdown tree as TeX.
- §1. Main rendering recursion
- §1.2. Container block items
- §1.7. Leaf blocks
- §1.14. Inline types
- §2. Character output
- §4. Links
- §5. Code block rendering
§1. Main rendering recursion. Do not call this function directly: use the API in Markdown Rendering.
Note that FOOTNOTE_BODY_MIT content is not in fact ignored: it's rendered
out of the normal traverse sequence, since in TeX the footnote content has
to be placed at the same position in the output as its cue.
void MDRenderLaTeX::recurse(OUTPUT_STREAM, markdown_render *rdr, markdown_item *md, int mode) { if (rdr == NULL) internal_error("no render details in recursion"); if (md == NULL) return; if (MarkdownVariations::intervene_in_rendering(rdr->variation, rdr, OUT, md, mode)) return; int old_mode = mode; switch (md->type) { /* Container blocks */ case BLOCK_QUOTE_MIT: Render a block quote1.2; break; case ALERT_MIT: Render an alert1.3; break; case UNORDERED_LIST_MIT: Render an unordered list1.4; break; case ORDERED_LIST_MIT: Render an ordered list1.5; break; case UNORDERED_LIST_ITEM_MIT: Render a list item1.6; break; case ORDERED_LIST_ITEM_MIT: Render a list item1.6; break; case FOOTNOTE_BODY_MIT: break; /* Leaf blocks */ case PARAGRAPH_MIT: Render a paragraph1.7; break; case THEMATIC_MIT: Render a thematic break1.8; break; case HEADING_MIT: Render a heading1.9; break; case CODE_BLOCK_MIT: Render a code block1.10; break; case HTML_MIT: Render a raw HTML block1.11; break; case EMPTY_MIT: break; case TABLE_MIT: Render a table1.12; break; case TICKBOX_MIT: Render a tickbox1.13; break; /* Inline types */ case PLAIN_MIT: Render plain text1.14; break; case LINE_BREAK_MIT: Render a line break1.15; break; case SOFT_BREAK_MIT: Render a soft line break1.16; break; case EMPHASIS_MIT: Render emphasised text1.17; break; case STRONG_MIT: Render strongly emphasised text1.18; break; case CODE_MIT: Render inline code sample1.19; break; case URI_AUTOLINK_MIT: Render URI link1.20; break; case EMAIL_AUTOLINK_MIT: Render email link1.21; break; case INLINE_HTML_MIT: Render inline HTML content1.22; break; case LINK_MIT: Render link1.23; break; case IMAGE_MIT: Render image1.24; break; case LINK_DEST_MIT: Render link destination1.25; break; case STRIKETHROUGH_MIT: Render struck-through text1.26; break; case XMPP_AUTOLINK_MIT: Render email link1.21; break; case TEX_MIT: Render maths matter in TeX1.27; break; case DISPLAYED_TEX_MIT: Render maths matter in TeX1.27; break; case INWEB_LINK_MIT: Render Inweb link1.28; break; default: Recurse1.1; break; } mode = old_mode; }
§1.1. Again, note that EXISTING_PAR_MDRMODE propagates only down the leading
edge of the tree: see Markdown to HTML for the reason.
Recurse1.1 =
int m = mode; for (markdown_item *c = md->down; c; c = c->next) { MDRenderLaTeX::recurse(OUT, rdr, c, m); m = m & (~EXISTING_PAR_MDRMODE); }
§1.2. Container block items. Render a block quote1.2 =
WRITE("\\smallskip\\par{\\narrower\\narrower\\noindent"); WRITE("\n"); Recurse1.1; WRITE("\\smallskip}\n");
- This code is used in §1.
int type = Markdown::get_alert_type(md); text_stream *name = MarkdownVariations::get_alert_form_name(rdr->variation, type); inchar32_t icon = MarkdownVariations::get_alert_form_icon(rdr->variation, type); WRITE("{\\narrower\\smallskip\\noindent"); WRITE("\n"); if (icon) WRITE("$\\otimes$ "); WRITE("{\\bf "); if (Str::len(name) > 0) { PUT(Str::get_at(name, 0)); for (int i=1; i<Str::len(name); i++) PUT(Characters::tolower(Str::get_at(name, i))); } else { WRITE("Alert"); } WRITE("}\n\n\\noindent "); Recurse1.1; WRITE("\\smallskip}\n");
- This code is used in §1.
Render an unordered list1.4 =
int skip_ul_rendering = FALSE; if (MarkdownVariations::supports(rdr->variation, GADGETS_MARKDOWNFEATURE)) skip_ul_rendering = MDRender::render_ul_as_carousel(OUT, mode, rdr, md); if (skip_ul_rendering == FALSE) { WRITE("\\begin{compactitem}\n"); Recurse through list1.4.1; WRITE("\\end{compactitem}\n"); }
- This code is used in §1.
Render an ordered list1.5 =
WRITE("\\begin{compactenum}\n"); Recurse through list1.4.1; WRITE("\\end{compactenum}\n");
- This code is used in §1.
§1.4.1. Recurse through list1.4.1 =
mode = mode & (~LOOSE_MDRMODE); for (markdown_item *ch = md->down; ch; ch = ch->next) { if ((ch->next) && (ch->whitespace_follows)) mode = mode | LOOSE_MDRMODE; for (markdown_item *gch = ch->down; gch; gch = gch->next) if ((gch->next) && (gch->whitespace_follows)) mode = mode | LOOSE_MDRMODE; } Recurse1.1;
WRITE("\n\\item "); int nl_issued = FALSE; for (markdown_item *ch = md->down; ch; ch = ch->next) if (((mode & LOOSE_MDRMODE) == 0) && (ch->type == PARAGRAPH_MIT)) { for (markdown_item *gch = ch->down; gch; gch = gch->next) MDRenderLaTeX::recurse(OUT, rdr, gch, mode); } else { if (nl_issued == FALSE) { nl_issued = TRUE; WRITE("\\smallskip\n"); } MDRenderLaTeX::recurse(OUT, rdr, ch, mode); } WRITE("\n");
- This code is used in §1 (twice).
§1.7. Leaf blocks. Render a paragraph1.7 =
mode = mode & (~EXISTING_PAR_MDRMODE); WRITE("\\iffollowsheading\\par\\noindent\\fi\\followsheadingfalse\n"); Recurse1.1; WRITE("\n\n");
- This code is used in §1.
§1.8. Render a thematic break1.8 =
WRITE("\n\\bigskip\\hrule\\bigskip\n\n");
- This code is used in §1.
switch (Markdown::get_heading_level(md)) { case 1: case 2: WRITE("\n\\medbreak\\par\\noindent{\\bf "); Recurse1.1; WRITE("}\\smallskip\n\n"); break; default: WRITE("\n\\medbreak\\par\\noindent{\\it "); Recurse1.1; WRITE("}\\smallskip\n\n"); break; } WRITE("\\followsheadingtrue\n");
- This code is used in §1.
§1.10. Render a code block1.10 =
mode = mode & (~ESCAPES_MDRMODE) & (~ENTITIES_MDRMODE); TEMPORARY_TEXT(language) for (int i=0; i<Str::len(md->info_string); i++) { inchar32_t c = Str::get_at(md->info_string, i); if ((c == ' ') || (c == '\t')) break; PUT_TO(language, c); } TEMPORARY_TEXT(language_rendered) if (Str::len(language) > 0) { md->sliced_from = language; md->from = 0; md->to = Str::len(language) - 1; if (MarkdownVariations::supports(rdr->variation, ENTITIES_MARKDOWNFEATURE)) MDRender::slice(language_rendered, rdr, md, mode | ENTITIES_MDRMODE); else MDRender::slice(language_rendered, rdr, md, mode); } DISCARD_TEXT(language) MDRenderLaTeX::render_code_block(OUT, mode, rdr, md->stashed, language_rendered); DISCARD_TEXT(language_rendered)
- This code is used in §1.
§1.11. Render a raw HTML block1.11 =
for (int i=0; i<Str::len(md->stashed); i++) MDRenderLaTeX::char(OUT, Str::get_at(md->stashed, i), 0);
- This code is used in §1.
markdown_item *alignment_markers = md->down; if (alignment_markers) { int row_count = 0; for (markdown_item *md = alignment_markers->next; md; md = md->next) row_count++; if (row_count > 0) { WRITE("\n\\medskip"); WRITE("\n\\catcode`\\|=\\other\\renewcommand{\\inwebltpreamble}{|"); int col_count = 0; markdown_item *row = alignment_markers->next; for (markdown_item *md = row->down, *top = alignment_markers->down; md; md = md->next, top = top->next) { switch (Markdown::get_alignment(top)) { case 0: WRITE("c"); break; case 1: WRITE("l"); break; case 2: WRITE("r"); break; case 3: WRITE("c"); break; } col_count++; if (md->next) WRITE("|"); } WRITE("|}\\catcode`\\|=\\active\n"); WRITE("\n\\begin{longtable}{\\inwebltpreamble}\n"); WRITE("\\hline\n"); for (markdown_item *md = row->down, *top = alignment_markers->down; md; md = md->next, top = top->next) { if (md->down) MDRenderLaTeX::recurse(OUT, rdr, md->down, mode); if (md->next) WRITE("&"); } WRITE("\\\\\n"); WRITE("\\hline\\endhead\n"); row = row->next; if (row_count > 1) { while (row) { for (markdown_item *md = row->down, *top = alignment_markers->down; md; md = md->next, top = top->next) { if (md->down) MDRenderLaTeX::recurse(OUT, rdr, md->down, mode); if (md->next) WRITE("&"); } WRITE("\\\\\n"); row = row->next; } } WRITE("\\hline\n"); WRITE("\\end{longtable}{}\n"); } }
- This code is used in §1.
if (Markdown::get_tick_state(md)) { WRITE("<input checked=\"\" disabled=\"\" type=\"checkbox\"> "); } else { WRITE("<input disabled=\"\" type=\"checkbox\"> "); }
- This code is used in §1.
§1.14. Inline types. Render plain text1.14 =
MDRender::slice(OUT, rdr, md, mode);
- This code is used in §1.
§1.15. Render a line break1.15 =
WRITE("\\\\\n"); /* that is, two consecutive backslashes and a newline */
- This code is used in §1.
§1.16. Render a soft line break1.16 =
MDRenderLaTeX::char(OUT, '\n', mode);
- This code is used in §1.
§1.17. Render emphasised text1.17 =
WRITE("\\emph{"); Recurse1.1; WRITE("}");
- This code is used in §1.
§1.18. Render strongly emphasised text1.18 =
WRITE("\\textbf{"); Recurse1.1; WRITE("}");
- This code is used in §1.
§1.19. Render inline code sample1.19 =
WRITE("|"); mode = mode & (~ESCAPES_MDRMODE); mode = mode & (~ENTITIES_MDRMODE); mode = mode | (INLINECODE_MDRMODE); MDRender::slice(OUT, rdr, md, mode); WRITE("|");
- This code is used in §1.
text_stream *supplied_scheme = NULL; if (Markdown::get_add_protocol_state(md)) supplied_scheme = I"http://"; Render autolink1.20.1;
- This code is used in §1.
§1.21. Render email link1.21 =
text_stream *supplied_scheme = NULL; if (Markdown::get_add_protocol_state(md)) { supplied_scheme = I"mailto:"; if (md->type == XMPP_AUTOLINK_MIT) supplied_scheme = I"xmpp:"; } Render autolink1.20.1;
- This code is used in §1 (twice).
§1.20.1. Render autolink1.20.1 =
TEMPORARY_TEXT(address) MDRender::slice(address, rdr, md, (mode & (~ESCAPES_MDRMODE)) | URI_MDRMODE); MDRenderLaTeX::link(OUT, rdr, supplied_scheme, address, mode); MDRender::stream(OUT, rdr, address, mode); MDRenderLaTeX::end_link(OUT, mode); DISCARD_TEXT(address)
§1.22. Render inline HTML content1.22 =
mode = mode | RAW_MDRMODE; if (Markdown::get_filtered_state(md)) mode = mode | FILTERED_MDRMODE; mode = mode & (~ESCAPES_MDRMODE); mode = mode & (~ENTITIES_MDRMODE); MDRender::slice(OUT, rdr, md, mode);
- This code is used in §1.
TEMPORARY_TEXT(URI) TEMPORARY_TEXT(title) if (md->details > 0) { Render footnote instead1.23.1; } else if (md->down->next) { if (md->down->next->type == LINK_DEST_MIT) { MDRenderLaTeX::recurse(URI, rdr, md->down->next, mode | URI_MDRMODE); if ((md->down->next->next) && (md->down->next->next->type == LINK_TITLE_MIT)) MDRenderLaTeX::recurse(title, rdr, md->down->next->next, mode); } else if (md->down->next->type == LINK_TITLE_MIT) { MDRenderLaTeX::recurse(title, rdr, md->down->next, mode); } MDRenderLaTeX::link(OUT, rdr, I"", URI, mode); MDRenderLaTeX::recurse(OUT, rdr, md->down, mode); MDRenderLaTeX::end_link(OUT, mode); } DISCARD_TEXT(URI) DISCARD_TEXT(title)
- This code is used in §1.
§1.23.1. Render footnote instead1.23.1 =
markdown_item *body = MDRender::seek_footnote_body(rdr, md->details); if (body == NULL) WRITE("{\\bf [Missing footnote?]}"); else { WRITE("\\footnote{\\quad "); int m = mode; markdown_item *r_from = body->down; for (markdown_item *c = r_from; c; c = c->next) { MDRenderLaTeX::recurse(OUT, rdr, c, m); m = m & (~EXISTING_PAR_MDRMODE); } WRITE("}"); }
- This code is used in §1.23.
define INWEB_POINTS_PER_CM 72
Render image1.24 =
TEMPORARY_TEXT(URI) TEMPORARY_TEXT(title) TEMPORARY_TEXT(alt) if (md->down->next) { if (md->down->next->type == LINK_DEST_MIT) { MDRenderLaTeX::recurse(URI, rdr, md->down->next, mode); if ((md->down->next->next) && (md->down->next->next->type == LINK_TITLE_MIT)) MDRenderLaTeX::recurse(title, rdr, md->down->next->next, mode); } else if (md->down->next->type == LINK_TITLE_MIT) { MDRenderLaTeX::recurse(title, rdr, md->down->next, mode); } } MDRenderLaTeX::recurse(alt, rdr, md->down, mode | ALT_TEXT_MDRMODE); int skip_image_rendering = FALSE; if (MarkdownVariations::supports(rdr->variation, GADGETS_MARKDOWNFEATURE)) skip_image_rendering = MDRender::render_gadget(OUT, rdr, mode, alt, URI); if (skip_image_rendering == FALSE) { int w = -1, h = -1; if (rdr) { int j = 0; for (int i=1; i<Str::len(URI)-1; i++) if (Str::get_at(URI, i) == '@') j = i; if (j > 0) { int x = 0; for (int i=j+1; i<Str::len(URI); i++) if (Str::get_at(URI, i) == 'x') x = i; int bad = FALSE; for (int i=j+1; i<Str::len(URI); i++) if ((i != x) && (Characters::isdigit(Str::get_at(URI, i)) == FALSE)) bad = TRUE; if (bad == FALSE) { if (x > 0) { if (x > j+1) w = Str::atoi(URI, j+1); if (x < Str::len(URI) - 1) h = Str::atoi(URI, x+1); } else { w = Str::atoi(URI, j+1); } Str::truncate(URI, j); } } } MDRender::notify_image(rdr, URI); if ((w < 0) && (h < 0)) w = INWEB_POINTS_PER_CM*15; if ((w > 0) && (h > 0)) WRITE("\\inwebimagewidthheight{%S}{%d}{%d}\n", URI, w/INWEB_POINTS_PER_CM, h/INWEB_POINTS_PER_CM); else if (w >= 0) WRITE("\\inwebimagewidth{%S}{%d}\n", URI, w/INWEB_POINTS_PER_CM); else if (h >= 0) WRITE("\\inwebimageheight{%S}{%d}\n", URI, h/INWEB_POINTS_PER_CM); else WRITE("\\inwebimage{%S}\n", URI); } DISCARD_TEXT(URI) DISCARD_TEXT(title) DISCARD_TEXT(alt)
- This code is used in §1.
§1.25. Render link destination1.25 =
mode = mode | URI_MDRMODE; MDRender::slice(OUT, rdr, md->down, mode);
- This code is used in §1.
§1.26. Render struck-through text1.26 =
WRITE("\\strikethrough{"); Recurse1.1; WRITE("}");
- This code is used in §1.
§1.27. Render maths matter in TeX1.27 =
TEMPORARY_TEXT(TEX) if (MarkdownVariations::supports(rdr->variation, ALT_TEX_MARKDOWNFEATURE)) { int m = RAW_MDRMODE; for (markdown_item *c = md->down; c; c = c->next) { MDRenderLaTeX::recurse(TEX, rdr, c, m); m = m & (~EXISTING_PAR_MDRMODE); } } else { for (int i=md->from; i<=md->to; i++) { inchar32_t c = Markdown::get_at(md, i); PUT_TO(TEX, c); } } if (md->type == DISPLAYED_TEX_MIT) WRITE("$$"); else WRITE("$"); WRITE("%S", TEX); if (md->type == DISPLAYED_TEX_MIT) WRITE("$$"); else WRITE("$"); DISCARD_TEXT(TEX)
- This code is used in §1 (twice).
§1.28. Render Inweb link1.28 =
TEMPORARY_TEXT(address) MDRenderLaTeX::recurse(address, rdr, md->down, RAW_MDRMODE); query_results qr = MDRender::resolve_link(rdr, address); if ((qr.found) && (qr.external == FALSE) && (Str::len(qr.internal_xref) == 0)) qr.found = FALSE; if (qr.found) { if (qr.external) { MDRenderLaTeX::link(OUT, rdr, I"", qr.url, mode); } else { MDRenderLaTeX::link_internal(OUT, qr.internal_xref, mode); } WRITE("%S", qr.title); MDRenderLaTeX::end_link(OUT, mode); } else { WRITE("{\\bf "); WRITE("[Unresolved link: "); MDRender::stream(OUT, rdr, address, mode); WRITE("]"); WRITE("}"); } DISCARD_TEXT(address) MDRender::dispose_of(&qr);
- This code is used in §1.
§2. Character output. DECATCODE_MDRMODE is an infuriating expedient. The verbatim text macros,
following those in the TeXBook, set the \catcode of various significant
characters to 12 ("other"), thus deactivating their normal function, within
the | and | delimiters: thus, |5&10$| is rendered by TeX (plus our macros)
as the five literal characters in the middle. However, timing issues in
the way the \halign table mechanism works mean that it is too late to change
catcodes because the table cell contents have already been expanded: in other
words, our regular way to deactivate these characters fails for inline code
extracts within table cells. So we have a special mode which deactivates them
by hand. Note that this relies on \ having its regular catcode; this is why
we can't use that trick in every inline code extract, only those where we know
ordinary catcode changing has been thwarted.
void MDRenderLaTeX::char(OUTPUT_STREAM, inchar32_t c, int mode) { if (mode & TOLOWER_MDRMODE) c = Characters::tolower(c); if (mode & INLINECODE_MDRMODE) { if (c == '|') { WRITE("|{\\tt\\|}|"); } else if (c == '`') { WRITE("`||"); } else if (c == '-') { WRITE("-||"); } else if (mode & DECATCODE_MDRMODE) { switch (c) { case '\\': WRITE("\\textbackslash{}"); break; case '{': WRITE("\\{"); break; case '}': WRITE("\\}"); break; case '$': WRITE("\\$"); break; case '&': WRITE("\\&"); break; case '#': WRITE("\\#"); break; case '%': WRITE("\\%%"); break; case '~': WRITE("\\char`~"); break; case '_': WRITE("\\_"); break; case '^': WRITE("\\char`^"); break; default: PUT(c); break; } } else { PUT(c); } } else if (mode & RAW_MDRMODE) { PUT(c); } else if (mode & URI_MDRMODE) { if (c >= 0x10000) { MARKDOWNTEX_URI_HEX(0xF0 + (unsigned char) (c >> 18)); MARKDOWNTEX_URI_HEX(0x80 + (unsigned char) ((c >> 12) & 0x3f)); MARKDOWNTEX_URI_HEX( 0x80 + (unsigned char) ((c >> 6) & 0x3f)); MARKDOWNTEX_URI_HEX(0x80 + (unsigned char) (c & 0x3f)); } else if (c >= 0x800) { MARKDOWNTEX_URI_HEX(0xE0 + (unsigned char) (c >> 12)); MARKDOWNTEX_URI_HEX(0x80 + (unsigned char) ((c >> 6) & 0x3f)); MARKDOWNTEX_URI_HEX(0x80 + (unsigned char) (c & 0x3f)); } else if (c >= 0x80) { MARKDOWNTEX_URI_HEX(0xC0 + (unsigned char) (c >> 6)); MARKDOWNTEX_URI_HEX(0x80 + (unsigned char) (c & 0x3f)); } else { switch (c) { case '%': WRITE("\\%%"); break; case '<': WRITE("\\<"); break; case '&': WRITE("\\&"); break; case '#': WRITE("\\#;"); break; case '>': WRITE("\\>"); break; case '[': MARKDOWNTEX_URI_HEX((unsigned char) c); break; case '\\':MARKDOWNTEX_URI_HEX((unsigned char) c); break; case '\"':MARKDOWNTEX_URI_HEX((unsigned char) c); break; case ']': MARKDOWNTEX_URI_HEX((unsigned char) c); break; case '`': MARKDOWNTEX_URI_HEX((unsigned char) c); break; case ' ': MARKDOWNTEX_URI_HEX((unsigned char) c); break; default: PUT(c); break; } } } else { switch (c) { case '{': WRITE("$\\{$"); break; case '}': WRITE("$\\}$"); break; case '&': WRITE("\\&"); break; case '%': WRITE("\\%%"); break; case '$': WRITE("\\$"); break; case '~': WRITE("\\char`~"); break; case '#': WRITE("\\#"); break; case '\\': WRITE("\\textbackslash{}"); break; case '_': WRITE("\\_"); break; case '|': WRITE("\\|"); break; case '^': WRITE("\\char`^"); break; case '"': WRITE("{\\tt\"}"); break; case '<': WRITE("{\\tt<}"); break; case '>': WRITE("{\\tt>}"); break; case '\'': WRITE("{\\tt\\char'15}"); break; case 0x201C: WRITE("``"); break; case 0x201D: WRITE("''"); break; case 0x2018: WRITE("`"); break; case 0x2019: WRITE("'"); break; default: PUT(c); break; } } }
define MARKDOWNTEX_URI_HEX(x) { unsigned int z = (unsigned int) x; PUT('\\'); PUT('%'); MDRender::hex_digit(OUT, z >> 4); MDRender::hex_digit(OUT, z & 0x0f); }
§4. Links. Links are all delegated to TeX macros, so that they can work differently in different TeX variants.
void MDRenderLaTeX::link(OUTPUT_STREAM, markdown_render *rdr, text_stream *supplied_scheme, text_stream *address, int mode) { WRITE("{\\inweblinktourl{"); MDRender::stream(OUT, rdr, supplied_scheme, RAW_MDRMODE); MDRender::stream(OUT, rdr, address, RAW_MDRMODE); WRITE("}{"); } void MDRenderLaTeX::link_internal(OUTPUT_STREAM, text_stream *nameid, int mode) { WRITE("{\\inweblinktoanchor{%S}{\n", nameid); } void MDRenderLaTeX::end_link(OUTPUT_STREAM, int mode) { WRITE("}}"); }
void MDRenderLaTeX::render_code_block(OUTPUT_STREAM, int mode, markdown_render *rdr, text_stream *code, text_stream *language_name) { WRITE("\\beginlines\n\\hskip1em"); TEMPORARY_TEXT(colouring) void *colours = NULL; MDRender::colour_code_block(rdr, language_name, code, colouring, &colours); if (Str::get_last_char(code) == '\n') { TEMPORARY_TEXT(trimmed) WRITE_TO(trimmed, "%S", code); Str::delete_last_character(colouring); Str::delete_last_character(trimmed); MDRenderLaTeX::render_syntax_coloured(OUT, rdr, trimmed, colouring, colours); DISCARD_TEXT(trimmed) } else { MDRenderLaTeX::render_syntax_coloured(OUT, rdr, code, colouring, colours); } DISCARD_TEXT(colouring) WRITE("\\endlines\n"); } void MDRenderLaTeX::render_syntax_coloured(OUTPUT_STREAM, markdown_render *rdr, text_stream *code, text_stream *colouring, void *colours) { WRITE("|"); MDRender::render_code_block(OUT, rdr, code, colouring, colours, I"|\n\\hskip1em|", MDRenderLaTeX::change_colour); WRITE("|"); } void MDRenderLaTeX::change_colour(text_stream *OUT, markdown_render *rdr, int col, void *cs) { WRITE("|"); if (col == -1) { WRITE("\\endcolouring{}"); } else { TEMPORARY_TEXT(cl) MDRender::name_colour(rdr, cl, col); if (Str::len(cl) == 0) { PRINT("col: %d\n", col); internal_error("bad colour"); } else { WRITE("\\"); if (cs) { TEMPORARY_TEXT(csname) MDRender::name_colour_scheme(rdr, csname, cs); for (int i=0; i<Str::len(csname); i++) if (Characters::isalpha(Str::get_at(csname, i))) PUT(Characters::tolower(Str::get_at(csname, i))); DISCARD_TEXT(csname) } if (cl) for (int i=0; i<Str::len(cl); i++) if (Characters::isalpha(Str::get_at(cl, i))) PUT(Characters::tolower(Str::get_at(cl, i))); WRITE("colouring{}"); } DISCARD_TEXT(cl) } WRITE("|"); }