integration done and testing done
This commit is contained in:
@@ -111,12 +111,64 @@ PYBIND11_MODULE(pdfengine, m) {
|
||||
return "FontInfo(font_name='" + self.fontName + "', type='" + self.type + "', is_embedded=" + (self.isEmbedded ? "True" : "False") + ")";
|
||||
});
|
||||
|
||||
py::class_<pdfengine::Glyph>(m, "Glyph")
|
||||
.def_readonly("text", &pdfengine::Glyph::text)
|
||||
.def_readonly("unicode", &pdfengine::Glyph::unicode)
|
||||
.def_readonly("font_name", &pdfengine::Glyph::fontName)
|
||||
.def_readonly("flags", &pdfengine::Glyph::flags)
|
||||
.def_readonly("font_size", &pdfengine::Glyph::fontSize)
|
||||
.def_readonly("origin_x", &pdfengine::Glyph::originX)
|
||||
.def_readonly("origin_y", &pdfengine::Glyph::originY)
|
||||
.def_readonly("bbox_x", &pdfengine::Glyph::bboxX)
|
||||
.def_readonly("bbox_y", &pdfengine::Glyph::bboxY)
|
||||
.def_readonly("bbox_w", &pdfengine::Glyph::bboxW)
|
||||
.def_readonly("bbox_h", &pdfengine::Glyph::bboxH)
|
||||
.def_readonly("angle", &pdfengine::Glyph::angle);
|
||||
|
||||
py::class_<pdfengine::TextRun>(m, "TextRun")
|
||||
.def_readonly("text", &pdfengine::TextRun::text)
|
||||
.def_readonly("font_name", &pdfengine::TextRun::fontName)
|
||||
.def_readonly("flags", &pdfengine::TextRun::flags)
|
||||
.def_readonly("font_size", &pdfengine::TextRun::fontSize)
|
||||
.def_readonly("internal_font_id", &pdfengine::TextRun::internalFontId)
|
||||
.def_readonly("is_embedded", &pdfengine::TextRun::isEmbedded)
|
||||
.def_readonly("type", &pdfengine::TextRun::type)
|
||||
.def_readonly("glyphs", &pdfengine::TextRun::glyphs)
|
||||
.def_readonly("x", &pdfengine::TextRun::x)
|
||||
.def_readonly("y", &pdfengine::TextRun::y)
|
||||
.def_readonly("w", &pdfengine::TextRun::w)
|
||||
.def_readonly("h", &pdfengine::TextRun::h);
|
||||
|
||||
py::class_<pdfengine::TextLine>(m, "TextLine")
|
||||
.def_readonly("runs", &pdfengine::TextLine::runs)
|
||||
.def_readonly("baseline_y", &pdfengine::TextLine::baselineY)
|
||||
.def_readonly("x", &pdfengine::TextLine::x)
|
||||
.def_readonly("y", &pdfengine::TextLine::y)
|
||||
.def_readonly("w", &pdfengine::TextLine::w)
|
||||
.def_readonly("h", &pdfengine::TextLine::h);
|
||||
|
||||
py::class_<pdfengine::Paragraph>(m, "Paragraph")
|
||||
.def_readonly("lines", &pdfengine::Paragraph::lines)
|
||||
.def_readonly("x", &pdfengine::Paragraph::x)
|
||||
.def_readonly("y", &pdfengine::Paragraph::y)
|
||||
.def_readonly("w", &pdfengine::Paragraph::w)
|
||||
.def_readonly("h", &pdfengine::Paragraph::h);
|
||||
|
||||
py::class_<pdfengine::PageModel>(m, "PageModel")
|
||||
.def_readonly("paragraphs", &pdfengine::PageModel::paragraphs)
|
||||
.def_readonly("width", &pdfengine::PageModel::width)
|
||||
.def_readonly("height", &pdfengine::PageModel::height)
|
||||
.def_readonly("page_index", &pdfengine::PageModel::pageIndex);
|
||||
|
||||
py::class_<pdfengine::PdfPage, std::shared_ptr<pdfengine::PdfPage>>(m, "PdfPage")
|
||||
.def_property_readonly("width", &pdfengine::PdfPage::width)
|
||||
.def_property_readonly("height", &pdfengine::PdfPage::height)
|
||||
.def("render", [](const pdfengine::PdfPage& self, int dpi) {
|
||||
return get_or_throw(self.render(dpi));
|
||||
}, py::arg("dpi") = 96)
|
||||
.def("extract_document_model", [](const pdfengine::PdfPage& self) {
|
||||
return get_or_throw(self.extractDocumentModel());
|
||||
})
|
||||
.def("extract_text", [](const pdfengine::PdfPage& self) {
|
||||
return get_or_throw(self.extractText());
|
||||
})
|
||||
|
||||
@@ -97,12 +97,16 @@ struct TextRun {
|
||||
uint32_t flags = 0;
|
||||
double fontSize = 0.0;
|
||||
std::string internalFontId;
|
||||
bool isEmbedded = false;
|
||||
std::string type;
|
||||
std::vector<Glyph> glyphs;
|
||||
double x = 0.0, y = 0.0, w = 0.0, h = 0.0;
|
||||
};
|
||||
|
||||
struct TextLine {
|
||||
std::vector<TextRun> runs;
|
||||
std::vector<Glyph> glyphs;
|
||||
double angle = 0.0;
|
||||
double baselineY = 0.0;
|
||||
double x = 0.0, y = 0.0, w = 0.0, h = 0.0;
|
||||
};
|
||||
|
||||
@@ -854,6 +854,13 @@ std::expected<PageModel, EngineError> PdfiumPage::extractDocumentModel() const {
|
||||
return model;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, FontInfo> fontMap;
|
||||
if (auto fontsRes = getFonts()) {
|
||||
for (const auto& f : *fontsRes) {
|
||||
fontMap[f.fontName] = f;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Glyph> documentGlyphs;
|
||||
documentGlyphs.reserve(charCount);
|
||||
|
||||
@@ -969,7 +976,13 @@ std::expected<PageModel, EngineError> PdfiumPage::extractDocumentModel() const {
|
||||
currentRun.fontName = firstG->fontName;
|
||||
currentRun.fontSize = firstG->fontSize;
|
||||
currentRun.flags = firstG->flags;
|
||||
if (auto it = fontMap.find(currentRun.fontName); it != fontMap.end()) {
|
||||
currentRun.internalFontId = it->second.internalFontId;
|
||||
currentRun.isEmbedded = it->second.isEmbedded;
|
||||
currentRun.type = it->second.type;
|
||||
}
|
||||
currentRun.glyphs.push_back(*firstG);
|
||||
currentRun.text += firstG->text;
|
||||
|
||||
for (size_t i = 1; i < line.glyphs.size(); ++i) {
|
||||
const auto& prevG = line.glyphs[i-1];
|
||||
@@ -1004,17 +1017,29 @@ std::expected<PageModel, EngineError> PdfiumPage::extractDocumentModel() const {
|
||||
currentRun.fontName = currG.fontName;
|
||||
currentRun.fontSize = currG.fontSize;
|
||||
currentRun.flags = currG.flags;
|
||||
if (auto it = fontMap.find(currentRun.fontName); it != fontMap.end()) {
|
||||
currentRun.internalFontId = it->second.internalFontId;
|
||||
currentRun.isEmbedded = it->second.isEmbedded;
|
||||
currentRun.type = it->second.type;
|
||||
}
|
||||
}
|
||||
currentRun.glyphs.push_back(spaceGlyph);
|
||||
currentRun.text += spaceGlyph.text;
|
||||
} else if (breakRun) {
|
||||
line.runs.push_back(std::move(currentRun));
|
||||
currentRun = TextRun();
|
||||
currentRun.fontName = currG.fontName;
|
||||
currentRun.fontSize = currG.fontSize;
|
||||
currentRun.flags = currG.flags;
|
||||
if (auto it = fontMap.find(currentRun.fontName); it != fontMap.end()) {
|
||||
currentRun.internalFontId = it->second.internalFontId;
|
||||
currentRun.isEmbedded = it->second.isEmbedded;
|
||||
currentRun.type = it->second.type;
|
||||
}
|
||||
}
|
||||
|
||||
currentRun.glyphs.push_back(currG);
|
||||
currentRun.text += currG.text;
|
||||
}
|
||||
if (!currentRun.glyphs.empty()) {
|
||||
line.runs.push_back(std::move(currentRun));
|
||||
@@ -1965,10 +1990,12 @@ std::expected<std::vector<uint8_t>, EngineError> PdfiumDocument::getFontData(con
|
||||
|
||||
// Always cache every font we encounter to avoid rescanning
|
||||
if (fontDataCache_.find(fontName) == fontDataCache_.end()) {
|
||||
size_t buflen = FPDFFont_GetFontData(font, nullptr, 0);
|
||||
size_t buflen = 0;
|
||||
FPDFFont_GetFontData(font, nullptr, 0, &buflen);
|
||||
if (buflen > 0) {
|
||||
std::vector<uint8_t> buffer(buflen);
|
||||
if (FPDFFont_GetFontData(font, buffer.data(), buflen) > 0) {
|
||||
size_t actual_len = 0;
|
||||
if (FPDFFont_GetFontData(font, buffer.data(), buflen, &actual_len)) {
|
||||
fontDataCache_[fontName] = buffer;
|
||||
} else {
|
||||
fontDataCache_[fontName] = std::vector<uint8_t>();
|
||||
|
||||
Generated
+55
-15
@@ -59,7 +59,6 @@
|
||||
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.0",
|
||||
"@babel/generator": "^7.29.0",
|
||||
@@ -269,10 +268,31 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.0.tgz",
|
||||
"integrity": "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.2.2",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.0.tgz",
|
||||
"integrity": "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
||||
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
|
||||
"integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -755,6 +775,37 @@
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
|
||||
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.2.1",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
||||
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
||||
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.1.tgz",
|
||||
@@ -1087,7 +1138,6 @@
|
||||
"integrity": "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
@@ -1098,7 +1148,6 @@
|
||||
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
@@ -1158,7 +1207,6 @@
|
||||
"integrity": "sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.59.3",
|
||||
"@typescript-eslint/types": "8.59.3",
|
||||
@@ -1389,7 +1437,6 @@
|
||||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -1480,7 +1527,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.10.12",
|
||||
"caniuse-lite": "^1.0.30001782",
|
||||
@@ -1628,7 +1674,6 @@
|
||||
"integrity": "sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
@@ -2524,7 +2569,6 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -2585,7 +2629,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz",
|
||||
"integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -2757,7 +2800,6 @@
|
||||
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -2843,7 +2885,6 @@
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz",
|
||||
"integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"lightningcss": "^1.32.0",
|
||||
"picomatch": "^4.0.4",
|
||||
@@ -2968,7 +3009,6 @@
|
||||
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
+43
-15
@@ -7,9 +7,12 @@
|
||||
--mono: 'JetBrains Mono', monospace;
|
||||
|
||||
/* Color Palette - Premium Royal/Midnight */
|
||||
--bg-main: #020617; /* Slate 950 */
|
||||
--bg-card: #0f172a; /* Slate 900 */
|
||||
--bg-sidebar: #090d16; /* Deep Midnight */
|
||||
--bg-main: #020617;
|
||||
/* Slate 950 */
|
||||
--bg-card: #0f172a;
|
||||
/* Slate 900 */
|
||||
--bg-sidebar: #090d16;
|
||||
/* Deep Midnight */
|
||||
--bg-accent-indigo: #4f46e5;
|
||||
--bg-accent-indigo-hover: #4338ca;
|
||||
--border-main: #1e293b;
|
||||
@@ -175,9 +178,18 @@ body {
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.status-checking .health-dot { background: var(--color-warning); animation: pulse 1.5s infinite; }
|
||||
.status-healthy .health-dot { background: var(--color-success); }
|
||||
.status-unhealthy .health-dot { background: var(--color-error); }
|
||||
.status-checking .health-dot {
|
||||
background: var(--color-warning);
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
.status-healthy .health-dot {
|
||||
background: var(--color-success);
|
||||
}
|
||||
|
||||
.status-unhealthy .health-dot {
|
||||
background: var(--color-error);
|
||||
}
|
||||
|
||||
.wasm-badge {
|
||||
display: flex;
|
||||
@@ -422,7 +434,8 @@ body {
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.doc-list-container, .annotations-list {
|
||||
.doc-list-container,
|
||||
.annotations-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
@@ -605,7 +618,8 @@ body {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background: #0b0f19; /* Slightly darker midnight */
|
||||
background: #0b0f19;
|
||||
/* Slightly darker midnight */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: start;
|
||||
@@ -698,11 +712,13 @@ body {
|
||||
}
|
||||
|
||||
.highlight-box.type-highlight {
|
||||
background: #fde047; /* Yellow 300 */
|
||||
background: #fde047;
|
||||
/* Yellow 300 */
|
||||
}
|
||||
|
||||
.highlight-box.type-comment {
|
||||
background: #fecdd3; /* Rose 200 */
|
||||
background: #fecdd3;
|
||||
/* Rose 200 */
|
||||
border-bottom: 2px solid #f43f5e;
|
||||
}
|
||||
|
||||
@@ -732,7 +748,7 @@ body {
|
||||
font-weight: 700;
|
||||
padding: 4px 10px;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.25);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
@@ -751,11 +767,23 @@ body {
|
||||
|
||||
/* Animations */
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
@@ -118,7 +118,7 @@ export interface PageRotationData {
|
||||
rotation: 0 | 90 | 180 | 270;
|
||||
}
|
||||
|
||||
export interface PageDeletionData {}
|
||||
export interface PageDeletionData { }
|
||||
|
||||
export interface PageReorderData {
|
||||
destPageIndex: number;
|
||||
@@ -266,6 +266,12 @@ class GatewayService {
|
||||
}
|
||||
}
|
||||
|
||||
async getPageModel(documentId: string, pageIndex: number): Promise<any> {
|
||||
const response = await fetch(`${this.baseUrl}/documents/${documentId}/pages/${pageIndex}/model`);
|
||||
if (!response.ok) throw new Error(`Failed to get page model: ${response.statusText}`);
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async applyEdits(documentId: string, operations: EditOperation[]): Promise<{ success: boolean; newDocumentId: string }> {
|
||||
const response = await fetch(`${this.baseUrl}/edits/${documentId}`, {
|
||||
method: 'POST',
|
||||
|
||||
@@ -152,7 +152,7 @@ export const PDFViewer = React.forwardRef<PDFViewerRef, PDFViewerProps>(({
|
||||
}
|
||||
|
||||
const isInside = (pageBottom >= viewportTop - (layout.height * buffer)) &&
|
||||
(pageTop <= viewportBottom + (layout.height * buffer));
|
||||
(pageTop <= viewportBottom + (layout.height * buffer));
|
||||
|
||||
if (isInside) {
|
||||
visible.push(layout);
|
||||
@@ -201,6 +201,35 @@ export const PDFViewer = React.forwardRef<PDFViewerRef, PDFViewerProps>(({
|
||||
};
|
||||
}, [visiblePages, documentId, zoom, renderedPages]);
|
||||
|
||||
useEffect(() => {
|
||||
const verifyPageModel = async () => {
|
||||
if (visiblePages.length > 0 && documentId && !renderedPages[visiblePages[0].index + '_verified']) {
|
||||
const pageIndex = visiblePages[0].index;
|
||||
try {
|
||||
const model = await gatewayService.getPageModel(documentId, pageIndex);
|
||||
console.log(`--- Verification for Page ${pageIndex} ---`);
|
||||
model.paragraphs?.forEach((p: any) => {
|
||||
p.lines?.forEach((l: any) => {
|
||||
l.runs?.forEach((r: any) => {
|
||||
const isBold = (r.flags & 262144) !== 0 || r.font_name.toLowerCase().includes('bold');
|
||||
const isItalic = (r.flags & 64) !== 0 || r.font_name.toLowerCase().includes('italic');
|
||||
console.log(`Run Text: "${r.text}", Font Name: ${r.font_name}, Font Size: ${r.font_size}, Bold: ${isBold}, Italic: ${isItalic}, Embedded: ${r.is_embedded}, Type: ${r.type}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
setRenderedPages((prev) => {
|
||||
const next = [...prev];
|
||||
next[pageIndex + '_verified' as any] = 'true';
|
||||
return next;
|
||||
});
|
||||
} catch (e) {
|
||||
// ignore or log
|
||||
}
|
||||
}
|
||||
};
|
||||
verifyPageModel();
|
||||
}, [visiblePages, documentId]);
|
||||
|
||||
const handleTextSelection = (text: string, bbox: Rect) => {
|
||||
if (activeTool === 'highlight') {
|
||||
const newAnno: Annotation = {
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
<<<<<<< HEAD
|
||||
from typing import List, Annotated
|
||||
from fastapi import APIRouter, HTTPException, status, File, UploadFile, Query
|
||||
=======
|
||||
from fastapi import APIRouter, File, HTTPException, UploadFile, status
|
||||
>>>>>>> 2638dfe969874b954502b26656df48ad971e306d
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.services import engine
|
||||
@@ -181,16 +176,10 @@ class FontInfoResponse(BaseModel):
|
||||
descent: float
|
||||
capHeight: float
|
||||
|
||||
<<<<<<< HEAD
|
||||
@router.get("/{document_id}/fonts", response_model=List[FontInfoResponse])
|
||||
def get_document_fonts(document_id: str, start_page: Annotated[int, Query(ge=0)] = 0, end_page: Annotated[int, Query(ge=-1)] = -1) -> List[FontInfoResponse]:
|
||||
=======
|
||||
|
||||
@router.get("/{document_id}/fonts", response_model=list[FontInfoResponse])
|
||||
def get_document_fonts(
|
||||
document_id: str, start_page: int = 0, end_page: int = -1
|
||||
) -> list[FontInfoResponse]:
|
||||
>>>>>>> 2638dfe969874b954502b26656df48ad971e306d
|
||||
if not engine.is_available():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_501_NOT_IMPLEMENTED,
|
||||
@@ -326,3 +315,134 @@ def search_document(document_id: str, q: str) -> list[SearchMatch]:
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
||||
|
||||
class GlyphModel(BaseModel):
|
||||
text: str
|
||||
unicode: int
|
||||
font_name: str
|
||||
flags: int
|
||||
font_size: float
|
||||
origin_x: float
|
||||
origin_y: float
|
||||
bbox_x: float
|
||||
bbox_y: float
|
||||
bbox_w: float
|
||||
bbox_h: float
|
||||
angle: float
|
||||
|
||||
class TextRunModel(BaseModel):
|
||||
text: str
|
||||
font_name: str
|
||||
flags: int
|
||||
font_size: float
|
||||
internal_font_id: str
|
||||
is_embedded: bool
|
||||
type: str
|
||||
glyphs: list[GlyphModel]
|
||||
x: float
|
||||
y: float
|
||||
w: float
|
||||
h: float
|
||||
|
||||
class TextLineModel(BaseModel):
|
||||
runs: list[TextRunModel]
|
||||
baseline_y: float
|
||||
x: float
|
||||
y: float
|
||||
w: float
|
||||
h: float
|
||||
|
||||
class ParagraphModel(BaseModel):
|
||||
lines: list[TextLineModel]
|
||||
x: float
|
||||
y: float
|
||||
w: float
|
||||
h: float
|
||||
|
||||
class PageModelResponse(BaseModel):
|
||||
paragraphs: list[ParagraphModel]
|
||||
width: float
|
||||
height: float
|
||||
page_index: int
|
||||
|
||||
@router.get("/{document_id}/pages/{page_index}/model", response_model=PageModelResponse)
|
||||
def get_page_model(document_id: str, page_index: int) -> PageModelResponse:
|
||||
if not engine.is_available():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_501_NOT_IMPLEMENTED,
|
||||
detail="Engine bridge (bindings/python) not yet available.",
|
||||
)
|
||||
|
||||
doc_info = document_store.get_document(document_id)
|
||||
if not doc_info:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document not found")
|
||||
|
||||
try:
|
||||
doc = doc_info["doc_instance"]
|
||||
page = doc.get_page(page_index)
|
||||
model = page.extract_document_model()
|
||||
|
||||
paragraphs = []
|
||||
for p in model.paragraphs:
|
||||
lines = []
|
||||
for l in p.lines:
|
||||
runs = []
|
||||
for r in l.runs:
|
||||
glyphs = []
|
||||
for g in r.glyphs:
|
||||
glyphs.append(GlyphModel(
|
||||
text=g.text,
|
||||
unicode=g.unicode,
|
||||
font_name=g.font_name,
|
||||
flags=g.flags,
|
||||
font_size=g.font_size,
|
||||
origin_x=g.origin_x,
|
||||
origin_y=g.origin_y,
|
||||
bbox_x=g.bbox_x,
|
||||
bbox_y=g.bbox_y,
|
||||
bbox_w=g.bbox_w,
|
||||
bbox_h=g.bbox_h,
|
||||
angle=g.angle
|
||||
))
|
||||
runs.append(TextRunModel(
|
||||
text=r.text,
|
||||
font_name=r.font_name,
|
||||
flags=r.flags,
|
||||
font_size=r.font_size,
|
||||
internal_font_id=r.internal_font_id,
|
||||
is_embedded=r.is_embedded,
|
||||
type=r.type,
|
||||
glyphs=glyphs,
|
||||
x=r.x,
|
||||
y=r.y,
|
||||
w=r.w,
|
||||
h=r.h
|
||||
))
|
||||
lines.append(TextLineModel(
|
||||
runs=runs,
|
||||
baseline_y=l.baseline_y,
|
||||
x=l.x,
|
||||
y=l.y,
|
||||
w=l.w,
|
||||
h=l.h
|
||||
))
|
||||
paragraphs.append(ParagraphModel(
|
||||
lines=lines,
|
||||
x=p.x,
|
||||
y=p.y,
|
||||
w=p.w,
|
||||
h=p.h
|
||||
))
|
||||
|
||||
return PageModelResponse(
|
||||
paragraphs=paragraphs,
|
||||
width=model.width,
|
||||
height=model.height,
|
||||
page_index=model.page_index
|
||||
)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
except IndexError as e:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
<<<<<<< HEAD
|
||||
from typing import List, Annotated
|
||||
from fastapi import APIRouter, HTTPException, status, Response, Path, Query
|
||||
=======
|
||||
from fastapi import APIRouter, HTTPException, Response, status
|
||||
>>>>>>> 2638dfe969874b954502b26656df48ad971e306d
|
||||
|
||||
from app.routers.documents import FontInfoResponse
|
||||
from app.services import engine
|
||||
@@ -68,13 +65,9 @@ def extract_page_text(document_id: str, page_index: Annotated[int, Path(ge=0)]):
|
||||
|
||||
|
||||
@compat_router.get("/render/{document_id}")
|
||||
<<<<<<< HEAD
|
||||
def render_page_compat(document_id: str, page: Annotated[int, Query(ge=0)] = 0, zoom: float = 1.0, rotation: int = 0) -> Response:
|
||||
=======
|
||||
def render_page_compat(
|
||||
document_id: str, page: int = 0, zoom: float = 1.0, rotation: int = 0
|
||||
) -> Response:
|
||||
>>>>>>> 2638dfe969874b954502b26656df48ad971e306d
|
||||
dpi = int(96 * zoom)
|
||||
return render_page(document_id, page, dpi)
|
||||
|
||||
@@ -104,9 +97,6 @@ def get_page_info(document_id: str, page_index: Annotated[int, Path(ge=0)]):
|
||||
|
||||
|
||||
@router.get("/{page_index}/transform/page-to-device")
|
||||
<<<<<<< HEAD
|
||||
def transform_page_to_device(document_id: str, page_index: Annotated[int, Path(ge=0)], x: float, y: float, device_width: int, device_height: int, rotate: int = 0):
|
||||
=======
|
||||
def transform_page_to_device(
|
||||
document_id: str,
|
||||
page_index: int,
|
||||
@@ -116,7 +106,6 @@ def transform_page_to_device(
|
||||
device_height: int,
|
||||
rotate: int = 0,
|
||||
):
|
||||
>>>>>>> 2638dfe969874b954502b26656df48ad971e306d
|
||||
if not engine.is_available():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Engine unavailable"
|
||||
@@ -139,9 +128,6 @@ def transform_page_to_device(
|
||||
|
||||
|
||||
@router.get("/{page_index}/transform/device-to-page")
|
||||
<<<<<<< HEAD
|
||||
def transform_device_to_page(document_id: str, page_index: Annotated[int, Path(ge=0)], x: int, y: int, device_width: int, device_height: int, rotate: int = 0):
|
||||
=======
|
||||
def transform_device_to_page(
|
||||
document_id: str,
|
||||
page_index: int,
|
||||
@@ -151,7 +137,6 @@ def transform_device_to_page(
|
||||
device_height: int,
|
||||
rotate: int = 0,
|
||||
):
|
||||
>>>>>>> 2638dfe969874b954502b26656df48ad971e306d
|
||||
if not engine.is_available():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Engine unavailable"
|
||||
@@ -171,15 +156,10 @@ def transform_device_to_page(
|
||||
return {"x": res.x, "y": res.y}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
<<<<<<< HEAD
|
||||
@router.get("/{page_index}/fonts", response_model=List[FontInfoResponse])
|
||||
def get_page_fonts(document_id: str, page_index: Annotated[int, Path(ge=0)]) -> List[FontInfoResponse]:
|
||||
=======
|
||||
|
||||
|
||||
@router.get("/{page_index}/fonts", response_model=list[FontInfoResponse])
|
||||
def get_page_fonts(document_id: str, page_index: int) -> list[FontInfoResponse]:
|
||||
>>>>>>> 2638dfe969874b954502b26656df48ad971e306d
|
||||
if not engine.is_available():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_501_NOT_IMPLEMENTED,
|
||||
@@ -222,7 +202,6 @@ def get_page_fonts(document_id: str, page_index: int) -> list[FontInfoResponse]:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Page index out of range")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
<<<<<<< HEAD
|
||||
|
||||
|
||||
@router.get("/{page_index}/fonts/glyph-width")
|
||||
@@ -247,5 +226,3 @@ def get_page_glyph_width(document_id: str, page_index: Annotated[int, Path(ge=0)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
|
||||
=======
|
||||
>>>>>>> 2638dfe969874b954502b26656df48ad971e306d
|
||||
|
||||
Binary file not shown.
@@ -10,7 +10,7 @@ if ($Port -le 0) {
|
||||
if ($envPort) {
|
||||
$Port = $envPort
|
||||
} else {
|
||||
$Port = 8080
|
||||
$Port = 8000
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user