Skip to content

Add PPTExporter with PPTX export support for PAGX documents#3361

Open
OnionsYu wants to merge 64 commits intoTencent:mainfrom
OnionsYu:feature/onionsyu_export_ppt2
Open

Add PPTExporter with PPTX export support for PAGX documents#3361
OnionsYu wants to merge 64 commits intoTencent:mainfrom
OnionsYu:feature/onionsyu_export_ppt2

Conversation

@OnionsYu
Copy link
Copy Markdown
Contributor

@OnionsYu OnionsYu commented Apr 3, 2026

新增 PPT 导出功能,支持将 PAGX 文档导出为 PPTX 格式。

主要变更:

  • 新增 PPTExporter,支持形状、文本、图片填充、渐变、描边、阴影、蒙版等元素导出为 PPTX
  • 提取 ExporterUtils 公共工具模块,供 SVG 和 PPT 导出器共用
  • 重构 SVGExporter 和 SVGTextLayout 复用 ExporterUtils 中的共享方法
  • 在 CLI CommandExport 中集成 PPT 导出命令
  • 新增 PPT 导出测试用例

@OnionsYu OnionsYu force-pushed the feature/onionsyu_export_ppt2 branch from 1bc505f to cdaf2a3 Compare April 3, 2026 08:07
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 3, 2026

Codecov Report

❌ Patch coverage is 89.35532% with 284 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.23%. Comparing base (ed723d4) to head (e957a27).

Files with missing lines Patch % Lines
src/pagx/ppt/PPTExporter.cpp 85.34% 83 Missing and 42 partials ⚠️
src/pagx/utils/ExporterUtils.cpp 75.81% 50 Missing and 39 partials ⚠️
src/pagx/xml/XMLBuilder.h 78.21% 23 Missing and 16 partials ⚠️
src/cli/CommandExport.cpp 76.74% 5 Missing and 5 partials ⚠️
src/pagx/ppt/PPTContourUtils.cpp 93.19% 1 Missing and 9 partials ⚠️
src/pagx/ppt/PPTBoilerplate.cpp 95.23% 0 Missing and 4 partials ⚠️
src/pagx/ppt/PPTGeomEmitter.cpp 96.61% 2 Missing and 2 partials ⚠️
src/pagx/svg/SVGExporter.cpp 88.23% 2 Missing ⚠️
src/pagx/ppt/PPTWriterContext.h 97.43% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3361      +/-   ##
==========================================
+ Coverage   78.22%   80.23%   +2.01%     
==========================================
  Files         529      540      +11     
  Lines       40527    45257    +4730     
  Branches    12234    12752     +518     
==========================================
+ Hits        31701    36313    +4612     
- Misses       6349     6376      +27     
- Partials     2477     2568      +91     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@OnionsYu OnionsYu force-pushed the feature/onionsyu_export_ppt2 branch from 56f6b20 to 664c2dd Compare April 13, 2026 02:54
@OnionsYu OnionsYu force-pushed the feature/onionsyu_export_ppt2 branch from df0a742 to 4a3415f Compare April 13, 2026 06:33
Copy link
Copy Markdown
Collaborator

@shlzxjp shlzxjp left a comment

Choose a reason for hiding this comment

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

Code Review 第二轮:之前的26条评论中绝大多数已确认修复(包括 const_cast、静态常量命名、Xform初始化、UTF-8字符数、lambda消除、幻灯片等比缩放、zipWrite返回值检查等),修复质量很好。本轮新发现 12 个问题(1 Critical、3 Major、8 Minor),详见行级评论。

if (PAG_BUILD_SVG)
set(PAG_BUILD_PAGX ON)
endif ()
if (PAG_BUILD_PPT)
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] PAG_BUILD_CLI(第97行)自动启用了 PAG_BUILD_SVG 但未启用 PAG_BUILD_PPT。而 CommandExport.cpp 无条件 #include "pagx/PPTExporter.h" 并调用 PPTExporter::ToFile()。当以 -DPAG_BUILD_CLI=ON -DPAG_BUILD_PPT=OFF 构建时会编译/链接失败。建议在97行的 if (PAG_BUILD_CLI) 块中补上 set(PAG_BUILD_PPT ON),或在 CommandExport.cpp 中用 #ifdef PAG_BUILD_PPT 保护 PPT 代码路径。

