diff --git a/docengine/app/services/pdf_service.py b/docengine/app/services/pdf_service.py index 5b74c54..ce306b2 100644 --- a/docengine/app/services/pdf_service.py +++ b/docengine/app/services/pdf_service.py @@ -107,49 +107,89 @@ class NativePDFService: def _extract_text_blocks(self, doc_page: DocumentPage, page: fitz.Page) -> None: """Extract text blocks with positioning and font information.""" - blocks = page.get_text("dict", flags=fitz.TEXT_PRESERVE_WHITESPACE)["blocks"] + blocks = page.get_text("rawdict")["blocks"] sequence = 0 + chunks = [] for block in blocks: - if block["type"] != 0: # Skip non-text blocks + if block["type"] != 0: continue - block_text_parts = [] - font_info = {"family": None, "size": None, "color": None, "style": None} + current_chunk = None + space_count = 0 for line in block.get("lines", []): for span in line.get("spans", []): - text = span.get("text", "").strip() - if text: - block_text_parts.append(text) - # Capture font info from the first non-empty span - if font_info["family"] is None: - font_info["family"] = span.get("font", None) - font_info["size"] = span.get("size", None) - color_int = span.get("color", 0) - font_info["color"] = f"#{color_int:06x}" if isinstance(color_int, int) else None - flags = span.get("flags", 0) - styles = [] - if flags & 1: - styles.append("superscript") - if flags & 2: - styles.append("italic") - if flags & 4: - styles.append("serif") - if flags & 8: - styles.append("monospace") - if flags & 16: - styles.append("bold") - font_info["style"] = ",".join(styles) if styles else "regular" + font_size = span.get("size", 12.0) + space_threshold = font_size * 1.5 + + font_family = span.get("font", None) + color_int = span.get("color", 0) + font_color = f"#{color_int:06x}" if isinstance(color_int, int) else None + + flags = span.get("flags", 0) + styles = [] + if flags & 1: styles.append("superscript") + if flags & 2: styles.append("italic") + if flags & 4: styles.append("serif") + if flags & 8: styles.append("monospace") + if flags & 16: styles.append("bold") + font_style = ",".join(styles) if styles else "regular" + + font_info = { + "family": font_family, + "size": font_size, + "color": font_color, + "style": font_style + } + + for char in span.get("chars", []): + c = char["c"] + bbox = char["bbox"] + + if c == ' ': + space_count += 1 + if space_count >= 2: + if current_chunk and current_chunk["text"].strip(): + chunks.append(current_chunk) + current_chunk = None + elif current_chunk: + current_chunk["text"] += c + current_chunk["bbox"][2] = max(current_chunk["bbox"][2], bbox[2]) + current_chunk["bbox"][3] = max(current_chunk["bbox"][3], bbox[3]) + continue + else: + space_count = 0 + + if current_chunk is None: + current_chunk = {"text": c, "bbox": list(bbox), "font_info": font_info} + continue + + prev_x1 = current_chunk["bbox"][2] + distance = bbox[0] - prev_x1 + + if distance > space_threshold: + if current_chunk["text"].strip(): + chunks.append(current_chunk) + current_chunk = {"text": c, "bbox": list(bbox), "font_info": font_info} + else: + current_chunk["text"] += c + current_chunk["bbox"][2] = max(current_chunk["bbox"][2], bbox[2]) + current_chunk["bbox"][3] = max(current_chunk["bbox"][3], bbox[3]) + current_chunk["bbox"][1] = min(current_chunk["bbox"][1], bbox[1]) + current_chunk["bbox"][0] = min(current_chunk["bbox"][0], bbox[0]) - full_text = " ".join(block_text_parts) - if not full_text.strip(): - continue + if current_chunk and current_chunk["text"].strip(): + chunks.append(current_chunk) + current_chunk = None - bbox = block["bbox"] + for chunk in chunks: + bbox = chunk["bbox"] + font_info = chunk["font_info"] + self.text_block_repo.create_text_block( page_id=doc_page.id, - text=full_text, + text=chunk["text"].strip(), x=bbox[0], y=bbox[1], width=bbox[2] - bbox[0],