summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAndrey Makarov <ph.makarov@gmail.com>2021-06-01 21:47:23 +0300
committerGitHub <noreply@github.com>2021-06-01 20:47:23 +0200
commitba3ec7b04954a9a60adb6e1a64405f7644f799ee (patch)
treefb3a4f82dda1d12001b9a918b4546c0e386b8e78
parentc2e3dc0ed16a854981769b426e9e327663447df7 (diff)
downloadNim-ba3ec7b04954a9a60adb6e1a64405f7644f799ee.tar.gz
docs: Latex generation improvements (#18141)
* docs: improve Latex generation

* make it work on Windows + fix ] escaping

* minor fixes with escapes and style
-rw-r--r--compiler/docgen.nim2
-rw-r--r--config/nimdoc.tex.cfg202
-rw-r--r--lib/packages/docutils/rstast.nim18
-rw-r--r--lib/packages/docutils/rstgen.nim140
-rw-r--r--tests/stdlib/trstgen.nim4
-rw-r--r--tools/kochdocs.nim14
6 files changed, 252 insertions, 128 deletions
diff --git a/compiler/docgen.nim b/compiler/docgen.nim
index e07db9188..18c568ba0 100644
--- a/compiler/docgen.nim
+++ b/compiler/docgen.nim
@@ -549,7 +549,7 @@ proc getAllRunnableExamplesImpl(d: PDoc; n: PNode, dest: var string,
         var msg = "Example:"
         if rdoccmd.len > 0: msg.add " cmd: " & rdoccmd
         dispA(d.conf, dest, "\n<p><strong class=\"examples_text\">$1</strong></p>\n",
-            "\n\\textbf{$1}\n", [msg])
+            "\n\n\\textbf{$1}\n", [msg])
         inc d.listingCounter
         let id = $d.listingCounter
         dest.add(d.config.getOrDefault"doc.listing_start" % [id, "langNim", ""])
diff --git a/config/nimdoc.tex.cfg b/config/nimdoc.tex.cfg
index b19b25678..75382ce26 100644
--- a/config/nimdoc.tex.cfg
+++ b/config/nimdoc.tex.cfg
@@ -8,7 +8,8 @@ split.item.toc = "20"
 # after this number of characters
 
 doc.section = """
-\rsthA{$sectionTitle}\label{$sectionID}
+\rsthA[$sectionTitle]{$sectionTitle}\label{$sectionID}
+
 $content
 """
 
@@ -17,12 +18,16 @@ doc.section.toc = ""
 
 doc.item = """
 
+\vspace{1em}
 \phantomsection\addcontentsline{toc}{subsection}{$uniqueName}
 
-\begin{rstpre}
+\begin{rstdocitem}
 $header
-\end{rstpre}
+\end{rstdocitem}
+
+\begin{addmargin}[0.05\linewidth]{0pt}
 $desc
+\end{addmargin}
 """
 
 doc.item.toc = ""
@@ -42,46 +47,118 @@ $content
 
 # $1 - number of listing in document, $2 - language (e.g. langNim), $3 - anchor
 doc.listing_start = "\\begin{rstpre}\n"
-doc.listing_end = "\n\\end{rstpre}\n"
+doc.listing_end = "\n\\end{rstpre}\n\n"
 
 doc.file = """
 % This file was generated by Nim.
 % Generated: $date $time UTC
-\documentclass[a4paper]{article}
-\usepackage[left=2cm,right=3cm,top=3cm,bottom=3cm]{geometry}
-\usepackage[utf8]{inputenc}
-\usepackage[T1]{fontenc}
+%
+% Compile it by:   xelatex    (up to 3 times to get labels generated)
+%                  -------
+%
+\documentclass[a4paper,11pt]{article}
+\usepackage[a4paper,xetex,left=3cm,right=3cm,top=1.5cm,bottom=2cm]{geometry}
+
+%   for 2-sided printing with larger inner "binding" margin
+%\usepackage[a4paper,xetex,twoside,left=4cm,right=2cm,top=1.5cm,bottom=2cm]{geometry}
+%   for e-readers with 1.77:1 aspect ratio (e.g. 1920x1080)
+%\usepackage[xetex,paperheight=27.6cm,paperwidth=15.5cm,left=3mm,right=3mm,top=3mm,bottom=3mm]{geometry}
+%   for e-readers with 1.45:1 aspect ratio (e.g. 1200x825)
+%\usepackage[xetex,paperheight=22.5cm,paperwidth=15.5cm,left=3mm,right=3mm,top=3mm,bottom=3mm]{geometry}
+%   for e-readers with 1.33:1 aspect ratio (e.g. 1872x1404)
+%\usepackage[xetex,paperheight=20.7cm,paperwidth=15.5cm,left=3mm,right=3mm,top=3mm,bottom=3mm]{geometry}
+
+\usepackage{fontspec}
+% logic to select default font with some fall-back fonts.
+\IfFontExistsTF{Times New Roman}{%
+  \setmainfont{Times New Roman}  % the default font
+  \typeout{========================================= nim: using Times New Roman}
+}{
+  \IfFontExistsTF{FreeSerif}{%
+    \setmainfont{FreeSerif}  % fallback #1 - official GNU font, resembles Times
+    \typeout{========================================= nim: using FreeSerif}
+  }{
+    \IfFontExistsTF{DejaVuSerif}{%
+      \setmainfont{DejaVuSerif}  % fallback #2 - very widespread free font
+      \typeout{========================================= nim: using DejaVuSerif}
+    }{
+      \typeout{!!!!!!!!!!!!!!!!!!! Fonts not found !!!!!!!!!!!!!!!!!!!!!!!}
+    }
+  }
+}
+
+% default monospace font for code:
+\usepackage{GoMono}
+\usepackage{relsize}
+% make this monospace font 2 steps smaller to hold 80-character line
+\newcommand{\rstverbblockfont}{\smaller[2]}
+\newcommand{\rstverbinlinefont}{\smaller}
+
+\usepackage{parskip}  % paragraphs delimited by vertical space, no indent
 \usepackage{graphicx}
-\usepackage{lmodern}
-\usepackage{fancyvrb, courier}
-\usepackage{tabularx}
-\usepackage{hyperref}
-\usepackage{enumitem}  % for option list, enumList, and rstfootnote
+
+\usepackage{dingbat} % for \carriagereturn, etc
+\usepackage{fvextra}  % for code blocks (works better than original fancyvrb)
+\fvset{
+  breaklines,
+  breakafter={=}:|\_\{\}[](){,}.;+-*/'",
+  breaksymbolleft=\color{red}{\ensuremath{\hookrightarrow}}, 
+  breaksymbolright=\color{red}{\small\carriagereturn}
+}
+\fvinlineset{%
+   breaklines,
+   breakafter={=}:|\_\{\}[](){,}.;+-*/'",
+     % that does not work at all when we underline inline code by ulem :-(
+   commandchars=\\\{\}
+}
+
+\usepackage{scrextend}  % for the `addmargin` environment
 
 \usepackage{xcolor}
-\usepackage[tikz]{mdframed}
-\usetikzlibrary{shadows}
-\mdfsetup{%
-linewidth=3,
-topline=false,
-rightline=false,
-bottomline=false}
+\usepackage[urlbordercolor=blue,linkbordercolor=cyan,
+            pdfborderstyle={/S/U/W 1}]{hyperref}
+\usepackage{enumitem}  % for option list, enumList, and rstfootnote
 
-\begin{document}
-\title{$title $version $subtitle}
-\author{$author}
+\usepackage[most]{tcolorbox}  % boxes around admonitions, code blocks, doc.item
 
-\tolerance 1414 
-\hbadness 1414 
-\emergencystretch 1.5em 
-\hfuzz 0.3pt 
-\widowpenalty=10000 
-\vfuzz \hfuzz 
-\raggedbottom 
+\newtcolorbox{rstadmonition}[1][]{blanker, breakable,
+     left=3mm, right=3mm, top=1mm, bottom=1mm,
+     before upper=\indent, parbox=false, #1}
+
+\definecolor{rstframecolor}{rgb}{0.85, 0.8, 0.6}
+
+\newtcolorbox{rstprebox}[1][]{blanker, breakable,
+     left=3mm, right=3mm, top=1mm, bottom=1mm,
+     borderline ={0.1em}{0pt}{rstframecolor},
+     before upper=\indent, parbox=false, #1}
+
+\newenvironment{rstpre}{%
+\VerbatimEnvironment\begingroup\begin{rstprebox}%
+\begin{Verbatim}[fontsize=\rstverbblockfont , commandchars=\\\{\}]}%
+{\end{Verbatim}\end{rstprebox}\endgroup}
+
+\newtcolorbox{rstdocitembox}[1][]{blanker, breakable,
+     left=3mm, right=3mm, top=1mm, bottom=1mm,
+     borderline ={1pt}{0pt}{cyan},
+     before upper=\indent, parbox=false, #1}
+
+% Inline code formatting: grey underline,
+% use \Verb from fvextras e.g. to display -- correctly as double -
+\usepackage[normalem]{ulem}
+\newcommand\rstuline{\bgroup\markoverwith{\textcolor{rstframecolor}{\rule[-0.8ex]{2pt}{1.0pt}}}\ULon}
+
+\newcommand{\rstcode}[1]{%
+{\rstverbinlinefont\Verb{\rstuline{#1}}}%
+}
+
+\newcommand{\rstcodeitem}[1]{\Verb{#1}}
+
+\newenvironment{rstdocitem}{%
+\VerbatimEnvironment\begingroup\begin{rstdocitembox}%
+\begin{Verbatim}[fontsize=\rstverbblockfont , commandchars=\\\{\}]}%
+{\end{Verbatim}\end{rstdocitembox}\endgroup}
 
-\maketitle
 
-\newenvironment{rstpre}{\VerbatimEnvironment\begingroup\begin{Verbatim}[fontsize=\footnotesize , commandchars=\\\{\}]}{\end{Verbatim}\endgroup}
 \newenvironment{rstfootnote}{\begin{description}[labelindent=1em,leftmargin=1em,labelwidth=2.6em]}{\end{description}}
 \ifdim\linewidth<30em
   \def\rstoptleftmargin{0.4\linewidth}
@@ -93,37 +170,41 @@ bottomline=false}
 \newenvironment{rstoptlist}{%
 \begin{description}[font=\sffamily\bfseries,style=nextline,leftmargin=\rstoptleftmargin,labelwidth=\rstoptlabelwidth]}{\end{description}}
 
-% to pack tabularx into a new environment, special syntax is needed :-(
-\newenvironment{rsttab}[1]{\tabularx{\linewidth}{#1}}{\endtabularx}
+\usepackage{tabulary}  % tables with adjustable cell width and no overflow
+% make tabulary prevent overflows (https://tex.stackexchange.com/a/195088)
+\tymin=60pt
+\tymax=\maxdimen
+% to pack tabulary into a new environment, special syntax is needed :-(
+\newenvironment{rsttab}[1]{\tabulary{\linewidth}{#1}}{\endtabulary}
 
 \newcommand{\rstsub}[1]{\raisebox{-0.5ex}{\scriptsize{#1}}}
 \newcommand{\rstsup}[1]{\raisebox{0.5ex}{\scriptsize{#1}}}
 
-\newcommand{\rsthA}[1]{\section{#1}}
-\newcommand{\rsthB}[1]{\subsection{#1}}
-\newcommand{\rsthC}[1]{\subsubsection{#1}}
-\newcommand{\rsthD}[1]{\paragraph{#1}}
-\newcommand{\rsthE}[1]{\paragraph{#1}}
+\newcommand{\rsthA}[2][]{\section[#1]{#2}}
+\newcommand{\rsthB}[2][]{\subsection[#1]{#2}}
+\newcommand{\rsthC}[2][]{\subsubsection[#1]{#2}}
+\newcommand{\rsthD}[2][]{\paragraph[#1]{#2}}
+\newcommand{\rsthE}[2][]{\paragraph[#1]{#2}}
 
-\newcommand{\rstovA}[1]{\section*{#1}}
-\newcommand{\rstovB}[1]{\subsection*{#1}}
-\newcommand{\rstovC}[1]{\subsubsection*{#1}}
-\newcommand{\rstovD}[1]{\paragraph*{#1}}
-\newcommand{\rstovE}[1]{\paragraph*{#1}}
+\newcommand{\rstovA}[2][]{\section*[#1]{#2}}
+\newcommand{\rstovB}[2][]{\subsection*[#1]{#2}}
+\newcommand{\rstovC}[2][]{\subsubsection*[#1]{#2}}
+\newcommand{\rstovD}[2][]{\paragraph*[#1]{#2}}
+\newcommand{\rstovE}[2][]{\paragraph*[#1]{#2}}
 
 % Syntax highlighting:
-\newcommand{\spanDecNumber}[1]{#1}
-\newcommand{\spanBinNumber}[1]{#1}
-\newcommand{\spanHexNumber}[1]{#1}
-\newcommand{\spanOctNumber}[1]{#1}
-\newcommand{\spanFloatNumber}[1]{#1}
+\newcommand{\spanDecNumber}[1]{\textbf{\textcolor{darkgray}{#1}}}
+\newcommand{\spanBinNumber}[1]{\textbf{\textcolor{darkgray}{#1}}}
+\newcommand{\spanHexNumber}[1]{\textbf{\textcolor{darkgray}{#1}}}
+\newcommand{\spanOctNumber}[1]{\textbf{\textcolor{darkgray}{#1}}}
+\newcommand{\spanFloatNumber}[1]{\textbf{\textcolor{darkgray}{#1}}}
 \newcommand{\spanIdentifier}[1]{#1}
 \newcommand{\spanKeyword}[1]{\textbf{#1}}
-\newcommand{\spanStringLit}[1]{#1}
-\newcommand{\spanLongStringLit}[1]{#1}
+\newcommand{\spanStringLit}[1]{\textbf{\textcolor{darkgray}{#1}}}
+\newcommand{\spanLongStringLit}[1]{\textbf{\textcolor{darkgray}{#1}}}
 \newcommand{\spanCharLit}[1]{#1}
 \newcommand{\spanEscapeSequence}[1]{#1}
-\newcommand{\spanOperator}[1]{#1}
+\newcommand{\spanOperator}[1]{\textbf{#1}}
 \newcommand{\spanPunctuation}[1]{#1}
 \newcommand{\spanComment}[1]{\emph{#1}}
 \newcommand{\spanLongComment}[1]{\emph{#1}}
@@ -132,7 +213,7 @@ bottomline=false}
 \newcommand{\spanTagEnd}[1]{#1}
 \newcommand{\spanKey}[1]{#1}
 \newcommand{\spanValue}[1]{#1}
-\newcommand{\spanRawData}[1]{#1}
+\newcommand{\spanRawData}[1]{\textbf{\textcolor{darkgray}{#1}}}
 \newcommand{\spanAssembler}[1]{#1}
 \newcommand{\spanPreprocessor}[1]{#1}
 \newcommand{\spanDirective}[1]{#1}
@@ -142,11 +223,20 @@ bottomline=false}
 \newcommand{\spanLabel}[1]{#1}
 \newcommand{\spanReference}[1]{#1}
 \newcommand{\spanOther}[1]{#1}
-\newcommand{\spantok}[1]{\frame{#1}}
+\newcommand{\spantok}[1]{\fbox{#1}}
 \newcommand{\spanPrompt}[1]{\textcolor{red}{\textbf{#1}}}
-\newcommand{\spanProgramOutput}[1]{\textcolor{gray}{\textbf{#1}}}
+\newcommand{\spanProgramOutput}[1]{\textcolor{darkgray}{\textbf{#1}}}
 \newcommand{\spanprogram}[1]{\textbf{\underline{#1}}}
-\newcommand{\spanoption}[1]{\textbf{#1}}
+\newcommand{\spanoption}[1]{\textbf{\textcolor{darkgray}{#1}}}
+
+\begin{document}
+\title{$title $version $subtitle}
+\author{$author}
+
+% Never allow text overflow to margin:
+\setlength\emergencystretch{\hsize}\hbadness=10000
+
+\maketitle
 
 $content
 \end{document}
diff --git a/lib/packages/docutils/rstast.nim b/lib/packages/docutils/rstast.nim
index 81e3ba6d9..2489ce40c 100644
--- a/lib/packages/docutils/rstast.nim
+++ b/lib/packages/docutils/rstast.nim
@@ -349,8 +349,24 @@ proc renderRstToJson*(node: PRstNode): string =
   ##   }
   renderRstToJsonNode(node).pretty
 
+proc renderRstToText*(node: PRstNode): string =
+  ## minimal text representation of markup node
+  const code = {rnCodeFragment, rnInterpretedText, rnInlineLiteral, rnInlineCode}
+  if node == nil:
+    return ""
+  case node.kind
+  of rnLeaf, rnSmiley:
+    result.add node.text
+  else:
+    if node.kind in code: result.add "`"
+    for i in 0 ..< node.sons.len:
+      if node.kind in {rnInlineCode, rnCodeBlock} and i == 0:
+        continue  # omit language specifier
+      result.add renderRstToText(node.sons[i])
+    if node.kind in code: result.add "`"
+
 proc renderRstToStr*(node: PRstNode, indent=0): string =
-  ## Writes the parsed RST `node` into a compact string
+  ## Writes the parsed RST `node` into an AST tree with compact string
   ## representation in the format (one line per every sub-node):
   ## ``indent - kind - [text|level|order|adType] - anchor (if non-zero)``
   ## (suitable for debugging of RST parsing).
diff --git a/lib/packages/docutils/rstgen.nim b/lib/packages/docutils/rstgen.nim
index 5ab6f28ee..e3f889283 100644
--- a/lib/packages/docutils/rstgen.nim
+++ b/lib/packages/docutils/rstgen.nim
@@ -60,6 +60,10 @@ type
   MetaEnum* = enum
     metaNone, metaTitle, metaSubtitle, metaAuthor, metaVersion
 
+  EscapeMode = enum  # in Latex text inside options [] and URLs is
+                     # escaped slightly differently than in normal text
+    emText, emOption, emUrl  # emText is currently used for code also
+
   RstGenerator* = object of RootObj
     target*: OutputTarget
     config*: StringTableRef
@@ -84,6 +88,7 @@ type
     id*: int               ## A counter useful for generating IDs.
     onTestSnippet*: proc (d: var RstGenerator; filename, cmd: string; status: int;
                           content: string)
+    escMode*: EscapeMode
 
   PDoc = var RstGenerator ## Alias to type less.
 
@@ -161,6 +166,7 @@ proc initRstGenerator*(g: var RstGenerator, target: OutputTarget,
   g.findFile = findFile
   g.currentSection = ""
   g.id = 0
+  g.escMode = emText
   let fileParts = filename.splitFile
   if fileParts.ext == ".nim":
     g.currentSection = "Module " & fileParts.name
@@ -189,28 +195,34 @@ proc addHtmlChar(dest: var string, c: char) =
   of '\"': add(dest, "&quot;")
   else: add(dest, c)
 
-proc addRtfChar(dest: var string, c: char) =
-  case c
-  of '{': add(dest, "\\{")
-  of '}': add(dest, "\\}")
-  of '\\': add(dest, "\\\\")
-  else: add(dest, c)
-
-proc addTexChar(dest: var string, c: char) =
-  # Escapes 10 special Latex characters. Note that [, ], and ` are not
-  # considered as such. TODO: neither is @, am I wrong?
+proc addTexChar(dest: var string, c: char, escMode: EscapeMode) =
+  ## Escapes 10 special Latex characters and sometimes ` and [, ].
+  ## TODO: @ is always a normal symbol (besides the header), am I wrong?
+  ## All escapes that need to work in text and code blocks (`emText` mode)
+  ## should start from \ (to be compatible with fancyvrb/fvextra).
   case c
-  of '_', '{', '}', '$', '&', '#', '%': add(dest, "\\" & c)
+  of '_', '$', '&', '#', '%': add(dest, "\\" & c)
   # \~ and \^ have a special meaning unless they are followed by {}
   of '~', '^': add(dest, "\\" & c & "{}")
+  # Latex loves to substitute ` to opening quote, even in texttt mode!
+  of '`': add(dest, "\\textasciigrave{}")
   # add {} to avoid gobbling up space by \textbackslash
   of '\\': add(dest, "\\textbackslash{}")
+  # Using { and } in URL in Latex: https://tex.stackexchange.com/a/469175
+  of '{':
+    add(dest, if escMode == emUrl: "\\%7B" else: "\\{")
+  of '}':
+    add(dest, if escMode == emUrl: "\\%7D" else: "\\}")
+  of ']':
+    # escape ] inside an optional argument in e.g. \section[static[T]]{..
+    add(dest, if escMode == emOption: "\\text{]}" else: "]")
   else: add(dest, c)
 
-proc escChar*(target: OutputTarget, dest: var string, c: char) {.inline.} =
+proc escChar*(target: OutputTarget, dest: var string,
+              c: char, escMode: EscapeMode) {.inline.} =
   case target
   of outHtml:  addHtmlChar(dest, c)
-  of outLatex: addTexChar(dest, c)
+  of outLatex: addTexChar(dest, c, escMode)
 
 proc addSplitter(target: OutputTarget; dest: var string) {.inline.} =
   case target
@@ -229,7 +241,7 @@ proc nextSplitPoint*(s: string, start: int): int =
     inc(result)
   dec(result)                 # last valid index
 
-proc esc*(target: OutputTarget, s: string, splitAfter = -1): string =
+proc esc*(target: OutputTarget, s: string, splitAfter = -1, escMode = emText): string =
   ## Escapes the HTML.
   result = ""
   if splitAfter >= 0:
@@ -240,11 +252,11 @@ proc esc*(target: OutputTarget, s: string, splitAfter = -1): string =
       #if (splitter != " ") or (partLen + k - j + 1 > splitAfter):
       partLen = 0
       addSplitter(target, result)
-      for i in countup(j, k): escChar(target, result, s[i])
+      for i in countup(j, k): escChar(target, result, s[i], escMode)
       inc(partLen, k - j + 1)
       j = k + 1
   else:
-    for i in countup(0, len(s) - 1): escChar(target, result, s[i])
+    for i in countup(0, len(s) - 1): escChar(target, result, s[i], escMode)
 
 
 proc disp(target: OutputTarget, xml, tex: string): string =
@@ -760,6 +772,8 @@ proc renderHeadline(d: PDoc, n: PRstNode, result: var string) =
       sectionPrefix = rstnodeToRefname(n2) & "-"
       break
   var refname = sectionPrefix & rstnodeToRefname(n)
+  var tocName = esc(d.target, renderRstToText(n), escMode = emOption)
+    # for Latex: simple text without commands that may break TOC/hyperref
   if d.hasToc:
     var length = len(d.tocPart)
     setLen(d.tocPart, length + 1)
@@ -768,13 +782,14 @@ proc renderHeadline(d: PDoc, n: PRstNode, result: var string) =
     d.tocPart[length].header = tmp
 
     dispA(d.target, result, "\n<h$1><a class=\"toc-backref\"" &
-      "$2 href=\"#$5\">$3</a></h$1>", "\\rsth$4{$3}$2\n",
-      [$n.level, refname.idS, tmp, $chr(n.level - 1 + ord('A')), refname])
+      "$2 href=\"#$5\">$3</a></h$1>", "\\rsth$4[$6]{$3}$2\n",
+      [$n.level, refname.idS, tmp,
+       $chr(n.level - 1 + ord('A')), refname, tocName])
   else:
     dispA(d.target, result, "\n<h$1$2>$3</h$1>",
-                            "\\rsth$4{$3}$2\n", [
+                            "\\rsth$4[$5]{$3}$2\n", [
         $n.level, refname.idS, tmp,
-        $chr(n.level - 1 + ord('A'))])
+        $chr(n.level - 1 + ord('A')), tocName])
 
   # Generate index entry using spaces to indicate TOC level for the output HTML.
   assert n.level >= 0
@@ -802,9 +817,10 @@ proc renderOverline(d: PDoc, n: PRstNode, result: var string) =
     var tmp = ""
     for i in countup(0, len(n) - 1): renderRstToOut(d, n.sons[i], tmp)
     d.currentSection = tmp
+    var tocName = esc(d.target, renderRstToText(n), escMode=emOption)
     dispA(d.target, result, "<h$1$2><center>$3</center></h$1>",
-                   "\\rstov$4{$3}$2\n", [$n.level,
-        rstnodeToRefname(n).idS, tmp, $chr(n.level - 1 + ord('A'))])
+                   "\\rstov$4[$5]{$3}$2\n", [$n.level,
+        rstnodeToRefname(n).idS, tmp, $chr(n.level - 1 + ord('A')), tocName])
 
 
 proc renderTocEntry(d: PDoc, e: TocEntry, result: var string) =
@@ -870,7 +886,7 @@ proc renderImage(d: PDoc, n: PRstNode, result: var string) =
     htmlOut = "<img$3 src=\"$1\"$2/>"
 
   # support for `:target:` links for images:
-  var target = esc(d.target, getFieldValue(n, "target").strip())
+  var target = esc(d.target, getFieldValue(n, "target").strip(), escMode=emUrl)
   if target.len > 0:
     # `htmlOut` needs to be of the following format for link to work for images:
     # <a class="reference external" href="target"><img src=\"$1\"$2/></a>
@@ -1031,16 +1047,16 @@ proc renderCode(d: PDoc, n: PRstNode, result: var string) =
       blockEnd = "</span></tt>"
   of outLatex:
     if n.kind == rnCodeBlock:
-      blockStart = "\n\n\\begin{rstpre}" & n.anchor.idS & "\n"
-      blockEnd = "\n\\end{rstpre}\n"
+      blockStart = "\n\n" & n.anchor.idS & "\\begin{rstpre}\n"
+      blockEnd = "\n\\end{rstpre}\n\n"
     else:  # rnInlineCode
-      blockStart = "\\texttt{"
+      blockStart = "\\rstcode{"
       blockEnd = "}"
   dispA(d.target, result, blockStart, blockStart, [])
   if params.lang == langNone:
     if len(params.langStr) > 0:
       d.msgHandler(d.filename, 1, 0, mwUnsupportedLanguage, params.langStr)
-    for letter in m.text: escChar(d.target, result, letter)
+    for letter in m.text: escChar(d.target, result, letter, emText)
   else:
     renderCodeLang(result, params.lang, m.text, d.target)
   dispA(d.target, result, blockEnd, blockEnd)
@@ -1055,9 +1071,8 @@ proc renderContainer(d: PDoc, n: PRstNode, result: var string) =
     dispA(d.target, result, "<div class=\"$1\">$2</div>", "$2", [arg, tmp])
 
 proc texColumns(n: PRstNode): string =
-  result = ""
   let nColumns = if n.sons.len > 0: len(n.sons[0]) else: 1
-  for i in countup(1, nColumns): add(result, "|X")
+  result = "L".repeat(nColumns)
 
 proc renderField(d: PDoc, n: PRstNode, result: var string) =
   var b = false
@@ -1144,19 +1159,38 @@ proc renderAdmonition(d: PDoc, n: PRstNode, result: var string) =
   renderAux(d, n,
       htmlHead & "<span$2 class=\"" & htmlCls & "-text\"><b>" & txt &
         ":</b></span>\n" & "$1</div>\n",
-      "\n\n\\begin{mdframed}[linecolor=" & texColor & "]$2\n" &
+      "\n\n\\begin{rstadmonition}[borderline west={0.2em}{0pt}{" &
+        texColor & "}]$2\n" &
         "{" & texSz & "\\color{" & texColor & "}{\\textbf{" & txt & ":}}} " &
-        "$1\n\\end{mdframed}\n",
+        "$1\n\\end{rstadmonition}\n",
       result)
 
+proc renderHyperlink(d: PDoc, text, link: PRstNode, result: var string, external: bool) =
+  var linkStr = ""
+  block:
+    let mode = d.escMode
+    d.escMode = emUrl
+    renderRstToOut(d, link, linkStr)
+    d.escMode = mode
+  var textStr = ""
+  renderRstToOut(d, text, textStr)
+  if external:
+    dispA(d.target, result,
+      "<a class=\"reference external\" href=\"$2\">$1</a>",
+      "\\href{$2}{$1}", [textStr, linkStr])
+  else:
+    dispA(d.target, result,
+      "<a class=\"reference internal\" href=\"#$2\">$1</a>",
+      "\\hyperlink{$2}{$1} (p.~\\pageref{$2})", [textStr, linkStr])
+
 proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) =
   if n == nil: return
   case n.kind
   of rnInner: renderAux(d, n, result)
   of rnHeadline, rnMarkdownHeadline: renderHeadline(d, n, result)
   of rnOverline: renderOverline(d, n, result)
-  of rnTransition: renderAux(d, n, "<hr$2 />\n", "\\hrule$2\n", result)
-  of rnParagraph: renderAux(d, n, "<p$2>$1</p>\n", "$2\n$1\n\n", result)
+  of rnTransition: renderAux(d, n, "<hr$2 />\n", "\n\n\\vspace{0.6em}\\hrule$2\n", result)
+  of rnParagraph: renderAux(d, n, "<p$2>$1</p>\n", "\n\n$2\n$1\n\n", result)
   of rnBulletList:
     renderAux(d, n, "<ul$2 class=\"simple\">$1</ul>\n",
                     "\\begin{itemize}\n$2\n$1\\end{itemize}\n", result)
@@ -1202,7 +1236,7 @@ proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) =
     renderAux(d, n,
         "<div class=\"option-list-label\"><tt><span class=\"option\">" &
         "$1</span></tt></div>",
-        "\\item[$1]", result)
+        "\\item[\\rstcodeitem{\\spanoption{$1}}]", result)
   of rnDescription:
     renderAux(d, n, "<div class=\"option-list-description\">$1</div>",
         " $1\n", result)
@@ -1210,7 +1244,7 @@ proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) =
     doAssert false, "renderRstToOut"
   of rnLiteralBlock:
     renderAux(d, n, "<pre$2>$1</pre>\n",
-                    "\\begin{rstpre}\n$2\n$1\n\\end{rstpre}\n", result)
+                    "\n\n\\begin{rstpre}\n$2\n$1\n\\end{rstpre}\n\n", result)
   of rnQuotedLiteralBlock:
     doAssert false, "renderRstToOut"
   of rnLineBlock:
@@ -1237,8 +1271,8 @@ proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) =
   of rnTable, rnGridTable, rnMarkdownTable:
     renderAux(d, n,
       "<table$2 border=\"1\" class=\"docutils\">$1</table>",
-      "\\begin{table}\n$2\n\\begin{rsttab}{" &
-        texColumns(n) & "|}\n\\hline\n$1\\end{rsttab}\\end{table}", result)
+      "\n$2\n\\begin{rsttab}{" &
+        texColumns(n) & "}\n\\hline\n$1\\end{rsttab}", result)
   of rnTableRow:
     if len(n) >= 1:
       if d.target == outLatex:
@@ -1275,21 +1309,13 @@ proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) =
       "\\item[\\textsuperscript{[$3]}]$2 $1\n",
       [body, n.anchor.idS, mark, n.anchor])
   of rnRef:
-    var tmp = ""
-    renderAux(d, n, tmp)
-    dispA(d.target, result,
-      "<a class=\"reference external\" href=\"#$2\">$1</a>",
-      "$1\\ref{$2}", [tmp, rstnodeToRefname(n)])
+    renderHyperlink(d, text=n.sons[0], link=n.sons[0], result, external=false)
   of rnStandaloneHyperlink:
-    renderAux(d, n,
-      "<a class=\"reference external\" href=\"$1\">$1</a>",
-      "\\href{$1}{$1}", result)
+    renderHyperlink(d, text=n.sons[0], link=n.sons[0], result, external=true)
   of rnInternalRef:
-    var tmp = ""
-    renderAux(d, n.sons[0], tmp)
-    dispA(d.target, result,
-      "<a class=\"reference internal\" href=\"#$2\">$1</a>",
-      "\\hyperlink{$2}{$1} (p.~\\pageref{$2})", [tmp, n.sons[1].text])
+    renderHyperlink(d, text=n.sons[0], link=n.sons[1], result, external=false)
+  of rnHyperlink:
+    renderHyperlink(d, text=n.sons[0], link=n.sons[1], result, external=true)
   of rnFootnoteRef:
     var tmp = "["
     renderAux(d, n.sons[0], tmp)
@@ -1299,14 +1325,6 @@ proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) =
           "$1</a></strong></sup>",
       "\\textsuperscript{\\hyperlink{$2}{\\textbf{$1}}}",
       [tmp, n.sons[1].text])
-  of rnHyperlink:
-    var tmp0 = ""
-    var tmp1 = ""
-    renderRstToOut(d, n.sons[0], tmp0)
-    renderRstToOut(d, n.sons[1], tmp1)
-    dispA(d.target, result,
-      "<a class=\"reference external\" href=\"$2\">$1</a>",
-      "\\href{$2}{$1}", [tmp0, tmp1])
   of rnDirArg, rnRaw: renderAux(d, n, result)
   of rnRawHtml:
     if d.target != outLatex and not lastSon(n).isNil:
@@ -1334,7 +1352,7 @@ proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) =
       dispA(d.target, result,
             "<tt class=\"docutils literal\"><span class=\"pre $2\">" &
               "$1</span></tt>",
-            "\\texttt{\\span$2{$1}}", [tmp0, class])
+            "\\rstcode{\\span$2{$1}}", [tmp0, class])
     else:  # rnUnknownRole, not necessarily code/monospace font
       dispA(d.target, result, "<span class=\"$2\">$1</span>", "\\span$2{$1}",
             [tmp0, class])
@@ -1351,7 +1369,7 @@ proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) =
   of rnInlineLiteral, rnInterpretedText:
     renderAux(d, n,
       "<tt class=\"docutils literal\"><span class=\"pre\">$1</span></tt>",
-      "\\texttt{$1}", result)
+      "\\rstcode{$1}", result)
   of rnInlineTarget:
     var tmp = ""
     renderAux(d, n, tmp)
@@ -1360,7 +1378,7 @@ proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) =
       "\\label{$2}\\hypertarget{$2}{$1}",
       [tmp, rstnodeToRefname(n)])
   of rnSmiley: renderSmiley(d, n, result)
-  of rnLeaf: result.add(esc(d.target, n.text))
+  of rnLeaf: result.add(esc(d.target, n.text, escMode=d.escMode))
   of rnContents: d.hasToc = true
   of rnDefaultRole: discard
   of rnTitle:
diff --git a/tests/stdlib/trstgen.nim b/tests/stdlib/trstgen.nim
index 864728686..34b172935 100644
--- a/tests/stdlib/trstgen.nim
+++ b/tests/stdlib/trstgen.nim
@@ -259,7 +259,7 @@ A2     A3
 A4     A5
 ====   === """
     let output1 = rstToLatex(input1, {})
