integration done and testing done

This commit is contained in:
saqib mir
2026-06-08 16:28:10 +05:30
parent 998f32c0a4
commit c27a80f840
11 changed files with 365 additions and 82 deletions
+52
View File
@@ -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;
};
+29 -2
View File
@@ -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>();
+55 -15
View File
@@ -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
View File
@@ -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;
}
}
+7 -1
View File
@@ -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',
+30 -1
View File
@@ -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 = {
+131 -11
View File
@@ -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))
-23
View File
@@ -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
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -10,7 +10,7 @@ if ($Port -le 0) {
if ($envPort) {
$Port = $envPort
} else {
$Port = 8080
$Port = 8000
}
}