基于Node.js实现PDF文本替换的Web表单开发问题及代码求助
PDF文本替换Web工具实现
本文实现了一个简易Web工具,支持上传包含图片等元素的PDF文件,将指定文本批量替换为新内容,处理完成后可下载修改后的PDF。该方案解决了其他PDF库仅能新增文本无法替换原有内容的问题,核心采用Hummus库实现PDF内容修改。
前端HTML页面代码
<html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>PDF文本替换工具</title> </head> <body> <h1>PDF上传与文本替换</h1> <form id="uploadForm" enctype="multipart/form-data"> <input type="file" name="pdfFile" accept=".pdf" required> <button type="submit">上传并处理PDF</button> </form> <script> document.getElementById('uploadForm').addEventListener('submit', async (event) => { event.preventDefault(); const formData = new FormData(event.target); try { const response = await fetch('/upload-pdf', { method: 'POST', body: formData }); if (!response.ok) { throw new Error('请求失败:' + response.statusText); } const result = await response.json(); alert(result.message); // 自动跳转至下载页面 window.location.href = '/download-output'; } catch (error) { console.error('错误信息:', error); alert('文件处理失败:' + error.message); } }); </script> </body> </html>
后端Node.js代码
const express = require('express'); const app = express(); const port = 3000; const path = require('path'); // 引入path模块 const hummus = require('hummus'); // 引入HummusJS库 const fs = require('fs'); const multer = require('multer'); // 根路由返回前端页面 app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'index.html')); }); // 配置Multer处理文件上传 const storage = multer.diskStorage({ destination: function (req, file, cb) { // 自动创建uploads目录(不存在时) if (!fs.existsSync('uploads/')) { fs.mkdirSync('uploads/'); } cb(null, 'uploads/'); }, filename: function (req, file, cb) { cb(null, file.originalname); // 保留原文件名存储 } }); const upload = multer({ storage: storage }); /** * 修改PDF中的指定文本 * @param {string} sourceFilePath 源PDF路径 * @param {string} findText 需替换的目标文本 * @param {string} replaceText 替换后的新文本 */ function modifyExistingPDF(sourceFilePath, findText, replaceText) { // 检查源文件是否存在 if (!fs.existsSync(sourceFilePath)) { throw new Error(`源文件 ${sourceFilePath} 不存在。`); } // 创建PDF修改器,生成临时输出文件 const modPdfWriter = hummus.createWriterToModify(sourceFilePath, { modifiedFilePath: './output.pdf', compress: false }); // 获取PDF解析上下文 const sourceParser = modPdfWriter.createPDFCopyingContextForModifiedFile().getSourceDocumentParser(); // 遍历处理所有页面 const pageCount = sourceParser.getPagesCount(); for (let pageNum = 0; pageNum < pageCount; pageNum++) { const pageObject = sourceParser.parsePage(pageNum); // 获取页面内容流 const contentsStream = sourceParser.queryDictionaryObject(pageObject.getDictionary(), 'Contents'); if (!contentsStream) { console.warn(`第${pageNum+1}页未找到内容流,跳过处理`); continue; } // 读取内容流数据 const data = []; const readStream = sourceParser.startReadingFromStream(contentsStream); while (readStream.notEnded()) { const readData = readStream.read(10000); data.push(...readData); } // 转换为字符串并替换文本 let contentStr = Buffer.from(data).toString('utf-8'); if (contentStr.includes(findText)) { contentStr = contentStr.replace(new RegExp(findText, 'g'), replaceText); } else { console.warn(`第${pageNum+1}页未找到文本 "${findText}"`); } // 写入修改后的内容流 const objectsContext = modPdfWriter.getObjectsContext(); const textObjectID = pageObject.getDictionary().toJSObject().Contents.getObjectID(); if (typeof textObjectID !== 'number') { throw new Error(`第${pageNum+1}页文本对象ID无效`); } objectsContext.startModifiedIndirectObject(textObjectID); const stream = objectsContext.startUnfilteredPDFStream(); stream.getWriteStream().write(Buffer.from(contentStr, 'utf-8')); objectsContext.endPDFStream(stream); objectsContext.endIndirectObject(); } // 完成PDF写入 modPdfWriter.end(); // 清理无效对象,生成最终可下载文件 hummus.recrypt('./output.pdf', './outputClean.pdf'); // 删除临时文件 fs.unlinkSync('./output.pdf'); } // 文件上传与处理接口 app.post('/upload-pdf', upload.single('pdfFile'), (req, res) => { console.log('文件正在上传至服务器...'); if (!req.file) { return res.status(400).json({ error: '未上传文件' }); } const sourceFilePath = req.file.path; const findText = 'Word'; // 需替换的目标文本,可改为前端传入参数 const replaceText = 'Letter'; // 替换后的新文本,可改为前端传入参数 try { modifyExistingPDF(sourceFilePath, findText, replaceText); // 删除上传的源文件 fs.unlinkSync(sourceFilePath); res.json({ message: 'PDF处理完成,即将开始下载' }); } catch (error) { console.error('PDF处理错误:', error); res.status(500).json({ error: 'PDF处理失败:' + error.message }); } }); // 下载处理后PDF的接口 app.get('/download-output', (req, res) => { const outputPath = './outputClean.pdf'; if (fs.existsSync(outputPath)) { res.download(outputPath, '修改后的PDF.pdf', (err) => { if (err) { res.status(500).json({ error: '文件下载失败' }); } // 下载完成后删除文件 fs.unlinkSync(outputPath); }); } else { res.status(404).json({ error: '未找到处理后的文件' }); } }); // 启动服务器 app.listen(port, () => { console.log(`服务器运行在 http://localhost:${port}`); });
说明:
- 扩展了原代码的单页处理逻辑,支持遍历处理PDF所有页面
- 新增自动清理临时文件、源文件的逻辑,避免服务器存储冗余
- 可将
findText和replaceText改为前端传入参数,提升工具灵活性
内容的提问来源于stack exchange,提问作者git abbuser
相关产品推荐
相关产品推荐

