fontTools qu2cu 完全指南:二次贝塞尔到三次曲线的转换算法、Cython 加速与 fonttools qu2cu 命令行实战 开发工具【免费下载链接】fonttoolsA library to manipulate font files from Python.项目地址https://gitcode.com/gh_mirrors/fo/fonttools点击查看免费下载本文聚焦 fontTools 的qu2cu包:将 TrueType 风格的二次贝塞尔轮廓转换为 CFF 风格的三次曲线(OpenType TrueType to CFF-flavored 轮廓转换的核心环节)。读完你将掌握核心 APIquadratic_to_curves的完整参数语义与输入输出格式、其背后升阶 动态规划 误差验证的算法原理、Cython 编译加速的启用方式,以及fonttools qu2cu命令行工具的全部选项与可复制的运行示例。1. qu2cu 是什么:定位与应用场景fontTools 文档 Doc/source/qu2cu/index.rst 对该包的定位非常明确:Routines for converting quadratic curves to cubic splines, suitable for use in TrueType to CFF-flavored OpenType outline conversion.也就是说,qu2cu提供的是一套二次曲线 → 三次曲线样条的转换例程,典型使用场景是把TrueType(glyf 表、二次贝塞尔)字形轮廓转换为 CFF 风格(三次贝塞尔)轮廓——例如 otf2ttf 流程的反向操作,或为构建glyf与CFF混合的 OpenType 字体做准备。它是 cu2qu(三次转二次)模块的逆过程,两者共用同一套误差控制思想。包的核心转换例程实现在fontTools.qu2cu.qu2cu模块(源码),对外只导出一个 API:# Lib/fontTools/qu2cu/qu2cu.py __all__ [quadratic_to_curves]包顶层 Lib/fontTools/qu2cu/init.py 仅有一行from .qu2cu import *,因此from fontTools.qu2cu import quadratic_to_curves即可使用。1.1 为什么模块名有冗余:Cython 加速机制原文档中有一条值得展开的注释(原文 Doc/source/qu2cu/index.rst 的 note):The redundancy in the module name is a workaround made necessary byfontTools.qu2cus usage of Cython. Providing Cython support for the module enables faster execution on systems where Cython is available. However, the module remains fully available on systems without Cython, too.原因是:该包希望像fontTools.cu2cu一样提供 Cython 编译版本,而 setuptools 只能从纯.py源文件编译出同名扩展模块。如果把算法直接写在fontTools/qu2cu/__init__.py里,就无法把包本身当作扩展模块编译,于是算法被放到子模块fontTools.qu2cu.qu2cu,形成qu2cu.qu2cu这样的冗余路径。这一机制在构建配置中可以确认。setup.py 在启用 Cython 时(默认:环境中检测到 Cython 即启用,也可用--with-cython/--without-cython显式控制)会注册扩展:ext_modules.append( Extension( fontTools.qu2cu.qu2cu, [Lib/fontTools/qu2cu/qu2cu.py], librarieslibraries, ), )而 Lib/fontTools/qu2cu/qu2cu.py 开头对 Cython 的依赖做了软处理:try: import cython except (AttributeError, ImportError): # if cython not installed, use mock module with no-op decorators and types from fontTools.misc import cython COMPILED cython.compiled即未安装 Cython 时退化为 fontTools.misc.cython 提供的空操作(no-op)装饰器与类型,纯 Python 版本功能完全可用,只是没有编译加速。源码中对热点函数(cubic_farthest_fit_inside、merge_curves、spline_to_curves等)标注了大量cython.locals/cython.cfunc类型提示,正是为编译路径服务的。2. 核心 API:quadratic_to_curves2.1 函数签名与参数quadratic_to_curves定义在 Lib/fontTools/qu2cu/qu2cu.py:def quadratic_to_curves( quads: List[Sequence[Point]], max_err: float 0.5, all_cubic: bool False, ) - List[Tuple[Point, ...]]:参数类型默认值含义quadsList[Sequence[Point]]—一组首尾相连的二次样条。每个样条是点的序列:首末为 on-curve 点,中间为 off-curve 点,每两个连续 off-curve 点之间隐含一个 on-curve 点(即 TrueType 的 q 曲线语义)max_errfloat0.5绝对误差容限(绝对坐标单位,不是 EM 比例)。max_err 0时抛出ValueError(max_err must be greater than zero)all_cubicboolFalse为True时只生成三次曲线;默认允许保留无法合并不划算的二次段输入/输出的点格式(Point Union[Tuple[float, float], complex]):输入点既可以是(x, y)二元组,也可以是复数(实部为 X、虚部为 Y);输出保持与输入相同的格式;quads为空时直接返回空列表;返回值的每个元素是一个点元组:长度为 3 表示一条二次曲线,长度为 4 表示一条三次曲线,且每条曲线的末点与下一条曲线的起点相同(首尾相接)。错误行为(均有测试覆盖,见 Tests/qu2cu/qu2cu_test.py):情形异常某条样条少于 3 个点ValueError(Quadratic splines must contain at least 3 points)相邻样条未尾接首ValueError(Quadratic splines must connect end-to-start; got ...)max_err 0ValueError(max_err must be greater than zero)对应校验函数见 qu2cu.py#L178-L191。2.2 最小可运行示例(取自仓库测试)以下用例来自 Tests/qu2cu/qu2cu_test.py 的test_simple参数化测试,可直接复制运行:from fontTools.qu2cu import quadratic_to_curves # 单条二次样条(3 点: 2 个 off-curve) quadratic_to_curves([((0, 0), (0, 1), (1, 1))]) # - [((0, 0), (0, 1), (1, 1))] # 保持为二次 # 强制 all_cubicTrue:两个相连的二次段被拟合为一条三次曲线 quadratic_to_curves( [[(0, 0), (0, 1), (1, 1)], [(1, 1), (3, 1), (3, 0)]], max_err0.2, all_cubicTrue, ) # - [((0, 0), (0, 2/3), (1/3, 1), (1, 1)), # ((1, 1), (7/3, 1), (3, 2/3), (3, 0))] # 不强制全三次:两条短二次样条原样保留 quadratic_to_curves( [[(0, 0), (0, 1), (1, 1)], [(1, 1), (3, 1), (3, 0)]], max_err0.2, all_cubicFalse, ) # - [((0, 0), (0, 1), (1, 1)), ((1, 1), (3, 1), (3, 0))]这组用例直观展示了all_cubic的语义差异:不强制时,短而独立的二次段没有合并收益会被原样保留;强制时,算法会跨段构造控制点,把相邻二次段合并进一条三次曲线。3. 算法原理:升阶、强制拐点、动态规划与误差验证spline_to_curves(qu2cu.py#L293)实现了核心优化,其 docstring 说明了内部数据约定:q是 on/off 交替的二次样条,costs是编码点数的累计代价表——隐含 on-curve 点不贡献编码代价(这正是 TrueType 轮廓压缩高效的原因),优化目标是在编码代价最小的前提下让误差最小。整个流程可拆解为四步:3.1 补齐隐含 on-curve 点:add_implicit_on_curvesadd_implicit_on_curves 把 TrueType 样条中每两个连续 off-curve 点之间隐含的中点显式插入:off1 p[i] off2 p[i 1] on off1 (off2 - off1) * 0.5插入后样条变成严格的 on/off 交替序列,便于后续按q[i:i3]切分二次段。3.2 升阶:elevate_quadraticelevate_quadratic 将每条二次贝塞尔升阶为几何上完全等价的三次贝塞尔:p1_2_3 p1 * (2 / 3) return ( p0, (p0 * (1 / 3) p1_2_3), (p2 * (1 / 3) p1_2_3), p2, )这是标准的 degree elevation:二次控制点p1映射为两条三等分处的三次控制点,曲线本身不变。之后所有误差判断都在等价的三次表示上进行,使二次/三次两类候选方案可用同一套容差比较。3.3 强制 on-curve 点:尖角不能被抹平在动态规划之前,算法先找出尖角(sharp corner):相邻两段升阶曲线在连接点处若不共线(退化三角形判定),该点必须保留为 on-curve,不能作为三次曲线的内部点,否则尖角会被平滑掉:p0 elevated_quadratics[i - 1][2] p1 elevated_quadratics[i][0] p2 elevated_quadratics[i][1] if abs(p1 - p0) abs(p2 - p1) tolerance abs(p2 - p0): forced.add(i)判定依据是三角不等式:|A-B| |B-C| |A-C|成立(超过容差)说明三点不共线,B 是真实的角点。命中后 DP 的搜索起点start会被重置到该索引,保证分段边界落在角点上。3.4 动态规划:最少数段优先,其次最小误差DP 状态为Solution namedtuple(Solution, [num_points, error, start_index, is_cubic]),对每个前缀i枚举断点j,比较两类候选:保留二次(仅在not all_cubic时):区间j:i内的段以二次形式编码,代价来自costs表,误差为 0;拟合一条三次:用 merge_curves 把j:i的升阶二次段合并成一条端点与切线一致的三次曲线,再用 fontTools.misc.bezierTools 的splitCubicAtTC按各段的比例参数ts把拟合曲线切回原分段,逐项验证:节点误差:每段端点与原段的偏差err abs(reconst[3] - orig[3]),任一超过tolerance则方案不可行;内部误差:对每段做cubic_farthest_fit_inside(p0, p1, p2, p3, tolerance)检查。cubic_farthest_fit_inside(qu2cu.py#L50-L79)递归判断一条三次曲线(以原点为参考系)是否整体落在原点半径tolerance的圆内:先做廉价的端点/中点检查,不合格就按 De Casteljau 一分为二递归。这是误差验证的热点,因此被标注为cython.cfunc供编译加速。方案的比较顺序是Solution的字段序:先比num_points(编码点数,即最少数段),平局再比error(最小误差)。all_cubicTrue时跳过二次候选,i_sol_count 3时提前终止(三条点已是最优下界)。最后按sols回溯重建:被标记is_cubic的区间输出merge_curves(...) 的合并曲线,其余区间逐段输出原二次元组。3.5 与 cu2qu 的互验:roundtrip 测试Tests/qu2cu/qu2cu_test.py 的test_roundtrip加载 Tests/cu2cu/data/curves.json 中的三次曲线样本,用fontTools.cu2cu.curve_to_quadratic转二次,再用quadratic_to_curves转回,断言结果与原始三次曲线近似相等(容差 1.0)。这验证了 qu2cu/cu2qu 这对互逆模块在相同容差下的往返一致性;模块自带的main()(qu2cu.py#L414-L429)也做了同样的随机曲线往返打印,便于人工观察。4. 命令行工具:fonttools qu2cuqu2cu包还提供 CLI 子模块(Doc/source/qu2cu/cli.rst),实现于 Lib/fontTools/qu2cu/cli.py,入口链为:console scriptfonttools(setup.py 的fonttools fontTools.__main__:main)→ fontTools/main.py 按首个参数动态 importfontTools.qu2cu→main.py 调用cli._main。功能:_font_to_cubic用TTFont读入 TTF,逐字形用Qu2CuPen重画轮廓并写回glyf表,最后把head.glyphDataFormat置为 1(无隐含 on-curve 点的格式),保存到新文件。若字体含gvar表(可变字体)则直接ValueError(Cannot convert variable font)拒绝处理——因为可变字体的实例化轮廓由 glyf delta 控制,单纯转换基字形轮廓语义不完整。4.1 参数一览选项说明INPUT(infiles,一个或多个)输入 TTF 源文件(支持批量)-o, --output-file OUTPUT输出文件名;多输入时不可用(-o/--output-file cant be used with multile inputs)-d, --output-dir DIRECTORY输出目录(不存在时自动创建),文件保留原名;与-o互斥-e, --conversion-error ERROR最大近似误差,以 EM 为单位,默认0.001;必须大于 0,否则parser.error-c, --all-cubic只生成三次曲线(默认允许保留二次段)-v, --verbose(可叠加)日志级别:无-v为 WARNING,一个为 INFO(并输出转换统计),两个及以上为 DEBUG--version打印 fontTools 版本注意-e的单位是EM(千分之一 EM 比例),CLI 内部会换算为绝对字素单位传给核心 API:# Lib/fontTools/qu2cu/cli.py qu2cu_kwargs { stats: stats, max_err: kwargs[max_err_em] * font[head].unitsPerEm, all_cubic: kwargs[all_cubic], }即max_err(绝对单位) 0.001 * unitsPerEm。这与 Qu2CuPen 文档中如知道 UPEM,建议max_err取 UPEM/1000 左右以获得最优结果的推荐一致。4.2 输出文件命名未指定-o/-d时,输出名由 fontTools.misc.cliTools.makeOutputFileName 生成,在原名中插入.cubic后缀并允许覆盖原位置,例如NotoSansArabic-Regular.ttf→NotoSansArabic-Regular.cubic.ttf。测试 Tests/qu2cu/qu2cu_cli_test.py 的test_no_output正是验证了这一点,并断言转换后font[head].glyphDataFormat 1,且输出文件比输入小(三次表示在此样本上编码更紧凑)。4.3 运行示例以仓库测试数据 Tests/qu2cu/data/NotoSansArabic-Regular.quadratic.subset.ttf 为例:# 基本转换:生成 NotoSansArabic-Regular.quadratic.subset.cubic.ttf fonttools qu2cu -v Tests/qu2cu/data/NotoSansArabic-Regular.quadratic.subset.ttf # 显式输出文件 仅三次曲线 fonttools qu2cu -c -o out.cubic.ttf input.ttf # 放宽误差到 0.002 EM,输出到目录 fonttools qu2cu -e 0.002 -d output_dir/ input1.ttf input2.ttf加-v时,Qu2CuPen的stats字典会统计各off-curve 点数区间的曲线段数量并打印,例如日志Stats: {...},方便评估转换结果中三次段与保留二次段的分布。4.4 Qu2CuPen:面向 Pen 协议的程序化入口CLI 之所以能逐字形转换,靠的是 fontTools.pens.qu2cuPen.Qu2CuPen(一个ContourFilterPen):class Qu2CuPen(ContourFilterPen): def __init__(self, other_pen, max_err, all_cubicFalse, reverse_directionFalse, statsNone):它监听 SegmentPen 操作流,把连续的qCurveTo序列攒起来交给quadratic_to_curves,输出curveTo(三次)或qCurveTo(二次);all_cubicFalse时会把共线中点形式的相邻二次段重新合并回一条多 off-curve 点的qCurveTo,恢复 TrueType 的隐含 on-curve 压缩语义(即 qu2cuPen.py#L85-L103 的回插逻辑);已知限制:all_cubicTrue时不支持无 on-curve 点的纯 off-curve 轮廓,会抛NotImplementedError(oncurve-less contours with all_cubic not implemented)。在 CLI 的调用中,它包裹TTGlyphPen完成draw→glyph(dropImpliedOnCurvesTrue)的闭环(cli.py#L29-L36)。如果你在自己的字体构建流程中需要自定义转换(如先变换轮廓再转换),直接复用Qu2CuPen即可,max_err仍建议按 UPEM/1000 量级取值。5. 性能基准:benchmark 模块Lib/fontTools/qu2cu/benchmark.py 用timeit对quadratic_to_curves做了轻量基准:生成 5 条随机三次曲线 →curve_to_quadratic转二次 → 计时quadratic_to_curves(容差 0.5,repeat25 取最小值),输出格式为微秒:def main(): run_benchmark(qu2cu, quadratic_to_curves)可用python -m fontTools.qu2cu.benchmark运行(它会random.seed(1)保证可复现)。该脚本与测试中的test_main(qu2cu_test.py#L127-L130)一同覆盖了基准路径;在有 Cython 的编译构建下运行同一脚本,可以直观感受COMPILED版本相对纯 Python 的速度差异。6. 小结与使用注意事项API 层:quadratic_to_curves(quads, max_err0.5, all_cubicFalse)接收 TrueType 语义的二次样条(首末 on-curve、隐含中点),返回首尾相接的二次/三次曲线元组列表;点格式支持(x, y)与复数,输入输出同格式;max_err为绝对坐标单位且必须为正。算法层:通过升阶统一到三次表示,尖角点强制保留为 on-curve,再在编码点数最少 → 误差最小的字典序下做动态规划;合并候选曲线须经节点误差与cubic_farthest_fit_inside内部误差双重验证。CLI 层:fonttools qu2cu只接受非可变TTF(gvar存在即报错),-e按 EM 比例换算为max_err(默认 0.001 × UPEM),输出默认带.cubic后缀,转换后head.glyphDataFormat 1;批量输入请用-d。性能层:该模块可被 Cython 编译加速(setup.py 中以fontTools.qu2cu.qu2cu注册扩展),无 Cython 环境退化为纯 Python 但功能完整;可用 benchmark 脚本自行对比。文中引用的关键路径汇总:核心算法、Pen 封装、CLI 实现、API 测试、CLI 测试、CLI 测试数据、官方文档。赞分享开发工具【免费下载链接】fonttoolsA library to manipulate font files from Python.项目地址https://gitcode.com/gh_mirrors/fo/fonttools点击查看免费下载相关推荐fontTools cu2qu 全解析三次贝塞尔曲线转二次样条的核心算法与 UFO 实战fontTools cu2qu 全解析三次贝塞尔曲线转二次样条的核心算法与 UFO 实战 cu2qucubic to quadratic是 fontToo开发工具fontTools cu2quPen 深度解析用 Pen 协议将三次贝塞尔曲线转换为二次 B 样条fontTools cu2quPen 深度解析用 Pen 协议将三次贝塞尔曲线转换为二次 B 样条 本文围绕 fontTools 的 fontTools.pe开发工具three.js 二次贝塞尔曲线完全指南QuadraticBezierCurve 的原理、源码与实战用法three.js 二次贝塞尔曲线完全指南QuadraticBezierCurve 的原理、源码与实战用法 导读 QuadraticBezierCurve 是前端3D渲染图形学创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考