云计算百科
云计算领域专业知识百科平台

用 Flask 从 0 到 1 实现一个本地版 WPS:PDF 合并拆分、Office 转 PDF、在线创建文档

用 Flask 从 0 到 1 实现一个本地版 WPS:PDF 合并拆分、Office 转 PDF、在线创建文档

一、为什么要做这个工具

日常办公里经常会遇到这些小需求:

  • 多张图片合并成一个 PDF;
  • 多个 PDF 合成一份文件;
  • 扫描件或图片 PDF 需要删掉某几页;
  • PDF 要拆分成多个文件;
  • PDF 要加水印、加页码、压缩;
  • Word、Excel、PPT 需要快速转成 PDF;
  • 临时写一个 Word 文档、表格或 PDF 页面。

这些功能单独看都不复杂,但如果每次都打开不同软件,或者上传到在线网站处理,会比较麻烦。于是我做了一个本地版的轻量办公工具,界面上尽量模仿 WPS 的工具中心形式,把常用功能做成独立卡片。

最终效果如下:

在这里插入图片描述

二、项目功能概览

当前项目叫 Office Studio 文档工作台,主要分成两块:

  • 文档创建能力
  • PDF 工具中心
  • 1. 文档创建

    支持在浏览器里新建三类文档:

    • Word 文档:支持标题、正文、图片、表格,导出 .docx;
    • Excel 表格:支持单元格编辑、增加行列,导出 .xlsx;
    • PDF 画布:支持添加文本、图片、新增页面,导出 .pdf。

    Word 编辑页面:

    在这里插入图片描述

    Excel 编辑页面:

    在这里插入图片描述

    PDF 创建页面:

    在这里插入图片描述

    2. PDF 工具中心

    PDF 工具中心支持:

    • 图片转 PDF;
    • 多 PDF 合并;
    • PDF 页面删除;
    • PDF 页面拖拽重排;
    • PDF 页面旋转;
    • PDF 拆分;
    • 提取指定页面;
    • PDF 压缩;
    • PDF 加文字水印;
    • PDF 加页码;
    • PDF 转图片;
    • PDF 转 Word;
    • Word 转 PDF;
    • Excel 转 PDF;
    • PPT 转 PDF;
    • 图片格式批量转换;
    • PDF 文本识别导出 Word。

    PDF 工具中心页面如下:

    在这里插入图片描述

    三、技术选型

    这个项目的目标是轻量、可本地运行、易扩展,所以没有引入很重的前端框架,整体技术栈如下:

    模块技术
    Web 服务 Flask
    PDF 读写 pypdf
    PDF 渲染预览 pypdfium2
    图片处理 Pillow
    生成 PDF ReportLab
    Word 读写 python-docx
    Excel 读写 openpyxl
    PPTX 读取 python-pptx
    前端 HTML + CSS + 原生 JavaScript
    测试 pytest

    依赖文件大致如下:

    Flask==3.1.2
    openpyxl==3.1.5
    Pillow==11.3.0
    pypdf==6.11.0
    pypdfium2==5.2.0
    python-docx==1.2.0
    python-pptx==1.0.2
    reportlab==4.4.4
    pytest==8.4.2

    源码仓库:

    https://gitee.com/wu-xubin/change_image_to_pdf.git

    也可以直接克隆运行:

    git clone https://gitee.com/wu-xubin/change_image_to_pdf.git
    cd change_image_to_pdf

    启动方式:

    .venv\\Scripts\\python m pip install r requirements.txt
    .venv\\Scripts\\python app.py

    访问:

    http://127.0.0.1:5000

    四、整体架构设计

    项目采用比较直接的 Flask 单体结构:

    change_image_to_pdf
    ├─ app.py
    ├─ requirements.txt
    ├─ static
    │ ├─ app.js
    │ └─ styles.css
    ├─ templates
    │ └─ index.html
    ├─ tests
    │ └─ test_app.py
    └─ storage
    ├─ uploads
    └─ generated

    后端主要负责:

    • 接收文件上传;
    • 校验文件类型;
    • 创建独立的转换目录;
    • 调用对应处理函数;
    • 返回下载地址。

    前端主要负责:

    • 首页功能卡片导航;
    • 文件选择和参数填写;
    • PDF 页面预览;
    • 页面删除、旋转、拖拽排序;
    • 调用后端接口;
    • 展示下载按钮。

    核心接口可以分成三类:

    /api/upload 上传图片或 PDF
    /api/generate 合并、删页、旋转、重排并生成 PDF

    /api/pdf-to-word PDF 转 Word
    /api/word-to-pdf Word 转 PDF
    /api/excel-to-pdf Excel 转 PDF
    /api/ppt-to-pdf PPT 转 PDF

    /api/split-pdf PDF 拆分
    /api/extract-pdf 提取页面
    /api/compress-pdf PDF 压缩
    /api/watermark-pdf 添加水印
    /api/add-page-numbers 添加页码
    /api/pdf-to-images PDF 转图片
    /api/convert-images 图片格式转换
    /api/ocr-pdf PDF 文本识别

    /api/create-word 创建 Word
    /api/create-excel 创建 Excel
    /api/create-pdf 创建 PDF

    五、PDF 合并、删页、旋转和重排

    PDF 主工作台的思路是:

  • 用户上传图片或 PDF;
  • 后端把上传文件保存到一个 session 目录;
  • 前端拿到文件列表后生成预览;
  • 用户可以删除页面、旋转页面、拖拽调整顺序;
  • 点击生成后,前端把页面顺序、删除页、旋转角度等参数传给后端;
  • 后端重新生成 PDF。
  • 伪代码流程如下:

    @app.post("/api/generate")
    def generate_pdf():
    payload = request.get_json(silent=True) or {}
    session_id = payload.get("session_id", "")
    settings = normalize_settings(payload.get("settings", {}))
    deleted_pages = normalize_deleted_pages(payload.get("deleted_pages", []))
    page_order = normalize_page_order(payload.get("page_order", []))
    rotations = normalize_page_rotations(payload.get("rotations", {}))

    page_result = build_document_pdf(
    source_paths=source_paths,
    output_path=output_path,
    settings=settings,
    deleted_pages=deleted_pages,
    page_order=page_order,
    rotations=rotations,
    )

    return jsonify({
    "preview_url": preview_url,
    "download_url": download_url,
    "page_count": page_result["page_count"],
    "kept_page_count": page_result["kept_page_count"],
    "pages": page_result["pages"],
    })

    这里有一个比较重要的设计点:不要在前端直接修改 PDF 文件,而是让前端只维护“用户想怎么处理页面”的状态,真正的 PDF 写入由后端完成。这样前端逻辑更轻,后端也更容易测试。

    六、PDF 拆分功能

    PDF 拆分功能支持用户输入类似这样的页码范围:

    1-3; 4-6; 7,9-10

    分号表示要拆成多个文件,逗号和横线表示页码组合。

    例如:

    • 1-3 生成第一个 PDF;
    • 4-6 生成第二个 PDF;
    • 7,9-10 生成第三个 PDF。

    后端处理完成后,会把多个 PDF 打包成 ZIP 返回。

    这个设计比“只能每 N 页拆一个文件”更灵活,适合处理合同、扫描件、资料包这类文档。

    七、Word、Excel、PPT 转 PDF

    Office 转 PDF 是这个项目比较像办公套件的一部分。

    1. Word 转 PDF

    Word 转 PDF 使用 python-docx 读取 .docx,再用 ReportLab 写 PDF。

    主要处理:

    • 段落;
    • 标题;
    • 表格;
    • 空文档兜底;
    • 中文字体注册。

    轻量版实现并不会 100% 复刻 Word 的所有排版,但对于普通文本和表格已经足够。

    2. Excel 转 PDF

    Excel 转 PDF 使用 openpyxl 读取 .xlsx,然后把工作表内容转成 ReportLab 的表格。

    这里做了几个限制:

    • 最多读取前 200 行;
    • 最多读取前 30 列;
    • 空单元格会保留为空字符串;
    • 每个工作表单独分页;
    • PDF 使用横向 A4,避免表格太窄。

    核心思路:

    def convert_xlsx_to_pdf(xlsx_path, output_path):
    workbook = load_workbook(xlsx_path, data_only=True)
    story = []

    for sheet_index, sheet in enumerate(workbook.worksheets):
    if sheet_index > 0:
    story.append(PageBreak())

    story.append(Paragraph(escape(sheet.title), styles["heading"]))
    rows = worksheet_to_rows(sheet)

    table_data = [
    [Paragraph(format_paragraph_text(str(cell)), styles["body"]) for cell in row]
    for row in rows
    ]
    story.append(Table(table_data, repeatRows=1))

    pdf = SimpleDocTemplate(str(output_path), pagesize=landscape(A4))
    pdf.build(story)

    3. PPT 转 PDF

    PPT 转 PDF 是最近新增的功能。

    实现思路是:

  • 用 python-pptx 打开 .pptx;
  • 读取幻灯片宽高;
  • 每一页 slide 对应 PDF 的一页;
  • 遍历页面中的 shape;
  • 如果是图片,就绘制图片;
  • 如果有文本框,就绘制文本;
  • 最后输出 PDF。
  • 核心代码思路:

    def convert_pptx_to_pdf(pptx_path, output_path):
    presentation = Presentation(pptx_path)

    page_width = emu_to_points(presentation.slide_width)
    page_height = emu_to_points(presentation.slide_height)
    pdf = canvas.Canvas(str(output_path), pagesize=(page_width, page_height))

    for slide in presentation.slides:
    pdf.setFillColor(colors.white)
    pdf.rect(0, 0, page_width, page_height, fill=1, stroke=0)

    for shape in slide.shapes:
    draw_pptx_image(pdf, shape, page_height)
    draw_pptx_text(pdf, shape, page_height, font_name)

    pdf.showPage()

    pdf.save()

    需要注意:这个方案是轻量级方案,不依赖本机 Office 或 LibreOffice,因此部署简单;但复杂动画、母版、艺术字、图表和高级形状不一定能完美还原。如果目标是高保真转换,可以后续接入 LibreOffice 命令行。

    八、在线创建 Word、Excel 和 PDF

    除了文件处理工具,我还做了三个创建页面:

    • Word 创建页;
    • Excel 创建页;
    • PDF 创建页。

    前端负责收集用户编辑的内容,然后把结构化 JSON 发给后端。后端根据 JSON 生成对应文件。

    例如 Word 创建的请求数据可以设计成:

    {
    "title": "新建文档",
    "blocks": [
    {
    "type": "heading",
    "text": "项目总结"
    },
    {
    "type": "paragraph",
    "text": "这里是正文内容。"
    },
    {
    "type": "table",
    "rows": [
    ["名称", "数量"],
    ["PDF 工具", "12"]
    ]
    }
    ]
    }

    这种结构的好处是:前端可以做得比较自由,后端只关心数据结构,不需要解析复杂 HTML。

    九、前端设计:把功能做成工具卡片

    一开始 PDF 工具很多,如果全部堆在一个页面上,会非常乱。后来我把每个独立功能都做成卡片:

    • 卡片有标题;
    • 卡片有简短说明;
    • 卡片有文件选择;
    • 有参数就放输入框或下拉框;
    • 提交成功后显示下载按钮。

    前端只需要一套通用逻辑,就可以处理多个接口:

    function bindToolCards() {
    document.querySelectorAll(".tool-card[data-endpoint]").forEach((card) => {
    const input = card.querySelector(".tool-file-input");
    const button = card.querySelector(".tool-submit");
    const downloadLink = card.querySelector(".tool-download");

    button.addEventListener("click", async () => {
    await submitToolCard(card, input, button, downloadLink);
    });
    });
    }

    每张卡片通过 data-endpoint 指定接口:

    <article class="tool-card" data-endpoint="/api/ppt-to-pdf" data-file-field="file">
    <h3>PPT 转 PDF</h3>
    <p>将 PPTX 幻灯片转换为 PDF 页面。</p>
    <label class="tool-file">
    <span>选择 PPTX</span>
    <input class="tool-file-input" type="file" accept=".pptx">
    </label>
    <button class="primary-btn tool-submit" type="button">开始转换</button>
    <a class="secondary-btn tool-download disabled" href="#" download>下载结果</a>
    </article>

    这样后续新增工具时,很多情况下只需要:

  • 后端新增接口;
  • 前端新增一张卡片;
  • 补一个测试用例;
  • README 更新说明。
  • 这个扩展方式比较顺手。

    十、中文字体和 PDF 生成问题

    做 PDF 生成时,中文字体是绕不开的问题。

    ReportLab 默认字体对中文支持不好,所以需要注册系统字体:

    def register_pdf_font():
    font_name = "AppUnicode"
    font_candidates = [
    Path("C:/Windows/Fonts/msyh.ttc"),
    Path("C:/Windows/Fonts/simsun.ttc"),
    Path("C:/Windows/Fonts/simhei.ttf"),
    Path("/System/Library/Fonts/PingFang.ttc"),
    Path("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"),
    ]

    for font_path in font_candidates:
    if font_path.exists():
    pdfmetrics.registerFont(TTFont(font_name, str(font_path)))
    return font_name

    return "Helvetica"

    Windows 下通常可以使用微软雅黑或宋体。跨平台时可以继续补充 macOS、Linux 的字体路径。

    十一、扫描件 PDF 和 OCR 的边界

    PDF 转 Word 这类功能有一个关键前提:PDF 里要有文本层。

    如果 PDF 是纯扫描件,本质上每一页都是图片,这时用普通 PDF 文本提取方法是拿不到文字的。

    所以当前项目里的“文本识别”逻辑是:

    • 优先提取 PDF 文本层;
    • 如果没有检测到文本,会在导出的 Word 中提示未检测到文本;
    • 真正 OCR 可以后续接入 Tesseract、PaddleOCR 或其他本地 OCR 引擎。

    这一点在文章里必须讲清楚,否则用户很容易误以为“PDF 转 Word”就一定能识别扫描件。

    十二、测试验证

    这个项目补了 pytest 测试,覆盖核心功能。

    当前测试包括:

    • 图片转 PDF;
    • PDF 合并;
    • PDF 删页;
    • PDF 重排;
    • PDF 旋转;
    • PDF 拆分;
    • PDF 页面提取;
    • PDF 转 Word;
    • Word 转 PDF;
    • Excel 转 PDF;
    • PPT 转 PDF;
    • PDF 压缩;
    • PDF 加水印;
    • PDF 加页码;
    • PDF 转图片;
    • 图片格式转换;
    • PDF 文本识别;
    • Word、Excel、PDF 创建导出。

    测试命令:

    .venv\\Scripts\\python m pytest q
    node check static\\app.js

    本地验证结果:

    21 passed

    有了测试之后,新增工具会安心很多。比如后面新增 PPT 转 PDF,只要补上接口、卡片和测试,就能快速确认没有把其他 PDF 功能弄坏。

    十三、一些开发踩坑

    1. Office 转 PDF 很难做到完美还原

    Word、Excel、PPT 的排版模型都比 PDF 复杂。

    如果只是轻量转换,可以用 python-docx、openpyxl、python-pptx 读取内容,再用 ReportLab 重新绘制 PDF。

    如果想高保真转换,建议接入 LibreOffice:

    soffice headless convert-to pdf input.docx outdir output

    但这样会增加部署成本,也需要考虑不同系统环境下 LibreOffice 的安装路径。

    2. 文件处理要做好目录隔离

    每次转换都应该创建一个独立目录:

    storage/generated/<uuid>/

    这样可以避免不同用户或不同任务之间文件名冲突。

    即使是本地工具,也最好保留这种习惯。

    3. 大文件上传要限制大小

    Flask 可以通过 MAX_CONTENT_LENGTH 限制上传大小:

    app.config.update(
    MAX_CONTENT_LENGTH=128 * 1024 * 1024
    )

    否则用户误传几百 MB 的 PDF,很容易把本地服务拖慢。

    4. 前端状态不要和文件内容强绑定

    PDF 页面删除、旋转、重排这些操作,前端只维护状态:

    {
    "deleted_pages": [2, 4],
    "page_order": [3, 1, 2],
    "rotations": {
    "1": 90
    }
    }

    真正写 PDF 时再交给后端处理。这个思路能让前端更简单,也方便测试。

    十四、后续可以继续优化什么

    这个项目还能继续往“本地版 WPS 工具箱”方向扩展:

    • 接入真正 OCR,支持扫描件文字识别;
    • 接入 LibreOffice,提高 Office 转 PDF 的还原度;
    • 增加批量任务队列;
    • 增加处理历史记录;
    • 增加文件自动清理机制;
    • 增加更多 PDF 编辑能力,比如插入空白页、裁剪页面、重设页面尺寸;
    • 增加 Docker 部署;
    • 前端继续优化成更接近 WPS 的工具栏和文档标签页。

    十五、总结

    这个项目从最开始的图片转 PDF,逐步扩展成了一个本地办公文档工作台。

    它的核心不是某一个复杂算法,而是把一组常用办公能力组合起来:

    • Flask 负责接口和文件流转;
    • pypdf 负责 PDF 结构处理;
    • pypdfium2 负责 PDF 渲染预览;
    • ReportLab 负责生成 PDF;
    • python-docx、openpyxl、python-pptx 负责读取和生成 Office 文档;
    • 前端用工具卡片把功能组织起来。

    如果你也想做一个本地办公工具,可以从“图片转 PDF”这种小功能开始,然后按卡片式工具逐步扩展。每新增一个功能,都补齐接口、前端入口、测试和说明文档,项目就会越来越像一个正式产品。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 用 Flask 从 0 到 1 实现一个本地版 WPS:PDF 合并拆分、Office 转 PDF、在线创建文档
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!