Skip to content

Support 3D transforms, anchored pivots and the perspective property in the HTML importer - #3820

Open
mite085 wants to merge 26 commits into
Tencent:mainfrom
mite085:feature/liushihao_3d_anchor
Open

mite085 wants to merge 26 commits into
Tencent:mainfrom
mite085:feature/liushihao_3d_anchor

Conversation

@mite085

@mite085 mite085 commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

总结

HTML 导入器的变换管线从 2D 仿射 升级为 4×4 + 锚点:transform 全函数支持、transform-origin 改写 Layer.anchor、动画按平面/深度分流 matrix / matrix3D,并接住 CSS perspective 属性。3D 从「完全丢失」提升到 7/8 用例 ≥ 0.9968。

行为变化

场景 改动前 改动后
transform 只支持单函数,复合链丢弃 按 CSS 顺序合成 4×4(含 rotate3d / skew / matrix3d)
matrix3d(...) 正交投影成 2D Layer.matrix3D 原值透传,仅真平面时降级为 matrix
transform-origin 烘焙进 matrix,无 anchor px / % / 关键字 / 三值深度 → anchor / anchorZ
动画 transform 逐帧烘焙支点 + 双层 wrapper x/y → matrix → matrix3D 三通道,关键帧不带支点
perspective 属性 丢弃 相机下推给直接子层(含 perspective-origin)

关键实现

模块 改动
HTMLTransform3D.{h,cpp}(新增) 函数列表解析、4×4 构造、origin 解析、平面判定与降级;静态与动画共用
HTMLLayerBuilder / HTMLAnimationBuilder 删除支点烘焙与结构性 wrapper,改为「平面 → matrix,非平面 → matrix3D + anchor」
HTMLStyleCascade / HTMLParserContext 新增 perspective / perspective-origin 解析与相机下推;<svg> 属性尺寸只作支点参考盒
tools/html-snapshot matrix3d 关键帧不再压平;函数式 perspective() 改写为父层相机;文本 span 不再重复带动画

评测(8 用例 × 5 帧,对比 Chromium 真值)

用例 承担 SSIM 像素差
43_rotate_offcenter_pivot 2D 旋转 + 非中心 pivot 0.9998 0.00%
44_scale_offcenter_pivot 2D 缩放 + 角点 pivot 1.0000 0.00%
45_rotate3d_center 纯 3D 0.9997 0.00%
46_rotate3d_offcenter_pivot 3D × 锚点 0.9998 0.00%
47_text_rotate_pivot 文本叶子 0.9770 0.05%
48_svg_rotate_pivot inline SVG 0.9999 0.00%
50_perspective_property 父级 perspective 属性 0.9968 0.07%

以上 7 例均值 0.9961、像素差 0.02%;8/8 动画均被捕获。

49_rotate3d_depth_pivot(支点带 30px 深度)为 0.9280 / 2.24%,是 CSS 语义本身的限制而非实现缺陷:CSS 让 transform-origin 连 perspective() 一起共轭(T(a)·P·L·T(-a)),而 PAGX / AE / Lottie 把投影留在支点之外(P·T(a)·L·T(-a));perspective-origin 只有 x/y 两个分量,无法表达"支点在平面外",因此任何换相机的写法都无法对齐。该用例作为已知差异保留。

测试

套件 结果
PAGFullTest_OpenGL 2850 全绿
tools/html-snapshot jest 730 全绿
cases/validate.sh 163 pass / 2 xfail / 2 fail(两个 fail 为既有)

兼容性与已知限制

项 说明
产物结构 旋转/缩放元素现在输出 anchor,纯 3D 输出 matrix3D,纯旋转动画少一层 → 结构敏感的基准需重新 seed
深度 pivot 见上文 49 的说明,属 CSS 语义限制

范围说明

不拆分 PR:3D 解析、anchor 迁移、perspective 属性、快照改写共用同一份姿态管线,拆开后任一半都无法独立验证;

@mite085
mite085 force-pushed the feature/liushihao_3d_anchor branch from 5f90058 to 02e7c32 Compare November 6, 2026 08:35
return origin;
}

std::string tokenX = tokens[0];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical|transform-origin 两值「垂直关键字在前」时 X/Y 互换

