多么炫酷的工具、排版,都无法掩盖我的写作能力低下🤣。
引言 Link to heading
文档排版一直是老大难问题,我最开始就是使用 Word 一把梭。通过修改一套合适的 .dotm 格式模板,应付了大学第一年的文档写作。
然而,这种方式暴露出越来越多的问题:首先,word 格式实在太灵活了,一个不经意的操作就可能导致排版混乱且无法恢复;其次,随着我将主力设备切换到 MacOS,Word 的兼容性问题也逐渐显现。比如 MacOS 默认会以兼容模式打开 docx 文件,而且不支持修改底层样式和文档字体。
后面我就接触到了 LaTeX。不得不说,LaTeX 治好了我的排版强迫症,但是无限延长了写作时间。我还记得我第一次用 LaTex 写《中级微观经济学》课程作业,踩坑、查文档、调格式……一个手写只需要 15 分钟的作业硬是花了我一晚上。当然,我也不是没有收获,起码现在用 LaTex 语法写公式还算得心应手。如果你还在痴迷使用 Latex 记笔记,尤其是需要写大量公式的理科生,可以参考一下:
How I’m able to take notes in mathematics lectures using LaTeX and Vim
这篇文章把 LaTeX + Vim 的工作流发挥到了极致,也是促使我学习 LaTeX 和 Vim 的原因。
当然,LaTeX 现在也被我放弃了。起码在写作阶段,使用 LaTeX 并不是一件非常美妙的事情。作为双语用户,我需要时不时在中文输入法和英文键盘之间切换。LaTeX 大量的语法细节让我额外在中、英文间反复跳跃,实在不是一件愉快的事情。
Markdown 是写作的救星 Link to heading
事情的转机出现在我安装了一台黑苹果系统后。疫情期间实在无聊,我把自己的联想 Thinkpad T480s 倒腾上了黑苹果系统。Ulysses、MWeb、Typora 等等 Markdown 编辑器开始映入眼帘,随后三年时间我都使用 Typora 作为主力写作工具。Typora 宣布收费时我就第一时间购买了三台授权,至今还在使用。
Markdown 语法简单、易读、易写,而且 Typora 的所见即所得编辑器让我可以专注于写作。Typora 主题可以快速调整到一个比较优美的样式。课程作业、报告、博客等等用 Markdown 来完成远比 Word 和 LaTeX 强。
但是 Typora 并不是完美的,一个不要求排版的课程报告还好说,对排版要求比较细致的老师,Typora 的主题定制就有点力不从心了。Typora 的主题定制还是基于 CSS,所以很多 Word 上的排版效果是无法实现的。例如图注、表格标题、页眉页脚等等。
用 Pandoc 实现 Markdown 到 Word 的转换 Link to heading
所幸,Typora 的导出选项中有 Word 格式,我也发现了 Pandoc 这个神器。
Typora 就是基于 Pandoc 导出的,我摸索出了一条使用 Pandoc 将 Markdown 转换为 Word 的路径,支持所有课程作业需要的板式:图注、脚注、页眉页脚。这几周随着我对 Pandoc 研究的深入,我也成功实现了参考文献、摘要、关键词等自动化生成。可以说实现了一套高度可用的 Markdown 到 Word 的转换工作流。
这套工作流不仅可以转化成 Word 或 PDF 这种适合提交作业的格式,也可以快速生成博文。一次写作,双重享受🐶。
方法简介 Link to heading
因为时间有限,我这里大概列出我做了什么:
1. 学习 Pandoc 的基本用法 Link to heading
pandoc 文档 几乎提供了所有关于 cli 如何操作的知识。大致了解其工作原理对后面使用 pandoc 排版很有帮助。建议粗略看一下这是啥玩意儿,然后上手试试格式转换过程。
2. 生成一套自己的样式参考文档 Link to heading
1pandoc -o custom-reference.docx --print-default-data-file reference.docx
这条命令生成一个 custom-reference.docx 文件,修改其中的样式文件就可以做到调整样式。你可以参考学校的论文排版样式要求来修改,注意只能对已经有定义的样式进行修改。
大部分样式都基于一个叫做 “正文” 的样式。你可以首先修改 “正文” 的字体、大小、行间距等。
“First Paragraph”和“正文文本”定义了文档正文的样式,“Heading 1”…“Heading n”用来调整各级标题的样式,你可以在「修改样式」 -> 「编号」 上定义一个多级编号。
页眉、页脚也可以提前定义,比如我就定义了三段式的页眉,通过「插入」 -> 「域」的方式插入页码、标题等。
你也可以试试我的:reference.docx
3. 使用自定义 filter Link to heading
简单使用样式文档已经可以满足绝大部分需求了,但是我还想插入摘要、关键词,对编号样式进行细致修改等等,这就需要使用 Pandoc 的 filter 功能了。
这里定义了很多有用的 filter,比如docx-extract-bullet-lists可以修改默认的无序列表样式。我简单写了一个支持插入摘要、关键词的脚本,在参考文件中定义 Abstract 和 Keywords 两种样式之后就可以自动生成:
1if FORMAT == "docx" then
2 local function abstract_to_divs(doc)
3 if #doc.meta.keywords then
4 local keywords_text = pandoc.utils.stringify(doc.meta.keywords)
5 local keywords_para = pandoc.Para(pandoc.Str(keywords_text))
6 local keywords_div = pandoc.Div(keywords_para)
7 keywords_div.attr.attributes["custom-style"] = "Keywords"
8
9 table.insert(doc.blocks, 1, keywords_div)
10 end
11
12 if #doc.meta.abstract then
13 local abstract_text = pandoc.utils.stringify(doc.meta.abstract)
14 local abstract_para = pandoc.Para(pandoc.Str(abstract_text))
15 local abstract_div = pandoc.Div(abstract_para)
16 abstract_div.attr.attributes["custom-style"] = "Abstract"
17
18 table.insert(doc.blocks, 1, abstract_div)
19 doc.meta.abstract = nil
20 end
21
22 return doc
23 end
24
25 return { { Pandoc = abstract_to_divs } }
26end
4. 使用 citeproc + zotxt + biblatex 生成参考文献 Link to heading
这一步网上已经有教程了,我觉得这篇教程写的最好:
使用Markdown搭配Pandoc撰写学术论文的详细指南 - 知乎
不过我建议使用下面的 csl 文件,你可以运行 pandoc --version
找到默认配置目录,重命名你下载的 csl 文件为 default.csl
就可以了。
redleafnew/Chinese-STD-GB-T-7714-related-csl: GB/T 7714相关的csl以及Zotero使用技巧及教程。
5. 使用 pandoc-crossref Link to heading
这篇教程的「交叉引用」部分写的很好:
6. 用脚本来实现转换 Link to heading
每次都运行一长串的 pandoc … 命令太麻烦了,我写了两个脚本分别简化 markdown to word 和移动图片并按照 Markdown 扩展语法复制两个脚本:
1function md2word() {
2 if [ "$1" = "-h" ] || [ "$1" = "--help" ] || [ "$1" = "help" ]; then
3 echo "Usage: md2word <file> [pandoc options]"
4 return 1
5 fi
6
7 local file="$1"
8 shift
9
10 osascript ~/bin/closedoc.scpt "${file%.md}.docx" # 这个是用来关闭已经打开的 word 文件的
11
12 /opt/homebrew/bin/pandoc \
13 -s \
14 --filter pandoc-crossref \
15 -L docx-table-custom-style.lua \
16 -L pandoc-zotxt.lua \
17 -L 5.4/pandocker/docx-extract-bullet-lists.lua \
18 -L 5.4/pandocker/docx-abstract-keywords.lua \
19 --from markdown+east_asian_line_breaks \
20 "$@" \
21 -M link-citations=false \
22 --shift-heading-level-by -1 \
23 --citeproc \
24 -M titleDelim='' \
25 -M figureTitle='图' -M tableTitle='表' \
26 -M figPrefix='图' -M eqnPrefix='公式' -M tblPrefix='表' -M secPrefix='' \
27 "$file" -o "${file%.md}.docx"
28
29 open "${file%.md}.docx"
30}
31
32function img() {
33 if [ "$1" = "-h" ] || [ "$1" = "--help" ] || [ "$1" = "help" ]; then
34 echo "Usage: img <filename>"
35 return 1
36 fi
37
38 # make sure image.png | image.jpg | image.jpeg | image.gif | image.bmp | image.tiff at least one exists
39 local file
40 local ext
41 for ext in png jpg jpeg gif bmp tiff; do
42 file="image.$ext"
43 if [ -f "$file" ]; then
44 break
45 fi
46 done
47
48 if [ ! -f "$file" ]; then
49 file=$(find . -maxdepth 1 -type f \( -iname "*.png" -o -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.gif" -o -iname "*.bmp" -o -iname "*.tiff" \) | fzf)
50 ext=$(echo "$file" | awk -F. '{print tolower($NF)}')
51 fi
52
53 if [ ! -f "$file" ]; then
54 echo "Error: No image file found"
55 return 1
56 fi
57
58 # replace '-' with ' ' and make it title
59 local title=$(echo "$1" | sed 's/-/ /g' | awk '{print toupper(substr($0,1,1))substr($0,2)}')
60
61 mv "$file" "./assets/$1.$ext"
62 echo "![$title](./assets/$1.$ext){#fig:$1 height=300px}" | pbcopy
63 echo "![$title](./assets/$1.$ext){#fig:$1 height=300px} Copied to clipboard"
64}
里面还包括一个 Apple OS Script 用来自动关闭已经打开的 Word 文件:
1on run (args)
2 tell application "Microsoft Word"
3 if (count args) = 0 then
4 return
5 end if
6
7 set targetDocumentName to item 1 of args -- 设置要检查的文档名
8 set documentFound to false -- 标记是否找到文档
9
10 -- 通过索引遍历所有打开的文档
11 repeat with i from 1 to count documents
12 set theDoc to document i -- 通过索引获取文档
13 set thisName to name of theDoc as string -- 获取文档名称
14 set cleanDocumentName to my trimText(thisName) -- 清理文档名称
15
16 if cleanDocumentName is targetDocumentName then
17 set documentFound to true -- 标记找到了文档
18 -- 检查文档是否已保存
19 if saved of theDoc is false then
20 display dialog "The document '" & targetDocumentName & "' has unsaved changes. Close without saving?" buttons {"Yes", "No"} default button 2
21 if the button returned of the result is "Yes" then
22 close theDoc saving no -- 关闭文档,不保存
23 end if
24 else
25 close theDoc saving no -- 如果文档已保存,直接关闭不保存
26 end if
27 exit repeat -- 退出循环
28 end if
29 end repeat
30 end tell
31end run
32
33on trimText(inputText)
34 return do shell script "echo " & quoted form of inputText & " | xargs"
35end trimText