继续研究pdfjs保存和还原批注

  1. 目前已经实现了手绘批注和文字批注
  2. 文字批注的定位问题跟玄学一样,现在也基本解决了
  3. 高亮部分跟魔鬼一样,始终不得要领,先放上来看看,后面找时间研究
js 复制代码
/**
 * @fileoverview 这个文件提供了保存和恢复PDF注释的功能。
 */

class AnnotationsManager {
  constructor() {
    // 初始化事件监听器
    this._initListeners();
  }

  /**
   * 初始化事件监听器
   * @private
   */
  _initListeners() {
    // 主工具栏按钮
    const saveButton = document.getElementById("saveAnnotationsButton");
    const restoreButton = document.getElementById("restoreAnnotationsButton");

    // 二级工具栏按钮
    const secondarySaveButton = document.getElementById("secondarySaveAnnotations");
    const secondaryRestoreButton = document.getElementById("secondaryRestoreAnnotations");

    // 添加事件监听器
    if (saveButton) {
      saveButton.addEventListener("click", this.saveAnnotations.bind(this));
    }
    if (restoreButton) {
      restoreButton.addEventListener("click", this.restoreAnnotations.bind(this));
    }
    if (secondarySaveButton) {
      secondarySaveButton.addEventListener("click", this.saveAnnotations.bind(this));
    }
    if (secondaryRestoreButton) {
      secondaryRestoreButton.addEventListener("click", this.restoreAnnotations.bind(this));
    }
  }

  /**
   * 获取当前PDF文件的唯一标识符
   * @returns {string} PDF文件的唯一标识符
   * @private
   */
  _getPdfId() {
    const pdfDocument = PDFViewerApplication.pdfDocument;
    if (!pdfDocument) {
      return null;
    }

    // 使用文件名和指纹作为唯一标识符
    const url = PDFViewerApplication.url;
    const fileName = url.split("/").pop();
    const fingerprint = pdfDocument.fingerprints?.[0] || "";

    return `${fileName}-${fingerprint}`;
  }

  /**
   * 保存注释到localStorage
   */
  saveAnnotations() {
    const pdfDocument = PDFViewerApplication.pdfDocument;
    if (!pdfDocument) {
      console.error("没有加载PDF文档");
      alert("请先加载PDF文档");
      return;
    }

    const pdfId = this._getPdfId();
    if (!pdfId) {
      console.error("无法获取PDF文档ID");
      alert("无法获取PDF文档ID");
      return;
    }

    try {
      // 获取注释数据
      const { annotationStorage } = pdfDocument;

      // 检查annotationStorage对象
      console.log("完整的annotationStorage对象:", annotationStorage);
      console.log("annotationStorage类型:", Object.prototype.toString.call(annotationStorage));
      console.log("annotationStorage方法:", Object.getOwnPropertyNames(Object.getPrototypeOf(annotationStorage)));
      console.log("annotationStorage大小:", annotationStorage.size);

      // 遍历所有存储的注释
      console.log("遍历annotationStorage中的所有条目:");
      const allAnnotations = {};
      for (const [key, value] of annotationStorage) {
        console.log("键:", key);
        console.log("值:", value);
        console.log("值类型:", Object.prototype.toString.call(value));

        // 手动序列化每个注释
        if (value && typeof value.serialize === 'function') {
          const serialized = value.serialize(false);

          // 特殊处理手绘注释的类型化数组
          if (serialized && serialized.paths) {
            if (serialized.paths.lines) {
              serialized.paths.lines = serialized.paths.lines.map(line =>
                line instanceof Float32Array ? Array.from(line) : line
              );
            }
            if (serialized.paths.points) {
              serialized.paths.points = serialized.paths.points.map(points =>
                points instanceof Float32Array ? Array.from(points) : points
              );
            }
          }

          allAnnotations[key] = serialized;
        } else {
          allAnnotations[key] = value;
        }
      }

      // 如果没有注释,则不保存
      if (Object.keys(allAnnotations).length === 0) {
        console.log("没有注释需要保存");
        alert("当前文档没有批注需要保存,请先添加批注");
        return;
      }

      // 将注释数据转换为可存储的格式
      const storageKey = `pdf_annotations_${pdfId}`;
      const storageData = {
        timestamp: Date.now(),
        fileName: PDFViewerApplication.url.split("/").pop(),
        annotations: allAnnotations
      };

      // 打印存储信息
      console.log("存储键值:", storageKey);
      console.log("存储数据:", storageData);
      console.log("存储数据JSON字符串长度:", JSON.stringify(storageData).length);

      // 保存到localStorage
      localStorage.setItem(storageKey, JSON.stringify(storageData));
      console.log("注释已保存到localStorage", storageKey);
      alert("批注已成功保存");
    } catch (error) {
      console.error("保存注释时出错:", error);
      alert(`保存批注时出错: ${error.message}`);
    }
  }