GetImageDPI(pattern->image, &imgDpiX, &imgDpiY);
double dpiCorrX = static_cast<double>(imgDpiX) / 96.0;
double dpiCorrY = static_cast<double>(imgDpiY) / 96.0;
auto sx = static_cast<int>(std::round(M.a * dpiCorrX * 100000.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.

[Major] Tiling 分支直接用 M.aM.d 作为缩放因子。当 pattern->matrix 含旋转时(如90度),M.a=0M.d=0 导致 sx=0sy=0。非 tiling 分支的 ComputeImagePatternRect(第151行)已修复为 sqrt(a*a+b*b) 提取缩放,但此处未同步。建议统一使用相同的缩放提取方式。

void ReverseContour(PathContour& c) {
size_t n = c.segs.size();
Point originalStart = c.start;
c.start = SegEndpoint(c.segs[n - 1]);
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] ReverseContour 未防御空 segs。当 n=0c.segs[n-1] 访问 segs[SIZE_MAX] 触发未定义行为。虽然当前内部调用者有 segs.empty() 守卫,但此函数是头文件中的公开接口,外部调用者可能不做检查。建议入口加 if (c.segs.empty()) return;

if (memcmp(data, kPNGSignature, 8) != 0) {
return false;
}
*width = static_cast<int>((data[16] << 24) | (data[17] << 16) | (data[18] << 8) | data[19]);
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] PNG 维度解析存在有符号整数溢出(未定义行为)。data[16]uint8_t,隐式提升为 int<< 24 在高位 >=0x80 时超出 int 范围。第329-330行的 GetPNGDPI 也有相同问题。建议先转 uint32_t 再移位:static_cast<uint32_t>(data[16]) << 24。对比第292行 WebP 解析已正确使用了 static_cast<uint32_t>

std::cerr << "pagx export: error: failed to load '" << options.inputFile << "'\n";
return 1;
}
if (!document->errors.empty()) {
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] ExportToPPT 缺少 hasUnresolvedImports() 检查。ExportToSVG(第145行)在有未解析导入时报错退出,但 PPT 导出完全没有此检查,可能导致不完整的 PPTX 被静默导出。建议添加与 SVG 相同的检查逻辑。

std::shared_ptr<tgfx::Data> RenderMaskedLayer(const std::shared_ptr<tgfx::Layer>& root,
const std::shared_ptr<tgfx::Layer>& targetLayer) {
auto globalBounds = targetLayer->getBounds(root.get(), true);
auto device = tgfx::GLDevice::Make();
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] RenderMaskedLayer 每次调用都创建新的 GL 设备和上下文。文档中有多个 masked layer 时会反复创建/销毁 GL 上下文,性能开销较大。建议由调用方创建设备传入复用。

using pag::DegreesToRadians;
using pag::FloatNearlyZero;

static const uint8_t kPNGSignature[8] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
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] 静态常量 kPNGSignature 使用了 k 前缀,不符合项目命名规范(全大写下划线无 k 前缀),应改为 PNG_SIGNATURE。第323行也有同样问题。

* elements when glyph outline data is unavailable. The default value is false.
*/
bool convertTextToPath = true;
bool convertTextToPath = false;
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] convertTextToPath 默认值从 true 改为 false 是用户可感知的行为变更(Breaking Change)。所有通过 C++ API 调用 SVGExporter::ToFile() 且未显式设置此选项的下游代码,输出会从 path 元素变为 text 元素。建议在 PR 描述中明确标注。

return seg.pts[0];
}

float ComputeSignedArea(const PathContour& contour) {
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] ComputeSignedAreaPointInsideContour(第77行)均为端点多边形近似,非精确几何计算。对于少量高曲率 bezier 段构成的路径(如4段 cubic 组成的圆),winding 方向和包含关系可能误判。建议在函数注释中说明此为端点近似的限制。

}
int idx = static_cast<int>(_images.size()) + 1;
std::string relId = "rId" + std::to_string(_nextRelId++);
bool jpeg = IsJPEG(data->bytes(), data->size());
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] WebP 图片会以 .png 扩展名嵌入 PPTX(addImage 中只区分 JPEG 和非 JPEG 两条路径)。PowerPoint 不原生支持 WebP,且 GenerateContentTypes 中也缺少 WebP 的 ContentType 声明。建议识别 WebP 格式并进行格式转换或添加相应的 content type。

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.

3 participants