You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
  }
}

问题分析

  1. 异步时序不可靠:使用setTimeout(500)等待画布渲染是不稳定的,虽然fabric的loadFromJSON回调会在加载完成后执行,但延迟操作可能导致在画布未完全渲染时就生成PNG,尤其最后一页的异步处理可能和PDF保存逻辑产生时序冲突。
  2. 尺寸不匹配:设置tempCanvas尺寸时使用了this.pdfCanvas.nativeElement的宽高,而非PDF页面的实际尺寸,可能导致生成的PNG与PDF页面尺寸存在细微差异,Adobe Acrobat对这种差异的容错性远低于浏览器内置PDF阅读器。
  3. 页面添加时机:循环中每次处理完页面就执行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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 14:47:33