  /**
   * 从localStorage恢复注释
   */
  async restoreAnnotations() {
    try {
      // 检查PDF文档是否已加载
      if (!PDFViewerApplication.pdfDocument) {
        console.error("PDF文档未加载,无法恢复注释");
        alert("请先加载PDF文档");
        return;
      }

      // 获取PDF文档对象
      const pdfDocument = PDFViewerApplication.pdfDocument;

      // 获取PDF ID
      const pdfId = this._getPdfId();
      if (!pdfId) {
        console.error("无法获取PDF ID,无法恢复注释");
        alert("无法获取文档ID,恢复批注失败");
        return;
      }

      // 从localStorage获取保存的注释
      const storageKey = `pdf_annotations_${pdfId}`;
      const storageData = localStorage.getItem(storageKey);
      if (!storageData) {
        console.log("未找到保存的注释");
        alert("没有找到该文档的保存批注");
        return;
      }

      const parsedData = JSON.parse(storageData);
      const { annotations } = parsedData;

      if (!annotations || Object.keys(annotations).length === 0) {
        console.log("恢复的注释数据为空");
        alert("恢复的批注数据为空");
        return;
      }

      // 获取当前页面的注释层
      const pdfViewer = PDFViewerApplication.pdfViewer;
      const annotationStorage = pdfDocument.annotationStorage;

      // 清除现有注释
      const existingKeys = [];
      for (const [key] of annotationStorage) {
        existingKeys.push(key);
      }

      // 删除所有现有注释
      for (const key of existingKeys) {
        annotationStorage.remove(key);
      }

      // 恢复保存的注释
      console.log("开始恢复注释...");
      let restoredCount = 0;

      // 打印所有要恢复的注释键
      console.log("要恢复的注释键:", Object.keys(annotations));

      // 删除这行:await this._ensurePagesRendered(pdfViewer);

      // 获取所有页面的注释编辑器层
      const annotationEditorLayers = pdfViewer._pages.map(page => page.annotationEditorLayer);

      // 使用deserialize方法恢复注释
      for (const [key, value] of Object.entries(annotations)) {
        try {
          console.log(`尝试恢复注释: ${key}`, value);

          // 特殊处理手绘注释的类型化数组恢复
          if (value.paths) {
            if (value.paths.lines && Array.isArray(value.paths.lines)) {
              value.paths.lines = value.paths.lines.map(line =>
                Array.isArray(line) ? new Float32Array(line) : line
              );
            }
            if (value.paths.points && Array.isArray(value.paths.points)) {
              value.paths.points = value.paths.points.map(points =>
                Array.isArray(points) ? new Float32Array(points) : points
              );
            }
          }

          // 特殊处理高亮注释 - 修正注释类型判断
          if (value.annotationType === 9) { // AnnotationEditorType.HIGHLIGHT = 9
            console.log(`处理高亮注释: ${key}`, value);

            // 修正 outlines 格式
            if (value.outlines && typeof value.outlines === 'object' && !Array.isArray(value.outlines)) {
              value.outlines = Object.values(value.outlines);
            }

            // 获取页面索引
            const pageIndex = value.pageIndex || 0;

            // 确保页面尺寸和偏移数据存在
            if (!value.pageDimensions || !value.pageTranslation) {
              console.log(`注释 ${key} 缺少页面数据,尝试从页面获取`);

              // 从 pdfViewer 获取页面数据
              const page = pdfViewer._pages[pageIndex];
              if (page && page.pdfPage) {
                const viewport = page.pdfPage.getViewport({ scale: 1 });
                value.pageDimensions = [viewport.width, viewport.height];
                value.pageTranslation = [0, 0]; // 通常为 [0, 0]

                console.log(`从页面获取的数据:`, {
                  pageDimensions: value.pageDimensions,
                  pageTranslation: value.pageTranslation
                });
              } else {
                console.error(`无法获取页面 ${pageIndex} 的数据`);
                continue;
              }
            }

            // 处理 quadPoints
            let quadPoints = value.quadPoints;
            if (!quadPoints || !Array.isArray(quadPoints) || quadPoints.length === 0) {
              console.log(`注释 ${key} 缺少quadPoints,尝试从rect重建`);

              if (value.rect && Array.isArray(value.rect) && value.rect.length === 4) {
                const [x, y, width, height] = value.rect;
                // 严格验证 rect 数据
                if (typeof x === 'number' && typeof y === 'number' &&
                    typeof width === 'number' && typeof height === 'number' &&
                    isFinite(x) && isFinite(y) && isFinite(width) && isFinite(height) &&
                    width > 0 && height > 0) {
                  quadPoints = [[
                    x, y + height,
                    x + width, y + height,
                    x + width, y,
                    x, y
                  ]];
                  console.log(`从rect重建的quadPoints:`, quadPoints);
                } else {
                  console.error(`注释 ${key} 的rect数据无效:`, value.rect);
                  continue;
                }
              } else {
                console.error(`注释 ${key} 缺少有效的rect数据`);
                continue;
              }
            }

            // 将 quadPoints 对象转换为二维数组并严格验证
            if (quadPoints && typeof quadPoints === 'object' && !Array.isArray(quadPoints)) {
              quadPoints = Object.values(quadPoints);
            }

            // 验证 quadPoints 数据
            if (!Array.isArray(quadPoints) || quadPoints.length === 0) {
              console.error(`注释 ${key} quadPoints格式无效`);
              continue;
            }

            // 严格验证每个 quadPoints 片段
            const validQuadPoints = [];
            for (const quad of quadPoints) {
              if (Array.isArray(quad) && quad.length === 8) {
                // 检查所有坐标是否为有效数字
                const allValid = quad.every(coord =>
                  typeof coord === 'number' &&
                  isFinite(coord) &&
                  !isNaN(coord)
                );

                if (allValid) {
                  // 额外检查坐标范围是否合理(避免极值)
                  const hasReasonableValues = quad.every(coord =>
                    Math.abs(coord) < 1e6 // 避免过大的坐标值
                  );

                  if (hasReasonableValues) {
                    validQuadPoints.push(quad);
                  } else {
                    console.warn(`注释 ${key} 的quadPoints包含异常大的坐标值:`, quad);
                  }
                } else {
                  console.warn(`注释 ${key} 的quadPoints包含无效坐标:`, quad);
                }
              } else {
                console.warn(`注释 ${key} 的quadPoints片段格式错误:`, quad);
              }
            }

            if (validQuadPoints.length === 0) {
              console.error(`注释 ${key} 没有有效的quadPoints数据`);
              continue;
            }

            console.log(`高亮注释 ${key} 数据验证通过,quadPoints数量: ${validQuadPoints.length}`);

            // 更新 value 对象
            value.quadPoints = validQuadPoints;

            // 清理可能存在的无效 boxes 字段
            if (value.boxes) {
              delete value.boxes;
            }

            // 添加详细的页面尺寸和偏移调试信息
            console.log(`🔍 调试页面数据 ${key}:`, {
              pageDimensions: value.pageDimensions,
              pageTranslation: value.pageTranslation,
              pageIndex: value.pageIndex,
              rect: value.rect
            });

            // 确保页面尺寸数据有效
            if (value.pageDimensions) {
              const [pageWidth, pageHeight] = value.pageDimensions;
              if (!isFinite(pageWidth) || !isFinite(pageHeight) || pageWidth <= 0 || pageHeight <= 0) {
                console.error(`注释 ${key} 的页面尺寸无效:`, value.pageDimensions);
                continue;
              }
            }

            // 确保页面偏移数据有效
            if (value.pageTranslation) {
              const [pageX, pageY] = value.pageTranslation;
              if (!isFinite(pageX) || !isFinite(pageY)) {
                console.error(`注释 ${key} 的页面偏移无效:`, value.pageTranslation);
                continue;
              }
            }
          }

          // 获取页面索引
          const pageIndex = value.pageIndex || 0;

          // 获取对应页面的注释编辑器层构建器
          const editorLayerBuilder = annotationEditorLayers[pageIndex];

          if (editorLayerBuilder) {
            // 确保注释编辑器层已经渲染(简化版本,不等待)
            debugger

            if (!editorLayerBuilder.annotationEditorLayer) {
              const page = pdfViewer._pages[pageIndex];
              if (page && page.pdfPage) {
                // 尝试触发渲染,但不等待
                try {
                  page._renderAnnotationEditorLayer();
                } catch (e) {
                  console.warn(`无法渲染页面 ${pageIndex} 的注释编辑器层:`, e);
                  continue;
                }
              }
            }

            const editorLayer = editorLayerBuilder.annotationEditorLayer;

            // 使用deserialize方法创建编辑器实例
            console.log("PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP")
            console.log(value)

            console.log("打印编辑层")
            console.log(editorLayer)
            debugger
            const editor = await editorLayer.deserialize(value);

            if (editor) {
              console.log(`✅ 成功创建编辑器: ${key}`, {
                editorType: editor.constructor.name,
                hasDiv: !!editor.div,
                divDisplay: editor.div?.style.display,
                divVisibility: editor.div?.style.visibility,
                editorId: editor.id
              });

              // 检查render前的状态
              console.log(`📋 render前状态: ${key}`, {
                hasDiv: !!editor.div,
                hasRenderMethod: typeof editor.render === 'function'
              });

              // 确保编辑器已渲染
              if (!editor.div) {
                console.log(`🔄 调用render方法: ${key}`);
                editor.render();
                console.log(`📋 render后状态: ${key}`, {
                  hasDiv: !!editor.div,
                  divInnerHTML: editor.div?.innerHTML?.substring(0, 100),
                  divStyle: editor.div?.style.cssText
                });
              }

              // 特殊处理文字注释的位置修正
              if (value.annotationType === 3) { // FREETEXT
                const [pageWidth, pageHeight] = editor.pageDimensions;
                const rect = value.rect;
                editor.x = rect[0] / pageWidth;
                editor.y = (pageHeight - rect[3]) / pageHeight;
                editor.width = (rect[2] - rect[0]) / pageWidth;
                editor.height = (rect[3] - rect[1]) / pageHeight;
                editor.fixAndSetPosition();
                if (value.value && editor.editorDiv) {
                  editor.editorDiv.textContent = value.value;
                }
              }


              // 确保编辑器可见
              if (editor.div) {
                editor.div.style.display = '';
              }

              // 将编辑器添加到层中
              editorLayer.add(editor);

              // 将编辑器实例添加到存储中
              annotationStorage.setValue(key, editor);

              restoredCount++;
              console.log(`成功恢复注释: ${key}`);
            } else {
              console.warn(`无法反序列化注释: ${key}`);
            }
          } else {
            console.warn(`找不到页面 ${pageIndex} 的注释编辑器层`);
          }
        } catch (err) {
          console.error(`恢复注释 ${key} 时出错:`, err);
        }
      }

      // 刷新视图以显示恢复的注释
      pdfViewer.update();

      // 触发重新渲染
      // 详细检查每个页面的注释层状态
      for (let i = 0; i < pdfViewer._pages.length; i++) {
        const page = pdfViewer._pages[i];

        if (page.annotationEditorLayer?.annotationEditorLayer) {
          const layer = page.annotationEditorLayer.annotationEditorLayer;
          layer.div.hidden = false;

          // 检查每个编辑器
          if (layer._editors) {
            let editorIndex = 0;
            for (const editor of layer._editors) {
              console.log(`📝 页面 ${i} 编辑器 ${editorIndex}:`, {
                type: editor.constructor.name,
                hasDiv: !!editor.div,
                divVisible: editor.div && editor.div.offsetWidth > 0 && editor.div.offsetHeight > 0,
                divRect: editor.div?.getBoundingClientRect()
              });
              editorIndex++;
            }
          }
        }
      }

      console.log(`注释已从localStorage恢复,共恢复 ${restoredCount} 个注释`);
      alert(`批注已成功恢复,共恢复 ${restoredCount} 个批注`);
    } catch (error) {
      console.error("恢复注释时出错:", error);
      alert(`恢复批注时出错: ${error.message}`);
    }
  }

