[TeX::] TeX Format. To provide for weaving in the standard maths and science typesetting software, TeX. @ \section{Creation.} <<*>>= void TeX::create(void) { weave_format *wf = Formats::create_weave_format(I"TeX", I".tex"); METHOD_ADD(wf, RENDER_FOR_MTID, TeX::render_TeX); METHOD_ADD(wf, PREFORM_DOCUMENT_FOR_MTID, TeX::preform_document); } @ \section{Rendering.} At present, this renderer only makes the dialect of TeX needed for [[pdftex]], which involves various extension commands: the curse of modern TeX is the combination of an outdated original, and a proliferation of non-canonical extensions, but [[pdftex]] is pretty good. All the same, we should perhaps consider adding LaTeX, or XeTeX. <<*>>= enum PDFTEX_TEX_FORM from 1 <<*>>= void TeX::render_TeX(weave_format *self, text_stream *OUT, heterogeneous_tree *tree) { TeX::render_inner(OUT, tree, PDFTEX_TEX_FORM); } @ From here on, then, the renderer, which should generate TeX which is as generic as possible, but with special features depending on [[trs->TeX_form]]. <<*>>= typedef struct TeX_render_state { struct text_stream *OUT; struct weave_order *wv; int TeX_form; } TeX_render_state; void TeX::render_inner(text_stream *OUT, heterogeneous_tree *tree, int form) { weave_document_node *C = RETRIEVE_POINTER_weave_document_node(tree->root->content); TeX_render_state trs; trs.OUT = OUT; trs.wv = C->wv; trs.TeX_form = form; Trees::traverse_from(tree->root, &TeX::render_visit, (void *) &trs, 0); } @ For the reason why footnotes are omitted, see below: they aren't really. <<*>>= int TeX::render_visit(tree_node *N, void *state, int L) { TeX_render_state *trs = (TeX_render_state *) state; text_stream *OUT = trs->OUT; if ((N->type == weave_document_node_type) || (N->type == weave_body_node_type) || (N->type == weave_chapter_title_page_node_type) || (N->type == weave_chapter_footer_node_type) || (N->type == weave_section_footer_node_type) || (N->type == weave_audio_node_type) || (N->type == weave_video_node_type) || (N->type == weave_download_node_type) || (N->type == weave_chapter_node_type) || (N->type == weave_carousel_slide_node_type) || (N->type == weave_begin_footnote_text_node_type)) <> else if (N->type == weave_head_node_type) <> else if (N->type == weave_tail_node_type) <> else if (N->type == weave_verbatim_node_type) <> else if (N->type == weave_chapter_header_node_type) <> else if (N->type == weave_section_header_node_type) <> else if (N->type == weave_section_purpose_node_type) <> else if (N->type == weave_subheading_node_type) <> else if (N->type == weave_bar_node_type) <> else if (N->type == weave_pagebreak_node_type) <> else if (N->type == weave_linebreak_node_type) <> else if (N->type == weave_paragraph_heading_node_type) <> else if (N->type == weave_endnote_node_type) <> else if (N->type == weave_figure_node_type) <> else if (N->type == weave_material_node_type) <> else if (N->type == weave_embed_node_type) <> else if (N->type == weave_pmac_node_type) <> else if (N->type == weave_vskip_node_type) <> else if (N->type == weave_section_node_type) <> else if (N->type == weave_code_line_node_type) <> else if (N->type == weave_function_usage_node_type) <> else if (N->type == weave_commentary_node_type) <> else if (N->type == weave_toc_node_type) <> else if (N->type == weave_toc_line_node_type) <> else if (N->type == weave_defn_node_type) <> else if (N->type == weave_source_code_node_type) <> else if (N->type == weave_url_node_type) <> else if (N->type == weave_footnote_cue_node_type) <> else if (N->type == weave_display_line_node_type) <> else if (N->type == weave_function_defn_node_type) <> else if (N->type == weave_item_node_type) <> else if (N->type == weave_grammar_index_node_type) <> else if (N->type == weave_inline_node_type) <> else if (N->type == weave_locale_node_type) <> else if (N->type == weave_maths_node_type) <> else internal_error("unable to render unknown node"); return TRUE; } <>= weave_head_node *C = RETRIEVE_POINTER_weave_head_node(N->content); WRITE("%% %S\n", C->banner); <>= weave_tail_node *C = RETRIEVE_POINTER_weave_tail_node(N->content); WRITE("%% %S\n", C->rennab); WRITE("\\end\n"); <>= weave_chapter_header_node *C = RETRIEVE_POINTER_weave_chapter_header_node(N->content); if (Str::ne(C->chap->md->ch_range, I"S")) { TeX::general_heading(OUT, trs->wv, FIRST_IN_LINKED_LIST(section, C->chap->sections), NULL, C->chap->md->ch_title, 3, FALSE); WRITE("%S\\medskip\n", C->chap->md->rubric); section *S; LOOP_OVER_LINKED_LIST(S, section, C->chap->sections) { WRITE("\\smallskip\\noindent "); if (trs->wv->pattern->number_sections) WRITE("%d. ", S->printed_number); WRITE("{\\it %S}\\qquad\n%S", S->md->sect_title, S->sect_purpose); } } <>= weave_section_header_node *C = RETRIEVE_POINTER_weave_section_header_node(N->content); TeX::general_heading(OUT, trs->wv, C->sect, NULL, C->sect->md->sect_title, 2, FALSE); <>= weave_section_purpose_node *C = RETRIEVE_POINTER_weave_section_purpose_node(N->content); WRITE("\\smallskip\\par\\noindent{\\it %S}\\smallskip\\noindent\n", C->purpose); <>= weave_subheading_node *C = RETRIEVE_POINTER_weave_subheading_node(N->content); WRITE("\\par\\noindent{\\bf %S}\\mark{%S}\\medskip\n", C->text, NULL); <>= WRITE("\\par\\medskip\\noindent\\hrule\\medskip\\noindent\n"); <>= WRITE("\\vfill\\eject\n"); <>= WRITE("\n"); <>= weave_paragraph_heading_node *C = RETRIEVE_POINTER_weave_paragraph_heading_node(N->content); TeX::general_heading(OUT, trs->wv, C->para->under_section, C->para, I"", 0, FALSE); <>= WRITE("\\par\\noindent\\penalty10000\n"); WRITE("{\\usagefont "); <>; WRITE("}\\smallskip\n"); return FALSE; @ TeX itself has an almost defiant lack of support for anything pictorial, which is one reason it didn't live up to its hope of being the definitive basis for typography; even today the loose confederation of TeX-like programs and extensions lack standard approaches. Here we're going to use [[pdftex]] features, having nothing better. All we're trying for is to insert a picture, scaled to a given width, into the text at the current position. <>= weave_figure_node *C = RETRIEVE_POINTER_weave_figure_node(N->content); filename *F = Filenames::in( Pathnames::down(trs->wv->weave_web->md->path_to_web, I"Figures"), C->figname); WRITE("\\pdfximage"); if (C->w >= 0) WRITE(" width %d cm{%f}\n", C->w/POINTS_PER_CM, F); else if (C->h >= 0) WRITE(" height %d cm{%f}\n", C->h/POINTS_PER_CM, F); else WRITE("{%f}\n", F); WRITE("\\smallskip\\noindent" "\\hbox to\\hsize{\\hfill\\pdfrefximage \\pdflastximage\\hfill}" "\\smallskip\n"); <>= weave_material_node *C = RETRIEVE_POINTER_weave_material_node(N->content); paragraph *first_in_para = NULL; if ((N == N->parent->child) && (N->parent->type == weave_paragraph_heading_node_type)) { weave_paragraph_heading_node *PC = RETRIEVE_POINTER_weave_paragraph_heading_node(N->parent->content); first_in_para = PC->para; } if (C->material_type == COMMENTARY_MATERIAL) <> else if (C->material_type == CODE_MATERIAL) <> else if (C->material_type == FOOTNOTES_MATERIAL) <> else if (C->material_type == ENDNOTES_MATERIAL) <> else if (C->material_type == MACRO_MATERIAL) <> else if (C->material_type == DEFINITION_MATERIAL) <>; return FALSE; <>= <>; WRITE("\n"); <>= WRITE("\\beginlines\n"); <>; WRITE("\\endlines\n"); <>= return FALSE; <>= <>; <>= <>; WRITE("\n"); <>= WRITE("\\beginlines\n"); <>; WRITE("\\endlines\n"); <>= weave_verbatim_node *C = RETRIEVE_POINTER_weave_verbatim_node(N->content); WRITE("%S", C->content); <>= ; <>= weave_embed_node *C = RETRIEVE_POINTER_weave_embed_node(N->content); LOG("It was %d\n", C->allocation_id); <>= weave_pmac_node *C = RETRIEVE_POINTER_weave_pmac_node(N->content); TeX::para_macro(OUT, trs->wv, C->pmac, C->defn); <>= weave_vskip_node *C = RETRIEVE_POINTER_weave_vskip_node(N->content); if (C->in_comment) WRITE("\\smallskip\\par\\noindent%%\n"); else WRITE("\\smallskip\n"); <>= weave_section_node *C = RETRIEVE_POINTER_weave_section_node(N->content); LOG("It was %d\n", C->allocation_id); <>= WRITE("\\smallskip\\par\\noindent "); WRITE("|"); <>; WRITE("|"); WRITE("\n"); return FALSE; <>= weave_function_usage_node *C = RETRIEVE_POINTER_weave_function_usage_node(N->content); WRITE("%S", C->fn->function_name); return FALSE; <>= weave_commentary_node *C = RETRIEVE_POINTER_weave_commentary_node(N->content); if (C->in_code) WRITE(" |\\hfill{\\ttninepoint\\it "); TeX::commentary_text(OUT, trs->wv, C->text); if (C->in_code) WRITE("}|"); <>= WRITE("\\medskip\\hrule\\smallskip\\par\\noindent{\\usagefont "); for (tree_node *M = N->child; M; M = M->next) { Trees::traverse_from(M, &TeX::render_visit, (void *) trs, L+1); if (M->next) WRITE("; "); } WRITE("}\\par\\medskip\\hrule\\bigskip\n"); return FALSE; <>= weave_toc_line_node *C = RETRIEVE_POINTER_weave_toc_line_node(N->content); WRITE("%S~%S", C->text1, C->text2); <>= weave_defn_node *C = RETRIEVE_POINTER_weave_defn_node(N->content); WRITE("[[{\\ninebf %S} ]]", C->keyword); <>= weave_source_code_node *C = RETRIEVE_POINTER_weave_source_code_node(N->content); int starts = FALSE; if (N == N->parent->child) starts = TRUE; TeX::source_code(OUT, trs->wv, C->matter, C->colouring, starts); <>= weave_url_node *C = RETRIEVE_POINTER_weave_url_node(N->content); WRITE("%S", C->url); @ The TeX macro for footnotes means that the text has to accompany the cue, which is tricky for us now because the footnote text is somewhere else in the weave tree -- so, we go for a little walk: <>= weave_footnote_cue_node *C = RETRIEVE_POINTER_weave_footnote_cue_node(N->content); WRITE("\\footnote{${}^{%S}$}{", C->cue_text); tree_node *M = N; while ((M) && (M->type != weave_paragraph_heading_node_type)) M = M->parent; if (M == NULL) internal_error("tree without section nodes"); M = M->child; int found = FALSE; while (M) { if (M->type == weave_material_node_type) { weave_material_node *MC = RETRIEVE_POINTER_weave_material_node(M->content); if (MC->material_type == FOOTNOTES_MATERIAL) { tree_node *F = M->child; while (F) { if (F->type == weave_begin_footnote_text_node_type) { weave_begin_footnote_text_node *FC = RETRIEVE_POINTER_weave_begin_footnote_text_node(F->content); if (Str::eq(FC->cue_text, C->cue_text)) <>; } F = F->next; } } } M = M->next; } WRITE("}"); if (found == FALSE) internal_error("cue without text"); @ And so here's the text. Note that we render only its second and subsequent child nodes: that's because the first child is a copy of the footnote cue, and TeX renders that automatically. (The TeX renderer otherwise ignores footnote texts, so if these nodes are not rendered here, they never will be.) <>= for (tree_node *X = F->child->next; X; X = X->next) Trees::traverse_from(X, &TeX::render_visit, (void *) trs, L+1); found = TRUE; <>= weave_display_line_node *C = RETRIEVE_POINTER_weave_display_line_node(N->content); WRITE("\\quotesource{%S}\n", C->text); <>= weave_function_defn_node *C = RETRIEVE_POINTER_weave_function_defn_node(N->content); TeX::change_colour_PDF(OUT, FUNCTION_COLOUR, TRUE); WRITE("%S", C->fn->function_name); TeX::change_colour_PDF(OUT, PLAIN_COLOUR, TRUE); return FALSE; <>= weave_item_node *C = RETRIEVE_POINTER_weave_item_node(N->content); if (Str::len(C->label) > 0) { if (C->depth == 1) WRITE("\\item{(%S)}", C->label); else WRITE("\\itemitem{(%S)}", C->label); } else { if (C->depth == 1) WRITE("\\item{}"); else WRITE("\\itemitem{}"); } <>= InCSupport::weave_grammar_index(OUT); <>= WRITE("|"); <>; WRITE("|"); return FALSE; <>= weave_locale_node *C = RETRIEVE_POINTER_weave_locale_node(N->content); WRITE("$\\%S$%S", C->par1->ornament, C->par1->paragraph_number); if (C->par2) WRITE("-%S", C->par2->paragraph_number); <>= weave_maths_node *C = RETRIEVE_POINTER_weave_maths_node(N->content); if (C->displayed) WRITE("$$"); else WRITE("$"); WRITE("%S", C->content); if (C->displayed) WRITE("$$"); else WRITE("$"); <>= for (tree_node *M = N->child; M; M = M->next) Trees::traverse_from(M, &TeX::render_visit, (void *) trs, L+1); <<*>>= text_stream *P_literal = NULL; void TeX::general_heading(text_stream *OUT, weave_order *wv, section *S, paragraph *P, text_stream *heading_text, int weight, int no_skip) { text_stream *TeX_macro = NULL; <>; if (P_literal == NULL) P_literal = Str::new_from_wide_string(L"P"); text_stream *orn = (P)?(P->ornament):P_literal; text_stream *N = (P)?(P->paragraph_number):NULL; TEMPORARY_TEXT(mark) <>; TEMPORARY_TEXT(modified) Str::copy(modified, heading_text); match_results mr = Regexp::create_mr(); if (Regexp::match(&mr, modified, L"(%c*?): (%c*)")) { Str::clear(modified); WRITE_TO(modified, "{\\sinchhigh %S}\\quad %S", mr.exp[0], mr.exp[1]); } if (weight == 2) WRITE("\\%S{%S}{%S}{%S}{\\%S}{%S}%%\n", TeX_macro, N, modified, mark, orn, NULL); else WRITE("\\%S{%S}{%S}{%S}{\\%S}{%S}%%\n", TeX_macro, N, modified, mark, orn, S->md->sect_range); DISCARD_TEXT(mark) DISCARD_TEXT(modified) Regexp::dispose_of(&mr); } @ We want to have different heading styles for different weights, and TeX is horrible at using macro parameters as function arguments, so we don't want to pass the weight that way. Instead we use \weavesection \weavesections \weavesectionss \weavesectionsss where the weight is the number of terminal [[s]]s, 0 to 3. (TeX macros, lamentably, are not allowed digits in their name.) In the cases 0 and 1, we also have variants [[\nsweavesection]] and [[\nsweavesections]] which are the same, but with the initial vertical spacing removed; these allow us to prevent unsightly excess white space in certain configurations of a section. <>= switch (weight) { case 0: TeX_macro = I"weavesection"; break; case 1: TeX_macro = I"weavesections"; break; case 2: TeX_macro = I"weavesectionss"; break; default: TeX_macro = I"weavesectionsss"; break; } if (wv->theme_match) { switch (weight) { case 0: TeX_macro = I"tweavesection"; break; case 1: TeX_macro = I"tweavesections"; break; case 2: TeX_macro = I"tweavesectionss"; break; default: TeX_macro = I"tweavesectionsss"; break; } } if (no_skip) { switch (weight) { case 0: TeX_macro = I"nsweavesection"; break; case 1: TeX_macro = I"nsweavesections"; break; } } @ "Marks" are the contrivance by which TeX produces running heads on pages which follow the material on those pages: so that the running head for a page can show the paragraph range for the material which tops it, for instance. The ornament has to be set in math mode, even in the mark. [[\S]] and [[\P]], making a section sign and a pilcrow respectively, only work in math mode because they abbreviate characters found in math fonts but not regular ones, in TeX's deeply peculiar font encoding system. <>= text_stream *chaptermark = Str::new(); text_stream *sectionmark = Str::new(); if (weight == 3) { Str::copy(chaptermark, S->owning_chapter->md->ch_title); Str::clear(sectionmark); } if (weight == 2) { Str::copy(sectionmark, S->md->sect_title); Str::clear(chaptermark); if (Str::len(chaptermark) > 0) { Str::clear(sectionmark); WRITE_TO(sectionmark, " - %S", S->md->sect_title); } } WRITE_TO(mark, "%S%S\\quad$\\%S$%S", chaptermark, sectionmark, orn, N); @ Code is typeset by TeX within vertical strokes; these switch a sort of typewriter-type verbatim mode on and off. To get an actual stroke, we must escape from code mode, escape it using a backslash, then re-enter code mode once again: <<*>>= void TeX::source_code(text_stream *OUT, weave_order *wv, text_stream *matter, text_stream *colouring, int starts) { int current_colour = PLAIN_COLOUR, colour_wanted = PLAIN_COLOUR; for (int i=0; i < Str::len(matter); i++) { colour_wanted = Str::get_at(colouring, i); <>; if (Str::get_at(matter, i) == '[[') WRITE("|\\|]]"); else WRITE("%c", Str::get_at(matter, i)); } colour_wanted = PLAIN_COLOUR; <>; } <>= if (colour_wanted != current_colour) { TeX::change_colour_PDF(OUT, colour_wanted, TRUE); current_colour = colour_wanted; } <<*>>= void TeX::change_colour_PDF(text_stream *OUT, int col, int in_code) { char *inout = ""; if (in_code) inout = "|"; switch (col) { case DEFINITION_COLOUR: WRITE("%s\\pdfliteral direct{1 1 0 0 k}%s", inout, inout); break; case FUNCTION_COLOUR: WRITE("%s\\pdfliteral direct{0 1 1 0 k}%s", inout, inout); break; case PLAIN_COLOUR: WRITE("%s\\special{PDF:0 g}%s", inout, inout); break; case EXTRACT_COLOUR: WRITE("%s\\special{PDF:0 g}%s", inout, inout); break; } } @ Any usage of angle-macros is highlighted in several cute ways: first, we make use of colour and we drop in the paragraph number of the definition of the macro in small type; and second, we use cross-reference links. In the PDF format, these three are all called, in sequence below; in TeX or DVI, only the middle one is. <<*>>= void TeX::para_macro(text_stream *OUT, weave_order *wv, para_macro *pmac, int defn) { if (defn) WRITE("|\\pdfdest num %d fit ", pmac->allocation_id + 100); else WRITE("|\\pdfstartlink attr{/C [0.9 0 0] /Border [0 0 0]} goto num %d ", pmac->allocation_id + 100); WRITE("$\\langle${\\xreffont"); TeX::change_colour_PDF(OUT, DEFINITION_COLOUR, FALSE); WRITE("%S ", pmac->macro_name); WRITE("{\\sevenss %S}}", pmac->defining_paragraph->paragraph_number); TeX::change_colour_PDF(OUT, PLAIN_COLOUR, FALSE); WRITE("$\\rangle$ "); if (defn) WRITE("$\\equiv$|"); else WRITE("\\pdfendlink|"); } <<*>>= void TeX::commentary_text(text_stream *OUT, weave_order *wv, text_stream *id) { int math_mode = FALSE; for (int i=0; i < Str::len(id); i++) { switch (Str::get_at(id, i)) { case '$': math_mode = (math_mode)?FALSE:TRUE; WRITE("%c", Str::get_at(id, i)); break; case '_': if (math_mode) WRITE("_"); else WRITE("\\_"); break; case '"': if ((Str::get_at(id, i) == '"') && ((i==0) [[| (Str::get_at(id, i-1) == ' ') |]] (Str::get_at(id, i-1) == '('))) WRITE("``"); else WRITE("''"); break; default: WRITE("%c", Str::get_at(id, i)); break; } } } @ The following is called only when the language is InC, and the weave is of the special Preform grammar document. <<*>>= int TeX::preform_document(weave_format *self, text_stream *OUT, web *W, weave_order *wv, chapter *C, section *S, source_line *L, text_stream *matter, text_stream *concluding_comment) { if (L->preform_nonterminal_defined) { preform_production_count = 0; <>; return TRUE; } else { if (L->category == PREFORM_GRAMMAR_LCAT) { <>; return TRUE; } } return FALSE; } <>= WRITE("\\nonterminal{%S} [[::=]]", L->preform_nonterminal_defined->unangled_name); if (L->preform_nonterminal_defined->as_function) { WRITE("\\quad{\\it internal definition"); if (L->preform_nonterminal_defined->voracious) WRITE(" (voracious)"); else if (L->preform_nonterminal_defined->min_word_count == L->preform_nonterminal_defined->max_word_count) WRITE(" (%d word%s)", L->preform_nonterminal_defined->min_word_count, (L->preform_nonterminal_defined->min_word_count != 1)?"s":""); WRITE("}"); } WRITE("\n"); <>= TEMPORARY_TEXT(problem) match_results mr = Regexp::create_mr(); if (Regexp::match(&mr, matter, L"Issue (%c*?) problem")) Str::copy(problem, mr.exp[0]); else if (Regexp::match(&mr, matter, L"FAIL_NONTERMINAL %+")) WRITE_TO(problem, "fail and skip"); else if (Regexp::match(&mr, matter, L"FAIL_NONTERMINAL")) WRITE_TO(problem, "fail"); preform_production_count++; WRITE_TO(matter, "[[%S]]", L->text_operand); while (Regexp::match(&mr, matter, L"(%c+?)|(%c+)")) { Str::clear(matter); WRITE_TO(matter, "%S___stroke___%S", mr.exp[0], mr.exp[1]); } while (Regexp::match(&mr, matter, L"(%c*?)___stroke___(%c*)")) { Str::clear(matter); WRITE_TO(matter, "%S[[\\|]]%S", mr.exp[0], mr.exp[1]); } while (Regexp::match(&mr, matter, L"(%c*)<(%c*?)>(%c*)")) { Str::clear(matter); WRITE_TO(matter, "%S[[\\nonterminal{%S}]]%S", mr.exp[0], mr.exp[1], mr.exp[2]); } TEMPORARY_TEXT(label) int N = preform_production_count; int L = ((N-1)%26) + 1; if (N <= 26) WRITE_TO(label, "%c", 'a'+L-1); else if (N <= 52) WRITE_TO(label, "%c%c", 'a'+L-1, 'a'+L-1); else if (N <= 78) WRITE_TO(label, "%c%c%c", 'a'+L-1, 'a'+L-1, 'a'+L-1); else { int n = (N-1)/26; WRITE_TO(label, "%c${}^{%d}$", 'a'+L-1, n); } WRITE("\\qquad {\\hbox to 0.4in{\\it %S\\hfil}}%S", label, matter); if (Str::len(problem) > 0) WRITE("\\hfill$\\longrightarrow$ {\\ttninepoint\\it %S}", problem); else if (Str::len(concluding_comment) > 0) { WRITE(" \\hfill{\\ttninepoint\\it "); if (Str::len(concluding_comment) > 0) TeX::commentary_text(OUT, wv, concluding_comment); WRITE("}"); } WRITE("\n"); DISCARD_TEXT(label) DISCARD_TEXT(problem) Regexp::dispose_of(&mr);