#include "pdfengine/document/document_builder.hpp" #include #include namespace pdfengine::document { PageModel DocumentBuilder::buildFromOCR(const RawOCRPage& rawPage) const { PageModel model; model.pageIndex = rawPage.pageIndex; model.width = rawPage.pageWidth; model.height = rawPage.pageHeight; if (rawPage.lines.empty()) { return model; } // Standard baseline clustering to group lines into Paragraphs std::vector paragraphs; Paragraph currentPara; double lastLineY = -1.0; double lastLineH = 0.0; for (size_t lIdx = 0; lIdx < rawPage.lines.size(); ++lIdx) { const auto& rawLine = rawPage.lines[lIdx]; TextLine line; line.x = rawLine.x; line.y = rawLine.y; line.w = rawLine.width; line.h = rawLine.height; line.baselineY = rawLine.baselineY; line.angle = 0.0; TextRun run; run.text = rawLine.text; run.fontName = rawLine.fontName.empty() ? "Helvetica" : rawLine.fontName; bool isBold = rawLine.isBold || rawLine.fontWeight >= 600; bool isItalic = rawLine.isItalic || rawLine.fontStyle == "italic"; run.flags = (isBold ? 2 : 0) | (isItalic ? 1 : 0); run.fontWeight = rawLine.fontWeight > 0 ? rawLine.fontWeight : (isBold ? 700 : 400); run.fontStyle = !rawLine.fontStyle.empty() ? rawLine.fontStyle : (isItalic ? "italic" : "normal"); if (!rawLine.fontFace.empty()) { run.fontFace = rawLine.fontFace; } else if (isBold && isItalic) { run.fontFace = run.fontName + "-BoldItalic"; } else if (isBold) { run.fontFace = run.fontName + "-Bold"; } else if (isItalic) { run.fontFace = run.fontName + "-Italic"; } else { run.fontFace = run.fontName; } run.internalFontId = !rawLine.fontId.empty() ? rawLine.fontId : (run.fontName + "_TrueType_" + std::to_string(run.fontWeight)); run.fontSize = rawLine.fontSize > 0.0 ? rawLine.fontSize : (rawLine.height * 0.72); run.x = rawLine.x; run.y = rawLine.y; run.w = rawLine.width; run.h = rawLine.height; run.fillColor = "#000000"; run.fontFidelity = "exact"; // Generate Glyphs if (!rawLine.words.empty()) { for (const auto& w : rawLine.words) { double charW = w.width / std::max(1, w.text.size()); double currX = w.x; for (char ch : w.text) { Glyph g; g.text = std::string(1, ch); g.unicode = static_cast(static_cast(ch)); g.fontName = run.fontName; g.fontSize = run.fontSize; g.flags = run.flags; g.originX = currX; g.originY = rawLine.baselineY; g.bboxX = currX; g.bboxY = w.y; g.bboxW = charW; g.bboxH = w.height; run.glyphs.push_back(g); line.glyphs.push_back(g); currX += charW; } } } else { double charW = rawLine.width / std::max(1, rawLine.text.size()); double currX = rawLine.x; for (char ch : rawLine.text) { Glyph g; g.text = std::string(1, ch); g.unicode = static_cast(static_cast(ch)); g.fontName = run.fontName; g.fontSize = run.fontSize; g.flags = run.flags; g.originX = currX; g.originY = rawLine.baselineY; g.bboxX = currX; g.bboxY = rawLine.y; g.bboxW = charW; g.bboxH = rawLine.height; run.glyphs.push_back(g); line.glyphs.push_back(g); currX += charW; } } line.runs.push_back(run); // Check paragraph break (vertical gap > 1.5x line height) bool isNewPara = false; if (lastLineY >= 0.0) { double gap = rawLine.y - (lastLineY + lastLineH); if (gap > lastLineH * 1.5 || gap < -lastLineH * 0.5) { isNewPara = true; } } if (isNewPara && !currentPara.lines.empty()) { // Finalize current paragraph bounds double pX1 = currentPara.lines.front().x; double pY1 = currentPara.lines.front().y; double pX2 = pX1 + currentPara.lines.front().w; double pY2 = pY1 + currentPara.lines.front().h; for (const auto& l : currentPara.lines) { pX1 = std::min(pX1, l.x); pY1 = std::min(pY1, l.y); pX2 = std::max(pX2, l.x + l.w); pY2 = std::max(pY2, l.y + l.h); } currentPara.x = pX1; currentPara.y = pY1; currentPara.w = pX2 - pX1; currentPara.h = pY2 - pY1; paragraphs.push_back(currentPara); currentPara = Paragraph(); } currentPara.lines.push_back(line); lastLineY = rawLine.y; lastLineH = rawLine.height; } if (!currentPara.lines.empty()) { double pX1 = currentPara.lines.front().x; double pY1 = currentPara.lines.front().y; double pX2 = pX1 + currentPara.lines.front().w; double pY2 = pY1 + currentPara.lines.front().h; for (const auto& l : currentPara.lines) { pX1 = std::min(pX1, l.x); pY1 = std::min(pY1, l.y); pX2 = std::max(pX2, l.x + l.w); pY2 = std::max(pY2, l.y + l.h); } currentPara.x = pX1; currentPara.y = pY1; currentPara.w = pX2 - pX1; currentPara.h = pY2 - pY1; paragraphs.push_back(currentPara); } model.paragraphs = std::move(paragraphs); return model; } } // namespace pdfengine::document