-*- mode: org -*-
#+TITLE:       spine (doc_reform) output latex
#+DESCRIPTION: documents - structuring, publishing in multiple formats & search
#+FILETAGS:    :spine:output:latex:pdf:
#+AUTHOR:      Ralph Amissah
#+EMAIL:       [[mailto:ralph.amissah@gmail.com][ralph.amissah@gmail.com]]
#+COPYRIGHT:   Copyright (C) 2015 - 2020 Ralph Amissah
#+LANGUAGE:    en
#+STARTUP:     content hideblocks hidestars noindent entitiespretty
#+PROPERTY:    header-args  :exports code
#+PROPERTY:    header-args+ :noweb yes
#+PROPERTY:    header-args+ :eval no
#+PROPERTY:    header-args+ :results no
#+PROPERTY:    header-args+ :cache no
#+PROPERTY:    header-args+ :padline no

- [[./spine.org][spine]]  [[./][org/]]
- [[./output_hub.org][output_hub]]

* latex
** _module template_                                         :latex:pdf:module:

#+BEGIN_SRC d :tangle "../src/doc_reform/io_out/latex.d" :noweb yes
module doc_reform.io_out.latex;
template outputLaTeX() {
  <<output_latex_imports>>
  mixin InternalMarkup; // watch
  mixin spineRgxOut;
  static auto rgx = RgxO();
  mixin spineLanguageCodes;
  auto lang = Lang();
  <<output_latex_shared>>
<<output_latex_shared_0>>
  <<output_latex_head>>
<<output_latex_head_1_tex>>
  <<output_latex_head_1_format_string_variables>>
  <<output_latex_head_0_format_string>>
<<output_latex_head_0_tex>>
  <<output_latex_head_0_format_string_variables>>
  <<output_latex_head_close>>
  <<output_latex_body>>
  <<output_latex_tail>>
<<output_latex_tail_tex>>
  <<output_latex_tail_close>>
  <<output_latex>>
}
#+END_SRC

** write latex output                                             :latex:out:

#+NAME: output_latex
#+BEGIN_SRC d
void writeOutputLaTeX(T,M)(
  const T      latex_content,
        M      doc_matters,
        string paper_size_orientation,
) {
  auto pth_latex = spinePathsLaTeX(doc_matters);
  try {
    { /+ debug +/
      if (doc_matters.opt.action.debug_do
      && doc_matters.opt.action.verbose) {
        writeln(latex_content.head);
        writeln(latex_content.content);
        writeln(latex_content.tail);
      }
    }
    if (!exists(pth_latex.latex_path_stuff)) {
      (pth_latex.latex_path_stuff).mkdirRecurse;
    }
    if (!(doc_matters.opt.action.quiet)) {
      writeln(" ", pth_latex.latex_file_with_path(paper_size_orientation));
    }
    auto f = File(pth_latex.latex_file_with_path(paper_size_orientation), "w");
    f.writeln(latex_content.head);
    f.writeln(latex_content.content);
    f.writeln(latex_content.tail);
    foreach (image; doc_matters.srcs.image_list) {
      string fn_src_in = doc_matters.src.image_dir_path ~ "/" ~ image;
      string fn_src_out_file = pth_latex.latex_path_stuff ~ "/" ~ image;
      if (exists(fn_src_in)) {
        fn_src_in.copy(fn_src_out_file);
      }
    }
  } catch (ErrnoException ex) {
    // handle error
  }
}
#+END_SRC

