179 lines
6.0 KiB
C++
179 lines
6.0 KiB
C++
#include "pdfengine/document/document_builder.hpp"
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
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<Paragraph> 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<size_t>(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<uint32_t>(static_cast<unsigned char>(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<size_t>(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<uint32_t>(static_cast<unsigned char>(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
|