feat/export-service
main
Export Service:把笔记或未保存的 Markdown 文本导出为 HTML / PDF / DOCX 的完整链路——创建即返回 queued、后台 asyncio.Task 渲染、产物 24h 过期、轮询 GET /api/exports/{job_id} 下载。
queued
GET /api/exports/{job_id}
renderer="ast"
DocumentNode
DocumentExporter
HtmlExporter
PdfExporter
Drawing
DocxExporter
w:eastAsia=宋体
parser.py
compute_geometry
render.py
render_reportlab.py
StaticRenderer
renderer.py
app/contracts.py
app/routes.py
/api/exports*
tests/test_export.py
tests/test_plot.py
docs/development/Export开发说明.md
720 passed
git diff --check
实现 Export Service 完整生命周期:mistune AST → Document AST → HtmlExporter 渲染完整 HTML5,异步任务注册表 + 取消 + 24h 产物过期。新增 5 个 /api/exports 端点与 15 项测试;pdf/docx 与函数图像静态渲染留待后续 PR。
- P1 链接/图片 URL 协议白名单校验,危险协议降级为纯文本 + warning - P2 图片 AST 字段映射(src=attrs.url,alt 取 children 文本) - P2 原始 HTML 块转义保留,正文不丢失 + warning - P2 过期/淘汰/重启清理导出产物文件 - P2 解析与渲染移入 asyncio.to_thread,运行中取消生效 - P2 function-plot 围栏别名补全 - 回归测试覆盖全部修复 Co-Authored-By: Claude Code <noreply@anthropic.com>
- 新增 app/plot 包:白名单表达式解析(ast 无 eval)+ FunctionPlot 模型 + 静态 SVG 渲染 - HtmlExporter 的 function_plot 节点解析并内嵌 SVG,解析失败回退占位并转诊断 - 新增 test_plot.py(13 个测试)覆盖表达式安全、指令解析、SVG 输出与导出链路集成 Co-Authored-By: Claude Code <noreply@anthropic.com>
- P1 浮点刻度死循环:_ticks 改为有上限的整数索引推进并校验步长推进 - P2 求值异常:白名单函数校验参数数量;负数底非整数指数按断点处理;采样容错复数 - P2 无效纵轴范围:退化/非有限 range 丢弃并自动采样重算;渲染异常回退占位不阻断导出 - 补 6 个回归测试 Co-Authored-By: Claude Code <noreply@anthropic.com>
同步 main(054f704),解决 contracts.py / main.py / README.md / 技术栈说明 的合并冲突。 - contracts.py:保留 pydantic 多行导入并新增 RequestOverride - main.py:合并 lifespan(导出孤儿清理 + 转写/本地模型生命周期) - README.md / 技术栈说明:文档取 main 最新版本 Co-Authored-By: Claude Code <noreply@anthropic.com>
- P2 复杂表达式绕过异常回退:解析与渲染共同纳入局部异常回退; AST 深度/节点数上限拦截 RecursionError - P2 极端有限范围生成 nan SVG:校验坐标跨度有限且 >0,回退安全范围; _polyline 拒绝非有限像素坐标 - P3 更新接口契约文档:function-plot 静态 SVG 已实现 Co-Authored-By: Claude Code <noreply@anthropic.com>
针对 PR 审阅「函数数量没有限制,可能生成数百 MB 的 SVG」: - parser: 单块 function-plot 表达式上限 _MAX_EXPRESSIONS=16,超限整块回退 - html: 单篇文档函数图像上限 _MAX_FUNCTION_PLOTS=16,超出回退源码占位 - service: 输入源 MAX_MARKDOWN_CHARS、产物 MAX_EXPORT_BYTES,超限分别 拒绝创建或标记 failed(EXPORT_OUTPUT_TOO_LARGE) - 补充 4 条回归测试与文档说明 Co-Authored-By: Claude Code <noreply@anthropic.com>
针对 PR 审阅 P1「组合复杂度仍可长时间占满导出线程」与 P3「EXPORT_OUTPUT_TOO_LARGE 误标 HTTP 413」: - plot: FunctionPlot 记录整块 AST 节点数(node_count),parser 累计 - html: 单篇文档累计节点预算 _MAX_TOTAL_PLOT_NODES=8000,超限回退占位 - service: 并发渲染信号量 MAX_CONCURRENT_RENDERS=2,超限额任务排队等待 - docs: 错误码区分同步 HTTP 错误与异步任务错误,EXPORT_OUTPUT_TOO_LARGE 由 error_code 返回而非 HTTP 413 - 补充节点预算与并发限制两条回归测试(全量 627 通过) Co-Authored-By: Claude Code <noreply@anthropic.com>
# Conflicts: # backend/app/routes.py
- 新增 PdfExporter(reportlab)与 DocxExporter(python-docx),实现与 HtmlExporter 一致的同步 render + 异步 export,v1 文本优先(标题/段落/ 行内强调与链接/列表/引用/表格/代码块/数学文本),function_plot 与 mermaid 保留源码占位并记 warning。 - service 层加 _EXPORTERS 注册表按格式分发,删除 format!=html 硬限制, 扩展名/MIME/产物清理泛化到 html/pdf/docx 三种格式。 - 新增 app/plot/renderer.py:StaticRenderRequest + StaticRenderer Protocol + FunctionPlotStaticRenderer + MermaidStaticRenderer;HtmlExporter 改经 FunctionPlotStaticRenderer 消费,去除对 render_svg 的直接依赖。 - 补齐 PDF/DOCX 魔法字节、CJK 字体、占位 warning 与 StaticRenderer 契约测试。 - 更新 Export开发说明.md。 Co-Authored-By: Claude Code <noreply@anthropic.com>
# Conflicts: # .gitignore # backend/app/main.py
- 引用块直接子节点为块级节点,PDF/DOCX 改为逐个渲染并继承缩进/颜色, 不再交给行内渲染器导致正文丢失 - PDF 嵌套列表先输出父级正文再输出子列表,修复顺序颠倒 - 等待渲染槽位期间保持 queued 并监听取消,取消即时生效 - DOCX 列表项补处理直接 text 子节点,避免正文被块级渲染器丢弃 - 补充引用块/嵌套列表/排队取消的结构内容回归测试 - 接口契约同步 html/pdf/docx 三格式均已实现,移除 EXPORT_FORMAT_UNSUPPORTED Co-Authored-By: Claude Code <noreply@anthropic.com>
- pdf.py `_block_list_item` 改为按 AST 顺序逐段输出:正文暂存为行内标记文本, 遇嵌套列表先 flush 再递归,之后继续后续正文,保持「父段—子列表—后续段」原始顺序 - pdf.py/docx.py 列表项直接行内节点改走 `_render_inline_node`,保留加粗/链接语义, 不再只渲染 children 而丢掉格式(PDF 链接以 /URI 注解保留,DOCX 写入 w:hyperlink) - docx.py `_render_inline_node` 增加 bold/italic 默认值,便于列表项直接调用 - 契约文档 StaticRenderer 状态「计划新增」→「已实现」 - 新增回归测试:混合嵌套顺序、PDF/DOCX 列表项行内语义 Co-Authored-By: Claude Code <noreply@anthropic.com>
- 抽取 render.py 共享几何:新增 PlotGeometry + compute_geometry,render_svg 改为薄序列化层,SVG 输出与重构前逐字节一致(8 组用例回归验证) - 新增 app/plot/render_reportlab.py:消费共享几何产出 reportlab 矢量 Drawing (网格/坐标轴 Line、曲线 PolyLine、刻度/标签 String、ylabel Group 旋转), 复用 STSong-Light 渲染中文,按页面内容宽 renderScale 缩放 - pdf.py _block_function_plot 改为内嵌矢量图(解析/渲染失败或超预算回退占位, 单图失败不阻断整篇);mermaid 仍占位 - 抽取 FunctionPlotBudget + format_plot_diagnostic 到 _common.py,html/pdf 共用 - 文档同步:PDF 已内嵌函数图像,DOCX 仍占位(栅格化范围外) Co-Authored-By: Claude Code <noreply@anthropic.com>
- 共享几何将曲线裁剪到绘图矩形,避免超出显式 range 的曲线覆盖 PDF 其他内容 - PDF 纵轴标签改为组内局部坐标 + 先平移后旋转,标签边界落回 Drawing 范围内 - 更新 pdf.py 模块说明:function_plot 已内嵌矢量图 - 新增曲线裁剪与纵轴标签边界回归测试 Co-Authored-By: Claude Code <noreply@anthropic.com>
暂不建议合并,仍有以下问题需要修复:
函数图像采样仅排除了非有限坐标,没有将曲线裁剪到绘图区。
例如设置 range: -1, 1 并绘制 y = 10*x 时,生成的曲线坐标会远超图表和页面范围。ReportLab 不会像 SVG viewport 一样自动裁剪,可能导致曲线覆盖 PDF 中的其他内容。
range: -1, 1
y = 10*x
建议:
range
纵轴标签先使用绝对坐标定位,随后又以相同坐标作为旋转中心,造成坐标重复变换。实测标签边界落在负数横坐标区域,因此导出的 PDF 中无法正常显示纵轴标签。
当前实现已经支持把 Function Plot 作为矢量图嵌入 PDF,但 pdf.py 的模块说明仍称函数图像使用占位内容,请同步更新。
pdf.py
上一轮提出的问题均已解决:
717 passed
另外,本 PR 后续加入了 PDF 函数图像矢量渲染,但标题和描述尚未体现,测试数量仍写为 711。建议修复上述问题后同步更新 PR 描述和测试结果。
这三条意见与上一轮(PR #39)一致,且均已在本 PR 的 head edc41fd(fix(export): 裁剪超出范围的曲线并修正 PDF 纵轴标签)中修复;审阅到的内容似乎仍是旧提交状态:
edc41fd
fix(export): 裁剪超出范围的曲线并修正 PDF 纵轴标签
app/plot/render.py
_PLOT_X0/Y0
_PLOT_X1/Y1
test_compute_geometry_clips_curves_to_plot_rect
app/plot/render_reportlab.py
String(0,0)
translate
rotate
T·R
test_render_reportlab_ylabel_within_drawing_bounds
完整测试 719 passed,PR 标题与描述已同步(不再是 711)。烦请对当前 head edc41fd 重新审阅。
暂不建议合并,渐近线问题。
当前实现只在采样结果为非有限值时断开折线。如果渐近点恰好位于两个采样点之间,两侧采样结果仍是有限值,随后会被 Liang-Barsky 算法裁剪成贯穿整个绘图区的伪线段。
已复现:
domain: -1, 1 range: -10, 10 y = 1/(x-0.013)
该配置会生成一条从绘图区底部延伸到顶部的错误竖线。
本 PR 实际包含整个 Export Service:
但当前标题和描述主要说明曲线裁剪及纵轴标签修复,无法准确反映实际合并范围。建议同步补全 PR 标题和描述,便于后续审阅、变更追踪和问题复盘。
719 passed
P2 渐近线错误连接已修复(head 4276cb7):
4276cb7
< ymin
> ymax
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。
No dependencies set.
The note is not visible to the blocked user.
概述
Export Service:把笔记或未保存的 Markdown 文本导出为 HTML / PDF / DOCX 的完整链路——创建即返回
queued、后台 asyncio.Task 渲染、产物 24h 过期、轮询GET /api/exports/{job_id}下载。范围(相对 main:28 文件 / +4836 行)
renderer="ast"→DocumentNode内部协议 +DocumentExporterProtocol。HtmlExporter:完整 HTML5 + function-plot 静态 SVG 内嵌。PdfExporter:reportlab,function-plot 内嵌矢量Drawing(与 SVG 同源共享几何),中文字体 STSong-Light,列表/表格/引用/行内语义。DocxExporter:python-docx,标题/列表/链接/表格,中文经w:eastAsia=宋体。parser.py)、共享几何compute_geometry(render.py)、reportlab 矢量后端(render_reportlab.py)、StaticRenderer内部契约(renderer.py)。app/contracts.py+app/routes.py的/api/exports*(创建/查询/下载/取消)。tests/test_export.py、tests/test_plot.py,docs/development/Export开发说明.md、接口契约 §10。审阅修复记录(多轮)
验证结果
720 passedgit diff --check:通过main:无合并冲突审阅结论
暂不建议合并,仍有以下问题需要修复:
P2:显式范围外的曲线未裁剪
函数图像采样仅排除了非有限坐标,没有将曲线裁剪到绘图区。
例如设置
range: -1, 1并绘制y = 10*x时,生成的曲线坐标会远超图表和页面范围。ReportLab 不会像 SVG viewport 一样自动裁剪,可能导致曲线覆盖 PDF 中的其他内容。建议:
range时的回归测试。P2:PDF 纵轴标签被旋转到画布之外
纵轴标签先使用绝对坐标定位,随后又以相同坐标作为旋转中心,造成坐标重复变换。实测标签边界落在负数横坐标区域,因此导出的 PDF 中无法正常显示纵轴标签。
建议:
P3:PDF 导出器说明已经过时
当前实现已经支持把 Function Plot 作为矢量图嵌入 PDF,但
pdf.py的模块说明仍称函数图像使用占位内容,请同步更新。已确认修复
上一轮提出的问题均已解决:
验证结果
717 passedgit diff --check:通过main:无合并冲突另外,本 PR 后续加入了 PDF 函数图像矢量渲染,但标题和描述尚未体现,测试数量仍写为 711。建议修复上述问题后同步更新 PR 描述和测试结果。
回复:本轮三条意见已在当前 head 修复
这三条意见与上一轮(PR #39)一致,且均已在本 PR 的 head
edc41fd(fix(export): 裁剪超出范围的曲线并修正 PDF 纵轴标签)中修复;审阅到的内容似乎仍是旧提交状态:app/plot/render.py已在共享几何层用 Liang-Barsky 把折线裁剪到绘图矩形(_PLOT_X0/Y0~_PLOT_X1/Y1),新增test_compute_geometry_clips_curves_to_plot_rect。app/plot/render_reportlab.py改为组内局部坐标String(0,0)+ 先translate后rotate(得到T·R),标签边界回到 Drawing 内,新增test_render_reportlab_ylabel_within_drawing_bounds。完整测试 719 passed,PR 标题与描述已同步(不再是 711)。烦请对当前 head
edc41fd重新审阅。审阅结论
暂不建议合并,渐近线问题。
P2:渐近线会被错误连接
当前实现只在采样结果为非有限值时断开折线。如果渐近点恰好位于两个采样点之间,两侧采样结果仍是有限值,随后会被 Liang-Barsky 算法裁剪成贯穿整个绘图区的伪线段。
已复现:
该配置会生成一条从绘图区底部延伸到顶部的错误竖线。
建议:
PR 范围说明
本 PR 实际包含整个 Export Service:
但当前标题和描述主要说明曲线裁剪及纵轴标签修复,无法准确反映实际合并范围。建议同步补全 PR 标题和描述,便于后续审阅、变更追踪和问题复盘。
验证结果
edc41fd719 passedgit diff --check:通过main:无合并冲突回复
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。
fix(export): 裁剪超出范围的曲线并修正 PDF 纵轴标签(复审)to feat(export): 多格式文档导出服务(HTML / PDF / DOCX + 函数图像渲染)Pull request closed