"浏览器屏幕上看的好好的,为什么打印出来就变了?"
这句话几乎每个做过打印需求的前端开发者都说过。字体被替换、字号失控、图标变方框,这些问题看似玄学,背后却有清晰的技术原因。本文将从打印需求中常遇到的三个问题来解释其背后的原理和对应的解决方法。
字体被替换-为什么打印出来不是指定的字体?
**现象:**在CSS中命名设置了font-family: "PingFang SC", sans-serif;打印或导出PDF时,却变成了其他字体。
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
p{
font-family: "PingFang SC", sans-serif;;
}
</style>
</head>
<body>
<p>字体</p>
</body>
</html>

根本原因:浏览器在触发打印或者另存为PDF时,会按照以下顺序处理字体:
1.检查Web字体
如果页面通过@font-face加载了字体,浏览器会尝试将这些字体数据嵌入到生成的PDF中,这是最可控的方式。
2.回退到系统字体
如果没有web字体,或者字体嵌入失败,浏览器会去用户的操作系统中查找指定的字体(如"PingFang SC")。
3.使用后备字体
如果系统里也找不到对应字体,浏览器只能使用自己的默认字体,例如Chrome在Windows上默认是Times New Roman。
而在第二步中,系统字体不等于可用字体,即使用户电脑上安装了指定字体,浏览器在生成PDF时可能也无法使用,原因涉及三个层面。
4.字体文件的嵌入权限(fsType标志位)
每个OpenType/TrueType字体文件头部都会包含一个fsType字段,由字体设计方设定,控制允许嵌入的行为:
| fsType值 | 含义 |
|---|---|
| 0x0000 | 可自由嵌入与安装 |
| 0x0002 | 禁止任何形式 嵌入 |
| 0x0004 | 仅允许预览/打印,不可提取 |
| 0x0008 | 可编辑嵌入(允许嵌入但不可安装) |
当fsType为0x0002或0x0004时,浏览器会遵守字体授权协议,拒绝将字体数据写入PDF,这也是商用字体(如方正、汉仪系列)在打印时被频繁替换的核心原因。
1.浏览器的字体子集化策略
即使字体允许嵌入,为了控制PDF体积,浏览器通常只嵌入页面实际用到的字符子集,而非完整字体文件。这在绝大多数场景下都是合理的,但会带来一个隐患:如果后续在PDF中编辑文字,新输入的字符可能因不在子集内而无法正常显示。
2.PDF阅读器的字体替换
即使浏览器成功嵌入字体,最终渲染效果仍然取决于PDF阅读器。部分阅读器(尤其是轻量级的移动端阅读器 )会忽略嵌入字体,优先使用本地字体,导致"最后一公里"的字体替换。
因此,系统字体能否出现在打印结果中,受字体fsType授权、浏览器嵌入实现、PDF阅读器三重因素制约,任一环节失守都会触发字体回退。
**解决方案:**通过@font-face加载允许嵌入的Web字体。
Python
@font-face {
font-family: 'MyPrintFont';
src: url('./fonts/myfont.woff2') format('woff2'),
url('./fonts/myfont.woff') format('woff');
/* 确认字体授权允许嵌入 */
}
@media print {
body {
font-family: 'MyPrintFont', serif;
}
}
将文件字体随项目部署,浏览器打印时会将Web字体直接嵌入PDF,彻底绕开系统字体的权限问题。
字体大小失控-为什么打印出来的字号和屏幕上不一样?
现象:屏幕上设置了font-size:16px,打印出来可能会出现字体要么小的难以看清,要么大的溢出纸张边缘。
**根本原因:**屏幕与纸张的度量体系不兼容。
| 单位 | 适用场景 | 物理含义 |
|---|---|---|
| px(像素) | 屏幕显示 | 相对单位,实际尺寸取决于设备DPI |
| pt(磅) | 印刷排版 | 绝对单位,1pt=1/72英寸(≈0.353mm) |
| mm/cm | 印刷排版 | 绝对单位,与物理尺寸严格对应 |
当使用px定义打印字号时,浏览器需要做一个跨体系换算:
CSS规范定义:屏幕渲染时1px = 1/96英寸,因此理论上12pt = 16px。
但在 打印时,浏览器还会叠加页面缩放比例和纸张适配逻辑进行二次调整。而不同浏览器的打印缩放实现存在差异,导致最终结果并不统一。这就是px字号在打印场景下失控的根源,它在依赖一个并不可靠的单位换算路径。
**解决方案:**在@media print中,明确使用印刷物理单位。
SQL
@media print {
body {
font-size: 10.5pt; /* 国标正文字号:五号字 */
}
h1 { font-size: 16pt; } /* 三号字 */
h2 { font-size: 14pt; } /* 四号字 */
h3 { font-size: 12pt; } /* 小四号字 */
/* 或使用 mm 单位,更直观 */
/* body { font-size: 3.7mm; } */
}
图标字体失踪-为什么打印出来变成了方框?
**现象:**屏幕上正常显示的FontAwesome图标,打印出来成了□或者完全空白。
**根本原因:**图标字体的双重脆弱性。
1.字体嵌入失败(同问题1)
图标字体本质上是字体文件,同样受fsType授权限制。如果图标字体未能嵌入到PDF,打印设备就无法获得这个码位对应什么形状的信息。
2.私有区Unicode的天然缺陷
图标字体的工作原理是将Unicode私有使用区(PUA,U+E000-U+F8FF)内的码位映射为图标字体。例如FontAwesome中,U+F004被映射为心形图标。而当PDF阅读器或打印驱动遇到U+F004这个码位时,会按照以下逻辑处理:
3.查找嵌入字体中是否有该码位的字形定义
- 1.↓ 无(字体未嵌入 / 子集化丢失)
5.查找系统字体中是否有该码位
-
- ↓ PUA 码位在系统字体中通常无定义
6.显示"缺失字符"占位符 → □ 或完全空白
PUA 码位本就不属于任何标准字符集,一旦字体信息丢失,系统完全没有"备用字形"可以回退,这就是为什么图标字体的"失踪"比普通文字更彻底。
**解决方案:**根据项目情况选择合适的方案。
方案一:SVG图标(推荐,最可靠)
XML
<!-- 直接内联 SVG,完全绕开字体问题 -->
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
aria-label="首页" role="img">
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
</svg>
方案二:打印时切换为文字(最快实现)
JavaScript
/* 打印时隐藏图标,显示文字 */
@media print {
.icon { display: none; }
.icon-label { display: inline !important; }
}
<span class="icon" aria-hidden="true"><i class="fas fa-home"></i></span>
<span class="icon-label" style="display: none;">首页</span>
方案三:使用标准 Unicode 符号(适用于简单场景**)**
Unicode 标准字符(Emoji 或特殊符号)有完整的系统字体支持,无需担心 PUA 问题:
HTML
<!-- 标准 Unicode,系统字体均有定义 -->
<!--Emoji 在打印时可能变为彩色或黑白,各平台渲染也有差异,建议测试后使用。-->
<span>⚠️ 警告</span>
<span>✓ 完成</span>
<span>→ 查看详情</span>
屏幕与纸张,本质上是两套不同的渲染体系。一个以像素为单位、依赖设备 DPI,另一个以物理尺寸为单位、受字体授权约束。前端开发者长期浸泡在屏幕世界里,在碰到打印需求时往往会对这种"体系切换"感到措手不及。字体替换、字号失控、图标失踪这三个问题,占据了日常打印"翻车"场景的绝大多数。理解它们背后的技术成因,能够快速定位问题根源,而不是在属性里盲目试错。