** latex output hub [#A]                                      :latex:pdf:out:

#+NAME: output_latex
#+BEGIN_SRC d
void outputLaTeX(D,M)(
  const    D   doc_abstraction,
           M   doc_matters,
) {
  struct LaTeX {
    string head;
    string content;
    string tail;
  }
  auto latex           = LaTeX();
  foreach (paper_size_orientation; doc_matters.conf_make_meta.conf.set_papersize) {
    latex.head           = latex_head(doc_matters, paper_size_orientation);
    latex.content        = latex_body(doc_abstraction, doc_matters, paper_size_orientation);
    latex.tail           = latex_tail(doc_matters, paper_size_orientation);
    latex.writeOutputLaTeX(doc_matters, paper_size_orientation);
  }
}
#+END_SRC

* stuff
** output imports

#+NAME: output_latex_imports
#+BEGIN_SRC d
import
  std.digest.sha,
  std.file,
  std.outbuffer,
  std.uri,
  std.conv : to;
import doc_reform.io_out;
#+END_SRC

** shared
*** paper dimensions (struct)

#+NAME: output_latex_shared
#+BEGIN_SRC d
    auto paper() {
      struct PaperType {
        @safe auto a4() {
          struct A4 {
            auto portrait() {
              struct V {
                const uint   w            = 160;
                const uint   h            = 228;
                string       width        = format(q"┃%dmm┃", w);
                string       height       = format(q"┃%dmm┃", h);
                string       font_size    = "12pt";
                string       name         = "a4paper";
                uint         img_px       = 450;
                bool         is_portrait  = true;
              }
              return V();
            }
            auto landscape() {
              struct H {
                const uint   w            = 238;
                const uint   h            = 160;
                string       width        = format(q"┃%dmm┃", w);
                string       height       = format(q"┃%dmm┃", h);
                string       font_size    = "11pt";
                string       name         = "a4paper";
                uint         img_px       = 300;
                bool         is_portrait  = false;
              }
              return H();
            }
          }
          return A4();
        }
        @safe auto a5() {
          struct A5 {
            auto portrait() {
              struct V {
                const uint   w           = 112;
                const uint   h           = 162;
                string       width       = format(q"┃%dmm┃", w);
                string       height      = format(q"┃%dmm┃", h);
                string       font_size   = "0pt";
                string       name        = "a5paper";
                uint         img_px      = 280;
                bool         is_portrait = true;
              }
              return V();
            }
            auto landscape() {
              struct H {
                const uint   w            = 152;
                const uint   h            = 100;
                string       width        = format(q"┃%dmm┃", w);
                string       height       = format(q"┃%dmm┃", h);
                string       font_size    = "0pt";
                string       name         = "a5paper";
                uint         img_px       = 190;
                bool         is_portrait  = false;
              }
              return H();
            }
          }
          return A5();
        }
        @safe auto b4() {
          struct B4 {
            auto portrait() {
              struct V {
                const uint   w            = 140;
                const uint   h            = 204;
                string       width        = format(q"┃%dmm┃", w);
                string       height       = format(q"┃%dmm┃", h);
                string       font_size    = "0pt";
                string       name         = "b4paper";
                uint         img_px       = 356;
                bool         is_portrait  = true;
              }
              return V();
            }
            auto landscape() {
              struct H {
                const uint   w            = 200;
                const uint   h            = 130;
                string       width        = format(q"┃%dmm┃", w);
                string       height       = format(q"┃%dmm┃", h);
                string       font_size    = "0pt";
                string       name         = "b4paper";
                uint         img_px       = 260;
                bool         is_portrait  = false;
              }
              return H();
            }
          }
          return B4();
        }
        @safe auto letter() {
          struct Letter {
            auto portrait() {
              struct V {
                const uint   w            = 166;
                const uint   h            = 212;
                string       width        = format(q"┃%dmm┃", w);
                string       height       = format(q"┃%dmm┃", h);
                string       font_size    = "0pt";
                string       name         = "letterpaper";
                uint         img_px       = 468;
                bool         is_portrait  = true;
              }
              return V();
            }
            auto landscape() {
              struct H {
                const uint   w            = 226;
                const uint   h            = 166;
                string       width        = format(q"┃%dmm┃", w);
                string       height       = format(q"┃%dmm┃", h);
                string       font_size    = "0pt";
                string       name         = "letterpaper";
                uint         img_px       = 290;
                bool         is_portrait  = false;
              }
              return H();
            }
          }
          return Letter();
        }
        @safe auto legal() {
          struct Legal {
            auto portrait() {
              struct V {
                const uint   w            = 168;
                const uint   h            = 286;
                string       width        = format(q"┃%dmm┃", w);
                string       height       = format(q"┃%dmm┃", h);
                string       font_size    = "0pt";
                string       name         = "legalpaper";
                uint         img_px       = 474;
                bool         is_portrait  = true;
              }
              return V();
            }
            auto landscape() {
              struct H {
                const uint   w            = 296;
                const uint   h            = 166;
                string       width        = format(q"┃%dmm┃", w);
                string       height       = format(q"┃%dmm┃", h);
                string       font_size    = "0pt";
                string       name         = "legalpaper";
                uint         img_px       = 420;
                bool         is_portrait  = false;
              }
              return H();
            }
          }
          return Legal();
        }
      }
      return PaperType();
    }
#+END_SRC

*** latex \escape special characters
**** general

#+NAME: output_latex_shared
#+BEGIN_SRC d
@safe string sp_char_esc(O)(
  string      _txt,
  const    O  obj,
) {
  string _unescape_sp_char_esc()(string _txt) {
    _txt = _txt
      .replaceAll(rgx.latex_special_char_escaped,
        format(q"┃%s┃", "$1"))
      .replaceAll(rgx.latex_special_char_escaped_braced,
        format(q"┃%s┃", "$1"));
    return _txt;
  }
  string _unescape_fontface_esc()(string _txt) {
    _txt = _txt.replaceAll(rgx.latex_identify_inline_fontface,
         format(q"┃%s%s┃", "$1", "$2"));
    return _txt;
  }
  if (obj.metainfo.is_a != "code") {
    _txt = replaceAll!(m => "\\" ~ m[1])(_txt, rgx.latex_special_char_for_escape);
    _txt = replaceAll!(m => "{\\" ~ m[1] ~ "}")(_txt, rgx.latex_special_char_for_escape_and_braces);
    _txt = replaceAll!(m => _unescape_sp_char_esc(m[0]))(_txt, rgx.latex_identify_inline_link);
    _txt = replaceAll!(m => _unescape_fontface_esc(m[0]))(_txt, rgx.latex_identify_inline_fontface);
  }
  return _txt;
}
#+END_SRC

#+NAME: output_latex_shared
#+BEGIN_SRC d
@safe string sp_char_esc_txt()(
  string      _txt,
) {
  string _unescape_sp_char_esc()(string _txt) {
    _txt = _txt
      .replaceAll(rgx.latex_special_char_escaped,
        format(q"┃%s┃", "$1"))
      .replaceAll(rgx.latex_special_char_escaped_braced,
        format(q"┃%s┃", "$1"));
    return _txt;
  }
  string _unescape_fontface_esc()(string _txt) {
    _txt = _txt.replaceAll(rgx.latex_identify_inline_fontface,
         format(q"┃%s%s┃", "$1", "$2"));
    return _txt;
  }
  _txt = replaceAll!(m => "\\" ~ m[1])(_txt, rgx.latex_special_char_for_escape);
  _txt = replaceAll!(m => "{\\" ~ m[1] ~ "}")(_txt, rgx.latex_special_char_for_escape_and_braces);
  _txt = replaceAll!(m => _unescape_sp_char_esc(m[0]))(_txt, rgx.latex_identify_inline_link);
  _txt = replaceAll!(m => _unescape_fontface_esc(m[0]))(_txt, rgx.latex_identify_inline_fontface);
  return _txt;
}
#+END_SRC

*** not used latex \escape special characters

#+BEGIN_SRC d
@safe string sp_char_esc()(
  string      _txt,
) {
  _txt = replaceAll!(m => "\\" ~ m[1])(_txt, rgx.latex_special_char);
  return _txt;
}
#+END_SRC

*** inline markup
**** fontface

- bold, italics, underscore, strikethrough

#+NAME: output_latex_shared
#+BEGIN_SRC d
@safe string fontface()(
  string      _txt,
) {
_txt = _txt
  .replaceAll(rgx.inline_emphasis,    format(q"┃\begin{bfseries}%s\end{bfseries}┃", "$1"))
  .replaceAll(rgx.inline_bold,        format(q"┃\begin{bfseries}%s\end{bfseries}┃", "$1"))
  .replaceAll(rgx.inline_italics,     format(q"┃\emph{%s}┃",                        "$1"))
  .replaceAll(rgx.inline_italics,     format(q"┃\uline{%s}┃",                       "$1"))
  .replaceAll(rgx.inline_superscript, format(q"┃$$^{\textrm{%s}}$$┃",               "$1"))
  .replaceAll(rgx.inline_subscript,   format(q"┃$$_{\textrm{%s}}$$┃",               "$1"))
  .replaceAll(rgx.inline_strike,      format(q"┃\sout{%s}┃",                        "$1"))
  .replaceAll(rgx.inline_insert,      format(q"┃\uline{%s}┃",                       "$1"))
  .replaceAll(rgx.inline_mono,        format(q"┃\begin{monosp}%s\end{monosp}┃",     "$1"))
  .replaceAll(rgx.inline_italics,     format(q"┃``%s''┃",                           "$1"));
  return _txt;
}
#+END_SRC

**** spaces
***** leading hardspace

#+NAME: output_latex_shared
#+BEGIN_SRC d
@safe string leading_hardspaces()(
  string      _txt,
) {
  string hardspaces(string _spaces) {
    _spaces  = _spaces
      .replaceAll(rgx.space, "\\hardspace ");
    return _spaces;
  }
  _txt = replaceAll!(m => hardspaces(m[0]))(_txt, rgx.spaces_line_start);
  return _txt;
}
#+END_SRC

***** nbsp character

#+NAME: output_latex_shared
#+BEGIN_SRC d
@safe string nbsp_char()(string _txt) {
  if (_txt.match(rgx.nbsp_char)) {
    _txt  = _txt.replaceAll(rgx.nbsp_char, "\\hardspace ");
  }
  return _txt;
}
#+END_SRC

***** remove nbsp character

#+NAME: output_latex_shared
#+BEGIN_SRC d
@safe string nbsp_char_to_space()(string _txt) {
  if (_txt.match(rgx.nbsp_char)) {
    _txt  = _txt.replaceAll(rgx.nbsp_char, " ");
  }
  return _txt;
}
#+END_SRC

**** links and images
***** links / urls

#+NAME: output_latex_shared
#+BEGIN_SRC d
@safe string links_and_images(O,M)(
  string      _txt,
  const    O  obj,
           M  doc_matters,
) {
  if (obj.has.inline_links) { // TODO some images do not have inline links ... image without link
    string _width_adjust(string _width) {
      if (_width.to!int > 300) { _width = "300"; } // will need to vary max with papersize & orientation
      return _width;
    }
    string _latex_image_path(string _image_path) {
      auto pth_latex = spinePathsLaTeX(doc_matters);
      _image_path = pth_latex.latex_path_stuff ~ "/" ~ _image_path;
      return _image_path;
    }
    string _if_images(string _linked_content) {
      if (_linked_content.match(rgx.inline_image_info)) {
        _linked_content = replaceAll!(m =>
            format(q"┃\includegraphics*[width=%spt]{%s}%s┃",
              _width_adjust(m[2]), _latex_image_path(m[1]), " \\\\\n")
          )(_linked_content, rgx.inline_image_info);
      }
      return _linked_content;
    }
    string _check_link(string _link) {
      _link = _link
        .replaceFirst(rgx.latex_clean_internal_link, "")
        .replaceAll(rgx.latex_special_char_for_escape_url, "\\$1");
      return _link;
    }
    if  (obj.metainfo.is_a != "code") {
      _txt = replaceAll!(m =>
          m[1] ~ "┤" ~ to!string((obj.stow.link[m[2].to!ulong])).encode ~ "├"
        )(_txt, rgx.inline_link_number_only);
      _txt = replaceAll!(m =>
          ((m[1] == m[2]) && (m[2].match(rgx.uri)))
          ? format(q"┃\begin{scriptsize}\lefthalfcap\url{%s}\righthalfcup\end{scriptsize}┃", _check_link(m[1]))
          : (m[2].match(rgx.uri)) // ERROR
            ? format(q"┃%s\href{%s}%s{%s}┃", "\\\\\n", _check_link(m[2]), "\n", _if_images(m[1]))
            : format(q"┃\hyperlink{%s}{%s}┃", _check_link(m[2]), _if_images(m[1]))
        )(_txt, rgx.inline_link);
    }
  }
  return _txt;
}
#+END_SRC

*** footnotes
**** footnotes

#+NAME: output_latex_shared
#+BEGIN_SRC d
@safe string footnotes()(
  string      _txt,
) {
  if (_txt.match(rgx.inline_notes_al_gen)) {
    string _tex_note = q"┃\hypertarget{noteref_%s}{}\footnote[%s]{%%
\label{note_%s}%s}┃";
    _txt = _txt.replaceAll(rgx.inline_notes_al_regular_number_note,
      format(_tex_note,
        "$1", "$1", "$1",
        "$2".strip
      ).strip
    );
  }
  return _txt;
}
#+END_SRC

