ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Skia Shaped Text 设计文档解读:从 TextBuilder 到 SkShaper 的文本整形 API 设计

Skia Shaped Text 设计文档解读:从 TextBuilder 到 SkShaper 的文本整形 API 设计 图形学图像处理【免费下载链接】skiaSkia is a complete 2D graphic library for drawing Text, Geometries, and Images.项目地址https://gitcode.com/gh_mirrors/skia1/skia点击查看免费下载本文围绕 Skia 仓库中的 Shaped Text 设计提案text_shaper.md展开。该文档提出了一套面向 Web 的低级文本整形shaping对象模型通过ParagraphBuilder → shape() → format()的两步式流程把富文本输入处理为整形结果并暴露出字形、位置与文本区间映射等原始数据。读完本文你将理解该设计的目标受众、核心接口WebIDL 定义、结果访问模型以及它与 Skia 源码中真实存在的SkShaper引擎之间的对应关系——这正是浏览器把 CoreText/DirectWrite 级别的国际文本能力暴露给 Web 应用的底层思路。1. 为什么需要独立的文本整形层概述文档Text API Overview首先点明了背景2D 图形库中原语矩形、路径、图像、文本通常共享一套绘制构造变换、裁剪、颜色、混合、色彩空间但文本有一个与绘制无关、却异常复杂且昂贵的处理步骤——Shaping整形。原生框架早已为此单独设立 APICoreGraphics 对应 CoreTextDirect2D/DirectWrite 对应 DirectWrite而Skia 对应 SkShaper。本提案正是拥抱这一模式把这些能力暴露给 Web。复杂性的来源在概述文档中用一句话举例说明The office 大楼 is in München.——ffi可能触发连字ligature、ü可以是单一字形也可以由u加变音符组合、中文词可能需要与前后完全不同的字体。从字符序列到该从哪些字体里取哪些字形、按什么顺序和位置摆放就是整形的全部工作量。文档给出的定义值得记住Typeface字体给定排印风格含可变字体设置 variation settings下的一个自洽形状集合通常存放在单个 OpenType 文件/blob 中Glyph字形字体内最小的独立绘制元素用从 0 开始的索引标识。整形决定了需要哪些字形、什么顺序、什么位置而本文档的核心贡献就是把typeface 已定位字形这一结果以何种对象模型暴露给客户端。2. 目标受众与设计原则设计文档明确了目标受众与三条原则原文逐条继承目标受众已经选择用 canvas2d、WebGL 或其他方式渲染内容、但仍需要浏览器内置的强大国际文本整形与布局服务的 Web 应用。对 canvas2d 而言现有文本能力不足以支撑交互式文本编辑、高性能渲染与动画——缺失的正是整形结果这类低级数据。JavaScript 框架。提案深受桌面/移动原生平台成功 API 的影响追求类似的控制力与性能因此框架可以在此之上构建更友好、约束更强的高层封装。核心决策不扩展现有的fillText()而是提出显式的两步模型——先把富文本输入处理成整形结果shaped results再把结果暴露给客户端由客户端自行决定如何绘制、编辑或消费。三条原则命令式imperative、对 JavaScript 友好的文本表示输入只包含整形与度量所需的最小信息装饰颜色、下划线、阴影、特效明确不在规范之内——因为这些随渲染技术乃至客户端想象力变化极大。3. 两阶段调用序列Shape 与 Format 分离为了最大化复用与效率从富文本描述到最终排版的流程被拆成若干阶段每个阶段完成特定处理同时充当下一阶段实例的工厂。TextBuilder、ShapedText、FormattedText三个对象按序使用const builder new ParagraphBuilder(font-fallback-chain); const shaped builder.shape(DOMString text, sequenceTextBlock blocks); const formatted shaped.format(double width, double height, alignment);Shape 阶段对给定文本执行整形连字替换、字形回退、双向处理等得到与具体字形绑定的结果此阶段不关心排版宽度。Format 阶段在给定宽度/高度/对齐方式下对整形结果做断行与排版。这种分离意味着若只是窗口尺寸变化需要重新断行可以复用同一份ShapedText反复调用format()无需重新整形——这是面向高性能动画场景的关键设计。3.1 输入对象模型WebIDLTextBlock是一段文本的块描述符目前有两个特化且设计上允许未来新增特化而不破坏整体设计interface Typeface { // Number or opaque object: Whatever is needed for the client to know exactly // what font-resource (e.g. file, byte-array, etc.) is being used. // Without this, the glyph IDs would be meaningless. // // This interface is really an instance of the font-resource. It includes // any font-wide modifies that the client (or the shaper) may have requested: // e.g. variations, synthetic-bold, … // // Factories to create Typeface can be described elsewhere. The point here // is that such a unique identifier exists for each font-asset-instance, // and that they can be passed around (in/out of the browser), and compared // to each other. }; interface TextBlock { unsigned long length; // number of codepoints in this block }; interface InFont { attribute sequenceTypeface typefaces; // for preferred fallback faces attribute double size; attribute double scaleX?; // 1.0 if not specified attribute double skewX?: // 0.0 if not specified (for oblique) attribute sequenceFontFeature features?; // additional attributes for letter spacing, etc. }; interface FontBlock : TextBlock { attribute InFont font; }; interface PlaceholderBlock : TextBlock { attribute double width; attribute double height; attribute double offsetFromBaseline; }; interface ShapedTextBuilder { constructor(TextDirection, // default direction (e.g. R2L, L2R) sequenceTypeface?, // optional shared fallback sequence (after TextBlocks) ...); ShapedText shape(DOMString text, sequenceTextBlock); };要点解读Typeface是字体资源实例而非字体名。文档特别强调没有这个唯一标识glyph ID 毫无意义——因为字形索引只在具体字体文件内有效。它是可传递进出浏览器、可比较的不透明句柄且包含字体级修改可变字体轴、合成粗体等。InFont.typefaces是首选回退链一个块可以声明多个候选字体ShapedTextBuilder构造函数还可传入一个共享回退链位于各TextBlock自己的回退链之后。这与 SkiaSkShaper::Make(sk_spSkFontMgr fallback)中接受 fallback 字体管理器的模式一脉相承见 SkShaper.h。PlaceholderBlock为占位符如图片、emoji 自定义渲染区提供宽高与相对基线的偏移——这让文本中混排非字形元素成为一等公民。3.2 一个完整的最小示例文档给出的三块文本示例值得完整保留它是该 API 的标准用法样板const fontA new Font({family-name: Helvetica, size: 14}); const fontB new Font({family-name: Times, size: 18}); const blocks [ { length: 6, font: fontA }, // Hello { length: 5, font: fontB }, // text { length: 6, font: fontA }, // world. ]; const shaped builder.shape(Hello text world., blocks); // now we can format the shaped text to get access to glyphs and positions. const formatted shaped.format({width: 50, alignment: CENTER});注意fontA被两个块复用——文档明确指出该设计有意追求效率既方便浏览器消化输入也允许客户端按自己意愿复用复合对象。另一条错误语义若文本字符串长度与各块length之和不匹配抛出异常。4. 访问整形与排版结果FormattedText 的原始数据FormattedText同时提供交互方法和原始数据typedef unsigned long TextIndex; interface TextPosition { readonly attribute TextIndex textIndex; readonly attribute unsigned long lineIndex; readonly attribute unsigned long runIndex; readonly attribute unsigned long glyphIndex; }; interface FormattedText { // Interaction methods // Given a valid index into the text, adjust it for proper grapheme // boundaries, and return the TextPosition. TextPosition indexToPosition(TextIndex index); // Given an x,y position, return the TextPosition // (adjusted for proper grapheme boundaries). TextPosition hitTextToPosition(double x, double y); // Given two logical text indices (e.g. the start and end of a selection range), // return the corresponding visual set of ranges (e.g. for highlighting). sequenceTextPosition indicesToVisualSelection(TextIndex t0, TextIndex t1); // Raw data readonly attribute Rect bounds; readonly attribute sequenceTextLine lines; };三个交互方法覆盖了文本编辑的核心需求indexToPosition逻辑索引 → 位置自动对齐到正确的字素/word grapheme 边界hitTextToPosition坐标反查点击命中测试同样做字素边界对齐indicesToVisualSelection给定选择区间的起止逻辑索引返回视觉区间集合——注意返回的是sequenceTextPosition而非单个区间因为跨行选择或 RTL/LTR 混排时一个逻辑区间会映射到多个视觉片段这直接服务高亮绘制。lines的数据结构是数组的数组每条TextLine包含若干TextRun当前为GlyphRun或PlaceholderRun// Shared by all output runs, specifying the range of code-points that produced // this run. Known subclasses: TextRun, PlaceholderRun. interface TextRun { readonly attribute TextIndex startIndex; readonly attribute TextIndex endIndex; }; interface GlyphRunFont { // Information to know which font-resource (typeface) to use, // and at what transformation (size, etc.) to use it. readonly attribute Typeface typeface; readonly attribute double size; readonly attribute double scaleX?; // 1.0 if not specified readonly attribute double skewX?: // 0.0 if not specified (could be a bool) }; interface GlyphRun : TextRun { readonly attribute GlyphRunFont font; // Information to know what positioned glyphs are in the run, // and what the corresponding text offsets are for those glyphs. // These offsets are not needed to correctly draw the glyphs, but are needed // during selections and editing, to know the mapping back to the original text. // readonly attribute sequenceunsigned short glyphs; // N glyphs readonly attribute sequencefloat positions; // N1 x,y pairs readonly attribute sequenceTextIndex indices; // N1 indices }; interface PlaceholderRun : TextRun { readonly attribute Rect bounds; }; interface TextLine { readonly attribute TextIndex startIndex; readonly attribute TextIndex endIndex; readonly attribute double top; readonly attribute double bottom; readonly attribute double baselineY; readonly attribute sequenceTextRun runs; };值得注意的细节GlyphRun中positions是N1 对 x,yindices也是N1 个索引——即数组长度比字形多一提供游标式的边界信息便于客户端精确做命中与选择。每条TextLine自带top/bottom/baselineY度量客户端无需再从字形数据反推行几何。4.1 设计基石run 携带文本区间回指原文文档用了一整段强调这是设计的根本fundamental to the design客户端创建富文本时会为每个块同时关联整形信息如 Font以及任意装饰与注解。每个 Run 返回其对应的startIndex/endIndex文本区间使客户端能查表找回自己挂在对应块上的全部私有信息颜色、阴影、下划线、占位符等。其价值在于把浏览器从理解所有可能装饰的并集中解放出来——这在逻辑上是不可能的负担。换句话说浏览器只负责正确的字形与位置语义与样式注解完全留在客户端通过文本区间这一最小契约对接。5. 在 Skia 中的对应实现SkShaper设计文档并非空中楼阁。Skia 仓库中modules/skshaper/模块提供了 C 侧的整形引擎其接口结构与上述提案高度呼应SkShaper::shape()核心签名SkShaper.h接收 UTF-8 文本及四个 run 迭代器——FontRunIterator逐段指定字体对应提案中每个FontBlock携带自己的字体、BiDiRunIterator双向文本嵌入层级、ScriptRunIteratorISO 15924 文字系统代码、LanguageRunIteratorBCP-47 语言外加显式Feature数组tag value utf8 区间对应提案InFont.features与排版宽度width结果通过RunHandler回调流式输出。RunHandler的两次遍历协议先beginLine → runInfo()×N → commitRunInfo()计算基线/行内偏移再runBuffer()×N → commitRunBuffer() → commitLine()填充字形与位置缓冲。RunHandler::Buffer中glyphs/positions/offsets/clusters的布局含可选clusters映射回 utf8 偏移与提案中GlyphRun.indices的映射回原文诉求完全同构。SkTextBlobBuilderRunHandlerSkShaper.h是一个官方示例 handler直接把整形结果写入SkTextBlob——这正是shape 一次、draw 多次性能路径的 C 版本。实现可插拔从SkShaper.h的条件编译宏可见SkShaper 依据SK_SHAPER_HARFBUZZ_AVAILABLE/SK_SHAPER_CORETEXT_AVAILABLE/SK_SHAPER_UNICODE_AVAILABLE在 HarfBuzz、CoreText、skunicode 等后端间切换Make()、MakeCoreText()、MakeShapeThenWrap()等工厂构建开关定义在 skshaper.gni。提案中Typeface 是字体资源实例、glyph ID 只在具体资源内有效的强调正对应 HarfBuzz 整形输出的字形 ID 语义。上层验证modules/skparagraph/展示了完整的整形 → 排版 → 文本块管线其中 SkShaperJSONWriter.cpp 及测试 SkShaperJSONWriterTest.cpp 将 SkShaper 的整形结果导出为 JSON 结构可作为理解整形结果如何序列化供外部渲染消费的真实参考——与提案暴露原始数据、由客户端自选渲染方式的思路一致。因此提案中的shape()阶段对应 Skia 的SkShaper::shape()format()阶段对应类似SkParagraph/行断行层的排版工作而TextBlock/FontRunIterator则对应逐段字体描述这一共同的输入模型。6. 结果如何被绘制Canvas2D 扩展提案文档最后一节指向姊妹提案 text_c2dCanvas2D Shaped Text Extensions它定义了结果的消费端context.fillGlyphs(glyphs, positions, Font)/context.strokeGlyphs(...)与fillText/strokeText平行的低级绘制调用尊重当前 transform/clip/style但忽略font/textAlign/textBaseline/direction属性——因为整形阶段已把这些算完结果就编码在 glyphs、positions 与 Font 参数里。由于回退与整形都已发生绘制调用天然比fillText()更快。Font在此扩展中比context.font字符串更具体它持有字体资源句柄而非名称并提供getGlyphAdvances()、getGlyphBounds()、getGlyphPaths()等工具方法——绘制字形不需要它们但实现自定义效果如字符级特效、命中检测辅助时需要。这印证了设计文档反复声明的目标Shaping 结果与 Canvas2D 解耦——同样的ShapedText/FormattedText数据可以用 WebGL/WebGPU 画、用 DOM 画复用断行与度量结果、或用 wasm 自绘渲染器消费。7. 备选方案与既有先例文档Alternatives and Prior Art一节明确了自我定位这是一个低级模型面向性能敏感应用动画与复杂文本编辑器同时让来自原生开发环境的工程师感到自然。对于更休闲的用户文档承认其需求合理但主张只要原语与数据暴露得当高层模型可以由 JS 社区自行构建正式框架或精炼的示例代码。文中还点名了一个高层数据模型先例——WICG 的 Formatted Text 提案并表达了将两者分层结合的期望高层客户端可用其数据模型同时保留访问本提案低级访问器的选项。8. 小结Shaped Text 提案的贡献可以概括为三句话两步分离shape()整形与布局无关与format()排版依赖尺寸解耦让只改窗口尺寸这类高频操作免于重复整形最小输入 原始输出输入只要求长度与字体描述输出是绑定具体 Typeface 的字形 ID 与坐标外加每条 run 的startIndex/endIndex文本区间让客户端注解可以无损回挂渲染无关结果不绑定任何特定绘制 APICanvas2D 只是众多消费端之一。而 Skia 仓库中的 SkShaper、SkTextBlobBuilderRunHandler与 skparagraph 模块正是这套对象模型在 C 侧的完整参照实现——研究该提案时这两个目录是最佳的交叉验证材料。赞分享图形学图像处理【免费下载链接】skiaSkia is a complete 2D graphic library for drawing Text, Geometries, and Images.项目地址https://gitcode.com/gh_mirrors/skia1/skia点击查看免费下载相关推荐Skia 文本 API 设计概览从 SkShaper 到 Web 端的 Shaping 能力开放Skia 文本 API 设计概览从 SkShaper 到 Web 端的 Shaping 能力开放 导读 本文以 Skia 仓库中 site/docs/dev图形学Skia Canvas2D Shaped Text 扩展设计用 fillGlyphs/strokeGlyphs 直绘定位字形与 Font 字形查询 APISkia Canvas2D Shaped Text 扩展设计用 fillGlyphs/strokeGlyphs 直绘定位字形与 Font 字形查询 API 本图形学图像处理Skia Canvas2D 文本扩展设计解读fillGlyphs 与 Font 字形查询 API 剖析Skia Canvas2D 文本扩展设计解读fillGlyphs 与 Font 字形查询 API 剖析 导读 本文深入解析 Skia 仓库中 Canvas2D图形学上一篇Dism 免费系统清理备份还原完整教程下一篇NetBox 网络资产管理实用指南从部署到查清每根线创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表