6  完整的 Markdown 工具

前面介绍了用 Markdown 来写书的步骤,当然,这属于自己造轮子的范畴。事实上,我们完全可以选用完整的工具集来做这个事情,毕竟这个世界上比我们聪明的人很多,我们想到的问题,别人或许已经解决了。

如果你只是用 Markdown 的方式来写,用自己的编辑器搭配 pandoc,那不可避免依然还会有参考文献的问题、输出风格的问题、交叉引用的问题,这些问题看似简单,实在是要形成完整的解决方案,还挺不容易的。下面推荐几个工具,或多或少都在尝试解决这些问题,选一个适用于你的。

6.1 R markdown

6.1.1 R Markdown 与 bookdown

R markdown 是使用 R 语言来写的一个 markdown 扩展包,工具链齐全,包括交叉引用、图表、参考文献引用、交互式数据操作等,非常适合用来写大部头的作品,这里就有一个用 R markdown 来写学位论文的样例:An Oxford University Thesis Template for R Markdown

为什么要使用 R markdown 来写书?bookdown: Authoring Books and Technical Documents with R Markdown 这本书的前言 Why read this book 就写得非常好,简而言之,LaTeX 写书时需要处理的技术细节过多,交互不够友好,与读者的交互也不够及时;而 Word 同样也有类似的问题。并且 LaTeX 和 Word 都有一个不可逆的问题,当你选用了其中一个格式后,要换成另外一种格式就非常麻烦。而 Markdown 则不存在这些问题,一来可以让你聚焦于写作内容本身,在聚焦写作的时候不用太关注文档格式的技术细节问题,二来可以利用 pandoc 很方便地在各个格式中转换。

R markdown 的设计理念非常好:是直接从 Rmd 编译生成 markdown,再生成 GitBook 或其它最终格式。它的这个设计理念,包含了现有大量成熟的工具,避免了重复造轮子。

不过我一直有一个疑问,为什么会选用 R 语言来开发这一套工具呢?这么完整的工具,为什么首先出现在 R 语言社区里呢?下面是一些我的推测,根据作者的说法:

I was hoping to work on it when I first saw the GitBook project in 2013, because I immediately realized it was a beautiful book style and there was a lot more power we could add to it, judging from my experience of writing the knitr book (Xie 2015) and reading other books.

对于写 R Markdown 的动机,作者也在 Motivation 一节 交待得很清楚:

There are still a few useful features missing in Pandoc’s Markdown at the moment that are necessary to write a relatively complicated document like a book, such as automatic numbering of figures and tables in the HTML output, cross-references of figures and tables, and fine control of the appearance of figures (e.g., currently it is impossible to specify the alignment of images using the Markdown syntax). These are some of the problems that we have addressed in the bookdown package.

需要 GitBook 是一个漂亮的书籍工具,但是谢益辉需要给 GitBook 添加一些能力。恰好后来他又在 RStudio 工作,所以用 R 语言来开发本工具,就理所当然了。不过这个只能说是契机,依然无法解释我的问题。

还有一个更齐全的参考文献:R Markdown: The Definitive Guide

6.1.2 给 bookdown 生成的 pdf 书籍添加封面

前面我在 LaTeX 一章中的制作封面一节里,提到给自制书籍添加封面的必要性。对于 R Markdown 生成的书籍,默认会生成一个非常丑陋的 title 页面,不管对于内部参考书籍或是自我参考书籍来说,这个封面的风格都是让人难以忍受的。

给 bookdown 生成的 pdf 书籍插入一个自制的封面,同样非常简单。按如下几个步骤,就可以生成自己心仪的书籍封面。

  1. 在 index.Rmd 文件里的 YAML 信息里,去掉会生成 title 页面的相关信息,比如说 “title”、“author”、“date”等,这样生成的 pdf 书籍就不会自动生成封面了。

  2. 在 bookdown 里,修改生成 LaTeX 里有关 preamble 和 body 前后相关的 LaTeX 代码(详细原理参看Add LaTeX code to the preamble),在 preamble.tex 里,引入 pdfpages 包,

\usepackage{pdfpages}

