#include "parser/pdfium_internal.hpp" namespace pdfengine::parser { std::expected PdfiumPage::extractDocumentModel() const { #ifdef PDFENGINE_WITH_PDFIUM if (!page_) { return std::unexpected(EngineError::Unknown); } ensureTextPageLoaded(); if (!textPage_) { return std::unexpected(EngineError::Unknown); } PageModel model; model.width = width(); model.height = height(); model.pageIndex = pageIndex_; int charCount = FPDFText_CountChars(textPage_); if (charCount <= 0) { return model; } std::unordered_map fontMap; if (auto fontsRes = getFonts()) { for (const auto& f : *fontsRes) { fontMap[f.fontName] = f; } } std::vector documentGlyphs; documentGlyphs.reserve(charCount); std::unordered_map objToIndex; int objCount = FPDFPage_CountObjects(page_); for (int i = 0; i < objCount; ++i) { objToIndex[FPDFPage_GetObject(page_, i)] = i; } for (int i = 0; i < charCount; ++i) { unsigned int codeUnit = FPDFText_GetUnicode(textPage_, i); unsigned int cp = codeUnit; if (codeUnit >= 0xD800 && codeUnit <= 0xDBFF && i + 1 < charCount) { unsigned int nextUnit = FPDFText_GetUnicode(textPage_, i + 1); if (nextUnit >= 0xDC00 && nextUnit <= 0xDFFF) { cp = 0x10000 + ((codeUnit - 0xD800) << 10) + (nextUnit - 0xDC00); } } std::string utf8_char = code_point_to_utf8(cp); if (utf8_char.empty() || cp == '\r' || cp == '\n') { if (cp > 0xFFFF) ++i; continue; } Glyph g; g.text = std::move(utf8_char); g.unicode = cp; g.srcIndex = i; double left, right, bottom, top; FPDFText_GetCharBox(textPage_, i, &left, &right, &bottom, &top); g.bboxX = (std::min)(left, right); g.bboxY = (std::min)(bottom, top); g.bboxW = std::abs(right - left); g.bboxH = std::abs(top - bottom); FPDFText_GetCharOrigin(textPage_, i, &g.originX, &g.originY); g.angle = FPDFText_GetCharAngle(textPage_, i); g.fontSize = FPDFText_GetFontSize(textPage_, i); int flags = 0; unsigned long len = FPDFText_GetFontInfo(textPage_, i, nullptr, 0, &flags); if (len > 0) { std::vector buf(len); if (FPDFText_GetFontInfo(textPage_, i, buf.data(), len, &flags) > 0) { g.fontName = std::string(buf.data()); } } g.flags = flags; FPDF_PAGEOBJECT textObj = FPDFText_GetTextObject(textPage_, i); if (textObj) { auto it = objToIndex.find(textObj); if (it != objToIndex.end()) { g.pageObjectIndex = it->second; } FS_MATRIX mtx; if (FPDFPageObj_GetMatrix(textObj, &mtx)) { double scaleY1 = std::hypot(static_cast(mtx.c), static_cast(mtx.d)); double scaleY2 = std::hypot(static_cast(mtx.a), static_cast(mtx.b)); double scaleY = (std::max)(scaleY1, scaleY2); if (scaleY > 0.001) { double trueSize = g.fontSize * scaleY; if (trueSize > 0.1) g.fontSize = trueSize; } } } if (g.bboxH > g.fontSize * 0.8) { g.fontSize = (std::max)(g.fontSize, g.bboxH); } documentGlyphs.push_back(g); if (cp > 0xFFFF) ++i; } std::sort(documentGlyphs.begin(), documentGlyphs.end(), [](const Glyph& a, const Glyph& b) { if (std::abs(a.originY - b.originY) > 1.0) { return a.originY > b.originY; } return a.originX < b.originX; }); std::vector lines; if (!documentGlyphs.empty()) { TextLine currentLine; currentLine.angle = documentGlyphs[0].angle; double currentOriginY = documentGlyphs[0].originY; for (const auto& g : documentGlyphs) { if (currentLine.glyphs.empty()) { currentLine.glyphs.push_back(g); continue; } if (std::abs(g.angle - currentLine.angle) < 0.1 && std::abs(g.originY - currentOriginY) < 1.0) { currentLine.glyphs.push_back(g); } else { lines.push_back(std::move(currentLine)); currentLine = TextLine(); currentLine.angle = g.angle; currentOriginY = g.originY; currentLine.glyphs.push_back(g); } } if (!currentLine.glyphs.empty()) { lines.push_back(std::move(currentLine)); } } for (auto& line : lines) { std::sort(line.glyphs.begin(), line.glyphs.end(), [](const Glyph& a, const Glyph& b) { if (a.originX != b.originX) return a.originX < b.originX; return a.srcIndex < b.srcIndex; }); std::vector gaps; for (size_t i = 1; i < line.glyphs.size(); ++i) { double gap = line.glyphs[i].bboxX - (line.glyphs[i-1].bboxX + line.glyphs[i-1].bboxW); if (gap > 0) { gaps.push_back(gap); } } double p25Gap = 0.0; if (!gaps.empty()) { std::sort(gaps.begin(), gaps.end()); p25Gap = gaps[gaps.size() / 4]; } double lineEm = 0.0; { std::vector hs; for (const auto& gg : line.glyphs) if (gg.bboxH > 0.1) hs.push_back(gg.bboxH); if (!hs.empty()) { std::sort(hs.begin(), hs.end()); double capH = hs[(hs.size() * 9) / 10]; lineEm = capH / 0.7; } } TextRun currentRun; if (!line.glyphs.empty()) { const Glyph* firstG = &line.glyphs[0]; 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.fontFidelity = classifyFontFidelity(it->second); } 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]; const auto& currG = line.glyphs[i]; double gap = currG.bboxX - (prevG.bboxX + prevG.bboxW); double em = (std::max)(static_cast(currG.fontSize), lineEm); double spaceThreshold = em * 0.2; if (p25Gap > spaceThreshold) { spaceThreshold = (std::min)(p25Gap * 1.5, em * 0.38); } bool addSpace = gap > spaceThreshold && prevG.text != " " && currG.text != " "; bool breakRun = currG.fontName != currentRun.fontName || std::abs(currG.fontSize - currentRun.fontSize) > 0.1 || currG.flags != currentRun.flags; if (addSpace) { Glyph spaceGlyph; spaceGlyph.text = " "; spaceGlyph.unicode = ' '; spaceGlyph.fontSize = currG.fontSize; spaceGlyph.fontName = currG.fontName; spaceGlyph.flags = currG.flags; spaceGlyph.originX = prevG.bboxX + prevG.bboxW; spaceGlyph.originY = currG.originY; spaceGlyph.angle = currG.angle; spaceGlyph.bboxX = spaceGlyph.originX; spaceGlyph.bboxY = currG.bboxY; spaceGlyph.bboxW = gap; spaceGlyph.bboxH = currG.bboxH; if (breakRun) { for (const auto& g : currentRun.glyphs) { if (g.pageObjectIndex != -1 && std::find(currentRun.objectIndices.begin(), currentRun.objectIndices.end(), g.pageObjectIndex) == currentRun.objectIndices.end()) { currentRun.objectIndices.push_back(g.pageObjectIndex); } } 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.fontFidelity = classifyFontFidelity(it->second); } } currentRun.glyphs.push_back(spaceGlyph); currentRun.text += spaceGlyph.text; } else if (breakRun) { for (const auto& g : currentRun.glyphs) { if (g.pageObjectIndex != -1 && std::find(currentRun.objectIndices.begin(), currentRun.objectIndices.end(), g.pageObjectIndex) == currentRun.objectIndices.end()) { currentRun.objectIndices.push_back(g.pageObjectIndex); } } 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.fontFidelity = classifyFontFidelity(it->second); } } currentRun.glyphs.push_back(currG); currentRun.text += currG.text; } if (!currentRun.glyphs.empty()) { for (const auto& g : currentRun.glyphs) { if (g.pageObjectIndex != -1 && std::find(currentRun.objectIndices.begin(), currentRun.objectIndices.end(), g.pageObjectIndex) == currentRun.objectIndices.end()) { currentRun.objectIndices.push_back(g.pageObjectIndex); } } line.runs.push_back(std::move(currentRun)); } } } for (auto& line : lines) { for (auto& run : line.runs) { std::string repaired = pdfengine::parser::repairMojibake(run.text); if (repaired != run.text) run.text = std::move(repaired); } } std::vector paragraphs; auto lineStyleKey = [](const TextLine& ln) -> std::pair { for (const auto& r : ln.runs) { if (r.text.empty()) continue; std::string n = r.fontName + "|" + r.internalFontId; std::transform(n.begin(), n.end(), n.begin(), [](unsigned char c){ return static_cast(std::tolower(c)); }); bool bold = n.find("bold") != std::string::npos; bool sans = n.find("arial") != std::string::npos || n.find("helvetica") != std::string::npos; return {bold, sans}; } return {false, false}; }; if (!lines.empty()) { Paragraph currentPara; currentPara.lines.push_back(std::move(lines[0])); for (size_t i = 1; i < lines.size(); ++i) { auto& prevLine = currentPara.lines.back(); auto& currLine = lines[i]; double prevY = prevLine.glyphs.empty() ? 0 : prevLine.glyphs[0].originY; double currY = currLine.glyphs.empty() ? 0 : currLine.glyphs[0].originY; double fontSize = currLine.runs.empty() ? 12.0 : currLine.runs[0].fontSize; double capH = 0.0; for (const auto& g : currLine.glyphs) if (g.bboxH > capH) capH = g.bboxH; if (capH > 0.0) fontSize = (std::max)(fontSize, capH / 0.7); double vGap = std::abs(prevY - currY); bool styleChanged = lineStyleKey(currLine) != lineStyleKey(prevLine); if (vGap > fontSize * 1.5 || styleChanged) { paragraphs.push_back(std::move(currentPara)); currentPara = Paragraph(); } currentPara.lines.push_back(std::move(currLine)); } if (!currentPara.lines.empty()) { paragraphs.push_back(std::move(currentPara)); } } auto computeRunBBox = [](TextRun& r) { if (r.glyphs.empty()) return; double minX = r.glyphs[0].bboxX; double minY = r.glyphs[0].bboxY; double maxX = r.glyphs[0].bboxX + r.glyphs[0].bboxW; double maxY = r.glyphs[0].bboxY + r.glyphs[0].bboxH; for (size_t i = 1; i < r.glyphs.size(); ++i) { minX = (std::min)(minX, r.glyphs[i].bboxX); minY = (std::min)(minY, r.glyphs[i].bboxY); maxX = (std::max)(maxX, r.glyphs[i].bboxX + r.glyphs[i].bboxW); maxY = (std::max)(maxY, r.glyphs[i].bboxY + r.glyphs[i].bboxH); } r.x = minX; r.y = minY; r.w = maxX - minX; r.h = maxY - minY; }; auto computeLineBBox = [](TextLine& l) { if (l.runs.empty()) return; double minX = l.runs[0].x; double minY = l.runs[0].y; double maxX = l.runs[0].x + l.runs[0].w; double maxY = l.runs[0].y + l.runs[0].h; for (size_t i = 1; i < l.runs.size(); ++i) { minX = (std::min)(minX, l.runs[i].x); minY = (std::min)(minY, l.runs[i].y); maxX = (std::max)(maxX, l.runs[i].x + l.runs[i].w); maxY = (std::max)(maxY, l.runs[i].y + l.runs[i].h); } l.x = minX; l.y = minY; l.w = maxX - minX; l.h = maxY - minY; }; auto computeParaBBox = [](Paragraph& p) { if (p.lines.empty()) return; double minX = p.lines[0].x; double minY = p.lines[0].y; double maxX = p.lines[0].x + p.lines[0].w; double maxY = p.lines[0].y + p.lines[0].h; for (size_t i = 1; i < p.lines.size(); ++i) { minX = (std::min)(minX, p.lines[i].x); minY = (std::min)(minY, p.lines[i].y); maxX = (std::max)(maxX, p.lines[i].x + p.lines[i].w); maxY = (std::max)(maxY, p.lines[i].y + p.lines[i].h); } p.x = minX; p.y = minY; p.w = maxX - minX; p.h = maxY - minY; }; auto hex2 = [](unsigned int c) -> std::string { static const char* h = "0123456789abcdef"; c &= 0xFF; return std::string{h[(c >> 4) & 0xF], h[c & 0xF]}; }; auto computeRunColor = [&](TextRun& r) { if (r.objectIndices.empty()) return; std::vector idxs = r.objectIndices; std::sort(idxs.begin(), idxs.end()); for (int idx : idxs) { FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page_, idx); if (!obj || FPDFPageObj_GetType(obj) != FPDF_PAGEOBJ_TEXT) continue; unsigned int cr = 0, cg = 0, cb = 0, ca = 0; bool got = (FPDFPageObj_GetFillColor(obj, &cr, &cg, &cb, &ca) && ca != 0) || (FPDFPageObj_GetStrokeColor(obj, &cr, &cg, &cb, &ca) && ca != 0); if (got) { r.fillColor = "#" + hex2(cr) + hex2(cg) + hex2(cb); } return; } }; auto computeRunParaId = [&](TextRun& r) { if (r.objectIndices.empty()) return; int minIdx = r.objectIndices[0]; for (int idx : r.objectIndices) if (idx < minIdx) minIdx = idx; FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page_, minIdx); if (obj) r.paraId = repagGetParaId(obj); }; for (auto& p : paragraphs) { for (auto& l : p.lines) { l.baselineY = l.glyphs.empty() ? 0.0 : l.glyphs[0].originY; for (auto& r : l.runs) { computeRunBBox(r); computeRunColor(r); computeRunParaId(r); } computeLineBBox(l); } computeParaBBox(p); } model.paragraphs = std::move(paragraphs); return model; #else return std::unexpected(EngineError::Unknown); #endif } std::expected, EngineError> PdfiumPage::extractAnnotationsText() const { #ifdef PDFENGINE_WITH_PDFIUM if (!page_) { return std::unexpected(EngineError::Unknown); } std::vector result; int count = FPDFPage_GetAnnotCount(page_); for (int i = 0; i < count; ++i) { FPDF_ANNOTATION annot = FPDFPage_GetAnnot(page_, i); if (!annot) continue; if (FPDFAnnot_GetSubtype(annot) == FPDF_ANNOT_FREETEXT) { unsigned long len = FPDFAnnot_GetStringValue(annot, "Contents", nullptr, 0); if (len > 2) { std::vector buf(len); FPDFAnnot_GetStringValue(annot, "Contents", reinterpret_cast(buf.data()), len); std::string text = utf16le_to_utf8(reinterpret_cast(buf.data()), len / sizeof(char16_t)); while (!text.empty() && text.back() == '\0') { text.pop_back(); } if (!text.empty()) { result.push_back(text); } } } } return result; #else return std::unexpected(EngineError::Unknown); #endif } std::expected, EngineError> PdfiumPage::extractAnnotations() const { #ifdef PDFENGINE_WITH_PDFIUM if (!page_) { return std::unexpected(EngineError::Unknown); } FPDF_FORMFILLINFO formInfo; memset(&formInfo, 0, sizeof(formInfo)); formInfo.version = 1; FPDF_FORMHANDLE formHandle = FPDFDOC_InitFormFillEnvironment(doc_, &formInfo); std::vector result; int count = FPDFPage_GetAnnotCount(page_); for (int i = 0; i < count; ++i) { FPDF_ANNOTATION annot = FPDFPage_GetAnnot(page_, i); if (!annot) continue; PdfPage::AnnotationInfo info; info.pageIndex = pageIndex_; unsigned long len = FPDFAnnot_GetStringValue(annot, "NM", nullptr, 0); if (len > 2) { std::vector buf(len); FPDFAnnot_GetStringValue(annot, "NM", reinterpret_cast(buf.data()), len); std::string text = utf16le_to_utf8(reinterpret_cast(buf.data()), len / sizeof(char16_t)); while (!text.empty() && text.back() == '\0') text.pop_back(); info.id = text; } if (info.id.empty()) { info.id = "anno_" + std::to_string(pageIndex_) + "_" + std::to_string(i); } int subtype = FPDFAnnot_GetSubtype(annot); if (subtype == FPDF_ANNOT_HIGHLIGHT) { info.type = "highlight"; } else if (subtype == FPDF_ANNOT_FREETEXT || subtype == FPDF_ANNOT_TEXT) { info.type = "comment"; } else if (subtype == FPDF_ANNOT_INK) { info.type = "ink"; } else if (subtype == FPDF_ANNOT_STRIKEOUT) { info.type = "strikeout"; } else if (subtype == FPDF_ANNOT_UNDERLINE) { info.type = "underline"; } else if (subtype == FPDF_ANNOT_SQUIGGLY) { info.type = "squiggly"; } else if (subtype == FPDF_ANNOT_WIDGET) { info.type = "widget"; int fieldType = FPDFAnnot_GetFormFieldType(formHandle, annot); if (fieldType == 1 || fieldType == 2 || fieldType == 3) { info.fieldType = "Btn"; } else if (fieldType == 4 || fieldType == 5) { info.fieldType = "Ch"; } else if (fieldType == 6) { info.fieldType = "Tx"; } else if (fieldType == 7) { info.fieldType = "Sig"; } else { info.fieldType = "Unknown"; } info.fieldFlags = FPDFAnnot_GetFormFieldFlags(formHandle, annot); unsigned long nameLen = FPDFAnnot_GetFormFieldName(formHandle, annot, nullptr, 0); if (nameLen > 2) { std::vector nameBuf(nameLen / 2); FPDFAnnot_GetFormFieldName(formHandle, annot, nameBuf.data(), nameLen); std::string text = utf16le_to_utf8(reinterpret_cast(nameBuf.data()), nameBuf.size()); while (!text.empty() && text.back() == '\0') text.pop_back(); info.fieldName = text; } unsigned long valLen = FPDFAnnot_GetFormFieldValue(formHandle, annot, nullptr, 0); if (valLen > 2) { std::vector valBuf(valLen / 2); FPDFAnnot_GetFormFieldValue(formHandle, annot, valBuf.data(), valLen); std::string text = utf16le_to_utf8(reinterpret_cast(valBuf.data()), valBuf.size()); while (!text.empty() && text.back() == '\0') text.pop_back(); info.fieldValue = text; } if (info.fieldType == "Ch") { int optCount = FPDFAnnot_GetOptionCount(formHandle, annot); for (int o = 0; o < optCount; ++o) { unsigned long optLen = FPDFAnnot_GetOptionLabel(formHandle, annot, o, nullptr, 0); if (optLen > 2) { std::vector optBuf(optLen / 2); FPDFAnnot_GetOptionLabel(formHandle, annot, o, optBuf.data(), optLen); std::string optText = utf16le_to_utf8(reinterpret_cast(optBuf.data()), optBuf.size()); while (!optText.empty() && optText.back() == '\0') optText.pop_back(); info.fieldOptions.push_back(optText); } } } } else { info.type = "unknown"; } if (info.type != "unknown") { FS_RECTF rect; if (FPDFAnnot_GetRect(annot, &rect)) { int dw = static_cast(std::round(width())); int dh = static_cast(std::round(height())); DevicePoint topLeft = pageToDevice({rect.left, rect.top}, dw, dh, 0); DevicePoint bottomRight = pageToDevice({rect.right, rect.bottom}, dw, dh, 0); info.x = (std::min)(topLeft.x, bottomRight.x); info.y = (std::min)(topLeft.y, bottomRight.y); info.width = std::abs(bottomRight.x - topLeft.x); info.height = std::abs(bottomRight.y - topLeft.y); } len = FPDFAnnot_GetStringValue(annot, "T", nullptr, 0); if (len > 2) { std::vector buf(len); FPDFAnnot_GetStringValue(annot, "T", reinterpret_cast(buf.data()), len); std::string text = utf16le_to_utf8(reinterpret_cast(buf.data()), len / sizeof(char16_t)); while (!text.empty() && text.back() == '\0') text.pop_back(); info.author = text; } len = FPDFAnnot_GetStringValue(annot, "Contents", nullptr, 0); if (len > 2) { std::vector buf(len); FPDFAnnot_GetStringValue(annot, "Contents", reinterpret_cast(buf.data()), len); std::string text = utf16le_to_utf8(reinterpret_cast(buf.data()), len / sizeof(char16_t)); while (!text.empty() && text.back() == '\0') text.pop_back(); info.content = text; } len = FPDFAnnot_GetStringValue(annot, "M", nullptr, 0); if (len <= 2) { len = FPDFAnnot_GetStringValue(annot, "CreationDate", nullptr, 0); if (len > 2) { std::vector buf(len); FPDFAnnot_GetStringValue(annot, "CreationDate", reinterpret_cast(buf.data()), len); std::string text = utf16le_to_utf8(reinterpret_cast(buf.data()), len / sizeof(char16_t)); while (!text.empty() && text.back() == '\0') text.pop_back(); info.timestamp = text; } } else { std::vector buf(len); FPDFAnnot_GetStringValue(annot, "M", reinterpret_cast(buf.data()), len); std::string text = utf16le_to_utf8(reinterpret_cast(buf.data()), len / sizeof(char16_t)); while (!text.empty() && text.back() == '\0') text.pop_back(); info.timestamp = text; } unsigned int R = 0, G = 0, B = 0, A = 0; if (FPDFAnnot_GetColor(annot, FPDFANNOT_COLORTYPE_Color, &R, &G, &B, &A)) { char hex[10]; snprintf(hex, sizeof(hex), "#%02x%02x%02x", R, G, B); info.color = hex; } if (subtype == FPDF_ANNOT_INK) { float h_radius = 0, v_radius = 0, border_width = 1.0f; info.thickness = 2.0; // default unsigned long ctLen = FPDFAnnot_GetStringValue(annot, "CustomThickness", nullptr, 0); if (ctLen > 2) { std::vector ctBuf(ctLen / 2); FPDFAnnot_GetStringValue(annot, "CustomThickness", reinterpret_cast(ctBuf.data()), ctLen); std::string ctStr = utf16le_to_utf8(ctBuf.data(), ctBuf.size()); try { info.thickness = std::stod(ctStr); } catch (...) {} } else if (FPDFAnnot_GetBorder(annot, &h_radius, &v_radius, &border_width)) { info.thickness = border_width; } const double pageH = height(); unsigned long strokeCount = FPDFAnnot_GetInkListCount(annot); for (unsigned long s = 0; s < strokeCount; ++s) { unsigned long ptCount = FPDFAnnot_GetInkListPath(annot, s, nullptr, 0); if (ptCount == 0) continue; std::vector pts(ptCount); FPDFAnnot_GetInkListPath(annot, s, pts.data(), ptCount); std::vector stroke; stroke.reserve(ptCount); for (const auto& p : pts) { stroke.push_back(Point2D{static_cast(p.x), pageH - static_cast(p.y)}); } if (!stroke.empty()) info.paths.push_back(std::move(stroke)); } } else if (subtype == FPDF_ANNOT_HIGHLIGHT || subtype == FPDF_ANNOT_STRIKEOUT || subtype == FPDF_ANNOT_UNDERLINE || subtype == FPDF_ANNOT_SQUIGGLY) { const double pageH = height(); size_t quadCount = FPDFAnnot_CountAttachmentPoints(annot); for (size_t q = 0; q < quadCount; ++q) { FS_QUADPOINTSF quad; if (FPDFAnnot_GetAttachmentPoints(annot, q, &quad)) { std::array pts = {{ {static_cast(quad.x1), pageH - static_cast(quad.y1)}, {static_cast(quad.x2), pageH - static_cast(quad.y2)}, {static_cast(quad.x3), pageH - static_cast(quad.y3)}, {static_cast(quad.x4), pageH - static_cast(quad.y4)} }}; info.quadPoints.push_back(pts); } } } result.push_back(info); } FPDFPage_CloseAnnot(annot); } FPDFDOC_ExitFormFillEnvironment(formHandle); return result; #else return std::unexpected(EngineError::Unknown); #endif } }