Fabric.js与pdf-lib实现的PDF编辑器在Adobe Acrobat中最后一页报错咨询
Adobe Acrobat Reader打开编辑PDF报错(错误18)问题排查与修复
问题背景
基于Angular/Laravel开发的PDF编辑器,使用fabric@6.5.3处理画布编辑,pdf-lib@1.17.1合并编辑内容到PDF。编辑后的PDF在Chrome、Edge中正常显示,但用Adobe Acrobat Reader打开时,仅当最后一页添加了形状、图像或绘图类编辑(纯文本/线条编辑无此问题),会触发错误:
There was an error processing a page. There was a problem reading this document (18)
问题代码
负责下载编辑后PDF的函数:
async downloadPdfWithEdits() { if (!this.pdfDoc) return; const pdfDoc = await PDFDocument.create(); const existingPdfBytes = await fetch(this.pdfURL).then(res => res.arrayBuffer()); const existingPdf = await PDFDocument.load(existingPdfBytes); for (let i = 1; i <= this.totalPages; i++) { const [copiedPage] = await pdfDoc.copyPages(existingPdf, [i - 1]); const canvasState = this.pageStates[i]; if (canvasState) { // Create an offscreen canvas element const tempCanvasElement = document.createElement('canvas'); tempCanvasElement.width = copiedPage.getWidth(); tempCanvasElement.height = copiedPage.getHeight(); // Ensure it is added to the DOM (hidden) to avoid "ctx" undefined errors document.body.appendChild(tempCanvasElement); const tempCanvas = new fabric.StaticCanvas(tempCanvasElement); console.log("Fabric.js canvas:", tempCanvas); try { await new Promise<void>((resolve, reject) => { tempCanvas.loadFromJSON(canvasState, () => { // 🔥 Ensure the canvas matches the PDF page size tempCanvas.setDimensions({ width: this.pdfCanvas.nativeElement.width, height: this.pdfCanvas.nativeElement.height, }); tempCanvas.renderAll(); console.log("Fabric.js canvas:", tempCanvas); setTimeout(async () => { try { // const imageData = tempCanvas.toDataURL({ format: 'png', multiplier: 1 }); const imageData = tempCanvas.toDataURL({ format: 'png', quality: 0.9, multiplier: 1 }); const img = await pdfDoc.embedPng(imageData); const { width, height } = copiedPage.getSize(); // Get original PDF page size copiedPage.drawImage(img, { x: 0, y: 0, width: width, // Match width height: height // Match height }); console.log(`✅ Edits applied to page ${i}`); resolve(); } catch (error) { console.error("❌ Error embedding PNG image:", error); reject(error); } }, 500); }); }); } catch (error) { console.error(`❌ Error processing page ${i}:`, error); } tempCanvas.dispose(); document.body.removeChild(tempCanvasElement); } else { console.log(`⚠️ No edits found for page ${i}`); } pdfDoc.addPage(copiedPage); } try { const pdfBytes = await pdfDoc.save(); const blob = new Blob([pdfBytes], { type: 'application/pdf' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = 'edited.pdf'; document.body.appendChild(link); link.click(); document.body.removeChild(link); console.log('✅ PDF downloaded successfully'); } catch (error) { console.error('❌ Error saving and downloading PDF:', error); } }
问题分析
- 异步时序不可靠:使用
setTimeout(500)等待画布渲染是不稳定的,虽然fabric的loadFromJSON回调会在加载完成后执行,但延迟操作可能导致在画布未完全渲染时就生成PNG,尤其最后一页的异步处理可能和PDF保存逻辑产生时序冲突。 - 尺寸不匹配:设置tempCanvas尺寸时使用了
this.pdfCanvas.nativeElement的宽高,而非PDF页面的实际尺寸,可能导致生成的PNG与PDF页面尺寸存在细微差异,Adobe Acrobat对这种差异的容错性远低于浏览器内置PDF阅读器。 - 页面添加时机:循环中每次处理完页面就执行
pdfDoc.addPage(copiedPage),但如果最后一页的图像嵌入操作因异步延迟未完成,可能导致页面被提前添加,PDF结构出现异常。
修复方案
1. 移除不必要的延迟操作
直接在loadFromJSON的回调中处理图像嵌入,无需setTimeout,确保画布渲染完成后立即生成PNG。
2. 严格匹配PDF页面尺寸
tempCanvas的宽高直接使用copiedPage.getSize()的结果,避免尺寸不一致。
3. 优化Promise逻辑
简化异步处理流程,减少嵌套层级,确保每个页面的编辑操作完全完成后再添加到新PDF中。
修改后的代码
async downloadPdfWithEdits() { if (!this.pdfDoc) return; const pdfDoc = await PDFDocument.create(); const existingPdfBytes = await fetch(this.pdfURL).then(res => res.arrayBuffer()); const existingPdf = await PDFDocument.load(existingPdfBytes); for (let i = 1; i <= this.totalPages; i++) { const [copiedPage] = await pdfDoc.copyPages(existingPdf, [i - 1]); const pageSize = copiedPage.getSize(); const canvasState = this.pageStates[i]; if (canvasState) { const tempCanvasElement = document.createElement('canvas'); // 直接使用PDF页面的实际尺寸初始化画布 tempCanvasElement.width = pageSize.width; tempCanvasElement.height = pageSize.height; document.body.appendChild(tempCanvasElement); const tempCanvas = new fabric.StaticCanvas(tempCanvasElement); try { await new Promise<void>((resolve, reject) => { tempCanvas.loadFromJSON(canvasState, async () => { try { // 确保画布尺寸与PDF页面严格一致 tempCanvas.setDimensions({ width: pageSize.width, height: pageSize.height }); tempCanvas.renderAll(); // 直接生成PNG,无需延迟 const imageData = tempCanvas.toDataURL({ format: 'png', quality: 0.9, multiplier: 1 }); const img = await pdfDoc.embedPng(imageData); copiedPage.drawImage(img, { x: 0, y: 0, width: pageSize.width, height: pageSize.height }); console.log(`✅ Edits applied to page ${i}`); resolve(); } catch (error) { console.error("❌ Error embedding PNG image:", error); reject(error); } }); }); } catch (error) { console.error(`❌ Error processing page ${i}:`, error); } finally { // 确保资源被清理 tempCanvas.dispose(); document.body.removeChild(tempCanvasElement); } } else { console.log(`⚠️ No edits found for page ${i}`); } pdfDoc.addPage(copiedPage); } try { const pdfBytes = await pdfDoc.save(); const blob = new Blob([pdfBytes], { type: 'application/pdf' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = 'edited.pdf'; document.body.appendChild(link); link.click(); document.body.removeChild(link); console.log('✅ PDF downloaded successfully'); } catch (error) { console.error('❌ Error saving and downloading PDF:', error); } }
额外建议
- 测试时使用Adobe Acrobat Reader的最新版本,旧版本可能存在已知的PDF解析bug。
- 如果问题仍存在,可以尝试将PNG格式改为JPEG(调整
toDataURL的format参数为'jpeg'),验证是否为PNG元数据导致的兼容性问题。 - 检查
pageStates中最后一页的画布数据是否存在异常,比如超出页面尺寸的元素,Adobe对超出页面范围的内容容错性较低。
内容的提问来源于stack exchange,提问作者Youssera Zoukha MECIFI
相关产品推荐
相关产品推荐