**** footnote remove

#+NAME: output_latex_shared
#+BEGIN_SRC d
@safe string remove_footnotes()(
  string      _txt,
) {
  if (_txt.match(rgx.inline_notes_al_gen)) {
    _txt = replaceAll!(m => "")(_txt, rgx.inline_notes_al_gen);
  }
  return _txt;
}
#+END_SRC

*** para
**** para

#+NAME: output_latex_shared
#+BEGIN_SRC d
@safe string para(O)(
  string      _txt,
  O           obj,
) {
  if (obj.metainfo.is_of_type == "para") {
    string _tex_para;
    _tex_para = q"┃\begin{tiny}\hspace{0mm}\end{tiny}{\marginpar{\begin{tiny}\hspace{0mm}\hypertarget{%s}{%s}\end{tiny}}}%s┃";
    _txt  = format(_tex_para,
      obj.metainfo.object_number,
      obj.metainfo.object_number,
      _txt.footnotes
    ).strip;
  }
  return _txt;
}
#+END_SRC

**** bookindex para

#+NAME: output_latex_shared
#+BEGIN_SRC d
@safe string bookindex(O)(
  string      _txt,
  O           obj,
) {
  if (obj.metainfo.is_of_type == "para"
    && obj.metainfo.is_a == "bookindex"
  ) {
    string _tex_para;
    _tex_para = q"┃%s┃";
    _txt  = format(_tex_para,
      _txt.replaceAll(rgx.latex_clean_bookindex_linebreak, "\n") ~ "\n\\\\\n"
    );
  }
  return _txt;
}
#+END_SRC

*** bullets & indentation

#+NAME: output_latex_head
#+BEGIN_SRC d
@safe string bullets_and_indentation(O)(
  string      _txt,
  O           obj,
) {
  string _tex_para;
  string _hang; string _indent;
  int _paper_margin = -10;
  int _indent_increment = 8; // 5; 10;
  if (obj.attrib.bullet) {
    int _bullet_space = 5;
    _indent = ((obj.attrib.indent_base * _indent_increment) + _paper_margin + _bullet_space).to!string;
    _txt  = format(q"┃\begin{Bullet}{%smm}$\txtbullet$\hspace{\enspace}%s\end{Bullet}┃",
      _indent,
      _txt.footnotes
    ).strip;
  } else if (
    obj.attrib.indent_base != 0
    && obj.attrib.indent_base == obj.attrib.indent_hang
  ) {
    _indent = ((obj.attrib.indent_base * _indent_increment) + _paper_margin).to!string;
    _tex_para = q"┃\begin{ParagraphIndent}{%smm}%s \end{ParagraphIndent}┃";
    _txt = format(_tex_para,
      _indent,
      _txt.footnotes
    ).strip;
  } else if (
    obj.attrib.indent_base != 0
    || obj.attrib.indent_hang != 0
  ) {
    _indent = ((obj.attrib.indent_base * _indent_increment) + _paper_margin).to!string;
    _hang = (((obj.attrib.indent_hang - obj.attrib.indent_base) * _indent_increment)).to!string;
    _tex_para = q"┃\begin{ParagraphHang}{%smm}{%smm}%s \end{ParagraphHang}┃";
    _txt = format(_tex_para,
      _indent, _hang,
      _txt.footnotes
    ).strip;
  }
  return _txt;
}
#+END_SRC

*** heading

#+NAME: output_latex_shared_0
#+BEGIN_SRC d
  @safe string heading(O,M)(
    string      _txt,
    O           obj,
    M           doc_matters,
    string      _part = ""
  ) {
    if (obj.metainfo.is_a == "heading") {
      string _tex_para;
      string _pg_break;
      string _sect;
      string _post;
      string _title_add;
      string _columns = "";
      switch (obj.metainfo.heading_lev_markup) {
      case 0: // A == TITLE
        _pg_break = "\\begin{document}\n";
        goto default;
      case 1: // B == part: section heading level
        _pg_break = "\\clearpage\n";
        goto default;
      case 2: // C == part: section heading level
        _pg_break = "\\clearpage\n";
        goto default;
      case 3: // D == part: section heading level
        _pg_break = "\\clearpage\n";
        goto default;
      case 4: // 1 == section
        _columns = (_part != "bookindex")
          ? "" : "\n\\\\\n\\begin{multicols}{2}";
        _pg_break = "\\clearpage\n";
        _sect = "section";
        _post = "";
        _title_add = format(q"┃
\markboth{%s}{%s}┃",
          doc_matters.conf_make_meta.meta.title_full,
          doc_matters.conf_make_meta.meta.title_full,
        );
        goto default;
      case 5: // 2 == subsection
        _pg_break = "";
        // _pg_break = "newpage"; // doubt this is necessary
        _sect = "subsection";
        _post = " \\\n";
        _title_add = "";
        goto default;
      case 6: // 3 == subsubsection
        _pg_break = "";
        // _pg_break = "newpage"; // doubt this is necessary
        _sect = "subsubsection";
        _post = " \\\n";
        _title_add = "";
        goto default;
      default:
        if (obj.metainfo.heading_lev_markup == 0) {
          _tex_para = q"┃\begin{document}
\title{%s}
\author{ \textnormal{%s}}
\date{\begin{tiny}%s\end{tiny}}
\pagenumbering{roman}\maketitle
\pagestyle{fancy}
\newpage
\markboth{%s}{%s}
\\\\[3]\ \linebreak Copyright {\begin{small}{\copyright\end{small}} %s \\
%s
\pagestyle{fancy}
\clearpage┃";
          _txt = format(_tex_para,
            (doc_matters.conf_make_meta.meta.title_full).sp_char_esc_txt,
            (doc_matters.conf_make_meta.meta.creator_author).sp_char_esc_txt,
            (doc_matters.conf_make_meta.meta.date_published).sp_char_esc_txt,
            (doc_matters.conf_make_meta.meta.title_main).sp_char_esc_txt,
            (doc_matters.conf_make_meta.meta.title_full).sp_char_esc_txt,
            (doc_matters.conf_make_meta.meta.rights_copyright).sp_char_esc_txt,
            (doc_matters.conf_make_meta.meta.rights_license).sp_char_esc_txt,
          );
        } else if (obj.metainfo.heading_lev_markup < 4) {
          if (!(_txt.footnotes.strip == "Endnotes")) {
            _tex_para = q"┃%s\part*{\begin{tiny}\hspace{0mm}\end{tiny}{\marginpar{\begin{tiny}\hspace{0mm}\hypertarget{%s}{%s}\end{tiny}}}%s}
\addcontentsline{toc}{part}{%s}
\markboth{%s}┃";
            _txt = format(_tex_para,
              _pg_break,
              obj.metainfo.object_number,
              obj.metainfo.object_number,
              _txt.strip.footnotes,
              _txt.strip.remove_footnotes,
              (doc_matters.conf_make_meta.meta.title_main).sp_char_esc_txt,
            );
          }
        } else if (obj.metainfo.heading_lev_markup > 3) {
          if (obj.metainfo.heading_lev_markup == 4
          && _txt.match(regex(r"^Table of Contents$"))) {
            _tex_para = q"┃
\pagenumbering{none}
\setcounter{page}{1}
\setlength{\parskip}{1ex plus0.5ex minus0.2ex}
\part*{\begin{tiny}\hspace{0mm}\end{tiny}{\marginpar{\begin{tiny}\hspace{0mm}\hypertarget{1}{1}\end{tiny}}}%s \newline %s}

\clearpage
\markboth{%s}{%s}
\pagenumbering{gobble}
\renewcommand{\contentsname}{}
\tableofcontents
\markboth{%s}{%s}

\clearpage
\pagenumbering{arabic}
\setcounter{page}{1}
\markboth{%s}{%s}
%% \null
\clearpage
\setcounter{page}{1}┃";
            _txt = format(_tex_para,
              (doc_matters.conf_make_meta.meta.title_full).sp_char_esc_txt,
              (doc_matters.conf_make_meta.meta.creator_author).sp_char_esc_txt,
              (doc_matters.conf_make_meta.meta.title_main).sp_char_esc_txt,
              (doc_matters.conf_make_meta.meta.title_main).sp_char_esc_txt,
              (doc_matters.conf_make_meta.meta.title_full).sp_char_esc_txt,
              (doc_matters.conf_make_meta.meta.title_full).sp_char_esc_txt,
              (doc_matters.conf_make_meta.meta.title_full).sp_char_esc_txt,
              (doc_matters.conf_make_meta.meta.title_full).sp_char_esc_txt,
            );
          } else if (obj.metainfo.heading_lev_markup == 4
            && _part == "bookindex"
            && _txt.match(regex(r"^Index$"))
          ) {
            _tex_para = q"┃%s\%s*{\begin{tiny}\hspace{0mm}\end{tiny}{\marginpar{\begin{tiny}\hspace{0mm}\hypertarget{%s}{%s}\end{tiny}}}%s}
\addcontentsline{toc}{%s}{%s%s}%s%s┃";
            _txt = format(_tex_para,
              _pg_break,
              _sect.strip,
              obj.metainfo.object_number,
              obj.metainfo.object_number,
              _txt.footnotes.strip,
              _sect,
              _txt.remove_footnotes.strip,
              _post,
              _title_add,
              _columns,
            );
          } else if (obj.metainfo.dummy_heading
            && obj.metainfo.heading_lev_markup == 4
          ) { /+ dummy headings completely omitted +/
            _txt = "";
          } else {
            _tex_para = q"┃%s\%s*{\begin{tiny}\hspace{0mm}\end{tiny}{\marginpar{\begin{tiny}\hspace{0mm}\hypertarget{%s}{%s}\end{tiny}}}%s}
\addcontentsline{toc}{%s}{%s%s}%s┃";
            _txt = format(_tex_para,
              _pg_break,
              _sect.strip,
              obj.metainfo.object_number,
              obj.metainfo.object_number,
              _txt.footnotes.strip,
              _sect,
              _txt.remove_footnotes.strip,
              _post,
              _title_add,
            );
          }
        }
        break;
      }
    }
    return _txt.strip;
  }
#+END_SRC

*** grouped text
**** group

- (hardspace not honored) clear hardspace marker

#+NAME: output_latex_shared_0
#+BEGIN_SRC d
string group(O,M)(
  string      _txt,
  O           obj,
  M           doc_matters,
) {
  if (obj.metainfo.is_a == "group") {
    string _tex_para;
    _tex_para = q"┃\begin{tiny}\hspace{0mm}\end{tiny}{\marginpar{\begin{tiny}\hspace{0mm}\hypertarget{%s}{%s}\end{tiny}}}\setlength{\parskip}{0.5ex plus0.2ex minus0.1ex}
\begin{footnotesize}
%s
\end{footnotesize}
┃";
    _txt  = format(_tex_para,
      obj.metainfo.object_number,
      obj.metainfo.object_number,
      _txt.footnotes.strip
    ).strip;
  }
  return _txt;
}
#+END_SRC

**** block

- (hardspace honored) \hardspace

#+NAME: output_latex_shared_0
#+BEGIN_SRC d
string block(O,M)(
  string      _txt,
  O           obj,
  M           doc_matters,
) {
  if (obj.metainfo.is_a == "block") {
    // _txt = _txt.nbsp_char;
    string _tex_para;
    _tex_para = q"┃\begin{tiny}\hspace{0mm}\end{tiny}{\marginpar{\begin{tiny}\hspace{0mm}\hypertarget{%s}{%s}\end{tiny}}}\setlength{\parskip}{0.5ex plus0.2ex minus0.1ex}
\begin{footnotesize}
%s
\end{footnotesize}
\setlength{\parskip}{1ex plus0.5ex minus0.2ex}
┃"; // \hardspace
    /+ try both: +/
    _txt = _txt.split(rgx.br_newlines_linebreaks).join("\n\n"); // _txt = _txt.split(rgx.br_newlines_linebreaks).join(" \\\n");
    _txt  = format(_tex_para,
      obj.metainfo.object_number,
      obj.metainfo.object_number,
      _txt.nbsp_char.footnotes.strip
    ).strip;
  }
  return _txt;
}
#+END_SRC

**** verse

- (hardspace honored) \hardspace

#+NAME: output_latex_shared_0
#+BEGIN_SRC d
string verse(O,M)(
  string      _txt,
  O           obj,
  M           doc_matters,
) {
  if (obj.metainfo.is_a == "verse") {
    string _tex_para;
    _tex_para = q"┃\begin{tiny}\hspace{0mm}\end{tiny}{\marginpar{\begin{tiny}\hspace{0mm}\hypertarget{%s}{%s}\end{tiny}}}\setlength{\parskip}{0.1ex plus0.1ex minus0.1ex}
\begin{footnotesize}

%s

\end{footnotesize}
\setlength{\parskip}{1ex plus0.5ex minus0.2ex}
\linebreak
┃"; // \hardspace
    _txt  = format(_tex_para,
      obj.metainfo.object_number,
      obj.metainfo.object_number,
      _txt.nbsp_char.footnotes.split("\n").join("\n\n").strip
    ).strip;
  }
  return _txt;
}
#+END_SRC

**** codeblock

- (hardspace honored) \begin{lstlisting} clear hardspace marker

#+NAME: output_latex_shared_0
#+BEGIN_SRC d
string codeblock(O,M)(
  string      _txt,
  O           obj,
  M           doc_matters,
) {
  if (obj.metainfo.is_a == "code") {
    string _tex_para;
    _tex_para = q"┃\begin{tiny}\hspace{0mm}\end{tiny}{\marginpar{\begin{tiny}\hspace{0mm}\hypertarget{%s}{%s}\end{tiny}}}\setlength{\parskip}{0.5ex plus0.2ex minus0.1ex}\begin{Codeblock}
\begin{lstlisting}
%s
\end{lstlisting}
\end{Codeblock}
\setlength{\parskip}{1ex plus0.5ex minus0.2ex}
┃";
    _txt  = format(_tex_para,
      obj.metainfo.object_number,
      obj.metainfo.object_number,
      _txt.nbsp_char_to_space
    ).strip;
  }
  return _txt;
}
#+END_SRC

**** table

- own set of rules

***** tablarize

#+NAME: output_latex_shared_0
#+BEGIN_SRC d
auto tablarize(O)(
  string            _txt,
  const        O    obj,
) {
  string[] _table_rows = (_txt).split(rgx.table_delimiter_row);
  string[] _table_cols;
  string _table;
  string _tablenote;
  foreach(row_idx, row; _table_rows) {
    _table_cols = row.split(rgx.table_delimiter_col);
    _table ~= "";
    foreach(col_idx, cell; _table_cols) {
      if ((_table_cols.length == 1)
      && (_table_rows.length <= row_idx+2)) { // check row_idx+2 (rather than == ++row_idx)
        _tablenote ~= cell;
      } else {
        // // _table ~= "\\bfseries ";
        // _table ~= cell;
        // _table ~= (_table_cols.length > (col_idx + 1)) ? "&" : "";
        _table ~= format(q"┃%s%s┃",
          cell,
          (_table_cols.length > (col_idx + 1)) ? "&" : ""
        );
      }
    }
    _table ~= "\\\\";
  }
  Tuple!(string, string) t = tuple(
    _table,
    _tablenote,
  );
  return t;
}
#+END_SRC

***** table

#+NAME: output_latex_shared_0
#+BEGIN_SRC d
string table(O,M)(
  string      _txt,
  O           obj,
  M           doc_matters,
  string      paper_size_orientation,
) {
  if (obj.metainfo.is_a == "table") {
    auto _t = _txt.tablarize(obj);
    string _table = _t[0];
    string _t_n = _t[1];
    uint pw = 0;
    switch (paper_size_orientation) {
    case "a4.portrait":      pw = (paper.a4.portrait.w      - 20); break;
    case "a4.landscape":     pw = (paper.a4.landscape.w     - 20); break;
    case "a5.portrait":      pw = (paper.a5.portrait.w      - 20); break;
    case "a5.landscape":     pw = (paper.a5.landscape.w     - 20); break;
    case "b4.portrait":      pw = (paper.b4.portrait.w      - 20); break;
    case "b4.landscape":     pw = (paper.b4.landscape.w     - 20); break;
    case "letter.portrait":  pw = (paper.letter.portrait.w  - 20); break;
    case "letter.landscape": pw = (paper.letter.landscape.w - 20); break;
    case "legal.portrait":   pw = (paper.legal.portrait.w   - 20); break;
    case "legal.landscape":  pw = (paper.legal.landscape.w  - 20); break;
    default:                 pw = 0;                               break;
    }
    // auto textwidth = (pw - 24);
    string _colw = "";
    foreach (w; obj.table.column_widths) {
      _colw ~= format(q"┃p{%.0fmm}┃",
        (w * pw / 100)
        // (w * (pw - 24)/ 100)
        // (w * textwidth / 100)
      );
    }
    string _tex_para;
    _tex_para = q"┃\begin{tiny}\hspace{0mm}\end{tiny}{\marginpar{\begin{tiny}\hspace{0mm}\hypertarget{%s}{%s}\end{tiny}}}
\setlength{\LTleft}{0pt}
\setlength{\LTright}{\fill}
\begin{tiny}
\begin{longtable}{%s}
%s
\end{longtable}
\end{tiny}
┃";
    _txt  = format(_tex_para,
      obj.metainfo.object_number,
      obj.metainfo.object_number,
      _colw,
      _table,
    ).strip;
  }
  return _txt;
}
#+END_SRC

** latex parts
*** latex head                                                         :head:
**** latex head function

#+NAME: output_latex_head
#+BEGIN_SRC d
string latex_head(M)(
  M      doc_matters,
  string paper_size_orientation,
) {
#+END_SRC

**** latex head options
***** paper type dimensions
****** struct

#+NAME: output_latex_head
#+BEGIN_SRC d
  struct paperTypeLatex {
    string a4_portrait;
    string a4_landscape;
    string a5_portrait;
    string a5_landscape;
    string b4_portrait;
    string b4_landscape;
    string us_letter_portrait;
    string us_letter_landscape;
    string us_legal_portrait;
    string us_legal_landscape;
  }
  auto paper_type_latex           = paperTypeLatex();
#+END_SRC

****** footer

#+NAME: output_latex_head
#+BEGIN_SRC d
  string _footer(M)(M doc_matters) {
    string _ft = "\\lfoot[\\textrm{\\thepage}]";
    string _ft_1 = format(q"┃{\tiny \href{%s}{%s}}┃", "http://sisudoc.org", "SiSU",);
    string _ft_2 = format(q"┃
\cfoot{\href{%s}{%s}}┃", "http://git.sisudoc.org", "git",);
    if (doc_matters.conf_make_meta.make.footer.length > 0) {
      if (doc_matters.conf_make_meta.make.footer.length > 0) {
        if (doc_matters.conf_make_meta.make.footer[0].matchAll(rgx.inline_link)) {
          _ft ~= doc_matters.conf_make_meta.make.footer[0]
            .replace(rgx.inline_link, "{\\tiny \\href{$2}{$1}}");
        } else {
          _ft ~= _ft_1;
        }
      }
      if (doc_matters.conf_make_meta.make.footer.length > 1) {
        if (doc_matters.conf_make_meta.make.footer[1].matchAll(rgx.inline_link)) {
          _ft ~= doc_matters.conf_make_meta.make.footer[1]
            .replace(rgx.inline_link, "\n\\cfoot{\\href{$2}{$1}}");
        } else {
          _ft ~= _ft_2;
        }
      }
    } else {
      _ft ~= _ft_1;
      _ft ~= _ft_2;
    }
    return _ft;
  }
#+END_SRC

***** paper margins
****** struct

#+NAME: output_latex_head_1_tex
#+BEGIN_SRC d
  struct paperMargins {
    string portrait;
    string landscape;
  }
  auto margins           = paperMargins();
#+END_SRC

****** portrait

#+NAME: output_latex_head_1_tex
#+BEGIN_SRC d
  margins.portrait    = format(q"┃
#+END_SRC

#+NAME: output_latex_head_1_tex
#+BEGIN_SRC latex
\setlength{\oddsidemargin}{0mm} \setlength{\evensidemargin}{0mm}
\setlength{\topmargin}{-12pt} \setlength{\headheight}{12pt}
\setlength{\headsep}{35pt}
#+END_SRC

#+NAME: output_latex_head_1_tex
#+BEGIN_SRC d
┃",
  );
#+END_SRC

****** landscape

#+NAME: output_latex_head_1_tex
#+BEGIN_SRC d
  margins.landscape    = format(q"┃
#+END_SRC

#+NAME: output_latex_head_1_tex
#+BEGIN_SRC latex
\setlength{\oddsidemargin}{6mm} \setlength{\evensidemargin}{6mm}
\setlength{\topmargin}{-12mm} \setlength{\headheight}{12pt}
\setlength{\headsep}{20pt}
#+END_SRC

#+NAME: output_latex_head_1_tex
#+BEGIN_SRC d
┃",
  );
#+END_SRC

***** multicol
****** struct

#+NAME: output_latex_head_1_tex
#+BEGIN_SRC d
  struct columnsMulti {
    string portrait;
    string landscape;
  }
  auto multicol           = columnsMulti();
#+END_SRC

****** portrait

#+NAME: output_latex_head_1_tex
#+BEGIN_SRC d
  multicol.portrait    = format(q"┃
#+END_SRC

#+NAME: output_latex_head_1_tex
#+BEGIN_SRC latex
\usepackage{multicol}
#+END_SRC

#+NAME: output_latex_head_1_tex
#+BEGIN_SRC d
┃",
  );
#+END_SRC

****** landscape

#+NAME: output_latex_head_1_tex
#+BEGIN_SRC d
  multicol.landscape    = "";
#+END_SRC

***** color links
****** struct

#+NAME: output_latex_head_1_tex
#+BEGIN_SRC d
  struct colorLinks {
    string mono;
    string color;
  }
  auto links           = colorLinks();
#+END_SRC

****** mono

#+NAME: output_latex_head_1_tex
#+BEGIN_SRC d
  links.mono    = format(q"┃
#+END_SRC

#+NAME: output_latex_head_1_tex
#+BEGIN_SRC latex
\usepackage[xetex,
  colorlinks=true,
  urlcolor=myblack,
  filecolor=myblack,
  linkcolor=myblack,
#+END_SRC

#+NAME: output_latex_head_1_tex
#+BEGIN_SRC d
┃",
  );
#+END_SRC

****** color

#+NAME: output_latex_head_1_tex
#+BEGIN_SRC d
  links.color    = format(q"┃
#+END_SRC

#+NAME: output_latex_head_1_tex
#+BEGIN_SRC latex
\usepackage[xetex,
  colorlinks=true,
  urlcolor=myblue,    %% \href{...}{...}   external url
  filecolor=mygreen,  %% \href{...}        local file
  linkcolor=myred,    %% \href{...} and \pageref{...}
#+END_SRC

#+NAME: output_latex_head_1_format_string_variables
#+BEGIN_SRC d
┃",
  );
#+END_SRC

**** latex head starts
***** dimensions & orientation
****** set

#+NAME: output_latex_head_0_format_string
#+BEGIN_SRC d
    string set_paper(P)(P paper_set,) {
      string paper_type_description;
      if (paper_set.is_portrait) {
        paper_type_description = format(q"┃
\documentclass[%s,%s,titlepage]{scrartcl}
\setlength{\textheight}{%s} \setlength{\textwidth}{%s}
┃",
          paper_set.font_size,
          paper_set.name,
          paper_set.height,
          paper_set.width,
        );
      } else {
        paper_type_description = format(q"┃
\documentclass[%s,%s,landscape,titlepage,twocolumn]{scrartcl}
\setlength{\textheight}{%s} \setlength{\textwidth}{%s}
┃",
          paper_set.font_size,
          paper_set.name,
          paper_set.height,
          paper_set.width,
        );
      }
      return paper_type_description;
    }
#+END_SRC

***** (a4, a5, b4, letter, legal) * (portrait & landscape)

#+NAME: output_latex_head_0_format_string
#+BEGIN_SRC d
  string paper_size_orientation_latex;
  switch (paper_size_orientation) {
  case "a4.portrait":      paper_size_orientation_latex = set_paper(paper.a4.portrait);      break;
  case "a4.landscape":     paper_size_orientation_latex = set_paper(paper.a4.landscape);     break;
  case "a5.portrait":      paper_size_orientation_latex = set_paper(paper.a5.portrait);      break;
  case "a5.landscape":     paper_size_orientation_latex = set_paper(paper.a5.landscape);     break;
  case "b4.portrait":      paper_size_orientation_latex = set_paper(paper.b4.portrait);      break;
  case "b4.landscape":     paper_size_orientation_latex = set_paper(paper.b4.landscape);     break;
  case "letter.portrait":  paper_size_orientation_latex = set_paper(paper.letter.portrait);  break;
  case "letter.landscape": paper_size_orientation_latex = set_paper(paper.letter.landscape); break;
  case "legal.portrait":   paper_size_orientation_latex = set_paper(paper.legal.portrait);   break;
  case "legal.landscape":  paper_size_orientation_latex = set_paper(paper.legal.landscape);  break;
  default:                 paper_size_orientation_latex = paper_type_latex.a4_portrait;
  }
#+END_SRC

***** set color links

#+NAME: output_latex_head_0_format_string
#+BEGIN_SRC d
  string links_mono_or_color_set = links.mono.strip;
  if (
    (doc_matters.opt.action.latex_color_links)
    || (paper_size_orientation ==
      "a4.landscape" ||
      "a5.landscape" ||
      "b4.landscape" ||
      "letter.landscape" ||
      "legal.landscape")
  ){
    links_mono_or_color_set = links.color.strip;
  }
#+END_SRC

***** format latex head, open

#+NAME: output_latex_head_0_format_string
#+BEGIN_SRC d
  string _latex_head = format(q"┃%%%% spine LaTeX output
#+END_SRC

***** description comment

#+NAME: output_latex_head_0_tex
#+BEGIN_SRC latex
%%%% Generated by: %s
%%%% D version: %s
%%%% LaTeX output last Generated on: %s
%%%% %s %s

\usepackage{geometry}
#+END_SRC

***** paper type (a4, letter, ...; ( portrait | landscape ))

- paper_type_latex.a4_portrait
- paper_type_latex.a4_landscape
- paper_type_latex.us_letter_portrait
- paper_type_latex.us_letter_landscape

#+NAME: output_latex_head_0_tex
#+BEGIN_SRC latex
%s
#+END_SRC

***** paper margins (portrait | landscape)

- margins.portrait
- margins.landscape

#+NAME: output_latex_head_0_tex
#+BEGIN_SRC latex
%s
#+END_SRC

***** margin shared

#+NAME: output_latex_head_0_tex
#+BEGIN_SRC latex
\setlength{\marginparsep}{4mm}
\setlength{\marginparwidth}{8mm}
#+END_SRC

***** multicol (portrait | landscape)

#+NAME: output_latex_head_0_tex
#+BEGIN_SRC latex
%s
#+END_SRC

***** language & font

#+NAME: output_latex_head_0_tex
#+BEGIN_SRC latex
\usepackage{polyglossia, ucs, fontspec, xltxtra, xunicode}
\setmainlanguage{%s}
\setotherlanguage{%s}
\setmainfont{Liberation Sans}
\setmonofont[Scale=0.85]{Liberation Mono}
#+END_SRC

%% \setsansfont{Liberation Sans}
%% \setromanfont{Liberation Serif}

***** latex head

#+NAME: output_latex_head_0_tex
#+BEGIN_SRC latex
\usepackage{alltt}
\usepackage{thumbpdf}
#+END_SRC

***** color links: no = mono | yes = color

#+NAME: output_latex_head_0_tex
#+BEGIN_SRC latex
%s
#+END_SRC

***** metadata

#+NAME: output_latex_head_0_tex
#+BEGIN_SRC latex
  pdftitle={%s},
  pdfauthor={%s},
  pdfsubject={%s},
  pdfkeywords={},
  pageanchor=true,
  plainpages=true,
  pdfpagelabels=true,
  pagebackref,
  bookmarks=true,
  bookmarksopen=true,
  pdfmenubar=true,
  pdfpagemode=UseOutline,
  pdffitwindow=true,
  pdfwindowui=true,
  plainpages=false,
  pdfstartview=FitH
]
{hyperref}
#+END_SRC

%%  pdfusetitle=true,
%%  pdfpagelayout=SinglePage,
%%  pdfpagelayout=TwoColumnRight,
%%  pdfpagelayout=TwoColumnLeft,
%%  pdfstartpage=3,

%%%% trace lost characters
%% \tracinglostchars = 1
%% \tracingonline = 1

***** define colors

#+NAME: output_latex_head_0_tex
#+BEGIN_SRC latex
\usepackage[usenames]{color}
\definecolor{myblack}{rgb}{0,0,0}
\definecolor{myred}{rgb}{0.75,0,0}
\definecolor{mygreen}{rgb}{0,0.5,0}
\definecolor{myblue}{rgb}{0,0,0.5}
\definecolor{mywhite}{rgb}{1,1,1}
#+END_SRC

***** latex head

#+NAME: output_latex_head_0_tex
#+BEGIN_SRC latex
\usepackage{url}
\urlstyle{sf}
#+END_SRC

%%\usepackage{breakurl}

***** latex head

#+NAME: output_latex_head_0_tex
#+BEGIN_SRC latex
\usepackage{textcomp}
\usepackage[parfill]{parskip}
\usepackage[normalem]{ulem}
\usepackage{soul}
\usepackage{longtable}
\usepackage[tc]{titlepic}
\usepackage{graphicx}
\makeatletter
\parindent0pt
\usepackage{amssymb}
\usepackage{listings}
\usepackage{color}
\usepackage{textcomp}
\setcounter{secnumdepth}{2}
\setcounter{tocdepth}{4}
\makeatletter
#+END_SRC

***** indent, bullet, list

#+NAME: output_latex_head_0_tex
#+BEGIN_SRC latex
\usepackage[multiple,ragged]{footmisc}
\setlength\footnotemargin{12pt}
\usepackage[para]{manyfoot}
\DeclareNewFootnote{A}
\newenvironment{ParagraphIndent}[1]%%
{
\begin{list}{}{%%
\setlength\topsep{0pt}%%
\addtolength{\leftmargin}{#1}
\setlength\parsep{0pt plus 1pt}%%
}
\item[]
}
{\end{list}}
\newenvironment{ParagraphHang}[2]%%
{
\begin{list}{}{%%
\setlength\topsep{0pt}%%
\addtolength{\leftmargin}{#1}
\itemindent=#2
\setlength\parsep{0pt plus 1pt}%%
}
\item[]
}
{\end{list}}
\newenvironment{Bullet}[1]%%
{
\begin{list}{}{%%
\setlength\topsep{0pt}%%
\addtolength{\leftmargin}{#1}
\itemindent=-1em
\setlength\parsep{0pt plus 1pt}%%
}
\item[]
}
{\end{list}}
#+END_SRC

%%\DeclareNewFootnote[para]{A}

***** part, section, subsection, paragraph, subparagraph

#+NAME: output_latex_head_0_tex
#+BEGIN_SRC latex
\usepackage{fancyhdr}
\lhead{}
\renewcommand{\part}{\@startsection
  {part}{1}{-2mm}%%
  {-\baselineskip}{0.5\baselineskip}%%
  {\bfseries\large\upshape\raggedright}}
\renewcommand{\section}{\@startsection
  {section}{2}{-2mm}%%
  {-\baselineskip}{0.5\baselineskip}%%
  {\bfseries\large\upshape\raggedright}}
\renewcommand{\subsection}{\@startsection
  {subsection}{3}{-2mm}%%
  {-\baselineskip}{0.5\baselineskip}%%
  {\bfseries\large\upshape\raggedright}}
\renewcommand{\subsubsection}{\@startsection
  {subsubsection}{4}{-2mm}%%
  {-\baselineskip}{0.5\baselineskip}%%
  {\normalfont\normalsize\bfseries\raggedright}}
\renewcommand{\paragraph}{\@startsection
  {paragraph}{5}{-2mm}%%
  {-\baselineskip}{0.5\baselineskip}%%
  {\normalfont\normalsize\itshape\raggedright}}
\renewcommand{\subparagraph}{\@startsection
  {subparagraph}%%{6}%%{-2mm}%%
  {-\baselineskip}{0.5\baselineskip}%%
  {\normalfont\normalsize\itshape\raggedright}}
#+END_SRC

%% \makeatother

***** latex head misc. including defined commands

#+NAME: output_latex_head_0_tex
#+BEGIN_SRC latex
\selectlanguage{%s}
\lhead[ ]{ }
\chead[ \fancyplain{} \bfseries \footnotesize \leftmark ]{ \fancyplain{} \bfseries \footnotesize \rightmark }
\rhead[ ]{ }
%s
\rfoot[\tiny \href{}{}]{\textrm{\thepage}}
\tolerance=300
\clubpenalty=300
\widowpenalty=300
\makeatother
\makeatother
\chardef\txtbullet="2022
\chardef\tilde="7E
\def\asterisk{{\rm \char42} }
\definecolor{Light}{gray}{.92}
\newcommand{\Codeblock}[1]{\normaltext\raggedright\small\ttfamily\texbackslash#1}
\newcommand{\monosp}[1]{\normaltext\ttfamily\texbackslash#1}
\newcommand{\parasep}{\\ \begin{center}*\hspace{2em}*\hspace{2em}*\end{center} \\}
\newcommand{\hardspace}{{~}}
\newcommand{\caret}{{\^{~}}}
\newcommand{\pipe}{{\textbar}}
\newcommand{\curlyopen}{{}
\newcommand{\curlyclose}{}}
\newcommand{\lt}{{UseTextSymbol{OML}{<}}}
\newcommand{\gt}{{UseTextSymbol{OML}{>}}}
\newcommand{\slash}{{/}}
\newcommand{\underscore}{\_}
\newcommand{\exclaim}{\Verbatim{!}}
\definecolor{listinggray}{gray}{0.9}
\definecolor{lbcolor}{rgb}{0.9,0.9,0.9}
\lstset{
  backgroundcolor=\color{lbcolor},
  tabsize=4,
  rulecolor=,
  language=,
  basicstyle=\scriptsize,
  upquote=true,
  aboveskip={1.5\baselineskip},
  columns=fixed,
  showstringspaces=false,
  extendedchars=true,
  breaklines=true,
  prebreak = \raisebox{0ex}[0ex][0ex]{\ensuremath{\hookleftarrow}},
  frame=single,
  showtabs=false,
  showspaces=false,
  showstringspaces=false,
  identifierstyle=\ttfamily,
  keywordstyle=\color[rgb]{0,0,1},
  commentstyle=\color[rgb]{0.133,0.545,0.133},
  stringstyle=\color[rgb]{0.627,0.126,0.941},
}
#+END_SRC

%%\chardef\asterisk="2A
%%\newcommand{\hardspace}{\hspace{.5em}}

**** latex head format inclusions

#+NAME: output_latex_head_0_format_string_variables
#+BEGIN_SRC d
┃",
  doc_matters.opt.action.debug_do ? "" : doc_matters.generator_program.name_and_version.strip,
  doc_matters.opt.action.debug_do ? "" : doc_matters.generator_program.compiler.strip,
  doc_matters.opt.action.debug_do ? "" : doc_matters.generator_program.stime.strip,
  doc_matters.generator_program.project_name.strip,
  doc_matters.generator_program.url_home.strip,
  paper_size_orientation_latex.strip,
  margins.portrait.strip,
  multicol.portrait.strip,
  lang.codes[doc_matters.src.language]["xlp"],
  "english",
  links_mono_or_color_set,
  doc_matters.conf_make_meta.meta.title_full.strip,
  doc_matters.conf_make_meta.meta.creator_author.strip,
  doc_matters.conf_make_meta.meta.classify_subject.strip,
  lang.codes[doc_matters.src.language]["xlp"],
  _footer(doc_matters),
);
#+END_SRC

**** latex head return

#+NAME: output_latex_head_close
#+BEGIN_SRC d
  return _latex_head.strip;
}
#+END_SRC

*** ↻ latex body                                               :content:body:
**** latex body function

#+NAME: output_latex_body
#+BEGIN_SRC d
string latex_body(D,M)(
  const  D      doc_abstraction,
         M      doc_matters,
         string paper_size_orientation,
) {
  string _latex_body = "";
  bool _multicolumns = false;
  string _txt;
#+END_SRC

**** ↻ loop open

#+NAME: output_latex_body
#+BEGIN_SRC d
  foreach (part; doc_matters.has.keys_seq.latex) {
    foreach (obj; doc_abstraction[part]) {
      switch (obj.metainfo.is_of_part) {
#+END_SRC

**** ↻ within loop
***** frontmatter

#+NAME: output_latex_body
#+BEGIN_SRC d
      case "frontmatter":              assert(part == "head" || "toc");
        _txt = obj.text
          .sp_char_esc(obj)
          .fontface;
        switch (obj.metainfo.is_of_type) {
        case "para":
          switch (obj.metainfo.is_a) {
          case "heading":
            _txt = _txt.heading(obj, doc_matters);
            goto default;
          case "toc":
            break;
          default:
            _latex_body ~= _txt ~ "\n\n";
            _txt = "";
            break;
          }
          break;
        default: break;
        }
        break;
#+END_SRC

***** body

#+NAME: output_latex_body
#+BEGIN_SRC d
      case "body":                     assert(part == "body" || "head"); // surprise
        _txt = obj.text
          .sp_char_esc(obj)
          .fontface;
        switch (obj.metainfo.is_of_type) {
        case "para":
          switch (obj.metainfo.is_a) {
          case "heading":
            _txt = _txt.heading(obj, doc_matters);
            goto default;
          case "para":
            _txt = _txt.para(obj)
              .bullets_and_indentation(obj)
              .links_and_images(obj, doc_matters);
            goto default;
          default:
            _latex_body ~= _txt ~ "\n\n";
            _txt = "";
            break;
          }
          break;
        case "block":
          switch (obj.metainfo.is_a) {
          case "quote":
            goto default; // TODO
          case "group":
            /+ (hardspaces not honored) [remove any hardspace marker] +/
            _txt = _txt.group(obj, doc_matters)
              .links_and_images(obj, doc_matters);
            goto default;
          case "block":
            /+ (hardspace honored) \hardspace +/
            _txt = _txt.block(obj, doc_matters)
              .links_and_images(obj, doc_matters);
            goto default;
          case "verse":
            /+ (hardspace honored) \hardspace +/
            _txt = _txt.verse(obj, doc_matters)
              .links_and_images(obj, doc_matters);
            goto default;
          case "code":
            /+ (hardspace honored) \begin{lstlisting} clear hardspace marker +/
            _txt = _txt.codeblock(obj, doc_matters);
            goto default;
          case "table":
            _txt = _txt.table(obj, doc_matters, paper_size_orientation);
            goto default; // TODO
          default:
            _latex_body ~= _txt ~ "\n\n";
            _txt = "";
            break;
          }
          break;
        default: break;
        }
        break;
#+END_SRC

***** backmatter

#+NAME: output_latex_body
#+BEGIN_SRC d
      case "backmatter":
        assert(part == "endnotes" || "glossary" || "bibliography" || "bookindex" || "blurb" || "tail");
        _txt = obj.text
          .sp_char_esc(obj)
          .fontface;
        switch (obj.metainfo.is_of_type) {
        case "para":
          if (part != "bookindex" && _multicolumns) {
            _multicolumns = false;
            _latex_body ~= "\n\\end{multicols}\n";
          }
          switch (obj.metainfo.is_a) {
          case "heading":
            if (part == "bookindex") {
              _multicolumns = true;
            }
            _txt = _txt.heading(obj, doc_matters, part);
            goto default;
          case "endnote":              assert(part == "endnotes");
            /* uncomment code to reinstate endnotes in endnote section */
            // _txt = _txt.para(obj)
            //   .bullets_and_indentation(obj)
            //   .links_and_images(obj, doc_matters);
            // goto default;
            break;
          case "glossary":             assert(part == "glossary");
            _txt = _txt.para(obj)
              .bullets_and_indentation(obj)
              .links_and_images(obj, doc_matters);
            goto default;
          case "bibliography":         assert(part == "bibliography");
            _txt = _txt.para(obj)
              .bullets_and_indentation(obj);
            goto default;
          case "bookindex":            assert(part == "bookindex");
            /+ two column, special section +/
            _txt = _txt.bookindex(obj)
              .links_and_images(obj, doc_matters);
            goto default;
          case "blurb":                assert(part == "blurb");
            _txt = _txt.para(obj)
              .bullets_and_indentation(obj)
              .links_and_images(obj, doc_matters);
            goto default;
          default:
            _latex_body ~= (part == "bookindex" && obj.metainfo.is_a != "heading")
              ? _txt : (_txt ~ "\n\n");
            _txt = "";
            break;
          }
          break;
        default: break;
        }
        break;
#+END_SRC

***** after

#+NAME: output_latex_body
#+BEGIN_SRC d
      case "comment":
        break;
      default:
        { /+ debug +/
          if (doc_matters.opt.action.debug_do
          && doc_matters.opt.action.verbose) {
            writeln(__FILE__, ":", __LINE__, ": ", obj.metainfo.is_of_part);
            writeln(__FILE__, ":", __LINE__, ": ", obj.metainfo.is_a);
            writeln(__FILE__, ":", __LINE__, ": ", obj.text);
          }
        }
        break;
      }
    }
  }
  if (_multicolumns) {
    _multicolumns = false;
    _latex_body ~= "\n\\end{multicols}\n";
  }
#+END_SRC

**** latex body return

#+NAME: output_latex_body
#+BEGIN_SRC d
  return _latex_body;
}
#+END_SRC

*** latex tail                                                         :tail:
**** latex tail function

#+NAME: output_latex_tail
#+BEGIN_SRC d
string latex_tail(M)(
  M      doc_matters,
  string paper_size_orientation,
) {
#+END_SRC

**** latex tail starts

#+NAME: output_latex_tail
#+BEGIN_SRC d
  string _latex_tail = format(q"┃
#+END_SRC

***** latex tail format inclusions


***** latex document end

#+NAME: output_latex_tail_tex
#+BEGIN_SRC latex

\end{document}
#+END_SRC

**** latex tail format inclusions

#+NAME: output_latex_tail_close
#+BEGIN_SRC d
┃",
  // doc_matters.conf_make_meta.meta.title_full,
  // doc_matters.conf_make_meta.meta.creator_author,
);
#+END_SRC

**** latex tail return

#+NAME: output_latex_tail_close
#+BEGIN_SRC d
  return _latex_tail;
}
#+END_SRC

* latex system command helper script
** latex command, ruby script

#+BEGIN_SRC ruby :tangle "../misc/util/rb/tex/dr_tex.rb"  :tangle-mode (identity #o755) :shebang #!/usr/bin/env ruby
require 'fileutils'
pwd = Dir.pwd
argv,texfiles_with_path,flags=[],[],[]
lngs = %{(am|bg|bn|br|ca|cs|cy|da|de|el|en|eo|es|et|eu|fi|fr|ga|gl|he|hi|hr|hy|ia|is|it|ja|ko|la|lo|lt|lv|ml|mr|nl|no|nn|oc|pl|pt|pt_BR|ro|ru|sa|se|sk|sl|sq|sr|sv|ta|te|th|tk|tr|uk|ur|vi|zh)}
Regexp.new(lngs, Regexp::IGNORECASE)
argv=$*
argv.sort.each{|y| (y =~/^--\S+$/i) ? (flags << y) : (texfiles_with_path << y) }
if flags.length==0 \
|| flags.inspect =~/"--help"/
  cmd=(/([^\/]+)$/).match($0)[1]
  puts <<WOK
#{cmd} --help
#{cmd} --out=[output path]
#{cmd} --paper-size=a5 --out=~/test
WOK
end
// paper_size_orientation = (flags.inspect.match(/"--paper-size=(a4|a5|b5|letter|legal)"/)) ? $1 : "a4"
out_path = Dir.pwd
if (flags.inspect.match(/"--out=\S+"/))
  out_path = flags.inspect.match(/"--out=(\S+)"/)[1]
  unless (FileTest.directory?(out_path))
    puts "Creating output directory: --out=#{out_path}"
    FileUtils::mkdir_p(out_path)
    unless (FileTest.directory?(out_path))
      puts "FAILS unable to create directory: #{out_path}"
      exit
    end
  end
end
if texfiles_with_path.length == 0
  texfiles_with_path=Dir.glob('*.tex')
end
if texfiles_with_path.length > 0
  texfiles_with_path.each do |texfile_with_path|
    if texfile_with_path =~/.+\.tex/
      #puts texfile_with_path
      if FileTest.file?(texfile_with_path)
        file_basename_with_path = texfile_with_path.sub(/\.tex$/,'')
        file_basename = file_basename_with_path.sub(/.*?([^\/]+)$/,'\1')
        _out_path = out_path
        if file_basename =~ /\.#{lngs}$/
          lng = file_basename.match(/\.#{lngs}$/)[1]
          puts file_basename
          puts lng
          puts _out_path
          unless _out_path.match(/\/#{lng}\/pdf$/)
            _out_path = "#{out_path}/#{lng}/pdf"
            FileUtils::mkdir_p(_out_path)
          end
        end
        texpdf_cmd = %{xetex -interaction=batchmode -fmt=xelatex #{texfile_with_path}\n}
        puts texpdf_cmd
        2.times { |i| system(texpdf_cmd) }
        if (FileTest.file?(%{#{pwd}/#{file_basename}.pdf})) && (FileTest.directory?(_out_path))
          FileUtils::Verbose::mv(%{#{pwd}/#{file_basename}.pdf}, %{#{_out_path}/#{file_basename}.pdf})
          puts (%{#{_out_path}/#{file_basename}.pdf})
        else
          puts "issue with pdf file or output directory"
          puts "pdf file:   #{pwd}/#{file_basename}.pdf}"
          puts "output dir: #{_out_path}/"
        end
        suffix = ['log', 'out', 'toc', 'aux']
        suffix.each { |s| FileUtils::rm_f(%{#{pwd}/#{file_basename}.#{s}})}
      end
    end
  end
end
Dir.chdir(pwd)
__END__
#+END_SRC