两值/三值这里无条件把 tokens[0] 当 X、tokens[1] 当 Y,只有单值才做了 top/bottom→Y 的修正;而 ResolveOriginAxis(:209) 又把 top→0、bottom→dimension 当作 X 值接受,于是不告警、直接给出错误支点。以 100×60 盒推演:bottom left→(100,0)(CSS 应为 (0,60))、top center→(0,30)(应为 (50,0))、top right→(0,60)(应为 (100,0))。同一函数还用于 perspective-origin(HTMLStyleCascade.cpp:806),消失点一起错。旧实现是「告警 + 回退中心」,新实现变成静默错误。

建议:先按轴归类(left/right→x,top/bottom→y,center 与长度按位置补位)再赋值;补 bottom left / top center / bottom right 20px 用例(现有用例只用了 right bottom,恰好正确)。

// The static pose is already un-pivoted (the pivot lives in `anchor`), so the keyframe baseline
// is exactly `layer->matrix` / `layer->matrix3D`.
const Matrix matrixChannelBaseline = baselineMatrix;
const Matrix3D matrix3DChannelBaseline = layer->matrix3D;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical|matrix3D 通道的 fill-mode 基线取 layer->matrix3D,静态 2D 变换在 delay/结束后被抹掉

通道路由用 staticPoseIs3D = !layer->matrix3D.isIdentity()(:1399);当元素有静态 2D matrix(如 transform: rotate(20deg))而关键帧含 3D 姿态时走 matrix3D 通道,但基线是 layer->matrix3D(单位阵)。ApplyFillMode(:636)会在 frame 0(delay 期)与 activeEnd+1(Once 结束后)插入单位阵 Hold,运行时 applyMatrix3D 以 mix=1 覆盖并 publish(LayerBuilder.cpp:420-440 / 495),静态 matrix 被彻底遮蔽。

触发:transform: rotate(20deg) + @keyframes{0%{rotateY(0)} 100%{rotateY(60deg)}},默认 fill-mode: none,播完后静态旋转永久丢失。

建议:基线改为 ComposeLayerTransform3DWithoutAnchor(layer)(LayerTransformUtils.h:69,本文件已 include),可同时覆盖静态 matrix3D / matrix / components 三种来源。

// A transform that carries `perspective()` is split so the projection can move onto a wrapper
// (`perspective` property), which is the encoding PAGX reproduces exactly.
const split = splitProjectiveTransform(boxTransform.transform);
const forwarded = split

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major|「跳过相机」分支在投影已被丢弃之后才回退,注释与文档与实际相反

这里 forwarded.transform 已是 split.affine(投影行已去掉),而 withPerspectiveCamera 的 depth-pivot / not-a-sized-div 分支只是 return html(:4719/:4724),注释却写 "the function form is kept instead"。关键帧路径更严重:rewritePerspectiveKeyframes(:2056)在任何元素渲染前就把投影拍平,元素若走 skip 分支,输出里就完全没有 perspective。

已确认实例:49_rotate3d_depth_pivot.html 用内联 transform-origin: 0px 80px 30px → depth-pivot skip,但其 keyframes 已被拍平,实际是正交投影,而非 spec 13.7 所述「只差 origin×projection 项、约几像素」。

建议:确认能包装成功后再剥离投影;关键帧改写前先确认所有引用该动画名的元素都可包装(或复制一份带投影的 keyframes);同时把 cameraSkips 的文案/列名改成真实语义。

