返回

Canvas PDF文本层定位不准?两种方案解决!

vue.js

自定义渲染文本位置计算不正确

在前端开发过程中,使用Canvas绘制PDF,并需要在Canvas之上叠加文本层来实现搜索高亮等功能时,经常遇到文本位置计算不准确的问题。这个问题的根本原因在于 Canvas 和 HTML 元素在坐标系上的差异,以及PDF文档本身可能包含的各种变换。深入理解这些概念对于解决问题至关重要。

问题分析

问题的核心是 HTML 元素的定位与 PDF 文本项的变换矩阵之间的匹配。PDF 文本项通过 transform 属性定义其在 Canvas 上的位置和变换。 你的尝试中,你尝试了使用 transform 矩阵,视口(viewport)信息和缩放比例计算文本元素位置,但这并不能直接实现准确对齐,因为Canvas的坐标原点和HTML元素的坐标原点不同。
例如,以下问题可能会影响文本位置:

  • 坐标原点差异: Canvas 原点在左上角,而 HTML 元素的默认原点通常也在左上角。但 PDF 的坐标可能需要进行进一步变换才能适配 HTML 元素。
  • 文本项的变换: PDF 中的每个文本项都可能带有独立的变换矩阵,包含平移、缩放、旋转等操作,必须正确解码并应用这些变换。
  • 缩放因素: 不同的屏幕像素比(device pixel ratio)和视口缩放会导致页面内容放大或缩小,在处理坐标计算时需一并考虑。

你的代码中涉及到的 transform[0] 尝试做旋转,transform[4]transform[5] 来定位,并且用到了viewport.height,这种方法有其原理,但如果不对矩阵做深度了解就可能在变换和坐标处理上产生误差。而且尝试中使用除以 2 进行定位也并没有深刻理解其原理。

解决方案:理解变换矩阵及坐标映射

解决这个问题的关键,是对 item.transform 中的数据进行深入的分析,找到其在实际的页面坐标上的表现。下面提供两种方案来更正这个问题,我们避免复杂深奥的变换矩阵解析,简化方案。

方案一:简化的坐标变换

虽然 transform 属性的矩阵数据很详细,但是对于简单应用而言,并不需要进行详细的矩阵解析。只需要根据PDF原始坐标和页面的比例进行对应的变换。

  • 理解: 直接使用 transform[4]transform[5] 作为文本元素的 lefttop 值,并使用viewport的高度来反转垂直坐标。然后用计算的 scale 比例进行缩放即可。
  • 代码示例:
    const span = document.createElement('span');
    span.textContent = item.str;

    span.style.position = 'absolute';
    span.style.whiteSpace = 'pre'; //保持文本格式
    span.style.left = `${item.transform[4] * scale}px`;
    span.style.top = `${(viewport.height - item.transform[5]) * scale}px`;
    span.style.fontSize = `${item.height * scale}px`;
    // 添加到容器元素
    textLayerContainer.appendChild(span);
  • 操作步骤:

    1. 获取PDF页面的视口(viewport)信息及缩放比例。
    2. 循环处理每一个文本项。
    3. 设置文本元素的 leftitem.transform[4] * scaletop(viewport.height - item.transform[5]) * scale 。使用 scale 缩放 font-size
    4. 将此文本元素追加到 textLayerContainer 元素中。
    5. 使用 CSS 定位: 为文本层容器 (textLayerContainer ) 设置 position: absolute;,保证所有文本层绝对定位于canvas之上,并通过调整 z-index 以显示。
    6. 测试缩放:验证缩放页面时文本层是否正确同步缩放。

    此方法有效避免了旋转,但通常这种应用旋转概率比较低,绝大部分 PDF 都是文本横向排列。

方案二:考虑 Canvas 元素的 CSS 变换
如果你在使用过程中遇到浏览器默认的缩放影响了位置(例如在非 100% 缩放的情况下),则有必要额外考虑。
浏览器会直接根据 CSS 的缩放属性,进行DOM 元素的缩放。
对于 Canvas 这种 DOM 元素,它的缩放也是根据自身计算得到的,Canvas 的坐标系统并没有真正被影响,因此我们可以用额外的 css transform 进行辅助。

  • 理解: 使用 CSS 的 transform: scale(n) 将文本层的容器进行缩放,确保它始终与 Canvas 的视觉尺寸匹配,无论用户如何缩放页面。
  • 代码示例:
#text-layer-container {
  position: absolute;
  transform-origin: top left;
  z-index: 2;
  overflow: hidden;
}
 const container = document.getElementById('text-layer-container')
  const scale = canvasEl.offsetWidth / page.getViewport({ scale: 1 }).width * (window.devicePixelRatio || 1);
    //根据实际缩放值对 text layer container 进行 scale 缩放
  container.style.transform =  `scale(${1/scale})`;
这种方式的原理是将原本 HTML 元素受到浏览器默认缩放而产生的误差通过容器元素的缩放属性进行了抵消。这种做法可以适配更多的应用场景。
  • 操作步骤:

    1. 添加 CSS, 使得text-layer-container 根据 transform-origin: top left; 和绝对定位保持文本的对齐效果
    2. 计算出画布实际显示的 scale值。
    3. 根据 scale 的反向比例( 1/scale ),调整 textLayerContainer 的 CSS transform
    4. 同样对每个 span 元素按照方案一的步骤计算绝对定位坐标和字体大小。

    需要注意,这里的scale必须是通过实际DOM 渲染得到的。也就是说需要 container.offsetWidth, 避免在异步处理数据过程中导致缩放错误。

注意事项

  1. 容器溢出 :为 textLayerContainer 设置 overflow: hidden;,防止文本内容溢出。
  2. 性能优化 :文本层的渲染非常消耗性能。如果页面包含大量文字,应该考虑文本项的可见性。 只渲染视口中的文本。避免过度的DOM 操作
  3. 设备像素比: window.devicePixelRatio 需要兼容没有设备像素比的情况。
  4. 调试: 使用浏览器开发者工具,查看DOM元素位置,确保元素正确的缩放和偏移。
  5. z-index 管理 : 确保 textLayerContainer 有一个高于canvas元素的 z-index , 保证始终显示于其上方。

相关资源:
无。

这些步骤旨在更深入的理解 HTML 和 PDF 元素渲染原理。仔细应用这些步骤应该可以显著提高你的渲染精度和用户体验。 采取任何措施时,确保备份你的原始代码并遵循调试原则。