原文地址:journal.stuffwithstuff.com/2021/07/29/…
原文作者:journal.stuffwithstuff.com/
发布时间:2021年7月29日
我的关于编程语言的《Crafting Interpreters》一书已经完成。好的,好的。我知道我说过它在15个月前就已经完成了。但现在它真的完成了。我的意思是,印刷版、电子版和PDF版都完成了。你可以买到它。你可以把它拿在手里。我的意思是 "手 "的复数。因为这本小 "手册 "比我预期的要大得多。
这是一份样书,所以它看起来和最终的设计有一点不同。
看看这个东西。640页,8英寸宽,10英寸高。如果你读累了,它可以作为一个门挡,或者保护你免受小武器的攻击。
还记得在《罗杰先生的邻居》中,他将带你到一个工厂,向你展示铅笔或雨伞是如何制造的吗?我喜欢这些东西,所以我想也许你会想看看我在过去一年里花了多少钱。你可以把这看作是对幕后的窥视,也可以看作是对为什么花了这么长时间的道歉。
我离开的地方
我在上一篇文章中说,《口译员的制作》已经完成,我的意思是,我写完了所有的代码,点完了最后一句话,扫描了最后一幅插图。这本书的内容已经完成。但它还不是一本书。只是一堆Markdown和PNG文件,我那令人尴尬的糟糕的Python代码将勉强把它变成一个网站。
我最初的目标是做一本真正的书,有页面和一切。完成内容只是这个过程中的一个(当然,是最重要的)步骤。一旦我完成了最后一章并把它放到网上,我就休息了大约一个月的时间。我几乎每天都在写作,已经写了四年了,我很疲惫。另外,如果你不记得了,2020年初在世界历史上并不是一个完全美好的时代。
一个新的构建系统
几周后,我又恢复了彻底完成这个项目的愿望,于是我又缓缓地投入到工作中。我修正了一大堆错别字和其他错误,这些错误都是读者提出来的(谢谢!)。
然后,没有什么好的理由,我决定用Dart重写这本书的整个构建系统。我为我的第一本书写的构建脚本非常简单。仅仅是一个Python脚本,它为每一章编写了一个Markdown文件,并将其渲染成HTML,同时将代码片段编入其中。这是世界上最愚蠢的静态网站生成器。
我从《口译员的制作》开始,但后来它不断地增长。我的第二本书包括两个完整的解释器的每一行代码,它在三十个章节中逐步建立起来。我不仅需要为这本书的网站建立HTML,还需要确保这些代码真的能够工作。我让构建系统不仅能够生成该书的网站,而且还能对代码进行切分。给定一个章节,甚至一个章节中的一个点,它可以输出一个程序,其中包含到那个点为止的所有解释器的代码。然后,我可以拿着这些代码,对其进行编译,并通过我的自动测试套件运行,以确保我向你展示的代码是它应该做的。
这很有用,但对于我想在像Python这样的动态类型语言中维护多少代码来说,确实是个很大的挑战,至少,以我的Python专业水平(低)。另外,坦率地说,它真的很慢。因此,在几个星期的时间里,我用Dart重写了整个事情。
我的日常工作是在谷歌的Dart语言团队工作,所以选择Dart并不是一个没有偏见的选择。但这是我的书的构建系统,而且我对Dart和它的许多核心库和包都了如指掌。该死的,我亲自编写了最初版本的经典markdown包。
我发现了一个相当不错的mustache模板包,所以我把书中旧的Liquid模板转换成了这个模板。我没有找到一个好的语法高亮程序。但我也不是很努力地寻找。这似乎只是一种从头开始实现的有趣的东西,所以我在Pygments的基础上粗略地做了一个。
最终的结果是一个新的构建系统,它能准确地生成我想要的HTML和语法高亮的代码。而且,它比原来的Python系统快十倍。正如你所看到的,我对Markdown的处理有了更好的控制,这是很方便的,但当时我基本上只是为了好玩和拖延真正的工作而这样做。
我计划在开发服务器中实现一些巧妙的增量重建逻辑,但它的构建速度非常快,我只是在每次刷新时重建一切。
一旦我有了新的构建系统,输出漂亮干净的HTML,并且我删除了旧的Python东西,就可以开始了。
设计这本书
做一个像书一样的大型平面设计项目,很像网络开发或游戏编程,它有两个层次。首先,你建立你的 "框架 "或 "引擎"。在网络上,这是你的网络框架和你所有的CSS和HTML模板。在游戏中,它是你的游戏引擎。然后你把内容倒入这个结构。有了正确的框架,添加内容就很容易。
对于使用像Adobe InDesign这样的程序进行图形设计,其工作方式是你设置样式和母版。主体定义了一个页面的边距和网格线。它是约束所有文本的篱笆,以防止那些野蛮的字母到处乱跑,吃掉所有的空白空间。样式就像CSS:它们让你采取一种语义上的文本或对象,并为其关联特定的字体、样式和颜色。
理论上说,你把大师和样式弄对了,然后排版就很容易和机械了。现在,在谈到书籍设计时,我的生活并不轻松。书籍设计从字面上看是一个二维的空间练习,而我在水平和垂直方向上都使我的工作更难。如果你在网上读过任何一篇,你就知道我的书有。
-
当然,是散文。这么多字。
-
很多旁白,需要紧挨着它们提到的某些文字、代码或插图。其中一些可以变得相当长。
-
大量的代码。另外,每个代码片段旁边都有一个小的位置说明,告诉你代码在结果程序中的位置。
有多宽?
在水平方向上,主文本栏需要有足够的宽度来容纳最长的一行代码。我可以使用较短的行,并更多地包裹代码片段,但这将使它们更难阅读。这也使得它们在垂直方向上更高,这也造成了自己的问题。
然后,我需要为旁白留出空间,因为它们经常直接评论特定的句子。当然,我也可以把它们变窄。但这样一来,它们就会被包裹起来,变得越来越高。有些旁白是相当长的,如果它们变得太高,它们就会开始相互碰撞或重叠的位置片段。
哦,由于我最终写了一本20万字以上的书,所以它的页数会很高。这意味着一本厚书。厚书需要更宽的内页边距,这样文字才不会消失在书脊中。
所有这些都指向了一个相当宽的页面。大多数CS教科书--至少是我书架上的那些--是7.5英寸宽。我努力想出一个设计,在这个宽度内容纳代码、旁白和健康的页边,同时又能提供一个不需要放大镜的文字尺寸。最终,我认输了。
一旦我试着为一个8英寸宽的页面设计一套指标,一切都水到渠成。我可以在文本周围留出足够的呼吸空间,使其能够愉快地阅读,为代码片断留出适当的长度,并为旁白留出足够的空间。(使用较窄的字体来写旁白也有帮助)。)
以下是最终的水平指标。
有多高?
这就留下了另一个维度。如果我和一个真正的出版商一起做全胶印,我可以选择我想要的任何页面尺寸。但是,由于我是自我出版,这就意味着要预先支付数千份印刷品的费用,而且,我不知道,要把所有的箱子存放在我的车库或其他地方。
按需印刷对我的上一本书很有效,我计划在这本书中做同样的事情。这意味着坚持使用KDP和IngramSpark支持的有限的页面尺寸集。唯一合理的8英寸宽的尺寸是8"×10",所以我就选了这个尺寸。最终的结果是一本感觉很大的书,但希望不是笨拙的巨大。我很抱歉。我保证如果我再写一本书,会写得更小。
一个完整的章节散页的度量。在垂直方向上,文字是按照经典的12pt基线网格排列的。
在我选择这些边距和指标的时候,我也在选择字体和建立风格。你不可能在真空中做平面设计,所以我挑选了一个测试章节,然后反复排版和调整字体。最终,我得到了一套我喜欢的字体和样式,一些看起来可行的主编,我准备好了。框架已经完成,现在是打开内容管的时候了。
XML,就像1999年一样
当然,问题一是没有内容管道。我必须要建立一个。InDesign不知道Markdown和我那古怪的构建系统是什么东西。我肯定不想,比如,从我的浏览器中复制每一章,把它粘贴到InDesign中,然后手动应用所有的样式。我有很高的痛苦容忍度,但我不是一个受虐狂。
InDesign可以做的是导入XML。更妙的是,你可以对它进行设置,使其自动将某些段落或字符样式应用于某些标签名称。可惜的是,自从我把InDesign对XML的支持说成是由一个患有嗜睡症的实习生实现的以来,它还没有成熟多少。例如,在HTML中,你可以通过使用斜体标签并将其嵌套在标题标签中,将标题中的某个单词斜体化。InDesign无法理解这种高级的数据建模。它需要一系列扁平的非嵌套标签,如果你需要在页眉中使用斜体,你最好为它准备一个独特的<italics-header>
标签。
但是,现在我有一张王牌在手。由于我对我的构建系统和它的Markdown处理有微观的控制,我可以编写我自己的自定义XML导出器,准确地生成能让InDesign不哭的标签,并尽可能地避免InDesign的XML导入错误。
JavaScript,在我的InDesign中?
即便如此,XML导入也只能让你走到这里。具体来说,用InDesign的话说,它能让你得到一个 "故事":一个连续的文本叙述,填满主文本框并跨越多个页面。
最初的XML导入是什么样子的。粉红色的文字是插图的位置,而"@"则告诉我应该在哪里锚定旁白。
主体故事是散文和代码片段所在的地方,但旁白和位置标记需要被拉出来浮在一边。在我的上一本书中,我手动将这些东西拉了出来。我把每一个旁白从主文本栏中剪下来,然后粘贴到一个新的文本框中。这花了很长时间,而那本书的长度还不到这本书的一半,代码片段也少得多。
Crafting Interpreters》中共有1133个代码片断。如果我不得不手动剪切和粘贴这些代码的位置标记,我就会失去理智。更糟的是,我会犯很多错误,这只会给自己带来更多的工作。
我是一名工程师,所以我在道义上反对容易出错的、可以自动化的手工作业。你知道InDesign是可以用JavaScript编写脚本的吗?嗯,你现在知道了,但这显然让你成为了少数人,因为那里没有任何关于它的文档。你可以找到一些自动生成的参考资料,以及一些明显不在状态的图形设计师发出的悲哀的求助声,但没有任何回应,仅此而已。
使用InDesign的JavaScript是一种特殊的痛苦。这里没有调试器。没有堆栈跟踪。甚至连调试打印都没有。只有alert(),而且你只能调用它一次。而且它会使你的脚本停止。幸运的是,我实际上是在浏览器只给你提供这些的时候学的JavaScript,所以我可以黑掉它。
就像为IE6开发一样。
我设法拼凑了一个可怕的脚本,可以找到所有的旁白和位置标记,把它们从主文本流中拉出来,并把它们放在一边。
旁白和位置标记被从主流程中拉出来,进入它们自己的文本框。
我没能让脚本正确定位这些文本框。但是InDesign有一个叫做 "锚 "的东西,你可以把一个元素相对于另一个元素的位置锁定。几个精心设计的对象样式甚至可以正确设置水平度量,并将文本对准右边的基线。我所要做的就是锚定每个文本框,然后就可以完美地工作了。
等等,我是说 "完美 "吗?我的意思是,它有一半的时间是正确的,另外一半时间InDesign会莫名其妙地关闭附近代码段的该死的边框。
当InDesign讨厌你的时候是什么样子的。
我因为这个愚蠢的错误失去了几个小时的时间。最后,我意识到,一些完全随机的位置标签子集必须要手动定位,因为锚定总是会破坏一些边框。
编辑,再次
上述所有的事情花了我一个多月的时间,然后是时候停止摆弄,开始着手处理实际的内容了。我做的第一件事是再次对整本书进行编辑,从头到尾。在写每一章的时候,我已经做了三稿,但现在已经完成了,我想再做一稿,以便对连续性有更好的感觉。事实证明,我经常重复同样的愚蠢的笑话。我修正了那些(大部分)。
这花了五个月时间。这没什么可说的,只是个苦差事。
文案编辑
接下来,我聘请了一位真正的专业文案编辑,卡里-萨默顿,去做同样的事情。她很出色。编辑界的大多数人都使用Microsoft Word和 "跟踪修改 "来处理编辑过程。像大多数软件工程师一样,我生活和呼吸着明文和Git。这样我就可以看到变化的差异,并回溯历史。
我不想放弃我的工作流程,所以我请Kari在Git和我的奇怪的完全定制的构建系统上加把劲。她处理得很好,很快就把这本书翻了个遍。她发现了成百上千的错误。尽管我已经做了四稿,读者已经提出了数百个问题。专业的文字编辑是物有所值的。
整本书的排版
一旦这些文字被认为是好的,就该把它们印在书页上了。这个过程是这样的。
-
为下一章创建一个新的InDesign文件。
-
将其导出为XML。
-
将XML导入InDesign。
-
运行我的小JavaScript脚本,取出边栏和位置标记。
-
仔细检查并锚定边栏的内容。
-
修正页面末尾的空白处。
前五个步骤是小菜一碟。我早上起来,冲一杯咖啡,摇摇晃晃地走到楼上的iMac,然后开始工作。我可以在半梦半醒之间完成这些步骤,并在半小时左右完成一章。这很平静。几乎是冥想。
然后是第六步。你看,这就是排版一本书的困难之处。而这也是这本书排版的真正困难之处。因为事实证明,在如何将内容垂直地装入一个页面上有很多限制。很明显,我们不能把一幅插图切成两半,然后把上半部分放在一页,下半部分放在下一页。旁白也确实需要放在一页上,否则就会让人搞不清楚它们指的是什么。
只要有可能,代码片段也最好不要被分割到各个页面。有些代码可能有十几行长。(这也是另一个原因,为什么更宽的水平度量衡有帮助。因为如果我把代码片段做得更窄,它们最终会变高,这将使它们更难在页面上被容纳)。) 而且,你不希望在页面的最后单独有一个标题,后面没有内容。而且,避免寡妇和孤儿也是好的......
把所有这些规则和限制,再加上完全固定的页面高度,你就得到了一个真正的约束解决的问题。或者,在我的例子中,有640个,都是相互交织的。因为,你看,InDesign很乐意为你解决这一切,只需将内容推到后面的页面。代码片段太长?把它移到下一页。标题下没有空间放散文了?把它全部移到下一页。这给你带来的是页面底部大量的死白空间。它看起来很糟糕,而且浪费了空间,就像这样。
我深思熟虑地在页面上为你自己的涂鸦留下了空间。
说到插图...
在一个层面上,插图很容易。我特别选择了黑白笔墨,因为它便于印刷。当我在写每一章的时候,我首先扫描了这些图片,我把它们带到了光辉的1200DPI。这里有一个裁剪。
在Photoshop中做了一点水平调整。
如此详细 将这些图片导出为高分辨率的位图,打印起来很容易,而且看起来很好。(我想,和我的笔迹看起来一样好)。)
将这些插图纳入页面布局是另一个故事。在排版到一半的时候,一个灯泡亮了起来,我终于明白为什么大多数书都说:"请参考图123,看看什么什么......" 这就给了排版人员自由,可以把图123放在任何附近的页面上的任何地方。
在我这本愚蠢的书里,因为我是个白痴,散文直接提到了插图。插图必须就在那里,否则文字就没有意义了。当每一章都是一个无限滚动的网页时,我没有想到这一点,而当我意识到时,已经太晚了。
由于有数百张插图和一千多个代码片段,我给自己提供了三十个巨大的相互关联的垃圾桶包装练习。排版的困难部分是找出如何调整东西以减少死角。有时我会把一个代码片段一分为二。也许在一张图片周围添加一点额外的填充物,使东西在页面上分布得更开。或者把另一张图片挤在一起,使其正好适合于页面。有时我会调整一幅插图,使其更短以适应页面,或更高以占用一些空白空间。
这是本书排版的真正挑战,也是为什么我花了两个月时间才完成所有的章节。
前事和后事
你知道有专业的索引员吗?他们的工作就是为书籍编写索引?他们甚至还写关于如何写索引的书。(人们会推测这些书有真正精湛的索引)。)
我没有雇用那些技术精湛的专业人员之一。相反,我花了两个星期的时间把每一个该死的章节都看了一遍,尽力假装我知道自己在做什么。InDesign对索引的支持实际上是非常好的。你基本上可以只选择一些文本,然后说,"为这个做一个索引条目"。然后它就会收集所有这些条目并为整本书生成一个索引。但实际上添加所有这些条目是一件令人头疼的苦差事。
索引是 "背景资料 "的主要部分--在一本书的主要内容之后的内容。还有 "前言"。你绝对猜不到那是什么。我把标题页、版权页、献词和致谢放在一起。然后我让InDesign为我生成一个目录。
这是一个神奇的时刻。在这一点上,我有了一本完整的书。
这就是里面的样子。
或者说,至少,我有了一个内部。
封面设计
很多作者痴迷于他们的封面,在写作时一直设想着它。尽管有句谚语说,人们确实通过封面来判断书籍,而一个好的封面会带来很大的不同。至少,在小说领域是这样的。在计算机科学领域,从我身边的其他书籍来看,封面的艺术价值似乎不那么重要。我想,当教授说你必须买这本书才能通过这门课时,剪贴画的封面就足够引人注目了。
由于我不是一个可以通过命令获得销售的教授,我在封面设计上花了很多时间。我拍摄照片,所以我认为在封面上放一些详细的东西,让它看起来更有活力。我翻阅了我的数千张照片,试图找到合适的东西。虽然我有一些漂亮的照片,但没有一张感觉是可以作为封面的。它们感觉很随意。
最后我意识到,这本书的视觉语言是那些笔墨插图。所以我画了一个更大更详细的山的插图,我用它来比喻编纂过程。我还手写了一个新的标题。
这是一个真正的字体(Acumin Pro Extra Condensed),但我手绘了一个打印稿,使其具有一些不完美的魅力。我选择了一个调色板,试图给它一种1950年代油印的侦察手册的感觉。
校对校样
现在我真的有了一本书。我把PDF文件上传到KDP,并订购了一份样书。一周后,一个出乎意料的沉重盒子到了。这是我第一次真正明白我写的这本书有多大。直到这时,它还只是数据文件。但看到它装满了一个亚马逊的箱子,我就明白了这个项目的规模,而我所花的时间却无法做到。
所以我有了一本书,但它仍然没有完成。因为排版过程涉及大量的手工劳动。犯错是人之常情,所以现在我不得不进行校对--从字面上看,就是翻看校对,寻找错误。我把它们都用便条做了标记。
我在每张便条上画了一个 "X",以标明我何时对InDesign文件进行了修改。
这时,压力就大了。如果你是一个程序员,那么源码控制和差异在你的工作流程中已经根深蒂固。每当我做了一个改动,我都会想当然地认为我可以在提交中看到差异,以验证我只改动了我想要的东西,而没有其他的改动。
我确实把InDesign的文件放在了Git repo中,但它们是巨大的不透明的二进制文件。而且,InDesign有一个习惯,即使我看起来没有做任何真正的修改,也会对它们进行修改。没有什么比同步各章的所有样式,看到每一个文件都被标记为更改,并想:"我是不是不小心把每一个子弹列表项目向左移动了3点?" 这感觉就像在这个过程中的盲目飞行,在这个阶段我真的想看到到底发生了什么。
我是否通过设计来解决这个问题?你说得很对,我做了。我写了一个Dart脚本,把书的PDF文件,提取每一页,然后生成一个巨大的PNG文件,每一页都平铺在上面。它看起来像这样。
如果你放大来看,每一页都有这么大。
如果你用的是视网膜显示器,这就是100%的放大。
每当我修改InDesign文件并提交时,我就为该提交导出一个PDF,并生成一个平铺图像。然后我写了一个小的Photoshop动作,将其中的两张图片,在任何不同的像素周围画一个大的红色边框。这就是所有校对修改后的样子。
再放大一点。
瓦片图像中没有足够的细节来告诉我到底有什么变化,但红色告诉我哪些页面需要目视检查。我知道我应该感到自豪,比如说,写出一整本关于编程语言的教科书。但是,说实话,我认为我最自豪的是这个愚蠢的小脚本。能够以编程方式验证,是的,除了一个预期的变化之外,这个PDF看起来和上一个PDF一模一样,这真是一种解脱。
电子书也是书
一旦我纳入了所有的校对修正,印刷版就真的完全完成了。但是人们也喜欢阅读电子书和Kindle,所以我也需要制作这些。
同样,编写我自己的构建系统也有帮助。我对它进行了调整,使其能够导出EPUB所要求的过时的XHTML,以及所有奇怪的元数据和清单的东西,并将其纳入其中。几个命令行的调用之后,我有了Kindle和EPUB电子书。我在一堆读者中进行了测试,并调整了CCS,试图在他们所有的渲染器之间找到一个妥协。
更新网站和启动
在这一点上--当我写下这句话时,我们正在谈论过去的这个周末--我有一个文件夹,里面有每个版本的书的所有最终文件。剩下的就是更新该书网站的首页,指出人们可以在哪里得到这些文件。我拍了一些照片,暂时戴上了我的网页设计师的帽子。我试着让它有一个可以容忍的反应。
然后我写了这篇博文。由于我刚刚写了这篇文章,把它放在过去式里很奇怪。但我想它现在已经是过去式了。
明天,我将把文件上传到各个网站和商店。我将更新网站,在它们上线时提供所有这些的链接。一旦商店处理了上传的文件,所有的东西都可以使用,我就会发表这个帖子并更新网站。我将为邮件列表写一份说明,给那么多人发邮件会感到紧张。如果你正在读这篇文章,我已经做了所有这些,这本书真的,真的完成了。
接下来怎么办?
自从我完成最后一章后,人们一直在问我接下来要做什么。现在有一些我认为是亲密朋友的人,在我没有写这本书的时候,他们从来没有认识过我。
许多人问我接下来要写什么,或建议一个主题。我把这理解为一种赞美。他们喜欢我的写作,想要更多。但这也感觉像问一个正在分娩的妈妈是否打算生更多的孩子。我的腿已经在马镫上站了六年了,所以我打算在所有的推拿结束后放松一下,然后再考虑另一个书中的孩子。
我真正打算做的是...不打算做任何事情。这个写作项目一直是一个巨大的自我应用延迟满足的练习。除此之外,大流行病还带来了它自己的一篮子推迟的梦想。我已经变得如此熟练地否认自己和推迟,如此擅长调整我想做的事情,以至于很难再听到它。我不记得我自己的快乐听起来是什么样的。
所以我打算离开一段时间,去寻找我的快乐中心。也许再做一些音乐。也许去钓鱼。也许会花更多时间与朋友和家人在一起(安全地)。我可能会回到我的roguelike的工作上。或者,也许我不会做任何这些事。也许我只是到后院去,像蜥蜴一样无意识地晒太阳。重要的是,我不会决定,直到我觉得喜欢。
我相信最终我将会感到痒,再去做一些更有规模的工作。(但是,如果上帝保佑,我这辈子再也不会在一个项目上花六年时间了)。) 在那之前,我希望你喜欢这本书。尽管做了这么多工作,我在制作过程中还是很有乐趣,而且听到读者喜欢它,总是很高兴。这是唯一让我坚持这么久并使我能够完成的事情。