R绘图中文字体显示乱码?showtext跨平台配置终极解决方案
发布时间:2026/10/4 8:36:47 锦皓数字建站

先说说这个经典的“方框问题”。我刚从 base 绘图转 ggplot2 的那阵子几乎每个新手都会遇到图能画出来但所有中文全部变成一个个小方框英文数字倒是好好的。有人管这叫“豆腐块”也有人叫“乱码”实际上严格说它不是乱码而是字形缺失——R 图形设备在绘制字符时找不到能覆盖中文字符的字体就渲染成占位方块。这个问题在不同系统、不同输出格式里表现还不太一样Windows 上最常见macOS 相对好一点Linux 纯命令行环境最折腾。这篇文章我就把解决方案从头到尾捋一遍重点推荐 showtext 这套跨平台方案再补充 Windows 字体注册、cairo 设备、extrafont 等常见替代方法把你可能踩的坑提前说清楚。这篇文章适合刚接触 R 语言绘图、被中文显示折磨过的朋友也适合已经在用 ggplot2 或 base plot 但导出 PDF、PNG 后发现中文缺失的人。核心就一句话你要告诉 R“中文字符该用哪个字体文件来画”剩下的都是围绕这句话展开的工程细节。1. 问题现象与根本原因为什么 R 画图就是不肯显示中文1.1 “豆腐块”是怎么来的R 的绘图系统本质上是把图形指令发送给图形设备device比如屏幕窗口、PDF 文件、PNG 文件等。设备负责把点、线、文字画出来。文字部分听起来简单实际涉及一个完整的字体匹配流程R 先根据你设置的 family 找到对应的字体族再从字体族里找到具体字形文件最后把每个字符映射到字形的索引上。问题出在“字体族”这一步。R 默认的字体族通常是 sans、serif、mono在 Windows 上对应 Arial、Times New Roman、Courier New在 macOS 上对应 Helvetica、Times、Courier。这些西文字体里压根没有中文字形。当绘图时遇到“中文”这两个字符设备去 Arial 里找字形找不到于是画一个空心方块替代。英文能正常显示、中文全是方块原因就在这里。更有意思的是同一份代码在不同设备上表现还不一样。RStudio 的 Plots 窗格、Windows 的窗口设备、PDF 导出、PNG 导出底层可能是完全不同的图形后端。RStudio 窗格用 quartz 或 windows 设备PDF 导出走 pdf()PNG 导出走 png()每个设备对字体的处理机制都不同。这也是为什么你在 RStudio 里画图看着中文正常一导出 PDF 就变方块的根本原因——不是代码问题是设备和字体映射在作怪。1.2 字体文件的格式与加载路径要解决这个问题首先得知道系统里有哪些中文字体、它们放在哪里。Windows 的字体集中在C:/Windows/Fonts/常见的包括simhei.ttf黑体、simsun.ttc宋体、msyh.ttc微软雅黑。macOS 在/System/Library/Fonts/和/Library/Fonts/常见的有PingFang.ttc苹方、STHeiti Light.ttc等。Linux 发行版一般装在/usr/share/fonts/下常见的有文泉驿正黑wqy-zenhei.ttc和思源黑体NotoSansCJK-Regular.ttc。字体文件后缀也值得留意.ttf是 TrueType 字体.otf是 OpenType 字体.ttc是 TrueType Collection一个文件里打包多个字体。R 的 showtext、extrafont 对不同格式的支持不完全一样后面我会具体说。建议你在动手之前先在系统字体目录里确认一下中文字体的确切文件名和路径这一步能省掉后面不少排查时间。2. 解决思路与方案选型先搞懂有哪些路线再决定用哪条2.1 三条主流路线的横向对比解决 R 中文显示问题核心思路无非两种要么让 R 认识系统中文字体要么让 R 在绘图时直接调用字体文件绘制字形。围绕这两种思路社区里沉淀出几套方案。第一套是 Windows 字体注册方案。用windowsFonts()把系统中文字体注册到 R 的字体表里然后绘图时指定family SimHei这类名称。优点是简单直接缺点是只在 Windows 有效换到 macOS、Linux 就抓瞎。第二套是 cairo 图形后端方案。png(type cairo)、pdf(file x.pdf, family Hei)这类写法让 R 调用 cairo 库来渲染图形。cairo 对字体的处理能力比 R 自带的设备强不少配合合适的字体族也能显示中文。但 cairo 的配置比较隐晦中文字体族名在不同系统上不统一容易出现“在 A 机器正常、在 B 机器挂掉”的情况。第三套是 showtext 方案核心思路。showtext 包的原理是绕过 R 自带的字体匹配机制直接用底层字体渲染库把文字画成路径path再嵌入图形里。你只需要在绘图前把系统里的中文字体文件加载进 showtext 的字体库然后全局开启 showtext后面所有绘图代码不需要改动base plot、ggplot2 通吃。这套方案跨平台、跨设备是我目前最推荐的。2.2 为什么我优先推荐 showtext先说我踩过的坑。最早我用windowsFonts(Hei windowsFont(SimHei))解决 Windows 下的问题图是能显示了换了台没有安装黑体的电脑立刻挂。后来用extrafont包导入字体流程繁琐不说在 Linux 服务器上跑起来还老是报错。折腾到最后showtext 用一次就稳定了原因有三个。其一showtext 不依赖系统当前的字体配置只看你加载的字体文件路径。只要字体文件存在不管在哪个平台表现完全一致。其二showtext 能同时作用于 base plot、ggplot2、lattice甚至某些情况下 plotly 导出的静态图也能接管写一次配置全项目复用。其三showtext 对中文、日文、韩文等 CJK 字符支持特别好因为它走的是 FreeType 渲染管线对复杂字形的处理能力远强于 R 默认设备。当然showtext 也不是毫无代价。它通过把文字转换为路径来绘制所以在某些极老版本的 R 上可能有兼容问题另外开启后所有文字渲染都在 R 层面完成大量文字标注时速度会比原生设备慢。不过对绝大多数的数据可视化场景这个性能损失可以忽略不计。3. 一劳永逸的 showtext 集成方案从安装到全自动显示全流程3.1 安装与初始化showtext 在 CRAN 上直接安装即可。注意它有个依赖包sysfonts负责字体文件加载和管理通常会自动装上但稳妥起见我建议两个一起加在 library 里。install.packages(showtext) library(showtext) library(sysfonts)加载完之后最关键的一步是往 showtext 的字体库里注册系统中文字体。font_add()函数有三个核心参数family是你在 R 绘图代码里引用的字体名可以随便起regular是字体文件的绝对路径italic和bold可以指定斜体、粗体对应的字体文件可选。以 Windows 为例我通常这样注册font_add(heiti, regular C:/Windows/Fonts/simhei.ttf) font_add(yahei, regular C:/Windows/Fonts/msyh.ttc) font_add(songti, regular C:/Windows/Fonts/simsun.ttc)macOS 可以换成苹方字体font_add(pingfang, regular /System/Library/Fonts/PingFang.ttc)Linux 用文泉驿或思源font_add(wenquanyi, regular /usr/share/fonts/truetype/wqy/wqy-zenhei.ttc)注册完之后调用showtext_auto()全局开启。这个函数的作用是让 showtext 接管当前会话中所有图形设备的文字渲染。开启后你不需要改动任何绘图代码base plot 的标题、坐标轴、图例ggplot2 的 theme 设置全都自动走 showtext 的字体库。3.2 base plot 场景实操注册好字体后base plot 里的用法就是在绘图参数里指定 family。举个例子showtext_auto() par(family heiti) plot(1:10, main R语言中文标题, xlab 横坐标中文, ylab 纵坐标中文) legend(topleft, legend c(中文图例, series 2), pch c(1, 2))这里的par(family heiti)是全局设置影响后续所有 base plot 的文字。如果只想某个图上用特定字体也可以在具体绘图函数里临时指定family heiti。坐标轴刻度、标题、图例、text()添加的注释文本全部跟着 family 走。另一个细节是main、xlab、ylab这些文本本身只要包含中文就可以正常显示不需要额外编码或转义。如果你发现某个中文字符在图上显示成一个方框优先检查这个字体文件里是否包含该字符——比如生僻字用某些精简字体就可能缺失。3.3 ggplot2 场景实操ggplot2 里的字体体系是通过theme()控制的。最常用的做法是在主题里整体设置字体族这样标题、坐标轴、图例一次性全部生效showtext_auto() ggplot(mtcars, aes(wt, mpg, color factor(cyl))) geom_point() labs(title 汽车油耗分析中文标题, x 重量吨, y 油耗英里/加仑, color 气缸数) theme_bw(base_family heiti) theme(plot.title element_text(hjust 0.5))关键在于theme_bw(base_family heiti)它会把这个主题的默认字体族设为 heiti后面叠加的theme()如果没再单独指定 family就都继承这个设置。如果你想对标题单独使用一种字体、图例使用另一种字体可以分别设置theme( plot.title element_text(family yahei, size 16, face bold), axis.title element_text(family songti, size 12), legend.text element_text(family heiti, size 10) )注意一个常见误解很多人以为开启 showtext 后ggplot2 里就不需要设置 family 了。不是的。showtext 接管的是“给定字体名之后如何把对应字形画出来”这个环节但 ggplot2 仍然需要你告诉它用哪个 family。只有在主题里设了base_familyshowtext 才会把该 family 下注册的字体字形画到图上。所以 showtext 和 family 设置是一个配合关系不是替代关系。如果你想偷懒可以在 R 会话开头把全局 ggplot2 主题统一设好theme_set(theme_bw(base_family heiti))之后所有 ggplot2 图形默认就是这个字体族不需要每个图的 theme 里重复写。3.4 导出图片时的高分辨率设置showtext 在导出高分辨率图片时有个容易忽略的参数DPI。默认情况下showtext 渲染文字的清晰度与图形设备的分辨率是挂钩的showtext.begin()或showtext_auto()会读取当前设备的分辨率来绘制文字。导出 300 DPI 的 PNG 时如果不额外设置可能出现文字边缘发虚或者大小比例不对的情况。建议在导出前调用showtext_opts(dpi 300)显式声明分辨率确保文字与图形设备的像素密度匹配showtext_auto() showtext_opts(dpi 300) png(output.png, width 3000, height 2000, res 300) ggplot(mtcars, aes(wt, mpg)) geom_point() labs(title 高分辨率中文图) theme_bw(base_family heiti) dev.off() showtext_opts(dpi 72) # 回到默认值这里的res 300是 png 设备的输出分辨率showtext 的 dpi 需要与之对齐。很多人在导出后觉得中文字体发虚八成就是这两个参数没对上。4. 平台差异与替代方案不依赖 showtext 的几种真实解法4.1 Windows 字体注册方案如果你只是偶尔画一两次图不想额外装包Windows 上有个轻量方案——windowsFonts()注册系统中文字体。原理是把系统字体名称注册成 R 能识别的字体族windowsFonts(Hei windowsFont(SimHei)) windowsFonts(YaHei windowsFont(Microsoft YaHei)) # base plot par(family Hei) plot(1:10, main Windows中文标题) # ggplot2 ggplot(mtcars, aes(wt, mpg)) geom_point() theme_bw(base_family Hei)这个方案的优点是零额外依赖缺点是只对 Windows 的窗口设备生效导出 PDF 时仍然可能失效。在 Windows 上png()如果不指定type cairo走的是 Windows GDI 设备中文渲染还算可以但如果用小众字体或特殊字形还是可能出现方框。适合救急不适合长期复用。4.2 macOS 与 Linux 下的纯 cairo 路线macOS 用户最常遇到的问题是 Quartz 设备与 PDF 导出的字体不一致。一个相对简单的解决方式是在打开图形设备时显式指定 cairo 后端和字体族# PNG 输出 png(plot.png, width 2400, height 1800, res 300, type cairo, family PingFang SC) # PDF 输出 pdf(plot.pdf, family PingFang SC) # base plot plot(1:10, main 中文标题) dev.off()type cairo让 R 调用 Cairo 图形库渲染它对字体的识别和处理能力比默认设备强。family PingFang SC需要填系统里的中文字体族名而不是字体文件路径这个名称可以通过systemfonts::system_fonts()查看library(systemfonts) system_fonts() | dplyr::filter(grepl(Hei|PingFang|Song|Noto|WenQuan, family, ignore.case TRUE))Linux 上的逻辑类似指定family WenQuanYi Zen Hei或family Noto Sans CJK SC同时使用 cairo 设备。注意 Linux 服务器通常没有额外安装图形界面某些 cairo 相关依赖可能缺失最稳妥的方式还是 showtext因为它自带 FreeType 和字体解析逻辑不依赖系统图形库。4.3 extrafont 包的集成流程extrafont 是另一套名声在外的方案它的思路是把系统字体导入 R 的字体数据库之后绘图时按字体名引用。流程比较繁琐但某些旧项目里还在用值得了解。install.packages(extrafont) library(extrafont) # 首次使用需要导入系统字体耗时可能较长 font_import(pattern SimHei|Microsoft YaHei|Noto|WenQuanYi) # 加载字体信息 loadfonts(device win) # WindowsmacOS 用 quartzLinux 用 cairo # 查看已加载的中文字体 fonts()之后绘图时指定 family注意用的是导入后的字体名par(family SimHei) plot(1:10, main extrafont 中文测试) # ggplot2 ggplot(mtcars, aes(wt, mpg)) geom_point() theme_bw(base_family SimHei)extrafont 有个容易踩的坑font_import()会把系统所有字体都扫进去速度很慢而且某些字体名称在导入时会带后缀比如SimHei可能是SimHei也可能是SimHei Bold需要用fonts()逐一确认。相比之下showtext 的font_add()让我们自己起名、自己指定文件逻辑清晰得多。如果项目已经用了 extrafont倒也不必推翻重来但新项目我更建议 direct 上 showtext。4.4 图形设备差异速查为了让你对“设备影响字体”这件事有更直观的感知我把常见设备的字体处理机制整理成一张表设备类型常用调用字体处理机制中文显示风险屏幕窗口Windowswindows()GDI 字体引擎需要注册字体中等风险屏幕窗口macOSquartz()Core Text一般正常导出需留意RStudio Plots 窗格RStudio 内建依赖操作系统与设备开发环境内大多正常PNG 默认设备png()取决于平台Windows 中文字体易出问题PNGcairopng(type cairo)Cairo FreeType较稳定依赖系统字体库PDF 默认设备pdf()R 内置 PDF 字体中文最容易变方块PDFcairopdf(type cairo)Cairo FreeType配合字体族可正常显示showtext 接管后的各类设备任意FreeType 字形路径化跨平台最稳定看了这张表应该能明白为什么大家反复强调“RStudio 显示正常导出 PDF 变方块”——因为两者根本不在同一条字体处理路径上。showtext 的价值是让所有设备走同一套字体渲染逻辑从根上消除这种不一致。5. 常见问题排查与避坑清单这些都是我实际踩过的坑5.1 常见报错与解决办法我在多个项目里反复遇到类似问题整理成一张排查表基本都是真实案例问题现象根本原因解决办法Windows 下中文全是方框未注册中文字体默认字体不含中文windowsFonts()注册或用 showtext 注册字体文件RStudio 里显示正常导出 PDF 变方框PDF 设备字体映射与屏幕不同showtext_auto() 主题设置 family或pdf(type cairo)报错font family not found in Windows font databaseextrafont 未加载或字体名错误执行loadfonts(device win)确认fonts()返回的准确名称showtext 已开启ggplot2 中文仍为方框主题未设置 familyshowtext 不知道该画哪个字体theme_bw(base_family heiti)或逐项设置 familyLinux 服务器上font_add报找不到字符系统中文字体未安装字体路径不存在安装字体包或在代码里使用服务器上实际存在的字体路径导出的 PNG 中文模糊showtext 的 dpi 与 png 设备的 res 不匹配showtext_opts(dpi 300)与png(res 300)对齐使用msyh.ttc时 showtext 报错某些 ttc 文件读取存在兼容问题换用同字族的.ttf文件或改用黑体.ttf图中部分中文正常、生僻字方块该字体文件不含对应字形换用思源黑体、Noto CJK 等字符覆盖更全的字体代码在 Windows 正常同事 macOS 上中文全方框family 名称在 macOS 上不存在改为 showtext让字体文件路径跨平台统一5.2 一个容易忽略的“字体名称”陷阱很多人会困惑为什么我在图纸里写family SimHeiWindows 上能跑但同样的代码交给 macOS 就不行因为SimHei是 Windows 系统字体注册名macOS 的字体表里根本没有这个名字R 找不到就退回默认字体。这也正是 showtext 的优势所在你用font_add()自己定义heiti这个名字底层挂到simhei.ttf或.ttc文件上然后在代码里统一用heiti换平台时只需要改一处字体路径甚至可以把字体文件放到项目目录下直接写相对路径。我现在做跨团队项目会在项目根目录建一个fonts/文件夹把字体文件放进去然后用相对路径注册font_add(heiti, regular fonts/simhei.ttf)这样团队里任何人 clone 项目后不需要额外安装字体直接绘图就能显示中文。比起依赖个人电脑的字体环境项目内嵌字体文件的维护成本最低。5.3 避免临时抱佛脚的三个习惯踩了无数次坑之后我总结出三个绘图前的好习惯基本能避免这类中文问题再次出现。第一个习惯是在代码开头就把字体环境和全局主题统一配置好。不要等到绘图代码写完再复查而是一开始就把 showtext、字体注册、ggplot2 默认主题这三件事写在脚本最前面后面所有绘图代码天然继承。第二个习惯是注意字体文件的字符集覆盖范围正则表达式多语言文本或分析报告中出现的中文尽量选字符覆盖范围广的开源字体比如思源黑体、Noto Sans CJK尽量避免用精简商用字体防止某些生僻字位缺失。第三个习惯是每次导出正式报告前做一次肉眼检查特别关注坐标轴刻度、图例文字、注释文本这几个高频出错位置。如果你在 R Markdown 或 Quarto 里出报告强烈建议在 setup chunk 里就把 showtext 相关配置写好。还有一个常被忽略的细节RStudio 的图形窗口默认缓存设备状态有时换了字体配置旧图还在显示旧结果。遇到“我明明改了字体图没变化”的情况可以手动点一下 Plots 窗格的刷新按钮或重启 R 会话让设备重新加载。5.4 为什么有时候 Y 轴中文正常、表格里中文却正常还有个容易让人困惑的现象绘图里的中文有问题但用write.csv()导出的表格数据中文完全正常或者在同一个图里标题正常、图例却是方框。这是因为表格导出走的是字符编码路径和绘图字体渲染完全两码事。同一个 R 会话里表格只要能正确读写中文字符串就说明字符数据本身没有问题问题纯粹出在图形设备对文字的渲染环节。而标题正常、图例异常的情况多半是主题设置里对 text 和 legend.text 使用了不同 family或者图例文字的 family 在字体库里没有注册。排查思路很简单把所有文字元素的 family 统一检查一遍确认每个用到的 family 都已经被windowsFonts()、font_add()或 extrafont 注册过了。6. 最终工作流我目前最常用的配置模板最后把我现在项目里最常用的一套配置分享出来你可以直接抄。它兼顾跨平台稳定性、报表输出质量而且维护成本低。# 字体与中文支持全局配置 library(showtext) library(sysfonts) # 注册字体按需保留需要的字体行 # Windows font_add(heiti, regular C:/Windows/Fonts/simhei.ttf) font_add(yahei, regular C:/Windows/Fonts/msyh.ttc) font_add(songti, regular C:/Windows/Fonts/simsun.ttc) # macOS - 如果上面路径不存在可改用下面两行 # font_add(heiti, regular /System/Library/Fonts/PingFang.ttc) # font_add(songti, regular /System/Library/Fonts/STSongti.ttc) # Linux - 可根据实际安装路径调整 # font_add(heiti, regular /usr/share/fonts/truetype/wqy/wqy-zenhei.ttc) # 全局启用 showtext_auto() showtext_opts(dpi 300) # ggplot2 默认中文主题 theme_set(theme_bw(base_family heiti))用这个配置模板base plot 和 ggplot2 都能直接写中文导出 PDF、PNG 也不需要重复改 device 参数。要说一个这些年总结下来的个人体会别在单个图上反复折腾字体花十分钟把项目的字体配置一次性搞定后面所有图都受益。最开始我每次画图都临时指定字体结果每个脚本里都有一段歪七扭八的字体代码改起来特别痛苦。后来统一用 showtext 全局主题整个人的出图效率提升了一大截。另外如果你经常做中文数据可视化建议在系统里装一套思源黑体或者 Noto Sans CJK它中文字形全、开源免费而且在多个系统里都有同名版本搭配 showtext 用起来很顺手。字体文件可以放在项目目录统一管理这样换电脑、换系统都不怕。绘图中文显示这个问题本质上就是一次性的环境配置问题并不是哪段绘图代码写错了。把字体映射这条链路摸清楚把 showtext 这套流程跑通以后再也不会被方块困扰。希望这篇内容能帮你省下当初我花在排查上的那些时间。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。