Skip to content

HELP:The highlighted area is not positioned correctly. #320

@flyingrookie

Description

@flyingrookie

The backend team migrated RagFlow service, and the frontend uses this library. In my Vue 3 project, I need to manually implement the highlighting after positioning, but I've encountered some issues with inaccurate positioning and areas not being fully covered. Below is my approach to handling the coordinates, taking into account the container width scaling ratio and device pixel ratio. I would appreciate some help.
const renderPdfPage = async (pdf, pageNum, container) => {
try {
const page = await pdf.getPage(pageNum)
const containerPadding = 40 // 与 CSS 保持一致
const maxWidth = container.clientWidth - containerPadding
// const RAGFLOW_BASE_WIDTH = 800
// const maxWidth = RAGFLOW_BASE_WIDTH
if (maxWidth <= 0) return

const pixelRatio = window.devicePixelRatio || 1
const rotation = page.rotate

// 1. 获取 Base Viewport (scale=1, 用于坐标转换)
const baseViewport = page.getViewport({ scale: 1, rotation })
// 计算适应容器的最大缩放比例
const maxScale = maxWidth / baseViewport.width

// 2. 逻辑 Viewport (用于 CSS 尺寸,scale=maxScale)
const logicalViewport = page.getViewport({ scale: maxScale, rotation })

// 3. 渲染 Viewport (用于 Canvas 物理尺寸,scale=maxScale * pixelRatio)
const renderViewport = page.getViewport({ scale: maxScale * pixelRatio, rotation })

// --- PDF 背景 canvas ---
const pdfCanvas = document.createElement('canvas')
pdfCanvas.width = renderViewport.width
pdfCanvas.height = renderViewport.height
pdfCanvas.style.width = `${logicalViewport.width}px`
pdfCanvas.style.height = `${logicalViewport.height}px`
pdfCanvas.style.display = 'block'
// pdfCanvas.style.margin = '0 auto 30px'
pdfCanvas.style.margin = '0'
pdfCanvas.style.boxShadow = '0 2px 10px rgba(0,0,0,0.1)'
pdfCanvas.style.borderRadius = '6px'

const pdfCtx = pdfCanvas.getContext('2d')
await page.render({ canvasContext: pdfCtx, viewport: renderViewport }).promise

// --- 高亮前景 canvas ---
const highlightCanvas = document.createElement('canvas')
highlightCanvas.width = renderViewport.width
highlightCanvas.height = renderViewport.height
highlightCanvas.style.width = `${logicalViewport.width}px` // 保持与 PDF canvas 相同的逻辑尺寸
highlightCanvas.style.height = `${logicalViewport.height}px`
highlightCanvas.style.position = 'absolute'
highlightCanvas.style.top = '0'
highlightCanvas.style.left = '0'
// highlightCanvas.style.left = '50%'
// highlightCanvas.style.transform = 'translateX(-50%)'
highlightCanvas.style.pointerEvents = 'none' // 确保不影响鼠标操作

// --- 包裹容器(相对定位)---
const wrapper = document.createElement('div')
wrapper.style.width = `${logicalViewport.width}px`
wrapper.style.margin = '0 auto 30px'
wrapper.style.position = 'relative'
wrapper.appendChild(pdfCanvas)
wrapper.appendChild(highlightCanvas)
container.appendChild(wrapper)

// 缓存 Viewport 信息,用于高亮时的精确坐标转换
pageCanvases.value[pageNum] = { pdfCanvas, highlightCanvas }
pageViewports.value[pageNum] = {
  page,
  baseViewport,
  logicalViewport,
  renderViewport,
  pixelRatio,
  maxScale,
  rotation: page.rotate
}

} catch (err) {
console.error(渲染第 ${pageNum} 页失败:, err)
}
}
const highlightChunk = (targetChunk) => {
const positions = targetChunk?.positions
if (!positions || !Array.isArray(positions)) return

clearHighlight()

// 1. 按页分组坐标
const byPage = {}
for (const item of positions) {
// 坐标格式:[page, x1, y1, x2, y2]
if (item.length !== 5) continue
const [page, x1, y1, x2, y2] = item
if (!byPage[page]) byPage[page] = []
byPage[page].push([x1, y1, x2, y2])
}

let firstPageToScroll = null

// 2. 绘制每个页面的高亮
for (const pageStr in byPage) {
const pageNum = parseInt(pageStr)
const canvases = pageCanvases.value[pageNum]
const pageInfo = pageViewports.value[pageNum]
if (!canvases || !pageInfo) continue
if (firstPageToScroll === null) {
firstPageToScroll = pageNum
}
const { highlightCanvas } = canvases
const { renderViewport } = pageInfo
const ctx = highlightCanvas.getContext('2d')

// 清空当前页的高亮区域
ctx.clearRect(0, 0, highlightCanvas.width, highlightCanvas.height)

// 试探性判断是否需要翻转 Y 轴
// 绘制高亮区域
for (const [x1, y1, x2, y2] of byPage[pageStr]) {
  let [x1v, y1v, x2v, y2v] = renderViewport.convertToViewportRectangle([x1, y1, x2, y2])
  const left = Math.min(x1v, x2v)
  const top = Math.min(y1v, y2v)
  const width = Math.abs(x2v - x1v)
  const height = Math.abs(y2v - y1v)

  // 绘制高亮区域
  ctx.fillStyle = 'rgba(255, 215, 0, 0.4)' // 黄色背景色
  ctx.fillRect(left, top, width, height)

  // 绘制边框
  ctx.strokeStyle = 'rgba(255, 215, 0, 0.6)'
  ctx.lineWidth = Math.max(1, 1) // 已为物理像素单位
  ctx.strokeRect(left + 0.5, top + 0.5, width - 1, height - 1)
}

}

// 3. 滚动到第一个高亮页面
if (firstPageToScroll !== null) {
const canvases = pageCanvases.value[firstPageToScroll]
const wrapper = canvases?.highlightCanvas?.parentElement
if (wrapper) {
// 使用 setTimeout 确保页面布局已稳定
setTimeout(() => {
wrapper.scrollIntoView({
behavior: 'smooth',
block: 'center' // 滚动到页面中央
})
}, 100)
}
}
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions