ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

使用 Skia 的 PDF 后端(SkPDF)生成文档:SkDocument/SkCanvas 实战与能力边界

使用 Skia 的 PDF 后端(SkPDF)生成文档:SkDocument/SkCanvas 实战与能力边界 图形学图像处理【免费下载链接】skiaSkia is a complete 2D graphic library for drawing Text, Geometries, and Images.项目地址https://gitcode.com/gh_mirrors/skia1/skia点击查看免费下载Skia 的 PDF 后端SkPDF允许开发者复用同一套SkCanvas绘制代码将文本、几何图形与位图输出为符合 PDF 规范的矢量文档。本文以仓库官方示例 docs/examples/PDF.cpp 为主线完整讲解基于SkDocument与SkCanvas的多页 PDF 生成流程并结合 site/docs/user/sample/pdf.md 中的官方能力对照表逐一分析 SkPDF 对各类绘制效果MaskFilter、PathEffect、ColorFilter、ImageFilter、混合模式、Shader的 drop / ignore / expand 处理策略帮助读者在实际选型与调试时准确预判输出结果。一、示例总览一段可运行的 SkPDF 生成代码仓库文档 site/docs/user/sample/pdf.md 的核心是一个嵌入式 Fiddle 示例fiddle-embed-sk namePDF其实际源码即 docs/examples/PDF.cpp。该示例展示了两件事封装一个通用的多页 PDF 写入函数WritePDF()通过SkPDF::MakeDocument()创建文档再以beginPage()/endPage()逐页绘制、以close()收尾用一段星形路径演示矢量绘制write_page()使用SkPaint::kStroke_Style描边一个 8 顶点星形并将最终 PDF 字节以十六进制打印到 stdout方便用xxd还原成.pdf文件。该示例同时是 Fiddle 平台fiddle.skia.org上的可交互样例意味着读者可以在浏览器中直接修改并运行同一段代码。二、逐步拆解SkDocument 生命周期与页面绘制流程SkPDF 的高层入口是SkDocument抽象基类其使用范式在 include/core/SkDocument.h 中明确给出三步流程创建文档并指定输出流对每一页内容canvas doc-beginPage(...)→ 在 canvas 上绘制 →doc-endPage()所有页完成后调用doc-close()。对应的虚函数onBeginPage()、onEndPage()、onClose()、onAbort()在 include/core/SkDocument.h 中声明SkPDF 在 src/pdf/SkPDFDocument.cpp 中完成实现onBeginPage()src/pdf/SkPDFDocument.cpp首页时先序列化%PDF-1.4文件头与 Info 字典随后按fRasterScale缩放创建SkPDFDevice页面设备并返回包装它的SkCanvas。页面尺寸单位为point1 pt 1/72 英寸示例中ansiLetterSize{8.5f * 72, 11.0f * 72}即标准美式信纸Letter尺寸onEndPage()src/pdf/SkPDFDocument.cpp将设备内容封装为页对象写入 MediaBox、资源字典与内容流onClose()src/pdf/SkPDFDocument.cpp生成 Catalog、Pages 页面树、交叉引用表xref与trailer/startxref/%%EOF尾部。SkPDF 内部把页面组织成一棵Pages 树generate_page_tree()src/pdf/SkPDFDocument.cpp每个内部节点最多 8 个孩子kMaxNodeSize 8这是 PDF 规范要求的分层结构文件头中还会写入 4 字节高位置位的注释\xD3\xEB\xE9\xE1即 Skia 的高位编码满足 PDF 规范对二进制注释的建议src/pdf/SkPDFDocument.cpp。2.1 示例核心函数 WritePDF()PDF.cpp 中的封装函数是理解整个 API 的最小模板void WritePDF(SkWStream* outputStream, const char* documentTitle, void (*writePage)(SkCanvas*, int page), int numberOfPages, SkSize pageSize) { SkPDF::Metadata metadata; metadata.fTitle documentTitle; metadata.fCreator Example WritePDF() Function; metadata.fCreation {0, 2019, 1, 4, 31, 12, 34, 56}; metadata.fModified {0, 2019, 1, 4, 31, 12, 34, 56}; auto pdfDocument SkPDF::MakeDocument(outputStream, metadata); SkASSERT(pdfDocument); for (int page 0; page numberOfPages; page) { SkCanvas* pageCanvas pdfDocument-beginPage(pageSize.width(), pageSize.height()); writePage(pageCanvas, page); pdfDocument-endPage(); } pdfDocument-close(); }要点说明输出流SkWStream是抽象基类示例中使用SkDynamicMemoryWStream在内存中累积字节最后detachAsData()取出SkData也可以换成SkFILEWStream直接写文件。文档在文档生命周期内任何时刻都可能写入该流直到close()或文档被销毁见 include/docs/SkPDFDocument.h 中MakeDocument的注释。MetadataSkPDF::Metadata的完整字段定义在 include/docs/SkPDFDocument.h示例只设置了fTitle、fCreator、fCreation、fModified四个字段其余字段fAuthor、fSubject、fKeywords、fLang、fRasterDPI、fPDFA、fEncodingQuality、fCompressionLevel等均有默认值可留空。fProducer默认为Skia/PDF m 里程碑版本号。工厂函数SkPDF::MakeDocument(stream, metadata)定义在 src/pdf/SkPDFDocument.cpp其中对参数做了兜底fRasterDPI 0时重置为 72fEncodingQuality 0时重置为 0stream为空时返回nullptr这也是示例中SkASSERT(pdfDocument)存在的原因。另有一个便捷重载MakeDocument(stream)使用默认Metadata()include/docs/SkPDFDocument.h。2.2 页面回调与星形路径绘制示例的页面回调write_page()docs/examples/PDF.cpp演示了最基础的矢量绘制void write_page(SkCanvas* canvas, int) { const SkScalar R 115.2f, C 128.0f; SkPath path; path.moveTo(C R, C); for (int i 1; i 8; i) { SkScalar a 2.6927937f * i; path.lineTo(C R * cos(a), C R * sin(a)); } SkPaint paint; paint.setStyle(SkPaint::kStroke_Style); canvas-drawPath(path, paint); }这里以一个固定角度步长2.6927937f约为 5π/6 的浮点近似在圆周上取 8 个点构成近似星形SkPaint::kStroke_Style表示只描边不填充。路径最终会被 SkPDF 转换为 PDF 的 path 绘制指令对应SkPDFDevice::drawPathsrc/pdf/SkPDFDevice.cpp保持矢量特性——放大不糊、可被 PDF 阅读器选中与编辑。示例末尾的print_data()docs/examples/PDF.cpp把生成的 PDF 二进制按 40 字节一行的十六进制打印并输出可直接执行的xxd -r -p skpdf_example.pdf EOF ... EOF还原命令方便开发者把示例产物落盘后用阅读器验证。三、Metadata 详解从文档信息到 PDF/A 与压缩SkPDF::Metadatainclude/docs/SkPDFDocument.h是 SkPDF 的配置中枢除示例用到的标题/创建者/时间外还包括字段默认值作用fTitle/fAuthor/fSubject/fKeywords/fCreator空文档信息字典Info 字典的标准字段fProducerSkia/PDF m里程碑生成产品名fCreation/fModified全零表示未设置DateTime结构含fTimeZoneMinutes时区偏移与年月日时分秒fLang空文档自然语言为空时回退到结构树根节点的fLangfRasterDPI72.0SK_ScalarDefaultRasterDPI对无原生 PDF 支持的绘制如透视变换下的图片/文本做栅格化时的 DPI值越大保真度越高、文件越大fPDFAfalse为 true 时写入 XMP 元数据、文档 UUID 与 sRGB 输出意图以符合 PDF/A-2b代价是输出不可复现、体积增大fEncodingQuality101101 表示无损设为 ≤100 且图片不透明时改用该质量档位的 JPEG 编码fStructureElementTreeRootnullptr结构化文档标签树根节点PDF 标签/无障碍语义fOutlineOutline::None是否根据结构元素生成书签大纲fExecutornullptr并行执行器设置后用于并行执行 Deflate 压缩输出对象顺序不再可复现实验性fCompressionLevelCompressionLevel::DefaultNone(0)/LowButFast(1)/Average(6)/HighButSlow(9)之间的压缩-速度权衡fSubsetterkHarfbuzz_Subsetter字体子集化器选择其中DateTime::toISO8601()实现在 src/pdf/SkPDFDocument.cpp将DateTime格式化为YYYY-MM-DDTHH:MM:SS±HH:MM的 ISO 8601 字符串写入 PDF。PDF/A 模式下的 sRGB 输出意图与内嵌 ICC 配置文件分别由make_srgb_output_intents()与SkSrgbIcm()生成src/pdf/SkPDFDocument.cpp内嵌的正是 IEC 61966-2.1 sRGB profile。从实现上看SkPDFDocument构造函数src/pdf/SkPDFDocument.cpp会根据fRasterDPI计算fRasterScalefRasterDPI / 72与fInverseRasterScale页面设备按此比例放大创建、画布再按fRasterScale缩放回来——目的是让栅格化发生在目标 DPI 而非 72dpi见onBeginPage()中的注释src/pdf/SkPDFDocument.cpp。四、SkPDF 能力边界官方效果对照表与处理语义site/docs/user/sample/pdf.md 的核心篇幅是 SkPDF Limitations 一节。官方文档首先定义了三个处理语义drop什么都不画该效果被丢弃ignore照常绘制但效果不生效当作普通绘制处理expand以非 PDF 原生的方式实现该效果——可能把矢量栅格化、把带路径效果的路径展开成大量独立路径、或把文本转成路径此时文本即文本的性质丢失无法再被选中/搜索。官方对照表如下Effecttextimageseverything elseSkMaskFilterdropignoreignoreSkPathEffectignoren/aexpandSkColorFilterignoreexpandignoreSkImageFilterexpandexpandexpandunsupported SkXferModesignoreignoreignorenon-gradient SkShaderexpandn/aexpand表格下的两条补充说明SkImageFilter被 expand 处理时文本作为文本的性质会丢失SkXferMode以下混合模式 PDF 不原生支持——DstOver、SrcIn、DstIn、SrcOut、DstATop、SrcATop、Modulate。4.1 源码印证各效果在 SkPDFDevice 中的真实处理对照 src/pdf/SkPDFDevice.cpp 的实现可以找到与上表一一对应的证据链SkMaskFilter在internalDrawPath()中只要paint-getMaskFilter()非空就转交internalDrawPathWithFilter()src/pdf/SkPDFDevice.cpp后者通过SkDraw::DrawToMask把路径栅格化为 mask再经as_MFB(...)-filterMask()过滤最终以位图方式绘制src/pdf/SkPDFDevice.cpp。这对应表格中的 everything else → ignore 语义——效果以栅格化方式近似实现。而对文本路径setupFont()中NOT_IMPLEMENTED(paint.getMaskFilter() ! nullptr, false)src/pdf/SkPDFDevice.cpp意味着文本上的 MaskFilter 根本不被实现对应 text → drop。SkPathEffect绘制文字时若runPaint.getPathEffect()非空会走普通路径绘制分支而非 Type3 字体src/pdf/SkPDFDevice.cpp绘制普通路径时internalDrawPath()会把带 PathEffect 的路径先经SkStrokeRec/路径工具展开成新路径再绘制src/pdf/SkPDFDevice.cpp即 everything else → expand。而文本场景 ignore从源码结构看文本若带有 PathEffect 会退化为路径绘制效果本身不参与文本排版。SkColorFilter绘制位图时若paint-getColorFilter()非空会把图片先画进一个临时栅格 surface 应用颜色滤镜再取回快照作为绘制源color_filter()辅助函数src/pdf/SkPDFDevice.cpp调用点 src/pdf/SkPDFDevice.cpp对应 images → expand。SkImageFilter / saveLayeronCreateDevice()在 saveLayer 带 ImageFilter 时创建位图层设备参见onBeginPage()注释 src/pdf/SkPDFDocument.cpp实现上同样走栅格化路线因此文本会失去文本属性符合 expand 语义。混合模式needShape()src/pdf/SkPDFDevice.cpp明确列出kClear、kSrc、kSrcIn、kSrcOut、kDstIn、kDstOut、kSrcATop、kDstATop、kModulate需要 shape 辅助非treat_as_regular_pdf_blend_mode的模式需要把目标内容转成 Form XObject 再合成src/pdf/SkPDFDevice.cppkDstOver也有专门分支src/pdf/SkPDFDevice.cpp。官方文档指出DstOver、SrcIn、DstIn、SrcOut、SrcATop、DstATop、Modulate这些模式 PDF 不原生支持对应 ignore 语义——绘制仍发生但混合效果不保证。非渐变 ShaderSkPDF 专门实现了SkPDFGradientShadersrc/pdf/SkPDFShader.cpp、src/pdf/SkPDFGradientShader.cpp以原生方式输出渐变而非渐变 Shader如图像 Shader、噪声等需要走栅格化/展开对应 expand。drawVertices / drawPatchSkPDFDevice::drawVertices()目前仅有// TODO: implement drawVertices占位src/pdf/SkPDFDevice.cppdrawMesh()同样未实现——与官方文档 drawVertices — drop、drawPatch — drop 的说明一致。4.2 其他限制清单官方文档还列出两条 API 级限制drawText with VerticalText — drop没有已知客户端使用VerticalText标志SkPDF 直接不绘制垂直文本drawTextOnPath — expand沿路径排布的文本会展开为路径文本即文本的性质丢失。五、测试与验证如何确认 SkPDF 行为仓库的 tests 目录提供了大量可验证 SkPDF 行为的单元测试可作为读者对照本文结论的实证材料tests/PDFDocumentTest.cpp验证文档创建、beginPage/endPage/close生命周期及输出结构tests/PDFDeflateWStreamTest.cpp验证流压缩Deflate路径对应fCompressionLevel配置tests/PDFJpegEmbedTest.cpp验证 JPEG 图片的嵌入编码对应fEncodingQualitytests/PDFOpaqueSrcModeToSrcOverTest.cpp验证不透明源图的 Src 模式到 SrcOver 的等价转换是混合模式处理的重要特例tests/PDFTaggedTest.cpp、tests/PDFTaggedLinkTest.cpp、tests/PDFTaggedPruningTest.cpp、tests/PDFTaggedTableTest.cpp覆盖结构元素树、标签链接与大纲fStructureElementTreeRoot/fOutline相关功能tests/PDFGlyphsToUnicodeTest.cpp验证 ToUnicode CMap 的生成保证文本可复制/搜索。需要说明的是本文所引 SkPDF 行为均基于当前仓库快照文件头输出%PDF-1.4见 src/pdf/SkPDFDocument.cpp不同 Skia 版本在能力边界与默认配置上可能存在差异具体以对应版本源码为准。六、实战要点小结最小可用模板SkPDF::MakeDocument(stream, metadata)→ 循环beginPage(w, h)/ 绘制 /endPage()→close()见 docs/examples/PDF.cpp页面尺寸以 point 为单位信纸为8.5×72 × 11×72。优先使用原生支持的特性普通路径、渐变 Shader、文本与常规混合模式会以矢量/原生形式输出质量最高而 MaskFilter路径、ColorFilter图片、ImageFilter、非渐变 Shader 会触发栅格化expand输出体积与清晰度取决于fRasterDPI。提前规避 drop 项垂直文本、drawVertices、drawPatch在 SkPDF 中直接不绘制文本上的 MaskFilter 同样被丢弃。若这些能力不可或缺应考虑栅格化后再嵌入。可访问性场景需要 PDF 标签树、书签大纲或 PDF/A 归档时配置fStructureElementTreeRoot、fOutline与fPDFA需要确定性的可复现输出时不要设置fExecutor并行压缩会改变对象顺序。控制体积通过fEncodingQuality≤100 启用 JPEG 编码与fCompressionLevel权衡文件大小与质量/速度。赞分享图形学图像处理【免费下载链接】skiaSkia is a complete 2D graphic library for drawing Text, Geometries, and Images.项目地址https://gitcode.com/gh_mirrors/skia1/skia点击查看免费下载相关推荐Skia PDF 后端SkPDF使用指南SkDocument/SkCanvas 生成 PDF 与功能限制全解析Skia PDF 后端SkPDF使用指南SkDocument/SkCanvas 生成 PDF 与功能限制全解析 导读 本文基于 Skia 官方文档 sit图形学深入解析 Skia PDF 后端用 SkDocument 与 SkCanvas 从零生成 PDF 文档深入解析 Skia PDF 后端用 SkDocument 与 SkCanvas 从零生成 PDF 文档 导读 本文以 Skia 官方示例文档 third_pa前端桌面应用arduino-esp32 中 democfg 参考用例详解用一份全量注释的 ci.yml 掌握 CI 测试配置体系arduino esp32 中 democfg 参考用例详解用一份全量注释的 ci.yml 掌握 CI 测试配置体系 tests/validation/dem图形学图像处理上一篇WechatBakTool企业级数据合规备份解决方案实战指南下一篇3步解锁iOS设备applera1n激活限制绕过完全指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表