图片水印的字形与几何
公开入口和参数见 Media README。AddTextAsync、AddLogoAsync 与透明叠加图入口共用同一绘制逻辑,视频组件复用透明 PNG 结果,避免两套位置和透明度规则。
内容与字形
图片和 Logo 先经 ImageCodec 方向校正。Logo 的尺寸框按比例适配,不改变原始资产。文字采用有效 UTF-16 的单行输入,并限制长度与字号后才创建栅格;无效代理项、控制字符、无可见内容均拒绝。
显式字体使用宿主文件;默认字体只在覆盖全部字符时采用。绘制前检查字符覆盖,HarfBuzz 塑形后再拒绝缺失 glyph,不进行隐式字体回退。原字形位置用于构造 SKTextBlob,默认 typeface 不由单次调用释放。
布局尺寸取逐字形边界平移到 HarfBuzz 原位置后的并集,并留出两像素抗锯齿余量。SKTextBlob.Bounds 是保守范围,不用于适配、锚点或平铺步长,否则能放下的可见文字也可能被拒绝。空白字形不扩大可见范围,全部没有可绘制字形时明确拒绝。
塑形仅处理一个 run,方向由 HarfBuzz 推测;它不提供完整段落 bidi、混合脚本分段、多行排版和字体回退。复杂文本宜先通过完整排版器生成透明 Logo。Linux 需要匹配的 HarfBuzz native 资产,字体仍由宿主提供。
旋转、定位与平铺
先计算旋转后的外接矩形,再把九个锚点应用到扣除四周边距的可用区域。直角旋转直接交换宽高,其他角度按正弦/余弦计算外框并向上取整;这样定位不依赖原始未旋转宽高。非平铺要求整个外框位于可用区域。
平铺以锚点确定网格相位,步长为外框尺寸加间距。仅当上一格与可用区域相交时才纳入,避免对齐边界出现完全不可见的额外行/列。循环前用行列数量检查 MaximumTiles,步长始终为正;边缘图案按内边距裁剪,不由循环中的临时条件改变预算。
文字颜色或 Logo alpha 与统一 opacity 相乘。零 opacity 仍走输入与参数校验,绘制应保持原图的规范化像素。全画布叠加图先清透明,固定输出 PNG;JPEG 等有损输出只用于完整图片,并继续使用共享合成规则。
生命周期与失败
每次绘制拥有字形、内容栅格和画布,不修改字体或 Logo 文件。输入输出流保持打开,从当前位置读写;禁止别名流做原地处理。取消在塑形、尺寸变换、逐格绘制和编码之间检查,单次 native 调用不可强制中断。失败的输出由调用方丢弃或通过暂存策略隔离。
回归关注点
ImageWatermarkerTests 验证九锚点精确像素、直角和任意角度外框、透明叠加图、alpha 相乘、零 opacity、平铺预算及实际字体。旋转平铺的文本叠加图与透明底图上的完整图片处理结果须逐像素一致。
Windows 字体用例验证中文和阿拉伯连字;它们证明指定字体与当前 HarfBuzz 的运行结果,不代表所有系统默认字体覆盖这些文字。升级字体或 Skia/HarfBuzz 时保留缺字失败和文本边界回归。