打印与PDF导出
打印要的是 矢量仍在矢量。先把整页转 PNG 再打印,再抱怨发糊,是自己把文档投影成了不够的像素。世界单位用 mm/pt 或「CSS px at 96dpi 再换算」;相机是这一页的 viewBox,不是屏幕上的漫游相机。
1. 场景怎么切
文档:与编辑器同一棵树(已剥 chrome)
页面:@page 尺寸 + 每页 viewBox(世界裁切矩形)
打印层:CSS print,直接印 inline SVG
PDF 层:矢量写入(自己生成 PDF 操作符,或打印到系统 PDF)
光栅例外:照片、已烘焙滤镜、必须位图的签名
必须是节点:线稿、图标、文字(若 PDF 要可选中,还要嵌入字体或改走 PDF text 对象)。图表坐标轴、流程图节点同理。
可以光栅:位图照片、阴影/滤镜的确认稿、超复杂重复图案。光栅按 物理 dpi 算像素,不要按屏幕 DPR 偷懒。
export interface PrintPage {
/** 世界坐标下这一页看到的矩形 */
view: { minX: number; minY: number; width: number; height: number };
/** 纸面 mm */
paper: { widthMm: number; heightMm: number };
}
export function pageViewBox(page: PrintPage): string {
const { minX, minY, width, height } = page.view;
return `${minX} ${minY} ${width} ${height}`;
}
export function pngPixelsForPrint(widthMm: number, dpi: number): number {
return Math.max(1, Math.round((widthMm / 25.4) * dpi));
}
屏幕预览可以 96dpi;送印 300dpi。同一 viewBox,backing store 变大。不要把屏幕上那张 2x PNG 拿去印刷。
打印相机按页 meet 进纸面,不要沿用画布漫游 viewBox。preserveAspectRatio 见 响应式。chrome(手柄、网格)必须在克隆里剥掉,与 编辑器 同一条 serialize 纪律。
2. 路径
| 路径 | 适用 |
|---|---|
window.print() + print CSS | 内部工具、浏览器里看一眼;字体和分页靠 CSS |
| 生成 PDF(矢量 path + 嵌入字体) | 要稳定分页、可发客户、可归档 |
| SVG → PNG → PDF | 仅当整页已是照片或法律要求位图;默认不要 |
foreignObject 里的 HTML 打印引擎差异大,见 SMIL / foreignObject。能不用就不用:文字回 SVG text 或 PDF 文本。
多页:每页一个 viewBox 裁切矩形,或打印时把大图拆成多张 <svg>。不要指望一张无限画布 viewBox 自动分页。
导出 PDF 时颜色:屏幕 sRGB 与印刷 CMYK 不是一回事。产品若承诺色准,不要假装 SVG fill="#f00" 就是专红。
@media print 里隐藏 DOM chrome,只留文档 SVG。不要靠用户「系统打印时自己别选页眉」。
3. 不适用
- 用屏幕相机直接打印:用户正盯着画布角落,纸上会裁成那一块。打印相机按页重新 fit。
- 为了「清晰」先 4k PNG:矢量文字变像素,文件暴涨,缩放仍糊。
- 依赖 SMIL / CSS 动画出现在纸上:打印取的是某一静态帧,要先把状态写进属性。
4. 失败形态
| 症状 | 原因 |
|---|---|
| 纸上发糊 | 先转了低分辨率 PNG |
| 左右被裁 | viewBox 与纸比例不同且 slice,或没设 preserveAspectRatio |
| 缺字 / 方框 | 系统 PDF 没嵌入字体;或文字在 DOM overlay 打印时被 display:none |
| 只有图没有线 | 线在 chrome 层被剥错,或在 canvas 预览没写回 |
| 颜色和屏幕差一截 | 当了 sRGB 印刷,没有转换策略 |
权威资料
- CSS Paged Media
- SVG 2 — Coordinate Systems
- PDF 1.7(归档以 ISO 32000 为准)
核对日期:2026-08-26