const camera = split ? split.distance : 0;
return withStrippedTransform(el, () => render(el, parentRect, {
...opts, _strippedTransform: true, transform: boxTransform,
...opts, _strippedTransform: true, transform: forwarded, _cameraDistance: camera,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major|用「已剥离 transform/origin」的 computed 取相机几何:perspective-origin 被重置、深度守卫失效

getComputedStyle(el) 在 withStrippedTransform 闭包内求值,而该函数会清空内联 transform-origin(:2130)。于是 withPerspectiveCamera 拿到的 transformOrigin 是 50% 50%:①perspective-origin 被重置为盒中心,消失点偏移;②内联 transform-origin: … 30px 的 depth 读成 0,depth-pivot 守卫被绕过,本该回退的元素被强行包装。

建议:相机几何一律用剥离前的值(opts.transform.transformOrigin 就是剥离前读到的 computed origin),只改取值时机。

const computed = precomputed || getComputedStyle(el);
// Name the element's own box before the camera wrapper is added: an author `name` identifies the
// layer that carries the content and the animation, not the structural camera layer above it.
const named = applyLayerName(el, renderElementBox(el, parentRect, opts, computed), opts, computed);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major|静态 perspective() 元素的 name 落到相机包装层

renderElementBox 返回的已是包装后的 html,外层 applyLayerName 会把 name 插到第一个标签(相机 wrapper)上;内层因 _strippedTransform 直接 stripNameAnchor(:4631)。liftBoxOntoCameraWrapper 的「属性留在元素自身盒子」只对动画路径成立(name 先加、后包装)。结果 layer->name 指向无内容、无动画的结构层,与本次提交 "Keep the authored layer name on the element box under a camera wrapper" 相反;perspective-rewrite.test.js 只覆盖了动画路径。

建议:包装成功后把 name 从 wrapper 移交内层 div,或由内层 render 负责注入。

}
out->push_back(Trim(value.substr(nameStart, closeParen - nameStart + 1)));
index = closeParen + 1;
if (index < size && !std::isspace(static_cast<unsigned char>(value[index]))) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major|拒绝合法的「函数间无空格」写法,整条 transform 被丢弃

CSS <transform-list> 是 <transform-function>+,) 后不需要空白:transform:rotate(45deg)scale(2)(压缩后的 CSS 很常见)完全合法,这里判为 malformed;subset 阶段直接 DropProperty(HTMLSubsetPropertyTable.cpp:328),resolver 再告警一次。

另::552 用 value.find(')') 而非深度匹配,translateX(calc(100% - 10px)) 会被切成残片,诊断退化成 malformed。

建议:去掉空格强制;括号改深度匹配,可直接复用 HTMLDetail 的 SplitTopLevel* 系列。


// `ParseAngle` maps unparsable input to 0, which would silently turn `rotate(abc)` into a no-op, so
// the numeric prefix is validated first.
bool ParseRotationDegrees(const std::string& token, float& out) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major|角度单位只校验「数字前缀」,非法/错误单位被静默当度

suffix 未 ToLower、无 grad、未知单位一律 return v(HTMLDetail.cpp:577-588)。结果:rotate(45px)→45°(CSS 应整条声明失效)、rotate(100grad)→100°(应 90°)、rotate(0.5TURN)→0.5°(CSS 单位大小写不敏感,应 180°)。skewX/skewY/skew/rotate3d 全族共用 AngleAt。

建议:在此严格校验 suffix ∈ {'', deg, grad, rad, turn}(先小写),无单位仅允许 0。

// pure translation, so the runtime can apply `T(anchor) * pose * T(-anchor)` once, after the
// keyframes have been interpolated. A pose whose linear part is the identity (pure translation or
// pure projection) is unaffected by the pivot, so it keeps the legacy matrix-only form.
const bool needsAnchor = box.transform.originResolved && !IsLinearIdentity(pose);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major|「姿态→通道/锚点」的决策链在两个 Builder 中重复实现且已分叉

静态侧(本函数 697-734)与关键帧侧(HTMLAnimationBuilder.cpp:1250-1280、1372-1422)各自实现 PushPerspective → IsFlatTransform → IsInvertible → IsLinearIdentity → ToAffine2D/LiftMatrixToMatrix3D,行为已经不同:静态侧不可逆时丢弃整条 transform,关键帧侧只丢该 stop;基线处理也不同(见另一条 matrix3D 基线问题)。

建议:抽到 HTMLTransform3D.h 的纯函数(如 ResolvePosePlacement 返回 {flat, affine, pose3D, needsAnchor}),两个 Builder 只保留「写入 Layer / 映射 Channel」的差异。

diagnostics.warn("html: skewX expects 1 argument; got '" + token + "'");
return false;
}
*out = MakeShear(-degrees, 0.0f);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major|skewX/skew 的 X 分量符号与 CSS 及同仓 SVG 解析相反

CSS skewX(α) = matrix(1,0,tanα,1,0,0)(c = +tanα),这里传 -degrees 得到 c = -tanα;同仓 SVGTransformParser.cpp:130 对同一语法用 +tan,而 HTMLWriterUtils 按 matrix(a,b,c,d) 原样回写,HTML→PAGX→HTML 会镜像翻转。skewX 属 main 既有行为,但新增的 skew(θ) 沿用了同一错误,且 :132-133 的注释把它固化成「一直如此」。

