Cursor 交互 ggplot:字体 API 与 httpgd 视口
font_info(weight=) 与 UseMethod(depth) on NULL:现象、根因与修复
经验来自《中国旱区农业技术发展报告2026》在 Cursor 终端里手工
print()分面图。书稿 knitr 出图不受这两处交互故障影响。可拷贝源:../R/httpgd-warmup.R;版本要求见下文字体栈表。
在 Cursor 集成 R 终端里执行 ggplot(...) 或 print(p),可能先后遇到两类报错。它们看起来像「这张图的几何图层写错了」,实际都发生在打开默认绘图设备或第一帧 grid 视口,与 geom_col / facet_grid 无关。隔离方法:同一对象用 ggplot2::ggsave(..., device = ragg::agg_png);若 ragg 能出图、交互 print() 不能,就不要改分析脚本。
发生情形
Cursor 用户设置 "r.plot.useHttpgd": true,会话监视器把 options(device) 换成:
function(...) {
httpgd::hgd(silent = TRUE)
.vsc$request("httpgd", url = httpgd::hgd_url())
}print.ggplot → grid.newpage() 会走这条设备。knitr 走 dev = "ragg_png"(../R/knitr-ragg.R),不走 httpgd。所以 Quarto html/docx 图正常、终端预览失败,是预期分裂,不是章脚本坏了。
mtcars 散点图若同样报错,即可排除具体业务图。
故障一:font_info(..., weight=)
Error in font_info(font, weight = if (bold) "bold" else "normal", italic = italic) :
unused argument (weight = if (bold) "bold" else "normal")
典型栈:print.ggplot → grid.newpage → httpgd::hgd → unigd::ugd → match_family → font_info(..., weight=)。设备在给 sans / serif / mono 做别名时就失败,names(dev.cur()) 常为 "null device"。
根因:unigd 的 match_family() 已按 systemfonts ≥ 1.3 的 API 传 weight=;项目若锁 systemfonts 1.2.x,font_info() 形参仍是 bold。httpgd / unigd 常由编辑器另装进项目库,不在 lockfile 里,与 knitr 用的 ragg 不是一条链。find.package("svglite") 为空不能否定这条路径:调用方是 unigd。
修复:升字体栈到互相匹配的版本,只钉这几条,不要全量 renv::snapshot():
| 包 | 须达到 | 作用 |
|---|---|---|
| systemfonts | ≥ 1.3.0(已验证 1.3.2) | font_info() 接受 weight= |
| textshaping | 与之配套(已验证 1.0.5) | ragg / unigd 的 shaping |
| ragg | 与之配套(已验证 1.5.2) | knitr ragg_png 的 C API |
renv::install(c("systemfonts@1.3.2", "textshaping@1.0.5", "ragg@1.5.2"))
# 然后只改 renv.lock 对应 Version装完必须退出占用 DLL 的 R 会话再开新会话。验收:
names(formals(systemfonts::font_info)) # 须含 weight
unigd:::match_family("sans") # 应返回族名,如 Arial故障二:UseMethod("depth") 作用在 NULL
Error in UseMethod("depth") :
no applicable method for 'depth' applied to an object of class "NULL"
根因:grid + 交互设备(httpgd / unigd)的已知问题。监视器在 hgd() 之后立刻 .vsc$request() 拉 SVG,第一帧 ggplot 绘制时 viewport 路径变成 NULL,postDraw 里 depth(path) 没有 depth.NULL 方法。ragg ggsave 通常不受影响。
修复:项目 .Rprofile(放在 renv/activate.R 之后)source httpgd-warmup.R:
if (interactive() && file.exists("scripts/book-plot/R/httpgd-warmup.R")) {
source("scripts/book-plot/R/httpgd-warmup.R")
}它做两件事:
registerS3method("depth", "NULL", function(x, ...) 0L, envir = asNamespace("grid")),路径为NULL时不 pop 视口。- 一次性
addTaskCallback:等options(device)已是 httpgd 包装函数后预开httpgd::hgd(),避免「开设备 + 第一张 ggplot」叠在同一帧。选项名bookplot.httpgd_warmed/bookplot.httpgd_tries。
若项目跑 languageserver 等后台会话,在外层再加自己的环境变量判断,避免后台进程也注册回调。现有会话可临时执行第 1 条。
诊断顺序
traceback()
names(dev.cur())
getOption("device")
names(formals(systemfonts::font_info))
packageVersion("systemfonts")
packageVersion("ragg")
find.package("httpgd", quiet = TRUE)
ggplot(mtcars, aes(wt, mpg)) + geom_point()
tf <- tempfile(fileext = ".png")
ggplot2::ggsave(tf, last_plot(), device = ragg::agg_png, width = 11, height = 6)
file.exists(tf)| 观察 | 结论 |
|---|---|
ggsave(ragg) 成功、print() 失败 |
不要改分析脚本;查 device / 字体栈 |
traceback 含 match_family / weight= |
systemfonts 低于 1.3,或与 unigd 代差 |
traceback 含 depth(path) / postDraw |
httpgd 首帧视口;用 httpgd-warmup.R |
| knitr / Quarto 图空白 | 另一件事:Windows png() + simsun;用 ragg_png,再跑 check-fig-png.py |
不要做
- 为交互预览报错去改
geom_*/facet_grid。 showtext_auto()与ragg_png叠用。- 只升
systemfonts、留下旧textshaping/raggDLL。 - 升包后不重启仍占用 library 的 R 会话。
- 把 httpgd 首帧
depth误判成「必须关掉r.plot.useHttpgd」。