如何造一台打印机
文章摘要
Nishant Josh(HN 上的 cat-whisperer)写了一篇八百字的硬件折腾记录,讲他如何把一台廉价墨水屏阅读器变成一台 macOS 能直接识别的打印机。整件事只花了他一个晚上。
起因。作者本来打算自己做一块墨水屏显示器,结果在网上看到了 Xteink X3,产品说明的小字里写着”完全可编程”,他就被拿下了。设备到手后他喜欢它的扁平和扎实手感,开始往 CrossPoint 固件里加东西:换开机动画、加了个摇一摇掷骰子的功能、放了个参加旧金山社交活动用的 LinkedIn 二维码。但把内容传上去的流程很烦——要连它的热点,再在浏览器里打开一个简陋的上传页面。他在找更舒服的传输方式时冒出一个念头:既然它长得像纸,它就该表现得像纸——我应该能往它上面打印。
打印机到底是什么。他想要的体验是:在 MacBook 上打开任何东西,按下”打印”,然后在打印机列表里选中这台设备。为此他得搞清楚电脑期待在另一端看到什么,于是一头扎进了 IPP(Internet Printing Protocol)。IPP 让电脑可以询问打印机支持什么、提交文档、再查询任务状态,消息都跑在 HTTP 上——比如 Get-Printer-Attributes 问能力,Print-Job 送任务。
他给自己的”打印机”声明的能力是:单色输出、300 dpi、一份、单面。文档格式他接受 Apple Raster 和 PWG Raster——这意味着由 Mac 负责把文档渲染成像素再发过来,设备只需把结果缩放到屏幕尺寸。纸张声明了 A5 和 Letter,介质类型 stationery,出纸槽叫 face-up。他给这台打印机取名 penguin(企鹅,”配色刚好对”),用 Bonjour 广播一个 _ipp._tcp 服务,广播内容里带上接受的格式和任务提交地址。为了让 macOS 能免驱发现它,他还得加上 _universal 子类型——而 Arduino 的封装没暴露这个能力,他只好直接调用 ESP-IDF 的 mDNS API。
内存才是真正的难题。一页 Letter 在 300 dpi 下是 2550 × 3300 像素,按每像素一字节的灰度算,未压缩约 8.4 MB。而 X3 只有 400 KB RAM,其中 16 KB 还要留给缓存。他要同时跑 Wi-Fi、跑打印服务器,还得接收一整页。在 Wi-Fi 启动、页面图像分配完之后,他只剩下 6.8 KB 堆内存。
他先想到 Linux 的 mmap——能不能用 SD 卡假装自己有更多 RAM?但 ESP32-C3 的内存映射只支持 flash,不支持 SD 卡上的文件。真正的突破是反过来想:能不能把显示屏本身当作存储? 显示驱动本来就为屏幕图像预留了 RAM。他的方案是把进来的页面数据过一条转换流水线:解码像素 → 缩放到适配屏幕 → 抖动成黑白,每处理好一行就直接写进显示屏的帧缓冲对应位置,然后复用那一小块工作内存,一行行推进直到整页完成。在此之前,他一直在额外拼装一整张图像只为了拷贝过去。他的解码器本来就是逐行工作的,他只需要改造缩放器让它也逐行交付。
一开始他让页面像纸从打印机里送出来那样一条条显现,但每次中间刷新要花大约半秒,他改成了整页一次性显示。最终结果:图像缓冲占用从约 113 KB 降到约 62 KB(总共 400 KB),给网络栈的 socket 缓冲腾出了空间。完成的页面会用现有的截图写入器存成 BMP 放到 SD 卡上。
第一次打印。他的 MacBook 上”不知为何”存着一张《无职转生》的漫画样图。他在预览里打开、点打印——打印机列表里真的出现了 penguin。他选中、打印,漫画页在墨水屏上看起来非常好,凭记忆大约一秒就出现了,”到现在还在上面”。
打印服务器跑在阅读器自身上,既可以让它加入某个 Wi-Fi,也可以让它开自己的热点(叫 literate-penguin)。代码在他 fork 的 CrossPoint 仓库里。最后他给自己的”打印机”补了一个俏皮的注脚:打印结果都留在 SD 卡上,可以在阅读器上浏览——所以他的打印机终究还是有出纸盘的,只不过那是个文件夹。
HN 评论精华
这条帖子拿到 447 分、106 条评论,气氛罕见地友善,而且作者 cat-whisperer 几乎回复了每一条实质提问。Brian_K_White 的第一条评论定了调:”太棒了。这太……对了。”andyfilms1 的联想被很多人附和:”我们终于有星际迷航里的 PADD 了!”
最有价值的一条评论来自打印协议圈内人。ValdikSS 给出了一份具体到可以直接照做的改进清单:第一,应该在 media-size-supported 里声明屏幕的精确尺寸——那多半是个非标准的 IPP 名称,所以还要在 media-supported 里以 om_NNNmmxYYYmm 或 oe_NNNinxYYYin 的形式定义,这样电脑会直接按屏幕尺寸排版,设备端根本不需要缩放。第二,PWG Raster 和 Apple Raster 都支持 1 bit/pixel 模式(作者用的是 8 bit/pixel),也就是输入数据量能小到八分之一;做法是把 print-color-mode-supported/default 设成 bi-level 而不是 monochrome,并把 urf-supported 里的 W 选项调成 W1。第三,1-bit 模式下的抖动效果可能很差,他为此给 libcupsfilters 提过增加抖动选项的 PR(不过只对 Linux 有效,”苹果看起来已经放弃 CUPS 了”)。作者回复说这能在转换和抖动阶段省下一大笔内存,会去实现。
darkvision77 补充了一条重要的现实警告:他一年前检查时发现 1bpp 突然只有 Windows 支持——RFC 里 monochrome 是必需的,bi-level 是可选的;他不确定现在情况如何,但从那以后他就一直随身带着 Floyd–Steinberg 抖动实现。
Neywiny 写下了这条讨论的元评论:”这就是我喜欢这里的原因。有人在自己舒适区之外做出了无可辩驳的出色工作,然后另一个人说’我其实就出生在那个区里,我是被它塑造的,这是改进的方法’——而且是以一种正面的方式。”
“为什么不直接打印成 PDF?” 这是被问得最多的问题,joshmarinacci、greazy 都提了。作者的回答一致:在一台只有 400 KB RAM 的设备上渲染 PDF 开销太大,而且需要一堆 ROM 装不下的东西——”Kindle 有很多 RAM,这个没有”。ValdikSS 又一次给出了权威的展开:PDF 是一个极其复杂的标准,光是他随口能想到的就有 4 种字体格式、9 种图像格式、带透明度的混合、每张图可以各用各的色彩空间和抖动算法、页面还能引用其他页面的元素——普通家用打印机绝对不该接受标准 PDF 作为输入,正是为此才有了 AirPrint 和 IPP Everywhere/Mopria 免驱标准下的 Apple Raster 和 PWG Raster,它们是专为打印机设计的简单光栅输入。他还顺带科普了 HP 的”光栅 PDF”(PCLm/PCLms):别被名字骗了,那是完全符合标准的 PDF,但每页除了一整张 JPEG 什么都没有,存在的目的只是让驱动输出能在 PC 上用普通 PDF 阅读器打开以便调试或业务留档。stdatomic 则直接引用了原文第四行——作者要解决的痛点从一开始就是”传文件太麻烦”,不是”没法看 PDF”。
“我以为要学怎么造打印机”。ks2048 的吐槽”干得好,但我还是想知道怎么造一台打印机”被很多人附和,作者俏皮地回了句”我这算环保打印机”。alternativeworl 说得更完整:”我本来以为会学到墨水、打印头之类的门道,结果学到的是’如何造一张表现得像打印机的纸’,反而有趣得多。”这条线下也长出了真正的硬件资源:tdeck 给出了一个笔式绘图仪项目的长清单,d–b 指向一个开源方案,trashb 说他一直有个菊花轮打印机的项目搁着(他所在的地方当年卖得少、现在很难找到能用的),觉得用 3D 打印加简单电子元件做出来是可行的——他还补了一句实用判断:喷墨相当困难,但点阵打印机的 DIY 项目好找得多。a3w 给出了一个完全不同方向的期待:”我想要的是怎么造一台不可追踪的喷墨打印机,出于隐私原因。”(指打印机的追踪点。)
很多人立刻想要这个功能。sowbug 描述了一个特别具体的场景:他一直想要这样的东西来打印马上要做的菜谱——他不想让手机在厨房里沾上油污,所以通常打印到纸上、把油污弄到纸上、然后扔掉。”我不会专门做一个菜谱 PDF 再传到阅读器上,那太麻烦了。但我很乐意在菜谱网页上按 Ctrl-P,然后选中墨水屏作为目标。”maybewhenthesun 提出了一个自然的延伸需求:希望阅读器显示为打印机,而打印的内容实际被存成 PDF 多页文件保存在阅读器上——他现在经常做”打印成 A5 PDF → 拷到阅读器”这两步,希望能从任何能打印的程序一步完成。tapia 指出在 Linux 上用 CUPS 做这类东西相当容易,并给出了他为自己的富士通 Quaderno A4 写的 CUPS 后端;IdiotSavage 在这条下面吐槽了一个反向趋势:打印支持正在过时,至少在 macOS 世界——他最近想快速打印一份 Markdown 文档(想保留样式),结果发现 VSCode、Xcode 和 Obsidian 都不能打印。
设备本身的选购讨论。stavros 说他最近老是看到 Xteink,”我不知道为什么想要,但我有墨水屏瘾,所以我就是需要”,问该买 X3 还是 X4。作者给了个有趣的事实:X3 其实是 X4 的后继型号,唯一的缺点是 X4 有 USB-C 口而 X3 没有。a2dam 说他三款都买过,推荐 X4 Pro——因为它有背光,那是他唯一真正怀念的东西。hamaluik 说他一个多月前买了 X4,零后悔:”听起来像夸张,但它终于真的把我和手机解绑了。在外面无聊的时候,我就把它抽出来接着读。在我的 Kobo 上阅读体验更好,但便携性和——坦白说——那种魅力,是比不了的。”
技术细节的边角讨论。e12e 问这类设备是不是用 DjVu 比 PDF 和光栅格式更合适。namibj 给出了一段关于电子墨水屏物理特性的长回复:为了快速刷新和高帧率、以及页面之间的平滑淡入淡出,这些屏幕只做黑白;它们通常能调出大约 14 级灰度,但要做到不出现严重残影很难,因为像素的翻转响应对温度非常敏感,而屏幕是静态的,所有灰度都得靠精确地在中途打断一次翻转来实现。他的结论是:对于那些达到 300 dpi”门槛”的阅读器上的纯文字内容,直接光栅化成纯 300 dpi 黑白(1 bit/pixel)就够了;DjVu 的价值主要在于超过约 5 bit 灰度的显示器,以及灰度/彩色扫描件的有损压缩,那不是这类阅读器的目标内容。