现有 PAGXHTMLImporterTest.cpp:8770 只断言非单位矩阵,抓不住符号。建议:确认符号约定后统一本文件 / SVG 解析 / 回写三处,并补 c 的数值断言。

diagnostics.warn("html: rotate3d expects 3 axis numbers and 1 angle; ignored");
return false;
}
const float length = std::sqrt(x * x + y * y + z * z);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor|rotate3d 轴向量平方溢出时退化为均匀缩放

rotate3d(1e20,0,0,45deg):x*x 溢出为 inf → length = inf → 通过 > 1e-6 检查 → 归一化得 (0,0,0) → MakeRotationAxis 产出 cos·I,且被判为合法 3D 姿态写入 matrix3D。

建议:用 double 累加平方和,或显式校验 std::isfinite(length)。

for (const auto& ts : transformStops) {
matrix3DKeys.push_back({ts.first,
IsFlatTransform(ts.second)
? LiftMatrixToMatrix3D(ToAffine2D(ts.second), 0.0f)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor|平面奇异 stop 被 lift 进 matrix3D 后未做可逆校验

ParseCssTransformList 明确允许平面奇异姿态,静态侧也不校验;但整条动画一旦判为 3D,平面 stop 会被 LiftMatrixToMatrix3D 提升写入 matrix3D 通道,这条路径没有可逆检查。PAGXImporter.cpp:1820 对 matrix3D 关键帧强制可逆,失败即报错并替换为单位阵,HTML→PAGX→再导入会丢值(触发:同一动画混有 scale(0) 与任意 3D 关键帧)。

建议:lift 前校验可逆,不可逆时按 dropped 处理。


// CSS `perspective` projects the direct children, so the bag handed down carries it only one level.
HTMLInheritedStyle childInherited = inherited;
childInherited.parentPerspectivePx = box.perspectivePx;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor|body 上的 perspective 不会下推

convertContainer 在这里把 box.perspectivePx 传给子层,但 convertBody(:647-704)没有对应的 childInherited 构造,body { perspective: 800px } 被完全静默丢弃,所有子元素的 3D 变换按无相机渲染。这是本次新增能力里唯一没接上的容器路径。

建议:convertBody 补一段与这里相同的下推逻辑。

"has no resolved offset, so the perspective centre follows its layout position");
warnedFlowChild = true;
}
box.parentPerspectiveOriginX = originX - (std::isnan(box.leftPx) ? 0.0f : box.leftPx);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor|相机原点换算只减 leftPx/topPx

①absolute 子元素的 left 相对父的 padding box,而 perspective-origin 在父的 border box 里度量,应减 borderWidth + left,现在少减一个边框宽度;②right/bottom 定位时 leftPx 为 NaN 被当作 0,消失点落到父左上角,且因 box.absolute 为真连 flow 告警也不会触发。

建议:能确定偏移时用 borderWidth + left;leftPx 不可用时并入 flow child 的告警。

// The declaring box had no absolute size; the projection step reports that once.
return;
}
// The camera only changes the result when the child's pose leaves the z = 0 plane (or its pivot

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor|只有动画里含 3D 的子层不会触发相机告警

cameraAffectsPose 只看静态 transform:元素仅通过 @keyframes 得到 3D 姿态时 box.transform.valid == false,不告警;但 buildForElement 仍会把 flow 近似原点推进每一帧,等于「已知会偏却不报」,与 spec 宣称的 one diagnostic 不符。

建议:判据扩展到「静态 transform 或含 transform 动画」,或在首次折叠投影时告警一次。

const auto& style = _styleCascade->getResolvedStyle(entry.element);
_animationBuilder->buildForElement(
style, entry.layer, _styleCascade->getTransformReferenceSize(entry.element),
entry.parentPerspectivePx, entry.parentPerspectiveOriginX, entry.parentPerspectiveOriginY);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor|inline-SVG 形状动画完全不带相机

注意下面的 buildForInlineSvgShape 调用没有相机参数(signature 里也没有)。父级声明 perspective 时,<svg> 自身无 transform 就不会带 P,形状动画的投影完全丢失。

建议:明确纳入,或写进 spec 13.7 的限制列表。