而在你的 pre body 设置的 tex 代码里(这个文件在我这里被称为 prebody.tex),添加上你制作好的 LaTeX pdf 封面:

\includepdf[fitpaper]{./images/bookcover.pdf}

并且在这一行代码后面,你还可以制作你自己专属的菲页,插入一些书籍标题、写作团队、版本信息等;而菲页的制作,你可以使用上 LaTeX 制作内页封面的相关技巧。比如这里的代码就是一个不错的例子:

\documentclass{book}
\usepackage{graphicx}
%\usepackage{fancyvrb}
\begin{document}
\clearpage
%% temporary titles
% command to provide stretchy vertical space in proportion
\newcommand\nbvspace[1][3]{\vspace*{\stretch{#1}}}
% allow some slack to avoid under/overfull boxes
\newcommand\nbstretchyspace{\spaceskip0.5em plus 0.25em minus 0.25em}
% To improve spacing on titlepages
\newcommand{\nbtitlestretch}{\spaceskip0.6em}
\pagestyle{empty}
\begin{center}
\bfseries
\nbvspace[1]
\Huge
{\nbtitlestretch\huge
AWK ONE LINERS EXPLAINED}

\nbvspace[1]
\normalsize

TO WHICH IS ADDED MANY USEFUL ONE\\
LINERS AND CODE SO THAT\\
YOU CAN AWK LIKE A HAWK
\nbvspace[1]
\small BY\\
\Large PETERIS KRUMINS\\[0.5em]
\footnotesize AUTHOR OF ``A WORKING ALGEBRA,'' ``WIRELESS TELEGRAPHY,\\
ITS HISTORY, THEORY AND PRACTICE,'' ETC., ETC.

\nbvspace[2]

\includegraphics[width=1.5in]{./graphics/pic37}
\nbvspace[3]
\normalsize

DOHA\\
\large
PUBLISHED IN THE WILD
\nbvspace[1]
\end{center}
\end{document}
  1. 补充说明一下:如果按第 1 点这样修改,你生成的网页版本里 index.html 文件就不会有作者信息了,如果为了将来还是需要生成网页版本,可以保留 YAML 里的 title 信息,然后重载 LaTeX 里的 maketitle 命令:
\renewcommand{\maketitle}{\includepdf[fitpaper]{./images/bookcover.pdf}}

备注:第3步的作用,与前两步类似,只是技巧性更强一些。

通过上述三个步骤,就可以在 bookdown 生成的 pdf 书籍上添加自制的封面,生动、直观又富于趣味性。

6.1.3 R Markdown 的缺点

使用 R Markdown 来写书,感觉相当爽,尤其是能和 BibTeX 无缝对接,一键编译自动生成书籍的感觉实在太好了。但是它同样有一些缺点:

  1. 整体使用略为有点复杂,文档结构也比较复杂,只能说配置和文档书写比 LaTeX 要简单,但是使用上的模式整体感觉和 LaTeX 很像。

  2. 需要安装 R 语言包,另外 RStudio 这个 IDE 不美观。

  3. Rmd 的文件后缀并不好用,很多编辑器识别不了。我的办法是用 md 为后缀的文件来写作及预览,然后和脚本批量转为 Rmd 的后缀再进行编译,这样我就可以使用我最习惯的编辑器。

6.1.4 bookdown 的几个小贴士

  1. 中文写作

注意,如果需要用 bookdown 来进行中文写作,如果你是从 yihui 的例子开始,必须注意把 index.Rmd 里的下面这行:

documentclass: book

换成

documentclass: ctexbook

懂 LaTeX 的同学都知道,这是为了让 LaTeX 挑一个支持中文的模板来输出 PDF。也可以查看 bookdown 中文模板开始制作中文书籍。

  1. 不编号“前言”、“序言”这些章节

不编号的章节,在章节后面上加 {-} 即可,比如说:

# 前言 {-}

这个”前言“章节就不会编号。

  1. 生成单独的“参考文献”一章

可以单独取一章,然后用下面的代码来生成单独的参考文献章节:


# 参考文献 {-}

对于中文字样的参考文献,在生成 html 的时候,最好还是用英文 URL 比较合适,这时可以用类似下面的配置:


# 参考文献 {- #reference}
  1. 对中文书籍使用“第X章”这样的格式来命名章节

中文书籍不同于英文书籍,英文书籍直接是“Chapter 2”来表示第二章,而对于中文来说,则需要简单修改一下 _bookdown.yml 里的配置,把

chapter_name: "Chapter "

改成

chapter_name: ["第 ", " 章"]

改成下面就可以生成“第2章”这样的式样:

  1. 输出文档为 Word

bookdown 可以直接导出为 Word 文档,只需要在 output 配置里添加下面这一行即可:

bookdown::word_document2: default 
  1. 输出书籍的配置方法

Bookdown 的目录结构,是有其特别的约定,具体先阅读一下《R Markdown: The Definitive Guide》的 Project structure 一节。

在 index.Rmd 或者_bookdown.yml中設置site: bookdown::bookdown_site 后, RStudio 就能识别这個项目是一個bookdown 项目, 这时 RStudio 会有一個 Build 窗格,其中有“Build book”快捷图标,从下拉菜单中选择一个输出格式(根据你的配置,可能包括 gitbook、pdf_book、epub_book), 就可以编译整本书。

对各种输出格式的具体设置,即可以在 index.Rmd 文件里通过 YAML 元数据字段 output 来配置,也可以通过单独的 _output.yml 文件来配置。

  1. 书的内容,针对不同输出格式进行调整

R Markdown 有条件语句可以使用,在针对不同的输出格式进行调整时,可以使用 R 语言的条件语句,比如说,如果针对 html 想输出一个 gif 文件,而默认在 pdf 里包含 gif 文件是很麻烦的一件事情,所以特意把代码针对 html 输出 gif 而针对 pdf 不输出,可以使用下面的条件语句:

```{r, echo=FALSE, comment=""}
cat(c("```{r, include=knitr::is_html_output(), out.width='100%', echo = FALSE, fig.align='center', fig.cap='Fourier Frequency 示意'}",
      "knitr::include_graphics('images/frequency.gif')",
      "```"), 
    sep='\n')
```

注意到里面的

include=knitr::is_html_output()

条件语句了么?类似的还有 latex 的输出条件:

include=knitr::is_latex_output()

6.1.5 给书籍添加统计

如果书籍是通过发布为 html 后放在在线给大家阅读,那最好有一个在线统计的功能,看读者最喜欢阅读的是哪一部分内容。虽然技术人员写书目的更多的自我娱乐和自我学习,但是通过查看统计结果,也可以知道哪一部分技术是大家感兴趣的方向,对自己将来工作方向的调整也是有帮助的。

对于这种静态站点,添加在线统计最经济的方式,就是借助第三方的统计工具,比如 Google Analystics 或百度统计之类的工具,免费好用报表功能齐全,甚至于还可以跟另外一些商业化工具整合在一起。当然,对于中文站点,主要读者依然是中国境内读者,所以推荐使用百度的统计功能,毕竟,在中国境内访问 Google Analystics 报表需要翻墙。这里就以百度为例,说明一下在 Bookdown 里添加 JS 统计方法(参考:Include the content of an existing HTML file)。

  1. 先获取百度统计的 JS 脚本,并且保存为 baidu_stat.html 文件。

  2. 方法一,可以在 index.Rmd 文件里,加上这么一个 in_header 文件:

title: "这是一篇演示文稿"
output: 
  html_document:
    includes:
       in_header: baidu_stat.html
  1. 方法二,在 *_output.yaml* 文件 里,加上这么一个 in_header 文件:
bookdown::gitbook:
  css: style.css
  includes:
    in_header: baidu_stat.html

第 2、3 两种方法选用一种即可,重新生成页面并发布,就可以获取你想要的在线阅读统计数据。

6.1.6 写作工具

写 R markdown,语法还是和 markdown 会略有一点不同,但是一般的 markdown 编辑工具都可以使用,只是在最后需要把后缀名从 .md 改为 .Rmd。

也可以使用 RStudio 来进行修改,不过 RStudio 的 UI 给人的感觉实在太旧式风格了。

对自己的写作习惯,依然是使用 Joplin 写 markdown,对一个主题新建一个目录开始按章节写作,毕竟 Jopline 可以自动保存、跨平台,还是比 git 之类的方便,可以专注于写作本身;书或笔记成形后,即可导出为 markdown 文件,再用命令行批量修改文件名为 .Rmd,比如在 Windows cmd 下就这个命令就可以:

ren *.md *.Rmd

把修改好的 Rmd 文件放到 R project 目录下就可以正常编译发布书籍。

6.1.7 排版技巧

R Markdown 虽然好用,但是依然会碰到一些问题,需要有一些处理技巧。

6.1.7.1 目录编号

默认情况下,我用 R Markdown 外加 ctex 宏包,默认生成的 pdf 里,目录编号并不是从1 开始,而是从0开始,导致会生成如图 的 toc 目录列表:

从0开始的TOC列表

可以通过配置来解决,可以在 _output.yaml 里添加如下字段保证编号从最顶级目录开始:

pandoc_args: --top-level-division=chapter

6.1.7.2 引用代码

在自然段里引用代码

在行内引用代码,直接用 code这样就可以写在某一行内的文字里:

代码的引用处理,对于 LaTeX 等解释代码来说特别重要,不然一大堆的 \input 会导致完全无法编译生成pdf文件。

6.1.7.3 控制 LaTeX 生成 pdf 格式的一些技巧

  1. 通过 latex 生成的 pdf 文件,从正文开始编码

默认生成的 pdf 文件,会从首页开始算第1页开始编码,这样不符合一般从正文开始从第1页编码的习惯,这个时候,有一个技巧可以用,直接用 latex 控制页码:

  • 在 preamble.tex 文件里的最后添加下面代码,保证刚开始先不编码页码:
\pagenumbering{gobble}
  • 在前言(也可以是你需要编码的标题 # 后的第一行)后第一行添加下面的 LaTeX 代码,保证从前言或第一章开始编码:
```{=latex}
\pagenumbering{arabic}
```

注意,这里是{=latex}而不是latex。

  1. 中文文档,每行开头空两格:

可以直接在 preamble.tex 的文件里添加如下两行:

```latex
\usepackage{indentfirst}
\setlength{\parindent}{2em}
```
  1. 在用 latex 生成 pdf 的时候,nocite 不生效的问题

用 nocite 生成 html 的时候,会把未引用的参考文献,直接贴到参考文献列表里,但是用 LaTeX 生成 pdf 的时候,却不会,这时可以直接在文档里使用 LaTeX 语句的 nocite:

```{=latex}
\nocite{zhangyi2016ziyou, sxzg2019congdiqiaer, fayushijie2021faguodeshuxue}
```
  1. 原封不动的把 R 代码输出到文档里:

这里有一个专题讨论 Embed Rmarkdown with Rmarkdown, without knitr evaluation,可以下面的方法:

```{r, echo=FALSE, comment=""}

cat(c("```{r, include=knitr::is_html_output(), out.width='100%',
           echo = FALSE,
           fig.align='center', fig.cap='Fourier Frequency 示意'}",
           "knitr::include_graphics('images/frequency.gif')",
           "```"),
           sep='\n')

