feat(export): 多格式文档导出服务(HTML / PDF / DOCX + 函数图像渲染) #40

Closed
yxx wants to merge 0 commits from feat/export-service into main
Collaborator

概述

Export Service:把笔记或未保存的 Markdown 文本导出为 HTML / PDF / DOCX 的完整链路——创建即返回 queued、后台 asyncio.Task 渲染、产物 24h 过期、轮询 GET /api/exports/{job_id} 下载。

范围(相对 main:28 文件 / +4836 行)

  • Document AST:mistune renderer="ast" → DocumentNode 内部协议 + DocumentExporter Protocol。
  • 三种导出器:
    • HtmlExporter:完整 HTML5 + function-plot 静态 SVG 内嵌。
    • PdfExporter:reportlab,function-plot 内嵌矢量 Drawing(与 SVG 同源共享几何),中文字体 STSong-Light,列表/表格/引用/行内语义。
    • DocxExporter:python-docx,标题/列表/链接/表格,中文经 w:eastAsia=宋体。
  • Function Plot:白名单 AST 表达式解析(parser.py)、共享几何 compute_geometry(render.py)、reportlab 矢量后端(render_reportlab.py)、StaticRenderer 内部契约(renderer.py)。
  • 资源上限:输入 200k 字符、单图 ≤16 表达式、文档 ≤16 图、累计 AST 节点预算 8000、并发渲染 2、产物 20MB。
  • HTTP 契约与路由:app/contracts.py + app/routes.py 的 /api/exports*(创建/查询/下载/取消)。
  • 测试与文档:tests/test_export.py、tests/test_plot.py,docs/development/Export开发说明.md、接口契约 §10。

审阅修复记录(多轮)

  • 引用块正文丢失、嵌套列表顺序与排队取消
  • 列表项行内语义丢失与混合嵌套顺序重排
  • 函数图像数量/复杂度预算上限
  • PDF 函数图像矢量内嵌
  • 显式 range 外曲线裁剪 + PDF 纵轴标签旋转修正
  • 渐近点落在采样点之间时断段(避免贯穿绘图区的伪竖线)

验证结果

  • 后端完整测试:720 passed
  • git diff --check:通过
  • 与当前远程 main:无合并冲突
## 概述 Export Service:把笔记或未保存的 Markdown 文本导出为 HTML / PDF / DOCX 的完整链路——创建即返回 `queued`、后台 asyncio.Task 渲染、产物 24h 过期、轮询 `GET /api/exports/{job_id}` 下载。 ## 范围(相对 main:28 文件 / +4836 行) - **Document AST**:mistune `renderer="ast"` → `DocumentNode` 内部协议 + `DocumentExporter` Protocol。 - **三种导出器**: - `HtmlExporter`:完整 HTML5 + function-plot 静态 SVG 内嵌。 - `PdfExporter`:reportlab,function-plot 内嵌矢量 `Drawing`(与 SVG 同源共享几何),中文字体 STSong-Light,列表/表格/引用/行内语义。 - `DocxExporter`:python-docx,标题/列表/链接/表格,中文经 `w:eastAsia=宋体`。 - **Function Plot**:白名单 AST 表达式解析(`parser.py`)、共享几何 `compute_geometry`(`render.py`)、reportlab 矢量后端(`render_reportlab.py`)、`StaticRenderer` 内部契约(`renderer.py`)。 - **资源上限**:输入 200k 字符、单图 ≤16 表达式、文档 ≤16 图、累计 AST 节点预算 8000、并发渲染 2、产物 20MB。 - **HTTP 契约与路由**:`app/contracts.py` + `app/routes.py` 的 `/api/exports*`(创建/查询/下载/取消)。 - **测试与文档**:`tests/test_export.py`、`tests/test_plot.py`,`docs/development/Export开发说明.md`、接口契约 §10。 ## 审阅修复记录(多轮) - 引用块正文丢失、嵌套列表顺序与排队取消 - 列表项行内语义丢失与混合嵌套顺序重排 - 函数图像数量/复杂度预算上限 - PDF 函数图像矢量内嵌 - 显式 range 外曲线裁剪 + PDF 纵轴标签旋转修正 - 渐近点落在采样点之间时断段(避免贯穿绘图区的伪竖线) ## 验证结果 - 后端完整测试:`720 passed` - `git diff --check`:通过 - 与当前远程 `main`:无合并冲突
Owner

