【免费下载链接】TinyRetroPad
TinyRetroPad 是一款仅约 2.5 KB 的 Notepad 风格 Windows 文本编辑器,完全用 x86 汇编编写。它的打印功能没有一行自绘 GDI 文字的代码——整个"分页渲染 + 打印"流程都交给 RICHEDIT 控件的一条EM_FORMATRANGE消息完成。本文带你拆解 trpad.asm 中PrintDoc函数的实现原理,看懂零GDI绘图的打印是怎么做到的。
为什么说它是"零GDI绘图"?
打开源码可以看到一个有意思的细节:trpad.asm 中gdi32.inc被直接注释掉了,官方解释是"编辑器不使用 GDI,Rich Edit 的字体通过EM_SETCHARFORMAT设置,无需 GDI 导入"。
换句话说:
- ✅ 屏幕上显示文字?RICHEDIT 控件自己画
- ✅ 打印到纸上?RICHEDIT 通过
EM_FORMATRANGE自己画 - ❌ 自己调用
TextOut/SetTextColor之类的 GDI 函数?一次都没有
这就是本文的核心:把"渲染"这件事整体委托给控件,程序只负责指挥流程。
EM_FORMATRANGE:一条消息完成分页渲染
EM_FORMATRANGE是 RICHEDIT 的私有消息(WM_USER+57,见 trpad.asm)。它的协议非常巧妙:
- 你把一个打印机 DC(设备上下文)和一个FORMATRANGE 结构体发给控件
- 控件按页面大小,把指定字符范围的文字排版渲染到这个 DC 上
- 返回值 = 这一页用掉了多少字符后的位置,也就是下一页的起点
程序只需要拿这个返回值做循环,就能一页一页打完整篇文档——这就是"分页渲染原理"的全部秘密。
页宽页高是怎么算的?
Rich Edit 内部使用twips(1/1440 英寸)作为排版单位。TinyRetroPad 用GetDeviceCaps查询打印机 DC 的四项指标(trpad.asm):
| 查询项 | 含义 | | - | - | |HORZRES/VERTRES| 纸张宽/高(设备单位) | |LOGPIXELSX/LOGPIXELSY| 每英寸像素密度 |
然后做一条乘法除法:页面宽度(twips) = HORZRES × 1440 ÷ LOGPIXELSX,高度同理。这样无论用户选 A4 还是 Letter、打印机 DPI 是多少,页面尺寸都能自动换算正确。
PrintDoc 完整流程拆解
菜单 File → Print 触发后,PrintDoc(trpad.asm)按以下 6 步走完整个打印任务:
第1步:PrintDlgA 拿到打印机 DC
调用系统打印对话框,并带上PD_RETURNDC标志——用户点"确定"后,PRINTDLGA结构的hDC字段里直接装好了一个可用打印机 DC,省去了手动创建逻辑 DC/物理 DC 的繁琐代码。
第2步:StartDocA 声明打印任务开始
填充DOCINFOA(文档名)后调用StartDocA,告诉打印 spooler"一份新文档要来了"。
第3步:准备 FORMATRANGE 结构体
这是"分页渲染"的配置核心(trpad.asm):
hdc/hdcTarget:都填打印机 DCrc/rcPage:上一步换算出的 twips 页面尺寸chrg.cpMin = 0、chrg.cpMax = 全文长度:渲染范围是整篇文档
第4步:StartPage → EM_FORMATRANGE → EndPage 循环
主循环(trpad.asm)每轮做三件事:
StartPage:声明新一页开始- 向控件发送
EM_FORMATRANGE,让它把cpMin..cpMax的文字排进当前页 EndPage:本页结束
第5步:用返回值驱动翻页
EM_FORMATRANGE返回的"下一页起点"写回fmt.chrg.cpMin,与全文长度比较——只要没打完整篇,就继续循环。整个分页算法不到 5 条指令。
第6步:清理资源
文档全部输出后,再发一次长度为 0 的EM_FORMATRANGE用于刷新控件内部的格式化缓存,然后EndDoc结束任务、DeleteDC释放 DC。
如何体验这个打印功能
- 菜单File → Print:菜单 ID 为
IDM_FILE_PRINT(trpad.asm),由WndProc中的命令分发路由到PrintDoc - 菜单File → Page Setup:调用
PageSetupDlgA公共对话框调整纸张与页边距(trpad.asm)
值得一提的是,这套完整打印能力(含打印对话框、分页循环、资源管理)在这个以字节数论英雄的項目里,只花费了固定的 2476 字节基线(增长日志见 trpad.asm),堪称"极小体积下最便宜的打印方案"。
总结:给新手的最短实践路径
| 环节 | TinyRetroPad 的做法 | 复用要点 | | - | - | - | | 拿打印机 DC |PrintDlgA+PD_RETURNDC| 对话框替你建好 DC | | 页面尺寸 |GetDeviceCaps换算 twips | 一次乘法除法,适配任意纸张 | | 排版渲染 |EM_FORMATRANGE| 控件渲染,零 GDI 代码 | | 分页驱动 | 用返回值更新cpMin| 一个 while 循环搞定 |
如果你正在写一个"小而全"的 Windows 文本编辑器,TinyRetroPad 给出的答案很清晰:能不写 GDI 就不写 GDI,让 RICHEDIT 替你排版分页。完整实现可对照 trpad.asm 阅读,背景资料见 README.md。
【免费下载链接】TinyRetroPad
相关推荐
Makepad图形渲染原理:从2D绘图到3D渲染的技术实现
Makepad图形渲染原理:从2D绘图到3D渲染的技术实现 Makepad是一个基于Rust语言的创意软件开发平台,它通过创新的图形渲染技术实现了跨平台的2D和
前端UI组件3D渲染跨平台游戏开发addLegend.js实现原理:图表图例的手绘风格渲染技术
addLegend.js实现原理:图表图例的手绘风格渲染技术 图表图例是数据可视化中连接视觉元素与数据含义的关键桥梁。在xkcd风格的手绘图表库中, addLe
数据可视化前端UI组件gte-micro-openmind常见问题解答:解决使用中的10个难题
gte micro openmind常见问题解答:解决使用中的10个难题 gte micro openmind是一款高效的文本嵌入模型,在使用过程中可能会遇到各
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考