```
  1. 空行

在 R markdown 里,要空行,可以直接用:

 
 
 

来做多行空行。

  1. 添加 draft 水印:
header-includes:
  - \usepackage{draftwatermark}

完整的效果如下图所示:

如果要定制水印就用 LaTeX 的 SetWatermarkText 命令:

header-includes:
  - \usepackage{draftwatermark}
  - \SetWatermarkText{DRAFT\\FOR WYQ}

这样提供版本给不同的人预览的时候,可以加不同的水印,方便追踪版本泄露。

  1. 写书的时候,给每一章,添加本章单独的作者,以及名言警句(prologue)等

R markdown 默认并不支持这类东西,latex 里倒是有,但是可以用一些技巧来处理:

直接使用 authorbox 与 prologuebox 来定义两者的样式,即使用单独的一个格式来处理,然后在 preamblex.tex 或 style.css 里来规定好相应的样式。

样例可以教考我的书籍《音视频质量推断》里的章节设置

详细的设置,可以看Custom blocks

方式一:通过 authorbox 和 prologuebox 来设置

::: {.authorbox data-latex=""}

**本章作者:** 林绪虹, linxuhong@joyy.com

:::

:::: {.prologuebox data-latex=""}

测试一下名言名言 **new notice**! Your noticing it has

been noted, and _will be reported to the authorities_!

::: {.authorbox data-latex=""}

--mark williams

:::

::::

方式二:直接通过 flushright 和 minipage 来设置

:::: {.flushright data-latex=""}

::: {.minipage data-latex="{.7\linewidth}"}

This paragraph will be centered on the page, and

its width is 50% of the width of its parent element.

:::

::::

6.1.8 推荐书籍

R markdown 体系略为有一点复杂,还是需要系统地参阅文档,这里推荐几本我个人觉得不错的书籍,方便快速入门:

6.1.9 资源

一些少量有用的资源:

  • Tufte 风格,很漂亮的风格,直接在旁边写上空白注释,非常适合在线阅读。

  • MSMB HTML Book Style,相比于 Tufte 风格,我觉得这个改进版本更漂亮,更符合长篇文章的习惯,起码是按章来分页,章节导航位于右边也更方便。下载这个 MSMB 风格的源码,用 R Studio 打开inst/rmarkdown/templates/msmb_html/skeleton/skeleton.Rmd 文件,点击”Build” ==> “Install and Restart” 就可以安装这个风格软件包。

6.2 GitBook

很有名气的项目,还提供书籍的托管工作。

我不喜欢的几点:

  1. Bibtex Citations Gitbook Plugin 的参考文献的引用格式太奇怪了,是这样的:
{{ "TLW" | cite("p.~121") }}

而现在在 Joplin 或是 Zettlr 里,都是使用的这样的格式:

[@citekey, P.121-122]

相比之下,R Markdown 的语法就很用符合当今主流。

  1. 定制参考文献格式的 cls 样式,太麻烦

6.3 mdbook

mdbook 是 gitbook 的 Rust 版本,更适合不喜欢用 node.js 的同学。使用上没有什么特别的地方,但是默认的功能不支持交叉引用、参考文献引用等高阶功能,很致命。

据说插件 mdbook-bib 可以解决 bib 的引用问题,但是我没试过,有用过的同学来点评点评。

这个毕竟是一个仿制 GitBook 的项目,插件好像没有 GitBook 多,不知道是不是我的主观感受而已。

GitBook 有的缺点,mdbook 也同样有,插件 mdbook-bib 参考文献的引用 markdown 语法不同于 pandoc 的语法,

{{#cite my-citation-key}}

或是

@@my-citation-key

参考文献的引用格式也不方便使用 csl 自定义。

6.4 托管 markdown 生成书籍

不管是用 bookdown、GitBook 或是 mdbook 生成的静态 html 书籍,都可以直接放在网上对外公布,基本的原则都很简单:就是把生成的 html 托管到一个站点上,这个站点的选择现在很多,可以用 GitHub Pages、bookdown.org、GitBook,也可以使用 Netfily。使用 Netlify 的好处是,你只需要把你生成的 html 页面上传,它可以链接你 GitHub 的私有仓库,对你的源代码 markdown 保密有一定的作用。

比如说,你可以直接用 Next + Netlify Starter 这种最简单的方式,生成一个托管站点,并把你生成的 html 书籍内容,直接放置到 https://nextjs.org/docs/basic-features/static-file-serving下,就可以访问你的书籍了。

6.5 小结

简单一点的书,用 markdown 写内容再用 pandoc 手工编译,基本上都够用;复杂一点的,并且需要输出格式好看的话,则需要使用 GitBook 或是 mdbook 这样的工具;如果再处理复杂的科技文献(包括图表编号、交叉引用等),则需要使用 R Markdown 配合 bookdown 来进行技术写作。