parsed.originResolved = origin.resolved;
if (!origin.resolved) {
_diagnostics.warn("html: transform-origin '" + originValue + "' on <" + elementName +
"> cannot be resolved against the box size; pivoting at the top-left "

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor|origin 无法解析时无条件告警,纯平移也会误报

origin.resolved == false 就告警,但线性部分为单位阵的姿态(纯平移/纯投影)支点对结果没有影响,HTMLLayerBuilder.cpp:717 已用 IsLinearIdentity 表达这一点。<div style="transform:translateX(10px)"> 无显式宽高时会报 transform-origin '' on <div> …(空串也不友好)。

建议:加 && !IsLinearIdentity(pose),文案回退 50% 50% (default)。

// supported single-function subset; compound chains and matrix(...) forms are dropped.
{"transform", PropAction::Transform, &TransformTransform, nullptr},
{"transform-origin", PropAction::Transform, &TransformTransformOrigin, nullptr},
// `transform-style: preserve-3d` maps onto `Layer.preserve3D`; the flat default is a no-op.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor|多处过时/矛盾注释

本表上方(:445-447)仍写「handler 只支持单函数子集,复合链与 matrix(...) 会被丢弃」,与 TransformTransform 现在的实现相反;同文件 :344-348 还写「downstream 只理解默认 50% 50%」,已被新的 ResolveTransformOrigin 推翻;:477 的「Explicit drops」小节标题压在两行 Keep 之上。另有 HTMLStyleCascade.cpp:793「perspective-origin 无 PAGX 对应」、HTMLAnimationBuilder.cpp:1663「origin 逐帧烘焙 / wrapper」、HTMLAnimationBuilder.h:35-47 通道列表缺 matrix3D。其中两处描述的机制已被本 PR 删除。

建议:逐条更新。

Comment thread spec/html_subset.md
Disallowed (warning + skip): `border-{top,right,bottom,left}`, per-corner `border-*-radius`,
`outline`, `perspective`, geometric `clip-path` forms (`inset`/`circle`/`ellipse`/`polygon`/
`path`), and compound / non-`matrix3d` 3D `transform` chains.
`outline`, the `perspective` property (use `transform: perspective()` instead), and geometric

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor|spec 与实现矛盾:perspective 属性

这里仍把 perspective 属性列为「禁用(请改用 transform: perspective())」,但本 PR 已把 HTMLSubsetPropertyTable 中的 perspective 改为 Keep 并实现了解析,同一份 spec 的 13.7 又要求 snapshot 把 perspective() 改写成父级 perspective 属性,并在用例注释里作为做法引用。三处不能同时成立。

建议:从 Disallowed 删除,并把 perspective / perspective-origin 加入 4.4 支持表。中文版 :188-189 同步。

| `transform` single fn + origin | supported | `13-transform__rotate/scale/scale-xy/skew-x/translate/matrix/transform-origin-center` |
| `transform` chained / 3d / perspective | dropped | `13-transform__rotate-chained`, `...rotate3d`, `...perspective` |
| `transform` functions + origin | supported; `transform-origin` lands on `Layer.anchor` | `13-transform__rotate/scale/scale-xy/skew-x/translate/matrix/transform-origin-center` |
| `transform-origin` px / % / keyword | honoured as the pivot (`Layer.anchor`) | `13-transform__transform-origin-px-noncenter`, `...percent`, `...keywords`, `...skew-corner` |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor|引用了 6 个不存在的用例

13-transform__transform-origin-px-noncenter / ...percent / ...keywords / ...skew-corner / ...transform-origin-three-value / ...transform-origin-translate 在 resources/html/cases/13-transform/ 与 manifest.csv 中均不存在(该目录只有 10 个文件,只有 transform-origin-center.html;manifest 187 行 ↔ 187 个 html 是一致的)。

建议:补用例并登记 manifest,或改回真实覆盖范围。

return std::isfinite(out);
}

std::vector<std::string> SplitCommaArgs(const std::string& args) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor|重复实现仓库已有的分隔工具

SplitCommaArgs 与 HTMLDetail.cpp:49 SplitTopLevelCommas 等价,SplitWhitespaceTokens 与 HTMLDetail.cpp:68 SplitTopLevelWhitespace 等价(后者还处理括号嵌套),而本文件已 include HTMLDetail.h;另 ParseStrictFloat 与 HTMLStyleCascade.cpp:70 ParseScalarFloat 重复。

建议:删掉这 34 行副本直接用 HTMLDetail 的实现,顺带改善 calc 嵌套处理。

#include <cctype>
#include <cmath>
#include <cstdlib>
#include <utility>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit|<utility> 未使用(全文件无 std::move/pair/swap)。另见 test/src/PAGXHTML3DTest.cpp:47-48 的 Point.h、ExporterUtils.h 同样未被引用。

* its bottom row set to `(0, 0, -1/distance, 1)`. `Layer.perspective` builds the same matrix, but a
* CSS `perspective` declaration lives on the parent and must be pushed into the child pose instead.
*/
Matrix3D PerspectiveMatrix(float distance);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit|PerspectiveMatrix 无需导出:唯一调用方是同文件的 PushPerspective(HTMLBoxAttributes.h:323 只是注释引用),可下沉匿名命名空间。IsInvertible(:93) 仅是 IsInvertibleMatrix3D 的一行转发,若无额外语义也可直接调用原名。


namespace {

constexpr float kDegToRad = 3.14159265358979323846f / 180.0f;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit|kDegToRad 与已 include 的 Matrix3DDecompose.h:30 DEGREES_TO_RADIANS 重复,且精度不同(float vs double),建议复用后者。

// `translate` stops at two lengths, so only `translate3d` reaches the depth axis.
if (!transformArgs.LengthAt(0, x)) {
diagnostics.warn("html: translate first argument '" + parts[0] +
"' is not a px length; ignored");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit|长度失败会产生两条诊断:parseAbsoluteLengthPx 已在单位非法时 warn(HTMLValueParser.cpp:341),这里再 warn 一次,translate(10foo) 会输出两条。建议加静默变体,或在调用方去重(仓库里的 ReferenceBoxResolutionDoesNotDuplicateDiagnostics 说明团队在意这一点)。

// Composing rotations leaves values like 0.99999994 on the Z axis, so the plane test needs a
// tolerance instead of exact comparisons.
return NearlyZero(matrix.getRowColumn(2, 0)) && NearlyZero(matrix.getRowColumn(2, 1)) &&
NearlyOne(matrix.getRowColumn(2, 2)) && NearlyZero(matrix.getRowColumn(2, 3)) &&

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit|IsFlatTransform 与 HasPerspective 对同一语义用了两套阈值:这里 1e-6 容差,LayerTransformUtils.h:36 用精确 != 0,perspective(1000000px) 两者结论相反。建议共用常量,或在注释里说明阈值来源。

// so the transform write and the animation registration read the same value regardless of which
// element happens to be converting when they run. `parentPerspectiveOrigin*` is the parent's
// `perspective-origin` re-expressed in this element's local coordinates.
float parentPerspectivePx = 0.0f;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit|同名字段两种坐标系:HTMLInheritedStyle:78-80 与这里都有 parentPerspectiveOriginX/Y,前者是父本地坐标、后者是本元素本地坐标,仅靠注释区分;转换发生在 HTMLParserContext.cpp:74-75 且不改名,后续容易误用。建议 box 侧改为 localPerspectiveOriginX/Y。

return parts.length === 2 ? parts[0] + ' ' + parts[1] : '50% 50%';
}

function withPerspectiveCamera(el, html, opts, computed) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit|el 参数未使用;另外 cameraDistanceForElement(:4672)对每个元素都做 String().split(',').map 并调用 pagxAnimCameras(),animationName 为空或 none 时可提前返回。

// Per-glyph / TextModifier / TextPath container host: convert the whole container into a
// single semantic Text layer instead of recursing into its PUA glyph spans.
HTMLBoxAttributes box = _styleCascade->computeBoxAttributes(element);
ApplyParentPerspective(box, inherited.parentPerspectivePx, inherited.parentPerspectiveOriginX,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit|四处完全相同的 ApplyParentPerspective 调用(731 / 756 / 763 / 772),可挪到 computeBoxAttributes 之后、分支之前统一调用一次(<br> 分支除外),减少漏调风险。

float heightPct = NAN;
ParseSizingDimension(LookupProperty(props, "width"), reference.widthPx, widthPct);
ParseSizingDimension(LookupProperty(props, "height"), reference.heightPx, heightPct);
if (element == nullptr || element->name != "svg") {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit|不可达的空指针防御:这里与 :474 的 element == nullptr 判断不可达——:444 的 getResolvedStyle(element) 与 :484 的 element->findAttribute 已无条件解引用。建议删除或改为断言。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants