Documentsragflow
PDF Chunk Position Rendering
PDF Chunk Position Rendering
Type
Topic
Status
Published
Created
Jul 8, 2026
Updated
Jul 8, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

PDF Chunk Position Rendering#

RAGFlow highlights the source location of each retrieved PDF chunk in its document viewer. This requires converting bounding-box coordinates from the parser's output space into the coordinate system expected by the React frontend. The pipeline has three stages: (1) parser → canonical storage format, (2) API response returning positions arrays to the frontend, and (3) frontend rendering using react-pdf-highlighter.


Canonical Position Format#

Every PDF chunk stores positions as a list of 5-element tuples:

[page_number, left, right, top, bottom] # integers, 1-based page numbers

This is the position_int field persisted in the chunk store . The helper build_pdf_position_fields() converts floating-point normalized positions into integer position_int, page_num_int, and top_int fields used for sorting and retrieval.

extract_pdf_positions() is the central normalizer. It accepts positions from any of four formats and emits a canonical list:

  • _pdf_positions (already-normalized internal field)
  • positions (direct list)
  • position_tag string — parsed by RAGFlowPdfParser.extract_positions() using the embedded format @@{pages}\t{x0}\t{x1}\t{top}\t{bottom}##
  • Individual fields: page_number, x0, x1, top, bottom

Page numbers of 0 or -N (0-based from some parsers) are corrected to 1-based during normalization .


Parser-Specific Coordinate Spaces#

Different parsers produce coordinates in different spaces; each must be normalized before storage.

DeepDoc (RAGFlowPdfParser)#

Uses pixel space with a top-left origin, at 72 × zoom DPI (where zoom defaults to 3, so 216 DPI for rendering). Position tags are encoded as @@{page}\t{x0}\t{x1}\t{top}\t{bottom}## . The Go parser mirrors this, with an explicit DlaScale = 3.0 factor used to scale OCR-detected boxes from 216 DPI down to 72 DPI PDF space .

MinerU Parser#

MinerU outputs bounding boxes in 0–1000 normalized coordinates relative to each page's width and height. _line_tag() in mineru_parser.py converts these to pixel coordinates by multiplying (bbox_value / 1000.0) × page_dimension . When rendered page images are unavailable, the parser falls back to captured page sizes, then to a default page size, ensuring coordinates are always in display space rather than raw normalized values .

MinerU also returns inverted (x0 > x1, top > bottom) boxes for some images; these are swapped before use .

Cross-Page Table Problem (MinerU)#

MinerU merges a cross-page table into a single content_list.json item but retains only the first fragment's bbox/page_idx. Before the fix in PR #16283, RAGFlow faithfully rendered that single bbox, displacing highlights to the page-header area for tall multi-page tables . The fix reads middle.json (which still has per-page fragments), normalizes those fragments into 0–1000 space, and emits one highlight tag per page the table spans .

Large-PDF Chunked Parsing#

When a PDF exceeds PDF_PARSER_PAGE_BATCH_SIZE (default: 50 pages), DeepDoc parses in batches. _to_global_boxes() adds the batch's page_from offset to every page_number value and re-offsets position_tag strings via _offset_position_tag(), ensuring chunk-local page indices are promoted to document-global ones .


Thumbnail Preview Generation (Backend)#

_crop_pdf_preview() produces the thumbnail shown alongside each chunk reference:

  1. Pages are rasterized at 72 × PDF_PREVIEW_ZOOM DPI (PDF_PREVIEW_ZOOM = 3) .
  2. Coordinates are scaled by zoom when cropping pixel data: left * zoom, top * zoom, etc. .
  3. Context strips (PDF_PREVIEW_CONTEXT = 120 points above and below the chunk, PDF_PREVIEW_GAP = 6) are added and dimmed with a 50 % alpha overlay so the highlighted region is visually distinct .
  4. For multi-position chunks, crops from each span are assembled vertically into a single canvas .

restore_pdf_text_previews() orchestrates thumbnail generation inside the agent/flow pipeline, caching previews by position key to avoid re-rendering duplicate spans .


Frontend Rendering#

Highlight Data Shape#

The API returns chunks with a positions field (aliased from position_int). buildChunkHighlights() in document-util.ts converts these into IHighlight objects for react-pdf-highlighter:

  • x1 = positions[i][1], x2 = positions[i][2], y1 = positions[i][3], y2 = positions[i][4]
  • pageNumber = positions[i][0]
  • width and height are populated from the PDF viewport

The viewport dimensions are obtained once from pdfDocument.getPage(1).getViewport({ scale: 1 }) and passed down via setWidthAndHeight. This means width/height always reflect page 1's dimensions; for PDFs with non-uniform page sizes, coordinates on other pages will be passed through with page-1 dimensions — a known limitation.

PdfPreview Component#

PdfPreview wraps react-pdf-highlighter's PdfHighlighter. Highlights are scrolled into view automatically: a useEffect fires scrollTo(highlights[0]) 100 ms after highlights change . The PdfLoader passes authorization headers so the PDF binary can be fetched from the backend storage API .


Key Known Issues & Fixes#

IssueRoot CauseFix
Highlight displaced to page header on wide MinerU tablesMinerU stores only first page's bbox for merged cross-page tablesPR #16283: reconstruct per-page positions from middle.json
"Coordinate lower is less than upper" error with MinerUMinerU emits inverted x0>x1 or top>bottom bbox valuesPR #13483: swap inverted axis pairs
Wrong page numbers after batch parsing of large PDFsChunk-local page indices not offset to global document page numbersPR #14385: _to_global_boxes() adds page_from
Raw 0–1000 values emitted when page images unavailableMissing fallback in _line_tagPR #16283: fall back to captured/default page size

Reference Map#

LayerFile / ModuleRole
Backend normalizationrag/flow/parser/pdf_chunk_metadata.pyCanonical format, thumbnail crop, preview restore
Frontend highlight builderweb/src/utils/document-util.tspositions[]IHighlight[]
PDF viewer componentweb/src/components/document-preview/pdf-preview.tsxreact-pdf-highlighter wrapper, scroll-to-highlight
MinerU parserdeepdoc/parser/mineru_parser.py0–1000 → pixel conversion, cross-page table fix
DeepDoc Python parserdeepdoc/parser/pdf_parser.pyposition_tag encoding, extract_positions()
Go PDF parserinternal/deepdoc/parser/pdf/DlaScale=3.0 OCR→PDF DPI conversion
PDF Chunk Position Rendering | Dosu