审阅结论

暂不建议合并,仍有以下问题需要修复:

P2:显式范围外的曲线未裁剪

函数图像采样仅排除了非有限坐标,没有将曲线裁剪到绘图区。

例如设置 range: -1, 1 并绘制 y = 10*x 时,生成的曲线坐标会远超图表和页面范围。ReportLab 不会像 SVG viewport 一样自动裁剪,可能导致曲线覆盖 PDF 中的其他内容。

建议:

  • 将线段裁剪到绘图矩形范围内,或在 ReportLab 渲染层设置裁剪路径。
  • 增加曲线超出显式 range 时的回归测试。

P2:PDF 纵轴标签被旋转到画布之外

纵轴标签先使用绝对坐标定位,随后又以相同坐标作为旋转中心,造成坐标重复变换。实测标签边界落在负数横坐标区域,因此导出的 PDF 中无法正常显示纵轴标签。

建议:

  • 使用组内局部坐标创建文本,再对分组执行旋转和平移。
  • 增加断言,确保纵轴标签的最终边界位于 Drawing 范围内。

P3:PDF 导出器说明已经过时

当前实现已经支持把 Function Plot 作为矢量图嵌入 PDF,但 pdf.py 的模块说明仍称函数图像使用占位内容,请同步更新。

已确认修复

上一轮提出的问题均已解决:

  • PDF 混合嵌套列表顺序正确。
  • PDF/DOCX 列表项保留加粗、链接等行内语义。
  • Function Plot 静态渲染的文档状态已经更新。

验证结果

  • 后端完整测试:717 passed
  • git diff --check:通过
  • 与当前 main:无合并冲突

另外,本 PR 后续加入了 PDF 函数图像矢量渲染,但标题和描述尚未体现,测试数量仍写为 711。建议修复上述问题后同步更新 PR 描述和测试结果。

## 审阅结论 暂不建议合并,仍有以下问题需要修复: ### P2:显式范围外的曲线未裁剪 函数图像采样仅排除了非有限坐标,没有将曲线裁剪到绘图区。 例如设置 `range: -1, 1` 并绘制 `y = 10*x` 时,生成的曲线坐标会远超图表和页面范围。ReportLab 不会像 SVG viewport 一样自动裁剪,可能导致曲线覆盖 PDF 中的其他内容。 建议: - 将线段裁剪到绘图矩形范围内,或在 ReportLab 渲染层设置裁剪路径。 - 增加曲线超出显式 `range` 时的回归测试。 ### P2:PDF 纵轴标签被旋转到画布之外 纵轴标签先使用绝对坐标定位,随后又以相同坐标作为旋转中心,造成坐标重复变换。实测标签边界落在负数横坐标区域,因此导出的 PDF 中无法正常显示纵轴标签。 建议: - 使用组内局部坐标创建文本,再对分组执行旋转和平移。 - 增加断言,确保纵轴标签的最终边界位于 Drawing 范围内。 ### P3:PDF 导出器说明已经过时 当前实现已经支持把 Function Plot 作为矢量图嵌入 PDF,但 `pdf.py` 的模块说明仍称函数图像使用占位内容,请同步更新。 ## 已确认修复 上一轮提出的问题均已解决: - PDF 混合嵌套列表顺序正确。 - PDF/DOCX 列表项保留加粗、链接等行内语义。 - Function Plot 静态渲染的文档状态已经更新。 ## 验证结果 - 后端完整测试:`717 passed` - `git diff --check`:通过 - 与当前 `main`:无合并冲突 另外,本 PR 后续加入了 PDF 函数图像矢量渲染,但标题和描述尚未体现,测试数量仍写为 711。建议修复上述问题后同步更新 PR 描述和测试结果。
Kronecker closed this pull request 2026-09-06 22:43:37 +08:00
Author
Collaborator

回复:本轮三条意见已在当前 head 修复

这三条意见与上一轮(PR #39)一致,且均已在本 PR 的 head edc41fd(fix(export): 裁剪超出范围的曲线并修正 PDF 纵轴标签)中修复;审阅到的内容似乎仍是旧提交状态:

  • P2 曲线未裁剪 → app/plot/render.py 已在共享几何层用 Liang-Barsky 把折线裁剪到绘图矩形(_PLOT_X0/Y0 ~ _PLOT_X1/Y1),新增 test_compute_geometry_clips_curves_to_plot_rect。
  • P2 纵轴标签旋转出画布 → app/plot/render_reportlab.py 改为组内局部坐标 String(0,0) + 先 translate 后 rotate(得到 T·R),标签边界回到 Drawing 内,新增 test_render_reportlab_ylabel_within_drawing_bounds。
  • P3 pdf.py 模块说明过时 → 已更新为「function_plot 内嵌为矢量图,mermaid 保留源码占位」。

完整测试 719 passed,PR 标题与描述已同步(不再是 711)。烦请对当前 head edc41fd 重新审阅。

## 回复:本轮三条意见已在当前 head 修复 这三条意见与上一轮(PR #39)一致,且均已在本 PR 的 head `edc41fd`(`fix(export): 裁剪超出范围的曲线并修正 PDF 纵轴标签`)中修复;审阅到的内容似乎仍是旧提交状态: - **P2 曲线未裁剪** → `app/plot/render.py` 已在共享几何层用 Liang-Barsky 把折线裁剪到绘图矩形(`_PLOT_X0/Y0` ~ `_PLOT_X1/Y1`),新增 `test_compute_geometry_clips_curves_to_plot_rect`。 - **P2 纵轴标签旋转出画布** → `app/plot/render_reportlab.py` 改为组内局部坐标 `String(0,0)` + 先 `translate` 后 `rotate`(得到 `T·R`),标签边界回到 Drawing 内,新增 `test_render_reportlab_ylabel_within_drawing_bounds`。 - **P3 pdf.py 模块说明过时** → 已更新为「function_plot 内嵌为矢量图,mermaid 保留源码占位」。 完整测试 **719 passed**,PR 标题与描述已同步(不再是 711)。烦请对当前 head `edc41fd` 重新审阅。
Kronecker reopened this pull request 2026-09-06 23:12:07 +08:00
Owner

审阅结论

暂不建议合并,渐近线问题。

P2:渐近线会被错误连接

当前实现只在采样结果为非有限值时断开折线。如果渐近点恰好位于两个采样点之间,两侧采样结果仍是有限值,随后会被 Liang-Barsky 算法裁剪成贯穿整个绘图区的伪线段。

已复现:

domain: -1, 1
range: -10, 10
y = 1/(x-0.013)

该配置会生成一条从绘图区底部延伸到顶部的错误竖线。

建议:

  • 在裁剪前增加曲线不连续性判断或自适应细分。
  • 增加渐近点不与固定采样点重合的回归测试。
  • 测试除了验证坐标位于绘图区,还应验证不存在跨越上下边界的伪连接线段。

PR 范围说明

本 PR 实际包含整个 Export Service:

  • 28 个文件发生变化
  • 约 4804 行新增内容
  • 包含 HTML、PDF、DOCX 导出、Document AST、Function Plot 解析与渲染等功能

但当前标题和描述主要说明曲线裁剪及纵轴标签修复,无法准确反映实际合并范围。建议同步补全 PR 标题和描述,便于后续审阅、变更追踪和问题复盘。

验证结果

  • 当前 head:edc41fd
  • 后端完整测试:719 passed
  • git diff --check:通过
  • 与当前远程 main:无合并冲突
  • 上一轮的曲线越界、PDF 纵轴标签和过时模块说明问题均已修复
## 审阅结论 暂不建议合并,渐近线问题。 ### P2:渐近线会被错误连接 当前实现只在采样结果为非有限值时断开折线。如果渐近点恰好位于两个采样点之间,两侧采样结果仍是有限值,随后会被 Liang-Barsky 算法裁剪成贯穿整个绘图区的伪线段。 已复现: ```text domain: -1, 1 range: -10, 10 y = 1/(x-0.013) ``` 该配置会生成一条从绘图区底部延伸到顶部的错误竖线。 建议: - 在裁剪前增加曲线不连续性判断或自适应细分。 - 增加渐近点不与固定采样点重合的回归测试。 - 测试除了验证坐标位于绘图区,还应验证不存在跨越上下边界的伪连接线段。 ## PR 范围说明 本 PR 实际包含整个 Export Service: - 28 个文件发生变化 - 约 4804 行新增内容 - 包含 HTML、PDF、DOCX 导出、Document AST、Function Plot 解析与渲染等功能 但当前标题和描述主要说明曲线裁剪及纵轴标签修复,无法准确反映实际合并范围。建议同步补全 PR 标题和描述,便于后续审阅、变更追踪和问题复盘。 ## 验证结果 - 当前 head:`edc41fd` - 后端完整测试:`719 passed` - `git diff --check`:通过 - 与当前远程 `main`:无合并冲突 - 上一轮的曲线越界、PDF 纵轴标签和过时模块说明问题均已修复
Kronecker closed this pull request 2026-09-06 23:18:10 +08:00
Author
Collaborator

回复

P2 渐近线错误连接已修复(head 4276cb7):

  • app/plot/render.py 采样阶段新增断段:相邻有限采样点分居可见范围上下两侧(一个 < ymin、一个 > ymax)时判定中间夹着竖直渐近线,先断段再裁剪,避免被 Liang-Barsky 裁剪成贯穿绘图区的伪竖线。
  • 新增回归测试 test_compute_geometry_breaks_at_asymptote(复现用例 domain:-1,1 range:-10,10 y=1/(x-0.013)),断言不存在跨越上下边界的伪连接线段。

PR 标题/描述已补全为整个 Export Service 范围(HTML / PDF / DOCX + 函数图像渲染,28 文件 / +4836 行)。

完整测试 720 passed。

## 回复 **P2 渐近线错误连接已修复**(head `4276cb7`): - `app/plot/render.py` 采样阶段新增断段:相邻有限采样点分居可见范围上下两侧(一个 `< ymin`、一个 `> ymax`)时判定中间夹着竖直渐近线,先断段再裁剪,避免被 Liang-Barsky 裁剪成贯穿绘图区的伪竖线。 - 新增回归测试 `test_compute_geometry_breaks_at_asymptote`(复现用例 `domain:-1,1 range:-10,10 y=1/(x-0.013)`),断言不存在跨越上下边界的伪连接线段。 **PR 标题/描述已补全**为整个 Export Service 范围(HTML / PDF / DOCX + 函数图像渲染,28 文件 / +4836 行)。 完整测试 **720 passed**。
yxx changed title from fix(export): 裁剪超出范围的曲线并修正 PDF 纵轴标签(复审) to feat(export): 多格式文档导出服务(HTML / PDF / DOCX + 函数图像渲染) 2026-09-07 00:00:24 +08:00
yxx reopened this pull request 2026-09-07 00:02:40 +08:00
Kronecker closed this pull request 2026-09-07 00:03:45 +08:00

Pull request closed

This pull request cannot be reopened because the branch was deleted.
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Kronecker/NotesAgentic#40