-    doAssert "{|X|X|}" in output1  # 2 columns
+    doAssert "{LL}" in output1  # 2 columns
     doAssert count(output1, "\\\\") == 4  # 4 rows
     for cell in ["H0", "H1", "A0", "A1", "A2", "A3", "A4", "A5"]:
       doAssert cell in output1
@@ -274,7 +274,7 @@ A0     A1   X
        Ax   Y
 ====   ===  = """
     let output2 = rstToLatex(input2, {})
-    doAssert "{|X|X|X|}" in output2  # 3 columns
+    doAssert "{LLL}" in output2  # 3 columns
     doAssert count(output2, "\\\\") == 2  # 2 rows
     for cell in ["H0", "H1", "H", "A0", "A1", "X", "Ax", "Y"]:
       doAssert cell in output2
diff --git a/tools/kochdocs.nim b/tools/kochdocs.nim
index 621dc643f..f25564fad 100644
--- a/tools/kochdocs.nim
+++ b/tools/kochdocs.nim
@@ -284,22 +284,22 @@ proc buildDoc(nimArgs, destPath: string) =
 
 proc nim2pdf(src: string, dst: string, nimArgs: string) =
   # xxx expose as a `nim` command or in some other reusable way.
-  let outDir = "build" / "pdflatextmp" # xxx factor pending https://github.com/timotheecour/Nim/issues/616
+  let outDir = "build" / "xelatextmp" # xxx factor pending https://github.com/timotheecour/Nim/issues/616
   # note: this will generate temporary files in gitignored `outDir`: aux toc log out tex
   exec("$# rst2tex $# --outdir:$# $#" % [findNim().quoteShell(), nimArgs, outDir.quoteShell, src.quoteShell])
   let texFile = outDir / src.lastPathPart.changeFileExt("tex")
-  for i in 0..<2: # call LaTeX twice to get cross references right:
-    let pdflatexLog = outDir / "pdflatex.log"
+  for i in 0..<3: # call LaTeX three times to get cross references right:
+    let xelatexLog = outDir / "xelatex.log"
     # `>` should work on windows, if not, we can use `execCmdEx`
-    let cmd = "pdflatex -interaction=nonstopmode -output-directory=$# $# > $#" % [outDir.quoteShell, texFile.quoteShell, pdflatexLog.quoteShell]
-    exec(cmd) # on error, user can inspect `pdflatexLog`
+    let cmd = "xelatex -interaction=nonstopmode -output-directory=$# $# > $#" % [outDir.quoteShell, texFile.quoteShell, xelatexLog.quoteShell]
+    exec(cmd) # on error, user can inspect `xelatexLog`
   moveFile(texFile.changeFileExt("pdf"), dst)
 
 proc buildPdfDoc*(nimArgs, destPath: string) =
   var pdfList: seq[string]
   createDir(destPath)
-  if os.execShellCmd("pdflatex -version") != 0:
-    doAssert false, "pdflatex not found" # or, raise an exception
+  if os.execShellCmd("xelatex -version") != 0:
+    doAssert false, "xelatex not found" # or, raise an exception
   else:
     for src in items(rstPdfList):
       let dst = destPath / src.lastPathPart.changeFileExt("pdf")