
【免费下载链接】open-slideA slide framework built for agents.项目地址https://gitcode.com/gh_mirrors/op/open-slide点击查看免费下载导读本文是 open-slide 幻灯片创作技能slide-authoring skill中 webfonts 参考文档的完整展开。它解决一个真实而棘手的问题在 open-slide 这种多页面同时挂载的框架里如何正确地加载网络字体避免每个页面重复注册整组font-face、避免 HMR 热更新后字体失效、避免 CJK 全量字体拖垮 PDF 导出。读完本文你将掌握何时该用系统字体栈、如何以幂等的方式在head中一次性注入字体link与keyframes样式、以及如何用text子集化让 CJK 字体体量骤减。阅读前提本文面向在slides/id/index.tsx中编写页面的场景是 slide-authoring 技能 中references/webfonts.md的姊妹篇。若你尚未了解幻灯片文件契约export default页面数组、1920×1080 画布、design令牌等建议先读 SKILL.md 与 design-system.md。默认选择系统字体栈没有之一原文档的第一条原则也是最重要的一条默认使用系统字体栈并优先它。open-slide 的每个页面渲染进固定的 1920×1080 画布正文与展示字体都由design常量里的fonts.display/fonts.body决定在 starter 模板 中二者都被写为system-ui, -apple-system, sans-serif——这就是系统字体栈优先的落地形态。只有当幻灯片真正需要网络字体时才加载原文档给出了两类典型场景品牌字体brand font公司或产品指定的展示字体系统栈无法替代系统覆盖差的语种CJK中日韩、泰文Thai、阿拉伯文Arabic等在部分平台缺字需要专门字体兜底。换言之webfont 是按需购买的增强而不是默认配置。每一次加载都意味着额外的网络请求、解析开销和 PDF 导出等待能省则省。核心铁律只在head加载一次绝不放进页面组件这是整篇文档的骨架。open-slide 的架构决定了它和其他单页应用截然不同每一页都是同时被挂载的——缩略图轨道thumbnail rail、总览网格overview grid以及 PDF 打印根节点PDF print root会在同一时刻渲染所有页面。于是如果你在某个Page组件内部渲染一个styleimport/style或link那么每挂载一次页面整组font-face就会被注册一次。页面数量一多重复的字体声明成倍增长浏览器会去重复下载、重复解析。正确的注入位置是index.tsx的模块顶层module top level——即文件开头、任何组件与 effect 之外的位置用顶层语句执行一次注入。原文档给出了完整范式const FONT_HREF https://fonts.googleapis.com/css2?family...displayswap; const FONT_LINK_ID osd-webfont-q2-roadmap; // suffix this slides folder id if (typeof document ! undefined) { let link document.getElementById(FONT_LINK_ID) as HTMLLinkElement | null; if (!link) { link document.createElement(link); link.id FONT_LINK_ID; link.rel stylesheet; document.head.appendChild(link); } if (link.href ! FONT_HREF) link.href FONT_HREF; }注意typeof document ! undefined守卫——它让这段代码在 SSR / 构建期环境Node 中没有document下也能安全求值避免引用错误。元素 id 必须键到幻灯片osd-webfont-slide-id原文档特别强调了 id 命名规则把元素 id 键到幻灯片本身例如osd-webfont-q2-roadmap后缀是该幻灯片目录的 kebab-case id。原因是 home 页会一次性挂载来自所有幻灯片的页面。如果大家共用一个泛化的 id比如所有人都用osd-webfont那么第一个幻灯片注入的link会被后续幻灯片的注入逻辑找到并复用——结果就是第一个幻灯片的字体声明压制了其他所有幻灯片的字体后者的字体永远加载不出来。每个幻灯片用自己唯一的 id注入逻辑互不干扰各自管理自己的字体。这一点与 open-slide 的一个幻灯片 一个slides/id/目录 一个index.tsx的文件契约是配套的slide-id在整个仓库内唯一因此由它派生的 DOM id 也是唯一的见 SKILL.md 的文件契约小节。create-or-update而不是 create-if-missing原文档的第二条铁律针对HMR热模块替换open-slide 的 dev server 会在你编辑index.tsx时实时更新浏览器SKILL.md 的 Runtime behavior 明确列出 editindex.tsxand the browser updates live。问题在于HMR 会重新执行模块但不会移除上一次注入到 document 中的link/style。于是如果你写的是if (!document.getElementById(id)) { … }这种仅创建、缺则补的守卫那么第一次加载创建并注入link字体生效你修改了字体 URLHMR 重跑模块守卫发现link已经存在跳过创建——新的字体 URL 永远不会生效只有完全刷新浏览器full reload才能看到改动。正确的模式是create-or-update创建或更新找不到就创建找到了就对比内容并更新。这正是原文档示例中if (link.href ! FONT_HREF) link.href FONT_HREF;这一行的意义——它让 HMR 后字体 URL 的变更能即时同步到已存在的link上。同一范式适用于注入stylekeyframes同样的 create-or-update 模式适用于任何你注入的style内容尤其是keyframes动画。原文档给出完整示例const STYLE_ID osd-styles-q2-roadmap; const css keyframes rise { from { opacity: 0 } to { opacity: 1 } }; if (typeof document ! undefined) { let style document.getElementById(STYLE_ID); if (!style) { style document.createElement(style); style.id STYLE_ID; document.head.appendChild(style); } if (style.textContent ! css) style.textContent css; }这里的关键是if (style.textContent ! css) style.textContent css;——以文本内容而非是否存在为更新依据。这样你在 HMR 中修改动画关键帧时样式会即时覆盖旧值同时因为比对的是内容重复执行同一注入也不会造成无意义的 DOM 写入。从源码结构看style注入同样遵循与link一致的设计哲学以模块顶层执行 唯一 id 内容比对保证多次执行幂等。两段代码可以提炼为同一心智模型先找、没有则建、有则比对并更新。只列出你实际用到的字重原文档提醒每个额外字重都会成倍增加font-face规则的条数。Google Fonts 的 css2 端点按字重/风格weight/style分别下发字体文件familyInter:wght400;700;900意味着三个独立的font-face块、三个或更多含 unicode-range 子集字体文件。在 open-slide 的上下文里这直接与 SKILL.md 的视觉方向 呼应展示字体用重字重800–900正文用常规字重400–500。据此一个典型 deck 只需要 2–3 个字重——在 URL 里只列出这些而不是图省事拉下整族。比如https://fonts.googleapis.com/css2?familySpaceGrotesk:wght400;700displayswap如果你在font-weight上引用了未加载的字重浏览器会做 faux bold / faux italic 的合成渲染观感劣化而多加载的字重只是白占带宽。按需取用两头都不亏。CJK 字体很大unicode-range子集与text定点子集这是原文档在实操层面最有价值的部分也是 open-slide 中 CJK deck 最容易踩的坑。为什么 CJK 字体体积失控一个 Google Fonts 的 CJK 家族会注册数百条unicode-range子集 face原文档给出的量级是 Noto Sans TC ≈105 个子集 × 每个字重。这是因为 CJK 字符集动辄数千到上万字字体厂商按 Unicode 区间把字体切碎成大量子集文件浏览器按需下载其中命中的子集。逻辑上这是按需的优化但在 open-slide 里有一个叠加效应PDF 导出会在打印前等待字体。源码里packages/core/src/app/lib/print-ready.ts的waitForFonts()直接await document.fonts.ready见 print-ready.ts而 slide-preload-layer.tsx 在挂载完页面后同样等待document.fonts.ready才宣告渲染完成。注释明确指出document.fonts.ready会等待每一个 in-flight 的 face而.load()会无视unicode-range强制下载全部 face——因此绝不能对 CJK 族调用face.load()否则数百个子集会全量下载标签页直接卡死或崩溃。这是一条从源码注释里可以确证的硬约束print-ready.ts。换句话说只要页面文本触发了 CJK 子集的下载document.fonts.ready就要等它们全部就绪若文本覆盖广、子集多导出前的等待时间会肉眼可见地拉长。CJK 字体大不只是下载问题还是导出延迟问题。用textunique chars请求单 face 定点子集原文档给出的解法非常优雅如果 deck 的文本是固定的就加上textunique chars参数让 Google Fonts 返回一个仅包含这些字符的微型子集而不是整套unicode-range切片。// 假设页面里只会出现这些中文字符 // 一键部署演示成功报告季度目标 const FONT_HREF https://fonts.googleapis.com/css2?familyNotoSansTCtext%E4%B8%80%E9%94%AE%E9%83%A8%E7%BD%B2%E6%BC%94%E7%A4%BA%E6%88%90%E5%8A%9F%E6%8A%A5%E5%91%8A%E5%AD%A3%E5%BA%A6%E7%9B%AE%E6%A0%87displayswap;使用要点unique chars指去重后的字符集合把所有页面可能出现的 CJK 文本拼起来、去掉重复字符再 URL 编码可以用encodeURIComponent生成不要贪心把整段文案全塞进去——只包含实际渲染会用到的字形缺一个字符那个字形就会回退到系统字体观感上冒出一个异类字体子集化后的结果是单个或极少数face与全量 CJK 的数百个 face 形成数量级差异document.fonts.ready的等待时间也随之从数百个文件降为一两个文件。补充一条实用技巧text子集不限于 CJK。任何只用到少量独特字符的字体logo 式西文字体、图标字形都可以用它显著瘦身。原文档聚焦 CJK 是因为它体量最大、收益最明显。别名框架自带的__gfonts代理open-slide 的 dev server 内置了一条 Google Fonts 代理路由__gfonts实现在 gfonts.ts由 api-plugin.ts 挂载提供两个端点GET /__gfonts/search?qlimit搜索 Google Fonts 目录公开元数据无需 API key返回家族、分类、字重变体、流行度排序GET /__gfonts/download?familyvariant从 gstatic 下载单个字重为单文件字体ttf/otf/woff/woff2。该代理在 Assets 管理器中承担字体导入前端通过 assets.ts 的searchGoogleFonts/fetchFontAsFile调用它把搜索结果导入为幻灯片本地资产资产列表里的字体预览FontPreview则用new FontFace(family, url(...))动态加载并通过document.fonts.add()注册见 asset-view.tsx。对 you 而言这条代理的存在意味着与其在index.tsx里手写link外链 Google Fonts更open-slide 原生的做法是走 Assets 面板把字体下载为本地资产——本地文件避免了运行时外链的跨域、可用性和版本漂移问题同时也规避了本文所讲的head注入复杂度。webfonts 参考文档的link注入范式则适用于确实要用运行时外链品牌字体、CSS2 子集化、或不便入库的字体的场景。两条路径并行不悖能入库的入库必须外链的按本文范式注入。与框架机制的耦合为什么这些细节是硬规则以上所有规则都不是洁癖而是与 open-slide 的运行时行为深度耦合的结果可以逐一在源码中定位规则框架机制源码证据只在模块顶层注入一次home 页、缩略图轨道、总览网格、PDF 打印根节点会同时挂载所有页面slide-preload-layer.tsx 按序批量挂载页面并等待document.fonts.readyid 键到幻灯片 idhome 页一次挂载所有幻灯片的页面泛化 id 会互相覆盖SKILL.md 的 slide id 规则create-or-updateHMR 重跑模块但不清理已注入的link/styleSKILL.md 的 HMR 行为不调用face.load()于 CJKdocument.fonts.ready等待所有 in-flight face.load()无视unicode-rangeprint-ready.ts换句话说webfonts 的每一条规则都对应着 open-slide 一个特定的运行时行为。遵循它们你的字体在任何入口home、present、PDF都表现一致违反任何一条症状都是某个入口字体错乱——而且往往是静默的错乱肉眼不易归因。自检清单与反模式原文档属于 slide-authoring 技能的references/体系其规则在执行上通过技能自检清单落地。结合 SKILL.md 的 Self-review 清单在使用 webfont 的幻灯片上应确认字体link/style只从index.tsx模块顶层注入未出现在任何Page组件内元素 id 为osd-webfont-slide-id/osd-styles-slide-id对应当前幻灯片目录 id不与其它幻灯片冲突注入逻辑是 create-or-update无则创建有则比对href/textContent并更新HMR 后改动即时生效URL 只列出实际使用的字重展示字体 800–900、正文 400–500CJK 字体在文本固定时使用了text定点子集未让浏览器下载全量 unicode-range 子集没有对 CJK 字体调用face.load()它会无视 unicode-range 强制全量下载。反模式速查违反即踩坑❌ 在Page组件内渲染link/style——每页重复注册整组font-face❌ 用泛化 id如osd-webfont——第一个幻灯片的字体压制所有其它幻灯片❌ 只写if (!document.getElementById(id))的创建守卫——HMR 后改动要到全量刷新才生效❌ 拉取整族字重——font-face规则成倍膨胀拖慢加载与导出❌ 对固定文本的 CJK 页面不加text子集——数百个子集 face 拖垮document.fonts.readyPDF 导出卡顿。小结在 open-slide 里加载 webfont本质是在回答三个问题在哪里注入、如何保证幂等、如何控制体量。答案是模块顶层注入一次、create-or-update 守卫、按需字重 CJK 定点子集。这三板斧分别对应框架的三条底层事实——多页面同时挂载、HMR 重跑模块、PDF 导出等待document.fonts.ready。把这三条事实记在脑子里webfonts 就再也不会是玄学配合__gfonts代理的本地资产路径你的幻灯片可以在任何字体、任何语种下都保持一致的渲染与导出体验。赞分享【免费下载链接】open-slideA slide framework built for agents.项目地址https://gitcode.com/gh_mirrors/op/open-slide点击查看免费下载相关推荐open-slide 幻灯片创作完全指南从文件契约到 Morph 过渡的源码级实践open slide 幻灯片创作完全指南从文件契约到 Morph 过渡的源码级实践 open slide 是一个为 Agent 而生的幻灯片框架A slid如何快速上手AI演示文稿框架open-slide从零到第一份幻灯片完整入门教程如何快速上手AI演示文稿框架open slide从零到第一份幻灯片完整入门教程 open slide 是一个 专为 AI Agent 打造的演示文稿框架 你open-slide CLI 快速上手用 open-slide/cli 搭建 Agent 驱动的 React 幻灯片工作区open slide CLI 快速上手用 open slide/cli 搭建 Agent 驱动的 React 幻灯片工作区 open slide/cli上一篇DLSS Swapper终极指南免费一键智能切换游戏DLSS版本提升显卡性能45%下一篇JetBrains IDE无限试用终极指南ide-eval-resetter完整解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考