  /**
   * 确保所有页面都已渲染
   * @private
   */
  async _ensurePagesRendered(pdfViewer) {
    const promises = [];

    for (let i = 0; i < pdfViewer._pages.length; i++) {
      const page = pdfViewer._pages[i];
      if (page && page.pdfPage && !page.annotationEditorLayer) {
        // 如果页面还没有注释编辑器层,等待页面渲染完成
        promises.push(new Promise(resolve => {
          const checkRendered = () => {
            if (page.annotationEditorLayer) {
              resolve();
            } else {
              setTimeout(checkRendered, 100);
            }
          };
          checkRendered();
        }));
      }
    }

    if (promises.length > 0) {
      await Promise.all(promises);
    }
  }
}

// 创建并导出AnnotationsManager实例
const annotationsManager = new AnnotationsManager();
export { annotationsManager };
相关推荐
tianchang7 分钟前
React Hook 解析(一):useCallback 与 useMemo
前端·react.js
炊烟行者8 分钟前
foreignObject
前端
OEC小胖胖21 分钟前
组件化(一):重新思考“组件”:状态、视图和逻辑的“最佳”分离实践
前端·javascript·html5·web
拾光拾趣录22 分钟前
用 Web Worker 计算大视频文件 Hash:从“页面卡死”到流畅上传
前端·javascript
伟大的兔神40 分钟前
cesium绘制动态柱状图
前端·gis·cesium
前端拿破轮43 分钟前
字节面试官:你对Promise很熟是吧?试一下手写所有静态方法
前端·面试·promise
一颗奇趣蛋1 小时前
React- useMemo & useCallback
前端·react.js
lichenyang4531 小时前
JS的基础概念--结束
前端
兵临天下api1 小时前
跨境电商 API 对接避坑指南:亚马逊 SP-API 超时问题的 5 种解决方案(附重试代码模板)
前端
半花1 小时前
【vue】v-自定义指令
前端·vue.js