From 3b1c36d64513fb58e4e177995cf171b7d896b254 Mon Sep 17 00:00:00 2001 From: Furqan-14 Date: Mon, 29 Jun 2026 10:51:04 +0530 Subject: [PATCH] fix: code refactor --- engine/CMakeLists.txt | 11 + engine/src/parser/pdfium_document.cpp | 4526 +---------------- engine/src/parser/pdfium_document.hpp | 18 + engine/src/parser/pdfium_edit.cpp | 89 + engine/src/parser/pdfium_edit_annotations.cpp | 572 +++ engine/src/parser/pdfium_edit_images.cpp | 170 + engine/src/parser/pdfium_edit_pages.cpp | 179 + engine/src/parser/pdfium_edit_reflow.cpp | 472 ++ engine/src/parser/pdfium_edit_replace.cpp | 610 +++ engine/src/parser/pdfium_fonts.cpp | 648 +++ engine/src/parser/pdfium_internal.cpp | 607 +++ engine/src/parser/pdfium_internal.hpp | 109 + engine/src/parser/pdfium_page.cpp | 390 ++ engine/src/parser/pdfium_page_model.cpp | 634 +++ engine/src/parser/pdfium_reflow.cpp | 286 ++ engine/tests/CMakeLists.txt | 17 +- engine/tests/document_edit_test.cpp | 689 +++ engine/tests/document_load_test.cpp | 153 + engine/tests/document_test.cpp | 1741 ------- engine/tests/document_test_helpers.hpp | 64 + engine/tests/font_diagnostics_test.cpp | 601 +++ engine/tests/font_encoding_test.cpp | 351 ++ engine/tests/font_loader_descriptor_test.cpp | 173 + engine/tests/font_rendering_test.cpp | 454 ++ engine/tests/font_types_test.cpp | 277 + engine/tests/fonts_test.cpp | 1323 ----- engine/tests/fonts_test_helpers.hpp | 80 + engine/tests/page_render_test.cpp | 174 + engine/tests/text_encoding_test.cpp | 74 + frontend/public/pdfium-engine.mjs | 2 +- frontend/public/pdfium-engine.wasm | Bin 10499507 -> 10505698 bytes frontend/src/lib/pdfiumEngine.ts | 2 +- gateway/app/routers/documents.py | 900 ---- gateway/app/routers/documents/__init__.py | 20 + gateway/app/routers/documents/content.py | 163 + gateway/app/routers/documents/crud.py | 107 + gateway/app/routers/documents/export.py | 46 + gateway/app/routers/documents/fonts.py | 109 + gateway/app/routers/documents/metadata.py | 56 + gateway/app/routers/documents/text_objects.py | 106 + gateway/app/routers/render.py | 28 +- gateway/app/schemas/__init__.py | 6 + gateway/app/schemas/annotation.py | 22 + gateway/app/schemas/document.py | 50 + gateway/app/schemas/font.py | 23 + gateway/app/schemas/page_model.py | 59 + gateway/app/schemas/search.py | 13 + gateway/app/schemas/text_object.py | 12 + gateway/app/services/export.py | 44 + gateway/app/services/font.py | 32 + gateway/app/services/page_model.py | 77 + gateway/app/services/search.py | 97 + wasm/bindings/pdf_engine_facade.cpp | 461 -- wasm/bindings/pdf_engine_facade.hpp | 80 - wasm/bindings/wasm_engine.cpp | 13 +- 55 files changed, 8961 insertions(+), 9059 deletions(-) create mode 100644 engine/src/parser/pdfium_edit.cpp create mode 100644 engine/src/parser/pdfium_edit_annotations.cpp create mode 100644 engine/src/parser/pdfium_edit_images.cpp create mode 100644 engine/src/parser/pdfium_edit_pages.cpp create mode 100644 engine/src/parser/pdfium_edit_reflow.cpp create mode 100644 engine/src/parser/pdfium_edit_replace.cpp create mode 100644 engine/src/parser/pdfium_fonts.cpp create mode 100644 engine/src/parser/pdfium_internal.cpp create mode 100644 engine/src/parser/pdfium_internal.hpp create mode 100644 engine/src/parser/pdfium_page.cpp create mode 100644 engine/src/parser/pdfium_page_model.cpp create mode 100644 engine/src/parser/pdfium_reflow.cpp create mode 100644 engine/tests/document_edit_test.cpp create mode 100644 engine/tests/document_load_test.cpp delete mode 100644 engine/tests/document_test.cpp create mode 100644 engine/tests/document_test_helpers.hpp create mode 100644 engine/tests/font_diagnostics_test.cpp create mode 100644 engine/tests/font_encoding_test.cpp create mode 100644 engine/tests/font_loader_descriptor_test.cpp create mode 100644 engine/tests/font_rendering_test.cpp create mode 100644 engine/tests/font_types_test.cpp delete mode 100644 engine/tests/fonts_test.cpp create mode 100644 engine/tests/fonts_test_helpers.hpp create mode 100644 engine/tests/page_render_test.cpp create mode 100644 engine/tests/text_encoding_test.cpp delete mode 100644 gateway/app/routers/documents.py create mode 100644 gateway/app/routers/documents/__init__.py create mode 100644 gateway/app/routers/documents/content.py create mode 100644 gateway/app/routers/documents/crud.py create mode 100644 gateway/app/routers/documents/export.py create mode 100644 gateway/app/routers/documents/fonts.py create mode 100644 gateway/app/routers/documents/metadata.py create mode 100644 gateway/app/routers/documents/text_objects.py create mode 100644 gateway/app/schemas/__init__.py create mode 100644 gateway/app/schemas/annotation.py create mode 100644 gateway/app/schemas/document.py create mode 100644 gateway/app/schemas/font.py create mode 100644 gateway/app/schemas/page_model.py create mode 100644 gateway/app/schemas/search.py create mode 100644 gateway/app/schemas/text_object.py create mode 100644 gateway/app/services/export.py create mode 100644 gateway/app/services/font.py create mode 100644 gateway/app/services/page_model.py create mode 100644 gateway/app/services/search.py delete mode 100644 wasm/bindings/pdf_engine_facade.cpp delete mode 100644 wasm/bindings/pdf_engine_facade.hpp diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 2c50d0b..3076e68 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -13,6 +13,17 @@ add_library(pdfengine STATIC src/core/skia_renderer.cpp src/parser/pdfium_loader.cpp src/parser/pdfium_document.cpp + src/parser/pdfium_internal.cpp + src/parser/pdfium_reflow.cpp + src/parser/pdfium_page.cpp + src/parser/pdfium_page_model.cpp + src/parser/pdfium_fonts.cpp + src/parser/pdfium_edit.cpp + src/parser/pdfium_edit_replace.cpp + src/parser/pdfium_edit_reflow.cpp + src/parser/pdfium_edit_annotations.cpp + src/parser/pdfium_edit_pages.cpp + src/parser/pdfium_edit_images.cpp src/parser/content_stream_parser.cpp src/parser/decoration_builder.cpp src/text/selection.cpp diff --git a/engine/src/parser/pdfium_document.cpp b/engine/src/parser/pdfium_document.cpp index 2a519e0..612b33e 100644 --- a/engine/src/parser/pdfium_document.cpp +++ b/engine/src/parser/pdfium_document.cpp @@ -1,672 +1,16 @@ -#include "parser/pdfium_document.hpp" -#ifdef PDFENGINE_WITH_PDFIUM -#include -#include -#include -#include -#include -#include -#include -#include -#include "parser/pdfium_loader.hpp" -#endif - -#include "fonts/loader/font_resolver.hpp" -#include "fonts/pdf_fonts/font.hpp" -#include "pdfengine/hardened_limits.h" -#include "fonts/pdf_fonts/font_fallback.hpp" -#include "fonts/pdf_fonts/font_subset.hpp" -#include "fonts/shaping/hb_shaper.hpp" -#include "fonts/face/font_face.hpp" -#include "decoration_builder.hpp" -#include "qpdf/qpdf_extractor.hpp" -#include "qpdf/qpdf_font_extractor.hpp" -#include "parser/lexer.hpp" -#include "parser/parser.hpp" - -#include -#include -#include -#include -#include - -namespace pdfengine::parser { - -std::vector utf8_to_utf16le(const std::string& utf8) { - std::vector utf16; - utf16.reserve(utf8.size()); - for (size_t i = 0; i < utf8.size(); ) { - unsigned char c = utf8[i]; - unsigned int cp = 0; - size_t extra = 0; - if (c < 0x80) { cp = c; extra = 0; } - else if ((c & 0xE0) == 0xC0) { cp = c & 0x1F; extra = 1; } - else if ((c & 0xF0) == 0xE0) { cp = c & 0x0F; extra = 2; } - else if ((c & 0xF8) == 0xF0) { cp = c & 0x07; extra = 3; } - else { i++; continue; } - - if (i + extra >= utf8.size()) break; - bool invalid = false; - for (size_t j = 1; j <= extra; ++j) { - unsigned char next = utf8[i + j]; - if ((next & 0xC0) != 0x80) { invalid = true; break; } - cp = (cp << 6) | (next & 0x3F); - } - if (invalid) { i++; continue; } - i += 1 + extra; - if (cp < 0x10000) { - utf16.push_back(static_cast(cp)); - } else { - cp -= 0x10000; - utf16.push_back(static_cast((cp >> 10) + 0xD800)); - utf16.push_back(static_cast((cp & 0x3FF) + 0xDC00)); - } - } - utf16.push_back(0); - return utf16; -} - -std::string utf16le_to_utf8(const char16_t* utf16, size_t length) { - std::string utf8; - for (size_t i = 0; i < length; ++i) { - char16_t c = utf16[i]; - if (c == 0) break; - - uint32_t cp = c; - if (c >= 0xD800 && c <= 0xDBFF) { - if (i + 1 < length) { - char16_t low = utf16[i + 1]; - if (low >= 0xDC00 && low <= 0xDFFF) { - cp = 0x10000 + (((c - 0xD800) << 10) | (low - 0xDC00)); - i++; - } - } - } - - if (cp < 0x80) { - utf8 += static_cast(cp); - } else if (cp < 0x800) { - utf8 += static_cast(0xC0 | (cp >> 6)); - utf8 += static_cast(0x80 | (cp & 0x3F)); - } else if (cp < 0x10000) { - utf8 += static_cast(0xE0 | (cp >> 12)); - utf8 += static_cast(0x80 | ((cp >> 6) & 0x3F)); - utf8 += static_cast(0x80 | (cp & 0x3F)); - } else { - utf8 += static_cast(0xF0 | (cp >> 18)); - utf8 += static_cast(0x80 | ((cp >> 12) & 0x3F)); - utf8 += static_cast(0x80 | ((cp >> 6) & 0x3F)); - utf8 += static_cast(0x80 | (cp & 0x3F)); - } - } - return utf8; -} - -std::string code_point_to_utf8(unsigned int cp) { - std::string utf8; - if (cp == 0) return ""; - if (cp < 0x80) { - utf8 += static_cast(cp); - } else if (cp < 0x800) { - utf8 += static_cast(0xC0 | (cp >> 6)); - utf8 += static_cast(0x80 | (cp & 0x3F)); - } else if (cp < 0x10000) { - utf8 += static_cast(0xE0 | (cp >> 12)); - utf8 += static_cast(0x80 | ((cp >> 6) & 0x3F)); - utf8 += static_cast(0x80 | (cp & 0x3F)); - } else if (cp < 0x200000) { - utf8 += static_cast(0xF0 | (cp >> 18)); - utf8 += static_cast(0x80 | ((cp >> 12) & 0x3F)); - utf8 += static_cast(0x80 | ((cp >> 6) & 0x3F)); - utf8 += static_cast(0x80 | (cp & 0x3F)); - } - return utf8; -} - -static int cp1252ToByte(unsigned int cp) { - if (cp <= 0x7F) return static_cast(cp); - if (cp >= 0xA0 && cp <= 0xFF) return static_cast(cp); - switch (cp) { - case 0x20AC: return 0x80; case 0x201A: return 0x82; case 0x0192: return 0x83; - case 0x201E: return 0x84; case 0x2026: return 0x85; case 0x2020: return 0x86; - case 0x2021: return 0x87; case 0x02C6: return 0x88; case 0x2030: return 0x89; - case 0x0160: return 0x8A; case 0x2039: return 0x8B; case 0x0152: return 0x8C; - case 0x017D: return 0x8E; case 0x2018: return 0x91; case 0x2019: return 0x92; - case 0x201C: return 0x93; case 0x201D: return 0x94; case 0x2022: return 0x95; - case 0x2013: return 0x96; case 0x2014: return 0x97; case 0x02DC: return 0x98; - case 0x2122: return 0x99; case 0x0161: return 0x9A; case 0x203A: return 0x9B; - case 0x0153: return 0x9C; case 0x017E: return 0x9E; case 0x0178: return 0x9F; - default: return -1; - } -} - -static std::vector utf8_to_codepoints(const std::string& s) { - std::vector cps; - for (size_t i = 0; i < s.size();) { - unsigned char c = static_cast(s[i]); - unsigned int cp = c; int extra = 0; - if (c < 0x80) { cp = c; extra = 0; } - else if ((c & 0xE0) == 0xC0) { cp = c & 0x1F; extra = 1; } - else if ((c & 0xF0) == 0xE0) { cp = c & 0x0F; extra = 2; } - else if ((c & 0xF8) == 0xF0) { cp = c & 0x07; extra = 3; } - else { cp = c; extra = 0; } - if (i + extra >= s.size()) { cps.push_back(c); i++; continue; } - bool ok = true; - for (int j = 1; j <= extra; ++j) { unsigned char n = static_cast(s[i + j]); if ((n & 0xC0) != 0x80) { ok = false; break; } cp = (cp << 6) | (n & 0x3F); } - if (!ok) { cps.push_back(c); i++; continue; } - cps.push_back(cp); i += extra + 1; - } - return cps; -} - -static std::string repairMojibake(const std::string& s) { - auto cps = utf8_to_codepoints(s); - std::string out; - auto emit = [&](unsigned int cp) { out += code_point_to_utf8(cp); }; - for (size_t i = 0; i < cps.size();) { - int b0 = cp1252ToByte(cps[i]); - int need = 0; - if (b0 >= 0xC0 && b0 <= 0xDF) need = 1; - else if (b0 >= 0xE0 && b0 <= 0xEF) need = 2; - else if (b0 >= 0xF0 && b0 <= 0xF7) need = 3; - if (need > 0 && i + need < cps.size()) { - std::string bytes; bytes.push_back(static_cast(b0)); - bool ok = true; - for (int j = 1; j <= need; ++j) { int b = cp1252ToByte(cps[i + j]); if (b < 0x80 || b > 0xBF) { ok = false; break; } bytes.push_back(static_cast(b)); } - if (ok) { auto rd = utf8_to_codepoints(bytes); if (rd.size() == 1 && rd[0] > 0x7F) { emit(rd[0]); i += need + 1; continue; } } - } - emit(cps[i]); i++; - } - return out; -} - -std::string normalizeReflowText(const std::string& s, const fonts::FontFace* face) { - auto cps = utf8_to_codepoints(s); - std::string out; - auto keepIfCovered = [&](unsigned int cp, const char* ascii) { - if (face && face->coversUnicode(cp)) out += code_point_to_utf8(cp); - else out += ascii; - }; - for (unsigned int cp : cps) { - switch (cp) { - case 0x2010: case 0x2011: case 0x2012: case 0x2013: case 0x2014: case 0x2015: - keepIfCovered(cp, "-"); break; - case 0x2018: case 0x2019: case 0x201B: keepIfCovered(cp, "'"); break; - case 0x201C: case 0x201D: case 0x201F: keepIfCovered(cp, "\""); break; - case 0x2026: keepIfCovered(cp, "..."); break; - case 0x00A0: case 0x2002: case 0x2003: case 0x2009: case 0x202F: out += ' '; break; - default: out += code_point_to_utf8(cp); break; - } - } - return out; -} -} - -namespace { - -std::string makeInternalFontId(const pdfengine::FontInfo& f) { - if (f.isSubset && !f.subsetTag.empty()) return f.fontName; - return f.fontName + "_" + f.type + "_" + std::to_string(f.flags); -} - -std::string classifyFontFidelity(const pdfengine::FontInfo& f) { - if (!f.isEmbedded) return "exact"; - std::string fam = f.normalizedFamily; - std::transform(fam.begin(), fam.end(), fam.begin(), [](unsigned char c){ return std::tolower(c); }); - auto has = [&](const char* s){ return fam.find(s) != std::string::npos; }; - if (fam.empty() || has("times") || has("arial") || has("helvetica") || has("courier") || - has("symbol") || has("zapf")) - return "exact"; - const bool reconstructable = - f.hasToUnicode && - (f.type.find("TrueType") != std::string::npos || - f.type.find("Type0") != std::string::npos || - f.type.find("CIDFontType2") != std::string::npos); - return reconstructable ? "partial" : "substituted"; -} - -std::string baseNameFromInternalFontId(const std::string& internalFontId) { - std::string expected = internalFontId; - size_t lastUnderscore = expected.rfind('_'); - if (lastUnderscore != std::string::npos && lastUnderscore > 0) { - size_t secondLast = expected.rfind('_', lastUnderscore - 1); - if (secondLast != std::string::npos) { - std::string typePart = expected.substr(secondLast + 1, lastUnderscore - secondLast - 1); - if (typePart == "TrueType" || typePart == "Type1" || - typePart == "CIDFontType0" || typePart == "CIDFontType2") { - expected = expected.substr(0, secondLast); - } - } - } - return expected; -} - -#ifdef PDFENGINE_WITH_PDFIUM -struct VectorWriter : public FPDF_FILEWRITE { - std::vector buffer; - - static int WriteBlockCallback(FPDF_FILEWRITE* pThis, const void* pData, unsigned long size) { - auto* self = static_cast(pThis); - const auto* bytes = static_cast(pData); - self->buffer.insert(self->buffer.end(), bytes, bytes + size); - return 1; - } - - VectorWriter() { - this->version = 1; - this->WriteBlock = &VectorWriter::WriteBlockCallback; - } -}; - -struct PngWriteState { - std::vector* buffer; -}; - -void pngWriteCallback(png_structp png_ptr, png_bytep data, png_size_t length) { - auto* state = reinterpret_cast(png_get_io_ptr(png_ptr)); - state->buffer->insert(state->buffer->end(), data, data + length); -} - -void pngFlushCallback(png_structp png_ptr) { - (void)png_ptr; -} - -std::vector encodeBgraToPng(const uint8_t* bgra, int width, int height, int stride) { - std::vector pngBytes; - png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); - if (!png) return {}; - - png_infop info = png_create_info_struct(png); - if (!info) { - png_destroy_write_struct(&png, nullptr); - return {}; - } - - #pragma warning(push) - #pragma warning(disable: 4611) - if (setjmp(png_jmpbuf(png))) { - png_destroy_write_struct(&png, &info); - return {}; - } - #pragma warning(pop) - - PngWriteState state{&pngBytes}; - png_set_write_fn(png, &state, pngWriteCallback, pngFlushCallback); - - png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); - - png_write_info(png, info); - png_set_bgr(png); - - std::vector rowPointers(height); - for (int y = 0; y < height; ++y) { - rowPointers[y] = const_cast(bgra + y * stride); - } - - png_write_image(png, rowPointers.data()); - png_write_end(png, nullptr); - png_destroy_write_struct(&png, &info); - - return pngBytes; -} - -pdfengine::EngineError mapPdfiumError(unsigned long err, bool passwordProvided) { - switch (err) { - case FPDF_ERR_SUCCESS: - return pdfengine::EngineError::Unknown; - case FPDF_ERR_FILE: - return pdfengine::EngineError::FileNotFound; - case FPDF_ERR_FORMAT: - return pdfengine::EngineError::InvalidFormat; - case FPDF_ERR_PASSWORD: - return passwordProvided ? pdfengine::EngineError::InvalidPassword - : pdfengine::EngineError::PasswordRequired; - default: - return pdfengine::EngineError::Unknown; - } -} -#endif - -#ifdef PDFENGINE_WITH_PDFIUM -struct PdfiumGlobalInit { - PdfiumGlobalInit() { - pdfengine::parser::pdfiumInitLibrary(); - } - ~PdfiumGlobalInit() { - pdfengine::parser::pdfiumDestroyLibrary(); - } -}; - -void ensure_pdfium_initialized() { - static PdfiumGlobalInit init; -} - -std::string normalizeFamilyName(const std::string& fontName) { - std::string name = fontName; - if (name.size() > 7 && name[6] == '+') { - name = name.substr(7); - } - - size_t sep = name.find_first_of("-,"); - if (sep != std::string::npos) { - name = name.substr(0, sep); - } - - auto cleanName = name; - auto lower = name; - std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char c) { return static_cast(::tolower(c)); }); - - std::vector suffixes = {"bold", "italic", "oblique", "regular", "medium", "light", "heavy", "black", "condensed", "mt", "ps"}; - for (const auto& s : suffixes) { - size_t pos = lower.rfind(s); - if (pos != std::string::npos && pos + s.size() == lower.size()) { - cleanName = cleanName.substr(0, pos); - lower = lower.substr(0, pos); - } - } - - while (!cleanName.empty() && (cleanName.back() == '-' || cleanName.back() == ' ' || cleanName.back() == '_')) { - cleanName.pop_back(); - } - - if (cleanName.empty()) return fontName; - return cleanName; -} - -void deduceFontMetadata(pdfengine::FontInfo& f) { - auto lowerName = f.fontName; - std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), [](unsigned char c) { return static_cast(::tolower(c)); }); - - if (f.fontName.size() > 7 && f.fontName[6] == '+') { - f.isSubset = true; - f.subsetTag = f.fontName.substr(0, 6); - bool validTag = true; - for (int ti = 0; ti < 6; ++ti) { - if (!std::isupper(static_cast(f.fontName[ti]))) { - validTag = false; break; - } - } - if (!validTag) { f.isSubset = false; f.subsetTag = ""; } - } else { - f.isSubset = false; - f.subsetTag = ""; - } - f.normalizedFamily = normalizeFamilyName(f.fontName); - - if (f.fontName.find("Identity-H") != std::string::npos) { - f.encoding = "Identity-H"; - f.cmapName = "Identity-H"; - } else if (f.fontName.find("Identity-V") != std::string::npos) { - f.encoding = "Identity-V"; - f.cmapName = "Identity-V"; - f.isVertical = true; - } else if (lowerName.find("symbol") != std::string::npos) { - f.encoding = "Symbol"; - f.cmapName = "None"; - } else { - f.encoding = "WinAnsiEncoding"; - f.cmapName = "None"; - } - - if (lowerName.find("symbol") != std::string::npos) { - f.hasToUnicode = false; - } else { - f.hasToUnicode = true; - } - - if (lowerName.find("simsun") != std::string::npos || - lowerName.find("simhei") != std::string::npos || - lowerName.find("heiti") != std::string::npos || - lowerName.find("fangsong") != std::string::npos || - lowerName.find("kaiti") != std::string::npos || - lowerName.find("song") != std::string::npos || - lowerName.find("gb") != std::string::npos) { - f.cidSystemInfo = "Adobe-GB1"; - } else if (lowerName.find("gothic") != std::string::npos || - lowerName.find("ms-gothic") != std::string::npos || - lowerName.find("msgothic") != std::string::npos || - lowerName.find("mincho") != std::string::npos || - lowerName.find("kozuka") != std::string::npos || - lowerName.find("hiragino") != std::string::npos || - lowerName.find("japan") != std::string::npos || - lowerName.find("heisei") != std::string::npos || - lowerName.find("morisawa") != std::string::npos || - lowerName.find("ryumin") != std::string::npos) { - f.cidSystemInfo = "Adobe-Japan1"; - } else if (lowerName.find("malgun") != std::string::npos || - lowerName.find("gulim") != std::string::npos || - lowerName.find("batang") != std::string::npos || - lowerName.find("dotum") != std::string::npos || - lowerName.find("korea") != std::string::npos || - lowerName.find("hangul") != std::string::npos || - lowerName.find("korean") != std::string::npos) { - f.cidSystemInfo = "Adobe-Korea1"; - } else if (lowerName.find("sung") != std::string::npos || - lowerName.find("ming") != std::string::npos || - lowerName.find("cns") != std::string::npos || - lowerName.find("traditional") != std::string::npos) { - f.cidSystemInfo = "Adobe-CNS1"; - } else { - f.cidSystemInfo = "None"; - } - - if (f.cidSystemInfo != "None" && f.cmapName == "None") { - f.cmapName = f.isVertical ? "UniJIS-UTF16-V" : "Identity-H"; - } - - bool isCid = (f.encoding == "Identity-H" || f.encoding == "Identity-V" || f.cidSystemInfo != "None"); - if (isCid) { - if (lowerName.find("bold") != std::string::npos || lowerName.find("italic") != std::string::npos) { - f.type = "CIDFontType0"; - } else { - f.type = "CIDFontType2"; - } - } else { - static const std::vector type1Names = { - "times", "helvetica", "courier", "symbol", "zapfdingbats", - "liberation", "palatino", "bookman", "new century", "avant garde" - }; - bool isType1 = false; - for (const auto& t1 : type1Names) { - if (lowerName.find(t1) != std::string::npos) { isType1 = true; break; } - } - f.type = isType1 ? "Type1" : "TrueType"; - } - - if (f.isSubset) { - f.isEmbedded = true; - f.sourceType = "Embedded"; - f.substitutedFrom = ""; - f.substitutedTo = ""; - } else { - static const std::vector standard14 = { - "helvetica", "times", "courier", "symbol", "zapfdingbats" - }; - bool isStandard14 = false; - for (const auto& s14 : standard14) { - if (lowerName.find(s14) != std::string::npos) { isStandard14 = true; break; } - } - bool isSystemFont = isStandard14 || - lowerName.find("arial") != std::string::npos || - lowerName.find("liberation") != std::string::npos || - lowerName.find("dejavu") != std::string::npos || - lowerName.find("freefont") != std::string::npos; - if (isSystemFont) { - f.isEmbedded = false; - f.sourceType = "SystemFallback"; - f.substitutedFrom = ""; - f.substitutedTo = ""; - } else { - f.isEmbedded = false; - f.sourceType = "Substituted"; - f.substitutedFrom = f.fontName; - #if defined(_WIN32) - f.substitutedTo = "Arial"; - #else - f.substitutedTo = "Liberation Sans"; - #endif - spdlog::warn("Font fallback occurred: '{}' -> '{}'", f.substitutedFrom, f.substitutedTo); - } - } - - f.internalFontId = makeInternalFontId(f); - - if (lowerName.find("times") != std::string::npos) { - f.ascent = 891.0; - f.descent = -216.0; - f.capHeight = 662.0; - } else if (lowerName.find("courier") != std::string::npos) { - f.ascent = 629.0; - f.descent = -157.0; - f.capHeight = 562.0; - } else if (lowerName.find("symbol") != std::string::npos) { - f.ascent = 1010.0; - f.descent = -293.0; - f.capHeight = 673.0; - } else if (lowerName.find("helvetica") != std::string::npos) { - f.ascent = 905.0; - f.descent = -211.0; - f.capHeight = 728.0; - } else { - f.ascent = 905.0; - f.descent = -211.0; - f.capHeight = 728.0; - } -} - -struct FontPdfData { - bool valid = false; - bool isEmbedded = false; - int flags = 0; - double ascent = 0.0; - double descent = 0.0; - bool hasUnicodeMapping = false; -}; - -static std::unordered_map -buildFontPdfDataMap(FPDF_PAGE page, FPDF_TEXTPAGE textPage) { - std::unordered_map result; - if (!page) return result; - - int objectCount = FPDFPage_CountObjects(page); - for (int i = 0; i < objectCount; ++i) { - FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, i); - if (!obj) continue; - if (FPDFPageObj_GetType(obj) != FPDF_PAGEOBJ_TEXT) continue; - - FPDF_FONT font = FPDFTextObj_GetFont(obj); - if (!font) continue; - - size_t nameLen = FPDFFont_GetBaseFontName(font, nullptr, 0); - if (nameLen == 0) continue; - std::vector nameBuf(nameLen); - if (FPDFFont_GetBaseFontName(font, nameBuf.data(), nameLen) == 0) continue; - std::string fontName(nameBuf.data()); - if (fontName.empty()) continue; - - if (result.count(fontName)) continue; - - FontPdfData data; - data.valid = true; - data.isEmbedded = (FPDFFont_GetIsEmbedded(font) != 0); - data.flags = FPDFFont_GetFlags(font); - - float rawAscent = 0.0f; - if (FPDFFont_GetAscent(font, 1000.0f, &rawAscent) && rawAscent != 0.0f) { - data.ascent = static_cast(rawAscent); - } - float rawDescent = 0.0f; - if (FPDFFont_GetDescent(font, 1000.0f, &rawDescent) && rawDescent != 0.0f) { - data.descent = static_cast(rawDescent); - } - - result[fontName] = data; - } - - if (textPage && !result.empty()) { - int charCount = FPDFText_CountChars(textPage); - if (charCount > 0 && charCount < 1000000) { - for (int ci = 0; ci < charCount; ++ci) { - int fi = 0; - unsigned long flen = - FPDFText_GetFontInfo(textPage, ci, nullptr, 0, &fi); - if (flen == 0) continue; - std::vector fbuf(flen); - if (FPDFText_GetFontInfo(textPage, ci, fbuf.data(), flen, &fi) == 0) - continue; - std::string fname(fbuf.data()); - - auto it = result.find(fname); - if (it == result.end() || it->second.hasUnicodeMapping) continue; - - unsigned int cp = FPDFText_GetUnicode(textPage, ci); - if (cp > 0x0020 && cp != 0xFFFD) { - it->second.hasUnicodeMapping = true; - } - } - } - } - - return result; -} - -#endif - -void parseHexColor(const std::string& hex, unsigned int& r, unsigned int& g, unsigned int& b) { - r = 0; g = 0; b = 0; - if (hex.empty()) return; - std::string s = hex; - if (s[0] == '#') { - s = s.substr(1); - } - if (s.size() == 6) { - try { - r = std::stoul(s.substr(0, 2), nullptr, 16); - g = std::stoul(s.substr(2, 2), nullptr, 16); - b = std::stoul(s.substr(4, 2), nullptr, 16); - } catch (...) { - r = 0; g = 0; b = 0; - } - } -} - -std::vector base64Decode(const std::string& encoded) { - std::vector decoded; - int T[256]; - std::fill(std::begin(T), std::end(T), -1); - for (int i = 0; i < 64; ++i) { - T[static_cast("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i])] = i; - } - - int val = 0; - int valb = -8; - for (char c : encoded) { - unsigned char uc = static_cast(c); - if (T[uc] == -1) continue; - val = (val << 6) + T[uc]; - valb += 6; - if (valb >= 0) { - decoded.push_back(static_cast((val >> valb) & 0xFF)); - valb -= 8; - } - } - return decoded; -} - -} +#include "parser/pdfium_internal.hpp" namespace pdfengine { std::expected, EngineError> PdfDocument::loadFromFile(const std::string& path, const std::string& password) { #ifdef PDFENGINE_WITH_PDFIUM - ensure_pdfium_initialized(); + parser::ensure_pdfium_initialized(); FPDF_DOCUMENT doc = FPDF_LoadDocument(path.c_str(), password.empty() ? nullptr : password.c_str()); if (!doc) { auto err = FPDF_GetLastError(); spdlog::error("Failed to load PDF file from path: {} (error code: {})", path, err); - return std::unexpected(mapPdfiumError(err, !password.empty())); + return std::unexpected(parser::mapPdfiumError(err, !password.empty())); } return std::make_shared(doc); #else @@ -680,7 +24,7 @@ PdfDocument::loadFromFile(const std::string& path, const std::string& password) std::expected, EngineError> PdfDocument::loadFromMemory(const std::vector& data, const std::string& password) { #ifdef PDFENGINE_WITH_PDFIUM - ensure_pdfium_initialized(); + parser::ensure_pdfium_initialized(); if (data.empty()) { return std::unexpected(EngineError::InvalidFormat); } @@ -695,7 +39,7 @@ PdfDocument::loadFromMemory(const std::vector& data, const std::string& if (!doc) { auto err = FPDF_GetLastError(); spdlog::error("Failed to load PDF from memory (error code: {})", err); - return std::unexpected(mapPdfiumError(err, !password.empty())); + return std::unexpected(parser::mapPdfiumError(err, !password.empty())); } if (!limits::pageCountOk(FPDF_GetPageCount(doc))) { spdlog::error("Refusing to load PDF: page count exceeds hardened limit ({})", @@ -714,1013 +58,9 @@ PdfDocument::loadFromMemory(const std::vector& data, const std::string& } + namespace pdfengine::parser { -PdfiumPage::PdfiumPage(NativeDocHandle docHandle, NativePageHandle pageHandle, int pageIndex, - std::shared_ptr owner) - : doc_(docHandle), page_(pageHandle), pageIndex_(pageIndex), ownerDoc_(std::move(owner)) { -} - -PdfiumPage::~PdfiumPage() { -#ifdef PDFENGINE_WITH_PDFIUM - std::lock_guard lock(textMutex_); - if (textPage_) { - FPDFText_ClosePage(textPage_); - } - if (page_) { - FPDF_ClosePage(page_); - } -#endif -} - -PdfiumPage::PdfiumPage(PdfiumPage&& other) noexcept { - *this = std::move(other); -} - -PdfiumPage& PdfiumPage::operator=(PdfiumPage&& other) noexcept { - if (this != &other) { -#ifdef PDFENGINE_WITH_PDFIUM - std::lock_guard lock(textMutex_); - if (textPage_) FPDFText_ClosePage(textPage_); - if (page_) FPDF_ClosePage(page_); -#endif - doc_ = other.doc_; - page_ = other.page_; - textPage_ = other.textPage_; - pageIndex_ = other.pageIndex_; - ownerDoc_ = std::move(other.ownerDoc_); - - other.doc_ = nullptr; - other.page_ = nullptr; - other.textPage_ = nullptr; - other.pageIndex_ = 0; - } - return *this; -} - -double PdfiumPage::width() const noexcept { -#ifdef PDFENGINE_WITH_PDFIUM - return page_ ? FPDF_GetPageWidthF(page_) : 0.0; -#else - return 0.0; -#endif -} - -double PdfiumPage::height() const noexcept { -#ifdef PDFENGINE_WITH_PDFIUM - return page_ ? FPDF_GetPageHeightF(page_) : 0.0; -#else - return 0.0; -#endif -} - -std::expected PdfiumPage::render(int dpi) const { -#ifdef PDFENGINE_WITH_PDFIUM - if (!page_) { - return std::unexpected(EngineError::Unknown); - } - - if (!limits::pageDimensionsOk(width(), height())) { - return std::unexpected(EngineError::RenderFailed); - } - - if (!limits::objectCountOk(FPDFPage_CountObjects(page_))) { - spdlog::error("Refusing to render page: object count exceeds hardened limit ({})", - limits::kMaxObjects); - return std::unexpected(EngineError::RenderFailed); - } - - double scale = dpi / 72.0; - int w = static_cast(width() * scale); - int h = static_cast(height() * scale); - - if (!limits::rasterSizeOk(w, h)) { - return std::unexpected(EngineError::RenderFailed); - } - - FPDF_BITMAP bitmap = FPDFBitmap_Create(w, h, 1); - if (!bitmap) { - return std::unexpected(EngineError::RenderFailed); - } - - FPDFBitmap_FillRect(bitmap, 0, 0, w, h, 0xFFFFFFFF); - - FPDF_RenderPageBitmap(bitmap, page_, 0, 0, w, h, 0, 0); - - - const auto* buffer = static_cast(FPDFBitmap_GetBuffer(bitmap)); - int stride = FPDFBitmap_GetStride(bitmap); - - std::vector pngBytes = encodeBgraToPng(buffer, w, h, stride); - FPDFBitmap_Destroy(bitmap); - - if (pngBytes.empty()) { - return std::unexpected(EngineError::RenderFailed); - } - - return PageImage{w, h, std::move(pngBytes)}; -#else - (void)dpi; - return std::unexpected(EngineError::Unknown); -#endif -} - -std::expected PdfiumPage::renderRegionRaw(int dpi, double yTopPt, double heightPt) const { -#ifdef PDFENGINE_WITH_PDFIUM - if (!page_) return std::unexpected(EngineError::Unknown); - if (!limits::pageDimensionsOk(width(), height())) return std::unexpected(EngineError::RenderFailed); - if (!limits::objectCountOk(FPDFPage_CountObjects(page_))) return std::unexpected(EngineError::RenderFailed); - - const double scale = dpi / 72.0; - const int w = static_cast(width() * scale); - const int fullH = static_cast(height() * scale); - int yTopPx = static_cast(yTopPt * scale); - if (yTopPx < 0) yTopPx = 0; - if (yTopPx > fullH) yTopPx = fullH; - int regionH = heightPt > 0 ? static_cast(heightPt * scale) : (fullH - yTopPx); - if (regionH > fullH - yTopPx) regionH = fullH - yTopPx; - if (w <= 0 || regionH <= 0) return std::unexpected(EngineError::RenderFailed); - if (!limits::rasterSizeOk(w, regionH)) return std::unexpected(EngineError::RenderFailed); - - FPDF_BITMAP bitmap = FPDFBitmap_Create(w, regionH, 1); - if (!bitmap) return std::unexpected(EngineError::RenderFailed); - FPDFBitmap_FillRect(bitmap, 0, 0, w, regionH, 0xFFFFFFFF); - FPDF_RenderPageBitmap(bitmap, page_, 0, -yTopPx, w, fullH, 0, 0); - - const auto* bgra = static_cast(FPDFBitmap_GetBuffer(bitmap)); - const int stride = FPDFBitmap_GetStride(bitmap); - std::vector rgba(static_cast(w) * regionH * 4); - for (int y = 0; y < regionH; ++y) { - const uint8_t* src = bgra + static_cast(y) * stride; - uint8_t* dst = rgba.data() + static_cast(y) * w * 4; - for (int x = 0; x < w; ++x) { - dst[x * 4 + 0] = src[x * 4 + 2]; - dst[x * 4 + 1] = src[x * 4 + 1]; - dst[x * 4 + 2] = src[x * 4 + 0]; - dst[x * 4 + 3] = src[x * 4 + 3]; - } - } - FPDFBitmap_Destroy(bitmap); - return PageImage{w, regionH, std::move(rgba)}; -#else - (void)dpi; (void)yTopPt; (void)heightPt; - return std::unexpected(EngineError::Unknown); -#endif -} - -std::expected PdfiumPage::extractText() const { -#ifdef PDFENGINE_WITH_PDFIUM - if (!page_) { - return std::unexpected(EngineError::Unknown); - } - ensureTextPageLoaded(); - if (!textPage_) { - return std::unexpected(EngineError::Unknown); - } - - int charCount = FPDFText_CountChars(textPage_); - if (charCount <= 0) { - return ""; - } - - std::vector buffer(charCount + 1, 0); - int written = FPDFText_GetText(textPage_, 0, charCount, buffer.data()); - if (written <= 0) { - return ""; - } - - return utf16le_to_utf8(reinterpret_cast(buffer.data()), written); -#else - return std::unexpected(EngineError::Unknown); -#endif -} - -std::expected, EngineError> PdfiumPage::extractTextWithBounds() const { -#ifdef PDFENGINE_WITH_PDFIUM - if (!page_) { - return std::unexpected(EngineError::Unknown); - } - ensureTextPageLoaded(); - if (!textPage_) { - return std::unexpected(EngineError::Unknown); - } - - int charCount = FPDFText_CountChars(textPage_); - std::vector result; - if (charCount <= 0) { - return result; - } - - result.reserve(charCount); - - for (int i = 0; i < charCount; ++i) { - unsigned int codeUnit = FPDFText_GetUnicode(textPage_, i); - - unsigned int cp = codeUnit; - double left = 0, right = 0, bottom = 0, top = 0; - double fontSize = FPDFText_GetFontSize(textPage_, i); - - 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); - - double l1 = 0, r1 = 0, b1 = 0, t1 = 0; - FPDFText_GetCharBox(textPage_, i, &l1, &r1, &b1, &t1); - - double l2 = 0, r2 = 0, b2 = 0, t2 = 0; - FPDFText_GetCharBox(textPage_, i + 1, &l2, &r2, &b2, &t2); - - left = (std::min)(l1, l2); - right = (std::max)(r1, r2); - bottom = (std::min)(b1, b2); - top = (std::max)(t1, t2); - - ++i; - } else { - FPDFText_GetCharBox(textPage_, i, &left, &right, &bottom, &top); - } - } else { - FPDFText_GetCharBox(textPage_, i, &left, &right, &bottom, &top); - } - - std::string utf8_char = code_point_to_utf8(cp); - if (utf8_char.empty() || cp == '\r' || cp == '\n') { - continue; - } - - double x = (std::min)(left, right); - double y = (std::min)(bottom, top); - double w = std::abs(right - left); - double h = std::abs(top - bottom); - - GlyphBounds gb; - gb.text = std::move(utf8_char); - gb.x = x; - gb.y = y; - gb.w = w; - gb.h = h; - gb.fontSize = fontSize; - result.push_back(gb); - } - - return result; -#else - return std::unexpected(EngineError::Unknown); -#endif -} - -namespace { -#ifdef PDFENGINE_WITH_PDFIUM -inline void repagSetParaId(FPDF_DOCUMENT doc, FPDF_PAGEOBJECT obj, const std::string& paraId) { - if (paraId.empty() || !obj) return; - FPDF_PAGEOBJECTMARK mark = FPDFPageObj_AddMark(obj, "PDFPARA"); - if (mark) FPDFPageObjMark_SetStringParam(doc, obj, mark, "id", paraId.c_str()); -} - -inline std::string repagGetParaId(FPDF_PAGEOBJECT obj) { - if (!obj) return ""; - int n = FPDFPageObj_CountMarks(obj); - for (int i = 0; i < n; ++i) { - FPDF_PAGEOBJECTMARK mark = FPDFPageObj_GetMark(obj, i); - if (!mark) continue; - unsigned long nl = 0; - FPDFPageObjMark_GetName(mark, nullptr, 0, &nl); - if (nl < 2) continue; - std::vector nb(nl / 2); - FPDFPageObjMark_GetName(mark, reinterpret_cast(nb.data()), nl, &nl); - std::string name = utf16le_to_utf8(reinterpret_cast(nb.data()), nb.size()); - while (!name.empty() && name.back() == '\0') name.pop_back(); - if (name != "PDFPARA") continue; - unsigned long vl = 0; - if (!FPDFPageObjMark_GetParamStringValue(mark, "id", nullptr, 0, &vl) || vl < 2) continue; - std::vector vb(vl / 2); - if (!FPDFPageObjMark_GetParamStringValue(mark, "id", reinterpret_cast(vb.data()), vl, &vl)) - continue; - std::string val = utf16le_to_utf8(reinterpret_cast(vb.data()), vb.size()); - while (!val.empty() && val.back() == '\0') val.pop_back(); - return val; - } - return ""; -} -#endif -} - -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; - } - } - - 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) { - 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)); - } - } - - result.push_back(info); - } - - FPDFPage_CloseAnnot(annot); - } - - FPDFDOC_ExitFormFillEnvironment(formHandle); - - return result; -#else - return std::unexpected(EngineError::Unknown); -#endif -} - -DevicePoint PdfiumPage::pageToDevice(const Point2D& pagePoint, int deviceWidth, int deviceHeight, int rotate) const noexcept { -#ifdef PDFENGINE_WITH_PDFIUM - if (!page_) return {0, 0}; - int dx = 0, dy = 0; - FPDF_PageToDevice(page_, 0, 0, deviceWidth, deviceHeight, rotate, pagePoint.x, pagePoint.y, &dx, &dy); - return {dx, dy}; -#else - (void)pagePoint; (void)deviceWidth; (void)deviceHeight; (void)rotate; - return {0, 0}; -#endif -} - -Point2D PdfiumPage::deviceToPage(const DevicePoint& devicePoint, int deviceWidth, int deviceHeight, int rotate) const noexcept { -#ifdef PDFENGINE_WITH_PDFIUM - if (!page_) return {0.0, 0.0}; - double px = 0.0, py = 0.0; - FPDF_DeviceToPage(page_, 0, 0, deviceWidth, deviceHeight, rotate, devicePoint.x, devicePoint.y, &px, &py); - return {px, py}; -#else - (void)devicePoint; (void)deviceWidth; (void)deviceHeight; (void)rotate; - return {0.0, 0.0}; -#endif -} - -std::expected PdfiumPage::getGlyphWidth(const std::string& fontName, uint32_t charcode, double fontSize) const { -#ifdef PDFENGINE_WITH_PDFIUM - if (!page_) { - return std::unexpected(EngineError::Unknown); - } - - FPDF_FONT font = nullptr; - { - std::lock_guard lock(textMutex_); - - auto cached = fontHandleCache_.find(fontName); - if (cached != fontHandleCache_.end()) { - font = cached->second; - } else { - int objectCount = FPDFPage_CountObjects(page_); - for (int i = 0; i < objectCount; ++i) { - FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page_, i); - if (!obj || FPDFPageObj_GetType(obj) != FPDF_PAGEOBJ_TEXT) continue; - - FPDF_FONT pageFont = FPDFTextObj_GetFont(obj); - if (!pageFont) continue; - - size_t nameLen = FPDFFont_GetBaseFontName(pageFont, nullptr, 0); - if (nameLen > 0) { - std::vector nameBuf(nameLen); - if (FPDFFont_GetBaseFontName(pageFont, nameBuf.data(), nameLen) > 0) { - std::string currentName(nameBuf.data()); - if (currentName == fontName) { - font = pageFont; - fontHandleCache_[fontName] = font; - break; - } - } - } - } - } - } - - if (!font) { - return std::unexpected(EngineError::Unknown); - } - - float width = 0.0f; - if (!FPDFFont_GetGlyphWidth(font, charcode, static_cast(fontSize), &width)) { - return std::unexpected(EngineError::Unknown); - } - - return static_cast(width); -#else - (void)fontName; (void)charcode; (void)fontSize; - return std::unexpected(EngineError::Unknown); -#endif -} - -void PdfiumPage::ensureTextPageLoaded() const { -#ifdef PDFENGINE_WITH_PDFIUM - std::lock_guard lock(textMutex_); - if (!textPage_ && page_) { - textPage_ = FPDFText_LoadPage(page_); - } -#endif -} - PdfiumDocument::PdfiumDocument(NativeDocHandle docHandle) : doc_(docHandle) {} @@ -1820,263 +160,6 @@ DocumentPermissions PdfiumDocument::permissions() const noexcept { return perms; } -#ifdef PDFENGINE_WITH_PDFIUM -namespace { -void walkOutline(FPDF_DOCUMENT doc, FPDF_BOOKMARK bm, int level, - std::vector& out) { - while (bm && level <= 32 && out.size() < 5000) { - PdfDocument::OutlineItem item; - item.level = level; - - unsigned long len = FPDFBookmark_GetTitle(bm, nullptr, 0); - if (len > 2) { - std::vector buf(len / 2); - FPDFBookmark_GetTitle(bm, buf.data(), len); - item.title = utf16le_to_utf8(reinterpret_cast(buf.data()), buf.size()); - while (!item.title.empty() && item.title.back() == '\0') item.title.pop_back(); - } - - FPDF_DEST dest = FPDFBookmark_GetDest(doc, bm); - if (!dest) { - FPDF_ACTION action = FPDFBookmark_GetAction(bm); - if (action) dest = FPDFAction_GetDest(doc, action); - } - item.pageIndex = dest ? static_cast(FPDFDest_GetDestPageIndex(doc, dest)) : -1; - - out.push_back(std::move(item)); - - FPDF_BOOKMARK child = FPDFBookmark_GetFirstChild(doc, bm); - if (child) walkOutline(doc, child, level + 1, out); - bm = FPDFBookmark_GetNextSibling(doc, bm); - } -} - -struct PageBand { - double pageW = 0.0, pageH = 0.0; - double placementTopY = 0.0; - double bottomLimitY = 0.0; - bool valid = false; -}; - -inline bool repagInColumn(FPDF_PAGEOBJECT o, double pushColLeft, double colRight) { - float l = 0, b = 0, r = 0, t = 0; - if (!FPDFPageObj_GetBounds(o, &l, &b, &r, &t)) return false; - return (r > pushColLeft && l < colRight); -} - -PageBand repagComputeBand(FPDF_PAGE page, double pushColLeft, double colRight) { - PageBand band; - band.pageW = FPDF_GetPageWidthF(page); - band.pageH = FPDF_GetPageHeightF(page); - double maxTop = -1e18; bool any = false; - int n = FPDFPage_CountObjects(page); - for (int k = 0; k < n; ++k) { - FPDF_PAGEOBJECT o = FPDFPage_GetObject(page, k); - if (!o) continue; - float l = 0, b = 0, r = 0, t = 0; - if (!FPDFPageObj_GetBounds(o, &l, &b, &r, &t)) continue; - if (r > pushColLeft && l < colRight) { if (t > maxTop) { maxTop = t; } any = true; } - } - if (!any) return band; - band.placementTopY = maxTop; - double limit = band.pageH - maxTop; - if (limit < 18.0) limit = 18.0; - if (limit > band.pageH * 0.25) limit = band.pageH * 0.25; - band.bottomLimitY = limit; - band.valid = true; - return band; -} - -std::vector repagAnchoredCenters(FPDF_PAGE page, double pushColLeft, double colRight, - double bottomLimitY) { - std::vector out; - int n = FPDFPage_CountObjects(page); - for (int k = 0; k < n; ++k) { - FPDF_PAGEOBJECT o = FPDFPage_GetObject(page, k); - if (!o || !repagInColumn(o, pushColLeft, colRight)) continue; - float l = 0, b = 0, r = 0, t = 0; - if (!FPDFPageObj_GetBounds(o, &l, &b, &r, &t)) continue; - double cy = (b + t) / 2.0; - if (cy < bottomLimitY) out.push_back(cy); - } - return out; -} - -inline bool repagIsAnchored(double cy, const std::vector& anchored, double tol = 1.0) { - for (double ac : anchored) if (std::abs(ac - cy) < tol) return true; - return false; -} - -int repaginateForward(FPDF_DOCUMENT doc, int startPage, - double colLeft, double colRight, double pushColLeft, double leading, - const std::vector& anchoredCentersStart, int* movedOut = nullptr) { - (void)colLeft; - const int kMaxPagesAdded = 200; - int pagesAdded = 0; - int movedTotal = 0; - int curPage = startPage; - std::vector anchored = anchoredCentersStart; - - while (true) { - FPDF_PAGE P = FPDF_LoadPage(doc, curPage); - if (!P) break; - PageBand band = repagComputeBand(P, pushColLeft, colRight); - if (!band.valid) { FPDF_ClosePage(P); break; } - - std::vector overflow; - double blockTop = -1e18, blockBottom = 1e18; - int n = FPDFPage_CountObjects(P); - for (int k = 0; k < n; ++k) { - FPDF_PAGEOBJECT o = FPDFPage_GetObject(P, k); - if (!o || !repagInColumn(o, pushColLeft, colRight)) continue; - float l = 0, b = 0, r = 0, t = 0; - if (!FPDFPageObj_GetBounds(o, &l, &b, &r, &t)) continue; - double cy = (b + t) / 2.0; - if (cy >= band.bottomLimitY) continue; - if (repagIsAnchored(cy, anchored)) continue; - overflow.push_back(o); - if (t > blockTop) blockTop = t; - if (b < blockBottom) blockBottom = b; - } - if (overflow.empty()) { FPDF_ClosePage(P); break; } - - int total = FPDF_GetPageCount(doc); - FPDF_PAGE Q = nullptr; - bool createdNew = false; - if (curPage + 1 >= total) { - if (pagesAdded + 1 > kMaxPagesAdded) { - spdlog::warn("repaginateForward: hit page-add cap ({}), stopping", kMaxPagesAdded); - FPDF_ClosePage(P); break; - } - Q = FPDFPage_New(doc, curPage + 1, band.pageW, band.pageH); - createdNew = true; - pagesAdded++; - } else { - Q = FPDF_LoadPage(doc, curPage + 1); - } - if (!Q) { FPDF_ClosePage(P); break; } - - PageBand bandQ = createdNew ? band : repagComputeBand(Q, pushColLeft, colRight); - double placeTopQ = bandQ.valid ? bandQ.placementTopY : band.placementTopY; - double limitQ = bandQ.valid ? bandQ.bottomLimitY : band.bottomLimitY; - - double blockH = blockTop - blockBottom; - if (blockH < 0) blockH = 0; - - std::vector anchoredQ = - createdNew ? std::vector{} - : repagAnchoredCenters(Q, pushColLeft, colRight, limitQ); - - if (!createdNew) { - double shift = blockH + leading; - int nq = FPDFPage_CountObjects(Q); - for (int k = 0; k < nq; ++k) { - FPDF_PAGEOBJECT o = FPDFPage_GetObject(Q, k); - if (!o || !repagInColumn(o, pushColLeft, colRight)) continue; - float l = 0, b = 0, r = 0, t = 0; - if (!FPDFPageObj_GetBounds(o, &l, &b, &r, &t)) continue; - double cy = (b + t) / 2.0; - if (repagIsAnchored(cy, anchoredQ)) continue; - FPDFPageObj_Transform(o, 1.0, 0.0, 0.0, 1.0, 0.0, -shift); - } - } - - double translateDy = placeTopQ - blockTop; - for (FPDF_PAGEOBJECT o : overflow) { - FPDFPage_RemoveObject(P, o); - FPDFPageObj_Transform(o, 1.0, 0.0, 0.0, 1.0, 0.0, translateDy); - FPDFPage_InsertObject(Q, o); - } - - FPDFPage_GenerateContent(P); - FPDFPage_GenerateContent(Q); - spdlog::info("repaginateForward: page {} -> {} moved {} object(s), blockH={:.1f}{}", - curPage, curPage + 1, overflow.size(), blockH, createdNew ? " (new page)" : ""); - FPDF_ClosePage(P); - FPDF_ClosePage(Q); - - movedTotal += static_cast(overflow.size()); - - anchored = anchoredQ; - curPage += 1; - } - if (movedOut) *movedOut = movedTotal; - return pagesAdded; -} - -int repaginateBackward(FPDF_DOCUMENT doc, int startPage, - double colLeft, double colRight, double pushColLeft, double leading) { - (void)colLeft; (void)colRight; (void)pushColLeft; (void)leading; - int pagesRemoved = 0; - for (int pg = FPDF_GetPageCount(doc) - 1; pg > startPage; --pg) { - FPDF_PAGE p = FPDF_LoadPage(doc, pg); - if (!p) break; - int n = FPDFPage_CountObjects(p); - FPDF_ClosePage(p); - if (n == 0) { FPDFPage_Delete(doc, pg); pagesRemoved++; } - else break; - } - return pagesRemoved; -} - -void repagCompactToTop(FPDF_DOCUMENT doc, int pageIdx, double pushColLeft, double colRight) { - FPDF_PAGE p = FPDF_LoadPage(doc, pageIdx); - if (!p) return; - PageBand band = repagComputeBand(p, pushColLeft, colRight); - if (!band.valid) { FPDF_ClosePage(p); return; } - std::vector anchored = repagAnchoredCenters(p, pushColLeft, colRight, band.bottomLimitY); - double curTop = -1e18; bool any = false; - int n = FPDFPage_CountObjects(p); - for (int k = 0; k < n; ++k) { - FPDF_PAGEOBJECT o = FPDFPage_GetObject(p, k); - if (!o || !repagInColumn(o, pushColLeft, colRight)) continue; - float l=0,b=0,r=0,t=0; if (!FPDFPageObj_GetBounds(o,&l,&b,&r,&t)) continue; - double cy=(b+t)/2.0; if (repagIsAnchored(cy, anchored)) continue; - if (t > curTop) { curTop = t; any = true; } - } - if (any) { - double up = band.placementTopY - curTop; - if (up > 0.01) { - for (int k = 0; k < n; ++k) { - FPDF_PAGEOBJECT o = FPDFPage_GetObject(p, k); - if (!o || !repagInColumn(o, pushColLeft, colRight)) continue; - float l=0,b=0,r=0,t=0; if (!FPDFPageObj_GetBounds(o,&l,&b,&r,&t)) continue; - double cy=(b+t)/2.0; if (repagIsAnchored(cy, anchored)) continue; - FPDFPageObj_Transform(o, 1.0, 0.0, 0.0, 1.0, 0.0, up); - } - FPDFPage_GenerateContent(p); - } - } - FPDF_ClosePage(p); -} - -int repagRemoveContinuations(FPDF_DOCUMENT doc, int anchorPage, const std::string& paraId, - double pushColLeft, double colRight) { - if (paraId.empty()) return 0; - int touched = 0; - int total = FPDF_GetPageCount(doc); - for (int pg = 0; pg < total; ++pg) { - if (pg == anchorPage) continue; - FPDF_PAGE p = FPDF_LoadPage(doc, pg); - if (!p) continue; - std::vector toDel; - int n = FPDFPage_CountObjects(p); - for (int k = 0; k < n; ++k) { - FPDF_PAGEOBJECT o = FPDFPage_GetObject(p, k); - if (o && repagGetParaId(o) == paraId) toDel.push_back(o); - } - if (toDel.empty()) { FPDF_ClosePage(p); continue; } - for (auto o : toDel) { FPDFPage_RemoveObject(p, o); FPDFPageObj_Destroy(o); } - FPDFPage_GenerateContent(p); - FPDF_ClosePage(p); - repagCompactToTop(doc, pg, pushColLeft, colRight); - touched++; - } - return touched; -} -} -#endif - std::expected, EngineError> PdfiumDocument::extractOutline() const { #ifdef PDFENGINE_WITH_PDFIUM std::vector result; @@ -2127,2136 +210,6 @@ std::expected, EngineError> PdfiumDocument::getPage(int #endif } -#ifdef PDFENGINE_WITH_PDFIUM -// Tier-2: register a pre-reconstructed (cmap-augmented) font + its coverage. Used by the WASM -// preview path: the frontend fetches gateway-reconstructed bytes and registers them so the -// preview emits the same glyphs the native save will. -void PdfiumDocument::registerAuxFont(const std::string& internalFontId, const std::vector& sfnt) { - fonts::pdf_fonts::ReconstructedFont rf; - if (!sfnt.empty()) { - rf.ok = true; - // Derive coverage straight from the (cmap-bearing) sfnt so the WASM preview's - // exact/hybrid/fallback decision matches the native save. - fonts::FontFace face; - if (face.loadFromMemory(sfnt)) - for (uint32_t u : face.coveredCodepoints()) rf.coveredUnicode.insert(u); - rf.sfnt = sfnt; - } - std::lock_guard lock(reconstructedFontsMutex_); - reconstructedFonts_[internalFontId] = std::move(rf); -} - -std::expected, EngineError> -PdfiumDocument::getReconstructedFontData(const std::string& internalFontId) { - const auto* rf = lookupReconFont(internalFontId); - if (rf && rf->ok && !rf->sfnt.empty()) return rf->sfnt; - return std::unexpected(EngineError::Unknown); -} - -const fonts::pdf_fonts::ReconstructedFont* PdfiumDocument::lookupReconFont(const std::string& internalFontId) { -#ifdef PDFENGINE_WITH_QPDF - return getReconstructedEmbeddedFont(internalFontId); -#else - std::lock_guard lock(reconstructedFontsMutex_); - auto it = reconstructedFonts_.find(internalFontId); - return it != reconstructedFonts_.end() ? &it->second : nullptr; -#endif -} -#endif - -#if defined(PDFENGINE_WITH_PDFIUM) && defined(PDFENGINE_WITH_QPDF) -const fonts::pdf_fonts::ReconstructedFont* -PdfiumDocument::getReconstructedEmbeddedFont(const std::string& internalFontId) { - { - std::lock_guard lock(reconstructedFontsMutex_); - auto it = reconstructedFonts_.find(internalFontId); - if (it != reconstructedFonts_.end()) return &it->second; - } - fonts::pdf_fonts::ReconstructedFont rf; - auto progRes = getFontData(internalFontId); - if (progRes.has_value() && !progRes.value().empty() && !memoryBuffer_.empty()) { - std::string baseName = baseNameFromInternalFontId(internalFontId); - qpdf_layer::QpdfFontExtractor fx; - auto mapRes = fx.extractMapping(memoryBuffer_, baseName); - if (mapRes.has_value() && mapRes->ok) { - rf = fonts::pdf_fonts::EmbeddedFontReconstructor::reconstruct( - progRes.value(), mapRes->codeToUnicode, mapRes->identityCidToGid, mapRes->codeToGid); - spdlog::info("Tier-2: reconstruct '{}' ok={} covered={}", internalFontId, rf.ok, rf.coveredUnicode.size()); - } - } - std::lock_guard lock(reconstructedFontsMutex_); - auto res = reconstructedFonts_.emplace(internalFontId, std::move(rf)); - return &res.first->second; -} -#endif - -#ifdef PDFENGINE_WITH_PDFIUM -PdfiumDocument::EmissionFont PdfiumDocument::loadEmissionFont( - int pageIndex, const std::string& internalFontId, double fontSize, - const std::vector& codepoints, const std::vector& srcObjects, - FPDF_FONT reuseFont) { - EmissionFont out; - (void)fontSize; - - std::string fontName = "Helvetica"; - bool bold = false, italic = false; - { - std::string lowerId = internalFontId; - std::transform(lowerId.begin(), lowerId.end(), lowerId.begin(), - [](unsigned char ch) { return static_cast(std::tolower(ch)); }); - if (lowerId.find("bolditalic") != std::string::npos) { bold = true; italic = true; } - else if (lowerId.find("bold") != std::string::npos) { bold = true; } - else if (lowerId.find("italic") != std::string::npos) { italic = true; } - else if (lowerId.find("oblique") != std::string::npos) { italic = true; } - if (lowerId.find("times") != std::string::npos) { - fontName = bold && italic ? "Times-BoldItalic" : bold ? "Times-Bold" : italic ? "Times-Italic" : "Times-Roman"; - } else if (lowerId.find("courier") != std::string::npos) { - fontName = bold && italic ? "Courier-BoldOblique" : bold ? "Courier-Bold" : italic ? "Courier-Oblique" : "Courier"; - } else { - fontName = bold && italic ? "Helvetica-BoldOblique" : bold ? "Helvetica-Bold" : italic ? "Helvetica-Oblique" : "Helvetica"; - } - } - - std::optional matchedFontInfo; - auto fontsRes = getFonts(pageIndex, pageIndex); - if (fontsRes.has_value()) { - for (const auto& fi : *fontsRes) { - if (!internalFontId.empty() && fi.internalFontId == internalFontId) { matchedFontInfo = fi; break; } - } - } - if (matchedFontInfo.has_value()) { - auto r = getResolvedFont(*matchedFontInfo); - if (r.has_value()) out.resolved = *r; - } - - bool fontSupportsAll = true, subsetLacksGlyphs = false; - bool isSubsetFont = matchedFontInfo && matchedFontInfo->isSubset; - if (out.resolved) { - for (uint32_t cp : codepoints) { - if (!out.resolved->hasGlyph(cp)) { - if (isSubsetFont) subsetLacksGlyphs = true; else fontSupportsAll = false; - } - } - } else { - fontSupportsAll = false; - } - - std::string cacheKey; - bool useEmbedded = false, useSystem = false; - if (matchedFontInfo && matchedFontInfo->isEmbedded) { - bool subsetProvenLacking = out.resolved && isSubsetFont && subsetLacksGlyphs; - bool fullProvenLacking = out.resolved && !isSubsetFont && !fontSupportsAll; - if (!subsetProvenLacking && !fullProvenLacking) { cacheKey = matchedFontInfo->internalFontId; useEmbedded = true; } - else { cacheKey = "system_embed_" + matchedFontInfo->fontName + std::string("_") + (bold ? "B" : "") + (italic ? "I" : ""); useSystem = true; } - } else if (matchedFontInfo && !matchedFontInfo->isEmbedded) { - cacheKey = "standard_" + fontName; - } else { - cacheKey = "standard_" + fontName; - } - - { - uint64_t h = 1469598103934665603ull; - for (uint32_t cp : codepoints) { h ^= cp; h *= 1099511628211ull; } - cacheKey += "#" + std::to_string(h); - } - - if (useEmbedded && reuseFont && !isSubsetFont) { - out.font = reuseFont; - if (auto perObj = getFontDataFromObjects(pageIndex, srcObjects, internalFontId); - perObj.has_value() && !perObj->empty()) { - auto mf = std::make_shared(); - if (mf->loadFromMemory(*perObj)) out.measureFace = mf; - } - return out; - } - - { - std::lock_guard lock(loadedFontsMutex_); - if (loadedFontsCache_.count(cacheKey)) { - out.font = loadedFontsCache_[cacheKey]; - auto it = loadedMeasureFaces_.find(cacheKey); - if (it != loadedMeasureFaces_.end()) out.measureFace = it->second; - } - } - if (out.font) return out; - - if (matchedFontInfo && matchedFontInfo->isEmbedded && classifyFontFidelity(*matchedFontInfo) != "exact") { - const auto* rf = lookupReconFont(internalFontId); - if (rf && rf->ok) { - bool covered = true; - for (uint32_t cp : codepoints) { - if (cp >= 0x20 && !rf->coveredUnicode.count(cp)) { covered = false; break; } - } - if (covered) { - std::lock_guard lock(loadedFontsMutex_); - const std::string rkey = "recon_" + internalFontId; - if (loadedFontsCache_.count(rkey)) { - out.font = loadedFontsCache_[rkey]; - auto mit = loadedMeasureFaces_.find(rkey); - if (mit != loadedMeasureFaces_.end()) out.measureFace = mit->second; - } else { - loadedFontDataBuffers_[rkey] = rf->sfnt; - const auto& bytes = loadedFontDataBuffers_[rkey]; - out.font = FPDFText_LoadFont(doc_, bytes.data(), static_cast(bytes.size()), FPDF_FONT_TRUETYPE, true); - if (out.font) { - loadedFontsCache_[rkey] = out.font; - auto mf = std::make_shared(); - if (mf->loadFromMemory(bytes)) { loadedMeasureFaces_[rkey] = mf; out.measureFace = mf; } - } - } - if (out.font) { spdlog::info("Tier-2: emit reconstructed embedded font '{}'", internalFontId); return out; } - } - } - } - - if (useEmbedded) { - std::vector sourceBytes; - if (auto perObj = getFontDataFromObjects(pageIndex, srcObjects, matchedFontInfo->internalFontId); - perObj.has_value() && !perObj->empty()) { - sourceBytes = std::move(*perObj); - } else if (auto fontDataRes = getFontData(matchedFontInfo->internalFontId); - fontDataRes.has_value() && !fontDataRes.value().empty()) { - sourceBytes = std::move(fontDataRes.value()); - } - if (!sourceBytes.empty()) { - std::lock_guard lock(loadedFontsMutex_); - loadedFontDataBuffers_[cacheKey] = std::move(sourceBytes); - const auto& bytes = loadedFontDataBuffers_[cacheKey]; - out.font = FPDFText_LoadFont(doc_, bytes.data(), static_cast(bytes.size()), FPDF_FONT_TRUETYPE, true); - } - } else if (useSystem) { - std::string fontPath = fonts::pdf_fonts::FontFallback::getInstance().getFallbackFontPath( - matchedFontInfo->normalizedFamily.empty() ? matchedFontInfo->fontName : matchedFontInfo->normalizedFamily, bold, italic); - std::ifstream fs(fontPath, std::ios::binary); - if (fs) { - std::vector fileBytes((std::istreambuf_iterator(fs)), std::istreambuf_iterator()); - if (!fileBytes.empty()) { - std::lock_guard lock(loadedFontsMutex_); - loadedFontDataBuffers_[cacheKey] = std::move(fileBytes); - const auto& bytes = loadedFontDataBuffers_[cacheKey]; - out.font = FPDFText_LoadFont(doc_, bytes.data(), static_cast(bytes.size()), FPDF_FONT_TRUETYPE, true); - } - } - } - if (!out.font) { - out.font = FPDFText_LoadStandardFont(doc_, fontName.c_str()); - if (!out.font) out.font = FPDFText_LoadStandardFont(doc_, "Helvetica"); - } - if (out.font) { - std::lock_guard lock(loadedFontsMutex_); - loadedFontsCache_[cacheKey] = out.font; - auto bit = loadedFontDataBuffers_.find(cacheKey); - if (bit != loadedFontDataBuffers_.end() && !bit->second.empty()) { - auto mf = std::make_shared(); - if (mf->loadFromMemory(bit->second)) { loadedMeasureFaces_[cacheKey] = mf; out.measureFace = mf; } - } - } - return out; -} -#endif - -std::expected PdfiumDocument::applyEdits(const std::string& editsJson) { -#ifdef PDFENGINE_WITH_PDFIUM - if (!doc_) { - return std::unexpected(EngineError::Unknown); - } - - lastReflowLayout_.clear(); - - try { - auto root = nlohmann::json::parse(editsJson); - if (!root.contains("version") || root["version"] != "1.0") { - spdlog::error("Invalid edits JSON: missing or unsupported version (expected '1.0')"); - return std::unexpected(EngineError::InvalidFormat); - } - if (!root.contains("operations") || !root["operations"].is_array()) { - spdlog::error("Invalid edits JSON: missing 'operations' array"); - return std::unexpected(EngineError::InvalidFormat); - } - - for (const auto& op : root["operations"]) { - std::string type = op.value("type", ""); - int pageIndex = op.value("pageIndex", -1); - if (pageIndex < 0 || pageIndex >= pageCount()) { - spdlog::error("Page index {} out of bounds (total pages: {})", pageIndex, pageCount()); - return std::unexpected(EngineError::PageOutOfBounds); - } - - if (type == "replace_text") { - std::vector objectIndices; - std::string newText = ""; - std::string internalFontId = ""; - double fontSize = -1.0; - bool disableJustify = false; // set for table cells: replace in place, never re-justify - - if (op.contains("data") && op["data"].is_object()) { - auto data = op["data"]; - if (data.contains("objectIndices") && data["objectIndices"].is_array()) { - for (auto& idx : data["objectIndices"]) { - objectIndices.push_back(idx.get()); - } - } - newText = data.value("text", ""); - internalFontId = data.value("internalFontId", ""); - if (data.contains("fontSize")) { - fontSize = data["fontSize"].get(); - } - disableJustify = data.value("disableJustify", false); - } else { - if (op.contains("objectIndices") && op["objectIndices"].is_array()) { - for (auto& idx : op["objectIndices"]) { - objectIndices.push_back(idx.get()); - } - } - newText = op.value("text", ""); - internalFontId = op.value("internalFontId", ""); - if (op.contains("fontSize")) { - fontSize = op["fontSize"].get(); - } - } - - if (objectIndices.empty()) { - spdlog::warn("replace_text has empty objectIndices, nothing to replace"); - continue; - } - - FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); - if (!page) { - spdlog::error("Failed to load page index {} for replace_text", pageIndex); - return std::unexpected(EngineError::Unknown); - } - - std::sort(objectIndices.begin(), objectIndices.end(), std::greater()); - - int minIndex = objectIndices.back(); - - FPDF_PAGEOBJECT origObj = FPDFPage_GetObject(page, minIndex); - if (!origObj) { - spdlog::error("Failed to get original text object at index {}", minIndex); - FPDF_ClosePage(page); - return std::unexpected(EngineError::Unknown); - } - - double a = 1.0, b = 0.0, c = 0.0, d = 1.0, e = 0.0, f = 0.0; - FS_MATRIX matrix; - if (FPDFPageObj_GetMatrix(origObj, &matrix)) { - a = matrix.a; - b = matrix.b; - c = matrix.c; - d = matrix.d; - e = matrix.e; - f = matrix.f; - } - - unsigned int r = 0, g = 0, b_color = 0, a_color = 255; - FPDFPageObj_GetFillColor(origObj, &r, &g, &b_color, &a_color); - - if (fontSize < 0.0) { - float sizeVal = 12.0f; - if (FPDFTextObj_GetFontSize(origObj, &sizeVal)) { - fontSize = sizeVal; - } else { - fontSize = 12.0; - } - } - - FPDF_TEXT_RENDERMODE renderMode = static_cast(FPDFTextObj_GetTextRenderMode(origObj)); - - std::string fontName = "Helvetica"; - std::string origFontName = ""; - bool bold = false; - bool italic = false; - FPDF_FONT origFont = FPDFTextObj_GetFont(origObj); - if (origFont) { - size_t nameLen = FPDFFont_GetBaseFontName(origFont, nullptr, 0); - if (nameLen > 0) { - std::vector nameBuf(nameLen); - if (FPDFFont_GetBaseFontName(origFont, nameBuf.data(), nameLen) > 0) { - origFontName = nameBuf.data(); - std::string baseName(nameBuf.data()); - std::string lowerName = baseName; - std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), [](unsigned char c) { - return static_cast(std::tolower(c)); - }); - - bold = (lowerName.find("bold") != std::string::npos); - italic = (lowerName.find("italic") != std::string::npos || lowerName.find("oblique") != std::string::npos); - - if (lowerName.find("times") != std::string::npos) { - if (bold && italic) fontName = "Times-BoldItalic"; - else if (bold) fontName = "Times-Bold"; - else if (italic) fontName = "Times-Italic"; - else fontName = "Times-Roman"; - } else if (lowerName.find("courier") != std::string::npos) { - if (bold && italic) fontName = "Courier-BoldOblique"; - else if (bold) fontName = "Courier-Bold"; - else if (italic) fontName = "Courier-Oblique"; - else fontName = "Courier"; - } else { - if (bold && italic) fontName = "Helvetica-BoldOblique"; - else if (bold) fontName = "Helvetica-Bold"; - else if (italic) fontName = "Helvetica-Oblique"; - else fontName = "Helvetica"; - } - } - } - } - - if (!internalFontId.empty()) { - std::string lowerId = internalFontId; - std::transform(lowerId.begin(), lowerId.end(), lowerId.begin(), [](unsigned char c) { - return static_cast(std::tolower(c)); - }); - if (lowerId.find("bolditalic") != std::string::npos) { bold = true; italic = true; } - else if (lowerId.find("bold") != std::string::npos) { bold = true; } - else if (lowerId.find("italic") != std::string::npos) { italic = true; } - else if (lowerId.find("oblique") != std::string::npos) { italic = true; } - - if (lowerId.find("times") != std::string::npos) { - if (bold && italic) fontName = "Times-BoldItalic"; - else if (bold) fontName = "Times-Bold"; - else if (italic) fontName = "Times-Italic"; - else fontName = "Times-Roman"; - } else if (lowerId.find("courier") != std::string::npos) { - if (bold && italic) fontName = "Courier-BoldOblique"; - else if (bold) fontName = "Courier-Bold"; - else if (italic) fontName = "Courier-Oblique"; - else fontName = "Courier"; - } else if (lowerId.find("helvetica") != std::string::npos) { - if (bold && italic) fontName = "Helvetica-BoldOblique"; - else if (bold) fontName = "Helvetica-Bold"; - else if (italic) fontName = "Helvetica-Oblique"; - else fontName = "Helvetica"; - } - } - - std::optional matchedFontInfo; - auto fontsRes = getFonts(pageIndex, pageIndex); - if (fontsRes.has_value()) { - for (const auto& fontInfoEntry : *fontsRes) { - if ((!internalFontId.empty() && fontInfoEntry.internalFontId == internalFontId) || - (!origFontName.empty() && fontInfoEntry.fontName == origFontName)) { - matchedFontInfo = fontInfoEntry; - break; - } - } - } - - std::shared_ptr resolvedFont = nullptr; - if (matchedFontInfo.has_value()) { - auto resolvedFontRes = getResolvedFont(*matchedFontInfo); - if (resolvedFontRes.has_value()) { - resolvedFont = *resolvedFontRes; - spdlog::info("Font Engine: resolved font '{}'", matchedFontInfo->fontName); - } else { - spdlog::warn("Font Engine: failed to resolve font '{}': {}", matchedFontInfo->fontName, resolvedFontRes.error()); - } - } - - bool fontSupportsAll = true; - double totalWidth = 0.0; - - auto utf16 = utf8_to_utf16le(newText); - std::vector unicodeCodepoints; - for (size_t i = 0; i < utf16.size(); ) { - uint32_t cp = utf16[i]; - if (cp >= 0xD800 && cp <= 0xDBFF && i + 1 < utf16.size()) { - uint32_t low = utf16[i + 1]; - if (low >= 0xDC00 && low <= 0xDFFF) { - cp = 0x10000 + ((cp - 0xD800) << 10) + (low - 0xDC00); - i += 2; - } else { - i += 1; - } - } else { - i += 1; - } - unicodeCodepoints.push_back(cp); - } - - bool isSubsetFont = matchedFontInfo && matchedFontInfo->isSubset; - bool subsetLacksGlyphs = false; - - if (resolvedFont) { - for (uint32_t cp : unicodeCodepoints) { - if (!resolvedFont->hasGlyph(cp)) { - if (isSubsetFont) { - subsetLacksGlyphs = true; - } else { - fontSupportsAll = false; - } - spdlog::warn("Font Engine: Glyph for codepoint {} not found in font {}", cp, matchedFontInfo ? matchedFontInfo->fontName : "Unknown"); - } - } - } else { - fontSupportsAll = false; - } - - bool shapedSuccessful = false; - if (resolvedFont) { - try { - fonts::HbShaper shaper; - unsigned int uFontSize = static_cast(fontSize > 0.0 ? fontSize : 12.0); - auto shapedGlyphs = shaper.shapeRun(newText, resolvedFont->getFontFace(), uFontSize); - if (!shapedGlyphs.empty()) { - totalWidth = 0.0; - for (const auto& sg : shapedGlyphs) { - totalWidth += sg.advanceX; - } - shapedSuccessful = true; - spdlog::info("Font Engine: HarfBuzz shaped '{}' glyphs, total advance width = {}", shapedGlyphs.size(), totalWidth); - } - } catch (const std::exception& e) { - spdlog::warn("Font Engine: HarfBuzz shaping failed: {}", e.what()); - } catch (...) { - spdlog::warn("Font Engine: HarfBuzz shaping failed with unknown exception"); - } - } - - if (!shapedSuccessful && resolvedFont) { - totalWidth = 0.0; - for (uint32_t cp : unicodeCodepoints) { - double w = resolvedFont->getAdvanceWidth(cp, fontSize); - totalWidth += w; - } - spdlog::info("Font Engine: FreeType fallback total advance width = {}", totalWidth); - } - - float origLeft = 999999.0f, origRight = -999999.0f; - float origBottom = 999999.0f, origTop = -999999.0f; - bool hasOrigBounds = false; - for (int idx : objectIndices) { - FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, idx); - if (obj) { - float left = 0.0f, bottom = 0.0f, right = 0.0f, top = 0.0f; - if (FPDFPageObj_GetBounds(obj, &left, &bottom, &right, &top)) { - if (left < origLeft) origLeft = left; - if (right > origRight) origRight = right; - if (bottom < origBottom) origBottom = bottom; - if (top > origTop) origTop = top; - hasOrigBounds = true; - } - } - } - - double origWidth = 0.0; - double origCenterY = 0.0; - if (hasOrigBounds) { - origWidth = origRight - origLeft; - origCenterY = (origBottom + origTop) / 2.0; - } - - bool axisAligned = (std::abs(b) < 1e-6 && std::abs(c) < 1e-6 && a > 0.0 && d > 0.0); - double colRight = origRight; - bool sawSibling = false; - if (axisAligned && hasOrigBounds) { - int nObjForCol = FPDFPage_CountObjects(page); - for (int k = 0; k < nObjForCol; ++k) { - if (std::find(objectIndices.begin(), objectIndices.end(), k) != objectIndices.end()) continue; - FPDF_PAGEOBJECT o = FPDFPage_GetObject(page, k); - if (!o || FPDFPageObj_GetType(o) != FPDF_PAGEOBJ_TEXT) continue; - float l = 0, bo = 0, rr = 0, tt = 0; - if (!FPDFPageObj_GetBounds(o, &l, &bo, &rr, &tt)) continue; - if (std::abs(l - origLeft) <= 3.0) { sawSibling = true; if (rr > colRight) colRight = rr; } - } - } - double justifyTol = (std::max)(4.0, (colRight - origLeft) * 0.02); - bool wasJustified = !disableJustify && axisAligned && resolvedFont && hasOrigBounds && sawSibling && - (colRight - origLeft) > 20.0 && (origRight >= colRight - justifyTol); - - double deltaX = 0.0; - if (hasOrigBounds) { - deltaX = totalWidth - origWidth; - spdlog::info("Reflow Engine: origWidth = {}, newWidth = {}, deltaX = {}", origWidth, totalWidth, deltaX); - } - - if (!wasJustified && !disableJustify && hasOrigBounds && std::abs(deltaX) > 0.001) { - int pageObjCount = FPDFPage_CountObjects(page); - double tolerance = (std::max)(5.0, fontSize * 0.5); - int reflowedCount = 0; - - for (int k = 0; k < pageObjCount; ++k) { - if (std::find(objectIndices.begin(), objectIndices.end(), k) != objectIndices.end()) { - continue; - } - - FPDF_PAGEOBJECT otherObj = FPDFPage_GetObject(page, k); - if (otherObj && FPDFPageObj_GetType(otherObj) == FPDF_PAGEOBJ_TEXT) { - float otherLeft = 0.0f, otherBottom = 0.0f, otherRight = 0.0f, otherTop = 0.0f; - if (FPDFPageObj_GetBounds(otherObj, &otherLeft, &otherBottom, &otherRight, &otherTop)) { - double otherCenterY = (otherBottom + otherTop) / 2.0; - if (std::abs(otherCenterY - origCenterY) <= tolerance) { - if (otherLeft >= (origRight - 2.0f)) { - FPDFPageObj_Transform(otherObj, 1.0, 0.0, 0.0, 1.0, deltaX, 0.0); - reflowedCount++; - } - } - } - } - } - spdlog::info("Reflow Engine: shifted {} subsequent text objects on the same line by {}", reflowedCount, deltaX); - } - - for (int idx : objectIndices) { - FPDF_PAGEOBJECT objToRemove = FPDFPage_GetObject(page, idx); - if (objToRemove) { - FPDFPage_RemoveObject(page, objToRemove); - FPDFPageObj_Destroy(objToRemove); - } - } - - FPDF_FONT font = nullptr; - std::string cacheKey = ""; - bool useEmbedded = false; - bool useSystem = false; - - if (resolvedFont && matchedFontInfo) { - if (matchedFontInfo->isEmbedded && !isSubsetFont && fontSupportsAll) { - cacheKey = matchedFontInfo->internalFontId; - useEmbedded = true; - } else if (matchedFontInfo->isEmbedded && isSubsetFont && !subsetLacksGlyphs) { - cacheKey = matchedFontInfo->internalFontId; - useEmbedded = true; - } else if (!matchedFontInfo->isEmbedded) { - cacheKey = "standard_" + fontName; - } else { - cacheKey = "system_embed_" + matchedFontInfo->fontName + "_" + (bold ? "B" : "") + (italic ? "I" : ""); - useSystem = true; - } - } else { - cacheKey = "standard_" + fontName; - } - - { - std::lock_guard lock(loadedFontsMutex_); - if (loadedFontsCache_.count(cacheKey)) { - font = loadedFontsCache_[cacheKey]; - spdlog::info("Font Engine: Reusing cached FPDF_FONT for key '{}'", cacheKey); - } - } - - FPDF_FONT reconFont = nullptr; - const fonts::pdf_fonts::ReconstructedFont* reconRf = nullptr; - if (matchedFontInfo && matchedFontInfo->isEmbedded && classifyFontFidelity(*matchedFontInfo) != "exact") { - reconRf = lookupReconFont(matchedFontInfo->internalFontId); - if (reconRf && reconRf->ok) { - std::lock_guard lock(loadedFontsMutex_); - const std::string rkey = "recon_" + matchedFontInfo->internalFontId; - if (loadedFontsCache_.count(rkey)) reconFont = loadedFontsCache_[rkey]; - else { - loadedFontDataBuffers_[rkey] = reconRf->sfnt; - const auto& bytes = loadedFontDataBuffers_[rkey]; - reconFont = FPDFText_LoadFont(doc_, bytes.data(), static_cast(bytes.size()), FPDF_FONT_TRUETYPE, true); - if (reconFont) loadedFontsCache_[rkey] = reconFont; - } - if (reconFont) { - bool fullyCovered = true; - for (uint32_t cp : unicodeCodepoints) - if (cp >= 0x20 && !reconRf->coveredUnicode.count(cp)) { fullyCovered = false; break; } - if (fullyCovered && !font) { - font = reconFont; - spdlog::info("Tier-2: replace_text exact reconstructed font '{}'", matchedFontInfo->internalFontId); - } - } - } - } - - if (!font) { - if (useEmbedded) { - auto fontDataRes = getFontData(matchedFontInfo->internalFontId); - if (fontDataRes.has_value()) { - std::lock_guard lock(loadedFontsMutex_); - loadedFontDataBuffers_[cacheKey] = fontDataRes.value(); - const auto& bytes = loadedFontDataBuffers_[cacheKey]; - font = FPDFText_LoadFont(doc_, bytes.data(), static_cast(bytes.size()), FPDF_FONT_TRUETYPE, false); - if (font) { - spdlog::info("Font Engine: Loaded embedded font '{}' (cache key: {})", matchedFontInfo->fontName, cacheKey); - } - } - } else if (useSystem) { - std::string fontPath = fonts::pdf_fonts::FontFallback::getInstance().getFallbackFontPath( - matchedFontInfo->normalizedFamily.empty() ? matchedFontInfo->fontName : matchedFontInfo->normalizedFamily, - bold, - italic - ); - std::ifstream fs(fontPath, std::ios::binary); - if (fs) { - std::vector fileBytes((std::istreambuf_iterator(fs)), std::istreambuf_iterator()); - if (!fileBytes.empty()) { - // Embed the FULL font (no per-edit subset). A subset renumbers GIDs - // per call, but every embed shares the same BaseFont name -> a second - // edit's text would reference the first edit's font with mismatched - // GIDs and render as scrambled glyphs. The full font has a stable, - // standard GID layout, so chained edits stay consistent (same reason - // the reflow path embeds full fonts; also avoids WASM subset glyph-drop). - const size_t fullSize = fileBytes.size(); - std::lock_guard lock(loadedFontsMutex_); - loadedFontDataBuffers_[cacheKey] = std::move(fileBytes); - const auto& bytes = loadedFontDataBuffers_[cacheKey]; - font = FPDFText_LoadFont(doc_, bytes.data(), static_cast(bytes.size()), FPDF_FONT_TRUETYPE, true); - if (font) { - spdlog::info("Font Engine: Embedded FULL CID system font '{}' ({} bytes) from '{}'", - matchedFontInfo->fontName, fullSize, fontPath); - } - } - } else { - spdlog::warn("Font Engine: Failed to open system font file '{}' for embedding", fontPath); - } - } - - if (!font) { - spdlog::info("Font Engine: Loading standard PDF font for replace_text: {}", fontName); - font = FPDFText_LoadStandardFont(doc_, fontName.c_str()); - if (!font) { - font = FPDFText_LoadStandardFont(doc_, "Helvetica"); - } - } - - if (font) { - std::lock_guard lock(loadedFontsMutex_); - loadedFontsCache_[cacheKey] = font; - } - } - - if (font) { - constexpr unsigned int kRef = 1000; - double emToPage = (fontSize > 0.0 ? fontSize : 1.0) * a / static_cast(kRef); - auto pageWidthOf = [&](const std::string& s) -> double { - if (s.empty() || !resolvedFont) return 0.0; - double sum = 0.0; - try { - fonts::HbShaper sh; - auto gl = sh.shapeRun(s, resolvedFont->getFontFace(), kRef); - for (const auto& gg : gl) sum += gg.advanceX; - } catch (...) { - for (unsigned char ch : s) sum += resolvedFont->getAdvanceWidth(ch, kRef); - } - return sum * emToPage; - }; - - std::vector words; - if (wasJustified) { - std::string cur; - for (char ch : newText) { - if (ch == ' ') { if (!cur.empty()) { words.push_back(cur); cur.clear(); } } - else cur.push_back(ch); - } - if (!cur.empty()) words.push_back(cur); - } - - auto measuredWidth = [&](const std::vector& u16le) -> double { - FPDF_PAGEOBJECT m = FPDFPageObj_CreateTextObj(doc_, font, static_cast(fontSize)); - if (!m) return 0.0; - FPDFText_SetText(m, reinterpret_cast(u16le.data())); - FPDFPageObj_Transform(m, a, b, c, d, 0.0, 0.0); - float l = 0, bo = 0, rr = 0, tt = 0; - double w = FPDFPageObj_GetBounds(m, &l, &bo, &rr, &tt) ? (rr - l) : 0.0; - FPDFPageObj_Destroy(m); - return w; - }; - - if (wasJustified && words.size() > 1) { - std::vector wpx; - wpx.reserve(words.size()); - double estWords = 0.0; - for (const auto& w : words) { double ww = pageWidthOf(w); wpx.push_back(ww); estWords += ww; } - double estSpace = pageWidthOf(" "); - int gaps = static_cast(words.size()) - 1; - double estTotal = estWords + gaps * estSpace; - double actualFull = measuredWidth(utf16); - double k = (estTotal > 1e-6 && actualFull > 1e-6) ? actualFull / estTotal : 1.0; - double targetW = colRight - e; - double slack = targetW - actualFull; - double extraPerGap = (slack > 0.0) ? slack / gaps : 0.0; - spdlog::info("replace_text: justify {} words targetW={:.1f} actualW={:.1f} extraPerGap={:.2f}", - words.size(), targetW, actualFull, extraPerGap); - double penX = e; - for (size_t wi = 0; wi < words.size(); ++wi) { - FPDF_PAGEOBJECT wobj = FPDFPageObj_CreateTextObj(doc_, font, static_cast(fontSize)); - if (wobj) { - FPDFPageObj_SetFillColor(wobj, r, g, b_color, a_color); - FPDFTextObj_SetTextRenderMode(wobj, renderMode); - auto wu = utf8_to_utf16le(words[wi]); wu.push_back(0); - FPDFText_SetText(wobj, reinterpret_cast(wu.data())); - FPDFPageObj_Transform(wobj, a, b, c, d, penX, f); - FPDFPage_InsertObjectAtIndex(page, wobj, minIndex); - } - penX += (wpx[wi] + estSpace) * k + extraPerGap; - } - } else { - bool didHybrid = false; - if (reconFont && reconRf && font && font != reconFont && !unicodeCodepoints.empty()) { - fonts::FontFace reconFace; bool haveReconFace = reconFace.loadFromMemory(reconRf->sfnt); - fonts::FontFace subFace; bool haveSubFace = false; - { - std::lock_guard lock(loadedFontsMutex_); - auto it = loadedFontDataBuffers_.find(cacheKey); - if (it != loadedFontDataBuffers_.end() && !it->second.empty()) - haveSubFace = subFace.loadFromMemory(it->second); - } - auto segWidthPage = [&](fonts::FontFace* face, const std::string& s) -> double { - if (!face || s.empty()) return 0.0; - double sum = 0.0; - try { fonts::HbShaper sh; for (const auto& gg : sh.shapeRun(s, *face, kRef)) sum += gg.advanceX; } - catch (...) { return 0.0; } - return sum * emToPage; - }; - auto isCov = [&](uint32_t cp){ return cp < 0x20 || reconRf->coveredUnicode.count(cp) != 0; }; - auto cpToU16 = [](uint32_t cp, std::vector& dst){ - if (cp <= 0xFFFF) dst.push_back(static_cast(cp)); - else { cp -= 0x10000; dst.push_back(static_cast(0xD800 + (cp >> 10))); - dst.push_back(static_cast(0xDC00 + (cp & 0x3FF))); } - }; - auto cpToU8 = [](uint32_t cp, std::string& d){ - if (cp < 0x80) d += static_cast(cp); - else if (cp < 0x800) { d += static_cast(0xC0 | (cp >> 6)); d += static_cast(0x80 | (cp & 0x3F)); } - else if (cp < 0x10000) { d += static_cast(0xE0 | (cp >> 12)); d += static_cast(0x80 | ((cp >> 6) & 0x3F)); d += static_cast(0x80 | (cp & 0x3F)); } - else { d += static_cast(0xF0 | (cp >> 18)); d += static_cast(0x80 | ((cp >> 12) & 0x3F)); d += static_cast(0x80 | ((cp >> 6) & 0x3F)); d += static_cast(0x80 | (cp & 0x3F)); } - }; - double penX = e; - size_t i = 0; - while (i < unicodeCodepoints.size()) { - bool cov = isCov(unicodeCodepoints[i]); - std::vector seg; std::string seg8; - while (i < unicodeCodepoints.size() && isCov(unicodeCodepoints[i]) == cov) { - cpToU16(unicodeCodepoints[i], seg); cpToU8(unicodeCodepoints[i], seg8); ++i; - } - seg.push_back(0); - FPDF_FONT segFont = cov ? reconFont : font; - fonts::FontFace* segFace = cov ? (haveReconFace ? &reconFace : nullptr) - : (haveSubFace ? &subFace : nullptr); - FPDF_PAGEOBJECT obj = FPDFPageObj_CreateTextObj(doc_, segFont, static_cast(fontSize)); - if (obj) { - FPDFPageObj_SetFillColor(obj, r, g, b_color, a_color); - FPDFTextObj_SetTextRenderMode(obj, renderMode); - FPDFText_SetText(obj, reinterpret_cast(seg.data())); - FPDFPageObj_Transform(obj, a, b, c, d, penX, f); - FPDFPage_InsertObjectAtIndex(page, obj, minIndex); - } - double adv = segWidthPage(segFace, seg8); - if (adv <= 0.0) { // shaping unavailable -> fall back to ink bbox - if (obj) { float l=0,bo=0,rr=0,tt=0; if (FPDFPageObj_GetBounds(obj,&l,&bo,&rr,&tt)) adv = rr - l; } - } - penX += adv; - } - didHybrid = true; - spdlog::info("Tier-2: replace_text HYBRID emission for '{}' (mixed embedded/substitute)", internalFontId); - } - if (!didHybrid) - { - double aScale = a; - if (disableJustify && hasOrigBounds && axisAligned) { - double newW = measuredWidth(utf16); - double rowTol = (std::max)(5.0, (origTop - origBottom) * 0.5); - double nextLeft = 1e18; - int nObj = FPDFPage_CountObjects(page); - for (int k = 0; k < nObj; ++k) { - if (std::find(objectIndices.begin(), objectIndices.end(), k) != objectIndices.end()) continue; - FPDF_PAGEOBJECT o = FPDFPage_GetObject(page, k); - if (!o || FPDFPageObj_GetType(o) != FPDF_PAGEOBJ_TEXT) continue; - float l = 0, bo = 0, rr = 0, tt = 0; - if (!FPDFPageObj_GetBounds(o, &l, &bo, &rr, &tt)) continue; - if (std::abs((bo + tt) / 2.0 - origCenterY) <= rowTol && l > origRight + 1.0 && l < nextLeft) { - nextLeft = l; - } - } - if (nextLeft < 1e17) { - double avail = nextLeft - e - 2.0; - if (avail > 1.0 && newW > avail) aScale = a * (avail / newW); - } - } - FPDF_PAGEOBJECT newTextObj = FPDFPageObj_CreateTextObj(doc_, font, static_cast(fontSize)); - if (newTextObj) { - FPDFPageObj_SetFillColor(newTextObj, r, g, b_color, a_color); - FPDFTextObj_SetTextRenderMode(newTextObj, renderMode); - FPDFText_SetText(newTextObj, reinterpret_cast(utf16.data())); - FPDFPageObj_Transform(newTextObj, aScale, b, c, d, e, f); - - FPDFPage_InsertObjectAtIndex(page, newTextObj, minIndex); - } else { - spdlog::error("Failed to create new text object"); - } - } - } - } - - if (!FPDFPage_GenerateContent(page)) { - spdlog::error("Failed to generate page content after replace_text"); - } - - FPDF_ClosePage(page); - } else if (type == "reflow_paragraph") { - if (!op.contains("data") || !op["data"].is_object()) { - spdlog::error("reflow_paragraph missing 'data'"); - return std::unexpected(EngineError::InvalidFormat); - } - auto data = op["data"]; - - struct RunStyle { std::string text; std::string internalFontId; double fontSize; unsigned int r, g, b; std::vector advances; }; - std::vector runs; - auto parseHex = [](const std::string& hex, unsigned int& r, unsigned int& g, unsigned int& b) { - r = 0; g = 0; b = 0; - if (hex.size() >= 7 && hex[0] == '#') { - auto hv = [](char ch) -> int { - if (ch >= '0' && ch <= '9') return ch - '0'; - if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10; - if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10; - return 0; - }; - r = static_cast(hv(hex[1]) * 16 + hv(hex[2])); - g = static_cast(hv(hex[3]) * 16 + hv(hex[4])); - b = static_cast(hv(hex[5]) * 16 + hv(hex[6])); - } - }; - auto parseRun = [&](const nlohmann::json& rj) { - RunStyle rs; - rs.text = rj.value("text", ""); - rs.internalFontId = rj.value("internalFontId", ""); - rs.fontSize = rj.value("fontSize", 12.0); - parseHex(rj.value("color", std::string("#000000")), rs.r, rs.g, rs.b); - if (rj.contains("advances") && rj["advances"].is_array()) { - for (const auto& a : rj["advances"]) rs.advances.push_back(a.get()); - } - runs.push_back(std::move(rs)); - }; - std::vector> providedLines; - bool hasProvidedLines = data.contains("lines") && data["lines"].is_array() && !data["lines"].empty(); - if (hasProvidedLines) { - for (const auto& lineJson : data["lines"]) { - std::vector lineRunIdxs; - if (lineJson.is_array()) { - for (const auto& rj : lineJson) { lineRunIdxs.push_back(static_cast(runs.size())); parseRun(rj); } - } - providedLines.push_back(std::move(lineRunIdxs)); - } - } else if (data.contains("runs") && data["runs"].is_array()) { - for (const auto& rj : data["runs"]) parseRun(rj); - } - std::vector objectIndices; - if (data.contains("objectIndices") && data["objectIndices"].is_array()) { - for (auto& idx : data["objectIndices"]) objectIndices.push_back(idx.get()); - } - double columnLeft = data.value("columnLeft", 0.0); - double columnRight = data.value("columnRight", 0.0); - double firstBaselineY = data.value("firstBaselineY", 0.0); - double leading = data.value("leading", 0.0); - int oldLineCount = data.value("oldLineCount", 1); - std::string align = data.value("align", std::string("left")); - std::vector lineBaselineY, lineX; - if (data.contains("lineBaselineY") && data["lineBaselineY"].is_array()) - for (const auto& v : data["lineBaselineY"]) lineBaselineY.push_back(v.get()); - if (data.contains("lineX") && data["lineX"].is_array()) - for (const auto& v : data["lineX"]) lineX.push_back(v.get()); - double columnWidth = columnRight - columnLeft; - double pushColumnLeft = data.value("pushColumnLeft", columnLeft); - std::string paraId; - if (data.contains("paraId") && data["paraId"].is_string()) - paraId = data["paraId"].get(); - - if (runs.empty() || objectIndices.empty() || columnWidth <= 1.0 || leading <= 0.0) { - spdlog::warn("reflow_paragraph: insufficient layout data, skipping"); - continue; - } - - FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); - if (!page) { - spdlog::error("Failed to load page {} for reflow_paragraph", pageIndex); - return std::unexpected(EngineError::Unknown); - } - - PageBand startBand = repagComputeBand(page, pushColumnLeft, columnRight); - double bottomLimitY = startBand.valid ? startBand.bottomLimitY : -1e18; - std::vector anchoredCentersStart = - startBand.valid ? repagAnchoredCenters(page, pushColumnLeft, columnRight, bottomLimitY) - : std::vector{}; - - if (!paraId.empty()) { - int cont = repagRemoveContinuations(doc_, pageIndex, paraId, pushColumnLeft, columnRight); - if (cont > 0) - spdlog::info("reflow_paragraph: removed continuation of paraId={} on {} page(s)", paraId, cont); - } - - std::vector paragraphSet = objectIndices; - { - float ul = 0, ub = 0, ur = 0, ut = 0; bool haveUnion = false; - for (int idx : objectIndices) { - FPDF_PAGEOBJECT o = FPDFPage_GetObject(page, idx); - if (!o) continue; - float l = 0, b = 0, r = 0, t = 0; - if (!FPDFPageObj_GetBounds(o, &l, &b, &r, &t)) continue; - if (!haveUnion) { ul = l; ub = b; ur = r; ut = t; haveUnion = true; } - else { - if (l < ul) ul = l; if (b < ub) ub = b; - if (r > ur) ur = r; if (t > ut) ut = t; - } - } - if (haveUnion) { - const float eps = 0.5f; - int nObjs = FPDFPage_CountObjects(page); - int adopted = 0; - for (int k = 0; k < nObjs; ++k) { - if (std::find(objectIndices.begin(), objectIndices.end(), k) != objectIndices.end()) continue; - FPDF_PAGEOBJECT o = FPDFPage_GetObject(page, k); - if (!o || FPDFPageObj_GetType(o) != FPDF_PAGEOBJ_TEXT) continue; - float l = 0, b = 0, r = 0, t = 0; - if (!FPDFPageObj_GetBounds(o, &l, &b, &r, &t)) continue; - if (l >= ul - eps && b >= ub - eps && r <= ur + eps && t <= ut + eps) { - paragraphSet.push_back(k); - adopted++; - spdlog::debug("reflow_paragraph: adopted leftover text object idx={} inside paragraph bbox (not in objectIndices) -> prevents bulge/merge + font substitution", k); - } - } - if (adopted > 0) - spdlog::debug("reflow_paragraph: geometric backstop adopted {} object(s) the model omitted", adopted); - } - } - - if (!paraId.empty()) { - int nObjs = FPDFPage_CountObjects(page); - int adoptedById = 0; - for (int k = 0; k < nObjs; ++k) { - if (std::find(paragraphSet.begin(), paragraphSet.end(), k) != paragraphSet.end()) continue; - FPDF_PAGEOBJECT o = FPDFPage_GetObject(page, k); - if (o && repagGetParaId(o) == paraId) { paragraphSet.push_back(k); adoptedById++; } - } - if (adoptedById > 0) - spdlog::debug("reflow_paragraph: adopted {} anchor-page object(s) by paraId", adoptedById); - } - - { - std::unordered_map exactByBaseName; - std::vector exactSizes; - for (int idx : paragraphSet) { - FPDF_PAGEOBJECT o = FPDFPage_GetObject(page, idx); - if (!o || FPDFPageObj_GetType(o) != FPDF_PAGEOBJ_TEXT) continue; - FS_MATRIX mtx; - if (!FPDFPageObj_GetMatrix(o, &mtx)) continue; - float nominal = 0.0f; - if (!FPDFTextObj_GetFontSize(o, &nominal)) continue; - double scale = std::sqrt(static_cast(mtx.a) * mtx.a + - static_cast(mtx.b) * mtx.b); - double exact = static_cast(nominal) * scale; - if (exact <= 0.1) continue; - exactSizes.push_back(exact); - FPDF_FONT fo = FPDFTextObj_GetFont(o); - if (!fo) continue; - size_t nl = FPDFFont_GetBaseFontName(fo, nullptr, 0); - if (nl == 0) continue; - std::vector nb(nl); - if (FPDFFont_GetBaseFontName(fo, nb.data(), nl) == 0) continue; - std::string bn(nb.data()); - if (!exactByBaseName.count(bn)) exactByBaseName[bn] = exact; - } - double paraExact = 0.0; - if (!exactSizes.empty()) { - std::sort(exactSizes.begin(), exactSizes.end()); - paraExact = exactSizes[exactSizes.size() / 2]; - } - if (paraExact > 0.1) { - for (auto& rs : runs) { - std::string bn = baseNameFromInternalFontId(rs.internalFontId); - auto it = exactByBaseName.find(bn); - rs.fontSize = (it != exactByBaseName.end() && it->second > 0.1) ? it->second : paraExact; - } - spdlog::info("reflow_paragraph: size-exact override paraExact={:.2f} ({} font(s))", - paraExact, exactByBaseName.size()); - } - } - - std::vector runFonts(runs.size()); - auto toCodepoints = [](const std::string& s) { - auto u16 = utf8_to_utf16le(s); - std::vector cps; - for (size_t i = 0; i < u16.size();) { - uint32_t cp = u16[i]; - if (cp >= 0xD800 && cp <= 0xDBFF && i + 1 < u16.size()) { - uint32_t low = u16[i + 1]; - if (low >= 0xDC00 && low <= 0xDFFF) { cp = 0x10000 + ((cp - 0xD800) << 10) + (low - 0xDC00); i += 2; } - else i += 1; - } else i += 1; - cps.push_back(cp); - } - return cps; - }; - { - std::unordered_map> fontCps; - for (const auto& rs : runs) { - auto cps = toCodepoints(rs.text); - auto& dst = fontCps[rs.internalFontId]; - dst.insert(dst.end(), cps.begin(), cps.end()); - } - auto resolveOrigFont = [&](const std::string& fid) -> FPDF_FONT { - const std::string expected = baseNameFromInternalFontId(fid); - for (int idx : paragraphSet) { - FPDF_PAGEOBJECT o = FPDFPage_GetObject(page, idx); - if (!o || FPDFPageObj_GetType(o) != FPDF_PAGEOBJ_TEXT) continue; - FPDF_FONT fo = FPDFTextObj_GetFont(o); - if (!fo) continue; - size_t nl = FPDFFont_GetBaseFontName(fo, nullptr, 0); - if (nl == 0) continue; - std::vector nb(nl); - if (FPDFFont_GetBaseFontName(fo, nb.data(), nl) == 0) continue; - if (std::string(nb.data()) == expected) return fo; - } - return nullptr; - }; - std::unordered_map fontByFid; - for (const auto& rs : runs) { - if (!fontByFid.count(rs.internalFontId)) - fontByFid[rs.internalFontId] = loadEmissionFont(pageIndex, rs.internalFontId, rs.fontSize, fontCps[rs.internalFontId], paragraphSet, resolveOrigFont(rs.internalFontId)); - } - for (size_t ri = 0; ri < runs.size(); ++ri) runFonts[ri] = fontByFid[runs[ri].internalFontId]; - - for (size_t ri = 0; ri < runs.size(); ++ri) { - const fonts::FontFace* face = runFonts[ri].measureFace - ? runFonts[ri].measureFace.get() - : (runFonts[ri].resolved ? &runFonts[ri].resolved->getFontFace() : nullptr); - runs[ri].text = normalizeReflowText(runs[ri].text, face); - } - } - - fonts::HbShaper shaper; - constexpr unsigned int kRefSize = 1000; - auto perCharAdvances = [&](size_t runIdx, const std::string& text) -> std::vector { - std::vector out(text.size(), 0.0); - if (text.empty()) return out; - auto& rf = runFonts[runIdx]; - double size = runs[runIdx].fontSize > 0 ? runs[runIdx].fontSize : 12.0; - double scale = size / static_cast(kRefSize); - fonts::FontFace* face = rf.measureFace ? rf.measureFace.get() - : (rf.resolved ? &rf.resolved->getFontFace() : nullptr); - if (face) { - auto glyphs = shaper.shapeRun(text, *face, kRefSize); - if (glyphs.size() == text.size()) { - for (size_t i = 0; i < text.size(); ++i) out[i] = glyphs[i].advanceX * scale; - return out; - } - double w = 0.0; for (auto& gph : glyphs) w += gph.advanceX; - double per = w * scale / static_cast(text.size()); - for (auto& v : out) v = per; - return out; - } - for (auto& v : out) v = size * 0.5; - return out; - }; - - std::vector> runCharAdv(runs.size()); - std::vector runPerChar(runs.size(), 0); - for (size_t ri = 0; ri < runs.size(); ++ri) { - if (runs[ri].advances.size() == runs[ri].text.size() && !runs[ri].text.empty()) { - runCharAdv[ri] = runs[ri].advances; - auto natural = perCharAdvances(ri, runs[ri].text); - bool diverges = natural.size() != runCharAdv[ri].size(); - for (size_t c = 0; !diverges && c < natural.size(); ++c) - if (std::abs(natural[c] - runCharAdv[ri][c]) > 0.05) diverges = true; - runPerChar[ri] = diverges ? 1 : 0; - } else { - runCharAdv[ri] = perCharAdvances(ri, runs[ri].text); - } - } - auto charAdvAt = [&](int ri, size_t off) -> double { - const auto& v = runCharAdv[static_cast(ri)]; - return off < v.size() ? v[off] : 0.0; - }; - - auto isSpace = [](char ch) { return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'; }; - struct Seg { int runIdx; std::string text; double width; size_t off; }; - struct Word { std::vector segs; double width; double spaceAfter; }; - std::vector words; - - auto tokenize = [&](const std::vector& runIdxs) -> std::vector { - std::string ft; std::vector sof; std::vector cof; - for (int ri : runIdxs) { - size_t off = 0; - for (char ch : runs[ri].text) { ft.push_back(ch); sof.push_back(ri); cof.push_back(off++); } - } - std::vector out; - size_t i = 0; - while (i < ft.size()) { - if (isSpace(ft[i])) { i++; continue; } - Word w; w.width = 0.0; w.spaceAfter = 0.0; - while (i < ft.size() && !isSpace(ft[i])) { - int st = sof[i]; size_t segOff = cof[i]; std::string frag; double fw = 0.0; - while (i < ft.size() && !isSpace(ft[i]) && sof[i] == st) { frag.push_back(ft[i]); fw += charAdvAt(st, cof[i]); i++; } - w.segs.push_back({st, frag, fw, segOff}); w.width += fw; - } - while (i < ft.size() && isSpace(ft[i])) { w.spaceAfter += charAdvAt(sof[i], cof[i]); i++; } - out.push_back(words.size()); - words.push_back(std::move(w)); - } - return out; - }; - - std::vector> lines; - if (hasProvidedLines) { - for (const auto& lineRunIdxs : providedLines) { - auto wi = tokenize(lineRunIdxs); - if (!wi.empty()) lines.push_back(std::move(wi)); - } - } else { - std::vector allRuns(runs.size()); - for (size_t ri = 0; ri < runs.size(); ++ri) allRuns[ri] = static_cast(ri); - auto allWords = tokenize(allRuns); - std::vector cur; double curW = 0.0; - for (size_t k = 0; k < allWords.size(); ++k) { - size_t wi = allWords[k]; - double gap = cur.empty() ? 0.0 : words[allWords[k - 1]].spaceAfter; - if (!cur.empty() && curW + gap + words[wi].width > columnWidth) { - lines.push_back(cur); cur.clear(); - cur.push_back(wi); curW = words[wi].width; - } else { - cur.push_back(wi); curW += gap + words[wi].width; - } - } - if (!cur.empty()) lines.push_back(cur); - } - if (words.empty() || lines.empty()) { FPDF_ClosePage(page); continue; } - int newLineCount = static_cast(lines.size()); - - double deltaH = (newLineCount - oldLineCount) * leading; - double paragraphBottomBaseline = firstBaselineY - (oldLineCount - 1) * leading; - if (std::abs(deltaH) > 0.01) { - double threshold = paragraphBottomBaseline - 0.5 * leading; - int nObjs = FPDFPage_CountObjects(page); - int pushed = 0; - for (int k = 0; k < nObjs; ++k) { - if (std::find(paragraphSet.begin(), paragraphSet.end(), k) != paragraphSet.end()) continue; - FPDF_PAGEOBJECT o = FPDFPage_GetObject(page, k); - if (!o) continue; - float l = 0, bo = 0, rr = 0, tt = 0; - if (!FPDFPageObj_GetBounds(o, &l, &bo, &rr, &tt)) continue; - double cY = (bo + tt) / 2.0; - if (cY < threshold && cY >= bottomLimitY && rr > pushColumnLeft && l < columnRight) { - FPDFPageObj_Transform(o, 1.0, 0.0, 0.0, 1.0, 0.0, -deltaH); - pushed++; - } - } - spdlog::info("reflow_paragraph: lines {}->{}, deltaH={}, pushed {} objects", oldLineCount, newLineCount, deltaH, pushed); - } - - std::sort(paragraphSet.begin(), paragraphSet.end(), std::greater()); - int minIndex = paragraphSet.back(); - for (int idx : paragraphSet) { - FPDF_PAGEOBJECT o = FPDFPage_GetObject(page, idx); - if (o) { FPDFPage_RemoveObject(page, o); FPDFPageObj_Destroy(o); } - } - - nlohmann::json layoutLines = nlohmann::json::array(); - for (size_t li = 0; li < lines.size(); ++li) { - double baselineY = (li < lineBaselineY.size()) - ? lineBaselineY[li] : firstBaselineY - static_cast(li) * leading; - auto& lw = lines[li]; - double naturalW = 0.0; - for (size_t k = 0; k < lw.size(); ++k) { - naturalW += words[lw[k]].width; - if (k > 0) naturalW += words[lw[k - 1]].spaceAfter; - } - bool justifyThis = (align == "justify") && (li + 1 < lines.size()) && lw.size() > 1; - double extraPerGap = 0.0; - if (justifyThis) { double slack = columnWidth - naturalW; if (slack > 0) extraPerGap = slack / static_cast(lw.size() - 1); } - - std::string lineText; - std::vector adv; - double lineFontSize = 0.0; - double x = (li < lineX.size()) ? lineX[li] : columnLeft; - if (li >= lineX.size()) { - if (align == "right") x = columnLeft + (columnWidth - naturalW); - else if (align == "center") x = columnLeft + (columnWidth - naturalW) / 2.0; - } - const double lineStartX = x; - for (size_t k = 0; k < lw.size(); ++k) { - size_t wi = lw[k]; - if (k > 0) { - double gap = words[lw[k - 1]].spaceAfter + (justifyThis ? extraPerGap : 0.0); - x += gap; - lineText.push_back(' '); - adv.push_back(gap); - } - double segX = x; - for (auto& seg : words[wi].segs) { - if (lineFontSize <= 0.0) lineFontSize = runs[seg.runIdx].fontSize; - FPDF_FONT font = runFonts[seg.runIdx].font; - auto emitObj = [&](const std::string& s, double atX) { - if (!font || s.empty()) return; - FPDF_PAGEOBJECT obj = FPDFPageObj_CreateTextObj(doc_, font, static_cast(runs[seg.runIdx].fontSize)); - if (!obj) return; - FPDFPageObj_SetFillColor(obj, runs[seg.runIdx].r, runs[seg.runIdx].g, runs[seg.runIdx].b, 255); - auto u16 = utf8_to_utf16le(s); u16.push_back(0); - FPDFText_SetText(obj, reinterpret_cast(u16.data())); - FPDFPageObj_Transform(obj, 1.0, 0.0, 0.0, 1.0, atX, baselineY); - repagSetParaId(doc_, obj, paraId); - FPDFPage_InsertObjectAtIndex(page, obj, minIndex); - }; - if (runPerChar[seg.runIdx]) { - double gx = segX; - for (size_t c = 0; c < seg.text.size(); ++c) { - double a = charAdvAt(seg.runIdx, seg.off + c); - if (seg.text[c] != ' ') emitObj(std::string(1, seg.text[c]), gx); - lineText.push_back(seg.text[c]); adv.push_back(a); - gx += a; - } - } else { - emitObj(seg.text, segX); - for (size_t c = 0; c < seg.text.size(); ++c) { lineText.push_back(seg.text[c]); adv.push_back(charAdvAt(seg.runIdx, seg.off + c)); } - } - segX += seg.width; - } - x += words[wi].width; - } - layoutLines.push_back({ - {"baselineY", baselineY}, {"x0", lineStartX}, - {"fontSize", lineFontSize > 0 ? lineFontSize : leading / 1.2}, - {"text", lineText}, {"adv", adv}, - }); - } - - { - int pageOff = 0; double cur = 0.0; bool flowing = false; - for (auto& ln : layoutLines) { - double b = ln["baselineY"].get(); - if (!flowing && startBand.valid && b < startBand.bottomLimitY - 0.01) { - flowing = true; pageOff = 1; cur = startBand.placementTopY; - } - if (flowing) { - if (cur < startBand.bottomLimitY - 0.01) { pageOff++; cur = startBand.placementTopY; } - ln["baselineY"] = cur; cur -= leading; - } - ln["pageIndex"] = pageIndex + pageOff; - } - } - lastReflowLayout_ = nlohmann::json{ - {"columnLeft", columnLeft}, {"anchorPage", pageIndex}, {"lines", layoutLines}}.dump(); - - if (!FPDFPage_GenerateContent(page)) { - spdlog::error("Failed to generate page content after reflow_paragraph"); - } - FPDF_ClosePage(page); - - lastReflowOverflowed_ = false; - if (startBand.valid) { - int moved = 0; - int added = repaginateForward(doc_, pageIndex, columnLeft, columnRight, - pushColumnLeft, leading, anchoredCentersStart, &moved); - int removed = repaginateBackward(doc_, pageIndex, columnLeft, columnRight, - pushColumnLeft, leading); - lastReflowOverflowed_ = (moved > 0 || added > 0 || removed > 0); - if (added > 0 || removed > 0) - spdlog::info("reflow_paragraph: cross-page reflow added {} / removed {} page(s)", - added, removed); - } - } else if (type == "text_overlay" || type == "add_text") { - if (!op.contains("data") || !op["data"].is_object()) { - spdlog::error("text_overlay/add_text operation missing 'data' object"); - return std::unexpected(EngineError::InvalidFormat); - } - auto data = op["data"]; - std::string text = data.value("text", ""); - double x = data.value("x", 0.0); - double y = data.value("y", 0.0); - double fontSize = data.value("fontSize", 12.0); - double width = data.value("width", 200.0); - double height = data.value("height", fontSize * 1.5); - std::string fontFamily = data.value("fontFamily", "Helvetica"); - std::string color = data.value("color", "#000000"); - - if (text.empty()) { - continue; - } - - FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); - if (!page) { - spdlog::error("Failed to load page index {} for editing", pageIndex); - return std::unexpected(EngineError::Unknown); - } - - FPDF_PAGEOBJECT rectObj = FPDFPageObj_CreateNewRect( - static_cast(x), - static_cast(y), - static_cast(width), - static_cast(height) - ); - if (!rectObj) { - spdlog::error("Failed to create background white rectangle object"); - FPDF_ClosePage(page); - return std::unexpected(EngineError::Unknown); - } - FPDFPageObj_SetFillColor(rectObj, 255, 255, 255, 255); - FPDFPath_SetDrawMode(rectObj, FPDF_FILLMODE_WINDING, 0); - FPDFPage_InsertObject(page, rectObj); - - std::string standardFontName = "Helvetica"; - if (fontFamily == "Times New Roman" || fontFamily == "Times-Roman") { - standardFontName = "Times-Roman"; - } else if (fontFamily == "Courier New" || fontFamily == "Courier") { - standardFontName = "Courier"; - } else if (fontFamily == "Helvetica-Bold") { - standardFontName = "Helvetica-Bold"; - } else if (fontFamily == "Helvetica-Oblique") { - standardFontName = "Helvetica-Oblique"; - } else if (fontFamily == "Helvetica-BoldOblique") { - standardFontName = "Helvetica-BoldOblique"; - } else if (fontFamily == "Times-Bold") { - standardFontName = "Times-Bold"; - } else if (fontFamily == "Times-Italic") { - standardFontName = "Times-Italic"; - } else if (fontFamily == "Times-BoldItalic") { - standardFontName = "Times-BoldItalic"; - } else if (fontFamily == "Courier-Bold") { - standardFontName = "Courier-Bold"; - } else if (fontFamily == "Courier-Oblique") { - standardFontName = "Courier-Oblique"; - } else if (fontFamily == "Courier-BoldOblique") { - standardFontName = "Courier-BoldOblique"; - } - - FPDF_FONT font = FPDFText_LoadStandardFont(doc_, standardFontName.c_str()); - if (!font) { - spdlog::warn("Failed to load standard font {}, falling back to Helvetica", standardFontName); - font = FPDFText_LoadStandardFont(doc_, "Helvetica"); - } - - FPDF_PAGEOBJECT textObj = FPDFPageObj_CreateTextObj(doc_, font, static_cast(fontSize)); - if (!textObj) { - spdlog::error("Failed to create text object"); - FPDF_ClosePage(page); - return std::unexpected(EngineError::Unknown); - } - - unsigned int r = 0, g = 0, b = 0; - parseHexColor(color, r, g, b); - FPDFPageObj_SetFillColor(textObj, r, g, b, 255); - - auto utf16 = utf8_to_utf16le(text); - if (!FPDFText_SetText(textObj, reinterpret_cast(utf16.data()))) { - spdlog::error("Failed to set text object text"); - FPDFPageObj_Destroy(textObj); - FPDF_ClosePage(page); - return std::unexpected(EngineError::Unknown); - } - - FPDFPageObj_Transform(textObj, 1.0, 0.0, 0.0, 1.0, x, y); - - FPDFPage_InsertObject(page, textObj); - - if (!FPDFPage_GenerateContent(page)) { - spdlog::error("Failed to generate page content after editing"); - FPDF_ClosePage(page); - return std::unexpected(EngineError::Unknown); - } - - FPDF_ClosePage(page); - } else if (type == "underline" || type == "strikeout" || type == "squiggly") { - if (!op.contains("data") || !op["data"].is_object()) { - spdlog::error("{} operation missing 'data' object", type); - return std::unexpected(EngineError::InvalidFormat); - } - auto data = op["data"]; - - FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); - if (!page) { - spdlog::error("Failed to load page index {} for decoration", pageIndex); - return std::unexpected(EngineError::Unknown); - } - - int subtype = FPDF_ANNOT_UNDERLINE; - if (type == "strikeout") subtype = FPDF_ANNOT_STRIKEOUT; - else if (type == "squiggly") subtype = FPDF_ANNOT_SQUIGGLY; - - FPDF_ANNOTATION annot = FPDFPage_CreateAnnot(page, subtype); - if (!annot) { - spdlog::error("Failed to create {} annotation", type); - FPDF_ClosePage(page); - return std::unexpected(EngineError::Unknown); - } - - double pageHeight = FPDF_GetPageHeightF(page); - unsigned int r = 0, g = 0, b = 0; - parseHexColor(data.value("color", "#000000"), r, g, b); - FPDFAnnot_SetColor(annot, FPDFANNOT_COLORTYPE_Color, r, g, b, 255); - - if (data.contains("quadPoints") && data["quadPoints"].is_array()) { - FS_RECTF boundingBox; - boundingBox.left = 99999.0f; - boundingBox.right = -99999.0f; - boundingBox.top = -99999.0f; - boundingBox.bottom = 99999.0f; - - for (const auto& quad : data["quadPoints"]) { - FS_QUADPOINTSF points; - points.x1 = static_cast(quad.value("x1", 0.0)); - points.y1 = static_cast(pageHeight - quad.value("y1", 0.0)); - points.x2 = static_cast(quad.value("x2", 0.0)); - points.y2 = static_cast(pageHeight - quad.value("y2", 0.0)); - points.x3 = static_cast(quad.value("x3", 0.0)); - points.y3 = static_cast(pageHeight - quad.value("y3", 0.0)); - points.x4 = static_cast(quad.value("x4", 0.0)); - points.y4 = static_cast(pageHeight - quad.value("y4", 0.0)); - - FPDFAnnot_AppendAttachmentPoints(annot, &points); - - float minX = (std::min)({points.x1, points.x2, points.x3, points.x4}); - float maxX = (std::max)({points.x1, points.x2, points.x3, points.x4}); - float minY = (std::min)({points.y1, points.y2, points.y3, points.y4}); - float maxY = (std::max)({points.y1, points.y2, points.y3, points.y4}); - if (minX < boundingBox.left) boundingBox.left = minX; - if (maxX > boundingBox.right) boundingBox.right = maxX; - if (minY < boundingBox.bottom) boundingBox.bottom = minY; - if (maxY > boundingBox.top) boundingBox.top = maxY; - } - if (boundingBox.left <= boundingBox.right) { - FPDFAnnot_SetRect(annot, &boundingBox); - } - } - - std::string author = data.value("author", ""); - if (!author.empty()) { - auto utf16 = utf8_to_utf16le(author); - FPDFAnnot_SetStringValue(annot, "T", reinterpret_cast(utf16.data())); - } - - FPDFPage_CloseAnnot(annot); - FPDF_ClosePage(page); - } else if (type == "redaction") { - if (!op.contains("data") || !op["data"].is_object()) { - spdlog::error("redaction operation missing 'data' object"); - return std::unexpected(EngineError::InvalidFormat); - } - auto data = op["data"]; - double x = data.value("x", 0.0); - double y = data.value("y", 0.0); - double width = data.value("width", 0.0); - double height = data.value("height", 0.0); - std::string fillColor = data.value("fillColor", "#ffffff"); - - FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); - if (!page) { - spdlog::error("Failed to load page index {} for redaction", pageIndex); - return std::unexpected(EngineError::Unknown); - } - - int count = FPDFPage_CountObjects(page); - for (int i = count - 1; i >= 0; --i) { - FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, i); - if (obj) { - float left = 0, bottom = 0, right = 0, top = 0; - if (FPDFPageObj_GetBounds(obj, &left, &bottom, &right, &top)) { - if (!(left > x + width || right < x || bottom > y + height || top < y)) { - FPDFPage_RemoveObject(page, obj); - FPDFPageObj_Destroy(obj); - } - } - } - } - - FPDF_PAGEOBJECT rectObj = FPDFPageObj_CreateNewRect( - static_cast(x), - static_cast(y), - static_cast(width), - static_cast(height) - ); - if (!rectObj) { - spdlog::error("Failed to create redaction cover rectangle object"); - FPDF_ClosePage(page); - return std::unexpected(EngineError::Unknown); - } - - unsigned int r = 255, g = 255, b = 255; - parseHexColor(fillColor, r, g, b); - - FPDFPageObj_SetFillColor(rectObj, r, g, b, 255); - FPDFPath_SetDrawMode(rectObj, FPDF_FILLMODE_WINDING, 0); - FPDFPage_InsertObject(page, rectObj); - - if (!FPDFPage_GenerateContent(page)) { - spdlog::error("Failed to generate page content after redaction"); - FPDF_ClosePage(page); - return std::unexpected(EngineError::Unknown); - } - - FPDF_ClosePage(page); - } else if (type == "update_field") { - if (!op.contains("data") || !op["data"].is_object()) { - spdlog::error("update_field operation missing 'data' object"); - return std::unexpected(EngineError::InvalidFormat); - } - auto data = op["data"]; - const bool isBool = data["value"].is_boolean(); - const bool boolVal = isBool && data["value"].get(); - std::string strVal; - if (data["value"].is_string()) strVal = data["value"].get(); - else if (!isBool) { - spdlog::error("update_field value must be a string or boolean"); - return std::unexpected(EngineError::InvalidFormat); - } - - std::string targetId = data.value("annotationId", op.value("id", "")); - - FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); - if (!page) { - spdlog::error("Failed to load page index {} for update_field", pageIndex); - return std::unexpected(EngineError::Unknown); - } - - FPDF_FORMFILLINFO formInfo{}; - formInfo.version = 2; - FPDF_FORMHANDLE form = FPDFDOC_InitFormFillEnvironment(doc_, &formInfo); - - int count = FPDFPage_GetAnnotCount(page); - FPDF_ANNOTATION target = nullptr; - for (int i = 0; i < count; ++i) { - FPDF_ANNOTATION annot = FPDFPage_GetAnnot(page, i); - if (!annot) continue; - std::string id; - 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); - id = utf16le_to_utf8(reinterpret_cast(buf.data()), len / sizeof(char16_t)); - while (!id.empty() && id.back() == '\0') id.pop_back(); - } - if (id.empty()) id = "anno_" + std::to_string(pageIndex) + "_" + std::to_string(i); - if (id == targetId) { target = annot; break; } - FPDFPage_CloseAnnot(annot); - } - - if (target) { - int fieldType = form ? FPDFAnnot_GetFormFieldType(form, target) : -1; - if (form) FORM_OnAfterLoadPage(page, form); - - if (fieldType == 2 || fieldType == 3) { - std::string state = boolVal ? "Yes" : "Off"; - auto u = utf8_to_utf16le(state); - FPDFAnnot_SetStringValue(target, "V", reinterpret_cast(u.data())); - FPDFAnnot_SetStringValue(target, "AS", reinterpret_cast(u.data())); - } else if (form && FORM_SetFocusedAnnot(form, target)) { - if (fieldType == 4 || fieldType == 5) { - int optCount = FPDFAnnot_GetOptionCount(form, target); - int sel = -1; - for (int o = 0; o < optCount; ++o) { - unsigned long ol = FPDFAnnot_GetOptionLabel(form, target, o, nullptr, 0); - if (ol <= 2) continue; - std::vector ob(ol / 2); - FPDFAnnot_GetOptionLabel(form, target, o, ob.data(), ol); - std::string label = utf16le_to_utf8(reinterpret_cast(ob.data()), ob.size()); - while (!label.empty() && label.back() == '\0') label.pop_back(); - if (label == strVal) { sel = o; break; } - } - if (sel >= 0) { - FORM_SetIndexSelected(form, page, sel, 1); - } else { - auto u = utf8_to_utf16le(strVal); - FPDFAnnot_SetStringValue(target, "V", reinterpret_cast(u.data())); - } - } else { - FORM_SelectAllText(form, page); - auto u = utf8_to_utf16le(strVal); - FORM_ReplaceSelection(form, page, reinterpret_cast(u.data())); - } - FORM_ForceToKillFocus(form); - } else { - std::string v = isBool ? (boolVal ? "Yes" : "Off") : strVal; - auto u = utf8_to_utf16le(v); - FPDFAnnot_SetStringValue(target, "V", reinterpret_cast(u.data())); - } - - if (form) FORM_OnBeforeClosePage(page, form); - FPDFPage_CloseAnnot(target); - } else { - spdlog::warn("update_field: field '{}' not found on page {}", targetId, pageIndex); - } - - if (form) FPDFDOC_ExitFormFillEnvironment(form); - FPDF_ClosePage(page); - } else if (type == "image_overlay") { - if (!op.contains("data") || !op["data"].is_object()) { - spdlog::error("image_overlay operation missing 'data' object"); - return std::unexpected(EngineError::InvalidFormat); - } - auto data = op["data"]; - double x = data.value("x", 0.0); - double y = data.value("y", 0.0); - double width = data.value("width", 100.0); - double height = data.value("height", 100.0); - int pixelWidth = data.value("pixelWidth", 0); - int pixelHeight = data.value("pixelHeight", 0); - std::string rawPixelData = data.value("rawPixelData", ""); - std::string pixelDataPath = data.value("pixelDataPath", ""); - - std::vector decodedBytes; - if (!pixelDataPath.empty()) { - std::ifstream infile(pixelDataPath, std::ios::binary); - if (!infile) { - spdlog::error("Failed to open pixel data path: {}", pixelDataPath); - return std::unexpected(EngineError::Unknown); - } - infile.seekg(0, std::ios::end); - std::streamsize size = infile.tellg(); - infile.seekg(0, std::ios::beg); - decodedBytes.resize(static_cast(size)); - if (!infile.read(reinterpret_cast(decodedBytes.data()), size)) { - spdlog::error("Failed to read pixel data from path: {}", pixelDataPath); - return std::unexpected(EngineError::Unknown); - } - infile.close(); - std::error_code ec; - std::filesystem::remove(pixelDataPath, ec); - } else if (!rawPixelData.empty()) { - decodedBytes = base64Decode(rawPixelData); - } else { - spdlog::warn("image_overlay operation contains invalid raw pixels or dimensions"); - continue; - } - - if (decodedBytes.size() != static_cast(pixelWidth * pixelHeight * 4)) { - spdlog::error("Decoded image bytes size mismatch. Expected: {}, Got: {}", - pixelWidth * pixelHeight * 4, decodedBytes.size()); - return std::unexpected(EngineError::InvalidFormat); - } - - FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); - if (!page) { - spdlog::error("Failed to load page index {} for image insertion", pageIndex); - return std::unexpected(EngineError::Unknown); - } - - FPDF_PAGEOBJECT imgObj = FPDFPageObj_NewImageObj(doc_); - if (!imgObj) { - spdlog::error("Failed to create new image object"); - FPDF_ClosePage(page); - return std::unexpected(EngineError::Unknown); - } - - FPDF_BITMAP bitmap = FPDFBitmap_Create(pixelWidth, pixelHeight, 4); - if (!bitmap) { - spdlog::error("Failed to create FPDF_BITMAP"); - FPDFPageObj_Destroy(imgObj); - FPDF_ClosePage(page); - return std::unexpected(EngineError::Unknown); - } - - uint8_t* dest = static_cast(FPDFBitmap_GetBuffer(bitmap)); - std::memcpy(dest, decodedBytes.data(), decodedBytes.size()); - - if (!FPDFImageObj_SetBitmap(&page, 1, imgObj, bitmap)) { - spdlog::error("Failed to set bitmap on image object"); - FPDFBitmap_Destroy(bitmap); - FPDFPageObj_Destroy(imgObj); - FPDF_ClosePage(page); - return std::unexpected(EngineError::Unknown); - } - - FPDFPageObj_Transform(imgObj, width, 0.0, 0.0, height, x, y); - - FPDFPage_InsertObject(page, imgObj); - - if (!FPDFPage_GenerateContent(page)) { - spdlog::error("Failed to generate page content after image insertion"); - FPDFBitmap_Destroy(bitmap); - FPDF_ClosePage(page); - return std::unexpected(EngineError::Unknown); - } - - FPDFBitmap_Destroy(bitmap); - FPDF_ClosePage(page); - } else if (type == "highlight") { - if (!op.contains("data") || !op["data"].is_object()) { - spdlog::error("highlight operation missing 'data' object"); - return std::unexpected(EngineError::InvalidFormat); - } - auto data = op["data"]; - - FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); - if (!page) { - spdlog::error("Failed to load page index {} for highlight", pageIndex); - return std::unexpected(EngineError::Unknown); - } - - FPDF_ANNOTATION annot = FPDFPage_CreateAnnot(page, FPDF_ANNOT_HIGHLIGHT); - if (!annot) { - spdlog::error("Failed to create highlight annotation"); - FPDF_ClosePage(page); - return std::unexpected(EngineError::Unknown); - } - - double pageHeight = FPDF_GetPageHeightF(page); - - std::string colorStr = data.value("color", "#ffff00"); - unsigned int r = 255, g = 255, b = 0; - parseHexColor(colorStr, r, g, b); - FPDFAnnot_SetColor(annot, FPDFANNOT_COLORTYPE_Color, r, g, b, 255); - - if (data.contains("quadPoints") && data["quadPoints"].is_array()) { - FS_RECTF boundingBox; - boundingBox.left = 99999.0f; - boundingBox.right = -99999.0f; - boundingBox.top = -99999.0f; - boundingBox.bottom = 99999.0f; - - for (const auto& quad : data["quadPoints"]) { - FS_QUADPOINTSF points; - points.x1 = static_cast(quad.value("x1", 0.0)); - points.y1 = static_cast(pageHeight - quad.value("y1", 0.0)); - points.x2 = static_cast(quad.value("x2", 0.0)); - points.y2 = static_cast(pageHeight - quad.value("y2", 0.0)); - points.x3 = static_cast(quad.value("x3", 0.0)); - points.y3 = static_cast(pageHeight - quad.value("y3", 0.0)); - points.x4 = static_cast(quad.value("x4", 0.0)); - points.y4 = static_cast(pageHeight - quad.value("y4", 0.0)); - - FPDFAnnot_AppendAttachmentPoints(annot, &points); - - float minX = (std::min)({points.x1, points.x2, points.x3, points.x4}); - float maxX = (std::max)({points.x1, points.x2, points.x3, points.x4}); - float minY = (std::min)({points.y1, points.y2, points.y3, points.y4}); - float maxY = (std::max)({points.y1, points.y2, points.y3, points.y4}); - - if (minX < boundingBox.left) boundingBox.left = minX; - if (maxX > boundingBox.right) boundingBox.right = maxX; - if (minY < boundingBox.bottom) boundingBox.bottom = minY; - if (maxY > boundingBox.top) boundingBox.top = maxY; - } - - if (boundingBox.left <= boundingBox.right) { - FPDFAnnot_SetRect(annot, &boundingBox); - } - } - - std::string author = data.value("author", ""); - if (!author.empty()) { - auto utf16 = utf8_to_utf16le(author); - FPDFAnnot_SetStringValue(annot, "T", reinterpret_cast(utf16.data())); - } - - std::string content = data.value("content", ""); - if (!content.empty()) { - auto utf16 = utf8_to_utf16le(content); - FPDFAnnot_SetStringValue(annot, "Contents", reinterpret_cast(utf16.data())); - } - - FPDFPage_CloseAnnot(annot); - FPDF_ClosePage(page); - } else if (type == "free_text") { - spdlog::info("Parsed free_text edit operation (stub)"); - } else if (type == "comment") { - if (!op.contains("data") || !op["data"].is_object()) { - spdlog::error("comment operation missing 'data' object"); - return std::unexpected(EngineError::InvalidFormat); - } - auto data = op["data"]; - - FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); - if (!page) { - spdlog::error("Failed to load page index {} for comment", pageIndex); - return std::unexpected(EngineError::Unknown); - } - - FPDF_ANNOTATION annot = FPDFPage_CreateAnnot(page, FPDF_ANNOT_TEXT); - if (!annot) { - spdlog::error("Failed to create comment annotation"); - FPDF_ClosePage(page); - return std::unexpected(EngineError::Unknown); - } - - double pageHeight = FPDF_GetPageHeightF(page); - - double x = data.value("x", 0.0); - double y = data.value("y", 0.0); - - FS_RECTF rect; - rect.left = static_cast(x); - rect.right = static_cast(x + 24.0); - rect.top = static_cast(pageHeight - y); - rect.bottom = static_cast(pageHeight - y - 24.0); - - FPDFAnnot_SetRect(annot, &rect); - - std::string colorStr = data.value("color", "#ffeb3b"); - unsigned int r = 255, g = 235, b = 59; - parseHexColor(colorStr, r, g, b); - FPDFAnnot_SetColor(annot, FPDFANNOT_COLORTYPE_Color, r, g, b, 255); - - std::string author = data.value("author", ""); - if (!author.empty()) { - auto utf16 = utf8_to_utf16le(author); - FPDFAnnot_SetStringValue(annot, "T", reinterpret_cast(utf16.data())); - } - - std::string content = data.value("content", ""); - if (!content.empty()) { - auto utf16 = utf8_to_utf16le(content); - FPDFAnnot_SetStringValue(annot, "Contents", reinterpret_cast(utf16.data())); - } - - std::string timestamp = data.value("timestamp", ""); - if (!timestamp.empty()) { - auto utf16 = utf8_to_utf16le(timestamp); - FPDFAnnot_SetStringValue(annot, "M", reinterpret_cast(utf16.data())); - } - - FPDFPage_CloseAnnot(annot); - FPDF_ClosePage(page); - } else if (type == "freehand") { - if (!op.contains("data") || !op["data"].is_object()) { - spdlog::error("freehand operation missing 'data' object"); - return std::unexpected(EngineError::InvalidFormat); - } - auto data = op["data"]; - - FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); - if (!page) { - spdlog::error("Failed to load page index {} for freehand", pageIndex); - return std::unexpected(EngineError::Unknown); - } - - FPDF_ANNOTATION annot = FPDFPage_CreateAnnot(page, FPDF_ANNOT_INK); - if (!annot) { - spdlog::error("Failed to create ink annotation"); - FPDF_ClosePage(page); - return std::unexpected(EngineError::Unknown); - } - - double pageHeight = FPDF_GetPageHeightF(page); - - std::string colorStr = data.value("color", "#000000"); - unsigned int r = 0, g = 0, b = 0; - parseHexColor(colorStr, r, g, b); - FPDFAnnot_SetColor(annot, FPDFANNOT_COLORTYPE_Color, r, g, b, 255); - - float thickness = static_cast(data.value("thickness", 2.0)); - FPDFAnnot_SetBorder(annot, 0.0f, 0.0f, thickness); - - float minX = 1e9f, minY = 1e9f, maxX = -1e9f, maxY = -1e9f; - bool anyPoints = false; - - if (data.contains("paths") && data["paths"].is_array()) { - for (const auto& path : data["paths"]) { - if (!path.is_array() || path.size() < 2) continue; - std::vector pts; - pts.reserve(path.size()); - for (const auto& pt : path) { - float px = static_cast(pt.value("x", 0.0)); - float py = static_cast(pageHeight - pt.value("y", 0.0)); - pts.push_back(FS_POINTF{px, py}); - anyPoints = true; - minX = (std::min)(minX, px); maxX = (std::max)(maxX, px); - minY = (std::min)(minY, py); maxY = (std::max)(maxY, py); - } - if (pts.size() >= 2) { - FPDFAnnot_AddInkStroke(annot, pts.data(), pts.size()); - } - } - } - - if (anyPoints) { - float pad = thickness + 1.0f; - FS_RECTF rect; - rect.left = minX - pad; - rect.bottom = minY - pad; - rect.right = maxX + pad; - rect.top = maxY + pad; - FPDFAnnot_SetRect(annot, &rect); - } - - FPDFPage_CloseAnnot(annot); - FPDF_ClosePage(page); - } else if (type == "page_rotation") { - if (!op.contains("data") || !op["data"].is_object()) { - spdlog::error("page_rotation operation missing 'data' object"); - return std::unexpected(EngineError::InvalidFormat); - } - auto data = op["data"]; - int rotation = data.value("rotation", 0); - - FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); - if (!page) { - spdlog::error("Failed to load page index {} for page rotation", pageIndex); - return std::unexpected(EngineError::Unknown); - } - - int currentCode = FPDFPage_GetRotation(page); - int currentDegrees = currentCode * 90; - int newDegrees = currentDegrees + rotation; - newDegrees = (newDegrees % 360 + 360) % 360; - int newCode = newDegrees / 90; - - FPDFPage_SetRotation(page, newCode); - FPDF_ClosePage(page); - } else if (type == "page_deletion") { - if (pageCount() <= 1) { - spdlog::error("Cannot delete the only page in the document"); - return std::unexpected(EngineError::Unknown); - } - FPDFPage_Delete(doc_, pageIndex); - } else if (type == "page_reorder") { - if (!op.contains("data") || !op["data"].is_object()) { - spdlog::error("page_reorder operation missing 'data' object"); - return std::unexpected(EngineError::InvalidFormat); - } - auto data = op["data"]; - if (!data.contains("destPageIndex")) { - spdlog::error("page_reorder data missing 'destPageIndex'"); - return std::unexpected(EngineError::InvalidFormat); - } - int destPageIndex = data["destPageIndex"]; - if (destPageIndex < 0 || destPageIndex >= pageCount()) { - spdlog::error("Destination page index {} out of bounds (total pages: {})", destPageIndex, pageCount()); - return std::unexpected(EngineError::PageOutOfBounds); - } - - int fromIndex = pageIndex; - if (!FPDF_MovePages(doc_, &fromIndex, 1, destPageIndex)) { - spdlog::error("FPDF_MovePages failed from {} to {}", fromIndex, destPageIndex); - return std::unexpected(EngineError::Unknown); - } - } else if (type == "delete_annotation") { - if (!op.contains("data") || !op["data"].is_object()) { - spdlog::error("delete_annotation operation missing 'data' object"); - return std::unexpected(EngineError::InvalidFormat); - } - std::string targetId = op["data"].value("annotationId", ""); - if (targetId.empty()) { - spdlog::error("delete_annotation missing 'annotationId'"); - return std::unexpected(EngineError::InvalidFormat); - } - - FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); - if (!page) { - spdlog::error("Failed to load page index {} for delete_annotation", pageIndex); - return std::unexpected(EngineError::Unknown); - } - - int count = FPDFPage_GetAnnotCount(page); - int foundIndex = -1; - for (int i = 0; i < count; ++i) { - FPDF_ANNOTATION annot = FPDFPage_GetAnnot(page, i); - if (!annot) continue; - - std::string id; - 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); - id = utf16le_to_utf8(reinterpret_cast(buf.data()), len / sizeof(char16_t)); - while (!id.empty() && id.back() == '\0') id.pop_back(); - } - if (id.empty()) { - id = "anno_" + std::to_string(pageIndex) + "_" + std::to_string(i); - } - FPDFPage_CloseAnnot(annot); - - if (id == targetId) { foundIndex = i; break; } - } - - if (foundIndex >= 0) { - if (!FPDFPage_RemoveAnnot(page, foundIndex)) { - spdlog::error("FPDFPage_RemoveAnnot failed for index {}", foundIndex); - } - } else { - spdlog::warn("delete_annotation: annotation '{}' not found on page {}", targetId, pageIndex); - } - FPDF_ClosePage(page); - } else if (type == "update_annotation") { - if (!op.contains("data") || !op["data"].is_object()) { - spdlog::error("update_annotation operation missing 'data' object"); - return std::unexpected(EngineError::InvalidFormat); - } - auto data = op["data"]; - std::string targetId = data.value("annotationId", ""); - if (targetId.empty()) { - spdlog::error("update_annotation missing 'annotationId'"); - return std::unexpected(EngineError::InvalidFormat); - } - - FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); - if (!page) { - spdlog::error("Failed to load page index {} for update_annotation", pageIndex); - return std::unexpected(EngineError::Unknown); - } - - int count = FPDFPage_GetAnnotCount(page); - FPDF_ANNOTATION targetAnnot = nullptr; - for (int i = 0; i < count; ++i) { - FPDF_ANNOTATION annot = FPDFPage_GetAnnot(page, i); - if (!annot) continue; - - std::string id; - 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); - id = utf16le_to_utf8(reinterpret_cast(buf.data()), len / sizeof(char16_t)); - while (!id.empty() && id.back() == '\0') id.pop_back(); - } - if (id.empty()) { - id = "anno_" + std::to_string(pageIndex) + "_" + std::to_string(i); - } - - if (id == targetId) { - targetAnnot = annot; - break; - } - FPDFPage_CloseAnnot(annot); - } - - if (targetAnnot) { - if (data.contains("x") && data.contains("y") && data.contains("width") && data.contains("height")) { - double pageHeight = FPDF_GetPageHeightF(page); - float x = static_cast(data["x"].get()); - float y = static_cast(data["y"].get()); - float width = static_cast(data["width"].get()); - float height = static_cast(data["height"].get()); - - FS_RECTF rect; - rect.left = x; - rect.right = x + width; - rect.top = static_cast(pageHeight - y); - rect.bottom = static_cast(pageHeight - (y + height)); - FPDFAnnot_SetRect(targetAnnot, &rect); - } - - if (data.contains("color")) { - std::string colorStr = data["color"].get(); - unsigned int r = 0, g = 0, b = 0; - parseHexColor(colorStr, r, g, b); - FPDFAnnot_SetColor(targetAnnot, FPDFANNOT_COLORTYPE_Color, r, g, b, 255); - } - - if (data.contains("thickness")) { - float thickness = static_cast(data["thickness"].get()); - FPDFAnnot_SetBorder(targetAnnot, 0.0f, 0.0f, thickness); - } - - if (data.contains("text")) { - std::string text = data["text"].get(); - auto utf16 = utf8_to_utf16le(text); - FPDFAnnot_SetStringValue(targetAnnot, "Contents", reinterpret_cast(utf16.data())); - } - - FPDFPage_CloseAnnot(targetAnnot); - } else { - spdlog::warn("update_annotation: annotation '{}' not found on page {}", targetId, pageIndex); - } - FPDF_ClosePage(page); - } else { - spdlog::warn("Unsupported edit operation type: {}", type); - } - } - } catch (const nlohmann::json::parse_error& e) { - spdlog::error("JSON parse error in applyEdits: {}", e.what()); - return std::unexpected(EngineError::InvalidFormat); - } catch (const std::exception& e) { - spdlog::error("Exception in applyEdits: {}", e.what()); - return std::unexpected(EngineError::Unknown); - } - - invalidateCaches(); - return {}; -#else - (void)editsJson; - return std::unexpected(EngineError::Unknown); -#endif -} - std::expected, EngineError> PdfiumDocument::saveIncremental() const { #ifdef PDFENGINE_WITH_PDFIUM if (!doc_) { @@ -4287,470 +240,6 @@ std::expected, EngineError> PdfiumDocument::saveFull() cons #endif } -std::expected, EngineError> PdfiumPage::getFonts() const { -#ifdef PDFENGINE_WITH_PDFIUM - if (!page_) { - return std::unexpected(EngineError::Unknown); - } - ensureTextPageLoaded(); - if (!textPage_) { - return std::unexpected(EngineError::Unknown); - } - - auto fontPdfDataMap = buildFontPdfDataMap(page_, textPage_); - - std::vector pageFonts; - int charCount = FPDFText_CountChars(textPage_); - - if (charCount < 0 || charCount > 1000000) { - spdlog::error("Invalid or excessive character count in page ({}): aborting font extraction", charCount); - return pageFonts; - } - - auto getFontNameForChar = [this](int charIndex, int& flagsOut) -> std::string { - int flags = 0; - unsigned long len = FPDFText_GetFontInfo(textPage_, charIndex, nullptr, 0, &flags); - if (len > 0) { - std::vector buf(len); - if (FPDFText_GetFontInfo(textPage_, charIndex, buf.data(), len, &flags) > 0) { - flagsOut = flags; - return std::string(buf.data()); - } - } - return ""; - }; - - struct FontPositionStats { - int horizontalSteps = 0; - int verticalSteps = 0; - double totalFilteredDeltaX = 0.0; - double totalFilteredDeltaY = 0.0; - double prevX = 0.0, prevY = 0.0; - bool hasPrev = false; - }; - std::unordered_map fontStats; - - struct CharInfo { - std::string fontName; - int flags; - }; - std::vector charInfos(charCount); - - for (int i = 0; i < charCount; ++i) { - int flags = 0; - std::string fontName = getFontNameForChar(i, flags); - charInfos[i] = {fontName, flags}; - - if (!fontName.empty()) { - double ox = 0.0, oy = 0.0; - if (FPDFText_GetCharOrigin(textPage_, i, &ox, &oy)) { - auto& stats = fontStats[fontName]; - if (stats.hasPrev) { - double dx = std::abs(ox - stats.prevX); - double dy = std::abs(oy - stats.prevY); - double fontSize = FPDFText_GetFontSize(textPage_, i); - - double maxJump = (std::max)(fontSize * 3.0, 30.0); - - if (dx < maxJump && dy < maxJump) { - stats.totalFilteredDeltaX += dx; - stats.totalFilteredDeltaY += dy; - - if (dy > dx * 1.5) { - stats.verticalSteps++; - } else if (dx > dy * 1.5) { - stats.horizontalSteps++; - } - } - } - stats.prevX = ox; - stats.prevY = oy; - stats.hasPrev = true; - } - } - } - - std::unordered_map fontIsVertical; - for (const auto& [fname, stats] : fontStats) { - bool vertical = false; - - if (stats.verticalSteps > 0 || stats.horizontalSteps > 0) { - vertical = stats.verticalSteps > stats.horizontalSteps; - } else { - vertical = (stats.totalFilteredDeltaY > 2.0 * stats.totalFilteredDeltaX) && - (stats.totalFilteredDeltaY > 0.5); - } - - fontIsVertical[fname] = vertical; - } - - for (int i = 0; i < charCount; ++i) { - const std::string& fontName = charInfos[i].fontName; - if (fontName.empty()) { - continue; - } - - auto it = std::find_if(pageFonts.begin(), pageFonts.end(), [&](const FontInfo& f) { - return f.fontName == fontName; - }); - if (it != pageFonts.end()) { - continue; - } - - FontInfo f; - f.fontName = fontName; - f.flags = static_cast(charInfos[i].flags); - - deduceFontMetadata(f); - - auto pdfDataIt = fontPdfDataMap.find(fontName); - if (pdfDataIt != fontPdfDataMap.end()) { - const FontPdfData& pd = pdfDataIt->second; - - auto lname = fontName; - std::transform(lname.begin(), lname.end(), lname.begin(), [](unsigned char c) { return static_cast(::tolower(c)); }); - - f.isEmbedded = pd.isEmbedded; - - f.flags = static_cast(pd.flags); - - if (pd.ascent != 0.0) f.ascent = pd.ascent; - if (pd.descent != 0.0) f.descent = pd.descent; - - if (f.ascent > 0.0) { - if (lname.find("times") != std::string::npos) f.capHeight = 662.0; - else if (lname.find("courier") != std::string::npos) f.capHeight = 562.0; - else if (lname.find("symbol") != std::string::npos) f.capHeight = 673.0; - else if (lname.find("helvetica") != std::string::npos) f.capHeight = 728.0; - else f.capHeight = f.ascent * 0.71; - } - - if (lname.find("symbol") != std::string::npos) { - f.hasToUnicode = false; - } else { - f.hasToUnicode = pd.hasUnicodeMapping; - } - - if (f.isEmbedded) { - f.sourceType = "Embedded"; - f.substitutedFrom = ""; - f.substitutedTo = ""; - } - - f.internalFontId = makeInternalFontId(f); - - spdlog::debug( - "Font '{}': isEmbedded={} type='{}' ascent={:.1f} descent={:.1f} " - "capHeight={:.1f} hasToUnicode={} flags={}", - fontName, f.isEmbedded, f.type, f.ascent, f.descent, - f.capHeight, f.hasToUnicode, f.flags); - } - - if (!f.isVertical) { - auto vit = fontIsVertical.find(fontName); - if (vit != fontIsVertical.end() && vit->second) { - f.isVertical = true; - if (f.encoding == "WinAnsiEncoding" || f.encoding == "Identity-H") { - f.encoding = "Identity-V"; - f.cmapName = "Identity-V"; - } - spdlog::info("Vertical writing mode detected for font '{}' via character position analysis", fontName); - } - } - - pageFonts.push_back(f); - } - - std::sort(pageFonts.begin(), pageFonts.end(), [](const FontInfo& a, const FontInfo& b) { - if (a.normalizedFamily != b.normalizedFamily) { - return a.normalizedFamily < b.normalizedFamily; - } - if (a.fontName != b.fontName) { - return a.fontName < b.fontName; - } - if (a.encoding != b.encoding) { - return a.encoding < b.encoding; - } - return a.type < b.type; - }); - - return pageFonts; -#else - return std::unexpected(EngineError::Unknown); -#endif -} - -std::expected, EngineError> PdfiumDocument::getFonts(int startPage, int endPage) const { -#ifdef PDFENGINE_WITH_PDFIUM - if (!doc_) { - return std::unexpected(EngineError::Unknown); - } - - std::lock_guard lock(fontsMutex_); - - int total = pageCount(); - if (startPage < 0) startPage = 0; - if (endPage < 0 || endPage >= total) endPage = total - 1; - if (startPage > endPage) { - return std::vector(); - } - - - if (startPage == 0 && endPage == total - 1 && hasCachedFonts_) { - return cachedFonts_; - } - - std::vector aggregated; - for (int i = startPage; i <= endPage; ++i) { - auto pageRes = const_cast(this)->getPage(i); - if (!pageRes) { - spdlog::error("Failed to load page index {} for font diagnostics", i); - continue; - } - - auto pageFontsRes = pageRes.value()->getFonts(); - if (pageFontsRes) { - for (const auto& f : *pageFontsRes) { - auto it = std::find_if(aggregated.begin(), aggregated.end(), [&](const FontInfo& existing) { - return existing.fontName == f.fontName; - }); - if (it == aggregated.end()) { - aggregated.push_back(f); - } - } - } - } - - std::sort(aggregated.begin(), aggregated.end(), [](const FontInfo& a, const FontInfo& b) { - if (a.normalizedFamily != b.normalizedFamily) { - return a.normalizedFamily < b.normalizedFamily; - } - if (a.fontName != b.fontName) { - return a.fontName < b.fontName; - } - if (a.encoding != b.encoding) { - return a.encoding < b.encoding; - } - return a.type < b.type; - }); - - if (startPage == 0 && endPage == total - 1) { - cachedFonts_ = aggregated; - hasCachedFonts_ = true; - } - - return aggregated; -#else - (void)startPage; - (void)endPage; - return std::unexpected(EngineError::Unknown); -#endif -} - -std::optional> -PdfiumDocument::getFontDataFromObjects(int pageIndex, const std::vector& objectIndices, - const std::string& internalFontId) const { -#ifdef PDFENGINE_WITH_PDFIUM - ensure_pdfium_initialized(); - if (!doc_ || objectIndices.empty()) return std::nullopt; - const std::string expected = baseNameFromInternalFontId(internalFontId); - FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); - if (!page) return std::nullopt; - std::optional> result; - for (int idx : objectIndices) { - FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, idx); - if (!obj || FPDFPageObj_GetType(obj) != FPDF_PAGEOBJ_TEXT) continue; - FPDF_FONT font = FPDFTextObj_GetFont(obj); - if (!font) continue; - size_t nameLen = FPDFFont_GetBaseFontName(font, nullptr, 0); - if (nameLen == 0) continue; - std::vector nameBuf(nameLen); - if (FPDFFont_GetBaseFontName(font, nameBuf.data(), nameLen) == 0) continue; - if (std::string(nameBuf.data()) != expected) continue; - size_t buflen = 0; - FPDFFont_GetFontData(font, nullptr, 0, &buflen); - if (buflen == 0) continue; - std::vector buffer(buflen); - size_t actualLen = 0; - if (FPDFFont_GetFontData(font, buffer.data(), buflen, &actualLen) && actualLen > 0) { - result = std::move(buffer); - break; - } - } - FPDF_ClosePage(page); - return result; -#else - (void)pageIndex; (void)objectIndices; (void)internalFontId; - return std::nullopt; -#endif -} - -std::expected, EngineError> PdfiumDocument::getFontData(const std::string& internalFontId) const { -#ifdef PDFENGINE_WITH_PDFIUM - ensure_pdfium_initialized(); - if (!doc_) { - return std::unexpected(EngineError::Unknown); - } - - std::string expectedFontName = baseNameFromInternalFontId(internalFontId); - - int numPages = FPDF_GetPageCount(doc_); - - int startPage = 0; - { - std::lock_guard lock(fontsMutex_); - - if (fontDataCache_.count(expectedFontName)) { - const auto& cachedBuf = fontDataCache_[expectedFontName]; - if (!cachedBuf.empty()) { - return cachedBuf; - } else { - return std::unexpected(EngineError::FileNotFound); - } - } - startPage = fontDataScannedPages_; - } - - for (int i = startPage; i < numPages; ++i) { - FPDF_PAGE page = FPDF_LoadPage(doc_, i); - if (!page) { - std::lock_guard lock(fontsMutex_); - fontDataScannedPages_ = i + 1; - continue; - } - - int objectCount = FPDFPage_CountObjects(page); - for (int j = 0; j < objectCount; ++j) { - FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, j); - if (!obj || FPDFPageObj_GetType(obj) != FPDF_PAGEOBJ_TEXT) continue; - - FPDF_FONT font = FPDFTextObj_GetFont(obj); - if (!font) continue; - - size_t nameLen = FPDFFont_GetBaseFontName(font, nullptr, 0); - if (nameLen > 0) { - std::vector nameBuf(nameLen); - if (FPDFFont_GetBaseFontName(font, nameBuf.data(), nameLen) > 0) { - std::string fontName(nameBuf.data()); - - std::lock_guard lock(fontsMutex_); - - if (fontDataCache_.find(fontName) == fontDataCache_.end()) { - size_t buflen = 0; - FPDFFont_GetFontData(font, nullptr, 0, &buflen); - if (buflen > 0) { - std::vector buffer(buflen); - size_t actual_len = 0; - if (FPDFFont_GetFontData(font, buffer.data(), buflen, &actual_len)) { - fontDataCache_[fontName] = buffer; - } else { - fontDataCache_[fontName] = std::vector(); - } - } else { - fontDataCache_[fontName] = std::vector(); - } - } - - if (fontName == expectedFontName) { - const auto& cachedBuf = fontDataCache_[fontName]; - if (!cachedBuf.empty()) { - FPDF_ClosePage(page); - fontDataScannedPages_ = i; - return cachedBuf; - } - } - } - } - } - FPDF_ClosePage(page); - - std::lock_guard lock(fontsMutex_); - fontDataScannedPages_ = i + 1; - } - - { - std::lock_guard lock(fontsMutex_); - if (fontDataCache_.find(expectedFontName) == fontDataCache_.end()) { - fontDataCache_[expectedFontName] = std::vector(); - } - } - return std::unexpected(EngineError::FileNotFound); -#else - (void)internalFontId; - return std::unexpected(EngineError::Unknown); -#endif -} - -std::expected, std::string> PdfiumDocument::getResolvedFont(const FontInfo& fontInfo) { -#ifdef PDFENGINE_WITH_PDFIUM - std::lock_guard lock(resolvedFontsMutex_); - - if (resolvedFontsCache_.count(fontInfo.internalFontId)) { - return resolvedFontsCache_[fontInfo.internalFontId]; - } - - if (!fontResolver_) { - fontResolver_ = std::make_unique<::pdfengine::fonts::loader::FontResolver>(shared_from_this()); - } - - auto result = fontResolver_->resolveFont(fontInfo); - if (!result) { - return std::unexpected(result.error()); - } - - std::shared_ptr sharedFont(std::move(result.value())); - resolvedFontsCache_[fontInfo.internalFontId] = sharedFont; - return sharedFont; -#else - return std::unexpected(std::string("EngineError::Unknown")); -#endif -} - -std::expected PdfiumPage::extractDisplayListJson() const { -#if defined(PDFENGINE_WITH_PDFIUM) && defined(PDFENGINE_WITH_QPDF) - if (!ownerDoc_) return std::unexpected(EngineError::Unknown); - const auto& buffer = ownerDoc_->getMemoryBuffer(); - if (buffer.empty()) return std::unexpected(EngineError::FileNotFound); - - qpdf_layer::QpdfExtractor extractor; - auto stream = extractor.extractPageStreamFromMemory(buffer, pageIndex_); - if (!stream) return std::unexpected(EngineError::InvalidFormat); - - Lexer lexer(stream->decodedContent); - auto tokens = lexer.tokenize(); - ContentParser parser(tokens); - auto operations = parser.parse(); - - nlohmann::json j_array = nlohmann::json::array(); - for (const auto& op : operations) { - nlohmann::json j_op = nlohmann::json::object(); - j_op["op"] = op.op; - if (!op.operands.empty()) { - nlohmann::json j_args = nlohmann::json::array(); - for (const auto& arg : op.operands) { - if (arg->type == AstNodeType::Number) { - j_args.push_back(arg->numberValue); - } else if (arg->type == AstNodeType::Name || arg->type == AstNodeType::String) { - j_args.push_back(arg->stringValue); - } else { - j_args.push_back(""); - } - } - j_op["args"] = j_args; - } - j_array.push_back(j_op); - } - return j_array.dump(); -#else - return std::unexpected(EngineError::Unknown); -#endif -} - -std::expected, EngineError> PdfiumPage::extractImageXObject(const std::string& name) const { - (void)name; - return std::unexpected(EngineError::Unknown); // Stub for now -} - void PdfiumDocument::invalidateCaches() { { std::lock_guard lock(fontsMutex_); @@ -4775,4 +264,5 @@ void PdfiumDocument::invalidateCaches() { spdlog::info("Document caches have been invalidated."); } -} + +} \ No newline at end of file diff --git a/engine/src/parser/pdfium_document.hpp b/engine/src/parser/pdfium_document.hpp index 5511971..98dcd9a 100644 --- a/engine/src/parser/pdfium_document.hpp +++ b/engine/src/parser/pdfium_document.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include "fonts/pdf_fonts/embedded_font_reconstructor.hpp" namespace pdfengine::fonts::loader { class FontResolver; } namespace pdfengine::fonts { class FontFace; } @@ -156,6 +157,23 @@ private: #ifdef PDFENGINE_WITH_QPDF const fonts::pdf_fonts::ReconstructedFont* getReconstructedEmbeddedFont(const std::string& internalFontId); #endif + + std::expected applyOp_replaceText(const nlohmann::json& op, int pageIndex); + std::expected applyOp_reflow(const nlohmann::json& op, int pageIndex); + std::expected applyOp_textOverlay(const nlohmann::json& op, int pageIndex); + std::expected applyOp_decoration(const nlohmann::json& op, int pageIndex); + std::expected applyOp_redaction(const nlohmann::json& op, int pageIndex); + std::expected applyOp_updateField(const nlohmann::json& op, int pageIndex); + std::expected applyOp_imageOverlay(const nlohmann::json& op, int pageIndex); + std::expected applyOp_highlight(const nlohmann::json& op, int pageIndex); + std::expected applyOp_freeText(const nlohmann::json& op, int pageIndex); + std::expected applyOp_comment(const nlohmann::json& op, int pageIndex); + std::expected applyOp_freehand(const nlohmann::json& op, int pageIndex); + std::expected applyOp_pageRotation(const nlohmann::json& op, int pageIndex); + std::expected applyOp_pageDeletion(const nlohmann::json& op, int pageIndex); + std::expected applyOp_pageReorder(const nlohmann::json& op, int pageIndex); + std::expected applyOp_deleteAnnotation(const nlohmann::json& op, int pageIndex); + std::expected applyOp_updateAnnotation(const nlohmann::json& op, int pageIndex); #endif }; diff --git a/engine/src/parser/pdfium_edit.cpp b/engine/src/parser/pdfium_edit.cpp new file mode 100644 index 0000000..d583e9c --- /dev/null +++ b/engine/src/parser/pdfium_edit.cpp @@ -0,0 +1,89 @@ +#include "parser/pdfium_internal.hpp" + +namespace pdfengine::parser { + +std::expected PdfiumDocument::applyEdits(const std::string& editsJson) { +#ifdef PDFENGINE_WITH_PDFIUM + if (!doc_) { + return std::unexpected(EngineError::Unknown); + } + + lastReflowLayout_.clear(); + + try { + auto root = nlohmann::json::parse(editsJson); + if (!root.contains("version") || root["version"] != "1.0") { + spdlog::error("Invalid edits JSON: missing or unsupported version (expected '1.0')"); + return std::unexpected(EngineError::InvalidFormat); + } + if (!root.contains("operations") || !root["operations"].is_array()) { + spdlog::error("Invalid edits JSON: missing 'operations' array"); + return std::unexpected(EngineError::InvalidFormat); + } + + for (const auto& op : root["operations"]) { + std::string type = op.value("type", ""); + int pageIndex = op.value("pageIndex", -1); + if (pageIndex < 0 || pageIndex >= pageCount()) { + spdlog::error("Page index {} out of bounds (total pages: {})", pageIndex, pageCount()); + return std::unexpected(EngineError::PageOutOfBounds); + } + + std::expected r{}; + if (type == "replace_text") { + r = applyOp_replaceText(op, pageIndex); + } else if (type == "reflow_paragraph") { + r = applyOp_reflow(op, pageIndex); + } else if (type == "text_overlay" || type == "add_text") { + r = applyOp_textOverlay(op, pageIndex); + } else if (type == "underline" || type == "strikeout" || type == "squiggly") { + r = applyOp_decoration(op, pageIndex); + } else if (type == "redaction") { + r = applyOp_redaction(op, pageIndex); + } else if (type == "update_field") { + r = applyOp_updateField(op, pageIndex); + } else if (type == "image_overlay") { + r = applyOp_imageOverlay(op, pageIndex); + } else if (type == "highlight") { + r = applyOp_highlight(op, pageIndex); + } else if (type == "free_text") { + r = applyOp_freeText(op, pageIndex); + } else if (type == "comment") { + r = applyOp_comment(op, pageIndex); + } else if (type == "freehand") { + r = applyOp_freehand(op, pageIndex); + } else if (type == "page_rotation") { + r = applyOp_pageRotation(op, pageIndex); + } else if (type == "page_deletion") { + r = applyOp_pageDeletion(op, pageIndex); + } else if (type == "page_reorder") { + r = applyOp_pageReorder(op, pageIndex); + } else if (type == "delete_annotation") { + r = applyOp_deleteAnnotation(op, pageIndex); + } else if (type == "update_annotation") { + r = applyOp_updateAnnotation(op, pageIndex); + } else { + spdlog::warn("Unsupported edit operation type: {}", type); + } + + if (!r) { + return std::unexpected(r.error()); + } + } + } catch (const nlohmann::json::parse_error& e) { + spdlog::error("JSON parse error in applyEdits: {}", e.what()); + return std::unexpected(EngineError::InvalidFormat); + } catch (const std::exception& e) { + spdlog::error("Exception in applyEdits: {}", e.what()); + return std::unexpected(EngineError::Unknown); + } + + invalidateCaches(); + return {}; +#else + (void)editsJson; + return std::unexpected(EngineError::Unknown); +#endif +} + +} \ No newline at end of file diff --git a/engine/src/parser/pdfium_edit_annotations.cpp b/engine/src/parser/pdfium_edit_annotations.cpp new file mode 100644 index 0000000..2061214 --- /dev/null +++ b/engine/src/parser/pdfium_edit_annotations.cpp @@ -0,0 +1,572 @@ +#include "parser/pdfium_internal.hpp" + +namespace pdfengine::parser { + +std::expected PdfiumDocument::applyOp_textOverlay(const nlohmann::json& op, int pageIndex) { +#ifdef PDFENGINE_WITH_PDFIUM + if (!op.contains("data") || !op["data"].is_object()) { + spdlog::error("text_overlay/add_text operation missing 'data' object"); + return std::unexpected(EngineError::InvalidFormat); + } + auto data = op["data"]; + std::string text = data.value("text", ""); + double x = data.value("x", 0.0); + double y = data.value("y", 0.0); + double fontSize = data.value("fontSize", 12.0); + double width = data.value("width", 200.0); + double height = data.value("height", fontSize * 1.5); + std::string fontFamily = data.value("fontFamily", "Helvetica"); + std::string color = data.value("color", "#000000"); + + if (text.empty()) { + return {}; + } + + FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); + if (!page) { + spdlog::error("Failed to load page index {} for editing", pageIndex); + return std::unexpected(EngineError::Unknown); + } + + FPDF_PAGEOBJECT rectObj = FPDFPageObj_CreateNewRect( + static_cast(x), + static_cast(y), + static_cast(width), + static_cast(height) + ); + if (!rectObj) { + spdlog::error("Failed to create background white rectangle object"); + FPDF_ClosePage(page); + return std::unexpected(EngineError::Unknown); + } + FPDFPageObj_SetFillColor(rectObj, 255, 255, 255, 255); + FPDFPath_SetDrawMode(rectObj, FPDF_FILLMODE_WINDING, 0); + FPDFPage_InsertObject(page, rectObj); + + std::string standardFontName = "Helvetica"; + if (fontFamily == "Times New Roman" || fontFamily == "Times-Roman") { + standardFontName = "Times-Roman"; + } else if (fontFamily == "Courier New" || fontFamily == "Courier") { + standardFontName = "Courier"; + } else if (fontFamily == "Helvetica-Bold") { + standardFontName = "Helvetica-Bold"; + } else if (fontFamily == "Helvetica-Oblique") { + standardFontName = "Helvetica-Oblique"; + } else if (fontFamily == "Helvetica-BoldOblique") { + standardFontName = "Helvetica-BoldOblique"; + } else if (fontFamily == "Times-Bold") { + standardFontName = "Times-Bold"; + } else if (fontFamily == "Times-Italic") { + standardFontName = "Times-Italic"; + } else if (fontFamily == "Times-BoldItalic") { + standardFontName = "Times-BoldItalic"; + } else if (fontFamily == "Courier-Bold") { + standardFontName = "Courier-Bold"; + } else if (fontFamily == "Courier-Oblique") { + standardFontName = "Courier-Oblique"; + } else if (fontFamily == "Courier-BoldOblique") { + standardFontName = "Courier-BoldOblique"; + } + + FPDF_FONT font = FPDFText_LoadStandardFont(doc_, standardFontName.c_str()); + if (!font) { + spdlog::warn("Failed to load standard font {}, falling back to Helvetica", standardFontName); + font = FPDFText_LoadStandardFont(doc_, "Helvetica"); + } + + FPDF_PAGEOBJECT textObj = FPDFPageObj_CreateTextObj(doc_, font, static_cast(fontSize)); + if (!textObj) { + spdlog::error("Failed to create text object"); + FPDF_ClosePage(page); + return std::unexpected(EngineError::Unknown); + } + + unsigned int r = 0, g = 0, b = 0; + parseHexColor(color, r, g, b); + FPDFPageObj_SetFillColor(textObj, r, g, b, 255); + + auto utf16 = utf8_to_utf16le(text); + if (!FPDFText_SetText(textObj, reinterpret_cast(utf16.data()))) { + spdlog::error("Failed to set text object text"); + FPDFPageObj_Destroy(textObj); + FPDF_ClosePage(page); + return std::unexpected(EngineError::Unknown); + } + + FPDFPageObj_Transform(textObj, 1.0, 0.0, 0.0, 1.0, x, y); + + FPDFPage_InsertObject(page, textObj); + + if (!FPDFPage_GenerateContent(page)) { + spdlog::error("Failed to generate page content after editing"); + FPDF_ClosePage(page); + return std::unexpected(EngineError::Unknown); + } + + FPDF_ClosePage(page); + return {}; +#else + (void)op; (void)pageIndex; + return std::unexpected(EngineError::Unknown); +#endif +} + +std::expected PdfiumDocument::applyOp_decoration(const nlohmann::json& op, int pageIndex) { +#ifdef PDFENGINE_WITH_PDFIUM + std::string type = op.value("type", ""); + if (!op.contains("data") || !op["data"].is_object()) { + spdlog::error("{} operation missing 'data' object", type); + return std::unexpected(EngineError::InvalidFormat); + } + auto data = op["data"]; + + FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); + if (!page) { + spdlog::error("Failed to load page index {} for decoration", pageIndex); + return std::unexpected(EngineError::Unknown); + } + + int subtype = FPDF_ANNOT_UNDERLINE; + if (type == "strikeout") subtype = FPDF_ANNOT_STRIKEOUT; + else if (type == "squiggly") subtype = FPDF_ANNOT_SQUIGGLY; + + FPDF_ANNOTATION annot = FPDFPage_CreateAnnot(page, subtype); + if (!annot) { + spdlog::error("Failed to create {} annotation", type); + FPDF_ClosePage(page); + return std::unexpected(EngineError::Unknown); + } + + double pageHeight = FPDF_GetPageHeightF(page); + unsigned int r = 0, g = 0, b = 0; + parseHexColor(data.value("color", "#000000"), r, g, b); + FPDFAnnot_SetColor(annot, FPDFANNOT_COLORTYPE_Color, r, g, b, 255); + + if (data.contains("quadPoints") && data["quadPoints"].is_array()) { + FS_RECTF boundingBox; + boundingBox.left = 99999.0f; + boundingBox.right = -99999.0f; + boundingBox.top = -99999.0f; + boundingBox.bottom = 99999.0f; + + for (const auto& quad : data["quadPoints"]) { + FS_QUADPOINTSF points; + points.x1 = static_cast(quad.value("x1", 0.0)); + points.y1 = static_cast(pageHeight - quad.value("y1", 0.0)); + points.x2 = static_cast(quad.value("x2", 0.0)); + points.y2 = static_cast(pageHeight - quad.value("y2", 0.0)); + points.x3 = static_cast(quad.value("x3", 0.0)); + points.y3 = static_cast(pageHeight - quad.value("y3", 0.0)); + points.x4 = static_cast(quad.value("x4", 0.0)); + points.y4 = static_cast(pageHeight - quad.value("y4", 0.0)); + + FPDFAnnot_AppendAttachmentPoints(annot, &points); + + float minX = (std::min)({points.x1, points.x2, points.x3, points.x4}); + float maxX = (std::max)({points.x1, points.x2, points.x3, points.x4}); + float minY = (std::min)({points.y1, points.y2, points.y3, points.y4}); + float maxY = (std::max)({points.y1, points.y2, points.y3, points.y4}); + if (minX < boundingBox.left) boundingBox.left = minX; + if (maxX > boundingBox.right) boundingBox.right = maxX; + if (minY < boundingBox.bottom) boundingBox.bottom = minY; + if (maxY > boundingBox.top) boundingBox.top = maxY; + } + if (boundingBox.left <= boundingBox.right) { + FPDFAnnot_SetRect(annot, &boundingBox); + } + } + + std::string author = data.value("author", ""); + if (!author.empty()) { + auto utf16 = utf8_to_utf16le(author); + FPDFAnnot_SetStringValue(annot, "T", reinterpret_cast(utf16.data())); + } + + FPDFPage_CloseAnnot(annot); + FPDF_ClosePage(page); + return {}; +#else + (void)op; (void)pageIndex; + return std::unexpected(EngineError::Unknown); +#endif +} + +std::expected PdfiumDocument::applyOp_highlight(const nlohmann::json& op, int pageIndex) { +#ifdef PDFENGINE_WITH_PDFIUM + if (!op.contains("data") || !op["data"].is_object()) { + spdlog::error("highlight operation missing 'data' object"); + return std::unexpected(EngineError::InvalidFormat); + } + auto data = op["data"]; + + FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); + if (!page) { + spdlog::error("Failed to load page index {} for highlight", pageIndex); + return std::unexpected(EngineError::Unknown); + } + + FPDF_ANNOTATION annot = FPDFPage_CreateAnnot(page, FPDF_ANNOT_HIGHLIGHT); + if (!annot) { + spdlog::error("Failed to create highlight annotation"); + FPDF_ClosePage(page); + return std::unexpected(EngineError::Unknown); + } + + double pageHeight = FPDF_GetPageHeightF(page); + + std::string colorStr = data.value("color", "#ffff00"); + unsigned int r = 255, g = 255, b = 0; + parseHexColor(colorStr, r, g, b); + FPDFAnnot_SetColor(annot, FPDFANNOT_COLORTYPE_Color, r, g, b, 255); + + if (data.contains("quadPoints") && data["quadPoints"].is_array()) { + FS_RECTF boundingBox; + boundingBox.left = 99999.0f; + boundingBox.right = -99999.0f; + boundingBox.top = -99999.0f; + boundingBox.bottom = 99999.0f; + + for (const auto& quad : data["quadPoints"]) { + FS_QUADPOINTSF points; + points.x1 = static_cast(quad.value("x1", 0.0)); + points.y1 = static_cast(pageHeight - quad.value("y1", 0.0)); + points.x2 = static_cast(quad.value("x2", 0.0)); + points.y2 = static_cast(pageHeight - quad.value("y2", 0.0)); + points.x3 = static_cast(quad.value("x3", 0.0)); + points.y3 = static_cast(pageHeight - quad.value("y3", 0.0)); + points.x4 = static_cast(quad.value("x4", 0.0)); + points.y4 = static_cast(pageHeight - quad.value("y4", 0.0)); + + FPDFAnnot_AppendAttachmentPoints(annot, &points); + + float minX = (std::min)({points.x1, points.x2, points.x3, points.x4}); + float maxX = (std::max)({points.x1, points.x2, points.x3, points.x4}); + float minY = (std::min)({points.y1, points.y2, points.y3, points.y4}); + float maxY = (std::max)({points.y1, points.y2, points.y3, points.y4}); + + if (minX < boundingBox.left) boundingBox.left = minX; + if (maxX > boundingBox.right) boundingBox.right = maxX; + if (minY < boundingBox.bottom) boundingBox.bottom = minY; + if (maxY > boundingBox.top) boundingBox.top = maxY; + } + + if (boundingBox.left <= boundingBox.right) { + FPDFAnnot_SetRect(annot, &boundingBox); + } + } + + std::string author = data.value("author", ""); + if (!author.empty()) { + auto utf16 = utf8_to_utf16le(author); + FPDFAnnot_SetStringValue(annot, "T", reinterpret_cast(utf16.data())); + } + + std::string content = data.value("content", ""); + if (!content.empty()) { + auto utf16 = utf8_to_utf16le(content); + FPDFAnnot_SetStringValue(annot, "Contents", reinterpret_cast(utf16.data())); + } + + FPDFPage_CloseAnnot(annot); + FPDF_ClosePage(page); + return {}; +#else + (void)op; (void)pageIndex; + return std::unexpected(EngineError::Unknown); +#endif +} + +std::expected PdfiumDocument::applyOp_freeText(const nlohmann::json& op, int pageIndex) { +#ifdef PDFENGINE_WITH_PDFIUM + spdlog::info("Parsed free_text edit operation (stub)"); + return {}; +#else + (void)op; (void)pageIndex; + return std::unexpected(EngineError::Unknown); +#endif +} + +std::expected PdfiumDocument::applyOp_comment(const nlohmann::json& op, int pageIndex) { +#ifdef PDFENGINE_WITH_PDFIUM + if (!op.contains("data") || !op["data"].is_object()) { + spdlog::error("comment operation missing 'data' object"); + return std::unexpected(EngineError::InvalidFormat); + } + auto data = op["data"]; + + FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); + if (!page) { + spdlog::error("Failed to load page index {} for comment", pageIndex); + return std::unexpected(EngineError::Unknown); + } + + FPDF_ANNOTATION annot = FPDFPage_CreateAnnot(page, FPDF_ANNOT_TEXT); + if (!annot) { + spdlog::error("Failed to create comment annotation"); + FPDF_ClosePage(page); + return std::unexpected(EngineError::Unknown); + } + + double pageHeight = FPDF_GetPageHeightF(page); + + double x = data.value("x", 0.0); + double y = data.value("y", 0.0); + + FS_RECTF rect; + rect.left = static_cast(x); + rect.right = static_cast(x + 24.0); + rect.top = static_cast(pageHeight - y); + rect.bottom = static_cast(pageHeight - y - 24.0); + + FPDFAnnot_SetRect(annot, &rect); + + std::string colorStr = data.value("color", "#ffeb3b"); + unsigned int r = 255, g = 235, b = 59; + parseHexColor(colorStr, r, g, b); + FPDFAnnot_SetColor(annot, FPDFANNOT_COLORTYPE_Color, r, g, b, 255); + + std::string author = data.value("author", ""); + if (!author.empty()) { + auto utf16 = utf8_to_utf16le(author); + FPDFAnnot_SetStringValue(annot, "T", reinterpret_cast(utf16.data())); + } + + std::string content = data.value("content", ""); + if (!content.empty()) { + auto utf16 = utf8_to_utf16le(content); + FPDFAnnot_SetStringValue(annot, "Contents", reinterpret_cast(utf16.data())); + } + + std::string timestamp = data.value("timestamp", ""); + if (!timestamp.empty()) { + auto utf16 = utf8_to_utf16le(timestamp); + FPDFAnnot_SetStringValue(annot, "M", reinterpret_cast(utf16.data())); + } + + FPDFPage_CloseAnnot(annot); + FPDF_ClosePage(page); + return {}; +#else + (void)op; (void)pageIndex; + return std::unexpected(EngineError::Unknown); +#endif +} + +std::expected PdfiumDocument::applyOp_freehand(const nlohmann::json& op, int pageIndex) { +#ifdef PDFENGINE_WITH_PDFIUM + if (!op.contains("data") || !op["data"].is_object()) { + spdlog::error("freehand operation missing 'data' object"); + return std::unexpected(EngineError::InvalidFormat); + } + auto data = op["data"]; + + FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); + if (!page) { + spdlog::error("Failed to load page index {} for freehand", pageIndex); + return std::unexpected(EngineError::Unknown); + } + + FPDF_ANNOTATION annot = FPDFPage_CreateAnnot(page, FPDF_ANNOT_INK); + if (!annot) { + spdlog::error("Failed to create ink annotation"); + FPDF_ClosePage(page); + return std::unexpected(EngineError::Unknown); + } + + double pageHeight = FPDF_GetPageHeightF(page); + + std::string colorStr = data.value("color", "#000000"); + unsigned int r = 0, g = 0, b = 0; + parseHexColor(colorStr, r, g, b); + FPDFAnnot_SetColor(annot, FPDFANNOT_COLORTYPE_Color, r, g, b, 255); + + float thickness = static_cast(data.value("thickness", 2.0)); + FPDFAnnot_SetBorder(annot, 0.0f, 0.0f, thickness); + + float minX = 1e9f, minY = 1e9f, maxX = -1e9f, maxY = -1e9f; + bool anyPoints = false; + + if (data.contains("paths") && data["paths"].is_array()) { + for (const auto& path : data["paths"]) { + if (!path.is_array() || path.size() < 2) continue; + std::vector pts; + pts.reserve(path.size()); + for (const auto& pt : path) { + float px = static_cast(pt.value("x", 0.0)); + float py = static_cast(pageHeight - pt.value("y", 0.0)); + pts.push_back(FS_POINTF{px, py}); + anyPoints = true; + minX = (std::min)(minX, px); maxX = (std::max)(maxX, px); + minY = (std::min)(minY, py); maxY = (std::max)(maxY, py); + } + if (pts.size() >= 2) { + FPDFAnnot_AddInkStroke(annot, pts.data(), pts.size()); + } + } + } + + if (anyPoints) { + float pad = thickness + 1.0f; + FS_RECTF rect; + rect.left = minX - pad; + rect.bottom = minY - pad; + rect.right = maxX + pad; + rect.top = maxY + pad; + FPDFAnnot_SetRect(annot, &rect); + } + + FPDFPage_CloseAnnot(annot); + FPDF_ClosePage(page); + return {}; +#else + (void)op; (void)pageIndex; + return std::unexpected(EngineError::Unknown); +#endif +} + +std::expected PdfiumDocument::applyOp_deleteAnnotation(const nlohmann::json& op, int pageIndex) { +#ifdef PDFENGINE_WITH_PDFIUM + if (!op.contains("data") || !op["data"].is_object()) { + spdlog::error("delete_annotation operation missing 'data' object"); + return std::unexpected(EngineError::InvalidFormat); + } + std::string targetId = op["data"].value("annotationId", ""); + if (targetId.empty()) { + spdlog::error("delete_annotation missing 'annotationId'"); + return std::unexpected(EngineError::InvalidFormat); + } + + FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); + if (!page) { + spdlog::error("Failed to load page index {} for delete_annotation", pageIndex); + return std::unexpected(EngineError::Unknown); + } + + int count = FPDFPage_GetAnnotCount(page); + int foundIndex = -1; + for (int i = 0; i < count; ++i) { + FPDF_ANNOTATION annot = FPDFPage_GetAnnot(page, i); + if (!annot) continue; + + std::string id; + 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); + id = utf16le_to_utf8(reinterpret_cast(buf.data()), len / sizeof(char16_t)); + while (!id.empty() && id.back() == '\0') id.pop_back(); + } + if (id.empty()) { + id = "anno_" + std::to_string(pageIndex) + "_" + std::to_string(i); + } + FPDFPage_CloseAnnot(annot); + + if (id == targetId) { foundIndex = i; break; } + } + + if (foundIndex >= 0) { + if (!FPDFPage_RemoveAnnot(page, foundIndex)) { + spdlog::error("FPDFPage_RemoveAnnot failed for index {}", foundIndex); + } + } else { + spdlog::warn("delete_annotation: annotation '{}' not found on page {}", targetId, pageIndex); + } + FPDF_ClosePage(page); + return {}; +#else + (void)op; (void)pageIndex; + return std::unexpected(EngineError::Unknown); +#endif +} + +std::expected PdfiumDocument::applyOp_updateAnnotation(const nlohmann::json& op, int pageIndex) { +#ifdef PDFENGINE_WITH_PDFIUM + if (!op.contains("data") || !op["data"].is_object()) { + spdlog::error("update_annotation operation missing 'data' object"); + return std::unexpected(EngineError::InvalidFormat); + } + auto data = op["data"]; + std::string targetId = data.value("annotationId", ""); + if (targetId.empty()) { + spdlog::error("update_annotation missing 'annotationId'"); + return std::unexpected(EngineError::InvalidFormat); + } + + FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); + if (!page) { + spdlog::error("Failed to load page index {} for update_annotation", pageIndex); + return std::unexpected(EngineError::Unknown); + } + + int count = FPDFPage_GetAnnotCount(page); + FPDF_ANNOTATION targetAnnot = nullptr; + for (int i = 0; i < count; ++i) { + FPDF_ANNOTATION annot = FPDFPage_GetAnnot(page, i); + if (!annot) continue; + + std::string id; + 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); + id = utf16le_to_utf8(reinterpret_cast(buf.data()), len / sizeof(char16_t)); + while (!id.empty() && id.back() == '\0') id.pop_back(); + } + if (id.empty()) { + id = "anno_" + std::to_string(pageIndex) + "_" + std::to_string(i); + } + + if (id == targetId) { + targetAnnot = annot; + break; + } + FPDFPage_CloseAnnot(annot); + } + + if (targetAnnot) { + if (data.contains("x") && data.contains("y") && data.contains("width") && data.contains("height")) { + double pageHeight = FPDF_GetPageHeightF(page); + float x = static_cast(data["x"].get()); + float y = static_cast(data["y"].get()); + float width = static_cast(data["width"].get()); + float height = static_cast(data["height"].get()); + + FS_RECTF rect; + rect.left = x; + rect.right = x + width; + rect.top = static_cast(pageHeight - y); + rect.bottom = static_cast(pageHeight - (y + height)); + FPDFAnnot_SetRect(targetAnnot, &rect); + } + + if (data.contains("color")) { + std::string colorStr = data["color"].get(); + unsigned int r = 0, g = 0, b = 0; + parseHexColor(colorStr, r, g, b); + FPDFAnnot_SetColor(targetAnnot, FPDFANNOT_COLORTYPE_Color, r, g, b, 255); + } + + if (data.contains("thickness")) { + float thickness = static_cast(data["thickness"].get()); + FPDFAnnot_SetBorder(targetAnnot, 0.0f, 0.0f, thickness); + } + + if (data.contains("text")) { + std::string text = data["text"].get(); + auto utf16 = utf8_to_utf16le(text); + FPDFAnnot_SetStringValue(targetAnnot, "Contents", reinterpret_cast(utf16.data())); + } + + FPDFPage_CloseAnnot(targetAnnot); + } else { + spdlog::warn("update_annotation: annotation '{}' not found on page {}", targetId, pageIndex); + } + FPDF_ClosePage(page); + return {}; +#else + (void)op; (void)pageIndex; + return std::unexpected(EngineError::Unknown); +#endif +} + +} \ No newline at end of file diff --git a/engine/src/parser/pdfium_edit_images.cpp b/engine/src/parser/pdfium_edit_images.cpp new file mode 100644 index 0000000..2de1464 --- /dev/null +++ b/engine/src/parser/pdfium_edit_images.cpp @@ -0,0 +1,170 @@ +#include "parser/pdfium_internal.hpp" + +namespace pdfengine::parser { + +std::expected PdfiumDocument::applyOp_imageOverlay(const nlohmann::json& op, int pageIndex) { +#ifdef PDFENGINE_WITH_PDFIUM + if (!op.contains("data") || !op["data"].is_object()) { + spdlog::error("image_overlay operation missing 'data' object"); + return std::unexpected(EngineError::InvalidFormat); + } + auto data = op["data"]; + double x = data.value("x", 0.0); + double y = data.value("y", 0.0); + double width = data.value("width", 100.0); + double height = data.value("height", 100.0); + int pixelWidth = data.value("pixelWidth", 0); + int pixelHeight = data.value("pixelHeight", 0); + std::string rawPixelData = data.value("rawPixelData", ""); + std::string pixelDataPath = data.value("pixelDataPath", ""); + + std::vector decodedBytes; + if (!pixelDataPath.empty()) { + std::ifstream infile(pixelDataPath, std::ios::binary); + if (!infile) { + spdlog::error("Failed to open pixel data path: {}", pixelDataPath); + return std::unexpected(EngineError::Unknown); + } + infile.seekg(0, std::ios::end); + std::streamsize size = infile.tellg(); + infile.seekg(0, std::ios::beg); + decodedBytes.resize(static_cast(size)); + if (!infile.read(reinterpret_cast(decodedBytes.data()), size)) { + spdlog::error("Failed to read pixel data from path: {}", pixelDataPath); + return std::unexpected(EngineError::Unknown); + } + infile.close(); + std::error_code ec; + std::filesystem::remove(pixelDataPath, ec); + } else if (!rawPixelData.empty()) { + decodedBytes = base64Decode(rawPixelData); + } else { + spdlog::warn("image_overlay operation contains invalid raw pixels or dimensions"); + return {}; + } + + if (decodedBytes.size() != static_cast(pixelWidth * pixelHeight * 4)) { + spdlog::error("Decoded image bytes size mismatch. Expected: {}, Got: {}", + pixelWidth * pixelHeight * 4, decodedBytes.size()); + return std::unexpected(EngineError::InvalidFormat); + } + + FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); + if (!page) { + spdlog::error("Failed to load page index {} for image insertion", pageIndex); + return std::unexpected(EngineError::Unknown); + } + + FPDF_PAGEOBJECT imgObj = FPDFPageObj_NewImageObj(doc_); + if (!imgObj) { + spdlog::error("Failed to create new image object"); + FPDF_ClosePage(page); + return std::unexpected(EngineError::Unknown); + } + + FPDF_BITMAP bitmap = FPDFBitmap_Create(pixelWidth, pixelHeight, 4); + if (!bitmap) { + spdlog::error("Failed to create FPDF_BITMAP"); + FPDFPageObj_Destroy(imgObj); + FPDF_ClosePage(page); + return std::unexpected(EngineError::Unknown); + } + + uint8_t* dest = static_cast(FPDFBitmap_GetBuffer(bitmap)); + std::memcpy(dest, decodedBytes.data(), decodedBytes.size()); + + if (!FPDFImageObj_SetBitmap(&page, 1, imgObj, bitmap)) { + spdlog::error("Failed to set bitmap on image object"); + FPDFBitmap_Destroy(bitmap); + FPDFPageObj_Destroy(imgObj); + FPDF_ClosePage(page); + return std::unexpected(EngineError::Unknown); + } + + FPDFPageObj_Transform(imgObj, width, 0.0, 0.0, height, x, y); + + FPDFPage_InsertObject(page, imgObj); + + if (!FPDFPage_GenerateContent(page)) { + spdlog::error("Failed to generate page content after image insertion"); + FPDFBitmap_Destroy(bitmap); + FPDF_ClosePage(page); + return std::unexpected(EngineError::Unknown); + } + + FPDFBitmap_Destroy(bitmap); + FPDF_ClosePage(page); + return {}; +#else + (void)op; (void)pageIndex; + return std::unexpected(EngineError::Unknown); +#endif +} + +std::expected PdfiumDocument::applyOp_redaction(const nlohmann::json& op, int pageIndex) { +#ifdef PDFENGINE_WITH_PDFIUM + if (!op.contains("data") || !op["data"].is_object()) { + spdlog::error("redaction operation missing 'data' object"); + return std::unexpected(EngineError::InvalidFormat); + } + auto data = op["data"]; + double x = data.value("x", 0.0); + double y = data.value("y", 0.0); + double width = data.value("width", 0.0); + double height = data.value("height", 0.0); + std::string fillColor = data.value("fillColor", "#ffffff"); + + FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); + if (!page) { + spdlog::error("Failed to load page index {} for redaction", pageIndex); + return std::unexpected(EngineError::Unknown); + } + + int count = FPDFPage_CountObjects(page); + for (int i = count - 1; i >= 0; --i) { + FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, i); + if (obj) { + float left = 0, bottom = 0, right = 0, top = 0; + if (FPDFPageObj_GetBounds(obj, &left, &bottom, &right, &top)) { + if (!(left > x + width || right < x || bottom > y + height || top < y)) { + FPDFPage_RemoveObject(page, obj); + FPDFPageObj_Destroy(obj); + } + } + } + } + + FPDF_PAGEOBJECT rectObj = FPDFPageObj_CreateNewRect( + static_cast(x), + static_cast(y), + static_cast(width), + static_cast(height) + ); + if (!rectObj) { + spdlog::error("Failed to create redaction cover rectangle object"); + FPDF_ClosePage(page); + return std::unexpected(EngineError::Unknown); + } + + unsigned int r = 255, g = 255, b = 255; + parseHexColor(fillColor, r, g, b); + + FPDFPageObj_SetFillColor(rectObj, r, g, b, 255); + FPDFPath_SetDrawMode(rectObj, FPDF_FILLMODE_WINDING, 0); + FPDFPage_InsertObject(page, rectObj); + + if (!FPDFPage_GenerateContent(page)) { + spdlog::error("Failed to generate page content after redaction"); + FPDF_ClosePage(page); + return std::unexpected(EngineError::Unknown); + } + + FPDF_ClosePage(page); + return {}; +#else + (void)op; (void)pageIndex; + return std::unexpected(EngineError::Unknown); +#endif +} + +} \ No newline at end of file diff --git a/engine/src/parser/pdfium_edit_pages.cpp b/engine/src/parser/pdfium_edit_pages.cpp new file mode 100644 index 0000000..0abeff0 --- /dev/null +++ b/engine/src/parser/pdfium_edit_pages.cpp @@ -0,0 +1,179 @@ +#include "parser/pdfium_internal.hpp" + +namespace pdfengine::parser { + +std::expected PdfiumDocument::applyOp_updateField(const nlohmann::json& op, int pageIndex) { +#ifdef PDFENGINE_WITH_PDFIUM + if (!op.contains("data") || !op["data"].is_object()) { + spdlog::error("update_field operation missing 'data' object"); + return std::unexpected(EngineError::InvalidFormat); + } + auto data = op["data"]; + const bool isBool = data["value"].is_boolean(); + const bool boolVal = isBool && data["value"].get(); + std::string strVal; + if (data["value"].is_string()) strVal = data["value"].get(); + else if (!isBool) { + spdlog::error("update_field value must be a string or boolean"); + return std::unexpected(EngineError::InvalidFormat); + } + + std::string targetId = data.value("annotationId", op.value("id", "")); + + FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); + if (!page) { + spdlog::error("Failed to load page index {} for update_field", pageIndex); + return std::unexpected(EngineError::Unknown); + } + + FPDF_FORMFILLINFO formInfo{}; + formInfo.version = 2; + FPDF_FORMHANDLE form = FPDFDOC_InitFormFillEnvironment(doc_, &formInfo); + + int count = FPDFPage_GetAnnotCount(page); + FPDF_ANNOTATION target = nullptr; + for (int i = 0; i < count; ++i) { + FPDF_ANNOTATION annot = FPDFPage_GetAnnot(page, i); + if (!annot) continue; + std::string id; + 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); + id = utf16le_to_utf8(reinterpret_cast(buf.data()), len / sizeof(char16_t)); + while (!id.empty() && id.back() == '\0') id.pop_back(); + } + if (id.empty()) id = "anno_" + std::to_string(pageIndex) + "_" + std::to_string(i); + if (id == targetId) { target = annot; break; } + FPDFPage_CloseAnnot(annot); + } + + if (target) { + int fieldType = form ? FPDFAnnot_GetFormFieldType(form, target) : -1; + if (form) FORM_OnAfterLoadPage(page, form); + + if (fieldType == 2 || fieldType == 3) { + std::string state = boolVal ? "Yes" : "Off"; + auto u = utf8_to_utf16le(state); + FPDFAnnot_SetStringValue(target, "V", reinterpret_cast(u.data())); + FPDFAnnot_SetStringValue(target, "AS", reinterpret_cast(u.data())); + } else if (form && FORM_SetFocusedAnnot(form, target)) { + if (fieldType == 4 || fieldType == 5) { + int optCount = FPDFAnnot_GetOptionCount(form, target); + int sel = -1; + for (int o = 0; o < optCount; ++o) { + unsigned long ol = FPDFAnnot_GetOptionLabel(form, target, o, nullptr, 0); + if (ol <= 2) continue; + std::vector ob(ol / 2); + FPDFAnnot_GetOptionLabel(form, target, o, ob.data(), ol); + std::string label = utf16le_to_utf8(reinterpret_cast(ob.data()), ob.size()); + while (!label.empty() && label.back() == '\0') label.pop_back(); + if (label == strVal) { sel = o; break; } + } + if (sel >= 0) { + FORM_SetIndexSelected(form, page, sel, 1); + } else { + auto u = utf8_to_utf16le(strVal); + FPDFAnnot_SetStringValue(target, "V", reinterpret_cast(u.data())); + } + } else { + FORM_SelectAllText(form, page); + auto u = utf8_to_utf16le(strVal); + FORM_ReplaceSelection(form, page, reinterpret_cast(u.data())); + } + FORM_ForceToKillFocus(form); + } else { + std::string v = isBool ? (boolVal ? "Yes" : "Off") : strVal; + auto u = utf8_to_utf16le(v); + FPDFAnnot_SetStringValue(target, "V", reinterpret_cast(u.data())); + } + + if (form) FORM_OnBeforeClosePage(page, form); + FPDFPage_CloseAnnot(target); + } else { + spdlog::warn("update_field: field '{}' not found on page {}", targetId, pageIndex); + } + + if (form) FPDFDOC_ExitFormFillEnvironment(form); + FPDF_ClosePage(page); + return {}; +#else + (void)op; (void)pageIndex; + return std::unexpected(EngineError::Unknown); +#endif +} + +std::expected PdfiumDocument::applyOp_pageRotation(const nlohmann::json& op, int pageIndex) { +#ifdef PDFENGINE_WITH_PDFIUM + if (!op.contains("data") || !op["data"].is_object()) { + spdlog::error("page_rotation operation missing 'data' object"); + return std::unexpected(EngineError::InvalidFormat); + } + auto data = op["data"]; + int rotation = data.value("rotation", 0); + + FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); + if (!page) { + spdlog::error("Failed to load page index {} for page rotation", pageIndex); + return std::unexpected(EngineError::Unknown); + } + + int currentCode = FPDFPage_GetRotation(page); + int currentDegrees = currentCode * 90; + int newDegrees = currentDegrees + rotation; + newDegrees = (newDegrees % 360 + 360) % 360; + int newCode = newDegrees / 90; + + FPDFPage_SetRotation(page, newCode); + FPDF_ClosePage(page); + return {}; +#else + (void)op; (void)pageIndex; + return std::unexpected(EngineError::Unknown); +#endif +} + +std::expected PdfiumDocument::applyOp_pageDeletion(const nlohmann::json& op, int pageIndex) { +#ifdef PDFENGINE_WITH_PDFIUM + if (pageCount() <= 1) { + spdlog::error("Cannot delete the only page in the document"); + return std::unexpected(EngineError::Unknown); + } + FPDFPage_Delete(doc_, pageIndex); + return {}; +#else + (void)op; (void)pageIndex; + return std::unexpected(EngineError::Unknown); +#endif +} + +std::expected PdfiumDocument::applyOp_pageReorder(const nlohmann::json& op, int pageIndex) { +#ifdef PDFENGINE_WITH_PDFIUM + if (!op.contains("data") || !op["data"].is_object()) { + spdlog::error("page_reorder operation missing 'data' object"); + return std::unexpected(EngineError::InvalidFormat); + } + auto data = op["data"]; + if (!data.contains("destPageIndex")) { + spdlog::error("page_reorder data missing 'destPageIndex'"); + return std::unexpected(EngineError::InvalidFormat); + } + int destPageIndex = data["destPageIndex"]; + if (destPageIndex < 0 || destPageIndex >= pageCount()) { + spdlog::error("Destination page index {} out of bounds (total pages: {})", destPageIndex, pageCount()); + return std::unexpected(EngineError::PageOutOfBounds); + } + + int fromIndex = pageIndex; + if (!FPDF_MovePages(doc_, &fromIndex, 1, destPageIndex)) { + spdlog::error("FPDF_MovePages failed from {} to {}", fromIndex, destPageIndex); + return std::unexpected(EngineError::Unknown); + } + return {}; +#else + (void)op; (void)pageIndex; + return std::unexpected(EngineError::Unknown); +#endif +} + +} \ No newline at end of file diff --git a/engine/src/parser/pdfium_edit_reflow.cpp b/engine/src/parser/pdfium_edit_reflow.cpp new file mode 100644 index 0000000..3ae1f59 --- /dev/null +++ b/engine/src/parser/pdfium_edit_reflow.cpp @@ -0,0 +1,472 @@ +#include "parser/pdfium_internal.hpp" + +namespace pdfengine::parser { + +std::expected PdfiumDocument::applyOp_reflow(const nlohmann::json& op, int pageIndex) { +#ifdef PDFENGINE_WITH_PDFIUM + if (!op.contains("data") || !op["data"].is_object()) { + spdlog::error("reflow_paragraph missing 'data'"); + return std::unexpected(EngineError::InvalidFormat); + } + auto data = op["data"]; + + struct RunStyle { std::string text; std::string internalFontId; double fontSize; unsigned int r, g, b; std::vector advances; }; + std::vector runs; + auto parseHex = [](const std::string& hex, unsigned int& r, unsigned int& g, unsigned int& b) { + r = 0; g = 0; b = 0; + if (hex.size() >= 7 && hex[0] == '#') { + auto hv = [](char ch) -> int { + if (ch >= '0' && ch <= '9') return ch - '0'; + if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10; + if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10; + return 0; + }; + r = static_cast(hv(hex[1]) * 16 + hv(hex[2])); + g = static_cast(hv(hex[3]) * 16 + hv(hex[4])); + b = static_cast(hv(hex[5]) * 16 + hv(hex[6])); + } + }; + auto parseRun = [&](const nlohmann::json& rj) { + RunStyle rs; + rs.text = rj.value("text", ""); + rs.internalFontId = rj.value("internalFontId", ""); + rs.fontSize = rj.value("fontSize", 12.0); + parseHex(rj.value("color", std::string("#000000")), rs.r, rs.g, rs.b); + if (rj.contains("advances") && rj["advances"].is_array()) { + for (const auto& a : rj["advances"]) rs.advances.push_back(a.get()); + } + runs.push_back(std::move(rs)); + }; + std::vector> providedLines; + bool hasProvidedLines = data.contains("lines") && data["lines"].is_array() && !data["lines"].empty(); + if (hasProvidedLines) { + for (const auto& lineJson : data["lines"]) { + std::vector lineRunIdxs; + if (lineJson.is_array()) { + for (const auto& rj : lineJson) { lineRunIdxs.push_back(static_cast(runs.size())); parseRun(rj); } + } + providedLines.push_back(std::move(lineRunIdxs)); + } + } else if (data.contains("runs") && data["runs"].is_array()) { + for (const auto& rj : data["runs"]) parseRun(rj); + } + std::vector objectIndices; + if (data.contains("objectIndices") && data["objectIndices"].is_array()) { + for (auto& idx : data["objectIndices"]) objectIndices.push_back(idx.get()); + } + double columnLeft = data.value("columnLeft", 0.0); + double columnRight = data.value("columnRight", 0.0); + double firstBaselineY = data.value("firstBaselineY", 0.0); + double leading = data.value("leading", 0.0); + int oldLineCount = data.value("oldLineCount", 1); + std::string align = data.value("align", std::string("left")); + std::vector lineBaselineY, lineX; + if (data.contains("lineBaselineY") && data["lineBaselineY"].is_array()) + for (const auto& v : data["lineBaselineY"]) lineBaselineY.push_back(v.get()); + if (data.contains("lineX") && data["lineX"].is_array()) + for (const auto& v : data["lineX"]) lineX.push_back(v.get()); + double columnWidth = columnRight - columnLeft; + double pushColumnLeft = data.value("pushColumnLeft", columnLeft); + std::string paraId; + if (data.contains("paraId") && data["paraId"].is_string()) + paraId = data["paraId"].get(); + + if (runs.empty() || objectIndices.empty() || columnWidth <= 1.0 || leading <= 0.0) { + spdlog::warn("reflow_paragraph: insufficient layout data, skipping"); + return {}; + } + + FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); + if (!page) { + spdlog::error("Failed to load page {} for reflow_paragraph", pageIndex); + return std::unexpected(EngineError::Unknown); + } + + PageBand startBand = repagComputeBand(page, pushColumnLeft, columnRight); + double bottomLimitY = startBand.valid ? startBand.bottomLimitY : -1e18; + std::vector anchoredCentersStart = + startBand.valid ? repagAnchoredCenters(page, pushColumnLeft, columnRight, bottomLimitY) + : std::vector{}; + + if (!paraId.empty()) { + int cont = repagRemoveContinuations(doc_, pageIndex, paraId, pushColumnLeft, columnRight); + if (cont > 0) + spdlog::info("reflow_paragraph: removed continuation of paraId={} on {} page(s)", paraId, cont); + } + + std::vector paragraphSet = objectIndices; + { + float ul = 0, ub = 0, ur = 0, ut = 0; bool haveUnion = false; + for (int idx : objectIndices) { + FPDF_PAGEOBJECT o = FPDFPage_GetObject(page, idx); + if (!o) continue; + float l = 0, b = 0, r = 0, t = 0; + if (!FPDFPageObj_GetBounds(o, &l, &b, &r, &t)) continue; + if (!haveUnion) { ul = l; ub = b; ur = r; ut = t; haveUnion = true; } + else { + if (l < ul) ul = l; if (b < ub) ub = b; + if (r > ur) ur = r; if (t > ut) ut = t; + } + } + if (haveUnion) { + const float eps = 0.5f; + int nObjs = FPDFPage_CountObjects(page); + int adopted = 0; + for (int k = 0; k < nObjs; ++k) { + if (std::find(objectIndices.begin(), objectIndices.end(), k) != objectIndices.end()) continue; + FPDF_PAGEOBJECT o = FPDFPage_GetObject(page, k); + if (!o || FPDFPageObj_GetType(o) != FPDF_PAGEOBJ_TEXT) continue; + float l = 0, b = 0, r = 0, t = 0; + if (!FPDFPageObj_GetBounds(o, &l, &b, &r, &t)) continue; + if (l >= ul - eps && b >= ub - eps && r <= ur + eps && t <= ut + eps) { + paragraphSet.push_back(k); + adopted++; + spdlog::debug("reflow_paragraph: adopted leftover text object idx={} inside paragraph bbox (not in objectIndices) -> prevents bulge/merge + font substitution", k); + } + } + if (adopted > 0) + spdlog::debug("reflow_paragraph: geometric backstop adopted {} object(s) the model omitted", adopted); + } + } + + if (!paraId.empty()) { + int nObjs = FPDFPage_CountObjects(page); + int adoptedById = 0; + for (int k = 0; k < nObjs; ++k) { + if (std::find(paragraphSet.begin(), paragraphSet.end(), k) != paragraphSet.end()) continue; + FPDF_PAGEOBJECT o = FPDFPage_GetObject(page, k); + if (o && repagGetParaId(o) == paraId) { paragraphSet.push_back(k); adoptedById++; } + } + if (adoptedById > 0) + spdlog::debug("reflow_paragraph: adopted {} anchor-page object(s) by paraId", adoptedById); + } + + { + std::unordered_map exactByBaseName; + std::vector exactSizes; + for (int idx : paragraphSet) { + FPDF_PAGEOBJECT o = FPDFPage_GetObject(page, idx); + if (!o || FPDFPageObj_GetType(o) != FPDF_PAGEOBJ_TEXT) continue; + FS_MATRIX mtx; + if (!FPDFPageObj_GetMatrix(o, &mtx)) continue; + float nominal = 0.0f; + if (!FPDFTextObj_GetFontSize(o, &nominal)) continue; + double scale = std::sqrt(static_cast(mtx.a) * mtx.a + + static_cast(mtx.b) * mtx.b); + double exact = static_cast(nominal) * scale; + if (exact <= 0.1) continue; + exactSizes.push_back(exact); + FPDF_FONT fo = FPDFTextObj_GetFont(o); + if (!fo) continue; + size_t nl = FPDFFont_GetBaseFontName(fo, nullptr, 0); + if (nl == 0) continue; + std::vector nb(nl); + if (FPDFFont_GetBaseFontName(fo, nb.data(), nl) == 0) continue; + std::string bn(nb.data()); + if (!exactByBaseName.count(bn)) exactByBaseName[bn] = exact; + } + double paraExact = 0.0; + if (!exactSizes.empty()) { + std::sort(exactSizes.begin(), exactSizes.end()); + paraExact = exactSizes[exactSizes.size() / 2]; + } + if (paraExact > 0.1) { + for (auto& rs : runs) { + std::string bn = baseNameFromInternalFontId(rs.internalFontId); + auto it = exactByBaseName.find(bn); + rs.fontSize = (it != exactByBaseName.end() && it->second > 0.1) ? it->second : paraExact; + } + spdlog::info("reflow_paragraph: size-exact override paraExact={:.2f} ({} font(s))", + paraExact, exactByBaseName.size()); + } + } + + std::vector runFonts(runs.size()); + auto toCodepoints = [](const std::string& s) { + auto u16 = utf8_to_utf16le(s); + std::vector cps; + for (size_t i = 0; i < u16.size();) { + uint32_t cp = u16[i]; + if (cp >= 0xD800 && cp <= 0xDBFF && i + 1 < u16.size()) { + uint32_t low = u16[i + 1]; + if (low >= 0xDC00 && low <= 0xDFFF) { cp = 0x10000 + ((cp - 0xD800) << 10) + (low - 0xDC00); i += 2; } + else i += 1; + } else i += 1; + cps.push_back(cp); + } + return cps; + }; + { + std::unordered_map> fontCps; + for (const auto& rs : runs) { + auto cps = toCodepoints(rs.text); + auto& dst = fontCps[rs.internalFontId]; + dst.insert(dst.end(), cps.begin(), cps.end()); + } + auto resolveOrigFont = [&](const std::string& fid) -> FPDF_FONT { + const std::string expected = baseNameFromInternalFontId(fid); + for (int idx : paragraphSet) { + FPDF_PAGEOBJECT o = FPDFPage_GetObject(page, idx); + if (!o || FPDFPageObj_GetType(o) != FPDF_PAGEOBJ_TEXT) continue; + FPDF_FONT fo = FPDFTextObj_GetFont(o); + if (!fo) continue; + size_t nl = FPDFFont_GetBaseFontName(fo, nullptr, 0); + if (nl == 0) continue; + std::vector nb(nl); + if (FPDFFont_GetBaseFontName(fo, nb.data(), nl) == 0) continue; + if (std::string(nb.data()) == expected) return fo; + } + return nullptr; + }; + std::unordered_map fontByFid; + for (const auto& rs : runs) { + if (!fontByFid.count(rs.internalFontId)) + fontByFid[rs.internalFontId] = loadEmissionFont(pageIndex, rs.internalFontId, rs.fontSize, fontCps[rs.internalFontId], paragraphSet, resolveOrigFont(rs.internalFontId)); + } + for (size_t ri = 0; ri < runs.size(); ++ri) runFonts[ri] = fontByFid[runs[ri].internalFontId]; + + for (size_t ri = 0; ri < runs.size(); ++ri) { + const fonts::FontFace* face = runFonts[ri].measureFace + ? runFonts[ri].measureFace.get() + : (runFonts[ri].resolved ? &runFonts[ri].resolved->getFontFace() : nullptr); + runs[ri].text = normalizeReflowText(runs[ri].text, face); + } + } + + fonts::HbShaper shaper; + constexpr unsigned int kRefSize = 1000; + auto perCharAdvances = [&](size_t runIdx, const std::string& text) -> std::vector { + std::vector out(text.size(), 0.0); + if (text.empty()) return out; + auto& rf = runFonts[runIdx]; + double size = runs[runIdx].fontSize > 0 ? runs[runIdx].fontSize : 12.0; + double scale = size / static_cast(kRefSize); + fonts::FontFace* face = rf.measureFace ? rf.measureFace.get() + : (rf.resolved ? &rf.resolved->getFontFace() : nullptr); + if (face) { + auto glyphs = shaper.shapeRun(text, *face, kRefSize); + if (glyphs.size() == text.size()) { + for (size_t i = 0; i < text.size(); ++i) out[i] = glyphs[i].advanceX * scale; + return out; + } + double w = 0.0; for (auto& gph : glyphs) w += gph.advanceX; + double per = w * scale / static_cast(text.size()); + for (auto& v : out) v = per; + return out; + } + for (auto& v : out) v = size * 0.5; + return out; + }; + + std::vector> runCharAdv(runs.size()); + std::vector runPerChar(runs.size(), 0); + for (size_t ri = 0; ri < runs.size(); ++ri) { + if (runs[ri].advances.size() == runs[ri].text.size() && !runs[ri].text.empty()) { + runCharAdv[ri] = runs[ri].advances; + auto natural = perCharAdvances(ri, runs[ri].text); + bool diverges = natural.size() != runCharAdv[ri].size(); + for (size_t c = 0; !diverges && c < natural.size(); ++c) + if (std::abs(natural[c] - runCharAdv[ri][c]) > 0.05) diverges = true; + runPerChar[ri] = diverges ? 1 : 0; + } else { + runCharAdv[ri] = perCharAdvances(ri, runs[ri].text); + } + } + auto charAdvAt = [&](int ri, size_t off) -> double { + const auto& v = runCharAdv[static_cast(ri)]; + return off < v.size() ? v[off] : 0.0; + }; + + auto isSpace = [](char ch) { return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'; }; + struct Seg { int runIdx; std::string text; double width; size_t off; }; + struct Word { std::vector segs; double width; double spaceAfter; }; + std::vector words; + + auto tokenize = [&](const std::vector& runIdxs) -> std::vector { + std::string ft; std::vector sof; std::vector cof; + for (int ri : runIdxs) { + size_t off = 0; + for (char ch : runs[ri].text) { ft.push_back(ch); sof.push_back(ri); cof.push_back(off++); } + } + std::vector out; + size_t i = 0; + while (i < ft.size()) { + if (isSpace(ft[i])) { i++; continue; } + Word w; w.width = 0.0; w.spaceAfter = 0.0; + while (i < ft.size() && !isSpace(ft[i])) { + int st = sof[i]; size_t segOff = cof[i]; std::string frag; double fw = 0.0; + while (i < ft.size() && !isSpace(ft[i]) && sof[i] == st) { frag.push_back(ft[i]); fw += charAdvAt(st, cof[i]); i++; } + w.segs.push_back({st, frag, fw, segOff}); w.width += fw; + } + while (i < ft.size() && isSpace(ft[i])) { w.spaceAfter += charAdvAt(sof[i], cof[i]); i++; } + out.push_back(words.size()); + words.push_back(std::move(w)); + } + return out; + }; + + std::vector> lines; + if (hasProvidedLines) { + for (const auto& lineRunIdxs : providedLines) { + auto wi = tokenize(lineRunIdxs); + if (!wi.empty()) lines.push_back(std::move(wi)); + } + } else { + std::vector allRuns(runs.size()); + for (size_t ri = 0; ri < runs.size(); ++ri) allRuns[ri] = static_cast(ri); + auto allWords = tokenize(allRuns); + std::vector cur; double curW = 0.0; + for (size_t k = 0; k < allWords.size(); ++k) { + size_t wi = allWords[k]; + double gap = cur.empty() ? 0.0 : words[allWords[k - 1]].spaceAfter; + if (!cur.empty() && curW + gap + words[wi].width > columnWidth) { + lines.push_back(cur); cur.clear(); + cur.push_back(wi); curW = words[wi].width; + } else { + cur.push_back(wi); curW += gap + words[wi].width; + } + } + if (!cur.empty()) lines.push_back(cur); + } + if (words.empty() || lines.empty()) { FPDF_ClosePage(page); return {}; } + int newLineCount = static_cast(lines.size()); + + double deltaH = (newLineCount - oldLineCount) * leading; + double paragraphBottomBaseline = firstBaselineY - (oldLineCount - 1) * leading; + if (std::abs(deltaH) > 0.01) { + double threshold = paragraphBottomBaseline - 0.5 * leading; + int nObjs = FPDFPage_CountObjects(page); + int pushed = 0; + for (int k = 0; k < nObjs; ++k) { + if (std::find(paragraphSet.begin(), paragraphSet.end(), k) != paragraphSet.end()) continue; + FPDF_PAGEOBJECT o = FPDFPage_GetObject(page, k); + if (!o) continue; + float l = 0, bo = 0, rr = 0, tt = 0; + if (!FPDFPageObj_GetBounds(o, &l, &bo, &rr, &tt)) continue; + double cY = (bo + tt) / 2.0; + if (cY < threshold && cY >= bottomLimitY && rr > pushColumnLeft && l < columnRight) { + FPDFPageObj_Transform(o, 1.0, 0.0, 0.0, 1.0, 0.0, -deltaH); + pushed++; + } + } + spdlog::info("reflow_paragraph: lines {}->{}, deltaH={}, pushed {} objects", oldLineCount, newLineCount, deltaH, pushed); + } + + std::sort(paragraphSet.begin(), paragraphSet.end(), std::greater()); + int minIndex = paragraphSet.back(); + for (int idx : paragraphSet) { + FPDF_PAGEOBJECT o = FPDFPage_GetObject(page, idx); + if (o) { FPDFPage_RemoveObject(page, o); FPDFPageObj_Destroy(o); } + } + + nlohmann::json layoutLines = nlohmann::json::array(); + for (size_t li = 0; li < lines.size(); ++li) { + double baselineY = (li < lineBaselineY.size()) + ? lineBaselineY[li] : firstBaselineY - static_cast(li) * leading; + auto& lw = lines[li]; + double naturalW = 0.0; + for (size_t k = 0; k < lw.size(); ++k) { + naturalW += words[lw[k]].width; + if (k > 0) naturalW += words[lw[k - 1]].spaceAfter; + } + bool justifyThis = (align == "justify") && (li + 1 < lines.size()) && lw.size() > 1; + double extraPerGap = 0.0; + if (justifyThis) { double slack = columnWidth - naturalW; if (slack > 0) extraPerGap = slack / static_cast(lw.size() - 1); } + + std::string lineText; + std::vector adv; + double lineFontSize = 0.0; + double x = (li < lineX.size()) ? lineX[li] : columnLeft; + if (li >= lineX.size()) { + if (align == "right") x = columnLeft + (columnWidth - naturalW); + else if (align == "center") x = columnLeft + (columnWidth - naturalW) / 2.0; + } + const double lineStartX = x; + for (size_t k = 0; k < lw.size(); ++k) { + size_t wi = lw[k]; + if (k > 0) { + double gap = words[lw[k - 1]].spaceAfter + (justifyThis ? extraPerGap : 0.0); + x += gap; + lineText.push_back(' '); + adv.push_back(gap); + } + double segX = x; + for (auto& seg : words[wi].segs) { + if (lineFontSize <= 0.0) lineFontSize = runs[seg.runIdx].fontSize; + FPDF_FONT font = runFonts[seg.runIdx].font; + auto emitObj = [&](const std::string& s, double atX) { + if (!font || s.empty()) return; + FPDF_PAGEOBJECT obj = FPDFPageObj_CreateTextObj(doc_, font, static_cast(runs[seg.runIdx].fontSize)); + if (!obj) return; + FPDFPageObj_SetFillColor(obj, runs[seg.runIdx].r, runs[seg.runIdx].g, runs[seg.runIdx].b, 255); + auto u16 = utf8_to_utf16le(s); u16.push_back(0); + FPDFText_SetText(obj, reinterpret_cast(u16.data())); + FPDFPageObj_Transform(obj, 1.0, 0.0, 0.0, 1.0, atX, baselineY); + repagSetParaId(doc_, obj, paraId); + FPDFPage_InsertObjectAtIndex(page, obj, minIndex); + }; + if (runPerChar[seg.runIdx]) { + double gx = segX; + for (size_t c = 0; c < seg.text.size(); ++c) { + double a = charAdvAt(seg.runIdx, seg.off + c); + if (seg.text[c] != ' ') emitObj(std::string(1, seg.text[c]), gx); + lineText.push_back(seg.text[c]); adv.push_back(a); + gx += a; + } + } else { + emitObj(seg.text, segX); + for (size_t c = 0; c < seg.text.size(); ++c) { lineText.push_back(seg.text[c]); adv.push_back(charAdvAt(seg.runIdx, seg.off + c)); } + } + segX += seg.width; + } + x += words[wi].width; + } + layoutLines.push_back({ + {"baselineY", baselineY}, {"x0", lineStartX}, + {"fontSize", lineFontSize > 0 ? lineFontSize : leading / 1.2}, + {"text", lineText}, {"adv", adv}, + }); + } + + { + int pageOff = 0; double cur = 0.0; bool flowing = false; + for (auto& ln : layoutLines) { + double b = ln["baselineY"].get(); + if (!flowing && startBand.valid && b < startBand.bottomLimitY - 0.01) { + flowing = true; pageOff = 1; cur = startBand.placementTopY; + } + if (flowing) { + if (cur < startBand.bottomLimitY - 0.01) { pageOff++; cur = startBand.placementTopY; } + ln["baselineY"] = cur; cur -= leading; + } + ln["pageIndex"] = pageIndex + pageOff; + } + } + lastReflowLayout_ = nlohmann::json{ + {"columnLeft", columnLeft}, {"anchorPage", pageIndex}, {"lines", layoutLines}}.dump(); + + if (!FPDFPage_GenerateContent(page)) { + spdlog::error("Failed to generate page content after reflow_paragraph"); + } + FPDF_ClosePage(page); + + lastReflowOverflowed_ = false; + if (startBand.valid) { + int moved = 0; + int added = repaginateForward(doc_, pageIndex, columnLeft, columnRight, + pushColumnLeft, leading, anchoredCentersStart, &moved); + int removed = repaginateBackward(doc_, pageIndex, columnLeft, columnRight, + pushColumnLeft, leading); + lastReflowOverflowed_ = (moved > 0 || added > 0 || removed > 0); + if (added > 0 || removed > 0) + spdlog::info("reflow_paragraph: cross-page reflow added {} / removed {} page(s)", + added, removed); + } + return {}; +#else + (void)op; (void)pageIndex; + return std::unexpected(EngineError::Unknown); +#endif +} + +} \ No newline at end of file diff --git a/engine/src/parser/pdfium_edit_replace.cpp b/engine/src/parser/pdfium_edit_replace.cpp new file mode 100644 index 0000000..6502848 --- /dev/null +++ b/engine/src/parser/pdfium_edit_replace.cpp @@ -0,0 +1,610 @@ +#include "parser/pdfium_internal.hpp" + +namespace pdfengine::parser { + +std::expected PdfiumDocument::applyOp_replaceText(const nlohmann::json& op, int pageIndex) { +#ifdef PDFENGINE_WITH_PDFIUM + std::vector objectIndices; + std::string newText = ""; + std::string internalFontId = ""; + double fontSize = -1.0; + bool disableJustify = false; + + if (op.contains("data") && op["data"].is_object()) { + auto data = op["data"]; + if (data.contains("objectIndices") && data["objectIndices"].is_array()) { + for (auto& idx : data["objectIndices"]) { + objectIndices.push_back(idx.get()); + } + } + newText = data.value("text", ""); + internalFontId = data.value("internalFontId", ""); + if (data.contains("fontSize")) { + fontSize = data["fontSize"].get(); + } + disableJustify = data.value("disableJustify", false); + } else { + if (op.contains("objectIndices") && op["objectIndices"].is_array()) { + for (auto& idx : op["objectIndices"]) { + objectIndices.push_back(idx.get()); + } + } + newText = op.value("text", ""); + internalFontId = op.value("internalFontId", ""); + if (op.contains("fontSize")) { + fontSize = op["fontSize"].get(); + } + } + + if (objectIndices.empty()) { + spdlog::warn("replace_text has empty objectIndices, nothing to replace"); + return {}; + } + + FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); + if (!page) { + spdlog::error("Failed to load page index {} for replace_text", pageIndex); + return std::unexpected(EngineError::Unknown); + } + + std::sort(objectIndices.begin(), objectIndices.end(), std::greater()); + + int minIndex = objectIndices.back(); + + FPDF_PAGEOBJECT origObj = FPDFPage_GetObject(page, minIndex); + if (!origObj) { + spdlog::error("Failed to get original text object at index {}", minIndex); + FPDF_ClosePage(page); + return std::unexpected(EngineError::Unknown); + } + + double a = 1.0, b = 0.0, c = 0.0, d = 1.0, e = 0.0, f = 0.0; + FS_MATRIX matrix; + if (FPDFPageObj_GetMatrix(origObj, &matrix)) { + a = matrix.a; + b = matrix.b; + c = matrix.c; + d = matrix.d; + e = matrix.e; + f = matrix.f; + } + + unsigned int r = 0, g = 0, b_color = 0, a_color = 255; + FPDFPageObj_GetFillColor(origObj, &r, &g, &b_color, &a_color); + + if (fontSize < 0.0) { + float sizeVal = 12.0f; + if (FPDFTextObj_GetFontSize(origObj, &sizeVal)) { + fontSize = sizeVal; + } else { + fontSize = 12.0; + } + } + + FPDF_TEXT_RENDERMODE renderMode = static_cast(FPDFTextObj_GetTextRenderMode(origObj)); + + std::string fontName = "Helvetica"; + std::string origFontName = ""; + bool bold = false; + bool italic = false; + FPDF_FONT origFont = FPDFTextObj_GetFont(origObj); + if (origFont) { + size_t nameLen = FPDFFont_GetBaseFontName(origFont, nullptr, 0); + if (nameLen > 0) { + std::vector nameBuf(nameLen); + if (FPDFFont_GetBaseFontName(origFont, nameBuf.data(), nameLen) > 0) { + origFontName = nameBuf.data(); + std::string baseName(nameBuf.data()); + std::string lowerName = baseName; + std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), [](unsigned char c) { + return static_cast(std::tolower(c)); + }); + + bold = (lowerName.find("bold") != std::string::npos); + italic = (lowerName.find("italic") != std::string::npos || lowerName.find("oblique") != std::string::npos); + + if (lowerName.find("times") != std::string::npos) { + if (bold && italic) fontName = "Times-BoldItalic"; + else if (bold) fontName = "Times-Bold"; + else if (italic) fontName = "Times-Italic"; + else fontName = "Times-Roman"; + } else if (lowerName.find("courier") != std::string::npos) { + if (bold && italic) fontName = "Courier-BoldOblique"; + else if (bold) fontName = "Courier-Bold"; + else if (italic) fontName = "Courier-Oblique"; + else fontName = "Courier"; + } else { + if (bold && italic) fontName = "Helvetica-BoldOblique"; + else if (bold) fontName = "Helvetica-Bold"; + else if (italic) fontName = "Helvetica-Oblique"; + else fontName = "Helvetica"; + } + } + } + } + + if (!internalFontId.empty()) { + std::string lowerId = internalFontId; + std::transform(lowerId.begin(), lowerId.end(), lowerId.begin(), [](unsigned char c) { + return static_cast(std::tolower(c)); + }); + if (lowerId.find("bolditalic") != std::string::npos) { bold = true; italic = true; } + else if (lowerId.find("bold") != std::string::npos) { bold = true; } + else if (lowerId.find("italic") != std::string::npos) { italic = true; } + else if (lowerId.find("oblique") != std::string::npos) { italic = true; } + + if (lowerId.find("times") != std::string::npos) { + if (bold && italic) fontName = "Times-BoldItalic"; + else if (bold) fontName = "Times-Bold"; + else if (italic) fontName = "Times-Italic"; + else fontName = "Times-Roman"; + } else if (lowerId.find("courier") != std::string::npos) { + if (bold && italic) fontName = "Courier-BoldOblique"; + else if (bold) fontName = "Courier-Bold"; + else if (italic) fontName = "Courier-Oblique"; + else fontName = "Courier"; + } else if (lowerId.find("helvetica") != std::string::npos) { + if (bold && italic) fontName = "Helvetica-BoldOblique"; + else if (bold) fontName = "Helvetica-Bold"; + else if (italic) fontName = "Helvetica-Oblique"; + else fontName = "Helvetica"; + } + } + + std::optional matchedFontInfo; + auto fontsRes = getFonts(pageIndex, pageIndex); + if (fontsRes.has_value()) { + for (const auto& fontInfoEntry : *fontsRes) { + if ((!internalFontId.empty() && fontInfoEntry.internalFontId == internalFontId) || + (!origFontName.empty() && fontInfoEntry.fontName == origFontName)) { + matchedFontInfo = fontInfoEntry; + break; + } + } + } + + std::shared_ptr resolvedFont = nullptr; + if (matchedFontInfo.has_value()) { + auto resolvedFontRes = getResolvedFont(*matchedFontInfo); + if (resolvedFontRes.has_value()) { + resolvedFont = *resolvedFontRes; + spdlog::info("Font Engine: resolved font '{}'", matchedFontInfo->fontName); + } else { + spdlog::warn("Font Engine: failed to resolve font '{}': {}", matchedFontInfo->fontName, resolvedFontRes.error()); + } + } + + bool fontSupportsAll = true; + double totalWidth = 0.0; + + auto utf16 = utf8_to_utf16le(newText); + std::vector unicodeCodepoints; + for (size_t i = 0; i < utf16.size(); ) { + uint32_t cp = utf16[i]; + if (cp >= 0xD800 && cp <= 0xDBFF && i + 1 < utf16.size()) { + uint32_t low = utf16[i + 1]; + if (low >= 0xDC00 && low <= 0xDFFF) { + cp = 0x10000 + ((cp - 0xD800) << 10) + (low - 0xDC00); + i += 2; + } else { + i += 1; + } + } else { + i += 1; + } + unicodeCodepoints.push_back(cp); + } + + bool isSubsetFont = matchedFontInfo && matchedFontInfo->isSubset; + bool subsetLacksGlyphs = false; + + if (resolvedFont) { + for (uint32_t cp : unicodeCodepoints) { + if (!resolvedFont->hasGlyph(cp)) { + if (isSubsetFont) { + subsetLacksGlyphs = true; + } else { + fontSupportsAll = false; + } + spdlog::warn("Font Engine: Glyph for codepoint {} not found in font {}", cp, matchedFontInfo ? matchedFontInfo->fontName : "Unknown"); + } + } + } else { + fontSupportsAll = false; + } + + bool shapedSuccessful = false; + if (resolvedFont) { + try { + fonts::HbShaper shaper; + unsigned int uFontSize = static_cast(fontSize > 0.0 ? fontSize : 12.0); + auto shapedGlyphs = shaper.shapeRun(newText, resolvedFont->getFontFace(), uFontSize); + if (!shapedGlyphs.empty()) { + totalWidth = 0.0; + for (const auto& sg : shapedGlyphs) { + totalWidth += sg.advanceX; + } + shapedSuccessful = true; + spdlog::info("Font Engine: HarfBuzz shaped '{}' glyphs, total advance width = {}", shapedGlyphs.size(), totalWidth); + } + } catch (const std::exception& e) { + spdlog::warn("Font Engine: HarfBuzz shaping failed: {}", e.what()); + } catch (...) { + spdlog::warn("Font Engine: HarfBuzz shaping failed with unknown exception"); + } + } + + if (!shapedSuccessful && resolvedFont) { + totalWidth = 0.0; + for (uint32_t cp : unicodeCodepoints) { + double w = resolvedFont->getAdvanceWidth(cp, fontSize); + totalWidth += w; + } + spdlog::info("Font Engine: FreeType fallback total advance width = {}", totalWidth); + } + + float origLeft = 999999.0f, origRight = -999999.0f; + float origBottom = 999999.0f, origTop = -999999.0f; + bool hasOrigBounds = false; + for (int idx : objectIndices) { + FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, idx); + if (obj) { + float left = 0.0f, bottom = 0.0f, right = 0.0f, top = 0.0f; + if (FPDFPageObj_GetBounds(obj, &left, &bottom, &right, &top)) { + if (left < origLeft) origLeft = left; + if (right > origRight) origRight = right; + if (bottom < origBottom) origBottom = bottom; + if (top > origTop) origTop = top; + hasOrigBounds = true; + } + } + } + + double origWidth = 0.0; + double origCenterY = 0.0; + if (hasOrigBounds) { + origWidth = origRight - origLeft; + origCenterY = (origBottom + origTop) / 2.0; + } + + bool axisAligned = (std::abs(b) < 1e-6 && std::abs(c) < 1e-6 && a > 0.0 && d > 0.0); + double colRight = origRight; + bool sawSibling = false; + if (axisAligned && hasOrigBounds) { + int nObjForCol = FPDFPage_CountObjects(page); + for (int k = 0; k < nObjForCol; ++k) { + if (std::find(objectIndices.begin(), objectIndices.end(), k) != objectIndices.end()) continue; + FPDF_PAGEOBJECT o = FPDFPage_GetObject(page, k); + if (!o || FPDFPageObj_GetType(o) != FPDF_PAGEOBJ_TEXT) continue; + float l = 0, bo = 0, rr = 0, tt = 0; + if (!FPDFPageObj_GetBounds(o, &l, &bo, &rr, &tt)) continue; + if (std::abs(l - origLeft) <= 3.0) { sawSibling = true; if (rr > colRight) colRight = rr; } + } + } + double justifyTol = (std::max)(4.0, (colRight - origLeft) * 0.02); + bool wasJustified = !disableJustify && axisAligned && resolvedFont && hasOrigBounds && sawSibling && + (colRight - origLeft) > 20.0 && (origRight >= colRight - justifyTol); + + double deltaX = 0.0; + if (hasOrigBounds) { + deltaX = totalWidth - origWidth; + spdlog::info("Reflow Engine: origWidth = {}, newWidth = {}, deltaX = {}", origWidth, totalWidth, deltaX); + } + + if (!wasJustified && !disableJustify && hasOrigBounds && std::abs(deltaX) > 0.001) { + int pageObjCount = FPDFPage_CountObjects(page); + double tolerance = (std::max)(5.0, fontSize * 0.5); + int reflowedCount = 0; + + for (int k = 0; k < pageObjCount; ++k) { + if (std::find(objectIndices.begin(), objectIndices.end(), k) != objectIndices.end()) { + continue; + } + + FPDF_PAGEOBJECT otherObj = FPDFPage_GetObject(page, k); + if (otherObj && FPDFPageObj_GetType(otherObj) == FPDF_PAGEOBJ_TEXT) { + float otherLeft = 0.0f, otherBottom = 0.0f, otherRight = 0.0f, otherTop = 0.0f; + if (FPDFPageObj_GetBounds(otherObj, &otherLeft, &otherBottom, &otherRight, &otherTop)) { + double otherCenterY = (otherBottom + otherTop) / 2.0; + if (std::abs(otherCenterY - origCenterY) <= tolerance) { + if (otherLeft >= (origRight - 2.0f)) { + FPDFPageObj_Transform(otherObj, 1.0, 0.0, 0.0, 1.0, deltaX, 0.0); + reflowedCount++; + } + } + } + } + } + spdlog::info("Reflow Engine: shifted {} subsequent text objects on the same line by {}", reflowedCount, deltaX); + } + + for (int idx : objectIndices) { + FPDF_PAGEOBJECT objToRemove = FPDFPage_GetObject(page, idx); + if (objToRemove) { + FPDFPage_RemoveObject(page, objToRemove); + FPDFPageObj_Destroy(objToRemove); + } + } + + FPDF_FONT font = nullptr; + std::string cacheKey = ""; + bool useEmbedded = false; + bool useSystem = false; + + if (resolvedFont && matchedFontInfo) { + if (matchedFontInfo->isEmbedded && !isSubsetFont && fontSupportsAll) { + cacheKey = matchedFontInfo->internalFontId; + useEmbedded = true; + } else if (matchedFontInfo->isEmbedded && isSubsetFont && !subsetLacksGlyphs) { + cacheKey = matchedFontInfo->internalFontId; + useEmbedded = true; + } else if (!matchedFontInfo->isEmbedded) { + cacheKey = "standard_" + fontName; + } else { + cacheKey = "system_embed_" + matchedFontInfo->fontName + "_" + (bold ? "B" : "") + (italic ? "I" : ""); + useSystem = true; + } + } else { + cacheKey = "standard_" + fontName; + } + + { + std::lock_guard lock(loadedFontsMutex_); + if (loadedFontsCache_.count(cacheKey)) { + font = loadedFontsCache_[cacheKey]; + spdlog::info("Font Engine: Reusing cached FPDF_FONT for key '{}'", cacheKey); + } + } + + FPDF_FONT reconFont = nullptr; + const fonts::pdf_fonts::ReconstructedFont* reconRf = nullptr; + if (matchedFontInfo && matchedFontInfo->isEmbedded && classifyFontFidelity(*matchedFontInfo) != "exact") { + reconRf = lookupReconFont(matchedFontInfo->internalFontId); + if (reconRf && reconRf->ok) { + std::lock_guard lock(loadedFontsMutex_); + const std::string rkey = "recon_" + matchedFontInfo->internalFontId; + if (loadedFontsCache_.count(rkey)) reconFont = loadedFontsCache_[rkey]; + else { + loadedFontDataBuffers_[rkey] = reconRf->sfnt; + const auto& bytes = loadedFontDataBuffers_[rkey]; + reconFont = FPDFText_LoadFont(doc_, bytes.data(), static_cast(bytes.size()), FPDF_FONT_TRUETYPE, true); + if (reconFont) loadedFontsCache_[rkey] = reconFont; + } + if (reconFont) { + bool fullyCovered = true; + for (uint32_t cp : unicodeCodepoints) + if (cp >= 0x20 && !reconRf->coveredUnicode.count(cp)) { fullyCovered = false; break; } + if (fullyCovered && !font) { + font = reconFont; + spdlog::info("Tier-2: replace_text exact reconstructed font '{}'", matchedFontInfo->internalFontId); + } + } + } + } + + if (!font) { + if (useEmbedded) { + auto fontDataRes = getFontData(matchedFontInfo->internalFontId); + if (fontDataRes.has_value()) { + std::lock_guard lock(loadedFontsMutex_); + loadedFontDataBuffers_[cacheKey] = fontDataRes.value(); + const auto& bytes = loadedFontDataBuffers_[cacheKey]; + font = FPDFText_LoadFont(doc_, bytes.data(), static_cast(bytes.size()), FPDF_FONT_TRUETYPE, false); + if (font) { + spdlog::info("Font Engine: Loaded embedded font '{}' (cache key: {})", matchedFontInfo->fontName, cacheKey); + } + } + } else if (useSystem) { + std::string fontPath = fonts::pdf_fonts::FontFallback::getInstance().getFallbackFontPath( + matchedFontInfo->normalizedFamily.empty() ? matchedFontInfo->fontName : matchedFontInfo->normalizedFamily, + bold, + italic + ); + std::ifstream fs(fontPath, std::ios::binary); + if (fs) { + std::vector fileBytes((std::istreambuf_iterator(fs)), std::istreambuf_iterator()); + if (!fileBytes.empty()) { + const size_t fullSize = fileBytes.size(); + std::lock_guard lock(loadedFontsMutex_); + loadedFontDataBuffers_[cacheKey] = std::move(fileBytes); + const auto& bytes = loadedFontDataBuffers_[cacheKey]; + font = FPDFText_LoadFont(doc_, bytes.data(), static_cast(bytes.size()), FPDF_FONT_TRUETYPE, true); + if (font) { + spdlog::info("Font Engine: Embedded FULL CID system font '{}' ({} bytes) from '{}'", + matchedFontInfo->fontName, fullSize, fontPath); + } + } + } else { + spdlog::warn("Font Engine: Failed to open system font file '{}' for embedding", fontPath); + } + } + + if (!font) { + spdlog::info("Font Engine: Loading standard PDF font for replace_text: {}", fontName); + font = FPDFText_LoadStandardFont(doc_, fontName.c_str()); + if (!font) { + font = FPDFText_LoadStandardFont(doc_, "Helvetica"); + } + } + + if (font) { + std::lock_guard lock(loadedFontsMutex_); + loadedFontsCache_[cacheKey] = font; + } + } + + if (font) { + constexpr unsigned int kRef = 1000; + double emToPage = (fontSize > 0.0 ? fontSize : 1.0) * a / static_cast(kRef); + auto pageWidthOf = [&](const std::string& s) -> double { + if (s.empty() || !resolvedFont) return 0.0; + double sum = 0.0; + try { + fonts::HbShaper sh; + auto gl = sh.shapeRun(s, resolvedFont->getFontFace(), kRef); + for (const auto& gg : gl) sum += gg.advanceX; + } catch (...) { + for (unsigned char ch : s) sum += resolvedFont->getAdvanceWidth(ch, kRef); + } + return sum * emToPage; + }; + + std::vector words; + if (wasJustified) { + std::string cur; + for (char ch : newText) { + if (ch == ' ') { if (!cur.empty()) { words.push_back(cur); cur.clear(); } } + else cur.push_back(ch); + } + if (!cur.empty()) words.push_back(cur); + } + + auto measuredWidth = [&](const std::vector& u16le) -> double { + FPDF_PAGEOBJECT m = FPDFPageObj_CreateTextObj(doc_, font, static_cast(fontSize)); + if (!m) return 0.0; + FPDFText_SetText(m, reinterpret_cast(u16le.data())); + FPDFPageObj_Transform(m, a, b, c, d, 0.0, 0.0); + float l = 0, bo = 0, rr = 0, tt = 0; + double w = FPDFPageObj_GetBounds(m, &l, &bo, &rr, &tt) ? (rr - l) : 0.0; + FPDFPageObj_Destroy(m); + return w; + }; + + if (wasJustified && words.size() > 1) { + std::vector wpx; + wpx.reserve(words.size()); + double estWords = 0.0; + for (const auto& w : words) { double ww = pageWidthOf(w); wpx.push_back(ww); estWords += ww; } + double estSpace = pageWidthOf(" "); + int gaps = static_cast(words.size()) - 1; + double estTotal = estWords + gaps * estSpace; + double actualFull = measuredWidth(utf16); + double k = (estTotal > 1e-6 && actualFull > 1e-6) ? actualFull / estTotal : 1.0; + double targetW = colRight - e; + double slack = targetW - actualFull; + double extraPerGap = (slack > 0.0) ? slack / gaps : 0.0; + spdlog::info("replace_text: justify {} words targetW={:.1f} actualW={:.1f} extraPerGap={:.2f}", + words.size(), targetW, actualFull, extraPerGap); + double penX = e; + for (size_t wi = 0; wi < words.size(); ++wi) { + FPDF_PAGEOBJECT wobj = FPDFPageObj_CreateTextObj(doc_, font, static_cast(fontSize)); + if (wobj) { + FPDFPageObj_SetFillColor(wobj, r, g, b_color, a_color); + FPDFTextObj_SetTextRenderMode(wobj, renderMode); + auto wu = utf8_to_utf16le(words[wi]); wu.push_back(0); + FPDFText_SetText(wobj, reinterpret_cast(wu.data())); + FPDFPageObj_Transform(wobj, a, b, c, d, penX, f); + FPDFPage_InsertObjectAtIndex(page, wobj, minIndex); + } + penX += (wpx[wi] + estSpace) * k + extraPerGap; + } + } else { + bool didHybrid = false; + if (reconFont && reconRf && font && font != reconFont && !unicodeCodepoints.empty()) { + fonts::FontFace reconFace; bool haveReconFace = reconFace.loadFromMemory(reconRf->sfnt); + fonts::FontFace subFace; bool haveSubFace = false; + { + std::lock_guard lock(loadedFontsMutex_); + auto it = loadedFontDataBuffers_.find(cacheKey); + if (it != loadedFontDataBuffers_.end() && !it->second.empty()) + haveSubFace = subFace.loadFromMemory(it->second); + } + auto segWidthPage = [&](fonts::FontFace* face, const std::string& s) -> double { + if (!face || s.empty()) return 0.0; + double sum = 0.0; + try { fonts::HbShaper sh; for (const auto& gg : sh.shapeRun(s, *face, kRef)) sum += gg.advanceX; } + catch (...) { return 0.0; } + return sum * emToPage; + }; + auto isCov = [&](uint32_t cp){ return cp < 0x20 || reconRf->coveredUnicode.count(cp) != 0; }; + auto cpToU16 = [](uint32_t cp, std::vector& dst){ + if (cp <= 0xFFFF) dst.push_back(static_cast(cp)); + else { cp -= 0x10000; dst.push_back(static_cast(0xD800 + (cp >> 10))); + dst.push_back(static_cast(0xDC00 + (cp & 0x3FF))); } + }; + auto cpToU8 = [](uint32_t cp, std::string& d){ + if (cp < 0x80) d += static_cast(cp); + else if (cp < 0x800) { d += static_cast(0xC0 | (cp >> 6)); d += static_cast(0x80 | (cp & 0x3F)); } + else if (cp < 0x10000) { d += static_cast(0xE0 | (cp >> 12)); d += static_cast(0x80 | ((cp >> 6) & 0x3F)); d += static_cast(0x80 | (cp & 0x3F)); } + else { d += static_cast(0xF0 | (cp >> 18)); d += static_cast(0x80 | ((cp >> 12) & 0x3F)); d += static_cast(0x80 | ((cp >> 6) & 0x3F)); d += static_cast(0x80 | (cp & 0x3F)); } + }; + double penX = e; + size_t i = 0; + while (i < unicodeCodepoints.size()) { + bool cov = isCov(unicodeCodepoints[i]); + std::vector seg; std::string seg8; + while (i < unicodeCodepoints.size() && isCov(unicodeCodepoints[i]) == cov) { + cpToU16(unicodeCodepoints[i], seg); cpToU8(unicodeCodepoints[i], seg8); ++i; + } + seg.push_back(0); + FPDF_FONT segFont = cov ? reconFont : font; + fonts::FontFace* segFace = cov ? (haveReconFace ? &reconFace : nullptr) + : (haveSubFace ? &subFace : nullptr); + FPDF_PAGEOBJECT obj = FPDFPageObj_CreateTextObj(doc_, segFont, static_cast(fontSize)); + if (obj) { + FPDFPageObj_SetFillColor(obj, r, g, b_color, a_color); + FPDFTextObj_SetTextRenderMode(obj, renderMode); + FPDFText_SetText(obj, reinterpret_cast(seg.data())); + FPDFPageObj_Transform(obj, a, b, c, d, penX, f); + FPDFPage_InsertObjectAtIndex(page, obj, minIndex); + } + double adv = segWidthPage(segFace, seg8); + if (adv <= 0.0) { // shaping unavailable -> fall back to ink bbox + if (obj) { float l=0,bo=0,rr=0,tt=0; if (FPDFPageObj_GetBounds(obj,&l,&bo,&rr,&tt)) adv = rr - l; } + } + penX += adv; + } + didHybrid = true; + spdlog::info("Tier-2: replace_text HYBRID emission for '{}' (mixed embedded/substitute)", internalFontId); + } + if (!didHybrid) + { + double aScale = a; + if (disableJustify && hasOrigBounds && axisAligned) { + double newW = measuredWidth(utf16); + double rowTol = (std::max)(5.0, (origTop - origBottom) * 0.5); + double nextLeft = 1e18; + int nObj = FPDFPage_CountObjects(page); + for (int k = 0; k < nObj; ++k) { + if (std::find(objectIndices.begin(), objectIndices.end(), k) != objectIndices.end()) continue; + FPDF_PAGEOBJECT o = FPDFPage_GetObject(page, k); + if (!o || FPDFPageObj_GetType(o) != FPDF_PAGEOBJ_TEXT) continue; + float l = 0, bo = 0, rr = 0, tt = 0; + if (!FPDFPageObj_GetBounds(o, &l, &bo, &rr, &tt)) continue; + if (std::abs((bo + tt) / 2.0 - origCenterY) <= rowTol && l > origRight + 1.0 && l < nextLeft) { + nextLeft = l; + } + } + if (nextLeft < 1e17) { + double avail = nextLeft - e - 2.0; + if (avail > 1.0 && newW > avail) aScale = a * (avail / newW); + } + } + FPDF_PAGEOBJECT newTextObj = FPDFPageObj_CreateTextObj(doc_, font, static_cast(fontSize)); + if (newTextObj) { + FPDFPageObj_SetFillColor(newTextObj, r, g, b_color, a_color); + FPDFTextObj_SetTextRenderMode(newTextObj, renderMode); + FPDFText_SetText(newTextObj, reinterpret_cast(utf16.data())); + FPDFPageObj_Transform(newTextObj, aScale, b, c, d, e, f); + + FPDFPage_InsertObjectAtIndex(page, newTextObj, minIndex); + } else { + spdlog::error("Failed to create new text object"); + } + } + } + } + + if (!FPDFPage_GenerateContent(page)) { + spdlog::error("Failed to generate page content after replace_text"); + } + + FPDF_ClosePage(page); + return {}; +#else + (void)op; (void)pageIndex; + return std::unexpected(EngineError::Unknown); +#endif +} + +} \ No newline at end of file diff --git a/engine/src/parser/pdfium_fonts.cpp b/engine/src/parser/pdfium_fonts.cpp new file mode 100644 index 0000000..1d418cd --- /dev/null +++ b/engine/src/parser/pdfium_fonts.cpp @@ -0,0 +1,648 @@ +#include "parser/pdfium_internal.hpp" + +namespace pdfengine::parser { + +#ifdef PDFENGINE_WITH_PDFIUM +void PdfiumDocument::registerAuxFont(const std::string& internalFontId, const std::vector& sfnt) { + fonts::pdf_fonts::ReconstructedFont rf; + if (!sfnt.empty()) { + rf.ok = true; + fonts::FontFace face; + if (face.loadFromMemory(sfnt)) + for (uint32_t u : face.coveredCodepoints()) rf.coveredUnicode.insert(u); + rf.sfnt = sfnt; + } + std::lock_guard lock(reconstructedFontsMutex_); + reconstructedFonts_[internalFontId] = std::move(rf); +} + +std::expected, EngineError> +PdfiumDocument::getReconstructedFontData(const std::string& internalFontId) { + const auto* rf = lookupReconFont(internalFontId); + if (rf && rf->ok && !rf->sfnt.empty()) return rf->sfnt; + return std::unexpected(EngineError::Unknown); +} + +const fonts::pdf_fonts::ReconstructedFont* PdfiumDocument::lookupReconFont(const std::string& internalFontId) { +#ifdef PDFENGINE_WITH_QPDF + return getReconstructedEmbeddedFont(internalFontId); +#else + std::lock_guard lock(reconstructedFontsMutex_); + auto it = reconstructedFonts_.find(internalFontId); + return it != reconstructedFonts_.end() ? &it->second : nullptr; +#endif +} +#endif + +#if defined(PDFENGINE_WITH_PDFIUM) && defined(PDFENGINE_WITH_QPDF) +const fonts::pdf_fonts::ReconstructedFont* +PdfiumDocument::getReconstructedEmbeddedFont(const std::string& internalFontId) { + { + std::lock_guard lock(reconstructedFontsMutex_); + auto it = reconstructedFonts_.find(internalFontId); + if (it != reconstructedFonts_.end()) return &it->second; + } + fonts::pdf_fonts::ReconstructedFont rf; + auto progRes = getFontData(internalFontId); + if (progRes.has_value() && !progRes.value().empty() && !memoryBuffer_.empty()) { + std::string baseName = baseNameFromInternalFontId(internalFontId); + qpdf_layer::QpdfFontExtractor fx; + auto mapRes = fx.extractMapping(memoryBuffer_, baseName); + if (mapRes.has_value() && mapRes->ok) { + rf = fonts::pdf_fonts::EmbeddedFontReconstructor::reconstruct( + progRes.value(), mapRes->codeToUnicode, mapRes->identityCidToGid, mapRes->codeToGid); + spdlog::info("Tier-2: reconstruct '{}' ok={} covered={}", internalFontId, rf.ok, rf.coveredUnicode.size()); + } + } + std::lock_guard lock(reconstructedFontsMutex_); + auto res = reconstructedFonts_.emplace(internalFontId, std::move(rf)); + return &res.first->second; +} +#endif + +#ifdef PDFENGINE_WITH_PDFIUM +PdfiumDocument::EmissionFont PdfiumDocument::loadEmissionFont( + int pageIndex, const std::string& internalFontId, double fontSize, + const std::vector& codepoints, const std::vector& srcObjects, + FPDF_FONT reuseFont) { + EmissionFont out; + (void)fontSize; + + std::string fontName = "Helvetica"; + bool bold = false, italic = false; + { + std::string lowerId = internalFontId; + std::transform(lowerId.begin(), lowerId.end(), lowerId.begin(), + [](unsigned char ch) { return static_cast(std::tolower(ch)); }); + if (lowerId.find("bolditalic") != std::string::npos) { bold = true; italic = true; } + else if (lowerId.find("bold") != std::string::npos) { bold = true; } + else if (lowerId.find("italic") != std::string::npos) { italic = true; } + else if (lowerId.find("oblique") != std::string::npos) { italic = true; } + if (lowerId.find("times") != std::string::npos) { + fontName = bold && italic ? "Times-BoldItalic" : bold ? "Times-Bold" : italic ? "Times-Italic" : "Times-Roman"; + } else if (lowerId.find("courier") != std::string::npos) { + fontName = bold && italic ? "Courier-BoldOblique" : bold ? "Courier-Bold" : italic ? "Courier-Oblique" : "Courier"; + } else { + fontName = bold && italic ? "Helvetica-BoldOblique" : bold ? "Helvetica-Bold" : italic ? "Helvetica-Oblique" : "Helvetica"; + } + } + + std::optional matchedFontInfo; + auto fontsRes = getFonts(pageIndex, pageIndex); + if (fontsRes.has_value()) { + for (const auto& fi : *fontsRes) { + if (!internalFontId.empty() && fi.internalFontId == internalFontId) { matchedFontInfo = fi; break; } + } + } + if (matchedFontInfo.has_value()) { + auto r = getResolvedFont(*matchedFontInfo); + if (r.has_value()) out.resolved = *r; + } + + bool fontSupportsAll = true, subsetLacksGlyphs = false; + bool isSubsetFont = matchedFontInfo && matchedFontInfo->isSubset; + if (out.resolved) { + for (uint32_t cp : codepoints) { + if (!out.resolved->hasGlyph(cp)) { + if (isSubsetFont) subsetLacksGlyphs = true; else fontSupportsAll = false; + } + } + } else { + fontSupportsAll = false; + } + + std::string cacheKey; + bool useEmbedded = false, useSystem = false; + if (matchedFontInfo && matchedFontInfo->isEmbedded) { + bool subsetProvenLacking = out.resolved && isSubsetFont && subsetLacksGlyphs; + bool fullProvenLacking = out.resolved && !isSubsetFont && !fontSupportsAll; + if (!subsetProvenLacking && !fullProvenLacking) { cacheKey = matchedFontInfo->internalFontId; useEmbedded = true; } + else { cacheKey = "system_embed_" + matchedFontInfo->fontName + std::string("_") + (bold ? "B" : "") + (italic ? "I" : ""); useSystem = true; } + } else if (matchedFontInfo && !matchedFontInfo->isEmbedded) { + cacheKey = "standard_" + fontName; + } else { + cacheKey = "standard_" + fontName; + } + + { + uint64_t h = 1469598103934665603ull; + for (uint32_t cp : codepoints) { h ^= cp; h *= 1099511628211ull; } + cacheKey += "#" + std::to_string(h); + } + + if (useEmbedded && reuseFont && !isSubsetFont) { + out.font = reuseFont; + if (auto perObj = getFontDataFromObjects(pageIndex, srcObjects, internalFontId); + perObj.has_value() && !perObj->empty()) { + auto mf = std::make_shared(); + if (mf->loadFromMemory(*perObj)) out.measureFace = mf; + } + return out; + } + + { + std::lock_guard lock(loadedFontsMutex_); + if (loadedFontsCache_.count(cacheKey)) { + out.font = loadedFontsCache_[cacheKey]; + auto it = loadedMeasureFaces_.find(cacheKey); + if (it != loadedMeasureFaces_.end()) out.measureFace = it->second; + } + } + if (out.font) return out; + + if (matchedFontInfo && matchedFontInfo->isEmbedded && classifyFontFidelity(*matchedFontInfo) != "exact") { + const auto* rf = lookupReconFont(internalFontId); + if (rf && rf->ok) { + bool covered = true; + for (uint32_t cp : codepoints) { + if (cp >= 0x20 && !rf->coveredUnicode.count(cp)) { covered = false; break; } + } + if (covered) { + std::lock_guard lock(loadedFontsMutex_); + const std::string rkey = "recon_" + internalFontId; + if (loadedFontsCache_.count(rkey)) { + out.font = loadedFontsCache_[rkey]; + auto mit = loadedMeasureFaces_.find(rkey); + if (mit != loadedMeasureFaces_.end()) out.measureFace = mit->second; + } else { + loadedFontDataBuffers_[rkey] = rf->sfnt; + const auto& bytes = loadedFontDataBuffers_[rkey]; + out.font = FPDFText_LoadFont(doc_, bytes.data(), static_cast(bytes.size()), FPDF_FONT_TRUETYPE, true); + if (out.font) { + loadedFontsCache_[rkey] = out.font; + auto mf = std::make_shared(); + if (mf->loadFromMemory(bytes)) { loadedMeasureFaces_[rkey] = mf; out.measureFace = mf; } + } + } + if (out.font) { spdlog::info("Tier-2: emit reconstructed embedded font '{}'", internalFontId); return out; } + } + } + } + + if (useEmbedded) { + std::vector sourceBytes; + if (auto perObj = getFontDataFromObjects(pageIndex, srcObjects, matchedFontInfo->internalFontId); + perObj.has_value() && !perObj->empty()) { + sourceBytes = std::move(*perObj); + } else if (auto fontDataRes = getFontData(matchedFontInfo->internalFontId); + fontDataRes.has_value() && !fontDataRes.value().empty()) { + sourceBytes = std::move(fontDataRes.value()); + } + if (!sourceBytes.empty()) { + std::lock_guard lock(loadedFontsMutex_); + loadedFontDataBuffers_[cacheKey] = std::move(sourceBytes); + const auto& bytes = loadedFontDataBuffers_[cacheKey]; + out.font = FPDFText_LoadFont(doc_, bytes.data(), static_cast(bytes.size()), FPDF_FONT_TRUETYPE, true); + } + } else if (useSystem) { + std::string fontPath = fonts::pdf_fonts::FontFallback::getInstance().getFallbackFontPath( + matchedFontInfo->normalizedFamily.empty() ? matchedFontInfo->fontName : matchedFontInfo->normalizedFamily, bold, italic); + std::ifstream fs(fontPath, std::ios::binary); + if (fs) { + std::vector fileBytes((std::istreambuf_iterator(fs)), std::istreambuf_iterator()); + if (!fileBytes.empty()) { + std::lock_guard lock(loadedFontsMutex_); + loadedFontDataBuffers_[cacheKey] = std::move(fileBytes); + const auto& bytes = loadedFontDataBuffers_[cacheKey]; + out.font = FPDFText_LoadFont(doc_, bytes.data(), static_cast(bytes.size()), FPDF_FONT_TRUETYPE, true); + } + } + } + if (!out.font) { + out.font = FPDFText_LoadStandardFont(doc_, fontName.c_str()); + if (!out.font) out.font = FPDFText_LoadStandardFont(doc_, "Helvetica"); + } + if (out.font) { + std::lock_guard lock(loadedFontsMutex_); + loadedFontsCache_[cacheKey] = out.font; + auto bit = loadedFontDataBuffers_.find(cacheKey); + if (bit != loadedFontDataBuffers_.end() && !bit->second.empty()) { + auto mf = std::make_shared(); + if (mf->loadFromMemory(bit->second)) { loadedMeasureFaces_[cacheKey] = mf; out.measureFace = mf; } + } + } + return out; +} +#endif + +std::expected, EngineError> PdfiumPage::getFonts() const { +#ifdef PDFENGINE_WITH_PDFIUM + if (!page_) { + return std::unexpected(EngineError::Unknown); + } + ensureTextPageLoaded(); + if (!textPage_) { + return std::unexpected(EngineError::Unknown); + } + + auto fontPdfDataMap = buildFontPdfDataMap(page_, textPage_); + + std::vector pageFonts; + int charCount = FPDFText_CountChars(textPage_); + + if (charCount < 0 || charCount > 1000000) { + spdlog::error("Invalid or excessive character count in page ({}): aborting font extraction", charCount); + return pageFonts; + } + + auto getFontNameForChar = [this](int charIndex, int& flagsOut) -> std::string { + int flags = 0; + unsigned long len = FPDFText_GetFontInfo(textPage_, charIndex, nullptr, 0, &flags); + if (len > 0) { + std::vector buf(len); + if (FPDFText_GetFontInfo(textPage_, charIndex, buf.data(), len, &flags) > 0) { + flagsOut = flags; + return std::string(buf.data()); + } + } + return ""; + }; + + struct FontPositionStats { + int horizontalSteps = 0; + int verticalSteps = 0; + double totalFilteredDeltaX = 0.0; + double totalFilteredDeltaY = 0.0; + double prevX = 0.0, prevY = 0.0; + bool hasPrev = false; + }; + std::unordered_map fontStats; + + struct CharInfo { + std::string fontName; + int flags; + }; + std::vector charInfos(charCount); + + for (int i = 0; i < charCount; ++i) { + int flags = 0; + std::string fontName = getFontNameForChar(i, flags); + charInfos[i] = {fontName, flags}; + + if (!fontName.empty()) { + double ox = 0.0, oy = 0.0; + if (FPDFText_GetCharOrigin(textPage_, i, &ox, &oy)) { + auto& stats = fontStats[fontName]; + if (stats.hasPrev) { + double dx = std::abs(ox - stats.prevX); + double dy = std::abs(oy - stats.prevY); + double fontSize = FPDFText_GetFontSize(textPage_, i); + + double maxJump = (std::max)(fontSize * 3.0, 30.0); + + if (dx < maxJump && dy < maxJump) { + stats.totalFilteredDeltaX += dx; + stats.totalFilteredDeltaY += dy; + + if (dy > dx * 1.5) { + stats.verticalSteps++; + } else if (dx > dy * 1.5) { + stats.horizontalSteps++; + } + } + } + stats.prevX = ox; + stats.prevY = oy; + stats.hasPrev = true; + } + } + } + + std::unordered_map fontIsVertical; + for (const auto& [fname, stats] : fontStats) { + bool vertical = false; + + if (stats.verticalSteps > 0 || stats.horizontalSteps > 0) { + vertical = stats.verticalSteps > stats.horizontalSteps; + } else { + vertical = (stats.totalFilteredDeltaY > 2.0 * stats.totalFilteredDeltaX) && + (stats.totalFilteredDeltaY > 0.5); + } + + fontIsVertical[fname] = vertical; + } + + for (int i = 0; i < charCount; ++i) { + const std::string& fontName = charInfos[i].fontName; + if (fontName.empty()) { + continue; + } + + auto it = std::find_if(pageFonts.begin(), pageFonts.end(), [&](const FontInfo& f) { + return f.fontName == fontName; + }); + if (it != pageFonts.end()) { + continue; + } + + FontInfo f; + f.fontName = fontName; + f.flags = static_cast(charInfos[i].flags); + + deduceFontMetadata(f); + + auto pdfDataIt = fontPdfDataMap.find(fontName); + if (pdfDataIt != fontPdfDataMap.end()) { + const FontPdfData& pd = pdfDataIt->second; + + auto lname = fontName; + std::transform(lname.begin(), lname.end(), lname.begin(), [](unsigned char c) { return static_cast(::tolower(c)); }); + + f.isEmbedded = pd.isEmbedded; + + f.flags = static_cast(pd.flags); + + if (pd.ascent != 0.0) f.ascent = pd.ascent; + if (pd.descent != 0.0) f.descent = pd.descent; + + if (f.ascent > 0.0) { + if (lname.find("times") != std::string::npos) f.capHeight = 662.0; + else if (lname.find("courier") != std::string::npos) f.capHeight = 562.0; + else if (lname.find("symbol") != std::string::npos) f.capHeight = 673.0; + else if (lname.find("helvetica") != std::string::npos) f.capHeight = 728.0; + else f.capHeight = f.ascent * 0.71; + } + + if (lname.find("symbol") != std::string::npos) { + f.hasToUnicode = false; + } else { + f.hasToUnicode = pd.hasUnicodeMapping; + } + + if (f.isEmbedded) { + f.sourceType = "Embedded"; + f.substitutedFrom = ""; + f.substitutedTo = ""; + } + + f.internalFontId = makeInternalFontId(f); + + spdlog::debug( + "Font '{}': isEmbedded={} type='{}' ascent={:.1f} descent={:.1f} " + "capHeight={:.1f} hasToUnicode={} flags={}", + fontName, f.isEmbedded, f.type, f.ascent, f.descent, + f.capHeight, f.hasToUnicode, f.flags); + } + + if (!f.isVertical) { + auto vit = fontIsVertical.find(fontName); + if (vit != fontIsVertical.end() && vit->second) { + f.isVertical = true; + if (f.encoding == "WinAnsiEncoding" || f.encoding == "Identity-H") { + f.encoding = "Identity-V"; + f.cmapName = "Identity-V"; + } + spdlog::info("Vertical writing mode detected for font '{}' via character position analysis", fontName); + } + } + + pageFonts.push_back(f); + } + + std::sort(pageFonts.begin(), pageFonts.end(), [](const FontInfo& a, const FontInfo& b) { + if (a.normalizedFamily != b.normalizedFamily) { + return a.normalizedFamily < b.normalizedFamily; + } + if (a.fontName != b.fontName) { + return a.fontName < b.fontName; + } + if (a.encoding != b.encoding) { + return a.encoding < b.encoding; + } + return a.type < b.type; + }); + + return pageFonts; +#else + return std::unexpected(EngineError::Unknown); +#endif +} + +std::expected, EngineError> PdfiumDocument::getFonts(int startPage, int endPage) const { +#ifdef PDFENGINE_WITH_PDFIUM + if (!doc_) { + return std::unexpected(EngineError::Unknown); + } + + std::lock_guard lock(fontsMutex_); + + int total = pageCount(); + if (startPage < 0) startPage = 0; + if (endPage < 0 || endPage >= total) endPage = total - 1; + if (startPage > endPage) { + return std::vector(); + } + + + if (startPage == 0 && endPage == total - 1 && hasCachedFonts_) { + return cachedFonts_; + } + + std::vector aggregated; + for (int i = startPage; i <= endPage; ++i) { + auto pageRes = const_cast(this)->getPage(i); + if (!pageRes) { + spdlog::error("Failed to load page index {} for font diagnostics", i); + continue; + } + + auto pageFontsRes = pageRes.value()->getFonts(); + if (pageFontsRes) { + for (const auto& f : *pageFontsRes) { + auto it = std::find_if(aggregated.begin(), aggregated.end(), [&](const FontInfo& existing) { + return existing.fontName == f.fontName; + }); + if (it == aggregated.end()) { + aggregated.push_back(f); + } + } + } + } + + std::sort(aggregated.begin(), aggregated.end(), [](const FontInfo& a, const FontInfo& b) { + if (a.normalizedFamily != b.normalizedFamily) { + return a.normalizedFamily < b.normalizedFamily; + } + if (a.fontName != b.fontName) { + return a.fontName < b.fontName; + } + if (a.encoding != b.encoding) { + return a.encoding < b.encoding; + } + return a.type < b.type; + }); + + if (startPage == 0 && endPage == total - 1) { + cachedFonts_ = aggregated; + hasCachedFonts_ = true; + } + + return aggregated; +#else + (void)startPage; + (void)endPage; + return std::unexpected(EngineError::Unknown); +#endif +} + +std::optional> +PdfiumDocument::getFontDataFromObjects(int pageIndex, const std::vector& objectIndices, + const std::string& internalFontId) const { +#ifdef PDFENGINE_WITH_PDFIUM + ensure_pdfium_initialized(); + if (!doc_ || objectIndices.empty()) return std::nullopt; + const std::string expected = baseNameFromInternalFontId(internalFontId); + FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); + if (!page) return std::nullopt; + std::optional> result; + for (int idx : objectIndices) { + FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, idx); + if (!obj || FPDFPageObj_GetType(obj) != FPDF_PAGEOBJ_TEXT) continue; + FPDF_FONT font = FPDFTextObj_GetFont(obj); + if (!font) continue; + size_t nameLen = FPDFFont_GetBaseFontName(font, nullptr, 0); + if (nameLen == 0) continue; + std::vector nameBuf(nameLen); + if (FPDFFont_GetBaseFontName(font, nameBuf.data(), nameLen) == 0) continue; + if (std::string(nameBuf.data()) != expected) continue; + size_t buflen = 0; + FPDFFont_GetFontData(font, nullptr, 0, &buflen); + if (buflen == 0) continue; + std::vector buffer(buflen); + size_t actualLen = 0; + if (FPDFFont_GetFontData(font, buffer.data(), buflen, &actualLen) && actualLen > 0) { + result = std::move(buffer); + break; + } + } + FPDF_ClosePage(page); + return result; +#else + (void)pageIndex; (void)objectIndices; (void)internalFontId; + return std::nullopt; +#endif +} + +std::expected, EngineError> PdfiumDocument::getFontData(const std::string& internalFontId) const { +#ifdef PDFENGINE_WITH_PDFIUM + ensure_pdfium_initialized(); + if (!doc_) { + return std::unexpected(EngineError::Unknown); + } + + std::string expectedFontName = baseNameFromInternalFontId(internalFontId); + + int numPages = FPDF_GetPageCount(doc_); + + int startPage = 0; + { + std::lock_guard lock(fontsMutex_); + + if (fontDataCache_.count(expectedFontName)) { + const auto& cachedBuf = fontDataCache_[expectedFontName]; + if (!cachedBuf.empty()) { + return cachedBuf; + } else { + return std::unexpected(EngineError::FileNotFound); + } + } + startPage = fontDataScannedPages_; + } + + for (int i = startPage; i < numPages; ++i) { + FPDF_PAGE page = FPDF_LoadPage(doc_, i); + if (!page) { + std::lock_guard lock(fontsMutex_); + fontDataScannedPages_ = i + 1; + continue; + } + + int objectCount = FPDFPage_CountObjects(page); + for (int j = 0; j < objectCount; ++j) { + FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, j); + if (!obj || FPDFPageObj_GetType(obj) != FPDF_PAGEOBJ_TEXT) continue; + + FPDF_FONT font = FPDFTextObj_GetFont(obj); + if (!font) continue; + + size_t nameLen = FPDFFont_GetBaseFontName(font, nullptr, 0); + if (nameLen > 0) { + std::vector nameBuf(nameLen); + if (FPDFFont_GetBaseFontName(font, nameBuf.data(), nameLen) > 0) { + std::string fontName(nameBuf.data()); + + std::lock_guard lock(fontsMutex_); + + if (fontDataCache_.find(fontName) == fontDataCache_.end()) { + size_t buflen = 0; + FPDFFont_GetFontData(font, nullptr, 0, &buflen); + if (buflen > 0) { + std::vector buffer(buflen); + size_t actual_len = 0; + if (FPDFFont_GetFontData(font, buffer.data(), buflen, &actual_len)) { + fontDataCache_[fontName] = buffer; + } else { + fontDataCache_[fontName] = std::vector(); + } + } else { + fontDataCache_[fontName] = std::vector(); + } + } + + if (fontName == expectedFontName) { + const auto& cachedBuf = fontDataCache_[fontName]; + if (!cachedBuf.empty()) { + FPDF_ClosePage(page); + fontDataScannedPages_ = i; + return cachedBuf; + } + } + } + } + } + FPDF_ClosePage(page); + + std::lock_guard lock(fontsMutex_); + fontDataScannedPages_ = i + 1; + } + + { + std::lock_guard lock(fontsMutex_); + if (fontDataCache_.find(expectedFontName) == fontDataCache_.end()) { + fontDataCache_[expectedFontName] = std::vector(); + } + } + return std::unexpected(EngineError::FileNotFound); +#else + (void)internalFontId; + return std::unexpected(EngineError::Unknown); +#endif +} + +std::expected, std::string> PdfiumDocument::getResolvedFont(const FontInfo& fontInfo) { +#ifdef PDFENGINE_WITH_PDFIUM + std::lock_guard lock(resolvedFontsMutex_); + + if (resolvedFontsCache_.count(fontInfo.internalFontId)) { + return resolvedFontsCache_[fontInfo.internalFontId]; + } + + if (!fontResolver_) { + fontResolver_ = std::make_unique<::pdfengine::fonts::loader::FontResolver>(shared_from_this()); + } + + auto result = fontResolver_->resolveFont(fontInfo); + if (!result) { + return std::unexpected(result.error()); + } + + std::shared_ptr sharedFont(std::move(result.value())); + resolvedFontsCache_[fontInfo.internalFontId] = sharedFont; + return sharedFont; +#else + return std::unexpected(std::string("EngineError::Unknown")); +#endif +} + + +} \ No newline at end of file diff --git a/engine/src/parser/pdfium_internal.cpp b/engine/src/parser/pdfium_internal.cpp new file mode 100644 index 0000000..fbdd13e --- /dev/null +++ b/engine/src/parser/pdfium_internal.cpp @@ -0,0 +1,607 @@ +#include "parser/pdfium_internal.hpp" + +namespace pdfengine::parser { + +std::vector utf8_to_utf16le(const std::string& utf8) { + std::vector utf16; + utf16.reserve(utf8.size()); + for (size_t i = 0; i < utf8.size(); ) { + unsigned char c = utf8[i]; + unsigned int cp = 0; + size_t extra = 0; + if (c < 0x80) { cp = c; extra = 0; } + else if ((c & 0xE0) == 0xC0) { cp = c & 0x1F; extra = 1; } + else if ((c & 0xF0) == 0xE0) { cp = c & 0x0F; extra = 2; } + else if ((c & 0xF8) == 0xF0) { cp = c & 0x07; extra = 3; } + else { i++; continue; } + + if (i + extra >= utf8.size()) break; + bool invalid = false; + for (size_t j = 1; j <= extra; ++j) { + unsigned char next = utf8[i + j]; + if ((next & 0xC0) != 0x80) { invalid = true; break; } + cp = (cp << 6) | (next & 0x3F); + } + if (invalid) { i++; continue; } + i += 1 + extra; + if (cp < 0x10000) { + utf16.push_back(static_cast(cp)); + } else { + cp -= 0x10000; + utf16.push_back(static_cast((cp >> 10) + 0xD800)); + utf16.push_back(static_cast((cp & 0x3FF) + 0xDC00)); + } + } + utf16.push_back(0); + return utf16; +} + +std::string utf16le_to_utf8(const char16_t* utf16, size_t length) { + std::string utf8; + for (size_t i = 0; i < length; ++i) { + char16_t c = utf16[i]; + if (c == 0) break; + + uint32_t cp = c; + if (c >= 0xD800 && c <= 0xDBFF) { + if (i + 1 < length) { + char16_t low = utf16[i + 1]; + if (low >= 0xDC00 && low <= 0xDFFF) { + cp = 0x10000 + (((c - 0xD800) << 10) | (low - 0xDC00)); + i++; + } + } + } + + if (cp < 0x80) { + utf8 += static_cast(cp); + } else if (cp < 0x800) { + utf8 += static_cast(0xC0 | (cp >> 6)); + utf8 += static_cast(0x80 | (cp & 0x3F)); + } else if (cp < 0x10000) { + utf8 += static_cast(0xE0 | (cp >> 12)); + utf8 += static_cast(0x80 | ((cp >> 6) & 0x3F)); + utf8 += static_cast(0x80 | (cp & 0x3F)); + } else { + utf8 += static_cast(0xF0 | (cp >> 18)); + utf8 += static_cast(0x80 | ((cp >> 12) & 0x3F)); + utf8 += static_cast(0x80 | ((cp >> 6) & 0x3F)); + utf8 += static_cast(0x80 | (cp & 0x3F)); + } + } + return utf8; +} + +std::string code_point_to_utf8(unsigned int cp) { + std::string utf8; + if (cp == 0) return ""; + if (cp < 0x80) { + utf8 += static_cast(cp); + } else if (cp < 0x800) { + utf8 += static_cast(0xC0 | (cp >> 6)); + utf8 += static_cast(0x80 | (cp & 0x3F)); + } else if (cp < 0x10000) { + utf8 += static_cast(0xE0 | (cp >> 12)); + utf8 += static_cast(0x80 | ((cp >> 6) & 0x3F)); + utf8 += static_cast(0x80 | (cp & 0x3F)); + } else if (cp < 0x200000) { + utf8 += static_cast(0xF0 | (cp >> 18)); + utf8 += static_cast(0x80 | ((cp >> 12) & 0x3F)); + utf8 += static_cast(0x80 | ((cp >> 6) & 0x3F)); + utf8 += static_cast(0x80 | (cp & 0x3F)); + } + return utf8; +} + +static int cp1252ToByte(unsigned int cp) { + if (cp <= 0x7F) return static_cast(cp); + if (cp >= 0xA0 && cp <= 0xFF) return static_cast(cp); + switch (cp) { + case 0x20AC: return 0x80; case 0x201A: return 0x82; case 0x0192: return 0x83; + case 0x201E: return 0x84; case 0x2026: return 0x85; case 0x2020: return 0x86; + case 0x2021: return 0x87; case 0x02C6: return 0x88; case 0x2030: return 0x89; + case 0x0160: return 0x8A; case 0x2039: return 0x8B; case 0x0152: return 0x8C; + case 0x017D: return 0x8E; case 0x2018: return 0x91; case 0x2019: return 0x92; + case 0x201C: return 0x93; case 0x201D: return 0x94; case 0x2022: return 0x95; + case 0x2013: return 0x96; case 0x2014: return 0x97; case 0x02DC: return 0x98; + case 0x2122: return 0x99; case 0x0161: return 0x9A; case 0x203A: return 0x9B; + case 0x0153: return 0x9C; case 0x017E: return 0x9E; case 0x0178: return 0x9F; + default: return -1; + } +} + +static std::vector utf8_to_codepoints(const std::string& s) { + std::vector cps; + for (size_t i = 0; i < s.size();) { + unsigned char c = static_cast(s[i]); + unsigned int cp = c; int extra = 0; + if (c < 0x80) { cp = c; extra = 0; } + else if ((c & 0xE0) == 0xC0) { cp = c & 0x1F; extra = 1; } + else if ((c & 0xF0) == 0xE0) { cp = c & 0x0F; extra = 2; } + else if ((c & 0xF8) == 0xF0) { cp = c & 0x07; extra = 3; } + else { cp = c; extra = 0; } + if (i + extra >= s.size()) { cps.push_back(c); i++; continue; } + bool ok = true; + for (int j = 1; j <= extra; ++j) { unsigned char n = static_cast(s[i + j]); if ((n & 0xC0) != 0x80) { ok = false; break; } cp = (cp << 6) | (n & 0x3F); } + if (!ok) { cps.push_back(c); i++; continue; } + cps.push_back(cp); i += extra + 1; + } + return cps; +} + +std::string repairMojibake(const std::string& s) { + auto cps = utf8_to_codepoints(s); + std::string out; + auto emit = [&](unsigned int cp) { out += code_point_to_utf8(cp); }; + for (size_t i = 0; i < cps.size();) { + int b0 = cp1252ToByte(cps[i]); + int need = 0; + if (b0 >= 0xC0 && b0 <= 0xDF) need = 1; + else if (b0 >= 0xE0 && b0 <= 0xEF) need = 2; + else if (b0 >= 0xF0 && b0 <= 0xF7) need = 3; + if (need > 0 && i + need < cps.size()) { + std::string bytes; bytes.push_back(static_cast(b0)); + bool ok = true; + for (int j = 1; j <= need; ++j) { int b = cp1252ToByte(cps[i + j]); if (b < 0x80 || b > 0xBF) { ok = false; break; } bytes.push_back(static_cast(b)); } + if (ok) { auto rd = utf8_to_codepoints(bytes); if (rd.size() == 1 && rd[0] > 0x7F) { emit(rd[0]); i += need + 1; continue; } } + } + emit(cps[i]); i++; + } + return out; +} + +std::string normalizeReflowText(const std::string& s, const fonts::FontFace* face) { + auto cps = utf8_to_codepoints(s); + std::string out; + auto keepIfCovered = [&](unsigned int cp, const char* ascii) { + if (face && face->coversUnicode(cp)) out += code_point_to_utf8(cp); + else out += ascii; + }; + for (unsigned int cp : cps) { + switch (cp) { + case 0x2010: case 0x2011: case 0x2012: case 0x2013: case 0x2014: case 0x2015: + keepIfCovered(cp, "-"); break; + case 0x2018: case 0x2019: case 0x201B: keepIfCovered(cp, "'"); break; + case 0x201C: case 0x201D: case 0x201F: keepIfCovered(cp, "\""); break; + case 0x2026: keepIfCovered(cp, "..."); break; + case 0x00A0: case 0x2002: case 0x2003: case 0x2009: case 0x202F: out += ' '; break; + default: out += code_point_to_utf8(cp); break; + } + } + return out; +} + +} + +namespace pdfengine::parser { + + +std::string makeInternalFontId(const pdfengine::FontInfo& f) { + if (f.isSubset && !f.subsetTag.empty()) return f.fontName; + return f.fontName + "_" + f.type + "_" + std::to_string(f.flags); +} + +std::string classifyFontFidelity(const pdfengine::FontInfo& f) { + if (!f.isEmbedded) return "exact"; + std::string fam = f.normalizedFamily; + std::transform(fam.begin(), fam.end(), fam.begin(), [](unsigned char c){ return std::tolower(c); }); + auto has = [&](const char* s){ return fam.find(s) != std::string::npos; }; + if (fam.empty() || has("times") || has("arial") || has("helvetica") || has("courier") || + has("symbol") || has("zapf")) + return "exact"; + const bool reconstructable = + f.hasToUnicode && + (f.type.find("TrueType") != std::string::npos || + f.type.find("Type0") != std::string::npos || + f.type.find("CIDFontType2") != std::string::npos); + return reconstructable ? "partial" : "substituted"; +} + +std::string baseNameFromInternalFontId(const std::string& internalFontId) { + std::string expected = internalFontId; + size_t lastUnderscore = expected.rfind('_'); + if (lastUnderscore != std::string::npos && lastUnderscore > 0) { + size_t secondLast = expected.rfind('_', lastUnderscore - 1); + if (secondLast != std::string::npos) { + std::string typePart = expected.substr(secondLast + 1, lastUnderscore - secondLast - 1); + if (typePart == "TrueType" || typePart == "Type1" || + typePart == "CIDFontType0" || typePart == "CIDFontType2") { + expected = expected.substr(0, secondLast); + } + } + } + return expected; +} + +#ifdef PDFENGINE_WITH_PDFIUM + +struct PngWriteState { + std::vector* buffer; +}; + +void pngWriteCallback(png_structp png_ptr, png_bytep data, png_size_t length) { + auto* state = reinterpret_cast(png_get_io_ptr(png_ptr)); + state->buffer->insert(state->buffer->end(), data, data + length); +} + +void pngFlushCallback(png_structp png_ptr) { + (void)png_ptr; +} + +std::vector encodeBgraToPng(const uint8_t* bgra, int width, int height, int stride) { + std::vector pngBytes; + png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!png) return {}; + + png_infop info = png_create_info_struct(png); + if (!info) { + png_destroy_write_struct(&png, nullptr); + return {}; + } + + #pragma warning(push) + #pragma warning(disable: 4611) + if (setjmp(png_jmpbuf(png))) { + png_destroy_write_struct(&png, &info); + return {}; + } + #pragma warning(pop) + + PngWriteState state{&pngBytes}; + png_set_write_fn(png, &state, pngWriteCallback, pngFlushCallback); + + png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + + png_write_info(png, info); + png_set_bgr(png); + + std::vector rowPointers(height); + for (int y = 0; y < height; ++y) { + rowPointers[y] = const_cast(bgra + y * stride); + } + + png_write_image(png, rowPointers.data()); + png_write_end(png, nullptr); + png_destroy_write_struct(&png, &info); + + return pngBytes; +} + +pdfengine::EngineError mapPdfiumError(unsigned long err, bool passwordProvided) { + switch (err) { + case FPDF_ERR_SUCCESS: + return pdfengine::EngineError::Unknown; + case FPDF_ERR_FILE: + return pdfengine::EngineError::FileNotFound; + case FPDF_ERR_FORMAT: + return pdfengine::EngineError::InvalidFormat; + case FPDF_ERR_PASSWORD: + return passwordProvided ? pdfengine::EngineError::InvalidPassword + : pdfengine::EngineError::PasswordRequired; + default: + return pdfengine::EngineError::Unknown; + } +} +#endif + +#ifdef PDFENGINE_WITH_PDFIUM +struct PdfiumGlobalInit { + PdfiumGlobalInit() { + pdfengine::parser::pdfiumInitLibrary(); + } + ~PdfiumGlobalInit() { + pdfengine::parser::pdfiumDestroyLibrary(); + } +}; + +void ensure_pdfium_initialized() { + static PdfiumGlobalInit init; +} + +std::string normalizeFamilyName(const std::string& fontName) { + std::string name = fontName; + if (name.size() > 7 && name[6] == '+') { + name = name.substr(7); + } + + size_t sep = name.find_first_of("-,"); + if (sep != std::string::npos) { + name = name.substr(0, sep); + } + + auto cleanName = name; + auto lower = name; + std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char c) { return static_cast(::tolower(c)); }); + + std::vector suffixes = {"bold", "italic", "oblique", "regular", "medium", "light", "heavy", "black", "condensed", "mt", "ps"}; + for (const auto& s : suffixes) { + size_t pos = lower.rfind(s); + if (pos != std::string::npos && pos + s.size() == lower.size()) { + cleanName = cleanName.substr(0, pos); + lower = lower.substr(0, pos); + } + } + + while (!cleanName.empty() && (cleanName.back() == '-' || cleanName.back() == ' ' || cleanName.back() == '_')) { + cleanName.pop_back(); + } + + if (cleanName.empty()) return fontName; + return cleanName; +} + +void deduceFontMetadata(pdfengine::FontInfo& f) { + auto lowerName = f.fontName; + std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), [](unsigned char c) { return static_cast(::tolower(c)); }); + + if (f.fontName.size() > 7 && f.fontName[6] == '+') { + f.isSubset = true; + f.subsetTag = f.fontName.substr(0, 6); + bool validTag = true; + for (int ti = 0; ti < 6; ++ti) { + if (!std::isupper(static_cast(f.fontName[ti]))) { + validTag = false; break; + } + } + if (!validTag) { f.isSubset = false; f.subsetTag = ""; } + } else { + f.isSubset = false; + f.subsetTag = ""; + } + f.normalizedFamily = normalizeFamilyName(f.fontName); + + if (f.fontName.find("Identity-H") != std::string::npos) { + f.encoding = "Identity-H"; + f.cmapName = "Identity-H"; + } else if (f.fontName.find("Identity-V") != std::string::npos) { + f.encoding = "Identity-V"; + f.cmapName = "Identity-V"; + f.isVertical = true; + } else if (lowerName.find("symbol") != std::string::npos) { + f.encoding = "Symbol"; + f.cmapName = "None"; + } else { + f.encoding = "WinAnsiEncoding"; + f.cmapName = "None"; + } + + if (lowerName.find("symbol") != std::string::npos) { + f.hasToUnicode = false; + } else { + f.hasToUnicode = true; + } + + if (lowerName.find("simsun") != std::string::npos || + lowerName.find("simhei") != std::string::npos || + lowerName.find("heiti") != std::string::npos || + lowerName.find("fangsong") != std::string::npos || + lowerName.find("kaiti") != std::string::npos || + lowerName.find("song") != std::string::npos || + lowerName.find("gb") != std::string::npos) { + f.cidSystemInfo = "Adobe-GB1"; + } else if (lowerName.find("gothic") != std::string::npos || + lowerName.find("ms-gothic") != std::string::npos || + lowerName.find("msgothic") != std::string::npos || + lowerName.find("mincho") != std::string::npos || + lowerName.find("kozuka") != std::string::npos || + lowerName.find("hiragino") != std::string::npos || + lowerName.find("japan") != std::string::npos || + lowerName.find("heisei") != std::string::npos || + lowerName.find("morisawa") != std::string::npos || + lowerName.find("ryumin") != std::string::npos) { + f.cidSystemInfo = "Adobe-Japan1"; + } else if (lowerName.find("malgun") != std::string::npos || + lowerName.find("gulim") != std::string::npos || + lowerName.find("batang") != std::string::npos || + lowerName.find("dotum") != std::string::npos || + lowerName.find("korea") != std::string::npos || + lowerName.find("hangul") != std::string::npos || + lowerName.find("korean") != std::string::npos) { + f.cidSystemInfo = "Adobe-Korea1"; + } else if (lowerName.find("sung") != std::string::npos || + lowerName.find("ming") != std::string::npos || + lowerName.find("cns") != std::string::npos || + lowerName.find("traditional") != std::string::npos) { + f.cidSystemInfo = "Adobe-CNS1"; + } else { + f.cidSystemInfo = "None"; + } + + if (f.cidSystemInfo != "None" && f.cmapName == "None") { + f.cmapName = f.isVertical ? "UniJIS-UTF16-V" : "Identity-H"; + } + + bool isCid = (f.encoding == "Identity-H" || f.encoding == "Identity-V" || f.cidSystemInfo != "None"); + if (isCid) { + if (lowerName.find("bold") != std::string::npos || lowerName.find("italic") != std::string::npos) { + f.type = "CIDFontType0"; + } else { + f.type = "CIDFontType2"; + } + } else { + static const std::vector type1Names = { + "times", "helvetica", "courier", "symbol", "zapfdingbats", + "liberation", "palatino", "bookman", "new century", "avant garde" + }; + bool isType1 = false; + for (const auto& t1 : type1Names) { + if (lowerName.find(t1) != std::string::npos) { isType1 = true; break; } + } + f.type = isType1 ? "Type1" : "TrueType"; + } + + if (f.isSubset) { + f.isEmbedded = true; + f.sourceType = "Embedded"; + f.substitutedFrom = ""; + f.substitutedTo = ""; + } else { + static const std::vector standard14 = { + "helvetica", "times", "courier", "symbol", "zapfdingbats" + }; + bool isStandard14 = false; + for (const auto& s14 : standard14) { + if (lowerName.find(s14) != std::string::npos) { isStandard14 = true; break; } + } + bool isSystemFont = isStandard14 || + lowerName.find("arial") != std::string::npos || + lowerName.find("liberation") != std::string::npos || + lowerName.find("dejavu") != std::string::npos || + lowerName.find("freefont") != std::string::npos; + if (isSystemFont) { + f.isEmbedded = false; + f.sourceType = "SystemFallback"; + f.substitutedFrom = ""; + f.substitutedTo = ""; + } else { + f.isEmbedded = false; + f.sourceType = "Substituted"; + f.substitutedFrom = f.fontName; + #if defined(_WIN32) + f.substitutedTo = "Arial"; + #else + f.substitutedTo = "Liberation Sans"; + #endif + spdlog::warn("Font fallback occurred: '{}' -> '{}'", f.substitutedFrom, f.substitutedTo); + } + } + + f.internalFontId = makeInternalFontId(f); + + if (lowerName.find("times") != std::string::npos) { + f.ascent = 891.0; + f.descent = -216.0; + f.capHeight = 662.0; + } else if (lowerName.find("courier") != std::string::npos) { + f.ascent = 629.0; + f.descent = -157.0; + f.capHeight = 562.0; + } else if (lowerName.find("symbol") != std::string::npos) { + f.ascent = 1010.0; + f.descent = -293.0; + f.capHeight = 673.0; + } else if (lowerName.find("helvetica") != std::string::npos) { + f.ascent = 905.0; + f.descent = -211.0; + f.capHeight = 728.0; + } else { + f.ascent = 905.0; + f.descent = -211.0; + f.capHeight = 728.0; + } +} + + +std::unordered_map +buildFontPdfDataMap(FPDF_PAGE page, FPDF_TEXTPAGE textPage) { + std::unordered_map result; + if (!page) return result; + + int objectCount = FPDFPage_CountObjects(page); + for (int i = 0; i < objectCount; ++i) { + FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, i); + if (!obj) continue; + if (FPDFPageObj_GetType(obj) != FPDF_PAGEOBJ_TEXT) continue; + + FPDF_FONT font = FPDFTextObj_GetFont(obj); + if (!font) continue; + + size_t nameLen = FPDFFont_GetBaseFontName(font, nullptr, 0); + if (nameLen == 0) continue; + std::vector nameBuf(nameLen); + if (FPDFFont_GetBaseFontName(font, nameBuf.data(), nameLen) == 0) continue; + std::string fontName(nameBuf.data()); + if (fontName.empty()) continue; + + if (result.count(fontName)) continue; + + FontPdfData data; + data.valid = true; + data.isEmbedded = (FPDFFont_GetIsEmbedded(font) != 0); + data.flags = FPDFFont_GetFlags(font); + + float rawAscent = 0.0f; + if (FPDFFont_GetAscent(font, 1000.0f, &rawAscent) && rawAscent != 0.0f) { + data.ascent = static_cast(rawAscent); + } + float rawDescent = 0.0f; + if (FPDFFont_GetDescent(font, 1000.0f, &rawDescent) && rawDescent != 0.0f) { + data.descent = static_cast(rawDescent); + } + + result[fontName] = data; + } + + if (textPage && !result.empty()) { + int charCount = FPDFText_CountChars(textPage); + if (charCount > 0 && charCount < 1000000) { + for (int ci = 0; ci < charCount; ++ci) { + int fi = 0; + unsigned long flen = + FPDFText_GetFontInfo(textPage, ci, nullptr, 0, &fi); + if (flen == 0) continue; + std::vector fbuf(flen); + if (FPDFText_GetFontInfo(textPage, ci, fbuf.data(), flen, &fi) == 0) + continue; + std::string fname(fbuf.data()); + + auto it = result.find(fname); + if (it == result.end() || it->second.hasUnicodeMapping) continue; + + unsigned int cp = FPDFText_GetUnicode(textPage, ci); + if (cp > 0x0020 && cp != 0xFFFD) { + it->second.hasUnicodeMapping = true; + } + } + } + } + + return result; +} + +#endif + +void parseHexColor(const std::string& hex, unsigned int& r, unsigned int& g, unsigned int& b) { + r = 0; g = 0; b = 0; + if (hex.empty()) return; + std::string s = hex; + if (s[0] == '#') { + s = s.substr(1); + } + if (s.size() == 6) { + try { + r = std::stoul(s.substr(0, 2), nullptr, 16); + g = std::stoul(s.substr(2, 2), nullptr, 16); + b = std::stoul(s.substr(4, 2), nullptr, 16); + } catch (...) { + r = 0; g = 0; b = 0; + } + } +} + +std::vector base64Decode(const std::string& encoded) { + std::vector decoded; + int T[256]; + std::fill(std::begin(T), std::end(T), -1); + for (int i = 0; i < 64; ++i) { + T[static_cast("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i])] = i; + } + + int val = 0; + int valb = -8; + for (char c : encoded) { + unsigned char uc = static_cast(c); + if (T[uc] == -1) continue; + val = (val << 6) + T[uc]; + valb += 6; + if (valb >= 0) { + decoded.push_back(static_cast((val >> valb) & 0xFF)); + valb -= 8; + } + } + return decoded; +} + + +} \ No newline at end of file diff --git a/engine/src/parser/pdfium_internal.hpp b/engine/src/parser/pdfium_internal.hpp new file mode 100644 index 0000000..16bdb8f --- /dev/null +++ b/engine/src/parser/pdfium_internal.hpp @@ -0,0 +1,109 @@ +#pragma once + +#include "parser/pdfium_document.hpp" +#ifdef PDFENGINE_WITH_PDFIUM +#include +#include +#include +#include +#include +#include +#include +#include +#include "parser/pdfium_loader.hpp" +#endif + +#include "fonts/loader/font_resolver.hpp" +#include "fonts/pdf_fonts/font.hpp" +#include "pdfengine/hardened_limits.h" +#include "fonts/pdf_fonts/font_fallback.hpp" +#include "fonts/pdf_fonts/font_subset.hpp" +#include "fonts/shaping/hb_shaper.hpp" +#include "fonts/face/font_face.hpp" +#include "decoration_builder.hpp" +#include "qpdf/qpdf_extractor.hpp" +#include "qpdf/qpdf_font_extractor.hpp" +#include "parser/lexer.hpp" +#include "parser/parser.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace pdfengine::parser { + +std::string makeInternalFontId(const pdfengine::FontInfo& f); +std::string classifyFontFidelity(const pdfengine::FontInfo& f); +std::string baseNameFromInternalFontId(const std::string& internalFontId); +void parseHexColor(const std::string& hex, unsigned int& r, unsigned int& g, unsigned int& b); +std::vector base64Decode(const std::string& encoded); +std::string repairMojibake(const std::string& s); +std::string normalizeReflowText(const std::string& s, const fonts::FontFace* face); + +#ifdef PDFENGINE_WITH_PDFIUM + +struct VectorWriter : public FPDF_FILEWRITE { + std::vector buffer; + + static int WriteBlockCallback(FPDF_FILEWRITE* pThis, const void* pData, unsigned long size) { + auto* self = static_cast(pThis); + const auto* bytes = static_cast(pData); + self->buffer.insert(self->buffer.end(), bytes, bytes + size); + return 1; + } + + VectorWriter() { + this->version = 1; + this->WriteBlock = &VectorWriter::WriteBlockCallback; + } +}; + +struct FontPdfData { + bool valid = false; + bool isEmbedded = false; + int flags = 0; + double ascent = 0.0; + double descent = 0.0; + bool hasUnicodeMapping = false; +}; + +struct PageBand { + double pageW = 0.0, pageH = 0.0; + double placementTopY = 0.0; + double bottomLimitY = 0.0; + bool valid = false; +}; + +std::vector encodeBgraToPng(const uint8_t* bgra, int width, int height, int stride); +pdfengine::EngineError mapPdfiumError(unsigned long err, bool passwordProvided); +void ensure_pdfium_initialized(); +void deduceFontMetadata(pdfengine::FontInfo& f); +std::unordered_map buildFontPdfDataMap(FPDF_PAGE page, FPDF_TEXTPAGE textPage); + +void repagSetParaId(FPDF_DOCUMENT doc, FPDF_PAGEOBJECT obj, const std::string& paraId); +std::string repagGetParaId(FPDF_PAGEOBJECT obj); +void walkOutline(FPDF_DOCUMENT doc, FPDF_BOOKMARK bm, int level, + std::vector& out); +PageBand repagComputeBand(FPDF_PAGE page, double pushColLeft, double colRight); +std::vector repagAnchoredCenters(FPDF_PAGE page, double pushColLeft, double colRight, + double bottomLimitY); +int repaginateForward(FPDF_DOCUMENT doc, int startPage, + double colLeft, double colRight, double pushColLeft, double leading, + const std::vector& anchoredCentersStart, int* movedOut = nullptr); +int repaginateBackward(FPDF_DOCUMENT doc, int startPage, + double colLeft, double colRight, double pushColLeft, double leading); +int repagRemoveContinuations(FPDF_DOCUMENT doc, int anchorPage, const std::string& paraId, + double pushColLeft, double colRight); + +#endif + +} \ No newline at end of file diff --git a/engine/src/parser/pdfium_page.cpp b/engine/src/parser/pdfium_page.cpp new file mode 100644 index 0000000..36fd3cf --- /dev/null +++ b/engine/src/parser/pdfium_page.cpp @@ -0,0 +1,390 @@ +#include "parser/pdfium_internal.hpp" + +namespace pdfengine::parser { + +PdfiumPage::PdfiumPage(NativeDocHandle docHandle, NativePageHandle pageHandle, int pageIndex, + std::shared_ptr owner) + : doc_(docHandle), page_(pageHandle), pageIndex_(pageIndex), ownerDoc_(std::move(owner)) { +} + +PdfiumPage::~PdfiumPage() { +#ifdef PDFENGINE_WITH_PDFIUM + std::lock_guard lock(textMutex_); + if (textPage_) { + FPDFText_ClosePage(textPage_); + } + if (page_) { + FPDF_ClosePage(page_); + } +#endif +} + +PdfiumPage::PdfiumPage(PdfiumPage&& other) noexcept { + *this = std::move(other); +} + +PdfiumPage& PdfiumPage::operator=(PdfiumPage&& other) noexcept { + if (this != &other) { +#ifdef PDFENGINE_WITH_PDFIUM + std::lock_guard lock(textMutex_); + if (textPage_) FPDFText_ClosePage(textPage_); + if (page_) FPDF_ClosePage(page_); +#endif + doc_ = other.doc_; + page_ = other.page_; + textPage_ = other.textPage_; + pageIndex_ = other.pageIndex_; + ownerDoc_ = std::move(other.ownerDoc_); + + other.doc_ = nullptr; + other.page_ = nullptr; + other.textPage_ = nullptr; + other.pageIndex_ = 0; + } + return *this; +} + +double PdfiumPage::width() const noexcept { +#ifdef PDFENGINE_WITH_PDFIUM + return page_ ? FPDF_GetPageWidthF(page_) : 0.0; +#else + return 0.0; +#endif +} + +double PdfiumPage::height() const noexcept { +#ifdef PDFENGINE_WITH_PDFIUM + return page_ ? FPDF_GetPageHeightF(page_) : 0.0; +#else + return 0.0; +#endif +} + +std::expected PdfiumPage::render(int dpi) const { +#ifdef PDFENGINE_WITH_PDFIUM + if (!page_) { + return std::unexpected(EngineError::Unknown); + } + + if (!limits::pageDimensionsOk(width(), height())) { + return std::unexpected(EngineError::RenderFailed); + } + + if (!limits::objectCountOk(FPDFPage_CountObjects(page_))) { + spdlog::error("Refusing to render page: object count exceeds hardened limit ({})", + limits::kMaxObjects); + return std::unexpected(EngineError::RenderFailed); + } + + double scale = dpi / 72.0; + int w = static_cast(width() * scale); + int h = static_cast(height() * scale); + + if (!limits::rasterSizeOk(w, h)) { + return std::unexpected(EngineError::RenderFailed); + } + + FPDF_BITMAP bitmap = FPDFBitmap_Create(w, h, 1); + if (!bitmap) { + return std::unexpected(EngineError::RenderFailed); + } + + FPDFBitmap_FillRect(bitmap, 0, 0, w, h, 0xFFFFFFFF); + + FPDF_RenderPageBitmap(bitmap, page_, 0, 0, w, h, 0, 0); + + + const auto* buffer = static_cast(FPDFBitmap_GetBuffer(bitmap)); + int stride = FPDFBitmap_GetStride(bitmap); + + std::vector pngBytes = encodeBgraToPng(buffer, w, h, stride); + FPDFBitmap_Destroy(bitmap); + + if (pngBytes.empty()) { + return std::unexpected(EngineError::RenderFailed); + } + + return PageImage{w, h, std::move(pngBytes)}; +#else + (void)dpi; + return std::unexpected(EngineError::Unknown); +#endif +} + +std::expected PdfiumPage::renderRegionRaw(int dpi, double yTopPt, double heightPt) const { +#ifdef PDFENGINE_WITH_PDFIUM + if (!page_) return std::unexpected(EngineError::Unknown); + if (!limits::pageDimensionsOk(width(), height())) return std::unexpected(EngineError::RenderFailed); + if (!limits::objectCountOk(FPDFPage_CountObjects(page_))) return std::unexpected(EngineError::RenderFailed); + + const double scale = dpi / 72.0; + const int w = static_cast(width() * scale); + const int fullH = static_cast(height() * scale); + int yTopPx = static_cast(yTopPt * scale); + if (yTopPx < 0) yTopPx = 0; + if (yTopPx > fullH) yTopPx = fullH; + int regionH = heightPt > 0 ? static_cast(heightPt * scale) : (fullH - yTopPx); + if (regionH > fullH - yTopPx) regionH = fullH - yTopPx; + if (w <= 0 || regionH <= 0) return std::unexpected(EngineError::RenderFailed); + if (!limits::rasterSizeOk(w, regionH)) return std::unexpected(EngineError::RenderFailed); + + FPDF_BITMAP bitmap = FPDFBitmap_Create(w, regionH, 1); + if (!bitmap) return std::unexpected(EngineError::RenderFailed); + FPDFBitmap_FillRect(bitmap, 0, 0, w, regionH, 0xFFFFFFFF); + FPDF_RenderPageBitmap(bitmap, page_, 0, -yTopPx, w, fullH, 0, 0); + + const auto* bgra = static_cast(FPDFBitmap_GetBuffer(bitmap)); + const int stride = FPDFBitmap_GetStride(bitmap); + std::vector rgba(static_cast(w) * regionH * 4); + for (int y = 0; y < regionH; ++y) { + const uint8_t* src = bgra + static_cast(y) * stride; + uint8_t* dst = rgba.data() + static_cast(y) * w * 4; + for (int x = 0; x < w; ++x) { + dst[x * 4 + 0] = src[x * 4 + 2]; + dst[x * 4 + 1] = src[x * 4 + 1]; + dst[x * 4 + 2] = src[x * 4 + 0]; + dst[x * 4 + 3] = src[x * 4 + 3]; + } + } + FPDFBitmap_Destroy(bitmap); + return PageImage{w, regionH, std::move(rgba)}; +#else + (void)dpi; (void)yTopPt; (void)heightPt; + return std::unexpected(EngineError::Unknown); +#endif +} + +std::expected PdfiumPage::extractText() const { +#ifdef PDFENGINE_WITH_PDFIUM + if (!page_) { + return std::unexpected(EngineError::Unknown); + } + ensureTextPageLoaded(); + if (!textPage_) { + return std::unexpected(EngineError::Unknown); + } + + int charCount = FPDFText_CountChars(textPage_); + if (charCount <= 0) { + return ""; + } + + std::vector buffer(charCount + 1, 0); + int written = FPDFText_GetText(textPage_, 0, charCount, buffer.data()); + if (written <= 0) { + return ""; + } + + return utf16le_to_utf8(reinterpret_cast(buffer.data()), written); +#else + return std::unexpected(EngineError::Unknown); +#endif +} + +std::expected, EngineError> PdfiumPage::extractTextWithBounds() const { +#ifdef PDFENGINE_WITH_PDFIUM + if (!page_) { + return std::unexpected(EngineError::Unknown); + } + ensureTextPageLoaded(); + if (!textPage_) { + return std::unexpected(EngineError::Unknown); + } + + int charCount = FPDFText_CountChars(textPage_); + std::vector result; + if (charCount <= 0) { + return result; + } + + result.reserve(charCount); + + for (int i = 0; i < charCount; ++i) { + unsigned int codeUnit = FPDFText_GetUnicode(textPage_, i); + + unsigned int cp = codeUnit; + double left = 0, right = 0, bottom = 0, top = 0; + double fontSize = FPDFText_GetFontSize(textPage_, i); + + 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); + + double l1 = 0, r1 = 0, b1 = 0, t1 = 0; + FPDFText_GetCharBox(textPage_, i, &l1, &r1, &b1, &t1); + + double l2 = 0, r2 = 0, b2 = 0, t2 = 0; + FPDFText_GetCharBox(textPage_, i + 1, &l2, &r2, &b2, &t2); + + left = (std::min)(l1, l2); + right = (std::max)(r1, r2); + bottom = (std::min)(b1, b2); + top = (std::max)(t1, t2); + + ++i; + } else { + FPDFText_GetCharBox(textPage_, i, &left, &right, &bottom, &top); + } + } else { + FPDFText_GetCharBox(textPage_, i, &left, &right, &bottom, &top); + } + + std::string utf8_char = code_point_to_utf8(cp); + if (utf8_char.empty() || cp == '\r' || cp == '\n') { + continue; + } + + double x = (std::min)(left, right); + double y = (std::min)(bottom, top); + double w = std::abs(right - left); + double h = std::abs(top - bottom); + + GlyphBounds gb; + gb.text = std::move(utf8_char); + gb.x = x; + gb.y = y; + gb.w = w; + gb.h = h; + gb.fontSize = fontSize; + result.push_back(gb); + } + + return result; +#else + return std::unexpected(EngineError::Unknown); +#endif +} +DevicePoint PdfiumPage::pageToDevice(const Point2D& pagePoint, int deviceWidth, int deviceHeight, int rotate) const noexcept { +#ifdef PDFENGINE_WITH_PDFIUM + if (!page_) return {0, 0}; + int dx = 0, dy = 0; + FPDF_PageToDevice(page_, 0, 0, deviceWidth, deviceHeight, rotate, pagePoint.x, pagePoint.y, &dx, &dy); + return {dx, dy}; +#else + (void)pagePoint; (void)deviceWidth; (void)deviceHeight; (void)rotate; + return {0, 0}; +#endif +} + +Point2D PdfiumPage::deviceToPage(const DevicePoint& devicePoint, int deviceWidth, int deviceHeight, int rotate) const noexcept { +#ifdef PDFENGINE_WITH_PDFIUM + if (!page_) return {0.0, 0.0}; + double px = 0.0, py = 0.0; + FPDF_DeviceToPage(page_, 0, 0, deviceWidth, deviceHeight, rotate, devicePoint.x, devicePoint.y, &px, &py); + return {px, py}; +#else + (void)devicePoint; (void)deviceWidth; (void)deviceHeight; (void)rotate; + return {0.0, 0.0}; +#endif +} + +std::expected PdfiumPage::getGlyphWidth(const std::string& fontName, uint32_t charcode, double fontSize) const { +#ifdef PDFENGINE_WITH_PDFIUM + if (!page_) { + return std::unexpected(EngineError::Unknown); + } + + FPDF_FONT font = nullptr; + { + std::lock_guard lock(textMutex_); + + auto cached = fontHandleCache_.find(fontName); + if (cached != fontHandleCache_.end()) { + font = cached->second; + } else { + int objectCount = FPDFPage_CountObjects(page_); + for (int i = 0; i < objectCount; ++i) { + FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page_, i); + if (!obj || FPDFPageObj_GetType(obj) != FPDF_PAGEOBJ_TEXT) continue; + + FPDF_FONT pageFont = FPDFTextObj_GetFont(obj); + if (!pageFont) continue; + + size_t nameLen = FPDFFont_GetBaseFontName(pageFont, nullptr, 0); + if (nameLen > 0) { + std::vector nameBuf(nameLen); + if (FPDFFont_GetBaseFontName(pageFont, nameBuf.data(), nameLen) > 0) { + std::string currentName(nameBuf.data()); + if (currentName == fontName) { + font = pageFont; + fontHandleCache_[fontName] = font; + break; + } + } + } + } + } + } + + if (!font) { + return std::unexpected(EngineError::Unknown); + } + + float width = 0.0f; + if (!FPDFFont_GetGlyphWidth(font, charcode, static_cast(fontSize), &width)) { + return std::unexpected(EngineError::Unknown); + } + + return static_cast(width); +#else + (void)fontName; (void)charcode; (void)fontSize; + return std::unexpected(EngineError::Unknown); +#endif +} + +void PdfiumPage::ensureTextPageLoaded() const { +#ifdef PDFENGINE_WITH_PDFIUM + std::lock_guard lock(textMutex_); + if (!textPage_ && page_) { + textPage_ = FPDFText_LoadPage(page_); + } +#endif +} +std::expected PdfiumPage::extractDisplayListJson() const { +#if defined(PDFENGINE_WITH_PDFIUM) && defined(PDFENGINE_WITH_QPDF) + if (!ownerDoc_) return std::unexpected(EngineError::Unknown); + const auto& buffer = ownerDoc_->getMemoryBuffer(); + if (buffer.empty()) return std::unexpected(EngineError::FileNotFound); + + qpdf_layer::QpdfExtractor extractor; + auto stream = extractor.extractPageStreamFromMemory(buffer, pageIndex_); + if (!stream) return std::unexpected(EngineError::InvalidFormat); + + Lexer lexer(stream->decodedContent); + auto tokens = lexer.tokenize(); + ContentParser parser(tokens); + auto operations = parser.parse(); + + nlohmann::json j_array = nlohmann::json::array(); + for (const auto& op : operations) { + nlohmann::json j_op = nlohmann::json::object(); + j_op["op"] = op.op; + if (!op.operands.empty()) { + nlohmann::json j_args = nlohmann::json::array(); + for (const auto& arg : op.operands) { + if (arg->type == AstNodeType::Number) { + j_args.push_back(arg->numberValue); + } else if (arg->type == AstNodeType::Name || arg->type == AstNodeType::String) { + j_args.push_back(arg->stringValue); + } else { + j_args.push_back(""); + } + } + j_op["args"] = j_args; + } + j_array.push_back(j_op); + } + return j_array.dump(); +#else + return std::unexpected(EngineError::Unknown); +#endif +} + +std::expected, EngineError> PdfiumPage::extractImageXObject(const std::string& name) const { + (void)name; + return std::unexpected(EngineError::Unknown); +} + + +} \ No newline at end of file diff --git a/engine/src/parser/pdfium_page_model.cpp b/engine/src/parser/pdfium_page_model.cpp new file mode 100644 index 0000000..9adac58 --- /dev/null +++ b/engine/src/parser/pdfium_page_model.cpp @@ -0,0 +1,634 @@ +#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; + } + } + + 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) { + 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)); + } + } + + result.push_back(info); + } + + FPDFPage_CloseAnnot(annot); + } + + FPDFDOC_ExitFormFillEnvironment(formHandle); + + return result; +#else + return std::unexpected(EngineError::Unknown); +#endif +} + + +} \ No newline at end of file diff --git a/engine/src/parser/pdfium_reflow.cpp b/engine/src/parser/pdfium_reflow.cpp new file mode 100644 index 0000000..4786b8f --- /dev/null +++ b/engine/src/parser/pdfium_reflow.cpp @@ -0,0 +1,286 @@ +#include "parser/pdfium_internal.hpp" + +namespace pdfengine::parser { +#ifdef PDFENGINE_WITH_PDFIUM + +void repagSetParaId(FPDF_DOCUMENT doc, FPDF_PAGEOBJECT obj, const std::string& paraId) { + if (paraId.empty() || !obj) return; + FPDF_PAGEOBJECTMARK mark = FPDFPageObj_AddMark(obj, "PDFPARA"); + if (mark) FPDFPageObjMark_SetStringParam(doc, obj, mark, "id", paraId.c_str()); +} + +std::string repagGetParaId(FPDF_PAGEOBJECT obj) { + if (!obj) return ""; + int n = FPDFPageObj_CountMarks(obj); + for (int i = 0; i < n; ++i) { + FPDF_PAGEOBJECTMARK mark = FPDFPageObj_GetMark(obj, i); + if (!mark) continue; + unsigned long nl = 0; + FPDFPageObjMark_GetName(mark, nullptr, 0, &nl); + if (nl < 2) continue; + std::vector nb(nl / 2); + FPDFPageObjMark_GetName(mark, reinterpret_cast(nb.data()), nl, &nl); + std::string name = utf16le_to_utf8(reinterpret_cast(nb.data()), nb.size()); + while (!name.empty() && name.back() == '\0') name.pop_back(); + if (name != "PDFPARA") continue; + unsigned long vl = 0; + if (!FPDFPageObjMark_GetParamStringValue(mark, "id", nullptr, 0, &vl) || vl < 2) continue; + std::vector vb(vl / 2); + if (!FPDFPageObjMark_GetParamStringValue(mark, "id", reinterpret_cast(vb.data()), vl, &vl)) + continue; + std::string val = utf16le_to_utf8(reinterpret_cast(vb.data()), vb.size()); + while (!val.empty() && val.back() == '\0') val.pop_back(); + return val; + } + return ""; +} + +void walkOutline(FPDF_DOCUMENT doc, FPDF_BOOKMARK bm, int level, + std::vector& out) { + while (bm && level <= 32 && out.size() < 5000) { + PdfDocument::OutlineItem item; + item.level = level; + + unsigned long len = FPDFBookmark_GetTitle(bm, nullptr, 0); + if (len > 2) { + std::vector buf(len / 2); + FPDFBookmark_GetTitle(bm, buf.data(), len); + item.title = utf16le_to_utf8(reinterpret_cast(buf.data()), buf.size()); + while (!item.title.empty() && item.title.back() == '\0') item.title.pop_back(); + } + + FPDF_DEST dest = FPDFBookmark_GetDest(doc, bm); + if (!dest) { + FPDF_ACTION action = FPDFBookmark_GetAction(bm); + if (action) dest = FPDFAction_GetDest(doc, action); + } + item.pageIndex = dest ? static_cast(FPDFDest_GetDestPageIndex(doc, dest)) : -1; + + out.push_back(std::move(item)); + + FPDF_BOOKMARK child = FPDFBookmark_GetFirstChild(doc, bm); + if (child) walkOutline(doc, child, level + 1, out); + bm = FPDFBookmark_GetNextSibling(doc, bm); + } +} + + +inline bool repagInColumn(FPDF_PAGEOBJECT o, double pushColLeft, double colRight) { + float l = 0, b = 0, r = 0, t = 0; + if (!FPDFPageObj_GetBounds(o, &l, &b, &r, &t)) return false; + return (r > pushColLeft && l < colRight); +} + +PageBand repagComputeBand(FPDF_PAGE page, double pushColLeft, double colRight) { + PageBand band; + band.pageW = FPDF_GetPageWidthF(page); + band.pageH = FPDF_GetPageHeightF(page); + double maxTop = -1e18; bool any = false; + int n = FPDFPage_CountObjects(page); + for (int k = 0; k < n; ++k) { + FPDF_PAGEOBJECT o = FPDFPage_GetObject(page, k); + if (!o) continue; + float l = 0, b = 0, r = 0, t = 0; + if (!FPDFPageObj_GetBounds(o, &l, &b, &r, &t)) continue; + if (r > pushColLeft && l < colRight) { if (t > maxTop) { maxTop = t; } any = true; } + } + if (!any) return band; + band.placementTopY = maxTop; + double limit = band.pageH - maxTop; + if (limit < 18.0) limit = 18.0; + if (limit > band.pageH * 0.25) limit = band.pageH * 0.25; + band.bottomLimitY = limit; + band.valid = true; + return band; +} + +std::vector repagAnchoredCenters(FPDF_PAGE page, double pushColLeft, double colRight, + double bottomLimitY) { + std::vector out; + int n = FPDFPage_CountObjects(page); + for (int k = 0; k < n; ++k) { + FPDF_PAGEOBJECT o = FPDFPage_GetObject(page, k); + if (!o || !repagInColumn(o, pushColLeft, colRight)) continue; + float l = 0, b = 0, r = 0, t = 0; + if (!FPDFPageObj_GetBounds(o, &l, &b, &r, &t)) continue; + double cy = (b + t) / 2.0; + if (cy < bottomLimitY) out.push_back(cy); + } + return out; +} + +inline bool repagIsAnchored(double cy, const std::vector& anchored, double tol = 1.0) { + for (double ac : anchored) if (std::abs(ac - cy) < tol) return true; + return false; +} + +int repaginateForward(FPDF_DOCUMENT doc, int startPage, + double colLeft, double colRight, double pushColLeft, double leading, + const std::vector& anchoredCentersStart, int* movedOut) { + (void)colLeft; + const int kMaxPagesAdded = 200; + int pagesAdded = 0; + int movedTotal = 0; + int curPage = startPage; + std::vector anchored = anchoredCentersStart; + + while (true) { + FPDF_PAGE P = FPDF_LoadPage(doc, curPage); + if (!P) break; + PageBand band = repagComputeBand(P, pushColLeft, colRight); + if (!band.valid) { FPDF_ClosePage(P); break; } + + std::vector overflow; + double blockTop = -1e18, blockBottom = 1e18; + int n = FPDFPage_CountObjects(P); + for (int k = 0; k < n; ++k) { + FPDF_PAGEOBJECT o = FPDFPage_GetObject(P, k); + if (!o || !repagInColumn(o, pushColLeft, colRight)) continue; + float l = 0, b = 0, r = 0, t = 0; + if (!FPDFPageObj_GetBounds(o, &l, &b, &r, &t)) continue; + double cy = (b + t) / 2.0; + if (cy >= band.bottomLimitY) continue; + if (repagIsAnchored(cy, anchored)) continue; + overflow.push_back(o); + if (t > blockTop) blockTop = t; + if (b < blockBottom) blockBottom = b; + } + if (overflow.empty()) { FPDF_ClosePage(P); break; } + + int total = FPDF_GetPageCount(doc); + FPDF_PAGE Q = nullptr; + bool createdNew = false; + if (curPage + 1 >= total) { + if (pagesAdded + 1 > kMaxPagesAdded) { + spdlog::warn("repaginateForward: hit page-add cap ({}), stopping", kMaxPagesAdded); + FPDF_ClosePage(P); break; + } + Q = FPDFPage_New(doc, curPage + 1, band.pageW, band.pageH); + createdNew = true; + pagesAdded++; + } else { + Q = FPDF_LoadPage(doc, curPage + 1); + } + if (!Q) { FPDF_ClosePage(P); break; } + + PageBand bandQ = createdNew ? band : repagComputeBand(Q, pushColLeft, colRight); + double placeTopQ = bandQ.valid ? bandQ.placementTopY : band.placementTopY; + double limitQ = bandQ.valid ? bandQ.bottomLimitY : band.bottomLimitY; + + double blockH = blockTop - blockBottom; + if (blockH < 0) blockH = 0; + + std::vector anchoredQ = + createdNew ? std::vector{} + : repagAnchoredCenters(Q, pushColLeft, colRight, limitQ); + + if (!createdNew) { + double shift = blockH + leading; + int nq = FPDFPage_CountObjects(Q); + for (int k = 0; k < nq; ++k) { + FPDF_PAGEOBJECT o = FPDFPage_GetObject(Q, k); + if (!o || !repagInColumn(o, pushColLeft, colRight)) continue; + float l = 0, b = 0, r = 0, t = 0; + if (!FPDFPageObj_GetBounds(o, &l, &b, &r, &t)) continue; + double cy = (b + t) / 2.0; + if (repagIsAnchored(cy, anchoredQ)) continue; + FPDFPageObj_Transform(o, 1.0, 0.0, 0.0, 1.0, 0.0, -shift); + } + } + + double translateDy = placeTopQ - blockTop; + for (FPDF_PAGEOBJECT o : overflow) { + FPDFPage_RemoveObject(P, o); + FPDFPageObj_Transform(o, 1.0, 0.0, 0.0, 1.0, 0.0, translateDy); + FPDFPage_InsertObject(Q, o); + } + + FPDFPage_GenerateContent(P); + FPDFPage_GenerateContent(Q); + spdlog::info("repaginateForward: page {} -> {} moved {} object(s), blockH={:.1f}{}", + curPage, curPage + 1, overflow.size(), blockH, createdNew ? " (new page)" : ""); + FPDF_ClosePage(P); + FPDF_ClosePage(Q); + + movedTotal += static_cast(overflow.size()); + + anchored = anchoredQ; + curPage += 1; + } + if (movedOut) *movedOut = movedTotal; + return pagesAdded; +} + +int repaginateBackward(FPDF_DOCUMENT doc, int startPage, + double colLeft, double colRight, double pushColLeft, double leading) { + (void)colLeft; (void)colRight; (void)pushColLeft; (void)leading; + int pagesRemoved = 0; + for (int pg = FPDF_GetPageCount(doc) - 1; pg > startPage; --pg) { + FPDF_PAGE p = FPDF_LoadPage(doc, pg); + if (!p) break; + int n = FPDFPage_CountObjects(p); + FPDF_ClosePage(p); + if (n == 0) { FPDFPage_Delete(doc, pg); pagesRemoved++; } + else break; + } + return pagesRemoved; +} + +void repagCompactToTop(FPDF_DOCUMENT doc, int pageIdx, double pushColLeft, double colRight) { + FPDF_PAGE p = FPDF_LoadPage(doc, pageIdx); + if (!p) return; + PageBand band = repagComputeBand(p, pushColLeft, colRight); + if (!band.valid) { FPDF_ClosePage(p); return; } + std::vector anchored = repagAnchoredCenters(p, pushColLeft, colRight, band.bottomLimitY); + double curTop = -1e18; bool any = false; + int n = FPDFPage_CountObjects(p); + for (int k = 0; k < n; ++k) { + FPDF_PAGEOBJECT o = FPDFPage_GetObject(p, k); + if (!o || !repagInColumn(o, pushColLeft, colRight)) continue; + float l=0,b=0,r=0,t=0; if (!FPDFPageObj_GetBounds(o,&l,&b,&r,&t)) continue; + double cy=(b+t)/2.0; if (repagIsAnchored(cy, anchored)) continue; + if (t > curTop) { curTop = t; any = true; } + } + if (any) { + double up = band.placementTopY - curTop; + if (up > 0.01) { + for (int k = 0; k < n; ++k) { + FPDF_PAGEOBJECT o = FPDFPage_GetObject(p, k); + if (!o || !repagInColumn(o, pushColLeft, colRight)) continue; + float l=0,b=0,r=0,t=0; if (!FPDFPageObj_GetBounds(o,&l,&b,&r,&t)) continue; + double cy=(b+t)/2.0; if (repagIsAnchored(cy, anchored)) continue; + FPDFPageObj_Transform(o, 1.0, 0.0, 0.0, 1.0, 0.0, up); + } + FPDFPage_GenerateContent(p); + } + } + FPDF_ClosePage(p); +} + +int repagRemoveContinuations(FPDF_DOCUMENT doc, int anchorPage, const std::string& paraId, + double pushColLeft, double colRight) { + if (paraId.empty()) return 0; + int touched = 0; + int total = FPDF_GetPageCount(doc); + for (int pg = 0; pg < total; ++pg) { + if (pg == anchorPage) continue; + FPDF_PAGE p = FPDF_LoadPage(doc, pg); + if (!p) continue; + std::vector toDel; + int n = FPDFPage_CountObjects(p); + for (int k = 0; k < n; ++k) { + FPDF_PAGEOBJECT o = FPDFPage_GetObject(p, k); + if (o && repagGetParaId(o) == paraId) toDel.push_back(o); + } + if (toDel.empty()) { FPDF_ClosePage(p); continue; } + for (auto o : toDel) { FPDFPage_RemoveObject(p, o); FPDFPageObj_Destroy(o); } + FPDFPage_GenerateContent(p); + FPDF_ClosePage(p); + repagCompactToTop(doc, pg, pushColLeft, colRight); + touched++; + } + return touched; +} + +#endif +} \ No newline at end of file diff --git a/engine/tests/CMakeLists.txt b/engine/tests/CMakeLists.txt index 2d2b580..2970ed4 100644 --- a/engine/tests/CMakeLists.txt +++ b/engine/tests/CMakeLists.txt @@ -1,11 +1,16 @@ -# Engine test suite. Phase 0: a single smoke test that backs Gate G0. - add_executable(pdfengine_smoke smoke_test.cpp - fonts_test.cpp + font_rendering_test.cpp + font_loader_descriptor_test.cpp + font_encoding_test.cpp + font_types_test.cpp graphics_state_test.cpp display_list_test.cpp - document_test.cpp + document_load_test.cpp + page_render_test.cpp + document_edit_test.cpp + font_diagnostics_test.cpp + text_encoding_test.cpp skia_renderer_test.cpp lexer_test.cpp parser_test.cpp @@ -46,7 +51,6 @@ target_include_directories(pdfengine_smoke "${CMAKE_CURRENT_SOURCE_DIR}/../src" ) -# Locate and normalize corpus path set(TEST_CORPUS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../corpus" CACHE PATH "Path to test corpus") file(TO_CMAKE_PATH "${TEST_CORPUS_DIR}" TEST_CORPUS_DIR_NORM) target_compile_definitions(pdfengine_smoke PRIVATE TEST_CORPUS_DIR="${TEST_CORPUS_DIR_NORM}") @@ -54,5 +58,4 @@ target_compile_definitions(pdfengine_smoke PRIVATE TEST_CORPUS_DIR="${TEST_CORPU pdfengine_set_warnings(pdfengine_smoke) pdfengine_enable_sanitizers(pdfengine_smoke) -# Registers each TEST() with CTest so `ctest --preset ...` runs them. -gtest_discover_tests(pdfengine_smoke) +gtest_discover_tests(pdfengine_smoke) \ No newline at end of file diff --git a/engine/tests/document_edit_test.cpp b/engine/tests/document_edit_test.cpp new file mode 100644 index 0000000..27c6b0f --- /dev/null +++ b/engine/tests/document_edit_test.cpp @@ -0,0 +1,689 @@ +#include "document_test_helpers.hpp" + +namespace pdfengine { + +TEST(DocumentEditTest, ApplyEditsAndIncrementalSave) { + SKIP_IF_NO_PDFIUM(); + auto path = getCorpusPath("basic", "hello_world.pdf"); + if (!std::filesystem::exists(path)) { + GTEST_SKIP() << "hello_world.pdf not found in corpus."; + } + + auto docRes = PdfDocument::loadFromFile(path.string()); + ASSERT_TRUE(docRes.has_value()); + auto doc = *docRes; + + std::string editsJson = R"({ + "version": "1.0", + "operations": [ + { + "id": "op_test_1", + "type": "text_overlay", + "pageIndex": 0, + "data": { + "text": "UniqueEditedTextAnnotation123", + "x": 100.0, + "y": 150.0, + "width": 200.0, + "height": 20.0, + "fontSize": 14.0, + "fontFamily": "Helvetica", + "color": "#000000" + } + } + ] + })"; + + auto editRes = doc->applyEdits(editsJson); + ASSERT_TRUE(editRes.has_value()); + + auto saveRes = doc->saveIncremental(); + ASSERT_TRUE(saveRes.has_value()); + const auto& savedBytes = *saveRes; + ASSERT_FALSE(savedBytes.empty()); + + auto newDocRes = PdfDocument::loadFromMemory(savedBytes); + ASSERT_TRUE(newDocRes.has_value()); + auto newDoc = *newDocRes; + EXPECT_EQ(newDoc->pageCount(), 1); + + auto newPageRes = newDoc->getPage(0); + ASSERT_TRUE(newPageRes.has_value()); + auto newPage = *newPageRes; + + auto textRes = newPage->extractText(); + ASSERT_TRUE(textRes.has_value()); + EXPECT_NE(textRes->find("UniqueEditedTextAnnotation123"), std::string::npos); +} + +TEST(DocumentEditTest, ApplyRedactionAndFullSave) { + SKIP_IF_NO_PDFIUM(); + auto path = getCorpusPath("basic", "hello_world.pdf"); + if (!std::filesystem::exists(path)) { + GTEST_SKIP() << "hello_world.pdf not found in corpus."; + } + + auto docRes = PdfDocument::loadFromFile(path.string()); + ASSERT_TRUE(docRes.has_value()); + auto doc = *docRes; + + { + auto pageRes = doc->getPage(0); + ASSERT_TRUE(pageRes.has_value()); + auto textRes = (*pageRes)->extractText(); + ASSERT_TRUE(textRes.has_value()); + EXPECT_NE(textRes->find("Hello"), std::string::npos); + } + + std::string editsJson = R"({ + "version": "1.0", + "operations": [ + { + "id": "op_redact_test_1", + "type": "redaction", + "pageIndex": 0, + "data": { + "x": 0.0, + "y": 0.0, + "width": 612.0, + "height": 792.0, + "fillColor": "#ffffff" + } + } + ] + })"; + + auto editRes = doc->applyEdits(editsJson); + ASSERT_TRUE(editRes.has_value()); + + auto saveRes = doc->saveFull(); + ASSERT_TRUE(saveRes.has_value()); + const auto& savedBytes = *saveRes; + ASSERT_FALSE(savedBytes.empty()); + + auto newDocRes = PdfDocument::loadFromMemory(savedBytes); + ASSERT_TRUE(newDocRes.has_value()); + auto newDoc = *newDocRes; + + auto newPageRes = newDoc->getPage(0); + ASSERT_TRUE(newPageRes.has_value()); + auto newPage = *newPageRes; + + auto textRes = newPage->extractText(); + ASSERT_TRUE(textRes.has_value()); + + EXPECT_EQ(textRes->find("Hello"), std::string::npos); + EXPECT_EQ(textRes->find("world"), std::string::npos); +} + +TEST(DocumentEditTest, ApplyImageOverlayAndIncrementalSave) { + SKIP_IF_NO_PDFIUM(); + auto path = getCorpusPath("basic", "hello_world.pdf"); + if (!std::filesystem::exists(path)) { + GTEST_SKIP() << "hello_world.pdf not found in corpus."; + } + + auto docRes = PdfDocument::loadFromFile(path.string()); + ASSERT_TRUE(docRes.has_value()); + auto doc = *docRes; + + std::string editsJson = R"({ + "version": "1.0", + "operations": [ + { + "id": "op_test_img_1", + "type": "image_overlay", + "pageIndex": 0, + "data": { + "x": 100.0, + "y": 150.0, + "width": 200.0, + "height": 150.0, + "pixelWidth": 2, + "pixelHeight": 2, + "rawPixelData": "AAD//wAA//8AAP//AAD//w==" + } + } + ] + })"; + + auto editRes = doc->applyEdits(editsJson); + ASSERT_TRUE(editRes.has_value()); + + auto saveRes = doc->saveIncremental(); + ASSERT_TRUE(saveRes.has_value()); + const auto& savedBytes = *saveRes; + ASSERT_FALSE(savedBytes.empty()); + + auto newDocRes = PdfDocument::loadFromMemory(savedBytes); + ASSERT_TRUE(newDocRes.has_value()); + auto newDoc = *newDocRes; + EXPECT_EQ(newDoc->pageCount(), 1); +} + +TEST(DocumentEditTest, ApplyPageRotationAndIncrementalSave) { + SKIP_IF_NO_PDFIUM(); + auto path = getCorpusPath("basic", "about_blank.pdf"); + if (!std::filesystem::exists(path)) { + GTEST_SKIP() << "about_blank.pdf not found in corpus."; + } + + auto docRes = PdfDocument::loadFromFile(path.string()); + ASSERT_TRUE(docRes.has_value()); + auto doc = *docRes; + + auto pageRes = doc->getPage(0); + ASSERT_TRUE(pageRes.has_value()); + double origW = (*pageRes)->width(); + double origH = (*pageRes)->height(); + EXPECT_GT(origW, 0.0); + EXPECT_GT(origH, origW); + + std::string editsJson1 = R"({ + "version": "1.0", + "operations": [ + { + "id": "op_test_rot_1", + "type": "page_rotation", + "pageIndex": 0, + "data": { + "rotation": 90 + } + } + ] + })"; + + auto editRes1 = doc->applyEdits(editsJson1); + ASSERT_TRUE(editRes1.has_value()); + + std::string editsJson2 = R"({ + "version": "1.0", + "operations": [ + { + "id": "op_test_rot_2", + "type": "page_rotation", + "pageIndex": 0, + "data": { + "rotation": 90 + } + } + ] + })"; + + auto editRes2 = doc->applyEdits(editsJson2); + ASSERT_TRUE(editRes2.has_value()); + + auto saveRes = doc->saveIncremental(); + ASSERT_TRUE(saveRes.has_value()); + const auto& savedBytes = *saveRes; + ASSERT_FALSE(savedBytes.empty()); + + auto newDocRes = PdfDocument::loadFromMemory(savedBytes); + ASSERT_TRUE(newDocRes.has_value()); + auto newDoc = *newDocRes; + EXPECT_EQ(newDoc->pageCount(), 1); + + auto newPageRes = newDoc->getPage(0); + ASSERT_TRUE(newPageRes.has_value()); + double rotatedW = (*newPageRes)->width(); + double rotatedH = (*newPageRes)->height(); + + EXPECT_NEAR(rotatedW, origW, 0.01); + EXPECT_NEAR(rotatedH, origH, 0.01); + + std::string editsJson3 = R"({ + "version": "1.0", + "operations": [ + { + "id": "op_test_rot_3", + "type": "page_rotation", + "pageIndex": 0, + "data": { + "rotation": -90 + } + } + ] + })"; + + auto editRes3 = newDoc->applyEdits(editsJson3); + ASSERT_TRUE(editRes3.has_value()); + + auto saveRes3 = newDoc->saveIncremental(); + ASSERT_TRUE(saveRes3.has_value()); + const auto& savedBytes3 = *saveRes3; + + auto finalDocRes = PdfDocument::loadFromMemory(savedBytes3); + ASSERT_TRUE(finalDocRes.has_value()); + auto finalPageRes = (*finalDocRes)->getPage(0); + ASSERT_TRUE(finalPageRes.has_value()); + + double finalW = (*finalPageRes)->width(); + double finalH = (*finalPageRes)->height(); + EXPECT_NEAR(finalW, origH, 0.01); + EXPECT_NEAR(finalH, origW, 0.01); +} + +TEST(DocumentEditTest, ApplyPageDeletionAndIncrementalSave) { + SKIP_IF_NO_PDFIUM(); + auto path = getCorpusPath("basic", "hello_world_2_pages.pdf"); + if (!std::filesystem::exists(path)) { + GTEST_SKIP() << "hello_world_2_pages.pdf not found in corpus."; + } + + auto docRes = PdfDocument::loadFromFile(path.string()); + ASSERT_TRUE(docRes.has_value()); + auto doc = *docRes; + EXPECT_EQ(doc->pageCount(), 2); + + std::string editsJson = R"({ + "version": "1.0", + "operations": [ + { + "id": "op_del_test_1", + "type": "page_deletion", + "pageIndex": 1, + "data": {} + } + ] + })"; + + auto editRes = doc->applyEdits(editsJson); + ASSERT_TRUE(editRes.has_value()); + EXPECT_EQ(doc->pageCount(), 1); + + auto saveRes = doc->saveIncremental(); + ASSERT_TRUE(saveRes.has_value()); + const auto& savedBytes = *saveRes; + ASSERT_FALSE(savedBytes.empty()); + + auto newDocRes = PdfDocument::loadFromMemory(savedBytes); + ASSERT_TRUE(newDocRes.has_value()); + EXPECT_EQ((*newDocRes)->pageCount(), 1); +} + +TEST(DocumentEditTest, ApplyPageReorderAndIncrementalSave) { + SKIP_IF_NO_PDFIUM(); + auto path = getCorpusPath("basic", "hello_world_2_pages.pdf"); + if (!std::filesystem::exists(path)) { + GTEST_SKIP() << "hello_world_2_pages.pdf not found in corpus."; + } + + auto docRes = PdfDocument::loadFromFile(path.string()); + ASSERT_TRUE(docRes.has_value()); + auto doc = *docRes; + EXPECT_EQ(doc->pageCount(), 2); + + std::string editsJson = R"({ + "version": "1.0", + "operations": [ + { + "id": "op_reorder_test_1", + "type": "page_reorder", + "pageIndex": 1, + "data": { + "destPageIndex": 0 + } + } + ] + })"; + + auto editRes = doc->applyEdits(editsJson); + ASSERT_TRUE(editRes.has_value()); + EXPECT_EQ(doc->pageCount(), 2); + + auto saveRes = doc->saveIncremental(); + ASSERT_TRUE(saveRes.has_value()); + const auto& savedBytes = *saveRes; + ASSERT_FALSE(savedBytes.empty()); + + auto newDocRes = PdfDocument::loadFromMemory(savedBytes); + ASSERT_TRUE(newDocRes.has_value()); + EXPECT_EQ((*newDocRes)->pageCount(), 2); +} +TEST(DocumentEditTest, ReplaceTextMVPStandardFont) { + SKIP_IF_NO_PDFIUM(); + auto path = getCorpusPath("basic", "hello_world.pdf"); + if (!std::filesystem::exists(path)) { + GTEST_SKIP() << "hello_world.pdf not found in corpus."; + } + + auto docRes = PdfDocument::loadFromFile(path.string()); + ASSERT_TRUE(docRes.has_value()); + auto doc = *docRes; + + auto pageRes = doc->getPage(0); + ASSERT_TRUE(pageRes.has_value()); + auto pageObj = *pageRes; + + auto modelRes = pageObj->extractDocumentModel(); + ASSERT_TRUE(modelRes.has_value()); + const auto& model = *modelRes; + + std::vector objectIndices; + for (const auto& p : model.paragraphs) { + for (const auto& line : p.lines) { + for (const auto& run : line.runs) { + if (run.text.find("Hello") != std::string::npos) { + objectIndices = run.objectIndices; + break; + } + } + if (!objectIndices.empty()) break; + } + if (!objectIndices.empty()) break; + } + + ASSERT_FALSE(objectIndices.empty()) << "Could not find a text object in hello_world.pdf"; + + std::string indicesStr = ""; + for (size_t i = 0; i < objectIndices.size(); ++i) { + indicesStr += std::to_string(objectIndices[i]); + if (i + 1 < objectIndices.size()) indicesStr += ","; + } + + std::string editsJson = R"({ + "version": "1.0", + "operations": [ + { + "id": "op_mvp_1", + "type": "replace_text", + "pageIndex": 0, + "objectIndices": [)" + indicesStr + R"(], + "text": "Greeting, universe!" + } + ] + })"; + + auto editRes = doc->applyEdits(editsJson); + ASSERT_TRUE(editRes.has_value()); + + auto saveRes = doc->saveIncremental(); + ASSERT_TRUE(saveRes.has_value()); + const auto& savedBytes = *saveRes; + ASSERT_FALSE(savedBytes.empty()); + + auto newDocRes = PdfDocument::loadFromMemory(savedBytes); + ASSERT_TRUE(newDocRes.has_value()); + auto newDoc = *newDocRes; + + auto newPageRes = newDoc->getPage(0); + ASSERT_TRUE(newPageRes.has_value()); + auto newPage = *newPageRes; + + auto textRes = newPage->extractText(); + ASSERT_TRUE(textRes.has_value()); + EXPECT_NE(textRes->find("Greeting, universe!"), std::string::npos); + EXPECT_EQ(textRes->find("Hello"), std::string::npos); +} + +TEST(DocumentEditTest, ReplaceTextRuntimeFontEngine) { + SKIP_IF_NO_PDFIUM(); + auto path = getCorpusPath("fonts", "latin_extended.pdf"); + if (!std::filesystem::exists(path)) { + GTEST_SKIP() << "latin_extended.pdf not found in corpus."; + } + + auto docRes = PdfDocument::loadFromFile(path.string()); + ASSERT_TRUE(docRes.has_value()); + auto doc = *docRes; + + auto pageRes = doc->getPage(0); + ASSERT_TRUE(pageRes.has_value()); + auto pageObj = *pageRes; + + auto modelRes = pageObj->extractDocumentModel(); + ASSERT_TRUE(modelRes.has_value()); + const auto& model = *modelRes; + + std::vector objectIndices; + std::string originalFontId = ""; + for (const auto& p : model.paragraphs) { + for (const auto& line : p.lines) { + for (const auto& run : line.runs) { + if (run.fontName.find("Roboto-Regular") != std::string::npos) { + objectIndices = run.objectIndices; + originalFontId = run.internalFontId; + break; + } + } + if (!objectIndices.empty()) break; + } + if (!objectIndices.empty()) break; + } + + ASSERT_FALSE(objectIndices.empty()) << "Could not find target text run in latin_extended.pdf"; + + std::string indicesStr = ""; + for (size_t i = 0; i < objectIndices.size(); ++i) { + indicesStr += std::to_string(objectIndices[i]); + if (i + 1 < objectIndices.size()) indicesStr += ","; + } + + std::string editsJson = R"({ + "version": "1.0", + "operations": [ + { + "id": "op_engine_1", + "type": "replace_text", + "pageIndex": 0, + "objectIndices": [)" + indicesStr + R"(], + "text": "Font Engine Active!", + "internalFontId": ")" + originalFontId + R"(" + } + ] + })"; + + auto editRes = doc->applyEdits(editsJson); + ASSERT_TRUE(editRes.has_value()); + + auto saveRes = doc->saveIncremental(); + ASSERT_TRUE(saveRes.has_value()); + const auto& savedBytes = *saveRes; + ASSERT_FALSE(savedBytes.empty()); + + auto newDocRes = PdfDocument::loadFromMemory(savedBytes); + ASSERT_TRUE(newDocRes.has_value()); + auto newDoc = *newDocRes; + + auto newPageRes = newDoc->getPage(0); + ASSERT_TRUE(newPageRes.has_value()); + auto newPage = *newPageRes; + + auto textRes = newPage->extractText(); + ASSERT_TRUE(textRes.has_value()); + EXPECT_NE(textRes->find("Font Engine Active!"), std::string::npos); +} + +TEST(DocumentEditTest, ReplaceTextFontReuseAndEmbedding) { + SKIP_IF_NO_PDFIUM(); + auto path = getCorpusPath("basic", "hello_world.pdf"); + if (!std::filesystem::exists(path)) { + GTEST_SKIP() << "hello_world.pdf not found in corpus."; + } + + auto docRes = PdfDocument::loadFromFile(path.string()); + ASSERT_TRUE(docRes.has_value()); + auto doc = *docRes; + + auto pageRes = doc->getPage(0); + ASSERT_TRUE(pageRes.has_value()); + auto pageObj = *pageRes; + + auto modelRes = pageObj->extractDocumentModel(); + ASSERT_TRUE(modelRes.has_value()); + const auto& model = *modelRes; + + std::vector objectIndices; + std::string originalFontId = ""; + for (const auto& p : model.paragraphs) { + for (const auto& line : p.lines) { + for (const auto& run : line.runs) { + if (!run.objectIndices.empty()) { + objectIndices = run.objectIndices; + originalFontId = run.internalFontId; + break; + } + } + if (!objectIndices.empty()) break; + } + if (!objectIndices.empty()) break; + } + + ASSERT_FALSE(objectIndices.empty()) << "Could not find a text run in hello_world.pdf"; + + std::string indicesStr = ""; + for (size_t i = 0; i < objectIndices.size(); ++i) { + indicesStr += std::to_string(objectIndices[i]); + if (i + 1 < objectIndices.size()) indicesStr += ","; + } + + std::string editsJson = R"({ + "version": "1.0", + "operations": [ + { + "id": "op_reuse_1", + "type": "replace_text", + "pageIndex": 0, + "objectIndices": [)" + indicesStr + R"(], + "text": "Embedded Arial", + "internalFontId": ")" + originalFontId + R"(" + } + ] + })"; + + auto editRes = doc->applyEdits(editsJson); + ASSERT_TRUE(editRes.has_value()); + + auto saveRes = doc->saveIncremental(); + ASSERT_TRUE(saveRes.has_value()); + const auto& savedBytes = *saveRes; + ASSERT_FALSE(savedBytes.empty()); + + auto newDocRes = PdfDocument::loadFromMemory(savedBytes); + ASSERT_TRUE(newDocRes.has_value()); + auto newDoc = *newDocRes; + + auto fontsRes = newDoc->getFonts(0, 0); + ASSERT_TRUE(fontsRes.has_value()); + + bool foundEmbeddedArial = false; + for (const auto& f : *fontsRes) { + if (f.isEmbedded && (f.fontName.find("Arial") != std::string::npos || f.fontName.find("LiberationSans") != std::string::npos)) { + foundEmbeddedArial = true; + } + } + + std::cout << "Font Embedding Test: foundEmbeddedArial = " << foundEmbeddedArial << std::endl; +} + +TEST(DocumentEditTest, ReplaceTextHarfBuzzShapingAndReflow) { + SKIP_IF_NO_PDFIUM(); + auto path = getCorpusPath("fonts", "latin_extended.pdf"); + if (!std::filesystem::exists(path)) { + GTEST_SKIP() << "latin_extended.pdf not found in corpus."; + } + + auto docRes = PdfDocument::loadFromFile(path.string()); + ASSERT_TRUE(docRes.has_value()); + auto doc = *docRes; + + auto pageRes = doc->getPage(0); + ASSERT_TRUE(pageRes.has_value()); + auto pageObj = *pageRes; + + auto modelRes = pageObj->extractDocumentModel(); + ASSERT_TRUE(modelRes.has_value()); + const auto& model = *modelRes; + + std::vector targetIndices; + std::string originalFontId = ""; + std::string runBText = ""; + double runBOrigX = 0.0; + double runBOrigY = 0.0; + + for (const auto& p : model.paragraphs) { + for (const auto& line : p.lines) { + if (line.runs.size() >= 2) { + const auto& runA = line.runs[0]; + const auto& runB = line.runs[1]; + if (runA.fontName.find("Roboto-Regular") != std::string::npos && + !runA.objectIndices.empty() && + runB.x > runA.x) { + targetIndices = runA.objectIndices; + originalFontId = runA.internalFontId; + runBText = runB.text; + runBOrigX = runB.x; + runBOrigY = runB.y; + break; + } + } + } + if (!targetIndices.empty()) break; + } + + if (targetIndices.empty()) { + GTEST_SKIP() << "Could not find a suitable line with multiple runs to test reflow."; + } + + std::string indicesStr = ""; + for (size_t i = 0; i < targetIndices.size(); ++i) { + indicesStr += std::to_string(targetIndices[i]); + if (i + 1 < targetIndices.size()) indicesStr += ","; + } + + std::string editsJson = R"({ + "version": "1.0", + "operations": [ + { + "id": "op_reflow_1", + "type": "replace_text", + "pageIndex": 0, + "objectIndices": [)" + indicesStr + R"(], + "text": "This is an extremely long replacement text to force the Reflow Engine to shift subsequent runs!", + "internalFontId": ")" + originalFontId + R"(" + } + ] + })"; + + auto editRes = doc->applyEdits(editsJson); + ASSERT_TRUE(editRes.has_value()); + + auto saveRes = doc->saveIncremental(); + ASSERT_TRUE(saveRes.has_value()); + const auto& savedBytes = *saveRes; + ASSERT_FALSE(savedBytes.empty()); + + auto newDocRes = PdfDocument::loadFromMemory(savedBytes); + ASSERT_TRUE(newDocRes.has_value()); + auto newDoc = *newDocRes; + + auto newPageRes = newDoc->getPage(0); + ASSERT_TRUE(newPageRes.has_value()); + auto newPage = *newPageRes; + + auto newModelRes = newPage->extractDocumentModel(); + ASSERT_TRUE(newModelRes.has_value()); + const auto& newModel = *newModelRes; + + bool foundRunB = false; + double runBNewX = 0.0; + for (const auto& p : newModel.paragraphs) { + for (const auto& line : p.lines) { + for (const auto& run : line.runs) { + if (run.text == runBText && std::abs(run.y - runBOrigY) < 5.0) { + foundRunB = true; + runBNewX = run.x; + break; + } + } + if (foundRunB) break; + } + if (foundRunB) break; + } + + ASSERT_TRUE(foundRunB) << "Could not find the subsequent text run '" << runBText << "' in the reflowed document."; + EXPECT_GT(runBNewX, runBOrigX + 10.0) << "The subsequent text run did not shift to the right by at least 10 points."; + + std::cout << "Reflow Engine verified: '" << runBText << "' shifted from X=" << runBOrigX << " to X=" << runBNewX << std::endl; +} +} \ No newline at end of file diff --git a/engine/tests/document_load_test.cpp b/engine/tests/document_load_test.cpp new file mode 100644 index 0000000..9d865ff --- /dev/null +++ b/engine/tests/document_load_test.cpp @@ -0,0 +1,153 @@ +#include "document_test_helpers.hpp" + +namespace pdfengine { + +TEST(DocumentLoadTest, NonExistentFileReturnsFileNotFound) { + SKIP_IF_NO_PDFIUM(); + auto result = PdfDocument::loadFromFile("nonexistent_file_12345.pdf"); + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), EngineError::FileNotFound); +} + +TEST(DocumentLoadTest, InvalidFileReturnsInvalidFormat) { + SKIP_IF_NO_PDFIUM(); + std::string path = "invalid_format_test.pdf"; + { + std::ofstream out(path, std::ios::binary); + out << "NOT A PDF FILE!"; + } + + auto result = PdfDocument::loadFromFile(path); + std::filesystem::remove(path); + + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), EngineError::InvalidFormat); +} + +TEST(DocumentLoadTest, EncryptedPdfRequiresPassword) { + SKIP_IF_NO_PDFIUM(); + auto path = getCorpusPath("edge-cases", "encrypted.pdf"); + if (!std::filesystem::exists(path)) { + GTEST_SKIP() << "encrypted.pdf not found in corpus."; + } + + auto result = PdfDocument::loadFromFile(path.string()); + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), EngineError::PasswordRequired); +} + +TEST(DocumentLoadTest, EncryptedPdfInvalidPassword) { + SKIP_IF_NO_PDFIUM(); + auto path = getCorpusPath("edge-cases", "encrypted.pdf"); + if (!std::filesystem::exists(path)) { + GTEST_SKIP() << "encrypted.pdf not found in corpus."; + } + + auto result = PdfDocument::loadFromFile(path.string(), "wrong_password"); + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), EngineError::InvalidPassword); +} + +TEST(DocumentLoadTest, EncryptedPdfCorrectPassword) { + SKIP_IF_NO_PDFIUM(); + auto path = getCorpusPath("edge-cases", "encrypted.pdf"); + if (!std::filesystem::exists(path)) { + GTEST_SKIP() << "encrypted.pdf not found in corpus."; + } + + std::vector candidates = {"tessy", "test", "password", "123456", "1234", "foobar", "user", "owner", ""}; + bool success = false; + for (const auto& pw : candidates) { + auto result = PdfDocument::loadFromFile(path.string(), pw); + if (result.has_value()) { + EXPECT_GT((*result)->pageCount(), 0); + success = true; + break; + } + } + EXPECT_TRUE(success) << "Failed to open encrypted.pdf with any of the candidate passwords."; +} + +TEST(DocumentLoadTest, LoadFromMemorySuccess) { + SKIP_IF_NO_PDFIUM(); + auto path = getCorpusPath("basic", "hello_world.pdf"); + if (!std::filesystem::exists(path)) { + GTEST_SKIP() << "hello_world.pdf not found in corpus."; + } + + auto buffer = readFile(path); + ASSERT_FALSE(buffer.empty()); + + auto result = PdfDocument::loadFromMemory(buffer); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ((*result)->pageCount(), 1); +} + +TEST(DocumentLoadTest, LoadFromMemoryEmptyBuffer) { + SKIP_IF_NO_PDFIUM(); + std::vector empty_buf; + auto result = PdfDocument::loadFromMemory(empty_buf); + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), EngineError::InvalidFormat); +} + +TEST(PageCountTest, CorpusPageCounts) { + SKIP_IF_NO_PDFIUM(); + + struct ExpectedPageCount { + std::string folder; + std::string file; + int count; + }; + + std::vector targets = { + {"basic", "about_blank.pdf", 1}, + {"basic", "black.pdf", 1}, + {"basic", "hello_world.pdf", 1}, + {"basic", "hello_world_2_pages.pdf", 2}, + {"basic", "rectangles_multi_pages.pdf", 5} + }; + + for (const auto& t : targets) { + auto path = getCorpusPath(t.folder, t.file); + if (!std::filesystem::exists(path)) { + continue; + } + auto docRes = PdfDocument::loadFromFile(path.string()); + ASSERT_TRUE(docRes.has_value()) << "Failed to open " << t.file; + EXPECT_EQ((*docRes)->pageCount(), t.count) << "Mismatch for " << t.file; + } + + auto verifyCorpusFolder = [](const std::string& folder, const std::vector& files) { + for (const auto& f : files) { + auto path = getCorpusPath(folder, f); + if (!std::filesystem::exists(path)) { + ADD_FAILURE() << "Missing corpus file: " << path.string(); + continue; + } + + std::vector passwords = {""}; + if (f == "encrypted.pdf") { + passwords = {"tessy", "test", "password", "123456", "1234", "foobar", "user", "owner"}; + } + + bool success = false; + EngineError lastErr = EngineError::Unknown; + for (const auto& pw : passwords) { + auto docRes = PdfDocument::loadFromFile(path.string(), pw); + if (docRes.has_value()) { + EXPECT_GE((*docRes)->pageCount(), 0) << "Page count negative for " << f; + success = true; + break; + } + lastErr = docRes.error(); + } + EXPECT_TRUE(success) << "Failed to open " << f << " error: " << static_cast(lastErr); + } + }; + + verifyCorpusFolder("basic", corpus_basic); + verifyCorpusFolder("fonts", corpus_fonts); + verifyCorpusFolder("edge-cases", corpus_edge); +} +} \ No newline at end of file diff --git a/engine/tests/document_test.cpp b/engine/tests/document_test.cpp deleted file mode 100644 index 48f1cb0..0000000 --- a/engine/tests/document_test.cpp +++ /dev/null @@ -1,1741 +0,0 @@ -#include -#include -#include -#include "parser/pdfium_document.hpp" -#include "fonts/pdf_fonts/encoding/cjk_collection_db.hpp" -#include -#include -#include -#include -#include -#include -#include -#include "fonts/cache/glyph_cache.hpp" -#include "fonts/pdf_fonts/font.hpp" -#ifndef TEST_CORPUS_DIR -#define TEST_CORPUS_DIR "../../corpus" -#endif - -namespace { - -#define SKIP_IF_NO_PDFIUM() \ - if (!pdfengine::engineHasPdfium()) { \ - GTEST_SKIP() << "Skipping PDFium tests because PDFium is not linked."; \ - } - -std::filesystem::path getCorpusPath(const std::string& subfolder, const std::string& filename) { - return std::filesystem::path(TEST_CORPUS_DIR) / subfolder / filename; -} - -std::vector readFile(const std::filesystem::path& path) { - std::ifstream file(path, std::ios::binary | std::ios::ate); - if (!file.is_open()) { - return {}; - } - std::streamsize size = file.tellg(); - file.seekg(0, std::ios::beg); - std::vector buffer(size); - if (file.read(reinterpret_cast(buffer.data()), size)) { - return buffer; - } - return {}; -} - -const std::vector corpus_basic = { - "about_blank.pdf", "black.pdf", "clip_path.pdf", "dashed_lines.pdf", - "hello_world.pdf", "hello_world_2_pages.pdf", "many_rectangles.pdf", - "rectangles.pdf", "rectangles_multi_pages.pdf", "whitespace.pdf" -}; - -const std::vector corpus_fonts = { - "latin_extended.pdf", "rotated_text.pdf", "rotated_text_90.pdf", - "text_font.pdf", "utf-8.pdf", "vertical_text.pdf", "hebrew_mirrored.pdf" -}; - -const std::vector corpus_edge = { - "annots.pdf", "bookmarks.pdf", "combobox_form.pdf", "embedded_attachments.pdf", - "empty_xref.pdf", "encrypted.pdf", "linearized.pdf", "listbox_form.pdf", - "no_page_count.pdf", "page_labels.pdf", "text_form.pdf", "unsupported_feature.pdf", - "zero_length_stream.pdf" -}; - -} - -namespace pdfengine { - -TEST(DocumentLoadTest, NonExistentFileReturnsFileNotFound) { - SKIP_IF_NO_PDFIUM(); - auto result = PdfDocument::loadFromFile("nonexistent_file_12345.pdf"); - ASSERT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), EngineError::FileNotFound); -} - -TEST(DocumentLoadTest, InvalidFileReturnsInvalidFormat) { - SKIP_IF_NO_PDFIUM(); - std::string path = "invalid_format_test.pdf"; - { - std::ofstream out(path, std::ios::binary); - out << "NOT A PDF FILE!"; - } - - auto result = PdfDocument::loadFromFile(path); - std::filesystem::remove(path); - - ASSERT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), EngineError::InvalidFormat); -} - -TEST(DocumentLoadTest, EncryptedPdfRequiresPassword) { - SKIP_IF_NO_PDFIUM(); - auto path = getCorpusPath("edge-cases", "encrypted.pdf"); - if (!std::filesystem::exists(path)) { - GTEST_SKIP() << "encrypted.pdf not found in corpus."; - } - - auto result = PdfDocument::loadFromFile(path.string()); - ASSERT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), EngineError::PasswordRequired); -} - -TEST(DocumentLoadTest, EncryptedPdfInvalidPassword) { - SKIP_IF_NO_PDFIUM(); - auto path = getCorpusPath("edge-cases", "encrypted.pdf"); - if (!std::filesystem::exists(path)) { - GTEST_SKIP() << "encrypted.pdf not found in corpus."; - } - - auto result = PdfDocument::loadFromFile(path.string(), "wrong_password"); - ASSERT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), EngineError::InvalidPassword); -} - -TEST(DocumentLoadTest, EncryptedPdfCorrectPassword) { - SKIP_IF_NO_PDFIUM(); - auto path = getCorpusPath("edge-cases", "encrypted.pdf"); - if (!std::filesystem::exists(path)) { - GTEST_SKIP() << "encrypted.pdf not found in corpus."; - } - - std::vector candidates = {"tessy", "test", "password", "123456", "1234", "foobar", "user", "owner", ""}; - bool success = false; - for (const auto& pw : candidates) { - auto result = PdfDocument::loadFromFile(path.string(), pw); - if (result.has_value()) { - EXPECT_GT((*result)->pageCount(), 0); - success = true; - break; - } - } - EXPECT_TRUE(success) << "Failed to open encrypted.pdf with any of the candidate passwords."; -} - -TEST(DocumentLoadTest, LoadFromMemorySuccess) { - SKIP_IF_NO_PDFIUM(); - auto path = getCorpusPath("basic", "hello_world.pdf"); - if (!std::filesystem::exists(path)) { - GTEST_SKIP() << "hello_world.pdf not found in corpus."; - } - - auto buffer = readFile(path); - ASSERT_FALSE(buffer.empty()); - - auto result = PdfDocument::loadFromMemory(buffer); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ((*result)->pageCount(), 1); -} - -TEST(DocumentLoadTest, LoadFromMemoryEmptyBuffer) { - SKIP_IF_NO_PDFIUM(); - std::vector empty_buf; - auto result = PdfDocument::loadFromMemory(empty_buf); - ASSERT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), EngineError::InvalidFormat); -} - -TEST(PageCountTest, CorpusPageCounts) { - SKIP_IF_NO_PDFIUM(); - - struct ExpectedPageCount { - std::string folder; - std::string file; - int count; - }; - - std::vector targets = { - {"basic", "about_blank.pdf", 1}, - {"basic", "black.pdf", 1}, - {"basic", "hello_world.pdf", 1}, - {"basic", "hello_world_2_pages.pdf", 2}, - {"basic", "rectangles_multi_pages.pdf", 5} - }; - - for (const auto& t : targets) { - auto path = getCorpusPath(t.folder, t.file); - if (!std::filesystem::exists(path)) { - continue; - } - auto docRes = PdfDocument::loadFromFile(path.string()); - ASSERT_TRUE(docRes.has_value()) << "Failed to open " << t.file; - EXPECT_EQ((*docRes)->pageCount(), t.count) << "Mismatch for " << t.file; - } - - auto verifyCorpusFolder = [](const std::string& folder, const std::vector& files) { - for (const auto& f : files) { - auto path = getCorpusPath(folder, f); - if (!std::filesystem::exists(path)) { - ADD_FAILURE() << "Missing corpus file: " << path.string(); - continue; - } - - std::vector passwords = {""}; - if (f == "encrypted.pdf") { - passwords = {"tessy", "test", "password", "123456", "1234", "foobar", "user", "owner"}; - } - - bool success = false; - EngineError lastErr = EngineError::Unknown; - for (const auto& pw : passwords) { - auto docRes = PdfDocument::loadFromFile(path.string(), pw); - if (docRes.has_value()) { - EXPECT_GE((*docRes)->pageCount(), 0) << "Page count negative for " << f; - success = true; - break; - } - lastErr = docRes.error(); - } - EXPECT_TRUE(success) << "Failed to open " << f << " error: " << static_cast(lastErr); - } - }; - - verifyCorpusFolder("basic", corpus_basic); - verifyCorpusFolder("fonts", corpus_fonts); - verifyCorpusFolder("edge-cases", corpus_edge); -} - -TEST(PageRenderTest, RenderAtDifferentDPI) { - SKIP_IF_NO_PDFIUM(); - auto path = getCorpusPath("basic", "hello_world.pdf"); - if (!std::filesystem::exists(path)) { - GTEST_SKIP() << "hello_world.pdf not found in corpus."; - } - - auto docRes = PdfDocument::loadFromFile(path.string()); - ASSERT_TRUE(docRes.has_value()); - auto pageRes = (*docRes)->getPage(0); - ASSERT_TRUE(pageRes.has_value()); - - auto page = *pageRes; - double ptWidth = page->width(); - double ptHeight = page->height(); - EXPECT_GT(ptWidth, 0.0); - EXPECT_GT(ptHeight, 0.0); - - std::vector dpis = {72, 144, 288}; - for (int dpi : dpis) { - auto imgRes = page->render(dpi); - ASSERT_TRUE(imgRes.has_value()) << "Failed to render at DPI " << dpi; - - double scale = dpi / 72.0; - int expectedW = static_cast(ptWidth * scale); - int expectedH = static_cast(ptHeight * scale); - - EXPECT_EQ(imgRes->width, expectedW); - EXPECT_EQ(imgRes->height, expectedH); - - ASSERT_GE(imgRes->data.size(), 8U); - EXPECT_EQ(imgRes->data[0], 0x89); - EXPECT_EQ(imgRes->data[1], 'P'); - EXPECT_EQ(imgRes->data[2], 'N'); - EXPECT_EQ(imgRes->data[3], 'G'); - EXPECT_EQ(imgRes->data[4], 0x0D); - EXPECT_EQ(imgRes->data[5], 0x0A); - EXPECT_EQ(imgRes->data[6], 0x1A); - EXPECT_EQ(imgRes->data[7], 0x0A); - } -} - -TEST(PageRenderTest, InvalidPageIndexReturnsPageOutOfBounds) { - SKIP_IF_NO_PDFIUM(); - auto path = getCorpusPath("basic", "hello_world.pdf"); - if (!std::filesystem::exists(path)) { - GTEST_SKIP() << "hello_world.pdf not found in corpus."; - } - - auto docRes = PdfDocument::loadFromFile(path.string()); - ASSERT_TRUE(docRes.has_value()); - - auto pageRes = (*docRes)->getPage(1); - ASSERT_FALSE(pageRes.has_value()); - EXPECT_EQ(pageRes.error(), EngineError::PageOutOfBounds); - - auto pageResNeg = (*docRes)->getPage(-1); - ASSERT_FALSE(pageResNeg.has_value()); - EXPECT_EQ(pageResNeg.error(), EngineError::PageOutOfBounds); -} - -TEST(CoordinateTransformTest, PageToDeviceAndBackRoundtrip) { - SKIP_IF_NO_PDFIUM(); - auto path = getCorpusPath("basic", "hello_world.pdf"); - if (!std::filesystem::exists(path)) { - GTEST_SKIP() << "hello_world.pdf not found in corpus."; - } - - auto docRes = PdfDocument::loadFromFile(path.string()); - ASSERT_TRUE(docRes.has_value()); - auto pageRes = (*docRes)->getPage(0); - ASSERT_TRUE(pageRes.has_value()); - - auto page = *pageRes; - - int deviceW = 1024; - int deviceH = 768; - std::vector rotations = {0, 90, 180, 270}; - - std::vector testPoints = { - {0.0, 0.0}, - {50.0, 50.0}, - {100.0, 200.0}, - {page->width() / 2.0, page->height() / 2.0}, - {page->width() - 10.0, page->height() - 10.0} - }; - - for (int rotation : rotations) { - for (const auto& pt : testPoints) { - auto devPt = page->pageToDevice(pt, deviceW, deviceH, rotation); - auto pagePt = page->deviceToPage(devPt, deviceW, deviceH, rotation); - - EXPECT_NEAR(pt.x, pagePt.x, 2.0) << "Failed roundtrip for x at rotation " << rotation; - EXPECT_NEAR(pt.y, pagePt.y, 2.0) << "Failed roundtrip for y at rotation " << rotation; - } - } -} - -TEST(TextExtractionTest, ExtractSimpleText) { - SKIP_IF_NO_PDFIUM(); - auto path = getCorpusPath("basic", "hello_world.pdf"); - if (!std::filesystem::exists(path)) { - GTEST_SKIP() << "hello_world.pdf not found in corpus."; - } - - auto docRes = PdfDocument::loadFromFile(path.string()); - ASSERT_TRUE(docRes.has_value()); - auto pageRes = (*docRes)->getPage(0); - ASSERT_TRUE(pageRes.has_value()); - - auto textRes = (*pageRes)->extractText(); - ASSERT_TRUE(textRes.has_value()); - - std::string text = *textRes; - EXPECT_NE(text.find("Hello"), std::string::npos); - EXPECT_NE(text.find("world"), std::string::npos); -} - -TEST(TextExtractionTest, ExtractUtf8ExtendedText) { - SKIP_IF_NO_PDFIUM(); - auto path = getCorpusPath("fonts", "latin_extended.pdf"); - if (!std::filesystem::exists(path)) { - GTEST_SKIP() << "latin_extended.pdf not found in corpus."; - } - - auto docRes = PdfDocument::loadFromFile(path.string()); - ASSERT_TRUE(docRes.has_value()); - auto pageRes = (*docRes)->getPage(0); - ASSERT_TRUE(pageRes.has_value()); - - auto textRes = (*pageRes)->extractText(); - ASSERT_TRUE(textRes.has_value()); - - std::string text = *textRes; - EXPECT_FALSE(text.empty()); -} - -TEST(TextExtractionTest, ExtractTextWithBounds) { - SKIP_IF_NO_PDFIUM(); - auto path = getCorpusPath("basic", "hello_world.pdf"); - if (!std::filesystem::exists(path)) { - GTEST_SKIP() << "hello_world.pdf not found in corpus."; - } - - auto docRes = PdfDocument::loadFromFile(path.string()); - ASSERT_TRUE(docRes.has_value()); - auto pageRes = (*docRes)->getPage(0); - ASSERT_TRUE(pageRes.has_value()); - - auto boundsRes = (*pageRes)->extractTextWithBounds(); - ASSERT_TRUE(boundsRes.has_value()); - - const auto& glyphs = *boundsRes; - ASSERT_FALSE(glyphs.empty()); - - bool found_hello = false; - for (const auto& glyph : glyphs) { - EXPECT_FALSE(glyph.text.empty()); - if (glyph.text != " " && glyph.text != "\r" && glyph.text != "\n" && glyph.text != "\t") { - EXPECT_GT(glyph.w, 0.0); - EXPECT_GT(glyph.h, 0.0); - } - EXPECT_GT(glyph.fontSize, 0.0); - if (glyph.text == "H" || glyph.text == "e" || glyph.text == "l" || glyph.text == "o") { - found_hello = true; - } - } - EXPECT_TRUE(found_hello); -} - -TEST(DocumentEditTest, ApplyEditsAndIncrementalSave) { - SKIP_IF_NO_PDFIUM(); - auto path = getCorpusPath("basic", "hello_world.pdf"); - if (!std::filesystem::exists(path)) { - GTEST_SKIP() << "hello_world.pdf not found in corpus."; - } - - auto docRes = PdfDocument::loadFromFile(path.string()); - ASSERT_TRUE(docRes.has_value()); - auto doc = *docRes; - - std::string editsJson = R"({ - "version": "1.0", - "operations": [ - { - "id": "op_test_1", - "type": "text_overlay", - "pageIndex": 0, - "data": { - "text": "UniqueEditedTextAnnotation123", - "x": 100.0, - "y": 150.0, - "width": 200.0, - "height": 20.0, - "fontSize": 14.0, - "fontFamily": "Helvetica", - "color": "#000000" - } - } - ] - })"; - - auto editRes = doc->applyEdits(editsJson); - ASSERT_TRUE(editRes.has_value()); - - auto saveRes = doc->saveIncremental(); - ASSERT_TRUE(saveRes.has_value()); - const auto& savedBytes = *saveRes; - ASSERT_FALSE(savedBytes.empty()); - - auto newDocRes = PdfDocument::loadFromMemory(savedBytes); - ASSERT_TRUE(newDocRes.has_value()); - auto newDoc = *newDocRes; - EXPECT_EQ(newDoc->pageCount(), 1); - - auto newPageRes = newDoc->getPage(0); - ASSERT_TRUE(newPageRes.has_value()); - auto newPage = *newPageRes; - - auto textRes = newPage->extractText(); - ASSERT_TRUE(textRes.has_value()); - EXPECT_NE(textRes->find("UniqueEditedTextAnnotation123"), std::string::npos); -} - -TEST(DocumentEditTest, ApplyRedactionAndFullSave) { - SKIP_IF_NO_PDFIUM(); - auto path = getCorpusPath("basic", "hello_world.pdf"); - if (!std::filesystem::exists(path)) { - GTEST_SKIP() << "hello_world.pdf not found in corpus."; - } - - auto docRes = PdfDocument::loadFromFile(path.string()); - ASSERT_TRUE(docRes.has_value()); - auto doc = *docRes; - - { - auto pageRes = doc->getPage(0); - ASSERT_TRUE(pageRes.has_value()); - auto textRes = (*pageRes)->extractText(); - ASSERT_TRUE(textRes.has_value()); - EXPECT_NE(textRes->find("Hello"), std::string::npos); - } - - std::string editsJson = R"({ - "version": "1.0", - "operations": [ - { - "id": "op_redact_test_1", - "type": "redaction", - "pageIndex": 0, - "data": { - "x": 0.0, - "y": 0.0, - "width": 612.0, - "height": 792.0, - "fillColor": "#ffffff" - } - } - ] - })"; - - auto editRes = doc->applyEdits(editsJson); - ASSERT_TRUE(editRes.has_value()); - - auto saveRes = doc->saveFull(); - ASSERT_TRUE(saveRes.has_value()); - const auto& savedBytes = *saveRes; - ASSERT_FALSE(savedBytes.empty()); - - auto newDocRes = PdfDocument::loadFromMemory(savedBytes); - ASSERT_TRUE(newDocRes.has_value()); - auto newDoc = *newDocRes; - - auto newPageRes = newDoc->getPage(0); - ASSERT_TRUE(newPageRes.has_value()); - auto newPage = *newPageRes; - - auto textRes = newPage->extractText(); - ASSERT_TRUE(textRes.has_value()); - - EXPECT_EQ(textRes->find("Hello"), std::string::npos); - EXPECT_EQ(textRes->find("world"), std::string::npos); -} - -TEST(DocumentEditTest, ApplyImageOverlayAndIncrementalSave) { - SKIP_IF_NO_PDFIUM(); - auto path = getCorpusPath("basic", "hello_world.pdf"); - if (!std::filesystem::exists(path)) { - GTEST_SKIP() << "hello_world.pdf not found in corpus."; - } - - auto docRes = PdfDocument::loadFromFile(path.string()); - ASSERT_TRUE(docRes.has_value()); - auto doc = *docRes; - - std::string editsJson = R"({ - "version": "1.0", - "operations": [ - { - "id": "op_test_img_1", - "type": "image_overlay", - "pageIndex": 0, - "data": { - "x": 100.0, - "y": 150.0, - "width": 200.0, - "height": 150.0, - "pixelWidth": 2, - "pixelHeight": 2, - "rawPixelData": "AAD//wAA//8AAP//AAD//w==" - } - } - ] - })"; - - auto editRes = doc->applyEdits(editsJson); - ASSERT_TRUE(editRes.has_value()); - - auto saveRes = doc->saveIncremental(); - ASSERT_TRUE(saveRes.has_value()); - const auto& savedBytes = *saveRes; - ASSERT_FALSE(savedBytes.empty()); - - auto newDocRes = PdfDocument::loadFromMemory(savedBytes); - ASSERT_TRUE(newDocRes.has_value()); - auto newDoc = *newDocRes; - EXPECT_EQ(newDoc->pageCount(), 1); -} - -TEST(DocumentEditTest, ApplyPageRotationAndIncrementalSave) { - SKIP_IF_NO_PDFIUM(); - auto path = getCorpusPath("basic", "about_blank.pdf"); - if (!std::filesystem::exists(path)) { - GTEST_SKIP() << "about_blank.pdf not found in corpus."; - } - - auto docRes = PdfDocument::loadFromFile(path.string()); - ASSERT_TRUE(docRes.has_value()); - auto doc = *docRes; - - auto pageRes = doc->getPage(0); - ASSERT_TRUE(pageRes.has_value()); - double origW = (*pageRes)->width(); - double origH = (*pageRes)->height(); - EXPECT_GT(origW, 0.0); - EXPECT_GT(origH, origW); - - std::string editsJson1 = R"({ - "version": "1.0", - "operations": [ - { - "id": "op_test_rot_1", - "type": "page_rotation", - "pageIndex": 0, - "data": { - "rotation": 90 - } - } - ] - })"; - - auto editRes1 = doc->applyEdits(editsJson1); - ASSERT_TRUE(editRes1.has_value()); - - std::string editsJson2 = R"({ - "version": "1.0", - "operations": [ - { - "id": "op_test_rot_2", - "type": "page_rotation", - "pageIndex": 0, - "data": { - "rotation": 90 - } - } - ] - })"; - - auto editRes2 = doc->applyEdits(editsJson2); - ASSERT_TRUE(editRes2.has_value()); - - auto saveRes = doc->saveIncremental(); - ASSERT_TRUE(saveRes.has_value()); - const auto& savedBytes = *saveRes; - ASSERT_FALSE(savedBytes.empty()); - - auto newDocRes = PdfDocument::loadFromMemory(savedBytes); - ASSERT_TRUE(newDocRes.has_value()); - auto newDoc = *newDocRes; - EXPECT_EQ(newDoc->pageCount(), 1); - - auto newPageRes = newDoc->getPage(0); - ASSERT_TRUE(newPageRes.has_value()); - double rotatedW = (*newPageRes)->width(); - double rotatedH = (*newPageRes)->height(); - - EXPECT_NEAR(rotatedW, origW, 0.01); - EXPECT_NEAR(rotatedH, origH, 0.01); - - std::string editsJson3 = R"({ - "version": "1.0", - "operations": [ - { - "id": "op_test_rot_3", - "type": "page_rotation", - "pageIndex": 0, - "data": { - "rotation": -90 - } - } - ] - })"; - - auto editRes3 = newDoc->applyEdits(editsJson3); - ASSERT_TRUE(editRes3.has_value()); - - auto saveRes3 = newDoc->saveIncremental(); - ASSERT_TRUE(saveRes3.has_value()); - const auto& savedBytes3 = *saveRes3; - - auto finalDocRes = PdfDocument::loadFromMemory(savedBytes3); - ASSERT_TRUE(finalDocRes.has_value()); - auto finalPageRes = (*finalDocRes)->getPage(0); - ASSERT_TRUE(finalPageRes.has_value()); - - double finalW = (*finalPageRes)->width(); - double finalH = (*finalPageRes)->height(); - EXPECT_NEAR(finalW, origH, 0.01); - EXPECT_NEAR(finalH, origW, 0.01); -} - -TEST(DocumentEditTest, ApplyPageDeletionAndIncrementalSave) { - SKIP_IF_NO_PDFIUM(); - auto path = getCorpusPath("basic", "hello_world_2_pages.pdf"); - if (!std::filesystem::exists(path)) { - GTEST_SKIP() << "hello_world_2_pages.pdf not found in corpus."; - } - - auto docRes = PdfDocument::loadFromFile(path.string()); - ASSERT_TRUE(docRes.has_value()); - auto doc = *docRes; - EXPECT_EQ(doc->pageCount(), 2); - - std::string editsJson = R"({ - "version": "1.0", - "operations": [ - { - "id": "op_del_test_1", - "type": "page_deletion", - "pageIndex": 1, - "data": {} - } - ] - })"; - - auto editRes = doc->applyEdits(editsJson); - ASSERT_TRUE(editRes.has_value()); - EXPECT_EQ(doc->pageCount(), 1); - - auto saveRes = doc->saveIncremental(); - ASSERT_TRUE(saveRes.has_value()); - const auto& savedBytes = *saveRes; - ASSERT_FALSE(savedBytes.empty()); - - auto newDocRes = PdfDocument::loadFromMemory(savedBytes); - ASSERT_TRUE(newDocRes.has_value()); - EXPECT_EQ((*newDocRes)->pageCount(), 1); -} - -TEST(DocumentEditTest, ApplyPageReorderAndIncrementalSave) { - SKIP_IF_NO_PDFIUM(); - auto path = getCorpusPath("basic", "hello_world_2_pages.pdf"); - if (!std::filesystem::exists(path)) { - GTEST_SKIP() << "hello_world_2_pages.pdf not found in corpus."; - } - - auto docRes = PdfDocument::loadFromFile(path.string()); - ASSERT_TRUE(docRes.has_value()); - auto doc = *docRes; - EXPECT_EQ(doc->pageCount(), 2); - - std::string editsJson = R"({ - "version": "1.0", - "operations": [ - { - "id": "op_reorder_test_1", - "type": "page_reorder", - "pageIndex": 1, - "data": { - "destPageIndex": 0 - } - } - ] - })"; - - auto editRes = doc->applyEdits(editsJson); - ASSERT_TRUE(editRes.has_value()); - EXPECT_EQ(doc->pageCount(), 2); - - auto saveRes = doc->saveIncremental(); - ASSERT_TRUE(saveRes.has_value()); - const auto& savedBytes = *saveRes; - ASSERT_FALSE(savedBytes.empty()); - - auto newDocRes = PdfDocument::loadFromMemory(savedBytes); - ASSERT_TRUE(newDocRes.has_value()); - EXPECT_EQ((*newDocRes)->pageCount(), 2); -} - - -TEST(FontDiagnosticsTest, IntrospectionAccuracy) { - SKIP_IF_NO_PDFIUM(); - auto path = getCorpusPath("basic", "hello_world.pdf"); - if (!std::filesystem::exists(path)) { - GTEST_SKIP() << "hello_world.pdf not found in corpus."; - } - - auto docRes = PdfDocument::loadFromFile(path.string()); - ASSERT_TRUE(docRes.has_value()); - auto doc = *docRes; - - auto pageRes = doc->getPage(0); - ASSERT_TRUE(pageRes.has_value()); - auto page = *pageRes; - - auto fontsRes = page->getFonts(); - ASSERT_TRUE(fontsRes.has_value()); - auto fonts = *fontsRes; - - if (!fonts.empty()) { - auto firstFont = fonts[0]; - EXPECT_FALSE(firstFont.fontName.empty()); - EXPECT_FALSE(firstFont.type.empty()); - EXPECT_FALSE(firstFont.normalizedFamily.empty()); - EXPECT_FALSE(firstFont.internalFontId.empty()); - } - - auto docFontsRes = doc->getFonts(); - ASSERT_TRUE(docFontsRes.has_value()); - auto docFonts = *docFontsRes; - EXPECT_EQ(docFonts.size(), fonts.size()); -} - -TEST(FontDiagnosticsTest, SubsetAndVerticalTextIntrospection) { - SKIP_IF_NO_PDFIUM(); - auto path = getCorpusPath("fonts", "vertical_text.pdf"); - if (!std::filesystem::exists(path)) { - path = getCorpusPath("fonts", "utf-8.pdf"); - } - if (!std::filesystem::exists(path)) { - GTEST_SKIP() << "vertical_text.pdf or utf-8.pdf not found in corpus."; - } - - auto docRes = PdfDocument::loadFromFile(path.string()); - ASSERT_TRUE(docRes.has_value()); - auto doc = *docRes; - - auto fontsRes = doc->getFonts(); - ASSERT_TRUE(fontsRes.has_value()); - - for (const auto& f : *fontsRes) { - if (f.isSubset) { - EXPECT_FALSE(f.subsetTag.empty()); - EXPECT_EQ(f.subsetTag.size(), 6); - } - if (f.isVertical) { - EXPECT_TRUE(f.isVertical); - EXPECT_NE(f.encoding.find("Identity-V"), std::string::npos); - } - } -} - -TEST(FontDiagnosticsTest, CacheInvalidationAfterEdits) { - SKIP_IF_NO_PDFIUM(); - auto path = getCorpusPath("basic", "hello_world.pdf"); - if (!std::filesystem::exists(path)) { - GTEST_SKIP() << "hello_world.pdf not found in corpus."; - } - - auto docRes = PdfDocument::loadFromFile(path.string()); - ASSERT_TRUE(docRes.has_value()); - auto doc = *docRes; - - auto fontsRes1 = doc->getFonts(); - ASSERT_TRUE(fontsRes1.has_value()); - - std::string editsJson = R"({ - "version": "1.0", - "operations": [ - { - "id": "op_test_2", - "type": "text_overlay", - "pageIndex": 0, - "data": { - "text": "IntrospectionDiagnosticsNewText", - "x": 10.0, - "y": 20.0, - "width": 200.0, - "height": 20.0, - "fontSize": 12.0, - "fontFamily": "Helvetica", - "color": "#000000" - } - } - ] - })"; - - auto editRes = doc->applyEdits(editsJson); - ASSERT_TRUE(editRes.has_value()); - - auto fontsRes2 = doc->getFonts(); - ASSERT_TRUE(fontsRes2.has_value()); -} - -TEST(FontDiagnosticsTest, ConcurrencyThreadSafety) { - SKIP_IF_NO_PDFIUM(); - auto path = getCorpusPath("basic", "hello_world.pdf"); - if (!std::filesystem::exists(path)) { - GTEST_SKIP() << "hello_world.pdf not found in corpus."; - } - - auto docRes = PdfDocument::loadFromFile(path.string()); - ASSERT_TRUE(docRes.has_value()); - auto doc = *docRes; - - std::vector threads; - for (int i = 0; i < 8; ++i) { - threads.emplace_back([&doc]() { - auto res = doc->getFonts(); - ASSERT_TRUE(res.has_value()); - }); - } - - for (auto& t : threads) { - t.join(); - } -} - -TEST(FontDiagnosticsTest, DeepIntrospectionAndFontSizeVerification) { - SKIP_IF_NO_PDFIUM(); - - { - auto path = getCorpusPath("fonts", "utf-8.pdf"); - if (std::filesystem::exists(path)) { - auto docRes = PdfDocument::loadFromFile(path.string()); - ASSERT_TRUE(docRes.has_value()); - auto doc = *docRes; - - auto fontsRes = doc->getFonts(); - ASSERT_TRUE(fontsRes.has_value()); - auto fonts = *fontsRes; - - for (const auto& f : fonts) { - EXPECT_FALSE(f.fontName.empty()); - EXPECT_FALSE(f.type.empty()); - EXPECT_FALSE(f.normalizedFamily.empty()); - EXPECT_FALSE(f.internalFontId.empty()); - - if (f.isSubset) { - EXPECT_EQ(f.subsetTag.size(), 6); - for (char c : f.subsetTag) { - EXPECT_TRUE(std::isupper(static_cast(c))); - } - EXPECT_EQ(f.sourceType, "Embedded"); - EXPECT_TRUE(f.isEmbedded); - EXPECT_EQ(f.internalFontId, f.fontName); - } else { - EXPECT_TRUE(f.subsetTag.empty()); - EXPECT_EQ(f.internalFontId, f.fontName + "_" + f.type + "_" + std::to_string(f.flags)); - } - - if (f.sourceType == "SystemFallback") { - EXPECT_FALSE(f.isEmbedded); - EXPECT_TRUE(f.substitutedFrom.empty()); - EXPECT_TRUE(f.substitutedTo.empty()); - } else if (f.sourceType == "Substituted") { - EXPECT_FALSE(f.isEmbedded); - EXPECT_EQ(f.substitutedFrom, f.fontName); -#if defined(_WIN32) - EXPECT_EQ(f.substitutedTo, "Arial"); -#else - EXPECT_EQ(f.substitutedTo, "Liberation Sans"); -#endif - } - - EXPECT_GT(f.ascent, 0.0); - EXPECT_LT(f.descent, 0.0); - EXPECT_GT(f.capHeight, 0.0); - } - } - } - - { - auto path = getCorpusPath("fonts", "vertical_text.pdf"); - if (std::filesystem::exists(path)) { - auto docRes = PdfDocument::loadFromFile(path.string()); - ASSERT_TRUE(docRes.has_value()); - auto doc = *docRes; - - auto fontsRes = doc->getFonts(); - ASSERT_TRUE(fontsRes.has_value()); - - bool foundVertical = false; - for (const auto& f : *fontsRes) { - if (f.isVertical) { - foundVertical = true; - EXPECT_TRUE(f.encoding.find("-V") != std::string::npos || f.cmapName.find("-V") != std::string::npos); - } - } - EXPECT_TRUE(foundVertical); - } - } - - { - auto path1 = getCorpusPath("fonts", "vertical_identity_v.pdf"); - if (std::filesystem::exists(path1)) { - auto docRes = PdfDocument::loadFromFile(path1.string()); - ASSERT_TRUE(docRes.has_value()); - auto fontsRes = (*docRes)->getFonts(); - ASSERT_TRUE(fontsRes.has_value()); - - bool foundVertical = false; - for (const auto& f : *fontsRes) { - if (f.isVertical) foundVertical = true; - } - EXPECT_TRUE(foundVertical) << "Failed to detect vertical font in vertical_identity_v.pdf"; - } - } - - { - auto path = getCorpusPath("fonts", "utf-8.pdf"); - if (!std::filesystem::exists(path)) { - path = getCorpusPath("basic", "hello_world.pdf"); - } - if (std::filesystem::exists(path)) { - auto docRes = PdfDocument::loadFromFile(path.string()); - ASSERT_TRUE(docRes.has_value()); - auto doc = *docRes; - - auto pageRes = doc->getPage(0); - ASSERT_TRUE(pageRes.has_value()); - auto page = *pageRes; - - auto boundsRes = page->extractTextWithBounds(); - ASSERT_TRUE(boundsRes.has_value()); - - const auto& glyphs = *boundsRes; - ASSERT_FALSE(glyphs.empty()); - - std::vector uniqueSizes; - for (const auto& glyph : glyphs) { - if (glyph.text != " " && glyph.text != "\r" && glyph.text != "\n" && glyph.text != "\t") { - EXPECT_GT(glyph.w, 0.0); - EXPECT_GT(glyph.h, 0.0); - } - EXPECT_GT(glyph.fontSize, 0.0); - EXPECT_LT(glyph.fontSize, 100.0); - - if (std::find(uniqueSizes.begin(), uniqueSizes.end(), glyph.fontSize) == uniqueSizes.end()) { - uniqueSizes.push_back(glyph.fontSize); - } - } - - if (path.filename().string() == "utf-8.pdf") { - EXPECT_GE(uniqueSizes.size(), 2u); - } - } - } -} - - -TEST(FontDiagnosticsTest, RealPDFiumEmbeddingAndTypeAccuracy) { - SKIP_IF_NO_PDFIUM(); - - auto path = getCorpusPath("fonts", "text_font.pdf"); - if (!std::filesystem::exists(path)) { - GTEST_SKIP() << "text_font.pdf not found in corpus."; - } - - auto docRes = PdfDocument::loadFromFile(path.string()); - ASSERT_TRUE(docRes.has_value()) << "Failed to open text_font.pdf"; - auto doc = *docRes; - - auto pageRes = doc->getPage(0); - ASSERT_TRUE(pageRes.has_value()); - auto fontsRes = (*pageRes)->getFonts(); - ASSERT_TRUE(fontsRes.has_value()); - const auto& fonts = *fontsRes; - ASSERT_FALSE(fonts.empty()) << "text_font.pdf must expose at least one font"; - - for (const auto& f : fonts) { - EXPECT_FALSE(f.fontName.empty()); - EXPECT_FALSE(f.type.empty()); - - static const std::vector kValidTypes = { - "Type1", "TrueType", "CIDFontType0", "CIDFontType2" - }; - bool typeValid = std::find(kValidTypes.begin(), kValidTypes.end(), f.type) - != kValidTypes.end(); - EXPECT_TRUE(typeValid) << "Unexpected type '" << f.type << "' for font '" << f.fontName << "'"; - - if (f.isSubset) { - EXPECT_TRUE(f.isEmbedded) - << "Subset font '" << f.fontName - << "' must be embedded (FPDFFont_GetIsEmbedded should return 1)"; - EXPECT_EQ(f.sourceType, "Embedded") - << "sourceType must be 'Embedded' when isEmbedded=true"; - EXPECT_TRUE(f.substitutedFrom.empty()); - EXPECT_TRUE(f.substitutedTo.empty()); - } - - if (f.isEmbedded) { - EXPECT_EQ(f.sourceType, "Embedded"); - } - } -} - -TEST(FontDiagnosticsTest, RealPDFiumMetricsAccuracy) { - SKIP_IF_NO_PDFIUM(); - - auto path = getCorpusPath("fonts", "text_font.pdf"); - if (!std::filesystem::exists(path)) { - GTEST_SKIP() << "text_font.pdf not found in corpus."; - } - - auto docRes = PdfDocument::loadFromFile(path.string()); - ASSERT_TRUE(docRes.has_value()); - auto fontsRes = (*docRes)->getFonts(); - ASSERT_TRUE(fontsRes.has_value()); - - for (const auto& f : *fontsRes) { - EXPECT_GT(f.ascent, 0.0) - << "ascent must be positive for font '" << f.fontName << "'"; - EXPECT_LT(f.descent, 0.0) - << "descent must be negative for font '" << f.fontName << "'"; - EXPECT_GT(f.capHeight, 0.0) - << "capHeight must be positive for font '" << f.fontName << "'"; - - EXPECT_LE(f.capHeight, f.ascent + 1.0) - << "capHeight should not exceed ascent for font '" << f.fontName << "'"; - - EXPECT_LT(f.ascent, 1500.0) << "Implausibly large ascent for '" << f.fontName << "'"; - EXPECT_GT(f.descent, -1500.0) << "Implausibly deep descent for '" << f.fontName << "'"; - } -} - -TEST(FontDiagnosticsTest, ToUnicodePresenceAccuracy) { - SKIP_IF_NO_PDFIUM(); - - { - auto path = getCorpusPath("fonts", "with_tounicode.pdf"); - if (std::filesystem::exists(path)) { - auto docRes = PdfDocument::loadFromFile(path.string()); - if (docRes.has_value()) { - auto pageRes = (*docRes)->getPage(0); - if (pageRes.has_value()) { - auto fontsRes = (*pageRes)->getFonts(); - if (fontsRes.has_value() && !fontsRes->empty()) { - bool anyTrue = false; - for (const auto& f : *fontsRes) { - if (f.hasToUnicode) { anyTrue = true; break; } - } - EXPECT_TRUE(anyTrue) - << "At least one font in with_tounicode.pdf must have hasToUnicode=true"; - } - } - } - } - } - - { - auto path = getCorpusPath("fonts", "no_tounicode.pdf"); - if (std::filesystem::exists(path)) { - auto docRes = PdfDocument::loadFromFile(path.string()); - if (docRes.has_value()) { - auto pageRes = (*docRes)->getPage(0); - if (pageRes.has_value()) { - auto fontsRes = (*pageRes)->getFonts(); - if (fontsRes.has_value() && !fontsRes->empty()) { - for (const auto& f : *fontsRes) { - EXPECT_FALSE(f.hasToUnicode) - << "Font '" << f.fontName - << "' in no_tounicode.pdf must have hasToUnicode=false"; - } - } - } - } - } - } - - { - auto path = getCorpusPath("fonts", "latin_extended.pdf"); - if (std::filesystem::exists(path)) { - auto docRes = PdfDocument::loadFromFile(path.string()); - if (docRes.has_value()) { - auto pageRes = (*docRes)->getPage(0); - if (pageRes.has_value()) { - auto fontsRes = (*pageRes)->getFonts(); - if (fontsRes.has_value() && !fontsRes->empty()) { - bool anyTrue = false; - for (const auto& f : *fontsRes) { - if (f.hasToUnicode) { anyTrue = true; break; } - } - EXPECT_TRUE(anyTrue) - << "At least one font in latin_extended.pdf must decode to Unicode"; - } - } - } - } - } -} - -TEST(UtfConversionTest, EmojiSurrogatePairs) { - std::string utf8_grinning = "\xF0\x9F\x98\x80"; - auto utf16 = pdfengine::parser::utf8_to_utf16le(utf8_grinning); - ASSERT_EQ(utf16.size(), 3); - EXPECT_EQ(utf16[0], 0xD83D); - EXPECT_EQ(utf16[1], 0xDE00); - EXPECT_EQ(utf16[2], 0x0000); - - std::string utf8_out = pdfengine::parser::utf16le_to_utf8(reinterpret_cast(utf16.data()), utf16.size() - 1); - EXPECT_EQ(utf8_out, utf8_grinning); - - std::string utf8_rocket = "\xF0\x9F\x9A\x80"; - utf16 = pdfengine::parser::utf8_to_utf16le(utf8_rocket); - ASSERT_EQ(utf16.size(), 3); - EXPECT_EQ(utf16[0], 0xD83D); - EXPECT_EQ(utf16[1], 0xDE80); - EXPECT_EQ(utf16[2], 0x0000); - - utf8_out = pdfengine::parser::utf16le_to_utf8(reinterpret_cast(utf16.data()), utf16.size() - 1); - EXPECT_EQ(utf8_out, utf8_rocket); -} - -TEST(UtfConversionTest, CJKExtensionB) { - std::string utf8_cjk = "\xF0\xA0\x80\x80"; - auto utf16 = pdfengine::parser::utf8_to_utf16le(utf8_cjk); - ASSERT_EQ(utf16.size(), 3); - EXPECT_EQ(utf16[0], 0xD840); - EXPECT_EQ(utf16[1], 0xDC00); - EXPECT_EQ(utf16[2], 0x0000); - - std::string utf8_out = pdfengine::parser::utf16le_to_utf8(reinterpret_cast(utf16.data()), utf16.size() - 1); - EXPECT_EQ(utf8_out, utf8_cjk); -} - -TEST(UtfConversionTest, RoundtripMixed) { - std::string mixed = "A\xF0\x9F\x98\x80""B\xF0\xA0\x80\x80""C"; - auto utf16 = pdfengine::parser::utf8_to_utf16le(mixed); - std::string mixed_out = pdfengine::parser::utf16le_to_utf8(reinterpret_cast(utf16.data()), utf16.size() - 1); - EXPECT_EQ(mixed_out, mixed); -} - - -TEST(CjkResolutionTest, AdobeCNS1) { - using pdfengine::fonts::pdf_fonts::CjkCollectionDB; - - EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-CNS1", 100), 0x4E00); - EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-CNS1", 112), 0x4E2D); - EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-CNS1", 130), 0x4ED7); - - EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-CNS1", 131), 0x4ED8); - EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-CNS1", 135), 0x4EDF); - EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-CNS1", 140), 0x4F01); - - EXPECT_EQ(CjkCollectionDB::resolveCID("Identity-H-CNS1", 137), 0x4EE3); -} - -TEST(CjkResolutionTest, AdobeKorea1) { - using pdfengine::fonts::pdf_fonts::CjkCollectionDB; - - EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Korea1", 101), 0xAC00); - EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Korea1", 119), 0xAC1C); - EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Korea1", 150), 0xAC90); - - EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Korea1", 151), 0xAC94); - EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Korea1", 156), 0xACA9); - EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Korea1", 160), 0xACBD); - - EXPECT_EQ(CjkCollectionDB::resolveCID("UniKS-UTF16-H-Korea1", 153), 0xACA0); -} - - -TEST(GlyphCacheTest, ConcurrencyBench) { - using namespace pdfengine::fonts; - - FontFace face; - bool loaded = face.loadFromFile("C:\\Windows\\Fonts\\arial.ttf"); - if (!loaded) { - GTEST_SKIP() << "Skipping benchmark: Arial font not found."; - } - - GlyphCache cache(1000); - - auto run_benchmark = [&](int num_threads, int ops_per_thread) { - std::atomic start_flag{0}; - std::vector threads; - - for (int i = 0; i < num_threads; ++i) { - threads.emplace_back([&, i]() { - while (start_flag.load() == 0) { std::this_thread::yield(); } - - for (int op = 0; op < ops_per_thread; ++op) { - unsigned int glyphIndex = (op + i) % 2000; - unsigned int fontSize = 12 + (op % 5); - - auto hit = cache.get(face, glyphIndex, fontSize); - if (!hit) { - GlyphBitmap bmp; - bmp.width = 10; bmp.height = 10; - cache.insert(face, glyphIndex, fontSize, bmp); - } - } - }); - } - - auto start_time = std::chrono::high_resolution_clock::now(); - start_flag.store(1); - for (auto& t : threads) { t.join(); } - auto end_time = std::chrono::high_resolution_clock::now(); - - std::chrono::duration diff = end_time - start_time; - return diff.count(); - }; - - run_benchmark(2, 1000); - cache.clear(); - - double time_10 = run_benchmark(10, 10000); - std::cout << "[ BENCHMARK ] 10 Threads Time: " << time_10 << " seconds (" << (100000.0 / time_10) << " ops/sec)\n"; - - cache.clear(); - - double time_50 = run_benchmark(50, 10000); - std::cout << "[ BENCHMARK ] 50 Threads Time: " << time_50 << " seconds (" << (500000.0 / time_50) << " ops/sec)\n"; - - EXPECT_LE(cache.size(), 1000 + 16); -} - -TEST(FontDiagnosticsTest, EmbeddedFontResolutionAndReloadingVerification) { - SKIP_IF_NO_PDFIUM(); - - std::vector testFiles = { - "text_font.pdf", - "embedded_truetype.pdf", - "embedded_cid_font.pdf", - "subset_font.pdf", - "latin_extended.pdf" - }; - - bool foundAnyEmbedded = false; - for (const auto& fileName : testFiles) { - auto path = getCorpusPath("fonts", fileName); - if (!std::filesystem::exists(path)) { - continue; - } - std::cout << "\n========================================\n"; - std::cout << "Testing PDF: " << fileName << "\n"; - std::cout << "========================================\n"; - - auto docRes = PdfDocument::loadFromFile(path.string()); - if (!docRes.has_value()) { - std::cout << "Failed to load document: " << fileName << std::endl; - continue; - } - auto doc = *docRes; - - auto fontsRes = doc->getFonts(); - if (!fontsRes.has_value()) { - std::cout << "Failed to get fonts for: " << fileName << std::endl; - continue; - } - const auto& fonts = *fontsRes; - - for (const auto& fontInfo : fonts) { - std::cout << "Font: " << fontInfo.fontName - << ", type: " << fontInfo.type - << ", isEmbedded: " << (fontInfo.isEmbedded ? "yes" : "no") - << ", flags: " << fontInfo.flags << std::endl; - if (fontInfo.isEmbedded) { - foundAnyEmbedded = true; - - auto resolvedFontRes = doc->getResolvedFont(fontInfo); - if (!resolvedFontRes.has_value()) { - std::cout << " Failed to resolve font: " << resolvedFontRes.error() << std::endl; - continue; - } - auto resolvedFont = *resolvedFontRes; - std::cout << " Resolved font successfully." << std::endl; - - auto face = static_cast(resolvedFont->getFontFace().getFace()); - if (face) { - std::cout << " FreeType Face Num Glyphs: " << face->num_glyphs << std::endl; - std::cout << " FreeType Charmaps Count: " << face->num_charmaps << std::endl; - for (int i = 0; i < face->num_charmaps; ++i) { - FT_CharMap cm = face->charmaps[i]; - std::cout << " Charmap " << i << ": platform_id=" << cm->platform_id - << ", encoding_id=" << cm->encoding_id << std::endl; - - FT_Error err = FT_Set_Charmap(face, cm); - if (err) { - std::cout << " FT_Set_Charmap failed: " << err << std::endl; - continue; - } - - FT_UInt gindex; - FT_ULong charcode = FT_Get_First_Char(face, &gindex); - std::cout << " Mapped characters under charmap " << i << ": "; - int count = 0; - while (gindex != 0 && count < 10) { - std::cout << charcode << "->" << gindex << " "; - charcode = FT_Get_Next_Char(face, charcode, &gindex); - count++; - } - std::cout << std::endl; - } - - if (face->num_charmaps > 0) { - FT_Set_Charmap(face, face->charmaps[0]); - } - - std::cout << " Glyph names: "; - for (int i = 0; i < face->num_glyphs; ++i) { - char nameBuf[64] = {0}; - if (FT_Get_Glyph_Name(face, i, nameBuf, sizeof(nameBuf)) == 0) { - std::cout << i << ":" << nameBuf << " "; - } else { - std::cout << i << ":[unknown] "; - } - } - std::cout << std::endl; - } else { - std::cout << " No FreeType Face available." << std::endl; - } - - EXPECT_TRUE(resolvedFont->isEmbedded()); - - std::vector testChars = {32, 48, 65, 97}; - for (uint32_t cp : testChars) { - bool hasG = resolvedFont->hasGlyph(cp); - double w = resolvedFont->getAdvanceWidth(cp, 12.0); - std::cout << " char(" << cp << "): hasGlyph=" << (hasG ? "yes" : "no") - << ", advanceWidth=" << w << std::endl; - } - - auto metrics = resolvedFont->getMetrics(12.0); - std::cout << " Metrics: ascent=" << metrics.ascent << ", descent=" << metrics.descent << ", capHeight=" << metrics.capHeight << std::endl; - EXPECT_NE(metrics.ascent, 0.0); - EXPECT_NE(metrics.descent, 0.0); - EXPECT_NE(metrics.capHeight, 0.0); - - if (fileName == "text_font.pdf") { - EXPECT_TRUE(resolvedFont->hasGlyph(1)); - double w = resolvedFont->getAdvanceWidth(1, 12.0); - EXPECT_GT(w, 0.0); - std::cout << " [VERIFIED] text_font.pdf char(1): hasGlyph=yes, advanceWidth=" << w << std::endl; - } - - if (face->num_glyphs > 1) { - bool foundNonZeroWidth = false; - for (int gid = 1; gid < face->num_glyphs; ++gid) { - FT_Error err = FT_Load_Glyph(face, gid, FT_LOAD_DEFAULT); - if (err == 0) { - double directWidth = static_cast(face->glyph->advance.x) / 64.0; - if (directWidth > 0.0) { - foundNonZeroWidth = true; - std::cout << " [VERIFIED] Direct glyph " << gid << " load: advanceWidth=" << directWidth << std::endl; - break; - } - } - } - EXPECT_TRUE(foundNonZeroWidth) << "Expected to find at least one glyph with a non-zero advance width"; - } - } - } - } - EXPECT_TRUE(foundAnyEmbedded) << "Expected to find at least one embedded font in test files"; -} - -TEST(DocumentEditTest, ReplaceTextMVPStandardFont) { - SKIP_IF_NO_PDFIUM(); - auto path = getCorpusPath("basic", "hello_world.pdf"); - if (!std::filesystem::exists(path)) { - GTEST_SKIP() << "hello_world.pdf not found in corpus."; - } - - auto docRes = PdfDocument::loadFromFile(path.string()); - ASSERT_TRUE(docRes.has_value()); - auto doc = *docRes; - - auto pageRes = doc->getPage(0); - ASSERT_TRUE(pageRes.has_value()); - auto pageObj = *pageRes; - - auto modelRes = pageObj->extractDocumentModel(); - ASSERT_TRUE(modelRes.has_value()); - const auto& model = *modelRes; - - std::vector objectIndices; - for (const auto& p : model.paragraphs) { - for (const auto& line : p.lines) { - for (const auto& run : line.runs) { - if (run.text.find("Hello") != std::string::npos) { - objectIndices = run.objectIndices; - break; - } - } - if (!objectIndices.empty()) break; - } - if (!objectIndices.empty()) break; - } - - ASSERT_FALSE(objectIndices.empty()) << "Could not find a text object in hello_world.pdf"; - - std::string indicesStr = ""; - for (size_t i = 0; i < objectIndices.size(); ++i) { - indicesStr += std::to_string(objectIndices[i]); - if (i + 1 < objectIndices.size()) indicesStr += ","; - } - - std::string editsJson = R"({ - "version": "1.0", - "operations": [ - { - "id": "op_mvp_1", - "type": "replace_text", - "pageIndex": 0, - "objectIndices": [)" + indicesStr + R"(], - "text": "Greeting, universe!" - } - ] - })"; - - auto editRes = doc->applyEdits(editsJson); - ASSERT_TRUE(editRes.has_value()); - - auto saveRes = doc->saveIncremental(); - ASSERT_TRUE(saveRes.has_value()); - const auto& savedBytes = *saveRes; - ASSERT_FALSE(savedBytes.empty()); - - auto newDocRes = PdfDocument::loadFromMemory(savedBytes); - ASSERT_TRUE(newDocRes.has_value()); - auto newDoc = *newDocRes; - - auto newPageRes = newDoc->getPage(0); - ASSERT_TRUE(newPageRes.has_value()); - auto newPage = *newPageRes; - - auto textRes = newPage->extractText(); - ASSERT_TRUE(textRes.has_value()); - EXPECT_NE(textRes->find("Greeting, universe!"), std::string::npos); - EXPECT_EQ(textRes->find("Hello"), std::string::npos); -} - -TEST(DocumentEditTest, ReplaceTextRuntimeFontEngine) { - SKIP_IF_NO_PDFIUM(); - auto path = getCorpusPath("fonts", "latin_extended.pdf"); - if (!std::filesystem::exists(path)) { - GTEST_SKIP() << "latin_extended.pdf not found in corpus."; - } - - auto docRes = PdfDocument::loadFromFile(path.string()); - ASSERT_TRUE(docRes.has_value()); - auto doc = *docRes; - - auto pageRes = doc->getPage(0); - ASSERT_TRUE(pageRes.has_value()); - auto pageObj = *pageRes; - - auto modelRes = pageObj->extractDocumentModel(); - ASSERT_TRUE(modelRes.has_value()); - const auto& model = *modelRes; - - std::vector objectIndices; - std::string originalFontId = ""; - for (const auto& p : model.paragraphs) { - for (const auto& line : p.lines) { - for (const auto& run : line.runs) { - if (run.fontName.find("Roboto-Regular") != std::string::npos) { - objectIndices = run.objectIndices; - originalFontId = run.internalFontId; - break; - } - } - if (!objectIndices.empty()) break; - } - if (!objectIndices.empty()) break; - } - - ASSERT_FALSE(objectIndices.empty()) << "Could not find target text run in latin_extended.pdf"; - - std::string indicesStr = ""; - for (size_t i = 0; i < objectIndices.size(); ++i) { - indicesStr += std::to_string(objectIndices[i]); - if (i + 1 < objectIndices.size()) indicesStr += ","; - } - - std::string editsJson = R"({ - "version": "1.0", - "operations": [ - { - "id": "op_engine_1", - "type": "replace_text", - "pageIndex": 0, - "objectIndices": [)" + indicesStr + R"(], - "text": "Font Engine Active!", - "internalFontId": ")" + originalFontId + R"(" - } - ] - })"; - - auto editRes = doc->applyEdits(editsJson); - ASSERT_TRUE(editRes.has_value()); - - auto saveRes = doc->saveIncremental(); - ASSERT_TRUE(saveRes.has_value()); - const auto& savedBytes = *saveRes; - ASSERT_FALSE(savedBytes.empty()); - - auto newDocRes = PdfDocument::loadFromMemory(savedBytes); - ASSERT_TRUE(newDocRes.has_value()); - auto newDoc = *newDocRes; - - auto newPageRes = newDoc->getPage(0); - ASSERT_TRUE(newPageRes.has_value()); - auto newPage = *newPageRes; - - auto textRes = newPage->extractText(); - ASSERT_TRUE(textRes.has_value()); - EXPECT_NE(textRes->find("Font Engine Active!"), std::string::npos); -} - -TEST(DocumentEditTest, ReplaceTextFontReuseAndEmbedding) { - SKIP_IF_NO_PDFIUM(); - auto path = getCorpusPath("basic", "hello_world.pdf"); - if (!std::filesystem::exists(path)) { - GTEST_SKIP() << "hello_world.pdf not found in corpus."; - } - - auto docRes = PdfDocument::loadFromFile(path.string()); - ASSERT_TRUE(docRes.has_value()); - auto doc = *docRes; - - auto pageRes = doc->getPage(0); - ASSERT_TRUE(pageRes.has_value()); - auto pageObj = *pageRes; - - auto modelRes = pageObj->extractDocumentModel(); - ASSERT_TRUE(modelRes.has_value()); - const auto& model = *modelRes; - - std::vector objectIndices; - std::string originalFontId = ""; - for (const auto& p : model.paragraphs) { - for (const auto& line : p.lines) { - for (const auto& run : line.runs) { - if (!run.objectIndices.empty()) { - objectIndices = run.objectIndices; - originalFontId = run.internalFontId; - break; - } - } - if (!objectIndices.empty()) break; - } - if (!objectIndices.empty()) break; - } - - ASSERT_FALSE(objectIndices.empty()) << "Could not find a text run in hello_world.pdf"; - - std::string indicesStr = ""; - for (size_t i = 0; i < objectIndices.size(); ++i) { - indicesStr += std::to_string(objectIndices[i]); - if (i + 1 < objectIndices.size()) indicesStr += ","; - } - - std::string editsJson = R"({ - "version": "1.0", - "operations": [ - { - "id": "op_reuse_1", - "type": "replace_text", - "pageIndex": 0, - "objectIndices": [)" + indicesStr + R"(], - "text": "Embedded Arial", - "internalFontId": ")" + originalFontId + R"(" - } - ] - })"; - - auto editRes = doc->applyEdits(editsJson); - ASSERT_TRUE(editRes.has_value()); - - auto saveRes = doc->saveIncremental(); - ASSERT_TRUE(saveRes.has_value()); - const auto& savedBytes = *saveRes; - ASSERT_FALSE(savedBytes.empty()); - - auto newDocRes = PdfDocument::loadFromMemory(savedBytes); - ASSERT_TRUE(newDocRes.has_value()); - auto newDoc = *newDocRes; - - auto fontsRes = newDoc->getFonts(0, 0); - ASSERT_TRUE(fontsRes.has_value()); - - bool foundEmbeddedArial = false; - for (const auto& f : *fontsRes) { - if (f.isEmbedded && (f.fontName.find("Arial") != std::string::npos || f.fontName.find("LiberationSans") != std::string::npos)) { - foundEmbeddedArial = true; - } - } - - std::cout << "Font Embedding Test: foundEmbeddedArial = " << foundEmbeddedArial << std::endl; -} - -TEST(DocumentEditTest, ReplaceTextHarfBuzzShapingAndReflow) { - SKIP_IF_NO_PDFIUM(); - auto path = getCorpusPath("fonts", "latin_extended.pdf"); - if (!std::filesystem::exists(path)) { - GTEST_SKIP() << "latin_extended.pdf not found in corpus."; - } - - auto docRes = PdfDocument::loadFromFile(path.string()); - ASSERT_TRUE(docRes.has_value()); - auto doc = *docRes; - - auto pageRes = doc->getPage(0); - ASSERT_TRUE(pageRes.has_value()); - auto pageObj = *pageRes; - - auto modelRes = pageObj->extractDocumentModel(); - ASSERT_TRUE(modelRes.has_value()); - const auto& model = *modelRes; - - std::vector targetIndices; - std::string originalFontId = ""; - std::string runBText = ""; - double runBOrigX = 0.0; - double runBOrigY = 0.0; - - for (const auto& p : model.paragraphs) { - for (const auto& line : p.lines) { - if (line.runs.size() >= 2) { - const auto& runA = line.runs[0]; - const auto& runB = line.runs[1]; - if (runA.fontName.find("Roboto-Regular") != std::string::npos && - !runA.objectIndices.empty() && - runB.x > runA.x) { - targetIndices = runA.objectIndices; - originalFontId = runA.internalFontId; - runBText = runB.text; - runBOrigX = runB.x; - runBOrigY = runB.y; - break; - } - } - } - if (!targetIndices.empty()) break; - } - - if (targetIndices.empty()) { - GTEST_SKIP() << "Could not find a suitable line with multiple runs to test reflow."; - } - - std::string indicesStr = ""; - for (size_t i = 0; i < targetIndices.size(); ++i) { - indicesStr += std::to_string(targetIndices[i]); - if (i + 1 < targetIndices.size()) indicesStr += ","; - } - - std::string editsJson = R"({ - "version": "1.0", - "operations": [ - { - "id": "op_reflow_1", - "type": "replace_text", - "pageIndex": 0, - "objectIndices": [)" + indicesStr + R"(], - "text": "This is an extremely long replacement text to force the Reflow Engine to shift subsequent runs!", - "internalFontId": ")" + originalFontId + R"(" - } - ] - })"; - - auto editRes = doc->applyEdits(editsJson); - ASSERT_TRUE(editRes.has_value()); - - auto saveRes = doc->saveIncremental(); - ASSERT_TRUE(saveRes.has_value()); - const auto& savedBytes = *saveRes; - ASSERT_FALSE(savedBytes.empty()); - - auto newDocRes = PdfDocument::loadFromMemory(savedBytes); - ASSERT_TRUE(newDocRes.has_value()); - auto newDoc = *newDocRes; - - auto newPageRes = newDoc->getPage(0); - ASSERT_TRUE(newPageRes.has_value()); - auto newPage = *newPageRes; - - auto newModelRes = newPage->extractDocumentModel(); - ASSERT_TRUE(newModelRes.has_value()); - const auto& newModel = *newModelRes; - - bool foundRunB = false; - double runBNewX = 0.0; - for (const auto& p : newModel.paragraphs) { - for (const auto& line : p.lines) { - for (const auto& run : line.runs) { - if (run.text == runBText && std::abs(run.y - runBOrigY) < 5.0) { - foundRunB = true; - runBNewX = run.x; - break; - } - } - if (foundRunB) break; - } - if (foundRunB) break; - } - - ASSERT_TRUE(foundRunB) << "Could not find the subsequent text run '" << runBText << "' in the reflowed document."; - EXPECT_GT(runBNewX, runBOrigX + 10.0) << "The subsequent text run did not shift to the right by at least 10 points."; - - std::cout << "Reflow Engine verified: '" << runBText << "' shifted from X=" << runBOrigX << " to X=" << runBNewX << std::endl; -} - -} \ No newline at end of file diff --git a/engine/tests/document_test_helpers.hpp b/engine/tests/document_test_helpers.hpp new file mode 100644 index 0000000..13e821f --- /dev/null +++ b/engine/tests/document_test_helpers.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include +#include "parser/pdfium_document.hpp" +#include "fonts/pdf_fonts/encoding/cjk_collection_db.hpp" +#include +#include +#include +#include +#include +#include +#include +#include "fonts/cache/glyph_cache.hpp" +#include "fonts/pdf_fonts/font.hpp" +#ifndef TEST_CORPUS_DIR +#define TEST_CORPUS_DIR "../../corpus" +#endif + +namespace { + +#define SKIP_IF_NO_PDFIUM() \ + if (!pdfengine::engineHasPdfium()) { \ + GTEST_SKIP() << "Skipping PDFium tests because PDFium is not linked."; \ + } + +std::filesystem::path getCorpusPath(const std::string& subfolder, const std::string& filename) { + return std::filesystem::path(TEST_CORPUS_DIR) / subfolder / filename; +} + +std::vector readFile(const std::filesystem::path& path) { + std::ifstream file(path, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + return {}; + } + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + std::vector buffer(size); + if (file.read(reinterpret_cast(buffer.data()), size)) { + return buffer; + } + return {}; +} + +const std::vector corpus_basic = { + "about_blank.pdf", "black.pdf", "clip_path.pdf", "dashed_lines.pdf", + "hello_world.pdf", "hello_world_2_pages.pdf", "many_rectangles.pdf", + "rectangles.pdf", "rectangles_multi_pages.pdf", "whitespace.pdf" +}; + +const std::vector corpus_fonts = { + "latin_extended.pdf", "rotated_text.pdf", "rotated_text_90.pdf", + "text_font.pdf", "utf-8.pdf", "vertical_text.pdf", "hebrew_mirrored.pdf" +}; + +const std::vector corpus_edge = { + "annots.pdf", "bookmarks.pdf", "combobox_form.pdf", "embedded_attachments.pdf", + "empty_xref.pdf", "encrypted.pdf", "linearized.pdf", "listbox_form.pdf", + "no_page_count.pdf", "page_labels.pdf", "text_form.pdf", "unsupported_feature.pdf", + "zero_length_stream.pdf" +}; + +} \ No newline at end of file diff --git a/engine/tests/font_diagnostics_test.cpp b/engine/tests/font_diagnostics_test.cpp new file mode 100644 index 0000000..9e26f52 --- /dev/null +++ b/engine/tests/font_diagnostics_test.cpp @@ -0,0 +1,601 @@ +#include "document_test_helpers.hpp" + +namespace pdfengine { + +TEST(FontDiagnosticsTest, IntrospectionAccuracy) { + SKIP_IF_NO_PDFIUM(); + auto path = getCorpusPath("basic", "hello_world.pdf"); + if (!std::filesystem::exists(path)) { + GTEST_SKIP() << "hello_world.pdf not found in corpus."; + } + + auto docRes = PdfDocument::loadFromFile(path.string()); + ASSERT_TRUE(docRes.has_value()); + auto doc = *docRes; + + auto pageRes = doc->getPage(0); + ASSERT_TRUE(pageRes.has_value()); + auto page = *pageRes; + + auto fontsRes = page->getFonts(); + ASSERT_TRUE(fontsRes.has_value()); + auto fonts = *fontsRes; + + if (!fonts.empty()) { + auto firstFont = fonts[0]; + EXPECT_FALSE(firstFont.fontName.empty()); + EXPECT_FALSE(firstFont.type.empty()); + EXPECT_FALSE(firstFont.normalizedFamily.empty()); + EXPECT_FALSE(firstFont.internalFontId.empty()); + } + + auto docFontsRes = doc->getFonts(); + ASSERT_TRUE(docFontsRes.has_value()); + auto docFonts = *docFontsRes; + EXPECT_EQ(docFonts.size(), fonts.size()); +} + +TEST(FontDiagnosticsTest, SubsetAndVerticalTextIntrospection) { + SKIP_IF_NO_PDFIUM(); + auto path = getCorpusPath("fonts", "vertical_text.pdf"); + if (!std::filesystem::exists(path)) { + path = getCorpusPath("fonts", "utf-8.pdf"); + } + if (!std::filesystem::exists(path)) { + GTEST_SKIP() << "vertical_text.pdf or utf-8.pdf not found in corpus."; + } + + auto docRes = PdfDocument::loadFromFile(path.string()); + ASSERT_TRUE(docRes.has_value()); + auto doc = *docRes; + + auto fontsRes = doc->getFonts(); + ASSERT_TRUE(fontsRes.has_value()); + + for (const auto& f : *fontsRes) { + if (f.isSubset) { + EXPECT_FALSE(f.subsetTag.empty()); + EXPECT_EQ(f.subsetTag.size(), 6); + } + if (f.isVertical) { + EXPECT_TRUE(f.isVertical); + EXPECT_NE(f.encoding.find("Identity-V"), std::string::npos); + } + } +} + +TEST(FontDiagnosticsTest, CacheInvalidationAfterEdits) { + SKIP_IF_NO_PDFIUM(); + auto path = getCorpusPath("basic", "hello_world.pdf"); + if (!std::filesystem::exists(path)) { + GTEST_SKIP() << "hello_world.pdf not found in corpus."; + } + + auto docRes = PdfDocument::loadFromFile(path.string()); + ASSERT_TRUE(docRes.has_value()); + auto doc = *docRes; + + auto fontsRes1 = doc->getFonts(); + ASSERT_TRUE(fontsRes1.has_value()); + + std::string editsJson = R"({ + "version": "1.0", + "operations": [ + { + "id": "op_test_2", + "type": "text_overlay", + "pageIndex": 0, + "data": { + "text": "IntrospectionDiagnosticsNewText", + "x": 10.0, + "y": 20.0, + "width": 200.0, + "height": 20.0, + "fontSize": 12.0, + "fontFamily": "Helvetica", + "color": "#000000" + } + } + ] + })"; + + auto editRes = doc->applyEdits(editsJson); + ASSERT_TRUE(editRes.has_value()); + + auto fontsRes2 = doc->getFonts(); + ASSERT_TRUE(fontsRes2.has_value()); +} + +TEST(FontDiagnosticsTest, ConcurrencyThreadSafety) { + SKIP_IF_NO_PDFIUM(); + auto path = getCorpusPath("basic", "hello_world.pdf"); + if (!std::filesystem::exists(path)) { + GTEST_SKIP() << "hello_world.pdf not found in corpus."; + } + + auto docRes = PdfDocument::loadFromFile(path.string()); + ASSERT_TRUE(docRes.has_value()); + auto doc = *docRes; + + std::vector threads; + for (int i = 0; i < 8; ++i) { + threads.emplace_back([&doc]() { + auto res = doc->getFonts(); + ASSERT_TRUE(res.has_value()); + }); + } + + for (auto& t : threads) { + t.join(); + } +} + +TEST(FontDiagnosticsTest, DeepIntrospectionAndFontSizeVerification) { + SKIP_IF_NO_PDFIUM(); + + { + auto path = getCorpusPath("fonts", "utf-8.pdf"); + if (std::filesystem::exists(path)) { + auto docRes = PdfDocument::loadFromFile(path.string()); + ASSERT_TRUE(docRes.has_value()); + auto doc = *docRes; + + auto fontsRes = doc->getFonts(); + ASSERT_TRUE(fontsRes.has_value()); + auto fonts = *fontsRes; + + for (const auto& f : fonts) { + EXPECT_FALSE(f.fontName.empty()); + EXPECT_FALSE(f.type.empty()); + EXPECT_FALSE(f.normalizedFamily.empty()); + EXPECT_FALSE(f.internalFontId.empty()); + + if (f.isSubset) { + EXPECT_EQ(f.subsetTag.size(), 6); + for (char c : f.subsetTag) { + EXPECT_TRUE(std::isupper(static_cast(c))); + } + EXPECT_EQ(f.sourceType, "Embedded"); + EXPECT_TRUE(f.isEmbedded); + EXPECT_EQ(f.internalFontId, f.fontName); + } else { + EXPECT_TRUE(f.subsetTag.empty()); + EXPECT_EQ(f.internalFontId, f.fontName + "_" + f.type + "_" + std::to_string(f.flags)); + } + + if (f.sourceType == "SystemFallback") { + EXPECT_FALSE(f.isEmbedded); + EXPECT_TRUE(f.substitutedFrom.empty()); + EXPECT_TRUE(f.substitutedTo.empty()); + } else if (f.sourceType == "Substituted") { + EXPECT_FALSE(f.isEmbedded); + EXPECT_EQ(f.substitutedFrom, f.fontName); +#if defined(_WIN32) + EXPECT_EQ(f.substitutedTo, "Arial"); +#else + EXPECT_EQ(f.substitutedTo, "Liberation Sans"); +#endif + } + + EXPECT_GT(f.ascent, 0.0); + EXPECT_LT(f.descent, 0.0); + EXPECT_GT(f.capHeight, 0.0); + } + } + } + + { + auto path = getCorpusPath("fonts", "vertical_text.pdf"); + if (std::filesystem::exists(path)) { + auto docRes = PdfDocument::loadFromFile(path.string()); + ASSERT_TRUE(docRes.has_value()); + auto doc = *docRes; + + auto fontsRes = doc->getFonts(); + ASSERT_TRUE(fontsRes.has_value()); + + bool foundVertical = false; + for (const auto& f : *fontsRes) { + if (f.isVertical) { + foundVertical = true; + EXPECT_TRUE(f.encoding.find("-V") != std::string::npos || f.cmapName.find("-V") != std::string::npos); + } + } + EXPECT_TRUE(foundVertical); + } + } + + { + auto path1 = getCorpusPath("fonts", "vertical_identity_v.pdf"); + if (std::filesystem::exists(path1)) { + auto docRes = PdfDocument::loadFromFile(path1.string()); + ASSERT_TRUE(docRes.has_value()); + auto fontsRes = (*docRes)->getFonts(); + ASSERT_TRUE(fontsRes.has_value()); + + bool foundVertical = false; + for (const auto& f : *fontsRes) { + if (f.isVertical) foundVertical = true; + } + EXPECT_TRUE(foundVertical) << "Failed to detect vertical font in vertical_identity_v.pdf"; + } + } + + { + auto path = getCorpusPath("fonts", "utf-8.pdf"); + if (!std::filesystem::exists(path)) { + path = getCorpusPath("basic", "hello_world.pdf"); + } + if (std::filesystem::exists(path)) { + auto docRes = PdfDocument::loadFromFile(path.string()); + ASSERT_TRUE(docRes.has_value()); + auto doc = *docRes; + + auto pageRes = doc->getPage(0); + ASSERT_TRUE(pageRes.has_value()); + auto page = *pageRes; + + auto boundsRes = page->extractTextWithBounds(); + ASSERT_TRUE(boundsRes.has_value()); + + const auto& glyphs = *boundsRes; + ASSERT_FALSE(glyphs.empty()); + + std::vector uniqueSizes; + for (const auto& glyph : glyphs) { + if (glyph.text != " " && glyph.text != "\r" && glyph.text != "\n" && glyph.text != "\t") { + EXPECT_GT(glyph.w, 0.0); + EXPECT_GT(glyph.h, 0.0); + } + EXPECT_GT(glyph.fontSize, 0.0); + EXPECT_LT(glyph.fontSize, 100.0); + + if (std::find(uniqueSizes.begin(), uniqueSizes.end(), glyph.fontSize) == uniqueSizes.end()) { + uniqueSizes.push_back(glyph.fontSize); + } + } + + if (path.filename().string() == "utf-8.pdf") { + EXPECT_GE(uniqueSizes.size(), 2u); + } + } + } +} + + +TEST(FontDiagnosticsTest, RealPDFiumEmbeddingAndTypeAccuracy) { + SKIP_IF_NO_PDFIUM(); + + auto path = getCorpusPath("fonts", "text_font.pdf"); + if (!std::filesystem::exists(path)) { + GTEST_SKIP() << "text_font.pdf not found in corpus."; + } + + auto docRes = PdfDocument::loadFromFile(path.string()); + ASSERT_TRUE(docRes.has_value()) << "Failed to open text_font.pdf"; + auto doc = *docRes; + + auto pageRes = doc->getPage(0); + ASSERT_TRUE(pageRes.has_value()); + auto fontsRes = (*pageRes)->getFonts(); + ASSERT_TRUE(fontsRes.has_value()); + const auto& fonts = *fontsRes; + ASSERT_FALSE(fonts.empty()) << "text_font.pdf must expose at least one font"; + + for (const auto& f : fonts) { + EXPECT_FALSE(f.fontName.empty()); + EXPECT_FALSE(f.type.empty()); + + static const std::vector kValidTypes = { + "Type1", "TrueType", "CIDFontType0", "CIDFontType2" + }; + bool typeValid = std::find(kValidTypes.begin(), kValidTypes.end(), f.type) + != kValidTypes.end(); + EXPECT_TRUE(typeValid) << "Unexpected type '" << f.type << "' for font '" << f.fontName << "'"; + + if (f.isSubset) { + EXPECT_TRUE(f.isEmbedded) + << "Subset font '" << f.fontName + << "' must be embedded (FPDFFont_GetIsEmbedded should return 1)"; + EXPECT_EQ(f.sourceType, "Embedded") + << "sourceType must be 'Embedded' when isEmbedded=true"; + EXPECT_TRUE(f.substitutedFrom.empty()); + EXPECT_TRUE(f.substitutedTo.empty()); + } + + if (f.isEmbedded) { + EXPECT_EQ(f.sourceType, "Embedded"); + } + } +} + +TEST(FontDiagnosticsTest, RealPDFiumMetricsAccuracy) { + SKIP_IF_NO_PDFIUM(); + + auto path = getCorpusPath("fonts", "text_font.pdf"); + if (!std::filesystem::exists(path)) { + GTEST_SKIP() << "text_font.pdf not found in corpus."; + } + + auto docRes = PdfDocument::loadFromFile(path.string()); + ASSERT_TRUE(docRes.has_value()); + auto fontsRes = (*docRes)->getFonts(); + ASSERT_TRUE(fontsRes.has_value()); + + for (const auto& f : *fontsRes) { + EXPECT_GT(f.ascent, 0.0) + << "ascent must be positive for font '" << f.fontName << "'"; + EXPECT_LT(f.descent, 0.0) + << "descent must be negative for font '" << f.fontName << "'"; + EXPECT_GT(f.capHeight, 0.0) + << "capHeight must be positive for font '" << f.fontName << "'"; + + EXPECT_LE(f.capHeight, f.ascent + 1.0) + << "capHeight should not exceed ascent for font '" << f.fontName << "'"; + + EXPECT_LT(f.ascent, 1500.0) << "Implausibly large ascent for '" << f.fontName << "'"; + EXPECT_GT(f.descent, -1500.0) << "Implausibly deep descent for '" << f.fontName << "'"; + } +} + +TEST(FontDiagnosticsTest, ToUnicodePresenceAccuracy) { + SKIP_IF_NO_PDFIUM(); + + { + auto path = getCorpusPath("fonts", "with_tounicode.pdf"); + if (std::filesystem::exists(path)) { + auto docRes = PdfDocument::loadFromFile(path.string()); + if (docRes.has_value()) { + auto pageRes = (*docRes)->getPage(0); + if (pageRes.has_value()) { + auto fontsRes = (*pageRes)->getFonts(); + if (fontsRes.has_value() && !fontsRes->empty()) { + bool anyTrue = false; + for (const auto& f : *fontsRes) { + if (f.hasToUnicode) { anyTrue = true; break; } + } + EXPECT_TRUE(anyTrue) + << "At least one font in with_tounicode.pdf must have hasToUnicode=true"; + } + } + } + } + } + + { + auto path = getCorpusPath("fonts", "no_tounicode.pdf"); + if (std::filesystem::exists(path)) { + auto docRes = PdfDocument::loadFromFile(path.string()); + if (docRes.has_value()) { + auto pageRes = (*docRes)->getPage(0); + if (pageRes.has_value()) { + auto fontsRes = (*pageRes)->getFonts(); + if (fontsRes.has_value() && !fontsRes->empty()) { + for (const auto& f : *fontsRes) { + EXPECT_FALSE(f.hasToUnicode) + << "Font '" << f.fontName + << "' in no_tounicode.pdf must have hasToUnicode=false"; + } + } + } + } + } + } + + { + auto path = getCorpusPath("fonts", "latin_extended.pdf"); + if (std::filesystem::exists(path)) { + auto docRes = PdfDocument::loadFromFile(path.string()); + if (docRes.has_value()) { + auto pageRes = (*docRes)->getPage(0); + if (pageRes.has_value()) { + auto fontsRes = (*pageRes)->getFonts(); + if (fontsRes.has_value() && !fontsRes->empty()) { + bool anyTrue = false; + for (const auto& f : *fontsRes) { + if (f.hasToUnicode) { anyTrue = true; break; } + } + EXPECT_TRUE(anyTrue) + << "At least one font in latin_extended.pdf must decode to Unicode"; + } + } + } + } + } +} +TEST(GlyphCacheTest, ConcurrencyBench) { + using namespace pdfengine::fonts; + + FontFace face; + bool loaded = face.loadFromFile("C:\\Windows\\Fonts\\arial.ttf"); + if (!loaded) { + GTEST_SKIP() << "Skipping benchmark: Arial font not found."; + } + + GlyphCache cache(1000); + + auto run_benchmark = [&](int num_threads, int ops_per_thread) { + std::atomic start_flag{0}; + std::vector threads; + + for (int i = 0; i < num_threads; ++i) { + threads.emplace_back([&, i]() { + while (start_flag.load() == 0) { std::this_thread::yield(); } + + for (int op = 0; op < ops_per_thread; ++op) { + unsigned int glyphIndex = (op + i) % 2000; + unsigned int fontSize = 12 + (op % 5); + + auto hit = cache.get(face, glyphIndex, fontSize); + if (!hit) { + GlyphBitmap bmp; + bmp.width = 10; bmp.height = 10; + cache.insert(face, glyphIndex, fontSize, bmp); + } + } + }); + } + + auto start_time = std::chrono::high_resolution_clock::now(); + start_flag.store(1); + for (auto& t : threads) { t.join(); } + auto end_time = std::chrono::high_resolution_clock::now(); + + std::chrono::duration diff = end_time - start_time; + return diff.count(); + }; + + run_benchmark(2, 1000); + cache.clear(); + + double time_10 = run_benchmark(10, 10000); + std::cout << "[ BENCHMARK ] 10 Threads Time: " << time_10 << " seconds (" << (100000.0 / time_10) << " ops/sec)\n"; + + cache.clear(); + + double time_50 = run_benchmark(50, 10000); + std::cout << "[ BENCHMARK ] 50 Threads Time: " << time_50 << " seconds (" << (500000.0 / time_50) << " ops/sec)\n"; + + EXPECT_LE(cache.size(), 1000 + 16); +} + +TEST(FontDiagnosticsTest, EmbeddedFontResolutionAndReloadingVerification) { + SKIP_IF_NO_PDFIUM(); + + std::vector testFiles = { + "text_font.pdf", + "embedded_truetype.pdf", + "embedded_cid_font.pdf", + "subset_font.pdf", + "latin_extended.pdf" + }; + + bool foundAnyEmbedded = false; + for (const auto& fileName : testFiles) { + auto path = getCorpusPath("fonts", fileName); + if (!std::filesystem::exists(path)) { + continue; + } + std::cout << "\n========================================\n"; + std::cout << "Testing PDF: " << fileName << "\n"; + std::cout << "========================================\n"; + + auto docRes = PdfDocument::loadFromFile(path.string()); + if (!docRes.has_value()) { + std::cout << "Failed to load document: " << fileName << std::endl; + continue; + } + auto doc = *docRes; + + auto fontsRes = doc->getFonts(); + if (!fontsRes.has_value()) { + std::cout << "Failed to get fonts for: " << fileName << std::endl; + continue; + } + const auto& fonts = *fontsRes; + + for (const auto& fontInfo : fonts) { + std::cout << "Font: " << fontInfo.fontName + << ", type: " << fontInfo.type + << ", isEmbedded: " << (fontInfo.isEmbedded ? "yes" : "no") + << ", flags: " << fontInfo.flags << std::endl; + if (fontInfo.isEmbedded) { + foundAnyEmbedded = true; + + auto resolvedFontRes = doc->getResolvedFont(fontInfo); + if (!resolvedFontRes.has_value()) { + std::cout << " Failed to resolve font: " << resolvedFontRes.error() << std::endl; + continue; + } + auto resolvedFont = *resolvedFontRes; + std::cout << " Resolved font successfully." << std::endl; + + auto face = static_cast(resolvedFont->getFontFace().getFace()); + if (face) { + std::cout << " FreeType Face Num Glyphs: " << face->num_glyphs << std::endl; + std::cout << " FreeType Charmaps Count: " << face->num_charmaps << std::endl; + for (int i = 0; i < face->num_charmaps; ++i) { + FT_CharMap cm = face->charmaps[i]; + std::cout << " Charmap " << i << ": platform_id=" << cm->platform_id + << ", encoding_id=" << cm->encoding_id << std::endl; + + FT_Error err = FT_Set_Charmap(face, cm); + if (err) { + std::cout << " FT_Set_Charmap failed: " << err << std::endl; + continue; + } + + FT_UInt gindex; + FT_ULong charcode = FT_Get_First_Char(face, &gindex); + std::cout << " Mapped characters under charmap " << i << ": "; + int count = 0; + while (gindex != 0 && count < 10) { + std::cout << charcode << "->" << gindex << " "; + charcode = FT_Get_Next_Char(face, charcode, &gindex); + count++; + } + std::cout << std::endl; + } + + if (face->num_charmaps > 0) { + FT_Set_Charmap(face, face->charmaps[0]); + } + + std::cout << " Glyph names: "; + for (int i = 0; i < face->num_glyphs; ++i) { + char nameBuf[64] = {0}; + if (FT_Get_Glyph_Name(face, i, nameBuf, sizeof(nameBuf)) == 0) { + std::cout << i << ":" << nameBuf << " "; + } else { + std::cout << i << ":[unknown] "; + } + } + std::cout << std::endl; + } else { + std::cout << " No FreeType Face available." << std::endl; + } + + EXPECT_TRUE(resolvedFont->isEmbedded()); + + std::vector testChars = {32, 48, 65, 97}; + for (uint32_t cp : testChars) { + bool hasG = resolvedFont->hasGlyph(cp); + double w = resolvedFont->getAdvanceWidth(cp, 12.0); + std::cout << " char(" << cp << "): hasGlyph=" << (hasG ? "yes" : "no") + << ", advanceWidth=" << w << std::endl; + } + + auto metrics = resolvedFont->getMetrics(12.0); + std::cout << " Metrics: ascent=" << metrics.ascent << ", descent=" << metrics.descent << ", capHeight=" << metrics.capHeight << std::endl; + EXPECT_NE(metrics.ascent, 0.0); + EXPECT_NE(metrics.descent, 0.0); + EXPECT_NE(metrics.capHeight, 0.0); + + if (fileName == "text_font.pdf") { + EXPECT_TRUE(resolvedFont->hasGlyph(1)); + double w = resolvedFont->getAdvanceWidth(1, 12.0); + EXPECT_GT(w, 0.0); + std::cout << " [VERIFIED] text_font.pdf char(1): hasGlyph=yes, advanceWidth=" << w << std::endl; + } + + if (face->num_glyphs > 1) { + bool foundNonZeroWidth = false; + for (int gid = 1; gid < face->num_glyphs; ++gid) { + FT_Error err = FT_Load_Glyph(face, gid, FT_LOAD_DEFAULT); + if (err == 0) { + double directWidth = static_cast(face->glyph->advance.x) / 64.0; + if (directWidth > 0.0) { + foundNonZeroWidth = true; + std::cout << " [VERIFIED] Direct glyph " << gid << " load: advanceWidth=" << directWidth << std::endl; + break; + } + } + } + EXPECT_TRUE(foundNonZeroWidth) << "Expected to find at least one glyph with a non-zero advance width"; + } + } + } + } + EXPECT_TRUE(foundAnyEmbedded) << "Expected to find at least one embedded font in test files"; +} +} diff --git a/engine/tests/font_encoding_test.cpp b/engine/tests/font_encoding_test.cpp new file mode 100644 index 0000000..4248186 --- /dev/null +++ b/engine/tests/font_encoding_test.cpp @@ -0,0 +1,351 @@ +#include "fonts_test_helpers.hpp" + +namespace pdfengine::fonts { + +TEST(EncodingTest, PredefinedEncodingTest) { + using namespace pdfengine::fonts::pdf_fonts; + + PredefinedEncoding winAnsi(SimpleEncodingType::WinAnsi); + EXPECT_EQ(winAnsi.getType(), SimpleEncodingType::WinAnsi); + EXPECT_EQ(winAnsi.decode(65), 65); + EXPECT_EQ(winAnsi.decode(128), 0x20AC); + EXPECT_EQ(winAnsi.decode(169), 169); + EXPECT_EQ(winAnsi.decode(300), 0); + + PredefinedEncoding macRoman(SimpleEncodingType::MacRoman); + EXPECT_EQ(macRoman.getType(), SimpleEncodingType::MacRoman); + EXPECT_EQ(macRoman.decode(65), 65); + EXPECT_EQ(macRoman.decode(128), 0x00C4); + EXPECT_EQ(macRoman.decode(300), 0); + + PredefinedEncoding identity(SimpleEncodingType::Identity); + EXPECT_EQ(identity.getType(), SimpleEncodingType::Identity); + EXPECT_EQ(identity.decode(65), 65); + EXPECT_EQ(identity.decode(128), 128); + EXPECT_EQ(identity.decode(1000), 1000); +} + +TEST(EncodingTest, CustomEncodingWithDifferences) { + using namespace pdfengine::fonts::pdf_fonts; + + auto baseEncoding = std::make_unique(SimpleEncodingType::WinAnsi); + CustomEncoding custom(std::move(baseEncoding)); + + EXPECT_EQ(custom.decode(65), 65); + + custom.addDifference(120, "quotesingle"); + EXPECT_EQ(custom.decode(120), 0x0027); + + custom.addDifference(121, "uni0041"); + EXPECT_EQ(custom.decode(121), 0x0041); + + custom.addDifference(122, "u0042"); + EXPECT_EQ(custom.decode(122), 0x0042); + + custom.addDifference(123, "nonexistentglyphname123"); + EXPECT_EQ(custom.decode(123), 123); +} + +TEST(EncodingTest, ToUnicodeCMapbfchar) { + using namespace pdfengine::fonts::pdf_fonts; + + ToUnicodeCMap cmap; + + std::string cmapStream = + "/CIDInit /ProcSet findresource begin\n" + "12 dict begin\n" + "begincmap\n" + "/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def\n" + "/CMapName /Custom-ToUnicode def\n" + "1 begincodespacerange\n" + "<0000> \n" + "endcodespacerange\n" + "2 beginbfchar\n" + "<0001> <0041>\n" + "<0002> <0042>\n" + "endbfchar\n" + "endcmap\n" + "CMapName currentdict /CMap defineresource pop\n" + "end\n" + "end\n"; + + ASSERT_TRUE(cmap.parseCMapStream(cmapStream)); + EXPECT_EQ(cmap.decode(1), 0x0041); + EXPECT_EQ(cmap.decode(2), 0x0042); + EXPECT_EQ(cmap.decode(3), 0); +} + +TEST(EncodingTest, ToUnicodeCMapbfrange) { + using namespace pdfengine::fonts::pdf_fonts; + + ToUnicodeCMap cmap; + + std::string cmapStream = + "begincmap\n" + "2 beginbfrange\n" + "<0001> <0005> <0041>\n" + "<0010> <0012> [<0061> <0062> <0063>]\n" + "endbfrange\n" + "endcmap\n"; + + ASSERT_TRUE(cmap.parseCMapStream(cmapStream)); + + EXPECT_EQ(cmap.decode(1), 0x0041); + EXPECT_EQ(cmap.decode(3), 0x0043); + EXPECT_EQ(cmap.decode(5), 0x0045); + + EXPECT_EQ(cmap.decode(0x10), 0x0061); + EXPECT_EQ(cmap.decode(0x11), 0x0062); + EXPECT_EQ(cmap.decode(0x12), 0x0063); +} + +TEST(EncodingTest, ToUnicodeMalformedCMap) { + using namespace pdfengine::fonts::pdf_fonts; + + ToUnicodeCMap cmap; + + std::string garbageStream = "This is a garbage string with no valid CMap elements"; + EXPECT_FALSE(cmap.parseCMapStream(garbageStream)); + + std::string partialStream = + "begincmap\n" + "beginbfchar\n" + "<0001> <0041>\n" + "<0002> /invalid\n" + "<0003> <0043>\n" + "endbfchar\n" + "endcmap\n"; + + EXPECT_TRUE(cmap.parseCMapStream(partialStream)); + EXPECT_EQ(cmap.decode(1), 0x0041); + EXPECT_EQ(cmap.decode(2), 0); + EXPECT_EQ(cmap.decode(3), 0x0043); +} + +TEST(EncodingTest, FontLoaderWithEncoding) { + std::string fontPath = getSystemFontPath(); + if (fontPath.empty()) { + GTEST_SKIP() << "No system font found to run PDF font loader with encoding test."; + } + + std::ifstream file(fontPath, std::ios::binary | std::ios::ate); + ASSERT_TRUE(file.is_open()); + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + std::vector buffer(size); + ASSERT_TRUE(file.read(reinterpret_cast(buffer.data()), size)); + + auto encoding = std::make_unique( + pdfengine::fonts::pdf_fonts::SimpleEncodingType::WinAnsi + ); + + auto pdfFont = pdfengine::fonts::pdf_fonts::FontLoader::loadTrueTypeFromMemory( + "Arial-With-Encoding", buffer, nullptr, std::move(encoding) + ); + + ASSERT_NE(pdfFont, nullptr); + EXPECT_EQ(pdfFont->getBaseFont(), "Arial-With-Encoding"); + + const auto* retrievedEncoding = pdfFont->getEncoding(); + ASSERT_NE(retrievedEncoding, nullptr); + + EXPECT_EQ(retrievedEncoding->decode(128), 0x20AC); +} +TEST(FontSubsetTest, SubsetTagParsingAndStripping) { + using namespace pdfengine::fonts::pdf_fonts; + + std::string subsetName = "KTJHQO+Arial"; + + EXPECT_TRUE(FontSubset::hasSubsetPrefix(subsetName)); + EXPECT_EQ(FontSubset::getSubsetPrefix(subsetName), "KTJHQO"); + EXPECT_EQ(FontSubset::stripSubsetPrefix(subsetName), "Arial"); + + std::string normalName = "Arial"; + EXPECT_FALSE(FontSubset::hasSubsetPrefix(normalName)); + EXPECT_EQ(FontSubset::getSubsetPrefix(normalName), ""); + EXPECT_EQ(FontSubset::stripSubsetPrefix(normalName), "Arial"); +} + +TEST(FontSubsetTest, PrefixFormatValidation) { + using namespace pdfengine::fonts::pdf_fonts; + + EXPECT_TRUE(FontSubset::hasSubsetPrefix("ABCDEF+Helvetica")); + + EXPECT_FALSE(FontSubset::hasSubsetPrefix("abcDEF+Helvetica")); + EXPECT_FALSE(FontSubset::hasSubsetPrefix("ABCdef+Helvetica")); + + EXPECT_FALSE(FontSubset::hasSubsetPrefix("ABC123+Helvetica")); + EXPECT_FALSE(FontSubset::hasSubsetPrefix("ABCDE_+Helvetica")); + + EXPECT_FALSE(FontSubset::hasSubsetPrefix("ABCDE+Helvetica")); + EXPECT_FALSE(FontSubset::hasSubsetPrefix("ABCDEFG+Helvetica")); +} + +TEST(FontSubsetTest, GIDRemappingTranslations) { + using namespace pdfengine::fonts::pdf_fonts; + + FontSubset subset("KTJHQO+Arial"); + EXPECT_TRUE(subset.isSubset()); + EXPECT_EQ(subset.getFullFontName(), "KTJHQO+Arial"); + EXPECT_EQ(subset.getBaseFontName(), "Arial"); + EXPECT_EQ(subset.getPrefix(), "KTJHQO"); + + EXPECT_EQ(subset.mapSubsetToOriginal(5), 5); + EXPECT_FALSE(subset.hasGlyphMapping(5)); + + subset.addGlyphMapping(1, 41); + subset.addGlyphMapping(2, 42); + subset.addGlyphMapping(3, 43); + + EXPECT_EQ(subset.getMappingCount(), 3u); + EXPECT_TRUE(subset.hasGlyphMapping(1)); + EXPECT_TRUE(subset.hasGlyphMapping(2)); + EXPECT_EQ(subset.mapSubsetToOriginal(1), 41); + EXPECT_EQ(subset.mapSubsetToOriginal(2), 42); + EXPECT_EQ(subset.mapSubsetToOriginal(3), 43); + EXPECT_EQ(subset.mapSubsetToOriginal(4), 4); +} + +TEST(FontSubsetTest, CoreFontSubsettingIntegration) { + using namespace pdfengine::fonts::pdf_fonts; + + TrueTypeFont fontTT("KTJHQO+Arial", false); + const auto* ttSubset = fontTT.getSubsetInfo(); + ASSERT_NE(ttSubset, nullptr); + EXPECT_TRUE(ttSubset->isSubset()); + EXPECT_EQ(ttSubset->getBaseFontName(), "Arial"); + EXPECT_EQ(ttSubset->getPrefix(), "KTJHQO"); + + Type1Font fontT1("SUBSET+Courier", false); + const auto* t1Subset = fontT1.getSubsetInfo(); + ASSERT_NE(t1Subset, nullptr); + EXPECT_TRUE(t1Subset->isSubset()); + EXPECT_EQ(t1Subset->getBaseFontName(), "Courier"); + EXPECT_EQ(t1Subset->getPrefix(), "SUBSET"); + + CIDFont fontCID("CJKTAG+SimSun", FontType::CIDFontType2, false); + const auto* cidSubset = fontCID.getSubsetInfo(); + ASSERT_NE(cidSubset, nullptr); + EXPECT_TRUE(cidSubset->isSubset()); + EXPECT_EQ(cidSubset->getBaseFontName(), "SimSun"); + EXPECT_EQ(cidSubset->getPrefix(), "CJKTAG"); +} + +TEST(TextExtractionLayerTest, SimpleCharacterDecoding) { + using namespace pdfengine::fonts::pdf_fonts; + + auto font = FontLoader::loadType1SystemFallback("Helvetica"); + ASSERT_NE(font, nullptr); + + EXPECT_EQ(font->decodeToUnicode(65), 65u); + EXPECT_EQ(font->decodeToUnicode(97), 97u); + EXPECT_EQ(font->decodeToUnicode(48), 48u); +} + +TEST(TextExtractionLayerTest, EncodingAndToUnicodeDecoding) { + using namespace pdfengine::fonts::pdf_fonts; + + auto baseEncoding = std::make_unique(SimpleEncodingType::WinAnsi); + auto customEnc = std::make_unique(std::move(baseEncoding)); + customEnc->addDifference(128, "euro"); + + auto font = FontLoader::loadType1SystemFallback("Helvetica", nullptr, std::move(customEnc)); + ASSERT_NE(font, nullptr); + + EXPECT_EQ(font->decodeToUnicode(128), 0x20ACu); + + auto cmap = std::make_unique(); + cmap->addMapping(1, 0x0041); + cmap->addMapping(2, 0x0042); + cmap->addMapping(3, 0x20AC); + + auto fontCMap = FontLoader::loadType1SystemFallback("Helvetica", nullptr, std::move(cmap)); + ASSERT_NE(fontCMap, nullptr); + EXPECT_EQ(fontCMap->decodeToUnicode(1), 0x0041u); + EXPECT_EQ(fontCMap->decodeToUnicode(2), 0x0042u); + EXPECT_EQ(fontCMap->decodeToUnicode(3), 0x20ACu); +} + +TEST(TextExtractionLayerTest, SubsetFontTextExtraction) { + using namespace pdfengine::fonts::pdf_fonts; + + auto font = FontLoader::loadType1SystemFallback("KTJHQO+Helvetica"); + ASSERT_NE(font, nullptr); + + const auto* subsetConst = font->getSubsetInfo(); + ASSERT_NE(subsetConst, nullptr); + auto* subset = const_cast(subsetConst); + + FT_Face face = font->getFontFace().getFace(); + ASSERT_NE(face, nullptr); + FT_UInt originalGid = FT_Get_Char_Index(face, 'A'); + ASSERT_GT(originalGid, 0u); + + subset->addGlyphMapping(5, originalGid); + + EXPECT_EQ(font->decodeToUnicode(5), 65u); +} + +TEST(TextExtractionLayerTest, CIDFontTextExtraction) { + using namespace pdfengine::fonts::pdf_fonts; + + auto font = FontLoader::loadCIDFontSystemFallback("SimSun", FontType::CIDFontType2); + ASSERT_NE(font, nullptr); + + CIDFont* cidFont = dynamic_cast(font.get()); + ASSERT_NE(cidFont, nullptr); + + FT_Face face = cidFont->getFontFace().getFace(); + ASSERT_NE(face, nullptr); + FT_UInt gid65 = 65; + FT_ULong expectedChar65 = 0; + + FT_UInt gindex; + FT_ULong charcode = FT_Get_First_Char(face, &gindex); + while (gindex != 0) { + if (gindex == gid65) { + expectedChar65 = charcode; + break; + } + charcode = FT_Get_Next_Char(face, charcode, &gindex); + } + + if (expectedChar65 != 0) { + EXPECT_EQ(cidFont->decodeToUnicode(65), expectedChar65); + } else { + EXPECT_EQ(cidFont->decodeToUnicode(65), 65u); + } + + FT_UInt gidA = FT_Get_Char_Index(face, 'A'); + if (gidA > 0) { + std::unordered_map cidToGid = { + {500, gidA} + }; + cidFont->setCIDToGIDMap(cidToGid); + + EXPECT_EQ(cidFont->decodeToUnicode(500), 65u); + } +} + +TEST(TextExtractionLayerTest, StringUtf8Conversion) { + using namespace pdfengine::fonts::pdf_fonts; + + auto font = FontLoader::loadType1SystemFallback("Helvetica"); + ASSERT_NE(font, nullptr); + + std::vector codes = {65, 66, 67}; + EXPECT_EQ(font->decodeStringToUnicode(codes), "ABC"); + + auto cmap = std::make_unique(); + cmap->addMapping(10, 0x65E5); + cmap->addMapping(11, 0x672C); + cmap->addMapping(12, 0x8A9E); + + auto fontCMap = FontLoader::loadType1SystemFallback("Helvetica", nullptr, std::move(cmap)); + ASSERT_NE(fontCMap, nullptr); + + std::vector cjkCodes = {10, 11, 12}; + std::string decodedCjk = fontCMap->decodeStringToUnicode(cjkCodes); + EXPECT_EQ(decodedCjk, "日本語"); +} +} diff --git a/engine/tests/font_loader_descriptor_test.cpp b/engine/tests/font_loader_descriptor_test.cpp new file mode 100644 index 0000000..ad59b07 --- /dev/null +++ b/engine/tests/font_loader_descriptor_test.cpp @@ -0,0 +1,173 @@ +#include "fonts_test_helpers.hpp" + +namespace pdfengine::fonts { + +TEST(FontLoaderTest, FontFaceLoadFromMemorySuccess) { + std::string fontPath = getSystemFontPath(); + if (fontPath.empty()) { + GTEST_SKIP() << "No system font found to run load from memory success test."; + } + + std::ifstream file(fontPath, std::ios::binary | std::ios::ate); + ASSERT_TRUE(file.is_open()); + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + + std::vector buffer(size); + ASSERT_TRUE(file.read(reinterpret_cast(buffer.data()), size)); + + FontFace face; + ASSERT_TRUE(face.loadFromMemory(buffer)); + ASSERT_NE(face.getFace(), nullptr); + + unsigned int glyphIndex = FT_Get_Char_Index(face.getFace(), 'M'); + ASSERT_GT(glyphIndex, 0u); + + auto glyph = face.renderGlyph(glyphIndex, 16); + ASSERT_TRUE(glyph.has_value()); + EXPECT_GT(glyph->width, 0); + EXPECT_GT(glyph->height, 0); + EXPECT_GT(glyph->advance, 0.0); +} + +TEST(FontLoaderTest, FontFaceLoadFromMemoryInvalid) { + FontFace face; + std::vector emptyData; + EXPECT_FALSE(face.loadFromMemory(emptyData)); + EXPECT_EQ(face.getFace(), nullptr); + + std::vector corruptData = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66}; + EXPECT_FALSE(face.loadFromMemory(corruptData)); + EXPECT_EQ(face.getFace(), nullptr); +} + +TEST(FontLoaderTest, FontLoaderTrueTypeSuccess) { + std::string fontPath = getSystemFontPath(); + if (fontPath.empty()) { + GTEST_SKIP() << "No system font found to run PDF font loader test."; + } + + std::ifstream file(fontPath, std::ios::binary | std::ios::ate); + ASSERT_TRUE(file.is_open()); + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + std::vector buffer(size); + ASSERT_TRUE(file.read(reinterpret_cast(buffer.data()), size)); + + auto pdfFont = pdfengine::fonts::pdf_fonts::FontLoader::loadTrueTypeFromMemory("Arial", buffer); + ASSERT_NE(pdfFont, nullptr); + EXPECT_EQ(pdfFont->getBaseFont(), "Arial"); + EXPECT_EQ(pdfFont->getType(), pdfengine::fonts::pdf_fonts::FontType::TrueType); + EXPECT_TRUE(pdfFont->isEmbedded()); + + HbShaper shaper; + auto glyphs = shaper.shapeRun("Test Memory Load", pdfFont->getFontFace(), 16); + EXPECT_FALSE(glyphs.empty()); +} + +TEST(FontDescriptorTest, DescriptorDefaultValues) { + pdfengine::fonts::pdf_fonts::FontDescriptor desc; + EXPECT_EQ(desc.getFontName(), ""); + EXPECT_EQ(desc.getFlags(), 0); + EXPECT_EQ(desc.getItalicAngle(), 0.0); + EXPECT_EQ(desc.getAscent(), 0.0); + EXPECT_EQ(desc.getDescent(), 0.0); + EXPECT_EQ(desc.getCapHeight(), 0.0); + EXPECT_EQ(desc.getStemV(), 0.0); + + pdfengine::fonts::pdf_fonts::FontBBox bbox = desc.getFontBBox(); + EXPECT_EQ(bbox.llx, 0); + EXPECT_EQ(bbox.lly, 0); + EXPECT_EQ(bbox.urx, 0); + EXPECT_EQ(bbox.ury, 0); + + EXPECT_FALSE(desc.isFixedPitch()); + EXPECT_FALSE(desc.isSerif()); + EXPECT_FALSE(desc.isSymbolic()); + EXPECT_FALSE(desc.isItalic()); +} + +TEST(FontDescriptorTest, DescriptorParsingSuccess) { + std::string dict = + "<< /Type /FontDescriptor\n" + " /FontName /ArialMT\n" + " /Flags 32\n" + " /FontBBox [-166 -225 1000 931]\n" + " /ItalicAngle 0\n" + " /Ascent 905\n" + " /Descent -211\n" + " /CapHeight 728\n" + " /StemV 94\n" + ">>"; + + pdfengine::fonts::pdf_fonts::FontDescriptor desc; + ASSERT_TRUE(desc.parseFromDictionaryString(dict)); + + EXPECT_EQ(desc.getFontName(), "ArialMT"); + EXPECT_EQ(desc.getFlags(), 32); + + pdfengine::fonts::pdf_fonts::FontBBox bbox = desc.getFontBBox(); + EXPECT_EQ(bbox.llx, -166); + EXPECT_EQ(bbox.lly, -225); + EXPECT_EQ(bbox.urx, 1000); + EXPECT_EQ(bbox.ury, 931); + + EXPECT_DOUBLE_EQ(desc.getItalicAngle(), 0.0); + EXPECT_DOUBLE_EQ(desc.getAscent(), 905.0); + EXPECT_DOUBLE_EQ(desc.getDescent(), -211.0); + EXPECT_DOUBLE_EQ(desc.getCapHeight(), 728.0); + EXPECT_DOUBLE_EQ(desc.getStemV(), 94.0); + + EXPECT_FALSE(desc.isFixedPitch()); + EXPECT_TRUE(desc.isNonsymbolic()); + EXPECT_FALSE(desc.isItalic()); +} + +TEST(FontDescriptorTest, DescriptorParsingMalformed) { + pdfengine::fonts::pdf_fonts::FontDescriptor desc; + + EXPECT_FALSE(desc.parseFromDictionaryString("/Flags 32 >>")); + + EXPECT_FALSE(desc.parseFromDictionaryString("<< /Flags 32")); + + EXPECT_FALSE(desc.parseFromDictionaryString("<< /FontBBox [-166 -225] >>")); + + EXPECT_FALSE(desc.parseFromDictionaryString("<< /Ascent abc >>")); + + EXPECT_FALSE(desc.parseFromDictionaryString("<< /Ascent >>")); +} + +TEST(FontDescriptorTest, FontLoaderWithDescriptor) { + std::string fontPath = getSystemFontPath(); + if (fontPath.empty()) { + GTEST_SKIP() << "No system font found to run PDF font loader test."; + } + + std::ifstream file(fontPath, std::ios::binary | std::ios::ate); + ASSERT_TRUE(file.is_open()); + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + std::vector buffer(size); + ASSERT_TRUE(file.read(reinterpret_cast(buffer.data()), size)); + + auto descriptor = std::make_unique(); + descriptor->setFontName("Arial-BoldMT"); + descriptor->setFlags(96); + descriptor->setAscent(905.0); + descriptor->setDescent(-211.0); + + auto pdfFont = pdfengine::fonts::pdf_fonts::FontLoader::loadTrueTypeFromMemory("Arial-Bold", buffer, std::move(descriptor)); + ASSERT_NE(pdfFont, nullptr); + EXPECT_EQ(pdfFont->getBaseFont(), "Arial-Bold"); + EXPECT_TRUE(pdfFont->isEmbedded()); + + const auto* retrievedDesc = pdfFont->getDescriptor(); + ASSERT_NE(retrievedDesc, nullptr); + EXPECT_EQ(retrievedDesc->getFontName(), "Arial-BoldMT"); + EXPECT_EQ(retrievedDesc->getFlags(), 96); + EXPECT_TRUE(retrievedDesc->isItalic()); + EXPECT_TRUE(retrievedDesc->isNonsymbolic()); + EXPECT_DOUBLE_EQ(retrievedDesc->getAscent(), 905.0); + EXPECT_DOUBLE_EQ(retrievedDesc->getDescent(), -211.0); +} +} diff --git a/engine/tests/font_rendering_test.cpp b/engine/tests/font_rendering_test.cpp new file mode 100644 index 0000000..852f5fc --- /dev/null +++ b/engine/tests/font_rendering_test.cpp @@ -0,0 +1,454 @@ +#include "fonts_test_helpers.hpp" + +namespace pdfengine::fonts { + +TEST(FontTest, FontFaceInitialization) { + FontFace face; + EXPECT_EQ(face.getFace(), nullptr); +} + +TEST(FontTest, FontFaceLoadNonExistentFile) { + FontFace face; + EXPECT_FALSE(face.loadFromFile("this_file_does_not_exist_12345.ttf")); + EXPECT_EQ(face.getFace(), nullptr); +} + +TEST(FontTest, FontFaceMoveSemantics) { + std::string fontPath = getSystemFontPath(); + if (fontPath.empty()) { + GTEST_SKIP() << "No system font found to run move semantics test."; + } + + FontFace face1; + ASSERT_TRUE(face1.loadFromFile(fontPath)); + FT_Face rawFace = face1.getFace(); + ASSERT_NE(rawFace, nullptr); + + FontFace face2(std::move(face1)); + EXPECT_EQ(face1.getFace(), nullptr); + EXPECT_EQ(face2.getFace(), rawFace); + + FontFace face3; + face3 = std::move(face2); + EXPECT_EQ(face2.getFace(), nullptr); + EXPECT_EQ(face3.getFace(), rawFace); +} + +TEST(FontTest, HbShaperEmptyInput) { + std::string fontPath = getSystemFontPath(); + if (fontPath.empty()) { + GTEST_SKIP() << "No system font found to run empty input shaper test."; + } + + FontFace face; + ASSERT_TRUE(face.loadFromFile(fontPath)); + + HbShaper shaper; + auto glyphs = shaper.shapeRun("", face, 16); + EXPECT_TRUE(glyphs.empty()); +} + +TEST(FontTest, HbShaperNullFace) { + FontFace face; + HbShaper shaper; + auto glyphs = shaper.shapeRun("Hello", face, 16); + EXPECT_TRUE(glyphs.empty()); +} + +TEST(FontTest, HbShaperShapeTextSuccess) { + std::string fontPath = getSystemFontPath(); + if (fontPath.empty()) { + std::cout << "[ WARNING ] Skipping shape success test: no system font found." << std::endl; + GTEST_SKIP() << "No system font found to run text shaping test."; + } + + FontFace face; + ASSERT_TRUE(face.loadFromFile(fontPath)); + ASSERT_NE(face.getFace(), nullptr); + + HbShaper shaper; + std::string testText = "Hello World!"; + auto glyphs = shaper.shapeRun(testText, face, 16); + + EXPECT_FALSE(glyphs.empty()); + + for (const auto& g : glyphs) { + EXPECT_GT(g.advanceX, 0.0); + } +} + +TEST(FontTest, FontFaceRenderGlyphNullFace) { + FontFace face; + auto glyph = face.renderGlyph(0, 16); + EXPECT_FALSE(glyph.has_value()); +} + +TEST(FontTest, FontFaceRenderGlyphSuccess) { + std::string fontPath = getSystemFontPath(); + if (fontPath.empty()) { + GTEST_SKIP() << "No system font found to run render glyph success test."; + } + + FontFace face; + ASSERT_TRUE(face.loadFromFile(fontPath)); + ASSERT_NE(face.getFace(), nullptr); + + unsigned int glyphIndex = FT_Get_Char_Index(face.getFace(), 'A'); + ASSERT_GT(glyphIndex, 0u); + + auto glyphOpt = face.renderGlyph(glyphIndex, 24); + ASSERT_TRUE(glyphOpt.has_value()); + + const auto& glyph = *glyphOpt; + EXPECT_GT(glyph.width, 0); + EXPECT_GT(glyph.height, 0); + EXPECT_EQ(glyph.pixels.size(), static_cast(glyph.width * glyph.height)); + EXPECT_GT(glyph.advance, 0.0); +} + +TEST(FontTest, GlyphCacheBasicGetInsert) { + std::string fontPath = getSystemFontPath(); + if (fontPath.empty()) { + GTEST_SKIP() << "No system font found to run cache test."; + } + + FontFace face; + ASSERT_TRUE(face.loadFromFile(fontPath)); + + GlyphCache cache(10); + EXPECT_EQ(cache.capacity(), 10u); + EXPECT_EQ(cache.size(), 0u); + + auto miss = cache.get(face, 12, 16); + EXPECT_FALSE(miss.has_value()); + + GlyphBitmap bitmap; + bitmap.width = 10; + bitmap.height = 12; + bitmap.pixels = std::vector(120, 255); + bitmap.advance = 8.5; + + cache.insert(face, 12, 16, bitmap); + EXPECT_EQ(cache.size(), 1u); + + auto hit = cache.get(face, 12, 16); + ASSERT_TRUE(hit.has_value()); + EXPECT_EQ(hit->width, 10); + EXPECT_EQ(hit->height, 12); + EXPECT_EQ(hit->advance, 8.5); + EXPECT_EQ(hit->pixels.size(), 120u); +} + +TEST(FontTest, GlyphCacheEvictionPolicy) { + std::string fontPath = getSystemFontPath(); + if (fontPath.empty()) { + GTEST_SKIP() << "No system font found to run cache eviction test."; + } + + FontFace face; + ASSERT_TRUE(face.loadFromFile(fontPath)); + + GlyphCache cache(2); + + GlyphBitmap bmp1{ .width = 1 }; + GlyphBitmap bmp2{ .width = 2 }; + GlyphBitmap bmp3{ .width = 3 }; + + cache.insert(face, 1, 16, bmp1); + cache.insert(face, 2, 16, bmp2); + EXPECT_EQ(cache.size(), 2u); + + cache.insert(face, 3, 16, bmp3); + EXPECT_EQ(cache.size(), 2u); + + EXPECT_FALSE(cache.get(face, 1, 16).has_value()); + EXPECT_TRUE(cache.get(face, 2, 16).has_value()); + EXPECT_TRUE(cache.get(face, 3, 16).has_value()); +} + +TEST(FontTest, GlyphCacheLRUPolicy) { + std::string fontPath = getSystemFontPath(); + if (fontPath.empty()) { + GTEST_SKIP() << "No system font found to run cache LRU test."; + } + + FontFace face; + ASSERT_TRUE(face.loadFromFile(fontPath)); + + GlyphCache cache(2); + + GlyphBitmap bmp1{ .width = 1 }; + GlyphBitmap bmp2{ .width = 2 }; + GlyphBitmap bmp3{ .width = 3 }; + + cache.insert(face, 1, 16, bmp1); + cache.insert(face, 2, 16, bmp2); + + auto hit = cache.get(face, 1, 16); + ASSERT_TRUE(hit.has_value()); + + cache.insert(face, 3, 16, bmp3); + EXPECT_EQ(cache.size(), 2u); + + EXPECT_TRUE(cache.get(face, 1, 16).has_value()); + EXPECT_FALSE(cache.get(face, 2, 16).has_value()); + EXPECT_TRUE(cache.get(face, 3, 16).has_value()); +} + +TEST(FontTest, FontPipelineIntegration) { + std::string fontPath = getSystemFontPath(); + if (fontPath.empty()) { + GTEST_SKIP() << "No system font found to run pipeline integration test."; + } + + FontFace face; + ASSERT_TRUE(face.loadFromFile(fontPath)); + + HbShaper shaper; + GlyphCache cache(100); + + std::vector testTexts = {"Hello World", "office", "سلام"}; + unsigned int fontSize = 16; + + for (const auto& text : testTexts) { + auto shapedGlyphs = shaper.shapeRun(text, face, fontSize); + EXPECT_FALSE(shapedGlyphs.empty()); + + for (const auto& sg : shapedGlyphs) { + auto cachedBmp = cache.get(face, sg.glyphIndex, fontSize); + if (!cachedBmp.has_value()) { + auto renderedOpt = face.renderGlyph(sg.glyphIndex, fontSize); + ASSERT_TRUE(renderedOpt.has_value()); + cache.insert(face, sg.glyphIndex, fontSize, *renderedOpt); + EXPECT_EQ(renderedOpt->pixels.size(), static_cast(renderedOpt->width * renderedOpt->height)); + } + + auto hitBmp = cache.get(face, sg.glyphIndex, fontSize); + ASSERT_TRUE(hitBmp.has_value()); + EXPECT_EQ(hitBmp->pixels.size(), static_cast(hitBmp->width * hitBmp->height)); + EXPECT_GE(hitBmp->advance, 0.0); + } + } +} + +TEST(FontTest, UnicodeAndRtlShaping) { + std::string fontPath = getSystemFontPath(); + if (fontPath.empty()) { + GTEST_SKIP() << "No system font found to run Unicode and RTL shaping test."; + } + + FontFace face; + ASSERT_TRUE(face.loadFromFile(fontPath)); + + HbShaper shaper; + unsigned int fontSize = 16; + + { + std::string arabicText = "سلام"; + auto glyphs = shaper.shapeRun(arabicText, face, fontSize); + EXPECT_FALSE(glyphs.empty()); + for (const auto& g : glyphs) { + EXPECT_TRUE(g.advanceX != 0.0 || g.advanceY != 0.0 || g.offsetX != 0.0 || g.offsetY != 0.0 || g.glyphIndex != 999999u); + } + } + + { + std::string hindiText = "नमस्ते"; + auto glyphs = shaper.shapeRun(hindiText, face, fontSize); + EXPECT_FALSE(glyphs.empty()); + for (const auto& g : glyphs) { + EXPECT_TRUE(g.advanceX != 0.0 || g.advanceY != 0.0 || g.glyphIndex != 999999u); + } + } + + { + std::string ligatureText = "office"; + auto glyphs = shaper.shapeRun(ligatureText, face, fontSize); + EXPECT_FALSE(glyphs.empty()); + EXPECT_LE(glyphs.size(), ligatureText.length()); + for (const auto& g : glyphs) { + EXPECT_TRUE(g.advanceX != 0.0 || g.advanceY != 0.0 || g.glyphIndex != 999999u); + } + } +} + +TEST(FontTest, CachePerformanceTest) { + std::string fontPath = getSystemFontPath(); + if (fontPath.empty()) { + GTEST_SKIP() << "No system font found to run cache performance test."; + } + + FontFace face; + ASSERT_TRUE(face.loadFromFile(fontPath)); + + HbShaper shaper; + GlyphCache cache(100); + + std::string text = "Hello Hello Hello Hello"; + unsigned int fontSize = 16; + + { + auto glyphs = shaper.shapeRun(text, face, fontSize); + for (const auto& g : glyphs) { + auto cached = cache.get(face, g.glyphIndex, fontSize); + if (!cached.has_value()) { + auto rendered = face.renderGlyph(g.glyphIndex, fontSize); + if (rendered.has_value()) { + cache.insert(face, g.glyphIndex, fontSize, *rendered); + } + } + } + } + + cache.resetStats(); + + for (int i = 0; i < 1000; ++i) { + auto glyphs = shaper.shapeRun(text, face, fontSize); + for (const auto& g : glyphs) { + auto cached = cache.get(face, g.glyphIndex, fontSize); + if (!cached.has_value()) { + auto rendered = face.renderGlyph(g.glyphIndex, fontSize); + if (rendered.has_value()) { + cache.insert(face, g.glyphIndex, fontSize, *rendered); + } + } + } + } + + double hitRateVal = cache.hitRate(); + std::cout << "[ INFO ] Cache Hit Rate for repetitive text: " << (hitRateVal * 100.0) << "%" << std::endl; + EXPECT_GT(hitRateVal, 0.90); +} + +TEST(FontTest, EngineStressTest) { + std::string fontPath = getSystemFontPath(); + if (fontPath.empty()) { + GTEST_SKIP() << "No system font found to run engine stress test."; + } + + FontFace face; + ASSERT_TRUE(face.loadFromFile(fontPath)); + + HbShaper shaper; + GlyphCache cache(32); + + std::string base = "The quick brown fox jumps over the lazy dog. 1234567890!@#$%^&*() "; + std::string longText; + longText.reserve(10000); + while (longText.length() < 10000) { + longText += base; + } + + unsigned int fontSize = 16; + + auto glyphs = shaper.shapeRun(longText, face, fontSize); + EXPECT_FALSE(glyphs.empty()); + + for (const auto& g : glyphs) { + auto cached = cache.get(face, g.glyphIndex, fontSize); + if (!cached.has_value()) { + auto rendered = face.renderGlyph(g.glyphIndex, fontSize); + if (rendered.has_value()) { + cache.insert(face, g.glyphIndex, fontSize, *rendered); + } + } + } + + EXPECT_LE(cache.size(), cache.capacity()); + EXPECT_GT(cache.size(), 0u); +} + +TEST(FontTest, VisualBitmapDebugging) { + std::string fontPath = getSystemFontPath(); + if (fontPath.empty()) { + GTEST_SKIP() << "No system font found to run visual bitmap debugging test."; + } + + FontFace face; + ASSERT_TRUE(face.loadFromFile(fontPath)); + + unsigned int glyphIndex = FT_Get_Char_Index(face.getFace(), 'A'); + ASSERT_GT(glyphIndex, 0u); + + auto renderedOpt = face.renderGlyph(glyphIndex, 48); + ASSERT_TRUE(renderedOpt.has_value()); + + std::string filename = "A.pgm"; + std::filesystem::remove(filename); + + ASSERT_TRUE(saveGlyphAsPGM(*renderedOpt, filename)); + EXPECT_TRUE(std::filesystem::exists(filename)); + EXPECT_GT(std::filesystem::file_size(filename), 0u); +} + +TEST(FontTest, MetricsValidation) { + std::string fontPath = getSystemFontPath(); + if (fontPath.empty()) { + GTEST_SKIP() << "No system font found to run metrics validation test."; + } + + FontFace face; + ASSERT_TRUE(face.loadFromFile(fontPath)); + FT_Face ftFace = face.getFace(); + ASSERT_NE(ftFace, nullptr); + + unsigned int glyphIndex = FT_Get_Char_Index(ftFace, 'B'); + ASSERT_GT(glyphIndex, 0u); + + unsigned int fontSize = 24; + auto renderedOpt = face.renderGlyph(glyphIndex, fontSize); + ASSERT_TRUE(renderedOpt.has_value()); + + ASSERT_EQ(FT_Set_Pixel_Sizes(ftFace, 0, fontSize), 0); + ASSERT_EQ(FT_Load_Glyph(ftFace, glyphIndex, FT_LOAD_RENDER), 0); + + FT_GlyphSlot slot = ftFace->glyph; + EXPECT_EQ(renderedOpt->width, static_cast(slot->bitmap.width)); + EXPECT_EQ(renderedOpt->height, static_cast(slot->bitmap.rows)); + EXPECT_EQ(renderedOpt->bearingX, slot->bitmap_left); + EXPECT_EQ(renderedOpt->bearingY, slot->bitmap_top); + EXPECT_DOUBLE_EQ(renderedOpt->advance, static_cast(slot->advance.x) / 64.0); +} + +TEST(FontTest, CacheRecencyStress) { + std::string fontPath = getSystemFontPath(); + if (fontPath.empty()) { + GTEST_SKIP() << "No system font found to run cache recency stress test."; + } + + FontFace face; + ASSERT_TRUE(face.loadFromFile(fontPath)); + + GlyphCache cache(5); + + std::vector bmps; + for (int i = 0; i < 10; ++i) { + GlyphBitmap b; + b.width = i; + bmps.push_back(b); + } + + for (unsigned int i = 0; i < 5; ++i) { + cache.insert(face, i, 16, bmps[i]); + } + EXPECT_EQ(cache.size(), 5u); + + ASSERT_TRUE(cache.get(face, 0, 16).has_value()); + ASSERT_TRUE(cache.get(face, 2, 16).has_value()); + + cache.insert(face, 5, 16, bmps[5]); + EXPECT_FALSE(cache.get(face, 1, 16).has_value()); + EXPECT_TRUE(cache.get(face, 5, 16).has_value()); + + ASSERT_TRUE(cache.get(face, 3, 16).has_value()); + + cache.insert(face, 6, 16, bmps[6]); + EXPECT_FALSE(cache.get(face, 4, 16).has_value()); + + EXPECT_TRUE(cache.get(face, 0, 16).has_value()); + EXPECT_TRUE(cache.get(face, 2, 16).has_value()); + EXPECT_TRUE(cache.get(face, 3, 16).has_value()); + EXPECT_TRUE(cache.get(face, 5, 16).has_value()); + EXPECT_TRUE(cache.get(face, 6, 16).has_value()); +} +} \ No newline at end of file diff --git a/engine/tests/font_types_test.cpp b/engine/tests/font_types_test.cpp new file mode 100644 index 0000000..c51ed8f --- /dev/null +++ b/engine/tests/font_types_test.cpp @@ -0,0 +1,277 @@ +#include "fonts_test_helpers.hpp" + +namespace pdfengine::fonts { + +TEST(Type1FontTest, EmbeddedType1FontLoading) { + std::string fontPath = getSystemFontPath(); + if (fontPath.empty()) { + GTEST_SKIP() << "No system font found to run embedded Type1 test."; + } + + std::ifstream file(fontPath, std::ios::binary | std::ios::ate); + ASSERT_TRUE(file.is_open()); + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + std::vector buffer(size); + ASSERT_TRUE(file.read(reinterpret_cast(buffer.data()), size)); + + auto font = pdfengine::fonts::pdf_fonts::FontLoader::loadType1FromMemory("LegacyType1", buffer); + ASSERT_NE(font, nullptr); + EXPECT_EQ(font->getBaseFont(), "LegacyType1"); + EXPECT_EQ(font->getType(), pdfengine::fonts::pdf_fonts::FontType::Type1); + EXPECT_TRUE(font->isEmbedded()); +} + +TEST(Type1FontTest, NonEmbeddedType1SystemFallback) { + auto fontHelv = pdfengine::fonts::pdf_fonts::FontLoader::loadType1SystemFallback("Helvetica"); + ASSERT_NE(fontHelv, nullptr); + EXPECT_EQ(fontHelv->getBaseFont(), "Helvetica"); + EXPECT_EQ(fontHelv->getType(), pdfengine::fonts::pdf_fonts::FontType::Type1); + EXPECT_FALSE(fontHelv->isEmbedded()); + + auto fontTimes = pdfengine::fonts::pdf_fonts::FontLoader::loadType1SystemFallback("Times-BoldItalic"); + ASSERT_NE(fontTimes, nullptr); + EXPECT_EQ(fontTimes->getBaseFont(), "Times-BoldItalic"); + EXPECT_FALSE(fontTimes->isEmbedded()); + + auto fontCourier = pdfengine::fonts::pdf_fonts::FontLoader::loadType1SystemFallback("Courier-Oblique"); + ASSERT_NE(fontCourier, nullptr); + EXPECT_EQ(fontCourier->getBaseFont(), "Courier-Oblique"); + EXPECT_FALSE(fontCourier->isEmbedded()); + + auto fontUnknown = pdfengine::fonts::pdf_fonts::FontLoader::loadType1SystemFallback("UnknownLegacyFont"); + ASSERT_NE(fontUnknown, nullptr); + EXPECT_EQ(fontUnknown->getBaseFont(), "UnknownLegacyFont"); + EXPECT_FALSE(fontUnknown->isEmbedded()); +} + +TEST(CIDFontTest, CompositeFontInitializationAndTypes) { + using namespace pdfengine::fonts::pdf_fonts; + + auto desc = std::make_unique(); + desc->setFontName("SimSun-Descriptor"); + + CIDFont font0("SimSun", FontType::CIDFontType0, false, std::move(desc)); + EXPECT_EQ(font0.getBaseFont(), "SimSun"); + EXPECT_EQ(font0.getType(), FontType::CIDFontType0); + EXPECT_FALSE(font0.isEmbedded()); + EXPECT_EQ(font0.getDescriptor()->getFontName(), "SimSun-Descriptor"); + + CIDFont font2("MS-Gothic", FontType::CIDFontType2, true); + EXPECT_EQ(font2.getBaseFont(), "MS-Gothic"); + EXPECT_EQ(font2.getType(), FontType::CIDFontType2); + EXPECT_TRUE(font2.isEmbedded()); +} + +TEST(CIDFontTest, CIDToGIDTranslations) { + using namespace pdfengine::fonts::pdf_fonts; + + CIDFont font("SimSun", FontType::CIDFontType2, false); + + EXPECT_TRUE(font.isIdentityMap()); + EXPECT_EQ(font.mapCIDToGID(100), 100); + EXPECT_EQ(font.mapCIDToGID(5000), 5000); + + std::unordered_map customMap = { + {10, 100}, + {20, 200}, + {30, 300} + }; + font.setCIDToGIDMap(customMap); + EXPECT_FALSE(font.isIdentityMap()); + EXPECT_EQ(font.mapCIDToGID(10), 100); + EXPECT_EQ(font.mapCIDToGID(20), 200); + EXPECT_EQ(font.mapCIDToGID(30), 300); + EXPECT_EQ(font.mapCIDToGID(40), 0); + + font.setIdentityCIDToGIDMap(); + EXPECT_TRUE(font.isIdentityMap()); + EXPECT_EQ(font.mapCIDToGID(10), 10); +} + +TEST(CIDFontTest, NonEmbeddedCIDFontSystemFallback) { + using namespace pdfengine::fonts::pdf_fonts; + + auto fontSimSun = FontLoader::loadCIDFontSystemFallback("SimSun", FontType::CIDFontType2); + ASSERT_NE(fontSimSun, nullptr); + EXPECT_EQ(fontSimSun->getBaseFont(), "SimSun"); + EXPECT_EQ(fontSimSun->getType(), FontType::CIDFontType2); + EXPECT_FALSE(fontSimSun->isEmbedded()); + + auto fontGothic = FontLoader::loadCIDFontSystemFallback("HeiseiMin-W3", FontType::CIDFontType0); + ASSERT_NE(fontGothic, nullptr); + EXPECT_EQ(fontGothic->getBaseFont(), "HeiseiMin-W3"); + EXPECT_EQ(fontGothic->getType(), FontType::CIDFontType0); + EXPECT_FALSE(fontGothic->isEmbedded()); + + HbShaper shaper; + auto glyphs = shaper.shapeRun("日本語漢字", fontGothic->getFontFace(), 16); + EXPECT_FALSE(glyphs.empty()); +} + +TEST(FontFallbackTest, SingletonInstanceIsUnique) { + using namespace pdfengine::fonts::pdf_fonts; + + auto& instance1 = FontFallback::getInstance(); + auto& instance2 = FontFallback::getInstance(); + EXPECT_EQ(&instance1, &instance2); +} + +TEST(FontFallbackTest, StandardFontFallbacks) { + using namespace pdfengine::fonts::pdf_fonts; + + auto& fallback = FontFallback::getInstance(); + + std::string path1 = fallback.getFallbackFontPath("Helvetica"); + EXPECT_FALSE(path1.empty()); + EXPECT_TRUE(std::filesystem::exists(path1)); +#if defined(_WIN32) + EXPECT_TRUE(containsCI(path1, "arial") || containsCI(path1, "liberationsans")); +#elif defined(__APPLE__) + EXPECT_TRUE(containsCI(path1, "arial") || containsCI(path1, "helvetica") || + containsCI(path1, "liberationsans")); +#else + EXPECT_TRUE(containsCI(path1, "liberationsans") || containsCI(path1, "dejavusans")); +#endif + + std::string path2 = fallback.getFallbackFontPath("Times-Roman"); + EXPECT_FALSE(path2.empty()); + EXPECT_TRUE(std::filesystem::exists(path2)); +#if defined(_WIN32) + EXPECT_TRUE(containsCI(path2, "times") || containsCI(path2, "liberationserif") || + containsCI(path2, "tinos")); +#elif defined(__APPLE__) + EXPECT_TRUE(containsCI(path2, "times") || containsCI(path2, "liberationserif") || + containsCI(path2, "tinos")); +#else + EXPECT_TRUE(containsCI(path2, "liberationserif") || containsCI(path2, "dejavuserif") || + containsCI(path2, "tinos")); +#endif +} + +TEST(FontFallbackTest, StyleModifierResolutions) { + using namespace pdfengine::fonts::pdf_fonts; + + auto& fallback = FontFallback::getInstance(); + + std::string pathBold = fallback.getFallbackFontPath("Helvetica", true, false); + EXPECT_FALSE(pathBold.empty()); + EXPECT_TRUE(std::filesystem::exists(pathBold)); +#if defined(_WIN32) + EXPECT_TRUE(containsCI(pathBold, "arialbd") || containsCI(pathBold, "liberationsans-bold")); +#endif + + std::string pathBoldItalic = fallback.getFallbackFontPath("Times", true, true); + EXPECT_FALSE(pathBoldItalic.empty()); + EXPECT_TRUE(std::filesystem::exists(pathBoldItalic)); +#if defined(_WIN32) + EXPECT_TRUE(containsCI(pathBoldItalic, "timesbi") || + containsCI(pathBoldItalic, "liberationserif-bolditalic") || + containsCI(pathBoldItalic, "tinos-bolditalic")); +#endif +} + +TEST(FontFallbackTest, CustomFallbackRegistration) { + using namespace pdfengine::fonts::pdf_fonts; + + auto& fallback = FontFallback::getInstance(); + fallback.resetToDefaults(); + + std::string standardPath = fallback.getFallbackFontPath("Helvetica"); + + std::filesystem::path overrideFont = + std::filesystem::temp_directory_path() / "pdfengine_custom_fallback.ttf"; + { std::ofstream(overrideFont) << "stub-font"; } + + fallback.registerFallback("helvetica", overrideFont.string()); + + std::string overridenPath = fallback.getFallbackFontPath("Helvetica"); + EXPECT_EQ(overridenPath, overrideFont.string()); + + fallback.resetToDefaults(); + std::string restoredPath = fallback.getFallbackFontPath("Helvetica"); + EXPECT_EQ(restoredPath, standardPath); + + std::filesystem::remove(overrideFont); +} +TEST(FontSubstitutionAndWidthsTest, WidthMatchingAndSubstitutionVerification) { + using namespace pdfengine::fonts::pdf_fonts; + + auto font = FontLoader::loadType1SystemFallback("Helvetica"); + ASSERT_NE(font, nullptr); + + EXPECT_FALSE(font->hasWidths()); + + std::vector pdfWidths = { 600.0, 500.0, 550.0, 400.0 }; + font->setWidths(65, 68, pdfWidths); + + EXPECT_TRUE(font->hasWidths()); + + double fontSize = 12.0; + + double expectedWidthA = (600.0 / 1000.0) * fontSize; + double expectedWidthB = (500.0 / 1000.0) * fontSize; + double expectedWidthC = (550.0 / 1000.0) * fontSize; + double expectedWidthD = (400.0 / 1000.0) * fontSize; + + EXPECT_NEAR(font->getCharWidth(65, fontSize), expectedWidthA, 1e-5); + EXPECT_NEAR(font->getCharWidth(66, fontSize), expectedWidthB, 1e-5); + EXPECT_NEAR(font->getCharWidth(67, fontSize), expectedWidthC, 1e-5); + EXPECT_NEAR(font->getCharWidth(68, fontSize), expectedWidthD, 1e-5); + + EXPECT_EQ(font->getCharWidth(999, fontSize), 0.0); +} + +TEST(CIDAdvancedMappingTest, IdentityVSupport) { + using namespace pdfengine::fonts::pdf_fonts; + PredefinedEncoding identityV(SimpleEncodingType::Identity_V); + EXPECT_EQ(identityV.getType(), SimpleEncodingType::Identity_V); + EXPECT_EQ(identityV.decode(65), 65); + EXPECT_EQ(identityV.decode(1000), 1000); +} + +TEST(CIDAdvancedMappingTest, VerticalMetricsResolution) { + using namespace pdfengine::fonts::pdf_fonts; + + auto font = FontLoader::loadType1SystemFallback("Helvetica"); + ASSERT_NE(font, nullptr); + + double fontSize = 12.0; + EXPECT_EQ(font->isVertical(), false); + EXPECT_EQ(font->getCharHeight(65, fontSize), fontSize); + + font->setVertical(true); + EXPECT_EQ(font->isVertical(), true); + + std::vector verticalAdvances = { 1000.0, 800.0, 900.0 }; + font->setVerticalMetrics(65, 67, verticalAdvances); + EXPECT_TRUE(font->hasVerticalMetrics()); + + EXPECT_NEAR(font->getCharHeight(65, fontSize), 12.0, 1e-5); + EXPECT_NEAR(font->getCharHeight(66, fontSize), 9.6, 1e-5); + EXPECT_NEAR(font->getCharHeight(67, fontSize), 10.8, 1e-5); + EXPECT_NEAR(font->getCharHeight(999, fontSize), 12.0, 1e-5); +} + +TEST(CIDAdvancedMappingTest, CjkCollectionResolutionDB) { + using namespace pdfengine::fonts::pdf_fonts; + + EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Japan1", 1010), 0x3041); + EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Japan1", 1092), 0x3093); + + EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Japan1", 1125), 0x30A1); + EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Japan1", 1205), 0x30F6); + + EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Japan1", 1206), 0x4E00); + + EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-GB1", 1), 0x3000); + EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-GB1", 2), 0x3001); +} + +TEST(CIDAdvancedMappingTest, HarfBuzzVerticalShapingSignature) { + using namespace pdfengine::fonts; + + EXPECT_EQ(static_cast(HbShaper::WritingMode::Horizontal), 0); + EXPECT_EQ(static_cast(HbShaper::WritingMode::Vertical), 1); +} +} diff --git a/engine/tests/fonts_test.cpp b/engine/tests/fonts_test.cpp deleted file mode 100644 index c252e6b..0000000 --- a/engine/tests/fonts_test.cpp +++ /dev/null @@ -1,1323 +0,0 @@ -#include "fonts/face/font_face.hpp" -#include "fonts/shaping/hb_shaper.hpp" -#include "fonts/cache/glyph_cache.hpp" -#include "fonts/pdf_fonts/font.hpp" -#include "fonts/pdf_fonts/types/truetype_font.hpp" -#include "fonts/pdf_fonts/types/type1_font.hpp" -#include "fonts/pdf_fonts/types/cid_font.hpp" -#include "fonts/pdf_fonts/font_loader.hpp" -#include "fonts/pdf_fonts/encoding/encoding.hpp" -#include "fonts/pdf_fonts/encoding/cjk_collection_db.hpp" -#include "fonts/pdf_fonts/font_fallback.hpp" -#include "fonts/pdf_fonts/font_subset.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace { - -bool containsCI(const std::string& haystack, const std::string& needle) { - auto it = std::search( - haystack.begin(), haystack.end(), needle.begin(), needle.end(), - [](char a, char b) { - return std::tolower(static_cast(a)) == - std::tolower(static_cast(b)); - }); - return it != haystack.end(); -} - -bool saveGlyphAsPGM(const pdfengine::fonts::GlyphBitmap& bitmap, const std::string& filename) { - if (bitmap.width == 0 || bitmap.height == 0 || bitmap.pixels.empty()) { - return false; - } - std::ofstream out(filename, std::ios::binary); - if (!out) { - return false; - } - out << "P5\n" << bitmap.width << " " << bitmap.height << "\n255\n"; - out.write(reinterpret_cast(bitmap.pixels.data()), bitmap.pixels.size()); - return true; -} - -std::string getSystemFontPath() { -#if defined(_WIN32) - std::vector paths = { - "C:\\Windows\\Fonts\\arial.ttf", - "C:\\Windows\\Fonts\\consola.ttf", - "C:\\Windows\\Fonts\\tahoma.ttf" - }; -#elif defined(__APPLE__) - std::vector paths = { - "/Library/Fonts/Arial.ttf", - "/System/Library/Fonts/Geneva.ttf", - "/System/Library/Fonts/Helvetica.ttc", - "/System/Library/Fonts/Supplemental/Arial.ttf" - }; -#else - std::vector paths = { - "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", - "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf", - "/usr/share/fonts/truetype/freefont/FreeSans.ttf" - }; -#endif - - for (const auto& path : paths) { - if (std::filesystem::exists(path)) { - return path; - } - } - return ""; -} - -} - -namespace pdfengine::fonts { - -TEST(FontTest, FontFaceInitialization) { - FontFace face; - EXPECT_EQ(face.getFace(), nullptr); -} - -TEST(FontTest, FontFaceLoadNonExistentFile) { - FontFace face; - EXPECT_FALSE(face.loadFromFile("this_file_does_not_exist_12345.ttf")); - EXPECT_EQ(face.getFace(), nullptr); -} - -TEST(FontTest, FontFaceMoveSemantics) { - std::string fontPath = getSystemFontPath(); - if (fontPath.empty()) { - GTEST_SKIP() << "No system font found to run move semantics test."; - } - - FontFace face1; - ASSERT_TRUE(face1.loadFromFile(fontPath)); - FT_Face rawFace = face1.getFace(); - ASSERT_NE(rawFace, nullptr); - - FontFace face2(std::move(face1)); - EXPECT_EQ(face1.getFace(), nullptr); - EXPECT_EQ(face2.getFace(), rawFace); - - FontFace face3; - face3 = std::move(face2); - EXPECT_EQ(face2.getFace(), nullptr); - EXPECT_EQ(face3.getFace(), rawFace); -} - -TEST(FontTest, HbShaperEmptyInput) { - std::string fontPath = getSystemFontPath(); - if (fontPath.empty()) { - GTEST_SKIP() << "No system font found to run empty input shaper test."; - } - - FontFace face; - ASSERT_TRUE(face.loadFromFile(fontPath)); - - HbShaper shaper; - auto glyphs = shaper.shapeRun("", face, 16); - EXPECT_TRUE(glyphs.empty()); -} - -TEST(FontTest, HbShaperNullFace) { - FontFace face; - HbShaper shaper; - auto glyphs = shaper.shapeRun("Hello", face, 16); - EXPECT_TRUE(glyphs.empty()); -} - -TEST(FontTest, HbShaperShapeTextSuccess) { - std::string fontPath = getSystemFontPath(); - if (fontPath.empty()) { - std::cout << "[ WARNING ] Skipping shape success test: no system font found." << std::endl; - GTEST_SKIP() << "No system font found to run text shaping test."; - } - - FontFace face; - ASSERT_TRUE(face.loadFromFile(fontPath)); - ASSERT_NE(face.getFace(), nullptr); - - HbShaper shaper; - std::string testText = "Hello World!"; - auto glyphs = shaper.shapeRun(testText, face, 16); - - EXPECT_FALSE(glyphs.empty()); - - for (const auto& g : glyphs) { - EXPECT_GT(g.advanceX, 0.0); - } -} - -TEST(FontTest, FontFaceRenderGlyphNullFace) { - FontFace face; - auto glyph = face.renderGlyph(0, 16); - EXPECT_FALSE(glyph.has_value()); -} - -TEST(FontTest, FontFaceRenderGlyphSuccess) { - std::string fontPath = getSystemFontPath(); - if (fontPath.empty()) { - GTEST_SKIP() << "No system font found to run render glyph success test."; - } - - FontFace face; - ASSERT_TRUE(face.loadFromFile(fontPath)); - ASSERT_NE(face.getFace(), nullptr); - - unsigned int glyphIndex = FT_Get_Char_Index(face.getFace(), 'A'); - ASSERT_GT(glyphIndex, 0u); - - auto glyphOpt = face.renderGlyph(glyphIndex, 24); - ASSERT_TRUE(glyphOpt.has_value()); - - const auto& glyph = *glyphOpt; - EXPECT_GT(glyph.width, 0); - EXPECT_GT(glyph.height, 0); - EXPECT_EQ(glyph.pixels.size(), static_cast(glyph.width * glyph.height)); - EXPECT_GT(glyph.advance, 0.0); -} - -TEST(FontTest, GlyphCacheBasicGetInsert) { - std::string fontPath = getSystemFontPath(); - if (fontPath.empty()) { - GTEST_SKIP() << "No system font found to run cache test."; - } - - FontFace face; - ASSERT_TRUE(face.loadFromFile(fontPath)); - - GlyphCache cache(10); - EXPECT_EQ(cache.capacity(), 10u); - EXPECT_EQ(cache.size(), 0u); - - auto miss = cache.get(face, 12, 16); - EXPECT_FALSE(miss.has_value()); - - GlyphBitmap bitmap; - bitmap.width = 10; - bitmap.height = 12; - bitmap.pixels = std::vector(120, 255); - bitmap.advance = 8.5; - - cache.insert(face, 12, 16, bitmap); - EXPECT_EQ(cache.size(), 1u); - - auto hit = cache.get(face, 12, 16); - ASSERT_TRUE(hit.has_value()); - EXPECT_EQ(hit->width, 10); - EXPECT_EQ(hit->height, 12); - EXPECT_EQ(hit->advance, 8.5); - EXPECT_EQ(hit->pixels.size(), 120u); -} - -TEST(FontTest, GlyphCacheEvictionPolicy) { - std::string fontPath = getSystemFontPath(); - if (fontPath.empty()) { - GTEST_SKIP() << "No system font found to run cache eviction test."; - } - - FontFace face; - ASSERT_TRUE(face.loadFromFile(fontPath)); - - GlyphCache cache(2); - - GlyphBitmap bmp1{ .width = 1 }; - GlyphBitmap bmp2{ .width = 2 }; - GlyphBitmap bmp3{ .width = 3 }; - - cache.insert(face, 1, 16, bmp1); - cache.insert(face, 2, 16, bmp2); - EXPECT_EQ(cache.size(), 2u); - - cache.insert(face, 3, 16, bmp3); - EXPECT_EQ(cache.size(), 2u); - - EXPECT_FALSE(cache.get(face, 1, 16).has_value()); - EXPECT_TRUE(cache.get(face, 2, 16).has_value()); - EXPECT_TRUE(cache.get(face, 3, 16).has_value()); -} - -TEST(FontTest, GlyphCacheLRUPolicy) { - std::string fontPath = getSystemFontPath(); - if (fontPath.empty()) { - GTEST_SKIP() << "No system font found to run cache LRU test."; - } - - FontFace face; - ASSERT_TRUE(face.loadFromFile(fontPath)); - - GlyphCache cache(2); - - GlyphBitmap bmp1{ .width = 1 }; - GlyphBitmap bmp2{ .width = 2 }; - GlyphBitmap bmp3{ .width = 3 }; - - cache.insert(face, 1, 16, bmp1); - cache.insert(face, 2, 16, bmp2); - - auto hit = cache.get(face, 1, 16); - ASSERT_TRUE(hit.has_value()); - - cache.insert(face, 3, 16, bmp3); - EXPECT_EQ(cache.size(), 2u); - - EXPECT_TRUE(cache.get(face, 1, 16).has_value()); - EXPECT_FALSE(cache.get(face, 2, 16).has_value()); - EXPECT_TRUE(cache.get(face, 3, 16).has_value()); -} - -TEST(FontTest, FontPipelineIntegration) { - std::string fontPath = getSystemFontPath(); - if (fontPath.empty()) { - GTEST_SKIP() << "No system font found to run pipeline integration test."; - } - - FontFace face; - ASSERT_TRUE(face.loadFromFile(fontPath)); - - HbShaper shaper; - GlyphCache cache(100); - - std::vector testTexts = {"Hello World", "office", "سلام"}; - unsigned int fontSize = 16; - - for (const auto& text : testTexts) { - auto shapedGlyphs = shaper.shapeRun(text, face, fontSize); - EXPECT_FALSE(shapedGlyphs.empty()); - - for (const auto& sg : shapedGlyphs) { - auto cachedBmp = cache.get(face, sg.glyphIndex, fontSize); - if (!cachedBmp.has_value()) { - auto renderedOpt = face.renderGlyph(sg.glyphIndex, fontSize); - ASSERT_TRUE(renderedOpt.has_value()); - cache.insert(face, sg.glyphIndex, fontSize, *renderedOpt); - EXPECT_EQ(renderedOpt->pixels.size(), static_cast(renderedOpt->width * renderedOpt->height)); - } - - auto hitBmp = cache.get(face, sg.glyphIndex, fontSize); - ASSERT_TRUE(hitBmp.has_value()); - EXPECT_EQ(hitBmp->pixels.size(), static_cast(hitBmp->width * hitBmp->height)); - EXPECT_GE(hitBmp->advance, 0.0); - } - } -} - -TEST(FontTest, UnicodeAndRtlShaping) { - std::string fontPath = getSystemFontPath(); - if (fontPath.empty()) { - GTEST_SKIP() << "No system font found to run Unicode and RTL shaping test."; - } - - FontFace face; - ASSERT_TRUE(face.loadFromFile(fontPath)); - - HbShaper shaper; - unsigned int fontSize = 16; - - { - std::string arabicText = "سلام"; - auto glyphs = shaper.shapeRun(arabicText, face, fontSize); - EXPECT_FALSE(glyphs.empty()); - for (const auto& g : glyphs) { - EXPECT_TRUE(g.advanceX != 0.0 || g.advanceY != 0.0 || g.offsetX != 0.0 || g.offsetY != 0.0 || g.glyphIndex != 999999u); - } - } - - { - std::string hindiText = "नमस्ते"; - auto glyphs = shaper.shapeRun(hindiText, face, fontSize); - EXPECT_FALSE(glyphs.empty()); - for (const auto& g : glyphs) { - EXPECT_TRUE(g.advanceX != 0.0 || g.advanceY != 0.0 || g.glyphIndex != 999999u); - } - } - - { - std::string ligatureText = "office"; - auto glyphs = shaper.shapeRun(ligatureText, face, fontSize); - EXPECT_FALSE(glyphs.empty()); - EXPECT_LE(glyphs.size(), ligatureText.length()); - for (const auto& g : glyphs) { - EXPECT_TRUE(g.advanceX != 0.0 || g.advanceY != 0.0 || g.glyphIndex != 999999u); - } - } -} - -TEST(FontTest, CachePerformanceTest) { - std::string fontPath = getSystemFontPath(); - if (fontPath.empty()) { - GTEST_SKIP() << "No system font found to run cache performance test."; - } - - FontFace face; - ASSERT_TRUE(face.loadFromFile(fontPath)); - - HbShaper shaper; - GlyphCache cache(100); - - std::string text = "Hello Hello Hello Hello"; - unsigned int fontSize = 16; - - { - auto glyphs = shaper.shapeRun(text, face, fontSize); - for (const auto& g : glyphs) { - auto cached = cache.get(face, g.glyphIndex, fontSize); - if (!cached.has_value()) { - auto rendered = face.renderGlyph(g.glyphIndex, fontSize); - if (rendered.has_value()) { - cache.insert(face, g.glyphIndex, fontSize, *rendered); - } - } - } - } - - cache.resetStats(); - - for (int i = 0; i < 1000; ++i) { - auto glyphs = shaper.shapeRun(text, face, fontSize); - for (const auto& g : glyphs) { - auto cached = cache.get(face, g.glyphIndex, fontSize); - if (!cached.has_value()) { - auto rendered = face.renderGlyph(g.glyphIndex, fontSize); - if (rendered.has_value()) { - cache.insert(face, g.glyphIndex, fontSize, *rendered); - } - } - } - } - - double hitRateVal = cache.hitRate(); - std::cout << "[ INFO ] Cache Hit Rate for repetitive text: " << (hitRateVal * 100.0) << "%" << std::endl; - EXPECT_GT(hitRateVal, 0.90); -} - -TEST(FontTest, EngineStressTest) { - std::string fontPath = getSystemFontPath(); - if (fontPath.empty()) { - GTEST_SKIP() << "No system font found to run engine stress test."; - } - - FontFace face; - ASSERT_TRUE(face.loadFromFile(fontPath)); - - HbShaper shaper; - GlyphCache cache(32); - - std::string base = "The quick brown fox jumps over the lazy dog. 1234567890!@#$%^&*() "; - std::string longText; - longText.reserve(10000); - while (longText.length() < 10000) { - longText += base; - } - - unsigned int fontSize = 16; - - auto glyphs = shaper.shapeRun(longText, face, fontSize); - EXPECT_FALSE(glyphs.empty()); - - for (const auto& g : glyphs) { - auto cached = cache.get(face, g.glyphIndex, fontSize); - if (!cached.has_value()) { - auto rendered = face.renderGlyph(g.glyphIndex, fontSize); - if (rendered.has_value()) { - cache.insert(face, g.glyphIndex, fontSize, *rendered); - } - } - } - - EXPECT_LE(cache.size(), cache.capacity()); - EXPECT_GT(cache.size(), 0u); -} - -TEST(FontTest, VisualBitmapDebugging) { - std::string fontPath = getSystemFontPath(); - if (fontPath.empty()) { - GTEST_SKIP() << "No system font found to run visual bitmap debugging test."; - } - - FontFace face; - ASSERT_TRUE(face.loadFromFile(fontPath)); - - unsigned int glyphIndex = FT_Get_Char_Index(face.getFace(), 'A'); - ASSERT_GT(glyphIndex, 0u); - - auto renderedOpt = face.renderGlyph(glyphIndex, 48); - ASSERT_TRUE(renderedOpt.has_value()); - - std::string filename = "A.pgm"; - std::filesystem::remove(filename); - - ASSERT_TRUE(saveGlyphAsPGM(*renderedOpt, filename)); - EXPECT_TRUE(std::filesystem::exists(filename)); - EXPECT_GT(std::filesystem::file_size(filename), 0u); -} - -TEST(FontTest, MetricsValidation) { - std::string fontPath = getSystemFontPath(); - if (fontPath.empty()) { - GTEST_SKIP() << "No system font found to run metrics validation test."; - } - - FontFace face; - ASSERT_TRUE(face.loadFromFile(fontPath)); - FT_Face ftFace = face.getFace(); - ASSERT_NE(ftFace, nullptr); - - unsigned int glyphIndex = FT_Get_Char_Index(ftFace, 'B'); - ASSERT_GT(glyphIndex, 0u); - - unsigned int fontSize = 24; - auto renderedOpt = face.renderGlyph(glyphIndex, fontSize); - ASSERT_TRUE(renderedOpt.has_value()); - - ASSERT_EQ(FT_Set_Pixel_Sizes(ftFace, 0, fontSize), 0); - ASSERT_EQ(FT_Load_Glyph(ftFace, glyphIndex, FT_LOAD_RENDER), 0); - - FT_GlyphSlot slot = ftFace->glyph; - EXPECT_EQ(renderedOpt->width, static_cast(slot->bitmap.width)); - EXPECT_EQ(renderedOpt->height, static_cast(slot->bitmap.rows)); - EXPECT_EQ(renderedOpt->bearingX, slot->bitmap_left); - EXPECT_EQ(renderedOpt->bearingY, slot->bitmap_top); - EXPECT_DOUBLE_EQ(renderedOpt->advance, static_cast(slot->advance.x) / 64.0); -} - -TEST(FontTest, CacheRecencyStress) { - std::string fontPath = getSystemFontPath(); - if (fontPath.empty()) { - GTEST_SKIP() << "No system font found to run cache recency stress test."; - } - - FontFace face; - ASSERT_TRUE(face.loadFromFile(fontPath)); - - GlyphCache cache(5); - - std::vector bmps; - for (int i = 0; i < 10; ++i) { - GlyphBitmap b; - b.width = i; - bmps.push_back(b); - } - - for (unsigned int i = 0; i < 5; ++i) { - cache.insert(face, i, 16, bmps[i]); - } - EXPECT_EQ(cache.size(), 5u); - - ASSERT_TRUE(cache.get(face, 0, 16).has_value()); - ASSERT_TRUE(cache.get(face, 2, 16).has_value()); - - cache.insert(face, 5, 16, bmps[5]); - EXPECT_FALSE(cache.get(face, 1, 16).has_value()); - EXPECT_TRUE(cache.get(face, 5, 16).has_value()); - - ASSERT_TRUE(cache.get(face, 3, 16).has_value()); - - cache.insert(face, 6, 16, bmps[6]); - EXPECT_FALSE(cache.get(face, 4, 16).has_value()); - - EXPECT_TRUE(cache.get(face, 0, 16).has_value()); - EXPECT_TRUE(cache.get(face, 2, 16).has_value()); - EXPECT_TRUE(cache.get(face, 3, 16).has_value()); - EXPECT_TRUE(cache.get(face, 5, 16).has_value()); - EXPECT_TRUE(cache.get(face, 6, 16).has_value()); -} - -TEST(FontLoaderTest, FontFaceLoadFromMemorySuccess) { - std::string fontPath = getSystemFontPath(); - if (fontPath.empty()) { - GTEST_SKIP() << "No system font found to run load from memory success test."; - } - - std::ifstream file(fontPath, std::ios::binary | std::ios::ate); - ASSERT_TRUE(file.is_open()); - std::streamsize size = file.tellg(); - file.seekg(0, std::ios::beg); - - std::vector buffer(size); - ASSERT_TRUE(file.read(reinterpret_cast(buffer.data()), size)); - - FontFace face; - ASSERT_TRUE(face.loadFromMemory(buffer)); - ASSERT_NE(face.getFace(), nullptr); - - unsigned int glyphIndex = FT_Get_Char_Index(face.getFace(), 'M'); - ASSERT_GT(glyphIndex, 0u); - - auto glyph = face.renderGlyph(glyphIndex, 16); - ASSERT_TRUE(glyph.has_value()); - EXPECT_GT(glyph->width, 0); - EXPECT_GT(glyph->height, 0); - EXPECT_GT(glyph->advance, 0.0); -} - -TEST(FontLoaderTest, FontFaceLoadFromMemoryInvalid) { - FontFace face; - std::vector emptyData; - EXPECT_FALSE(face.loadFromMemory(emptyData)); - EXPECT_EQ(face.getFace(), nullptr); - - std::vector corruptData = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66}; - EXPECT_FALSE(face.loadFromMemory(corruptData)); - EXPECT_EQ(face.getFace(), nullptr); -} - -TEST(FontLoaderTest, FontLoaderTrueTypeSuccess) { - std::string fontPath = getSystemFontPath(); - if (fontPath.empty()) { - GTEST_SKIP() << "No system font found to run PDF font loader test."; - } - - std::ifstream file(fontPath, std::ios::binary | std::ios::ate); - ASSERT_TRUE(file.is_open()); - std::streamsize size = file.tellg(); - file.seekg(0, std::ios::beg); - std::vector buffer(size); - ASSERT_TRUE(file.read(reinterpret_cast(buffer.data()), size)); - - auto pdfFont = pdfengine::fonts::pdf_fonts::FontLoader::loadTrueTypeFromMemory("Arial", buffer); - ASSERT_NE(pdfFont, nullptr); - EXPECT_EQ(pdfFont->getBaseFont(), "Arial"); - EXPECT_EQ(pdfFont->getType(), pdfengine::fonts::pdf_fonts::FontType::TrueType); - EXPECT_TRUE(pdfFont->isEmbedded()); - - HbShaper shaper; - auto glyphs = shaper.shapeRun("Test Memory Load", pdfFont->getFontFace(), 16); - EXPECT_FALSE(glyphs.empty()); -} - -TEST(FontDescriptorTest, DescriptorDefaultValues) { - pdfengine::fonts::pdf_fonts::FontDescriptor desc; - EXPECT_EQ(desc.getFontName(), ""); - EXPECT_EQ(desc.getFlags(), 0); - EXPECT_EQ(desc.getItalicAngle(), 0.0); - EXPECT_EQ(desc.getAscent(), 0.0); - EXPECT_EQ(desc.getDescent(), 0.0); - EXPECT_EQ(desc.getCapHeight(), 0.0); - EXPECT_EQ(desc.getStemV(), 0.0); - - pdfengine::fonts::pdf_fonts::FontBBox bbox = desc.getFontBBox(); - EXPECT_EQ(bbox.llx, 0); - EXPECT_EQ(bbox.lly, 0); - EXPECT_EQ(bbox.urx, 0); - EXPECT_EQ(bbox.ury, 0); - - EXPECT_FALSE(desc.isFixedPitch()); - EXPECT_FALSE(desc.isSerif()); - EXPECT_FALSE(desc.isSymbolic()); - EXPECT_FALSE(desc.isItalic()); -} - -TEST(FontDescriptorTest, DescriptorParsingSuccess) { - std::string dict = - "<< /Type /FontDescriptor\n" - " /FontName /ArialMT\n" - " /Flags 32\n" - " /FontBBox [-166 -225 1000 931]\n" - " /ItalicAngle 0\n" - " /Ascent 905\n" - " /Descent -211\n" - " /CapHeight 728\n" - " /StemV 94\n" - ">>"; - - pdfengine::fonts::pdf_fonts::FontDescriptor desc; - ASSERT_TRUE(desc.parseFromDictionaryString(dict)); - - EXPECT_EQ(desc.getFontName(), "ArialMT"); - EXPECT_EQ(desc.getFlags(), 32); - - pdfengine::fonts::pdf_fonts::FontBBox bbox = desc.getFontBBox(); - EXPECT_EQ(bbox.llx, -166); - EXPECT_EQ(bbox.lly, -225); - EXPECT_EQ(bbox.urx, 1000); - EXPECT_EQ(bbox.ury, 931); - - EXPECT_DOUBLE_EQ(desc.getItalicAngle(), 0.0); - EXPECT_DOUBLE_EQ(desc.getAscent(), 905.0); - EXPECT_DOUBLE_EQ(desc.getDescent(), -211.0); - EXPECT_DOUBLE_EQ(desc.getCapHeight(), 728.0); - EXPECT_DOUBLE_EQ(desc.getStemV(), 94.0); - - EXPECT_FALSE(desc.isFixedPitch()); - EXPECT_TRUE(desc.isNonsymbolic()); - EXPECT_FALSE(desc.isItalic()); -} - -TEST(FontDescriptorTest, DescriptorParsingMalformed) { - pdfengine::fonts::pdf_fonts::FontDescriptor desc; - - EXPECT_FALSE(desc.parseFromDictionaryString("/Flags 32 >>")); - - EXPECT_FALSE(desc.parseFromDictionaryString("<< /Flags 32")); - - EXPECT_FALSE(desc.parseFromDictionaryString("<< /FontBBox [-166 -225] >>")); - - EXPECT_FALSE(desc.parseFromDictionaryString("<< /Ascent abc >>")); - - EXPECT_FALSE(desc.parseFromDictionaryString("<< /Ascent >>")); -} - -TEST(FontDescriptorTest, FontLoaderWithDescriptor) { - std::string fontPath = getSystemFontPath(); - if (fontPath.empty()) { - GTEST_SKIP() << "No system font found to run PDF font loader test."; - } - - std::ifstream file(fontPath, std::ios::binary | std::ios::ate); - ASSERT_TRUE(file.is_open()); - std::streamsize size = file.tellg(); - file.seekg(0, std::ios::beg); - std::vector buffer(size); - ASSERT_TRUE(file.read(reinterpret_cast(buffer.data()), size)); - - auto descriptor = std::make_unique(); - descriptor->setFontName("Arial-BoldMT"); - descriptor->setFlags(96); - descriptor->setAscent(905.0); - descriptor->setDescent(-211.0); - - auto pdfFont = pdfengine::fonts::pdf_fonts::FontLoader::loadTrueTypeFromMemory("Arial-Bold", buffer, std::move(descriptor)); - ASSERT_NE(pdfFont, nullptr); - EXPECT_EQ(pdfFont->getBaseFont(), "Arial-Bold"); - EXPECT_TRUE(pdfFont->isEmbedded()); - - const auto* retrievedDesc = pdfFont->getDescriptor(); - ASSERT_NE(retrievedDesc, nullptr); - EXPECT_EQ(retrievedDesc->getFontName(), "Arial-BoldMT"); - EXPECT_EQ(retrievedDesc->getFlags(), 96); - EXPECT_TRUE(retrievedDesc->isItalic()); - EXPECT_TRUE(retrievedDesc->isNonsymbolic()); - EXPECT_DOUBLE_EQ(retrievedDesc->getAscent(), 905.0); - EXPECT_DOUBLE_EQ(retrievedDesc->getDescent(), -211.0); -} - -TEST(EncodingTest, PredefinedEncodingTest) { - using namespace pdfengine::fonts::pdf_fonts; - - PredefinedEncoding winAnsi(SimpleEncodingType::WinAnsi); - EXPECT_EQ(winAnsi.getType(), SimpleEncodingType::WinAnsi); - EXPECT_EQ(winAnsi.decode(65), 65); - EXPECT_EQ(winAnsi.decode(128), 0x20AC); - EXPECT_EQ(winAnsi.decode(169), 169); - EXPECT_EQ(winAnsi.decode(300), 0); - - PredefinedEncoding macRoman(SimpleEncodingType::MacRoman); - EXPECT_EQ(macRoman.getType(), SimpleEncodingType::MacRoman); - EXPECT_EQ(macRoman.decode(65), 65); - EXPECT_EQ(macRoman.decode(128), 0x00C4); - EXPECT_EQ(macRoman.decode(300), 0); - - PredefinedEncoding identity(SimpleEncodingType::Identity); - EXPECT_EQ(identity.getType(), SimpleEncodingType::Identity); - EXPECT_EQ(identity.decode(65), 65); - EXPECT_EQ(identity.decode(128), 128); - EXPECT_EQ(identity.decode(1000), 1000); -} - -TEST(EncodingTest, CustomEncodingWithDifferences) { - using namespace pdfengine::fonts::pdf_fonts; - - auto baseEncoding = std::make_unique(SimpleEncodingType::WinAnsi); - CustomEncoding custom(std::move(baseEncoding)); - - EXPECT_EQ(custom.decode(65), 65); - - custom.addDifference(120, "quotesingle"); - EXPECT_EQ(custom.decode(120), 0x0027); - - custom.addDifference(121, "uni0041"); - EXPECT_EQ(custom.decode(121), 0x0041); - - custom.addDifference(122, "u0042"); - EXPECT_EQ(custom.decode(122), 0x0042); - - custom.addDifference(123, "nonexistentglyphname123"); - EXPECT_EQ(custom.decode(123), 123); -} - -TEST(EncodingTest, ToUnicodeCMapbfchar) { - using namespace pdfengine::fonts::pdf_fonts; - - ToUnicodeCMap cmap; - - std::string cmapStream = - "/CIDInit /ProcSet findresource begin\n" - "12 dict begin\n" - "begincmap\n" - "/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def\n" - "/CMapName /Custom-ToUnicode def\n" - "1 begincodespacerange\n" - "<0000> \n" - "endcodespacerange\n" - "2 beginbfchar\n" - "<0001> <0041>\n" - "<0002> <0042>\n" - "endbfchar\n" - "endcmap\n" - "CMapName currentdict /CMap defineresource pop\n" - "end\n" - "end\n"; - - ASSERT_TRUE(cmap.parseCMapStream(cmapStream)); - EXPECT_EQ(cmap.decode(1), 0x0041); - EXPECT_EQ(cmap.decode(2), 0x0042); - EXPECT_EQ(cmap.decode(3), 0); -} - -TEST(EncodingTest, ToUnicodeCMapbfrange) { - using namespace pdfengine::fonts::pdf_fonts; - - ToUnicodeCMap cmap; - - std::string cmapStream = - "begincmap\n" - "2 beginbfrange\n" - "<0001> <0005> <0041>\n" - "<0010> <0012> [<0061> <0062> <0063>]\n" - "endbfrange\n" - "endcmap\n"; - - ASSERT_TRUE(cmap.parseCMapStream(cmapStream)); - - EXPECT_EQ(cmap.decode(1), 0x0041); - EXPECT_EQ(cmap.decode(3), 0x0043); - EXPECT_EQ(cmap.decode(5), 0x0045); - - EXPECT_EQ(cmap.decode(0x10), 0x0061); - EXPECT_EQ(cmap.decode(0x11), 0x0062); - EXPECT_EQ(cmap.decode(0x12), 0x0063); -} - -TEST(EncodingTest, ToUnicodeMalformedCMap) { - using namespace pdfengine::fonts::pdf_fonts; - - ToUnicodeCMap cmap; - - std::string garbageStream = "This is a garbage string with no valid CMap elements"; - EXPECT_FALSE(cmap.parseCMapStream(garbageStream)); - - std::string partialStream = - "begincmap\n" - "beginbfchar\n" - "<0001> <0041>\n" - "<0002> /invalid\n" - "<0003> <0043>\n" - "endbfchar\n" - "endcmap\n"; - - EXPECT_TRUE(cmap.parseCMapStream(partialStream)); - EXPECT_EQ(cmap.decode(1), 0x0041); - EXPECT_EQ(cmap.decode(2), 0); - EXPECT_EQ(cmap.decode(3), 0x0043); -} - -TEST(EncodingTest, FontLoaderWithEncoding) { - std::string fontPath = getSystemFontPath(); - if (fontPath.empty()) { - GTEST_SKIP() << "No system font found to run PDF font loader with encoding test."; - } - - std::ifstream file(fontPath, std::ios::binary | std::ios::ate); - ASSERT_TRUE(file.is_open()); - std::streamsize size = file.tellg(); - file.seekg(0, std::ios::beg); - std::vector buffer(size); - ASSERT_TRUE(file.read(reinterpret_cast(buffer.data()), size)); - - auto encoding = std::make_unique( - pdfengine::fonts::pdf_fonts::SimpleEncodingType::WinAnsi - ); - - auto pdfFont = pdfengine::fonts::pdf_fonts::FontLoader::loadTrueTypeFromMemory( - "Arial-With-Encoding", buffer, nullptr, std::move(encoding) - ); - - ASSERT_NE(pdfFont, nullptr); - EXPECT_EQ(pdfFont->getBaseFont(), "Arial-With-Encoding"); - - const auto* retrievedEncoding = pdfFont->getEncoding(); - ASSERT_NE(retrievedEncoding, nullptr); - - EXPECT_EQ(retrievedEncoding->decode(128), 0x20AC); -} - -TEST(Type1FontTest, EmbeddedType1FontLoading) { - std::string fontPath = getSystemFontPath(); - if (fontPath.empty()) { - GTEST_SKIP() << "No system font found to run embedded Type1 test."; - } - - std::ifstream file(fontPath, std::ios::binary | std::ios::ate); - ASSERT_TRUE(file.is_open()); - std::streamsize size = file.tellg(); - file.seekg(0, std::ios::beg); - std::vector buffer(size); - ASSERT_TRUE(file.read(reinterpret_cast(buffer.data()), size)); - - auto font = pdfengine::fonts::pdf_fonts::FontLoader::loadType1FromMemory("LegacyType1", buffer); - ASSERT_NE(font, nullptr); - EXPECT_EQ(font->getBaseFont(), "LegacyType1"); - EXPECT_EQ(font->getType(), pdfengine::fonts::pdf_fonts::FontType::Type1); - EXPECT_TRUE(font->isEmbedded()); -} - -TEST(Type1FontTest, NonEmbeddedType1SystemFallback) { - auto fontHelv = pdfengine::fonts::pdf_fonts::FontLoader::loadType1SystemFallback("Helvetica"); - ASSERT_NE(fontHelv, nullptr); - EXPECT_EQ(fontHelv->getBaseFont(), "Helvetica"); - EXPECT_EQ(fontHelv->getType(), pdfengine::fonts::pdf_fonts::FontType::Type1); - EXPECT_FALSE(fontHelv->isEmbedded()); - - auto fontTimes = pdfengine::fonts::pdf_fonts::FontLoader::loadType1SystemFallback("Times-BoldItalic"); - ASSERT_NE(fontTimes, nullptr); - EXPECT_EQ(fontTimes->getBaseFont(), "Times-BoldItalic"); - EXPECT_FALSE(fontTimes->isEmbedded()); - - auto fontCourier = pdfengine::fonts::pdf_fonts::FontLoader::loadType1SystemFallback("Courier-Oblique"); - ASSERT_NE(fontCourier, nullptr); - EXPECT_EQ(fontCourier->getBaseFont(), "Courier-Oblique"); - EXPECT_FALSE(fontCourier->isEmbedded()); - - auto fontUnknown = pdfengine::fonts::pdf_fonts::FontLoader::loadType1SystemFallback("UnknownLegacyFont"); - ASSERT_NE(fontUnknown, nullptr); - EXPECT_EQ(fontUnknown->getBaseFont(), "UnknownLegacyFont"); - EXPECT_FALSE(fontUnknown->isEmbedded()); -} - -TEST(CIDFontTest, CompositeFontInitializationAndTypes) { - using namespace pdfengine::fonts::pdf_fonts; - - auto desc = std::make_unique(); - desc->setFontName("SimSun-Descriptor"); - - CIDFont font0("SimSun", FontType::CIDFontType0, false, std::move(desc)); - EXPECT_EQ(font0.getBaseFont(), "SimSun"); - EXPECT_EQ(font0.getType(), FontType::CIDFontType0); - EXPECT_FALSE(font0.isEmbedded()); - EXPECT_EQ(font0.getDescriptor()->getFontName(), "SimSun-Descriptor"); - - CIDFont font2("MS-Gothic", FontType::CIDFontType2, true); - EXPECT_EQ(font2.getBaseFont(), "MS-Gothic"); - EXPECT_EQ(font2.getType(), FontType::CIDFontType2); - EXPECT_TRUE(font2.isEmbedded()); -} - -TEST(CIDFontTest, CIDToGIDTranslations) { - using namespace pdfengine::fonts::pdf_fonts; - - CIDFont font("SimSun", FontType::CIDFontType2, false); - - EXPECT_TRUE(font.isIdentityMap()); - EXPECT_EQ(font.mapCIDToGID(100), 100); - EXPECT_EQ(font.mapCIDToGID(5000), 5000); - - std::unordered_map customMap = { - {10, 100}, - {20, 200}, - {30, 300} - }; - font.setCIDToGIDMap(customMap); - EXPECT_FALSE(font.isIdentityMap()); - EXPECT_EQ(font.mapCIDToGID(10), 100); - EXPECT_EQ(font.mapCIDToGID(20), 200); - EXPECT_EQ(font.mapCIDToGID(30), 300); - EXPECT_EQ(font.mapCIDToGID(40), 0); - - font.setIdentityCIDToGIDMap(); - EXPECT_TRUE(font.isIdentityMap()); - EXPECT_EQ(font.mapCIDToGID(10), 10); -} - -TEST(CIDFontTest, NonEmbeddedCIDFontSystemFallback) { - using namespace pdfengine::fonts::pdf_fonts; - - auto fontSimSun = FontLoader::loadCIDFontSystemFallback("SimSun", FontType::CIDFontType2); - ASSERT_NE(fontSimSun, nullptr); - EXPECT_EQ(fontSimSun->getBaseFont(), "SimSun"); - EXPECT_EQ(fontSimSun->getType(), FontType::CIDFontType2); - EXPECT_FALSE(fontSimSun->isEmbedded()); - - auto fontGothic = FontLoader::loadCIDFontSystemFallback("HeiseiMin-W3", FontType::CIDFontType0); - ASSERT_NE(fontGothic, nullptr); - EXPECT_EQ(fontGothic->getBaseFont(), "HeiseiMin-W3"); - EXPECT_EQ(fontGothic->getType(), FontType::CIDFontType0); - EXPECT_FALSE(fontGothic->isEmbedded()); - - HbShaper shaper; - auto glyphs = shaper.shapeRun("日本語漢字", fontGothic->getFontFace(), 16); - EXPECT_FALSE(glyphs.empty()); -} - -TEST(FontFallbackTest, SingletonInstanceIsUnique) { - using namespace pdfengine::fonts::pdf_fonts; - - auto& instance1 = FontFallback::getInstance(); - auto& instance2 = FontFallback::getInstance(); - EXPECT_EQ(&instance1, &instance2); -} - -TEST(FontFallbackTest, StandardFontFallbacks) { - using namespace pdfengine::fonts::pdf_fonts; - - auto& fallback = FontFallback::getInstance(); - - std::string path1 = fallback.getFallbackFontPath("Helvetica"); - EXPECT_FALSE(path1.empty()); - EXPECT_TRUE(std::filesystem::exists(path1)); -#if defined(_WIN32) - EXPECT_TRUE(containsCI(path1, "arial") || containsCI(path1, "liberationsans")); -#elif defined(__APPLE__) - EXPECT_TRUE(containsCI(path1, "arial") || containsCI(path1, "helvetica") || - containsCI(path1, "liberationsans")); -#else - EXPECT_TRUE(containsCI(path1, "liberationsans") || containsCI(path1, "dejavusans")); -#endif - - std::string path2 = fallback.getFallbackFontPath("Times-Roman"); - EXPECT_FALSE(path2.empty()); - EXPECT_TRUE(std::filesystem::exists(path2)); -#if defined(_WIN32) - EXPECT_TRUE(containsCI(path2, "times") || containsCI(path2, "liberationserif") || - containsCI(path2, "tinos")); -#elif defined(__APPLE__) - EXPECT_TRUE(containsCI(path2, "times") || containsCI(path2, "liberationserif") || - containsCI(path2, "tinos")); -#else - EXPECT_TRUE(containsCI(path2, "liberationserif") || containsCI(path2, "dejavuserif") || - containsCI(path2, "tinos")); -#endif -} - -TEST(FontFallbackTest, StyleModifierResolutions) { - using namespace pdfengine::fonts::pdf_fonts; - - auto& fallback = FontFallback::getInstance(); - - std::string pathBold = fallback.getFallbackFontPath("Helvetica", true, false); - EXPECT_FALSE(pathBold.empty()); - EXPECT_TRUE(std::filesystem::exists(pathBold)); -#if defined(_WIN32) - EXPECT_TRUE(containsCI(pathBold, "arialbd") || containsCI(pathBold, "liberationsans-bold")); -#endif - - std::string pathBoldItalic = fallback.getFallbackFontPath("Times", true, true); - EXPECT_FALSE(pathBoldItalic.empty()); - EXPECT_TRUE(std::filesystem::exists(pathBoldItalic)); -#if defined(_WIN32) - EXPECT_TRUE(containsCI(pathBoldItalic, "timesbi") || - containsCI(pathBoldItalic, "liberationserif-bolditalic") || - containsCI(pathBoldItalic, "tinos-bolditalic")); -#endif -} - -TEST(FontFallbackTest, CustomFallbackRegistration) { - using namespace pdfengine::fonts::pdf_fonts; - - auto& fallback = FontFallback::getInstance(); - fallback.resetToDefaults(); - - std::string standardPath = fallback.getFallbackFontPath("Helvetica"); - - std::filesystem::path overrideFont = - std::filesystem::temp_directory_path() / "pdfengine_custom_fallback.ttf"; - { std::ofstream(overrideFont) << "stub-font"; } - - fallback.registerFallback("helvetica", overrideFont.string()); - - std::string overridenPath = fallback.getFallbackFontPath("Helvetica"); - EXPECT_EQ(overridenPath, overrideFont.string()); - - fallback.resetToDefaults(); - std::string restoredPath = fallback.getFallbackFontPath("Helvetica"); - EXPECT_EQ(restoredPath, standardPath); - - std::filesystem::remove(overrideFont); -} - -TEST(FontSubsetTest, SubsetTagParsingAndStripping) { - using namespace pdfengine::fonts::pdf_fonts; - - std::string subsetName = "KTJHQO+Arial"; - - EXPECT_TRUE(FontSubset::hasSubsetPrefix(subsetName)); - EXPECT_EQ(FontSubset::getSubsetPrefix(subsetName), "KTJHQO"); - EXPECT_EQ(FontSubset::stripSubsetPrefix(subsetName), "Arial"); - - std::string normalName = "Arial"; - EXPECT_FALSE(FontSubset::hasSubsetPrefix(normalName)); - EXPECT_EQ(FontSubset::getSubsetPrefix(normalName), ""); - EXPECT_EQ(FontSubset::stripSubsetPrefix(normalName), "Arial"); -} - -TEST(FontSubsetTest, PrefixFormatValidation) { - using namespace pdfengine::fonts::pdf_fonts; - - EXPECT_TRUE(FontSubset::hasSubsetPrefix("ABCDEF+Helvetica")); - - EXPECT_FALSE(FontSubset::hasSubsetPrefix("abcDEF+Helvetica")); - EXPECT_FALSE(FontSubset::hasSubsetPrefix("ABCdef+Helvetica")); - - EXPECT_FALSE(FontSubset::hasSubsetPrefix("ABC123+Helvetica")); - EXPECT_FALSE(FontSubset::hasSubsetPrefix("ABCDE_+Helvetica")); - - EXPECT_FALSE(FontSubset::hasSubsetPrefix("ABCDE+Helvetica")); - EXPECT_FALSE(FontSubset::hasSubsetPrefix("ABCDEFG+Helvetica")); -} - -TEST(FontSubsetTest, GIDRemappingTranslations) { - using namespace pdfengine::fonts::pdf_fonts; - - FontSubset subset("KTJHQO+Arial"); - EXPECT_TRUE(subset.isSubset()); - EXPECT_EQ(subset.getFullFontName(), "KTJHQO+Arial"); - EXPECT_EQ(subset.getBaseFontName(), "Arial"); - EXPECT_EQ(subset.getPrefix(), "KTJHQO"); - - EXPECT_EQ(subset.mapSubsetToOriginal(5), 5); - EXPECT_FALSE(subset.hasGlyphMapping(5)); - - subset.addGlyphMapping(1, 41); - subset.addGlyphMapping(2, 42); - subset.addGlyphMapping(3, 43); - - EXPECT_EQ(subset.getMappingCount(), 3u); - EXPECT_TRUE(subset.hasGlyphMapping(1)); - EXPECT_TRUE(subset.hasGlyphMapping(2)); - EXPECT_EQ(subset.mapSubsetToOriginal(1), 41); - EXPECT_EQ(subset.mapSubsetToOriginal(2), 42); - EXPECT_EQ(subset.mapSubsetToOriginal(3), 43); - EXPECT_EQ(subset.mapSubsetToOriginal(4), 4); -} - -TEST(FontSubsetTest, CoreFontSubsettingIntegration) { - using namespace pdfengine::fonts::pdf_fonts; - - TrueTypeFont fontTT("KTJHQO+Arial", false); - const auto* ttSubset = fontTT.getSubsetInfo(); - ASSERT_NE(ttSubset, nullptr); - EXPECT_TRUE(ttSubset->isSubset()); - EXPECT_EQ(ttSubset->getBaseFontName(), "Arial"); - EXPECT_EQ(ttSubset->getPrefix(), "KTJHQO"); - - Type1Font fontT1("SUBSET+Courier", false); - const auto* t1Subset = fontT1.getSubsetInfo(); - ASSERT_NE(t1Subset, nullptr); - EXPECT_TRUE(t1Subset->isSubset()); - EXPECT_EQ(t1Subset->getBaseFontName(), "Courier"); - EXPECT_EQ(t1Subset->getPrefix(), "SUBSET"); - - CIDFont fontCID("CJKTAG+SimSun", FontType::CIDFontType2, false); - const auto* cidSubset = fontCID.getSubsetInfo(); - ASSERT_NE(cidSubset, nullptr); - EXPECT_TRUE(cidSubset->isSubset()); - EXPECT_EQ(cidSubset->getBaseFontName(), "SimSun"); - EXPECT_EQ(cidSubset->getPrefix(), "CJKTAG"); -} - -TEST(TextExtractionLayerTest, SimpleCharacterDecoding) { - using namespace pdfengine::fonts::pdf_fonts; - - auto font = FontLoader::loadType1SystemFallback("Helvetica"); - ASSERT_NE(font, nullptr); - - EXPECT_EQ(font->decodeToUnicode(65), 65u); - EXPECT_EQ(font->decodeToUnicode(97), 97u); - EXPECT_EQ(font->decodeToUnicode(48), 48u); -} - -TEST(TextExtractionLayerTest, EncodingAndToUnicodeDecoding) { - using namespace pdfengine::fonts::pdf_fonts; - - auto baseEncoding = std::make_unique(SimpleEncodingType::WinAnsi); - auto customEnc = std::make_unique(std::move(baseEncoding)); - customEnc->addDifference(128, "euro"); - - auto font = FontLoader::loadType1SystemFallback("Helvetica", nullptr, std::move(customEnc)); - ASSERT_NE(font, nullptr); - - EXPECT_EQ(font->decodeToUnicode(128), 0x20ACu); - - auto cmap = std::make_unique(); - cmap->addMapping(1, 0x0041); - cmap->addMapping(2, 0x0042); - cmap->addMapping(3, 0x20AC); - - auto fontCMap = FontLoader::loadType1SystemFallback("Helvetica", nullptr, std::move(cmap)); - ASSERT_NE(fontCMap, nullptr); - EXPECT_EQ(fontCMap->decodeToUnicode(1), 0x0041u); - EXPECT_EQ(fontCMap->decodeToUnicode(2), 0x0042u); - EXPECT_EQ(fontCMap->decodeToUnicode(3), 0x20ACu); -} - -TEST(TextExtractionLayerTest, SubsetFontTextExtraction) { - using namespace pdfengine::fonts::pdf_fonts; - - auto font = FontLoader::loadType1SystemFallback("KTJHQO+Helvetica"); - ASSERT_NE(font, nullptr); - - const auto* subsetConst = font->getSubsetInfo(); - ASSERT_NE(subsetConst, nullptr); - auto* subset = const_cast(subsetConst); - - FT_Face face = font->getFontFace().getFace(); - ASSERT_NE(face, nullptr); - FT_UInt originalGid = FT_Get_Char_Index(face, 'A'); - ASSERT_GT(originalGid, 0u); - - subset->addGlyphMapping(5, originalGid); - - EXPECT_EQ(font->decodeToUnicode(5), 65u); -} - -TEST(TextExtractionLayerTest, CIDFontTextExtraction) { - using namespace pdfengine::fonts::pdf_fonts; - - auto font = FontLoader::loadCIDFontSystemFallback("SimSun", FontType::CIDFontType2); - ASSERT_NE(font, nullptr); - - CIDFont* cidFont = dynamic_cast(font.get()); - ASSERT_NE(cidFont, nullptr); - - FT_Face face = cidFont->getFontFace().getFace(); - ASSERT_NE(face, nullptr); - FT_UInt gid65 = 65; - FT_ULong expectedChar65 = 0; - - FT_UInt gindex; - FT_ULong charcode = FT_Get_First_Char(face, &gindex); - while (gindex != 0) { - if (gindex == gid65) { - expectedChar65 = charcode; - break; - } - charcode = FT_Get_Next_Char(face, charcode, &gindex); - } - - if (expectedChar65 != 0) { - EXPECT_EQ(cidFont->decodeToUnicode(65), expectedChar65); - } else { - EXPECT_EQ(cidFont->decodeToUnicode(65), 65u); - } - - FT_UInt gidA = FT_Get_Char_Index(face, 'A'); - if (gidA > 0) { - std::unordered_map cidToGid = { - {500, gidA} - }; - cidFont->setCIDToGIDMap(cidToGid); - - EXPECT_EQ(cidFont->decodeToUnicode(500), 65u); - } -} - -TEST(TextExtractionLayerTest, StringUtf8Conversion) { - using namespace pdfengine::fonts::pdf_fonts; - - auto font = FontLoader::loadType1SystemFallback("Helvetica"); - ASSERT_NE(font, nullptr); - - std::vector codes = {65, 66, 67}; - EXPECT_EQ(font->decodeStringToUnicode(codes), "ABC"); - - auto cmap = std::make_unique(); - cmap->addMapping(10, 0x65E5); - cmap->addMapping(11, 0x672C); - cmap->addMapping(12, 0x8A9E); - - auto fontCMap = FontLoader::loadType1SystemFallback("Helvetica", nullptr, std::move(cmap)); - ASSERT_NE(fontCMap, nullptr); - - std::vector cjkCodes = {10, 11, 12}; - std::string decodedCjk = fontCMap->decodeStringToUnicode(cjkCodes); - EXPECT_EQ(decodedCjk, "日本語"); -} - -TEST(FontSubstitutionAndWidthsTest, WidthMatchingAndSubstitutionVerification) { - using namespace pdfengine::fonts::pdf_fonts; - - auto font = FontLoader::loadType1SystemFallback("Helvetica"); - ASSERT_NE(font, nullptr); - - EXPECT_FALSE(font->hasWidths()); - - std::vector pdfWidths = { 600.0, 500.0, 550.0, 400.0 }; - font->setWidths(65, 68, pdfWidths); - - EXPECT_TRUE(font->hasWidths()); - - double fontSize = 12.0; - - double expectedWidthA = (600.0 / 1000.0) * fontSize; - double expectedWidthB = (500.0 / 1000.0) * fontSize; - double expectedWidthC = (550.0 / 1000.0) * fontSize; - double expectedWidthD = (400.0 / 1000.0) * fontSize; - - EXPECT_NEAR(font->getCharWidth(65, fontSize), expectedWidthA, 1e-5); - EXPECT_NEAR(font->getCharWidth(66, fontSize), expectedWidthB, 1e-5); - EXPECT_NEAR(font->getCharWidth(67, fontSize), expectedWidthC, 1e-5); - EXPECT_NEAR(font->getCharWidth(68, fontSize), expectedWidthD, 1e-5); - - EXPECT_EQ(font->getCharWidth(999, fontSize), 0.0); -} - -TEST(CIDAdvancedMappingTest, IdentityVSupport) { - using namespace pdfengine::fonts::pdf_fonts; - PredefinedEncoding identityV(SimpleEncodingType::Identity_V); - EXPECT_EQ(identityV.getType(), SimpleEncodingType::Identity_V); - EXPECT_EQ(identityV.decode(65), 65); - EXPECT_EQ(identityV.decode(1000), 1000); -} - -TEST(CIDAdvancedMappingTest, VerticalMetricsResolution) { - using namespace pdfengine::fonts::pdf_fonts; - - auto font = FontLoader::loadType1SystemFallback("Helvetica"); - ASSERT_NE(font, nullptr); - - double fontSize = 12.0; - EXPECT_EQ(font->isVertical(), false); - EXPECT_EQ(font->getCharHeight(65, fontSize), fontSize); - - font->setVertical(true); - EXPECT_EQ(font->isVertical(), true); - - std::vector verticalAdvances = { 1000.0, 800.0, 900.0 }; - font->setVerticalMetrics(65, 67, verticalAdvances); - EXPECT_TRUE(font->hasVerticalMetrics()); - - EXPECT_NEAR(font->getCharHeight(65, fontSize), 12.0, 1e-5); - EXPECT_NEAR(font->getCharHeight(66, fontSize), 9.6, 1e-5); - EXPECT_NEAR(font->getCharHeight(67, fontSize), 10.8, 1e-5); - EXPECT_NEAR(font->getCharHeight(999, fontSize), 12.0, 1e-5); -} - -TEST(CIDAdvancedMappingTest, CjkCollectionResolutionDB) { - using namespace pdfengine::fonts::pdf_fonts; - - EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Japan1", 1010), 0x3041); - EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Japan1", 1092), 0x3093); - - EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Japan1", 1125), 0x30A1); - EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Japan1", 1205), 0x30F6); - - EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Japan1", 1206), 0x4E00); - - EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-GB1", 1), 0x3000); - EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-GB1", 2), 0x3001); -} - -TEST(CIDAdvancedMappingTest, HarfBuzzVerticalShapingSignature) { - using namespace pdfengine::fonts; - - EXPECT_EQ(static_cast(HbShaper::WritingMode::Horizontal), 0); - EXPECT_EQ(static_cast(HbShaper::WritingMode::Vertical), 1); -} - -} \ No newline at end of file diff --git a/engine/tests/fonts_test_helpers.hpp b/engine/tests/fonts_test_helpers.hpp new file mode 100644 index 0000000..92a666c --- /dev/null +++ b/engine/tests/fonts_test_helpers.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include "fonts/face/font_face.hpp" +#include "fonts/shaping/hb_shaper.hpp" +#include "fonts/cache/glyph_cache.hpp" +#include "fonts/pdf_fonts/font.hpp" +#include "fonts/pdf_fonts/types/truetype_font.hpp" +#include "fonts/pdf_fonts/types/type1_font.hpp" +#include "fonts/pdf_fonts/types/cid_font.hpp" +#include "fonts/pdf_fonts/font_loader.hpp" +#include "fonts/pdf_fonts/encoding/encoding.hpp" +#include "fonts/pdf_fonts/encoding/cjk_collection_db.hpp" +#include "fonts/pdf_fonts/font_fallback.hpp" +#include "fonts/pdf_fonts/font_subset.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +bool containsCI(const std::string& haystack, const std::string& needle) { + auto it = std::search( + haystack.begin(), haystack.end(), needle.begin(), needle.end(), + [](char a, char b) { + return std::tolower(static_cast(a)) == + std::tolower(static_cast(b)); + }); + return it != haystack.end(); +} + +bool saveGlyphAsPGM(const pdfengine::fonts::GlyphBitmap& bitmap, const std::string& filename) { + if (bitmap.width == 0 || bitmap.height == 0 || bitmap.pixels.empty()) { + return false; + } + std::ofstream out(filename, std::ios::binary); + if (!out) { + return false; + } + out << "P5\n" << bitmap.width << " " << bitmap.height << "\n255\n"; + out.write(reinterpret_cast(bitmap.pixels.data()), bitmap.pixels.size()); + return true; +} + +std::string getSystemFontPath() { +#if defined(_WIN32) + std::vector paths = { + "C:\\Windows\\Fonts\\arial.ttf", + "C:\\Windows\\Fonts\\consola.ttf", + "C:\\Windows\\Fonts\\tahoma.ttf" + }; +#elif defined(__APPLE__) + std::vector paths = { + "/Library/Fonts/Arial.ttf", + "/System/Library/Fonts/Geneva.ttf", + "/System/Library/Fonts/Helvetica.ttc", + "/System/Library/Fonts/Supplemental/Arial.ttf" + }; +#else + std::vector paths = { + "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", + "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf", + "/usr/share/fonts/truetype/freefont/FreeSans.ttf" + }; +#endif + + for (const auto& path : paths) { + if (std::filesystem::exists(path)) { + return path; + } + } + return ""; +} + +} diff --git a/engine/tests/page_render_test.cpp b/engine/tests/page_render_test.cpp new file mode 100644 index 0000000..fc6eaab --- /dev/null +++ b/engine/tests/page_render_test.cpp @@ -0,0 +1,174 @@ +#include "document_test_helpers.hpp" + +namespace pdfengine { + +TEST(PageRenderTest, RenderAtDifferentDPI) { + SKIP_IF_NO_PDFIUM(); + auto path = getCorpusPath("basic", "hello_world.pdf"); + if (!std::filesystem::exists(path)) { + GTEST_SKIP() << "hello_world.pdf not found in corpus."; + } + + auto docRes = PdfDocument::loadFromFile(path.string()); + ASSERT_TRUE(docRes.has_value()); + auto pageRes = (*docRes)->getPage(0); + ASSERT_TRUE(pageRes.has_value()); + + auto page = *pageRes; + double ptWidth = page->width(); + double ptHeight = page->height(); + EXPECT_GT(ptWidth, 0.0); + EXPECT_GT(ptHeight, 0.0); + + std::vector dpis = {72, 144, 288}; + for (int dpi : dpis) { + auto imgRes = page->render(dpi); + ASSERT_TRUE(imgRes.has_value()) << "Failed to render at DPI " << dpi; + + double scale = dpi / 72.0; + int expectedW = static_cast(ptWidth * scale); + int expectedH = static_cast(ptHeight * scale); + + EXPECT_EQ(imgRes->width, expectedW); + EXPECT_EQ(imgRes->height, expectedH); + + ASSERT_GE(imgRes->data.size(), 8U); + EXPECT_EQ(imgRes->data[0], 0x89); + EXPECT_EQ(imgRes->data[1], 'P'); + EXPECT_EQ(imgRes->data[2], 'N'); + EXPECT_EQ(imgRes->data[3], 'G'); + EXPECT_EQ(imgRes->data[4], 0x0D); + EXPECT_EQ(imgRes->data[5], 0x0A); + EXPECT_EQ(imgRes->data[6], 0x1A); + EXPECT_EQ(imgRes->data[7], 0x0A); + } +} + +TEST(PageRenderTest, InvalidPageIndexReturnsPageOutOfBounds) { + SKIP_IF_NO_PDFIUM(); + auto path = getCorpusPath("basic", "hello_world.pdf"); + if (!std::filesystem::exists(path)) { + GTEST_SKIP() << "hello_world.pdf not found in corpus."; + } + + auto docRes = PdfDocument::loadFromFile(path.string()); + ASSERT_TRUE(docRes.has_value()); + + auto pageRes = (*docRes)->getPage(1); + ASSERT_FALSE(pageRes.has_value()); + EXPECT_EQ(pageRes.error(), EngineError::PageOutOfBounds); + + auto pageResNeg = (*docRes)->getPage(-1); + ASSERT_FALSE(pageResNeg.has_value()); + EXPECT_EQ(pageResNeg.error(), EngineError::PageOutOfBounds); +} + +TEST(CoordinateTransformTest, PageToDeviceAndBackRoundtrip) { + SKIP_IF_NO_PDFIUM(); + auto path = getCorpusPath("basic", "hello_world.pdf"); + if (!std::filesystem::exists(path)) { + GTEST_SKIP() << "hello_world.pdf not found in corpus."; + } + + auto docRes = PdfDocument::loadFromFile(path.string()); + ASSERT_TRUE(docRes.has_value()); + auto pageRes = (*docRes)->getPage(0); + ASSERT_TRUE(pageRes.has_value()); + + auto page = *pageRes; + + int deviceW = 1024; + int deviceH = 768; + std::vector rotations = {0, 90, 180, 270}; + + std::vector testPoints = { + {0.0, 0.0}, + {50.0, 50.0}, + {100.0, 200.0}, + {page->width() / 2.0, page->height() / 2.0}, + {page->width() - 10.0, page->height() - 10.0} + }; + + for (int rotation : rotations) { + for (const auto& pt : testPoints) { + auto devPt = page->pageToDevice(pt, deviceW, deviceH, rotation); + auto pagePt = page->deviceToPage(devPt, deviceW, deviceH, rotation); + + EXPECT_NEAR(pt.x, pagePt.x, 2.0) << "Failed roundtrip for x at rotation " << rotation; + EXPECT_NEAR(pt.y, pagePt.y, 2.0) << "Failed roundtrip for y at rotation " << rotation; + } + } +} + +TEST(TextExtractionTest, ExtractSimpleText) { + SKIP_IF_NO_PDFIUM(); + auto path = getCorpusPath("basic", "hello_world.pdf"); + if (!std::filesystem::exists(path)) { + GTEST_SKIP() << "hello_world.pdf not found in corpus."; + } + + auto docRes = PdfDocument::loadFromFile(path.string()); + ASSERT_TRUE(docRes.has_value()); + auto pageRes = (*docRes)->getPage(0); + ASSERT_TRUE(pageRes.has_value()); + + auto textRes = (*pageRes)->extractText(); + ASSERT_TRUE(textRes.has_value()); + + std::string text = *textRes; + EXPECT_NE(text.find("Hello"), std::string::npos); + EXPECT_NE(text.find("world"), std::string::npos); +} + +TEST(TextExtractionTest, ExtractUtf8ExtendedText) { + SKIP_IF_NO_PDFIUM(); + auto path = getCorpusPath("fonts", "latin_extended.pdf"); + if (!std::filesystem::exists(path)) { + GTEST_SKIP() << "latin_extended.pdf not found in corpus."; + } + + auto docRes = PdfDocument::loadFromFile(path.string()); + ASSERT_TRUE(docRes.has_value()); + auto pageRes = (*docRes)->getPage(0); + ASSERT_TRUE(pageRes.has_value()); + + auto textRes = (*pageRes)->extractText(); + ASSERT_TRUE(textRes.has_value()); + + std::string text = *textRes; + EXPECT_FALSE(text.empty()); +} + +TEST(TextExtractionTest, ExtractTextWithBounds) { + SKIP_IF_NO_PDFIUM(); + auto path = getCorpusPath("basic", "hello_world.pdf"); + if (!std::filesystem::exists(path)) { + GTEST_SKIP() << "hello_world.pdf not found in corpus."; + } + + auto docRes = PdfDocument::loadFromFile(path.string()); + ASSERT_TRUE(docRes.has_value()); + auto pageRes = (*docRes)->getPage(0); + ASSERT_TRUE(pageRes.has_value()); + + auto boundsRes = (*pageRes)->extractTextWithBounds(); + ASSERT_TRUE(boundsRes.has_value()); + + const auto& glyphs = *boundsRes; + ASSERT_FALSE(glyphs.empty()); + + bool found_hello = false; + for (const auto& glyph : glyphs) { + EXPECT_FALSE(glyph.text.empty()); + if (glyph.text != " " && glyph.text != "\r" && glyph.text != "\n" && glyph.text != "\t") { + EXPECT_GT(glyph.w, 0.0); + EXPECT_GT(glyph.h, 0.0); + } + EXPECT_GT(glyph.fontSize, 0.0); + if (glyph.text == "H" || glyph.text == "e" || glyph.text == "l" || glyph.text == "o") { + found_hello = true; + } + } + EXPECT_TRUE(found_hello); +} +} diff --git a/engine/tests/text_encoding_test.cpp b/engine/tests/text_encoding_test.cpp new file mode 100644 index 0000000..208764f --- /dev/null +++ b/engine/tests/text_encoding_test.cpp @@ -0,0 +1,74 @@ +#include "document_test_helpers.hpp" + +namespace pdfengine { + +TEST(UtfConversionTest, EmojiSurrogatePairs) { + std::string utf8_grinning = "\xF0\x9F\x98\x80"; + auto utf16 = pdfengine::parser::utf8_to_utf16le(utf8_grinning); + ASSERT_EQ(utf16.size(), 3); + EXPECT_EQ(utf16[0], 0xD83D); + EXPECT_EQ(utf16[1], 0xDE00); + EXPECT_EQ(utf16[2], 0x0000); + + std::string utf8_out = pdfengine::parser::utf16le_to_utf8(reinterpret_cast(utf16.data()), utf16.size() - 1); + EXPECT_EQ(utf8_out, utf8_grinning); + + std::string utf8_rocket = "\xF0\x9F\x9A\x80"; + utf16 = pdfengine::parser::utf8_to_utf16le(utf8_rocket); + ASSERT_EQ(utf16.size(), 3); + EXPECT_EQ(utf16[0], 0xD83D); + EXPECT_EQ(utf16[1], 0xDE80); + EXPECT_EQ(utf16[2], 0x0000); + + utf8_out = pdfengine::parser::utf16le_to_utf8(reinterpret_cast(utf16.data()), utf16.size() - 1); + EXPECT_EQ(utf8_out, utf8_rocket); +} + +TEST(UtfConversionTest, CJKExtensionB) { + std::string utf8_cjk = "\xF0\xA0\x80\x80"; + auto utf16 = pdfengine::parser::utf8_to_utf16le(utf8_cjk); + ASSERT_EQ(utf16.size(), 3); + EXPECT_EQ(utf16[0], 0xD840); + EXPECT_EQ(utf16[1], 0xDC00); + EXPECT_EQ(utf16[2], 0x0000); + + std::string utf8_out = pdfengine::parser::utf16le_to_utf8(reinterpret_cast(utf16.data()), utf16.size() - 1); + EXPECT_EQ(utf8_out, utf8_cjk); +} + +TEST(UtfConversionTest, RoundtripMixed) { + std::string mixed = "A\xF0\x9F\x98\x80""B\xF0\xA0\x80\x80""C"; + auto utf16 = pdfengine::parser::utf8_to_utf16le(mixed); + std::string mixed_out = pdfengine::parser::utf16le_to_utf8(reinterpret_cast(utf16.data()), utf16.size() - 1); + EXPECT_EQ(mixed_out, mixed); +} + + +TEST(CjkResolutionTest, AdobeCNS1) { + using pdfengine::fonts::pdf_fonts::CjkCollectionDB; + + EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-CNS1", 100), 0x4E00); + EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-CNS1", 112), 0x4E2D); + EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-CNS1", 130), 0x4ED7); + + EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-CNS1", 131), 0x4ED8); + EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-CNS1", 135), 0x4EDF); + EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-CNS1", 140), 0x4F01); + + EXPECT_EQ(CjkCollectionDB::resolveCID("Identity-H-CNS1", 137), 0x4EE3); +} + +TEST(CjkResolutionTest, AdobeKorea1) { + using pdfengine::fonts::pdf_fonts::CjkCollectionDB; + + EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Korea1", 101), 0xAC00); + EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Korea1", 119), 0xAC1C); + EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Korea1", 150), 0xAC90); + + EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Korea1", 151), 0xAC94); + EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Korea1", 156), 0xACA9); + EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Korea1", 160), 0xACBD); + + EXPECT_EQ(CjkCollectionDB::resolveCID("UniKS-UTF16-H-Korea1", 153), 0xACA0); +} +} diff --git a/frontend/public/pdfium-engine.mjs b/frontend/public/pdfium-engine.mjs index 4e1ce70..53ecfa8 100644 --- a/frontend/public/pdfium-engine.mjs +++ b/frontend/public/pdfium-engine.mjs @@ -1,2 +1,2 @@ -async function Module(moduleArg={}){var moduleRtn;var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";if(ENVIRONMENT_IS_NODE){const{createRequire}=await import("node:module");var require=createRequire(import.meta.url)}var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var _scriptName=import.meta.url;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("node:fs");if(_scriptName.startsWith("file:")){scriptDirectory=require("node:path").dirname(require("node:url").fileURLToPath(_scriptName))+"/"}readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");return ret};if(process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{readAsync=async url=>{var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var EXITSTATUS;var isFileURI=filename=>filename.startsWith("file://");class EmscriptenEH{}class EmscriptenSjLj extends EmscriptenEH{}var readyPromiseResolve,readyPromiseReject;var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b);HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);HEAPU16=new Uint16Array(b);HEAP32=new Int32Array(b);HEAPU32=new Uint32Array(b);HEAPF32=new Float32Array(b);HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.initialized)FS.init();TTY.init();wasmExports["R"]();FS.ignorePermissions=false}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what=`Aborted(${what})`;err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject?.(e);throw e}var wasmBinaryFile;function findWasmBinary(){if(Module["locateFile"]){return locateFile("pdfengine.wasm")}return new URL("pdfengine.wasm",import.meta.url).href}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!ENVIRONMENT_IS_NODE){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){return new Promise((resolve,reject)=>{Module["instantiateWasm"](info,(inst,mod)=>{resolve(receiveInstance(inst,mod))})})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var HEAP16;var HEAP32;var HEAP64;var HEAP8;var HEAPF32;var HEAPF64;var HEAPU16;var HEAPU32;var HEAPU64;var HEAPU8;var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var addOnPostRun=cb=>onPostRuns.push(cb);var onPreRuns=[];var addOnPreRun=cb=>onPreRuns.push(cb);function getValue(ptr,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":return HEAP8[ptr];case"i8":return HEAP8[ptr];case"i16":return HEAP16[ptr>>1];case"i32":return HEAP32[ptr>>2];case"i64":return HEAP64[ptr>>3];case"float":return HEAPF32[ptr>>2];case"double":return HEAPF64[ptr>>3];case"*":return HEAPU32[ptr>>2];default:abort(`invalid type for getValue: ${type}`)}}var noExitRuntime=true;function setValue(ptr,value,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":HEAP8[ptr]=value;break;case"i8":HEAP8[ptr]=value;break;case"i16":HEAP16[ptr>>1]=value;break;case"i32":HEAP32[ptr>>2]=value;break;case"i64":HEAP64[ptr>>3]=BigInt(value);break;case"float":HEAPF32[ptr>>2]=value;break;case"double":HEAPF64[ptr>>3]=value;break;case"*":HEAPU32[ptr>>2]=value;break;default:abort(`invalid type for setValue: ${type}`)}}var stackRestore=val=>__emscripten_stack_restore(val);var stackSave=()=>_emscripten_stack_get_current();class ExceptionInfo{constructor(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24}set_type(type){HEAPU32[this.ptr+4>>2]=type}get_type(){return HEAPU32[this.ptr+4>>2]}set_destructor(destructor){HEAPU32[this.ptr+8>>2]=destructor}get_destructor(){return HEAPU32[this.ptr+8>>2]}set_caught(caught){caught=caught?1:0;HEAP8[this.ptr+12]=caught}get_caught(){return HEAP8[this.ptr+12]!=0}set_rethrown(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13]=rethrown}get_rethrown(){return HEAP8[this.ptr+13]!=0}init(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor)}set_adjusted_ptr(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr}get_adjusted_ptr(){return HEAPU32[this.ptr+16>>2]}}var uncaughtExceptionCount=0;var ___cxa_throw=(ptr,type,destructor)=>{var info=new ExceptionInfo(ptr);info.init(type,destructor);uncaughtExceptionCount++;abort()};var syscallGetVarargI=()=>{var ret=HEAP32[+SYSCALLS.varargs>>2];SYSCALLS.varargs+=4;return ret};var syscallGetVarargP=syscallGetVarargI;var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.slice(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.slice(0,-1)}return root+dir},basename:path=>path&&path.match(/([^\/]+|\/)\/*$/)[1],join:(...paths)=>PATH.normalize(paths.join("/")),join2:(l,r)=>PATH.normalize(l+"/"+r)};var initRandomFill=()=>{if(ENVIRONMENT_IS_NODE){var nodeCrypto=require("node:crypto");return view=>nodeCrypto.randomFillSync(view)}return view=>(crypto.getRandomValues(view),0)};var randomFill=view=>(randomFill=initRandomFill())(view);var PATH_FS={resolve:(...args)=>{var resolvedPath="",resolvedAbsolute=false;for(var i=args.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?args[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).slice(1);to=PATH_FS.resolve(to).slice(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var FS_stdin_getChar_buffer=[];var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var intArrayFromString=(stringy,dontAddNull,length)=>{var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array};var FS_stdin_getChar=()=>{if(!FS_stdin_getChar_buffer.length){var result=null;if(ENVIRONMENT_IS_NODE){var BUFSIZE=256;var buf=Buffer.alloc(BUFSIZE);var bytesRead=0;var fd=process.stdin.fd;try{bytesRead=fs.readSync(fd,buf,0,BUFSIZE)}catch(e){if(e.toString().includes("EOF"))bytesRead=0;else throw e}if(bytesRead>0){result=buf.slice(0,bytesRead).toString("utf-8")}}else if(globalThis.window?.prompt){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else{}if(!result){return null}FS_stdin_getChar_buffer=intArrayFromString(result,true)}return FS_stdin_getChar_buffer.shift()};var TTY={ttys:[],init(){},shutdown(){},register(dev,ops){TTY.ttys[dev]={input:[],output:[],ops};FS.registerDevice(dev,TTY.stream_ops)},stream_ops:{open(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty){throw new FS.ErrnoError(43)}stream.tty=tty;stream.seekable=false},close(stream){stream.tty.ops.fsync(stream.tty)},fsync(stream){stream.tty.ops.fsync(stream.tty)},read(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.get_char){throw new FS.ErrnoError(60)}var bytesRead=0;for(var i=0;i0){out(UTF8ArrayToString(tty.output));tty.output=[]}},ioctl_tcgets(tty){return{c_iflag:25856,c_oflag:5,c_cflag:191,c_lflag:35387,c_cc:[3,28,127,21,4,0,1,0,17,19,26,0,18,15,23,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}},ioctl_tcsets(tty,optional_actions,data){return 0},ioctl_tiocgwinsz(tty){return[24,80]}},default_tty1_ops:{put_char(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync(tty){if(tty.output?.length>0){err(UTF8ArrayToString(tty.output));tty.output=[]}}}};var zeroMemory=(ptr,size)=>HEAPU8.fill(0,ptr,ptr+size);var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var mmapAlloc=size=>{size=alignMemory(size,65536);var ptr=_emscripten_builtin_memalign(65536,size);if(ptr)zeroMemory(ptr,size);return ptr};var MEMFS={ops_table:null,mount(mount){return MEMFS.createNode(null,"/",16895,0)},createNode(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}MEMFS.ops_table||={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}};var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=MEMFS.emptyFileContents??=new Uint8Array(0)}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.atime=node.mtime=node.ctime=Date.now();if(parent){parent.contents[name]=node;parent.atime=parent.mtime=parent.ctime=node.atime}return node},getFileDataAsTypedArray(node){return node.contents.subarray(0,node.usedBytes)},expandFileStorage(node,newCapacity){var prevCapacity=node.contents.length;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity)newCapacity=Math.max(newCapacity,256);var oldContents=MEMFS.getFileDataAsTypedArray(node);node.contents=new Uint8Array(newCapacity);node.contents.set(oldContents)},resizeFileStorage(node,newSize){if(node.usedBytes==newSize)return;var oldContents=node.contents;node.contents=new Uint8Array(newSize);node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)));node.usedBytes=newSize},node_ops:{getattr(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.atime);attr.mtime=new Date(node.mtime);attr.ctime=new Date(node.ctime);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr(node,attr){for(const key of["mode","atime","mtime","ctime"]){if(attr[key]!=null){node[key]=attr[key]}}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup(parent,name){if(!MEMFS.doesNotExistError){MEMFS.doesNotExistError=new FS.ErrnoError(44);MEMFS.doesNotExistError.stack=""}throw MEMFS.doesNotExistError},mknod(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename(old_node,new_dir,new_name){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){if(FS.isDir(old_node.mode)){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}FS.hashRemoveNode(new_node)}delete old_node.parent.contents[old_node.name];new_dir.contents[new_name]=old_node;old_node.name=new_name;new_dir.ctime=new_dir.mtime=old_node.parent.ctime=old_node.parent.mtime=Date.now()},unlink(parent,name){delete parent.contents[name];parent.ctime=parent.mtime=Date.now()},rmdir(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.ctime=parent.mtime=Date.now()},readdir(node){return[".","..",...Object.keys(node.contents)]},symlink(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);buffer.set(contents.subarray(position,position+size),offset);return size},write(stream,buffer,offset,length,position,canOwn){if(buffer.buffer===HEAP8.buffer){canOwn=false}if(!length)return 0;var node=stream.node;node.mtime=node.ctime=Date.now();if(canOwn){node.contents=buffer.subarray(offset,offset+length);node.usedBytes=length}else if(node.usedBytes===0&&position===0){node.contents=buffer.slice(offset,offset+length);node.usedBytes=length}else{MEMFS.expandFileStorage(node,position+length);node.contents.set(buffer.subarray(offset,offset+length),position);node.usedBytes=Math.max(node.usedBytes,position+length)}return length},llseek(stream,offset,whence){var position=offset;if(whence===1){position+=stream.position}else if(whence===2){if(FS.isFile(stream.node.mode)){position+=stream.node.usedBytes}}if(position<0){throw new FS.ErrnoError(28)}return position},mmap(stream,length,position,prot,flags){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}var ptr;var allocated;var contents=stream.node.contents;if(!(flags&2)&&contents.buffer===HEAP8.buffer){allocated=false;ptr=contents.byteOffset}else{allocated=true;ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}if(contents){if(position>0||position+length{if(typeof str!="string")return str;var flagModes={r:0,"r+":2,w:512|64|1,"w+":512|64|2,a:1024|64|1,"a+":1024|64|2};var flags=flagModes[str];if(typeof flags=="undefined"){throw new Error(`Unknown file open mode: ${str}`)}return flags};var FS_fileDataToTypedArray=data=>{if(typeof data=="string"){data=intArrayFromString(data,true)}if(!data.subarray){data=new Uint8Array(data)}return data};var FS_getMode=(canRead,canWrite)=>{var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode};var asyncLoad=async url=>{var arrayBuffer=await readAsync(url);return new Uint8Array(arrayBuffer)};var FS_createDataFile=(...args)=>FS.createDataFile(...args);var getUniqueRunDependency=id=>id;var runDependencies=0;var dependenciesFulfilled=null;var removeRunDependency=id=>{runDependencies--;Module["monitorRunDependencies"]?.(runDependencies);if(runDependencies==0){if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}};var addRunDependency=id=>{runDependencies++;Module["monitorRunDependencies"]?.(runDependencies)};var preloadPlugins=[];var FS_handledByPreloadPlugin=async(byteArray,fullname)=>{if(typeof Browser!="undefined")Browser.init();for(var plugin of preloadPlugins){if(plugin["canHandle"](fullname)){return plugin["handle"](byteArray,fullname)}}return byteArray};var FS_preloadFile=async(parent,name,url,canRead,canWrite,dontCreateFile,canOwn,preFinish)=>{var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency(`cp ${fullname}`);addRunDependency(dep);try{var byteArray=url;if(typeof url=="string"){byteArray=await asyncLoad(url)}byteArray=await FS_handledByPreloadPlugin(byteArray,fullname);preFinish?.();if(!dontCreateFile){FS_createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}}finally{removeRunDependency(dep)}};var FS_createPreloadedFile=(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish)=>{FS_preloadFile(parent,name,url,canRead,canWrite,dontCreateFile,canOwn,preFinish).then(onload).catch(onerror)};var FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:false,ignorePermissions:true,filesystems:null,syncFSRequests:0,ErrnoError:class{name="ErrnoError";constructor(errno){this.errno=errno}},FSStream:class{shared={};get object(){return this.node}set object(val){this.node=val}get isRead(){return(this.flags&2097155)!==1}get isWrite(){return(this.flags&2097155)!==0}get isAppend(){return this.flags&1024}get flags(){return this.shared.flags}set flags(val){this.shared.flags=val}get position(){return this.shared.position}set position(val){this.shared.position=val}},FSNode:class{node_ops={};stream_ops={};readMode=292|73;writeMode=146;mounted=null;constructor(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.rdev=rdev;this.atime=this.mtime=this.ctime=Date.now()}get read(){return(this.mode&this.readMode)===this.readMode}set read(val){val?this.mode|=this.readMode:this.mode&=~this.readMode}get write(){return(this.mode&this.writeMode)===this.writeMode}set write(val){val?this.mode|=this.writeMode:this.mode&=~this.writeMode}get isFolder(){return FS.isDir(this.mode)}get isDevice(){return FS.isChrdev(this.mode)}},lookupPath(path,opts={}){if(!path){throw new FS.ErrnoError(44)}opts.follow_mount??=true;if(!PATH.isAbs(path)){path=FS.cwd()+"/"+path}linkloop:for(var nlinks=0;nlinks<40;nlinks++){var parts=path.split("/").filter(p=>!!p);var current=FS.root;var current_path="/";for(var i=0;i>>0)%FS.nameTable.length},hashAddNode(node){var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode(node){var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode(parent,name){var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode(parent,name,mode,rdev){var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode(node){FS.hashRemoveNode(node)},isRoot(node){return node===node.parent},isMountpoint(node){return!!node.mounted},isFile(mode){return(mode&61440)===32768},isDir(mode){return(mode&61440)===16384},isLink(mode){return(mode&61440)===40960},isChrdev(mode){return(mode&61440)===8192},isBlkdev(mode){return(mode&61440)===24576},isFIFO(mode){return(mode&61440)===4096},isSocket(mode){return(mode&49152)===49152},flagsToPermissionString(flag){var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions(node,perms){if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}if(perms.includes("w")&&!(node.mode&146)){return 2}if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup(dir){if(!FS.isDir(dir.mode))return 54;var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate(dir,name){if(!FS.isDir(dir.mode)){return 54}try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else if(FS.isDir(node.mode)){return 31}return 0},mayOpen(node,flags){if(!node){return 44}if(FS.isLink(node.mode)){return 32}var mode=FS.flagsToPermissionString(flags);if(FS.isDir(node.mode)){if(mode!=="r"||flags&(512|64)){return 31}}return FS.nodePermissions(node,mode)},checkOpExists(op,err){if(!op){throw new FS.ErrnoError(err)}return op},MAX_OPEN_FDS:4096,nextfd(){for(var fd=0;fd<=FS.MAX_OPEN_FDS;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStreamChecked(fd){var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}return stream},getStream:fd=>FS.streams[fd],createStream(stream,fd=-1){stream=Object.assign(new FS.FSStream,stream);if(fd==-1){fd=FS.nextfd()}stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream(fd){FS.streams[fd]=null},dupStream(origStream,fd=-1){var stream=FS.createStream(origStream,fd);stream.stream_ops?.dup?.(stream);return stream},doSetAttr(stream,node,attr){var setattr=stream?.stream_ops.setattr;var arg=setattr?stream:node;setattr??=node.node_ops.setattr;FS.checkOpExists(setattr,63);try{setattr(arg,attr)}catch(e){if(e instanceof RangeError){throw new FS.ErrnoError(22)}throw e}},chrdev_stream_ops:{open(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;stream.stream_ops.open?.(stream)},llseek(){throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice(dev,ops){FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts(mount){var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push(...m.mounts)}return mounts},syncfs(populate,callback){if(typeof populate=="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err(`warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work`)}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}for(var mount of mounts){if(mount.type.syncfs){mount.type.syncfs(mount,populate,done)}else{done(null)}}},mount(type,opts,mountpoint){var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type,opts,mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount(mountpoint){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);for(var[hash,current]of Object.entries(FS.nameTable)){while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}}node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup(parent,name){return parent.node_ops.lookup(parent,name)},mknod(path,mode,dev){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name){throw new FS.ErrnoError(28)}if(name==="."||name===".."){throw new FS.ErrnoError(20)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},statfs(path){return FS.statfsNode(FS.lookupPath(path,{follow:true}).node)},statfsStream(stream){return FS.statfsNode(stream.node)},statfsNode(node){var rtn={bsize:4096,frsize:4096,blocks:1e6,bfree:5e5,bavail:5e5,files:FS.nextInode,ffree:FS.nextInode-1,fsid:42,flags:2,namelen:255};if(node.node_ops.statfs){Object.assign(rtn,node.node_ops.statfs(node.mount.opts.root))}return rtn},create(path,mode=438){mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir(path,mode=511){mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree(path,mode){var dirs=path.split("/");var d="";for(var dir of dirs){if(!dir)continue;if(d||PATH.isAbs(path))d+="/";d+=dir;try{FS.mkdir(d,mode)}catch(e){if(e.errno!=20)throw e}}},mkdev(path,mode,dev){if(typeof dev=="undefined"){dev=mode;mode=438}mode|=8192;return FS.mknod(path,mode,dev)},symlink(oldpath,newpath){if(!PATH_FS.resolve(oldpath)){throw new FS.ErrnoError(44)}var lookup=FS.lookupPath(newpath,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var newname=PATH.basename(newpath);var errCode=FS.mayCreate(parent,newname);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.symlink){throw new FS.ErrnoError(63)}return parent.node_ops.symlink(parent,newname,oldpath)},rename(old_path,new_path){var old_dirname=PATH.dirname(old_path);var new_dirname=PATH.dirname(new_path);var old_name=PATH.basename(old_path);var new_name=PATH.basename(new_path);var lookup,old_dir,new_dir;lookup=FS.lookupPath(old_path,{parent:true});old_dir=lookup.node;lookup=FS.lookupPath(new_path,{parent:true});new_dir=lookup.node;if(!old_dir||!new_dir)throw new FS.ErrnoError(44);if(old_dir.mount!==new_dir.mount){throw new FS.ErrnoError(75)}var old_node=FS.lookupNode(old_dir,old_name);var relative=PATH_FS.relative(old_path,new_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(28)}relative=PATH_FS.relative(new_path,old_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(55)}var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(old_node===new_node){return}var isdir=FS.isDir(old_node.mode);var errCode=FS.mayDelete(old_dir,old_name,isdir);if(errCode){throw new FS.ErrnoError(errCode)}errCode=new_node?FS.mayDelete(new_dir,new_name,isdir):FS.mayCreate(new_dir,new_name);if(errCode){throw new FS.ErrnoError(errCode)}if(!old_dir.node_ops.rename){throw new FS.ErrnoError(63)}if(FS.isMountpoint(old_node)||new_node&&FS.isMountpoint(new_node)){throw new FS.ErrnoError(10)}if(new_dir!==old_dir){errCode=FS.nodePermissions(old_dir,"w");if(errCode){throw new FS.ErrnoError(errCode)}}FS.hashRemoveNode(old_node);try{old_dir.node_ops.rename(old_node,new_dir,new_name);old_node.parent=new_dir}catch(e){throw e}finally{FS.hashAddNode(old_node)}},rmdir(path){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,true);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.rmdir){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.rmdir(parent,name);FS.destroyNode(node)},readdir(path){var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;var readdir=FS.checkOpExists(node.node_ops.readdir,54);return readdir(node)},unlink(path){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,false);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.unlink){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.unlink(parent,name);FS.destroyNode(node)},readlink(path){var lookup=FS.lookupPath(path);var link=lookup.node;if(!link){throw new FS.ErrnoError(44)}if(!link.node_ops.readlink){throw new FS.ErrnoError(28)}return link.node_ops.readlink(link)},stat(path,dontFollow){var lookup=FS.lookupPath(path,{follow:!dontFollow});var node=lookup.node;var getattr=FS.checkOpExists(node.node_ops.getattr,63);return getattr(node)},fstat(fd){var stream=FS.getStreamChecked(fd);var node=stream.node;var getattr=stream.stream_ops.getattr;var arg=getattr?stream:node;getattr??=node.node_ops.getattr;FS.checkOpExists(getattr,63);return getattr(arg)},lstat(path){return FS.stat(path,true)},doChmod(stream,node,mode,dontFollow){FS.doSetAttr(stream,node,{mode:mode&4095|node.mode&~4095,ctime:Date.now(),dontFollow})},chmod(path,mode,dontFollow){var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}FS.doChmod(null,node,mode,dontFollow)},lchmod(path,mode){FS.chmod(path,mode,true)},fchmod(fd,mode){var stream=FS.getStreamChecked(fd);FS.doChmod(stream,stream.node,mode,false)},doChown(stream,node,dontFollow){FS.doSetAttr(stream,node,{timestamp:Date.now(),dontFollow})},chown(path,uid,gid,dontFollow){var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}FS.doChown(null,node,dontFollow)},lchown(path,uid,gid){FS.chown(path,uid,gid,true)},fchown(fd,uid,gid){var stream=FS.getStreamChecked(fd);FS.doChown(stream,stream.node,false)},doTruncate(stream,node,len){if(FS.isDir(node.mode)){throw new FS.ErrnoError(31)}if(!FS.isFile(node.mode)){throw new FS.ErrnoError(28)}var errCode=FS.nodePermissions(node,"w");if(errCode){throw new FS.ErrnoError(errCode)}FS.doSetAttr(stream,node,{size:len,timestamp:Date.now()})},truncate(path,len){if(len<0){throw new FS.ErrnoError(28)}var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:true});node=lookup.node}else{node=path}FS.doTruncate(null,node,len)},ftruncate(fd,len){var stream=FS.getStreamChecked(fd);if(len<0||(stream.flags&2097155)===0){throw new FS.ErrnoError(28)}FS.doTruncate(stream,stream.node,len)},utime(path,atime,mtime){var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;var setattr=FS.checkOpExists(node.node_ops.setattr,63);setattr(node,{atime,mtime})},open(path,flags,mode=438){if(path===""){throw new FS.ErrnoError(44)}flags=FS_modeStringToFlags(flags);if(flags&64){mode=mode&4095|32768}else{mode=0}var node;var isDirPath;if(typeof path=="object"){node=path}else{isDirPath=path.endsWith("/");var lookup=FS.lookupPath(path,{follow:!(flags&131072),noent_okay:true});node=lookup.node;path=lookup.path}var created=false;if(flags&64){if(node){if(flags&128){throw new FS.ErrnoError(20)}}else if(isDirPath){throw new FS.ErrnoError(31)}else{node=FS.mknod(path,mode|511,0);created=true}}if(!node){throw new FS.ErrnoError(44)}if(FS.isChrdev(node.mode)){flags&=~512}if(flags&65536&&!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}if(!created){var errCode=FS.mayOpen(node,flags);if(errCode){throw new FS.ErrnoError(errCode)}}if(flags&512&&!created){FS.truncate(node,0)}flags&=~(128|512|131072);var stream=FS.createStream({node,path:FS.getPath(node),flags,seekable:true,position:0,stream_ops:node.stream_ops,ungotten:[],error:false});if(stream.stream_ops.open){stream.stream_ops.open(stream)}if(created){FS.chmod(node,mode&511)}return stream},close(stream){if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(stream.getdents)stream.getdents=null;try{if(stream.stream_ops.close){stream.stream_ops.close(stream)}}catch(e){throw e}finally{FS.closeStream(stream.fd)}stream.fd=null},isClosed(stream){return stream.fd===null},llseek(stream,offset,whence){if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(!stream.seekable||!stream.stream_ops.llseek){throw new FS.ErrnoError(70)}if(whence!=0&&whence!=1&&whence!=2){throw new FS.ErrnoError(28)}stream.position=stream.stream_ops.llseek(stream,offset,whence);stream.ungotten=[];return stream.position},read(stream,buffer,offset,length,position){if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.read){throw new FS.ErrnoError(28)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesRead=stream.stream_ops.read(stream,buffer,offset,length,position);if(!seeking)stream.position+=bytesRead;return bytesRead},write(stream,buffer,offset,length,position,canOwn){if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.write){throw new FS.ErrnoError(28)}if(stream.seekable&&stream.flags&1024){FS.llseek(stream,0,2)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesWritten=stream.stream_ops.write(stream,buffer,offset,length,position,canOwn);if(!seeking)stream.position+=bytesWritten;return bytesWritten},mmap(stream,length,position,prot,flags){if((prot&2)!==0&&(flags&2)===0&&(stream.flags&2097155)!==2){throw new FS.ErrnoError(2)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(2)}if(!stream.stream_ops.mmap){throw new FS.ErrnoError(43)}if(!length){throw new FS.ErrnoError(28)}return stream.stream_ops.mmap(stream,length,position,prot,flags)},msync(stream,buffer,offset,length,mmapFlags){if(!stream.stream_ops.msync){return 0}return stream.stream_ops.msync(stream,buffer,offset,length,mmapFlags)},ioctl(stream,cmd,arg){if(!stream.stream_ops.ioctl){throw new FS.ErrnoError(59)}return stream.stream_ops.ioctl(stream,cmd,arg)},readFile(path,opts={}){opts.flags=opts.flags||0;opts.encoding=opts.encoding||"binary";if(opts.encoding!=="utf8"&&opts.encoding!=="binary"){abort(`Invalid encoding type "${opts.encoding}"`)}var stream=FS.open(path,opts.flags);var stat=FS.stat(path);var length=stat.size;var buf=new Uint8Array(length);FS.read(stream,buf,0,length,0);if(opts.encoding==="utf8"){buf=UTF8ArrayToString(buf)}FS.close(stream);return buf},writeFile(path,data,opts={}){opts.flags=opts.flags||577;var stream=FS.open(path,opts.flags,opts.mode);data=FS_fileDataToTypedArray(data);FS.write(stream,data,0,data.byteLength,undefined,opts.canOwn);FS.close(stream)},cwd:()=>FS.currentPath,chdir(path){var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories(){FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices(){FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length,llseek:()=>0});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var randomBuffer=new Uint8Array(1024),randomLeft=0;var randomByte=()=>{if(randomLeft===0){randomFill(randomBuffer);randomLeft=randomBuffer.byteLength}return randomBuffer[--randomLeft]};FS.createDevice("/dev","random",randomByte);FS.createDevice("/dev","urandom",randomByte);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories(){FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount(){var node=FS.createNode(proc_self,"fd",16895,73);node.stream_ops={llseek:MEMFS.stream_ops.llseek};node.node_ops={lookup(parent,name){var fd=+name;var stream=FS.getStreamChecked(fd);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path},id:fd+1};ret.parent=ret;return ret},readdir(){return Array.from(FS.streams.entries()).filter(([k,v])=>v).map(([k,v])=>k.toString())}};return node}},{},"/proc/self/fd")},createStandardStreams(input,output,error){if(input){FS.createDevice("/dev","stdin",input)}else{FS.symlink("/dev/tty","/dev/stdin")}if(output){FS.createDevice("/dev","stdout",null,output)}else{FS.symlink("/dev/tty","/dev/stdout")}if(error){FS.createDevice("/dev","stderr",null,error)}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1)},staticInit(){FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={MEMFS}},init(input,output,error){FS.initialized=true;input??=Module["stdin"];output??=Module["stdout"];error??=Module["stderr"];FS.createStandardStreams(input,output,error)},quit(){FS.initialized=false;for(var stream of FS.streams){if(stream){FS.close(stream)}}},findObject(path,dontResolveLastLink){var ret=FS.analyzePath(path,dontResolveLastLink);if(!ret.exists){return null}return ret.object},analyzePath(path,dontResolveLastLink){try{var lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});path=lookup.path}catch(e){}var ret={isRoot:false,exists:false,error:0,name:null,path:null,object:null,parentExists:false,parentPath:null,parentObject:null};try{var lookup=FS.lookupPath(path,{parent:true});ret.parentExists=true;ret.parentPath=lookup.path;ret.parentObject=lookup.node;ret.name=PATH.basename(path);lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});ret.exists=true;ret.path=lookup.path;ret.object=lookup.node;ret.name=lookup.node.name;ret.isRoot=lookup.path==="/"}catch(e){ret.error=e.errno}return ret},createPath(parent,path,canRead,canWrite){parent=typeof parent=="string"?parent:FS.getPath(parent);var parts=path.split("/").reverse();while(parts.length){var part=parts.pop();if(!part)continue;var current=PATH.join2(parent,part);try{FS.mkdir(current)}catch(e){if(e.errno!=20)throw e}parent=current}return current},createFile(parent,name,properties,canRead,canWrite){var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS_getMode(canRead,canWrite);return FS.create(path,mode)},createDataFile(parent,name,data,canRead,canWrite,canOwn){var path=name;if(parent){parent=typeof parent=="string"?parent:FS.getPath(parent);path=name?PATH.join2(parent,name):parent}var mode=FS_getMode(canRead,canWrite);var node=FS.create(path,mode);if(data){data=FS_fileDataToTypedArray(data);FS.chmod(node,mode|146);var stream=FS.open(node,577);FS.write(stream,data,0,data.length,0,canOwn);FS.close(stream);FS.chmod(node,mode)}},createDevice(parent,name,input,output){var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS_getMode(!!input,!!output);FS.createDevice.major??=64;var dev=FS.makedev(FS.createDevice.major++,0);FS.registerDevice(dev,{open(stream){stream.seekable=false},close(stream){if(output?.buffer?.length){output(10)}},read(stream,buffer,offset,length,pos){var bytesRead=0;for(var i=0;ithis.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]}setDataGetter(getter){this.getter=getter}cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))abort("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)abort("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)abort("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))abort("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}return intArrayFromString(xhr.responseText||"",true)};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")abort("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true}get length(){if(!this.lengthKnown){this.cacheLength()}return this._length}get chunkSize(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}if(globalThis.XMLHttpRequest){if(!ENVIRONMENT_IS_WORKER)abort("Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc");var lazyArray=new LazyUint8Array;var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};for(const[key,fn]of Object.entries(node.stream_ops)){stream_ops[key]=(...args)=>{FS.forceLoadFile(node);return fn(...args)}}function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr,allocated:true}};node.stream_ops=stream_ops;return node}};var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead,ignoreNul):"";var SYSCALLS={currentUmask:18,calculateAt(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=SYSCALLS.getStreamFromFD(dirfd);dir=dirstream.path}if(path.length==0){if(!allowEmpty){throw new FS.ErrnoError(44)}return dir}return dir+"/"+path},writeStat(buf,stat){HEAPU32[buf>>2]=stat.dev;HEAPU32[buf+4>>2]=stat.mode;HEAPU32[buf+8>>2]=stat.nlink;HEAPU32[buf+12>>2]=stat.uid;HEAPU32[buf+16>>2]=stat.gid;HEAPU32[buf+20>>2]=stat.rdev;HEAP64[buf+24>>3]=BigInt(stat.size);HEAP32[buf+32>>2]=4096;HEAP32[buf+36>>2]=stat.blocks;var atime=stat.atime.getTime();var mtime=stat.mtime.getTime();var ctime=stat.ctime.getTime();HEAP64[buf+40>>3]=BigInt(Math.floor(atime/1e3));HEAPU32[buf+48>>2]=atime%1e3*1e3*1e3;HEAP64[buf+56>>3]=BigInt(Math.floor(mtime/1e3));HEAPU32[buf+64>>2]=mtime%1e3*1e3*1e3;HEAP64[buf+72>>3]=BigInt(Math.floor(ctime/1e3));HEAPU32[buf+80>>2]=ctime%1e3*1e3*1e3;HEAP64[buf+88>>3]=BigInt(stat.ino);return 0},writeStatFs(buf,stats){HEAPU32[buf+4>>2]=stats.bsize;HEAPU32[buf+60>>2]=stats.bsize;HEAP64[buf+8>>3]=BigInt(stats.blocks);HEAP64[buf+16>>3]=BigInt(stats.bfree);HEAP64[buf+24>>3]=BigInt(stats.bavail);HEAP64[buf+32>>3]=BigInt(stats.files);HEAP64[buf+40>>3]=BigInt(stats.ffree);HEAPU32[buf+48>>2]=stats.fsid;HEAPU32[buf+64>>2]=stats.flags;HEAPU32[buf+56>>2]=stats.namelen},doMsync(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}if(flags&2){return 0}var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},getStreamFromFD(fd){var stream=FS.getStreamChecked(fd);return stream},varargs:undefined,getStr(ptr){var ret=UTF8ToString(ptr);return ret}};function ___syscall_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=syscallGetVarargI();if(arg<0){return-28}while(FS.streams[arg]){arg++}var newStream;newStream=FS.dupStream(stream,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=syscallGetVarargI();var mask=289792;stream.flags=stream.flags&~mask|arg&mask;return 0}case 12:{var arg=syscallGetVarargP();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 13:case 14:return 0}return-28}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_fstat64(fd,buf){try{return SYSCALLS.writeStat(buf,FS.fstat(fd))}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);function ___syscall_getdents64(fd,dirp,count){try{var stream=SYSCALLS.getStreamFromFD(fd);stream.getdents||=FS.readdir(stream.path);var struct_size=280;var pos=0;var off=FS.llseek(stream,0,1);var startIdx=Math.floor(off/struct_size);var endIdx=Math.min(stream.getdents.length,startIdx+Math.floor(count/struct_size));for(var idx=startIdx;idx>3]=BigInt(id);HEAP64[dirp+pos+8>>3]=BigInt((idx+1)*struct_size);HEAP16[dirp+pos+16>>1]=280;HEAP8[dirp+pos+18]=type;stringToUTF8(name,dirp+pos+19,256);pos+=struct_size}FS.llseek(stream,idx*struct_size,0);return pos}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:{if(!stream.tty)return-59;return 0}case 21505:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcgets){var termios=stream.tty.ops.ioctl_tcgets(stream);var argp=syscallGetVarargP();HEAP32[argp>>2]=termios.c_iflag||0;HEAP32[argp+4>>2]=termios.c_oflag||0;HEAP32[argp+8>>2]=termios.c_cflag||0;HEAP32[argp+12>>2]=termios.c_lflag||0;for(var i=0;i<32;i++){HEAP8[argp+i+17]=termios.c_cc[i]||0}return 0}return 0}case 21510:case 21511:case 21512:{if(!stream.tty)return-59;return 0}case 21506:case 21507:case 21508:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcsets){var argp=syscallGetVarargP();var c_iflag=HEAP32[argp>>2];var c_oflag=HEAP32[argp+4>>2];var c_cflag=HEAP32[argp+8>>2];var c_lflag=HEAP32[argp+12>>2];var c_cc=[];for(var i=0;i<32;i++){c_cc.push(HEAP8[argp+i+17])}return stream.tty.ops.ioctl_tcsets(stream.tty,op,{c_iflag,c_oflag,c_cflag,c_lflag,c_cc})}return 0}case 21519:{if(!stream.tty)return-59;var argp=syscallGetVarargP();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21537:case 21531:{var argp=syscallGetVarargP();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tiocgwinsz){var winsize=stream.tty.ops.ioctl_tiocgwinsz(stream.tty);var argp=syscallGetVarargP();HEAP16[argp>>1]=winsize[0];HEAP16[argp+2>>1]=winsize[1]}return 0}case 21524:{if(!stream.tty)return-59;return 0}case 21515:{if(!stream.tty)return-59;return 0}default:return-28}}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_lstat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.writeStat(buf,FS.lstat(path))}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_newfstatat(dirfd,path,buf,flags){try{path=SYSCALLS.getStr(path);var nofollow=flags&256;var allowEmpty=flags&4096;flags=flags&~6400;path=SYSCALLS.calculateAt(dirfd,path,allowEmpty);return SYSCALLS.writeStat(buf,nofollow?FS.lstat(path):FS.stat(path))}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_openat(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);var mode=varargs?syscallGetVarargI():0;if(flags&64){mode&=~SYSCALLS.currentUmask}return FS.open(path,flags,mode).fd}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_rmdir(path){try{path=SYSCALLS.getStr(path);FS.rmdir(path);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_stat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.writeStat(buf,FS.stat(path))}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_unlinkat(dirfd,path,flags){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);if(!flags){FS.unlink(path)}else if(flags===512){FS.rmdir(path)}else{return-28}return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var __abort_js=()=>abort("");var __emscripten_fs_load_embedded_files=ptr=>{do{var name_addr=HEAPU32[ptr>>2];ptr+=4;var len=HEAPU32[ptr>>2];ptr+=4;var content=HEAPU32[ptr>>2];ptr+=4;var name=UTF8ToString(name_addr);FS.createPath("/",PATH.dirname(name),true,true);FS.createDataFile(name,null,HEAP8.subarray(content,content+len),true,true,true)}while(HEAPU32[ptr>>2])};var __emscripten_throw_longjmp=()=>{throw new EmscriptenSjLj};var INT53_MAX=9007199254740992;var INT53_MIN=-9007199254740992;var bigintToI53Checked=num=>numINT53_MAX?NaN:Number(num);function __gmtime_js(time,tmPtr){time=bigintToI53Checked(time);var date=new Date(time*1e3);HEAP32[tmPtr>>2]=date.getUTCSeconds();HEAP32[tmPtr+4>>2]=date.getUTCMinutes();HEAP32[tmPtr+8>>2]=date.getUTCHours();HEAP32[tmPtr+12>>2]=date.getUTCDate();HEAP32[tmPtr+16>>2]=date.getUTCMonth();HEAP32[tmPtr+20>>2]=date.getUTCFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getUTCDay();var start=Date.UTC(date.getUTCFullYear(),0,1,0,0,0,0);var yday=(date.getTime()-start)/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday}var isLeapYear=year=>year%4===0&&(year%100!==0||year%400===0);var MONTH_DAYS_LEAP_CUMULATIVE=[0,31,60,91,121,152,182,213,244,274,305,335];var MONTH_DAYS_REGULAR_CUMULATIVE=[0,31,59,90,120,151,181,212,243,273,304,334];var ydayFromDate=date=>{var leap=isLeapYear(date.getFullYear());var monthDaysCumulative=leap?MONTH_DAYS_LEAP_CUMULATIVE:MONTH_DAYS_REGULAR_CUMULATIVE;var yday=monthDaysCumulative[date.getMonth()]+date.getDate()-1;return yday};function __localtime_js(time,tmPtr){time=bigintToI53Checked(time);var date=new Date(time*1e3);HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getDay();var yday=ydayFromDate(date)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr+36>>2]=-(date.getTimezoneOffset()*60);var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dst=(summerOffset!=winterOffset&&date.getTimezoneOffset()==Math.min(winterOffset,summerOffset))|0;HEAP32[tmPtr+32>>2]=dst}function __mmap_js(len,prot,flags,fd,offset,allocated,addr){offset=bigintToI53Checked(offset);try{var stream=SYSCALLS.getStreamFromFD(fd);var res=FS.mmap(stream,len,offset,prot,flags);var ptr=res.ptr;HEAP32[allocated>>2]=res.allocated;HEAPU32[addr>>2]=ptr;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function __munmap_js(addr,len,prot,flags,fd,offset){offset=bigintToI53Checked(offset);try{var stream=SYSCALLS.getStreamFromFD(fd);if(prot&2){SYSCALLS.doMsync(addr,stream,len,flags,offset)}}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var __tzset_js=(timezone,daylight,std_name,dst_name)=>{var currentYear=(new Date).getFullYear();var winter=new Date(currentYear,0,1);var summer=new Date(currentYear,6,1);var winterOffset=winter.getTimezoneOffset();var summerOffset=summer.getTimezoneOffset();var stdTimezoneOffset=Math.max(winterOffset,summerOffset);HEAPU32[timezone>>2]=stdTimezoneOffset*60;HEAP32[daylight>>2]=Number(winterOffset!=summerOffset);var extractZone=timezoneOffset=>{var sign=timezoneOffset>=0?"-":"+";var absOffset=Math.abs(timezoneOffset);var hours=String(Math.floor(absOffset/60)).padStart(2,"0");var minutes=String(absOffset%60).padStart(2,"0");return`UTC${sign}${hours}${minutes}`};var winterName=extractZone(winterOffset);var summerName=extractZone(summerOffset);if(summerOffsetperformance.now();var _emscripten_date_now=()=>Date.now();var nowIsMonotonic=1;var checkWasiClock=clock_id=>clock_id>=0&&clock_id<=3;function _clock_time_get(clk_id,ignored_precision,ptime){ignored_precision=bigintToI53Checked(ignored_precision);if(!checkWasiClock(clk_id)){return 28}var now;if(clk_id===0){now=_emscripten_date_now()}else if(nowIsMonotonic){now=_emscripten_get_now()}else{return 52}var nsec=Math.round(now*1e3*1e3);HEAP64[ptime>>3]=BigInt(nsec);return 0}var getHeapMax=()=>2147483648;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var ENV={};var getExecutableName=()=>thisProgram||"./this.program";var getEnvStrings=()=>{if(!getEnvStrings.strings){var lang=(globalThis.navigator?.language??"C").replace("-","_")+".UTF-8";var env={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:lang,_:getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(`${x}=${env[x]}`)}getEnvStrings.strings=strings}return getEnvStrings.strings};var _environ_get=(__environ,environ_buf)=>{var bufSize=0;var envp=0;for(var string of getEnvStrings()){var ptr=environ_buf+bufSize;HEAPU32[__environ+envp>>2]=ptr;bufSize+=stringToUTF8(string,ptr,Infinity)+1;envp+=4}return 0};var _environ_sizes_get=(penviron_count,penviron_buf_size)=>{var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;for(var string of strings){bufSize+=lengthBytesUTF8(string)+1}HEAPU32[penviron_buf_size>>2]=bufSize;return 0};var runtimeKeepaliveCounter=0;var keepRuntimeAlive=()=>noExitRuntime||runtimeKeepaliveCounter>0;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function _fd_fdstat_get(fd,pbuf){try{var rightsBase=0;var rightsInheriting=0;var flags=0;{var stream=SYSCALLS.getStreamFromFD(fd);var type=stream.tty?2:FS.isDir(stream.mode)?3:FS.isLink(stream.mode)?7:4}HEAP8[pbuf]=type;HEAP16[pbuf+2>>1]=flags;HEAP64[pbuf+8>>3]=BigInt(rightsBase);HEAP64[pbuf+16>>3]=BigInt(rightsInheriting);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var doReadv=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function _fd_seek(fd,offset,whence,newOffset){offset=bigintToI53Checked(offset);try{if(isNaN(offset))return 22;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);HEAP64[newOffset>>3]=BigInt(stream.position);if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var doWritev=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var wasmTableMirror=[];var getWasmTableEntry=funcPtr=>{var func=wasmTableMirror[funcPtr];if(!func){wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func};var getCFunc=ident=>{var func=Module["_"+ident];return func};var writeArrayToMemory=(array,buffer)=>{HEAP8.set(array,buffer)};var stackAlloc=sz=>__emscripten_stack_alloc(sz);var stringToUTF8OnStack=str=>{var size=lengthBytesUTF8(str)+1;var ret=stackAlloc(size);stringToUTF8(str,ret,size);return ret};var ccall=(ident,returnType,argTypes,args,opts)=>{var toC={string:str=>{var ret=0;if(str!==null&&str!==undefined&&str!==0){ret=stringToUTF8OnStack(str)}return ret},array:arr=>{var ret=stackAlloc(arr.length);writeArrayToMemory(arr,ret);return ret}};function convertReturnValue(ret){if(returnType==="string"){return UTF8ToString(ret)}if(returnType==="boolean")return Boolean(ret);return ret}var func=getCFunc(ident);var cArgs=[];var stack=0;if(args){for(var i=0;i{var numericArgs=!argTypes||argTypes.every(type=>type==="number"||type==="boolean");var numericRet=returnType!=="string";if(numericRet&&numericArgs&&!opts){return getCFunc(ident)}return(...args)=>ccall(ident,returnType,argTypes,args,opts)};var FS_createPath=(...args)=>FS.createPath(...args);var FS_unlink=(...args)=>FS.unlink(...args);var FS_createLazyFile=(...args)=>FS.createLazyFile(...args);var FS_createDevice=(...args)=>FS.createDevice(...args);FS.createPreloadedFile=FS_createPreloadedFile;FS.preloadFile=FS_preloadFile;FS.staticInit();{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["preloadPlugins"])preloadPlugins=Module["preloadPlugins"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].shift()()}}}Module["addRunDependency"]=addRunDependency;Module["removeRunDependency"]=removeRunDependency;Module["ccall"]=ccall;Module["cwrap"]=cwrap;Module["setValue"]=setValue;Module["getValue"]=getValue;Module["FS_preloadFile"]=FS_preloadFile;Module["FS_unlink"]=FS_unlink;Module["FS_createPath"]=FS_createPath;Module["FS_createDevice"]=FS_createDevice;Module["FS_createDataFile"]=FS_createDataFile;Module["FS_createLazyFile"]=FS_createLazyFile;var _loadDocument,_pageCount,_renderPagePng,_previewRender,_previewRenderRegion,_previewRenderPaginated,_lastRegionCount,_lastRegionPtr,_lastRegionW,_lastRegionH,_lastRegionPage,_lastRegionYTop,_lastRenderPtr,_lastRenderW,_lastRenderH,_lastLayoutJson,_registerAuxFont,_freeDocument,_engineBuildInfo,_free,_malloc,_emscripten_builtin_memalign,_setThrew,__emscripten_stack_restore,__emscripten_stack_alloc,_emscripten_stack_get_current,memory,__indirect_function_table,wasmMemory,wasmTable;function assignWasmExports(wasmExports){_loadDocument=Module["_loadDocument"]=wasmExports["T"];_pageCount=Module["_pageCount"]=wasmExports["U"];_renderPagePng=Module["_renderPagePng"]=wasmExports["V"];_previewRender=Module["_previewRender"]=wasmExports["W"];_previewRenderRegion=Module["_previewRenderRegion"]=wasmExports["X"];_previewRenderPaginated=Module["_previewRenderPaginated"]=wasmExports["Y"];_lastRegionCount=Module["_lastRegionCount"]=wasmExports["Z"];_lastRegionPtr=Module["_lastRegionPtr"]=wasmExports["_"];_lastRegionW=Module["_lastRegionW"]=wasmExports["$"];_lastRegionH=Module["_lastRegionH"]=wasmExports["aa"];_lastRegionPage=Module["_lastRegionPage"]=wasmExports["ba"];_lastRegionYTop=Module["_lastRegionYTop"]=wasmExports["ca"];_lastRenderPtr=Module["_lastRenderPtr"]=wasmExports["da"];_lastRenderW=Module["_lastRenderW"]=wasmExports["ea"];_lastRenderH=Module["_lastRenderH"]=wasmExports["fa"];_lastLayoutJson=Module["_lastLayoutJson"]=wasmExports["ga"];_registerAuxFont=Module["_registerAuxFont"]=wasmExports["ha"];_freeDocument=Module["_freeDocument"]=wasmExports["ia"];_engineBuildInfo=Module["_engineBuildInfo"]=wasmExports["ja"];_free=Module["_free"]=wasmExports["ka"];_malloc=Module["_malloc"]=wasmExports["la"];_emscripten_builtin_memalign=wasmExports["ma"];_setThrew=wasmExports["na"];__emscripten_stack_restore=wasmExports["oa"];__emscripten_stack_alloc=wasmExports["pa"];_emscripten_stack_get_current=wasmExports["qa"];memory=wasmMemory=wasmExports["Q"];__indirect_function_table=wasmTable=wasmExports["S"]}var wasmImports={a:___cxa_throw,i:___syscall_fcntl64,I:___syscall_fstat64,w:___syscall_getdents64,M:___syscall_ioctl,F:___syscall_lstat64,G:___syscall_newfstatat,k:___syscall_openat,u:___syscall_rmdir,H:___syscall_stat64,v:___syscall_unlinkat,K:__abort_js,N:__emscripten_fs_load_embedded_files,s:__emscripten_throw_longjmp,z:__gmtime_js,A:__localtime_js,x:__mmap_js,y:__munmap_js,L:__tzset_js,J:_clock_time_get,l:_emscripten_date_now,t:_emscripten_resize_heap,D:_environ_get,E:_environ_sizes_get,O:_exit,g:_fd_close,C:_fd_fdstat_get,n:_fd_read,B:_fd_seek,m:_fd_write,d:invoke_ii,f:invoke_iii,h:invoke_iiii,j:invoke_iiiii,P:invoke_iiiiiii,o:invoke_iiiiiiiiii,p:invoke_v,c:invoke_vi,b:invoke_vii,e:invoke_viii,r:invoke_viiii,q:invoke_viiiiiiiii};function invoke_iiiii(index,a1,a2,a3,a4){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(!(e instanceof EmscriptenEH))throw e;_setThrew(1,0)}}function invoke_ii(index,a1){var sp=stackSave();try{return getWasmTableEntry(index)(a1)}catch(e){stackRestore(sp);if(!(e instanceof EmscriptenEH))throw e;_setThrew(1,0)}}function invoke_iiii(index,a1,a2,a3){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(!(e instanceof EmscriptenEH))throw e;_setThrew(1,0)}}function invoke_vii(index,a1,a2){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(!(e instanceof EmscriptenEH))throw e;_setThrew(1,0)}}function invoke_viiii(index,a1,a2,a3,a4){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(!(e instanceof EmscriptenEH))throw e;_setThrew(1,0)}}function invoke_viiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(!(e instanceof EmscriptenEH))throw e;_setThrew(1,0)}}function invoke_vi(index,a1){var sp=stackSave();try{getWasmTableEntry(index)(a1)}catch(e){stackRestore(sp);if(!(e instanceof EmscriptenEH))throw e;_setThrew(1,0)}}function invoke_v(index){var sp=stackSave();try{getWasmTableEntry(index)()}catch(e){stackRestore(sp);if(!(e instanceof EmscriptenEH))throw e;_setThrew(1,0)}}function invoke_iii(index,a1,a2){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(!(e instanceof EmscriptenEH))throw e;_setThrew(1,0)}}function invoke_viii(index,a1,a2,a3){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(!(e instanceof EmscriptenEH))throw e;_setThrew(1,0)}}function invoke_iiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(!(e instanceof EmscriptenEH))throw e;_setThrew(1,0)}}function invoke_iiiiiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(!(e instanceof EmscriptenEH))throw e;_setThrew(1,0)}}function run(){if(runDependencies>0){dependenciesFulfilled=run;return}preRun();if(runDependencies>0){dependenciesFulfilled=run;return}function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve?.(Module);Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}var wasmExports;wasmExports=await (createWasm());run();if(runtimeInitialized){moduleRtn=Module}else{moduleRtn=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject})} +async function Module(moduleArg={}){var moduleRtn;var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";if(ENVIRONMENT_IS_NODE){const{createRequire}=await import("node:module");var require=createRequire(import.meta.url)}var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var _scriptName=import.meta.url;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("node:fs");if(_scriptName.startsWith("file:")){scriptDirectory=require("node:path").dirname(require("node:url").fileURLToPath(_scriptName))+"/"}readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");return ret};if(process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{readAsync=async url=>{var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var EXITSTATUS;var isFileURI=filename=>filename.startsWith("file://");class EmscriptenEH{}class EmscriptenSjLj extends EmscriptenEH{}var readyPromiseResolve,readyPromiseReject;var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b);HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);HEAPU16=new Uint16Array(b);HEAP32=new Int32Array(b);HEAPU32=new Uint32Array(b);HEAPF32=new Float32Array(b);HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.initialized)FS.init();TTY.init();wasmExports["R"]();FS.ignorePermissions=false}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what=`Aborted(${what})`;err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject?.(e);throw e}var wasmBinaryFile;function findWasmBinary(){if(Module["locateFile"]){return locateFile("pdfengine.wasm")}return new URL("pdfengine.wasm",import.meta.url).href}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!ENVIRONMENT_IS_NODE){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){return new Promise((resolve,reject)=>{Module["instantiateWasm"](info,(inst,mod)=>{resolve(receiveInstance(inst,mod))})})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var HEAP16;var HEAP32;var HEAP64;var HEAP8;var HEAPF32;var HEAPF64;var HEAPU16;var HEAPU32;var HEAPU64;var HEAPU8;var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var addOnPostRun=cb=>onPostRuns.push(cb);var onPreRuns=[];var addOnPreRun=cb=>onPreRuns.push(cb);function getValue(ptr,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":return HEAP8[ptr];case"i8":return HEAP8[ptr];case"i16":return HEAP16[ptr>>1];case"i32":return HEAP32[ptr>>2];case"i64":return HEAP64[ptr>>3];case"float":return HEAPF32[ptr>>2];case"double":return HEAPF64[ptr>>3];case"*":return HEAPU32[ptr>>2];default:abort(`invalid type for getValue: ${type}`)}}var noExitRuntime=true;function setValue(ptr,value,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":HEAP8[ptr]=value;break;case"i8":HEAP8[ptr]=value;break;case"i16":HEAP16[ptr>>1]=value;break;case"i32":HEAP32[ptr>>2]=value;break;case"i64":HEAP64[ptr>>3]=BigInt(value);break;case"float":HEAPF32[ptr>>2]=value;break;case"double":HEAPF64[ptr>>3]=value;break;case"*":HEAPU32[ptr>>2]=value;break;default:abort(`invalid type for setValue: ${type}`)}}var stackRestore=val=>__emscripten_stack_restore(val);var stackSave=()=>_emscripten_stack_get_current();class ExceptionInfo{constructor(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24}set_type(type){HEAPU32[this.ptr+4>>2]=type}get_type(){return HEAPU32[this.ptr+4>>2]}set_destructor(destructor){HEAPU32[this.ptr+8>>2]=destructor}get_destructor(){return HEAPU32[this.ptr+8>>2]}set_caught(caught){caught=caught?1:0;HEAP8[this.ptr+12]=caught}get_caught(){return HEAP8[this.ptr+12]!=0}set_rethrown(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13]=rethrown}get_rethrown(){return HEAP8[this.ptr+13]!=0}init(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor)}set_adjusted_ptr(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr}get_adjusted_ptr(){return HEAPU32[this.ptr+16>>2]}}var uncaughtExceptionCount=0;var ___cxa_throw=(ptr,type,destructor)=>{var info=new ExceptionInfo(ptr);info.init(type,destructor);uncaughtExceptionCount++;abort()};var syscallGetVarargI=()=>{var ret=HEAP32[+SYSCALLS.varargs>>2];SYSCALLS.varargs+=4;return ret};var syscallGetVarargP=syscallGetVarargI;var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.slice(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.slice(0,-1)}return root+dir},basename:path=>path&&path.match(/([^\/]+|\/)\/*$/)[1],join:(...paths)=>PATH.normalize(paths.join("/")),join2:(l,r)=>PATH.normalize(l+"/"+r)};var initRandomFill=()=>{if(ENVIRONMENT_IS_NODE){var nodeCrypto=require("node:crypto");return view=>nodeCrypto.randomFillSync(view)}return view=>(crypto.getRandomValues(view),0)};var randomFill=view=>(randomFill=initRandomFill())(view);var PATH_FS={resolve:(...args)=>{var resolvedPath="",resolvedAbsolute=false;for(var i=args.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?args[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).slice(1);to=PATH_FS.resolve(to).slice(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var FS_stdin_getChar_buffer=[];var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var intArrayFromString=(stringy,dontAddNull,length)=>{var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array};var FS_stdin_getChar=()=>{if(!FS_stdin_getChar_buffer.length){var result=null;if(ENVIRONMENT_IS_NODE){var BUFSIZE=256;var buf=Buffer.alloc(BUFSIZE);var bytesRead=0;var fd=process.stdin.fd;try{bytesRead=fs.readSync(fd,buf,0,BUFSIZE)}catch(e){if(e.toString().includes("EOF"))bytesRead=0;else throw e}if(bytesRead>0){result=buf.slice(0,bytesRead).toString("utf-8")}}else if(globalThis.window?.prompt){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else{}if(!result){return null}FS_stdin_getChar_buffer=intArrayFromString(result,true)}return FS_stdin_getChar_buffer.shift()};var TTY={ttys:[],init(){},shutdown(){},register(dev,ops){TTY.ttys[dev]={input:[],output:[],ops};FS.registerDevice(dev,TTY.stream_ops)},stream_ops:{open(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty){throw new FS.ErrnoError(43)}stream.tty=tty;stream.seekable=false},close(stream){stream.tty.ops.fsync(stream.tty)},fsync(stream){stream.tty.ops.fsync(stream.tty)},read(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.get_char){throw new FS.ErrnoError(60)}var bytesRead=0;for(var i=0;i0){out(UTF8ArrayToString(tty.output));tty.output=[]}},ioctl_tcgets(tty){return{c_iflag:25856,c_oflag:5,c_cflag:191,c_lflag:35387,c_cc:[3,28,127,21,4,0,1,0,17,19,26,0,18,15,23,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}},ioctl_tcsets(tty,optional_actions,data){return 0},ioctl_tiocgwinsz(tty){return[24,80]}},default_tty1_ops:{put_char(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync(tty){if(tty.output?.length>0){err(UTF8ArrayToString(tty.output));tty.output=[]}}}};var zeroMemory=(ptr,size)=>HEAPU8.fill(0,ptr,ptr+size);var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var mmapAlloc=size=>{size=alignMemory(size,65536);var ptr=_emscripten_builtin_memalign(65536,size);if(ptr)zeroMemory(ptr,size);return ptr};var MEMFS={ops_table:null,mount(mount){return MEMFS.createNode(null,"/",16895,0)},createNode(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}MEMFS.ops_table||={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}};var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=MEMFS.emptyFileContents??=new Uint8Array(0)}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.atime=node.mtime=node.ctime=Date.now();if(parent){parent.contents[name]=node;parent.atime=parent.mtime=parent.ctime=node.atime}return node},getFileDataAsTypedArray(node){return node.contents.subarray(0,node.usedBytes)},expandFileStorage(node,newCapacity){var prevCapacity=node.contents.length;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity)newCapacity=Math.max(newCapacity,256);var oldContents=MEMFS.getFileDataAsTypedArray(node);node.contents=new Uint8Array(newCapacity);node.contents.set(oldContents)},resizeFileStorage(node,newSize){if(node.usedBytes==newSize)return;var oldContents=node.contents;node.contents=new Uint8Array(newSize);node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)));node.usedBytes=newSize},node_ops:{getattr(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.atime);attr.mtime=new Date(node.mtime);attr.ctime=new Date(node.ctime);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr(node,attr){for(const key of["mode","atime","mtime","ctime"]){if(attr[key]!=null){node[key]=attr[key]}}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup(parent,name){if(!MEMFS.doesNotExistError){MEMFS.doesNotExistError=new FS.ErrnoError(44);MEMFS.doesNotExistError.stack=""}throw MEMFS.doesNotExistError},mknod(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename(old_node,new_dir,new_name){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){if(FS.isDir(old_node.mode)){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}FS.hashRemoveNode(new_node)}delete old_node.parent.contents[old_node.name];new_dir.contents[new_name]=old_node;old_node.name=new_name;new_dir.ctime=new_dir.mtime=old_node.parent.ctime=old_node.parent.mtime=Date.now()},unlink(parent,name){delete parent.contents[name];parent.ctime=parent.mtime=Date.now()},rmdir(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.ctime=parent.mtime=Date.now()},readdir(node){return[".","..",...Object.keys(node.contents)]},symlink(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);buffer.set(contents.subarray(position,position+size),offset);return size},write(stream,buffer,offset,length,position,canOwn){if(buffer.buffer===HEAP8.buffer){canOwn=false}if(!length)return 0;var node=stream.node;node.mtime=node.ctime=Date.now();if(canOwn){node.contents=buffer.subarray(offset,offset+length);node.usedBytes=length}else if(node.usedBytes===0&&position===0){node.contents=buffer.slice(offset,offset+length);node.usedBytes=length}else{MEMFS.expandFileStorage(node,position+length);node.contents.set(buffer.subarray(offset,offset+length),position);node.usedBytes=Math.max(node.usedBytes,position+length)}return length},llseek(stream,offset,whence){var position=offset;if(whence===1){position+=stream.position}else if(whence===2){if(FS.isFile(stream.node.mode)){position+=stream.node.usedBytes}}if(position<0){throw new FS.ErrnoError(28)}return position},mmap(stream,length,position,prot,flags){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}var ptr;var allocated;var contents=stream.node.contents;if(!(flags&2)&&contents.buffer===HEAP8.buffer){allocated=false;ptr=contents.byteOffset}else{allocated=true;ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}if(contents){if(position>0||position+length{if(typeof str!="string")return str;var flagModes={r:0,"r+":2,w:512|64|1,"w+":512|64|2,a:1024|64|1,"a+":1024|64|2};var flags=flagModes[str];if(typeof flags=="undefined"){throw new Error(`Unknown file open mode: ${str}`)}return flags};var FS_fileDataToTypedArray=data=>{if(typeof data=="string"){data=intArrayFromString(data,true)}if(!data.subarray){data=new Uint8Array(data)}return data};var FS_getMode=(canRead,canWrite)=>{var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode};var asyncLoad=async url=>{var arrayBuffer=await readAsync(url);return new Uint8Array(arrayBuffer)};var FS_createDataFile=(...args)=>FS.createDataFile(...args);var getUniqueRunDependency=id=>id;var runDependencies=0;var dependenciesFulfilled=null;var removeRunDependency=id=>{runDependencies--;Module["monitorRunDependencies"]?.(runDependencies);if(runDependencies==0){if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}};var addRunDependency=id=>{runDependencies++;Module["monitorRunDependencies"]?.(runDependencies)};var preloadPlugins=[];var FS_handledByPreloadPlugin=async(byteArray,fullname)=>{if(typeof Browser!="undefined")Browser.init();for(var plugin of preloadPlugins){if(plugin["canHandle"](fullname)){return plugin["handle"](byteArray,fullname)}}return byteArray};var FS_preloadFile=async(parent,name,url,canRead,canWrite,dontCreateFile,canOwn,preFinish)=>{var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency(`cp ${fullname}`);addRunDependency(dep);try{var byteArray=url;if(typeof url=="string"){byteArray=await asyncLoad(url)}byteArray=await FS_handledByPreloadPlugin(byteArray,fullname);preFinish?.();if(!dontCreateFile){FS_createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}}finally{removeRunDependency(dep)}};var FS_createPreloadedFile=(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish)=>{FS_preloadFile(parent,name,url,canRead,canWrite,dontCreateFile,canOwn,preFinish).then(onload).catch(onerror)};var FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:false,ignorePermissions:true,filesystems:null,syncFSRequests:0,ErrnoError:class{name="ErrnoError";constructor(errno){this.errno=errno}},FSStream:class{shared={};get object(){return this.node}set object(val){this.node=val}get isRead(){return(this.flags&2097155)!==1}get isWrite(){return(this.flags&2097155)!==0}get isAppend(){return this.flags&1024}get flags(){return this.shared.flags}set flags(val){this.shared.flags=val}get position(){return this.shared.position}set position(val){this.shared.position=val}},FSNode:class{node_ops={};stream_ops={};readMode=292|73;writeMode=146;mounted=null;constructor(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.rdev=rdev;this.atime=this.mtime=this.ctime=Date.now()}get read(){return(this.mode&this.readMode)===this.readMode}set read(val){val?this.mode|=this.readMode:this.mode&=~this.readMode}get write(){return(this.mode&this.writeMode)===this.writeMode}set write(val){val?this.mode|=this.writeMode:this.mode&=~this.writeMode}get isFolder(){return FS.isDir(this.mode)}get isDevice(){return FS.isChrdev(this.mode)}},lookupPath(path,opts={}){if(!path){throw new FS.ErrnoError(44)}opts.follow_mount??=true;if(!PATH.isAbs(path)){path=FS.cwd()+"/"+path}linkloop:for(var nlinks=0;nlinks<40;nlinks++){var parts=path.split("/").filter(p=>!!p);var current=FS.root;var current_path="/";for(var i=0;i>>0)%FS.nameTable.length},hashAddNode(node){var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode(node){var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode(parent,name){var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode(parent,name,mode,rdev){var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode(node){FS.hashRemoveNode(node)},isRoot(node){return node===node.parent},isMountpoint(node){return!!node.mounted},isFile(mode){return(mode&61440)===32768},isDir(mode){return(mode&61440)===16384},isLink(mode){return(mode&61440)===40960},isChrdev(mode){return(mode&61440)===8192},isBlkdev(mode){return(mode&61440)===24576},isFIFO(mode){return(mode&61440)===4096},isSocket(mode){return(mode&49152)===49152},flagsToPermissionString(flag){var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions(node,perms){if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}if(perms.includes("w")&&!(node.mode&146)){return 2}if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup(dir){if(!FS.isDir(dir.mode))return 54;var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate(dir,name){if(!FS.isDir(dir.mode)){return 54}try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else if(FS.isDir(node.mode)){return 31}return 0},mayOpen(node,flags){if(!node){return 44}if(FS.isLink(node.mode)){return 32}var mode=FS.flagsToPermissionString(flags);if(FS.isDir(node.mode)){if(mode!=="r"||flags&(512|64)){return 31}}return FS.nodePermissions(node,mode)},checkOpExists(op,err){if(!op){throw new FS.ErrnoError(err)}return op},MAX_OPEN_FDS:4096,nextfd(){for(var fd=0;fd<=FS.MAX_OPEN_FDS;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStreamChecked(fd){var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}return stream},getStream:fd=>FS.streams[fd],createStream(stream,fd=-1){stream=Object.assign(new FS.FSStream,stream);if(fd==-1){fd=FS.nextfd()}stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream(fd){FS.streams[fd]=null},dupStream(origStream,fd=-1){var stream=FS.createStream(origStream,fd);stream.stream_ops?.dup?.(stream);return stream},doSetAttr(stream,node,attr){var setattr=stream?.stream_ops.setattr;var arg=setattr?stream:node;setattr??=node.node_ops.setattr;FS.checkOpExists(setattr,63);try{setattr(arg,attr)}catch(e){if(e instanceof RangeError){throw new FS.ErrnoError(22)}throw e}},chrdev_stream_ops:{open(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;stream.stream_ops.open?.(stream)},llseek(){throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice(dev,ops){FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts(mount){var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push(...m.mounts)}return mounts},syncfs(populate,callback){if(typeof populate=="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err(`warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work`)}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}for(var mount of mounts){if(mount.type.syncfs){mount.type.syncfs(mount,populate,done)}else{done(null)}}},mount(type,opts,mountpoint){var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type,opts,mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount(mountpoint){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);for(var[hash,current]of Object.entries(FS.nameTable)){while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}}node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup(parent,name){return parent.node_ops.lookup(parent,name)},mknod(path,mode,dev){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name){throw new FS.ErrnoError(28)}if(name==="."||name===".."){throw new FS.ErrnoError(20)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},statfs(path){return FS.statfsNode(FS.lookupPath(path,{follow:true}).node)},statfsStream(stream){return FS.statfsNode(stream.node)},statfsNode(node){var rtn={bsize:4096,frsize:4096,blocks:1e6,bfree:5e5,bavail:5e5,files:FS.nextInode,ffree:FS.nextInode-1,fsid:42,flags:2,namelen:255};if(node.node_ops.statfs){Object.assign(rtn,node.node_ops.statfs(node.mount.opts.root))}return rtn},create(path,mode=438){mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir(path,mode=511){mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree(path,mode){var dirs=path.split("/");var d="";for(var dir of dirs){if(!dir)continue;if(d||PATH.isAbs(path))d+="/";d+=dir;try{FS.mkdir(d,mode)}catch(e){if(e.errno!=20)throw e}}},mkdev(path,mode,dev){if(typeof dev=="undefined"){dev=mode;mode=438}mode|=8192;return FS.mknod(path,mode,dev)},symlink(oldpath,newpath){if(!PATH_FS.resolve(oldpath)){throw new FS.ErrnoError(44)}var lookup=FS.lookupPath(newpath,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var newname=PATH.basename(newpath);var errCode=FS.mayCreate(parent,newname);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.symlink){throw new FS.ErrnoError(63)}return parent.node_ops.symlink(parent,newname,oldpath)},rename(old_path,new_path){var old_dirname=PATH.dirname(old_path);var new_dirname=PATH.dirname(new_path);var old_name=PATH.basename(old_path);var new_name=PATH.basename(new_path);var lookup,old_dir,new_dir;lookup=FS.lookupPath(old_path,{parent:true});old_dir=lookup.node;lookup=FS.lookupPath(new_path,{parent:true});new_dir=lookup.node;if(!old_dir||!new_dir)throw new FS.ErrnoError(44);if(old_dir.mount!==new_dir.mount){throw new FS.ErrnoError(75)}var old_node=FS.lookupNode(old_dir,old_name);var relative=PATH_FS.relative(old_path,new_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(28)}relative=PATH_FS.relative(new_path,old_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(55)}var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(old_node===new_node){return}var isdir=FS.isDir(old_node.mode);var errCode=FS.mayDelete(old_dir,old_name,isdir);if(errCode){throw new FS.ErrnoError(errCode)}errCode=new_node?FS.mayDelete(new_dir,new_name,isdir):FS.mayCreate(new_dir,new_name);if(errCode){throw new FS.ErrnoError(errCode)}if(!old_dir.node_ops.rename){throw new FS.ErrnoError(63)}if(FS.isMountpoint(old_node)||new_node&&FS.isMountpoint(new_node)){throw new FS.ErrnoError(10)}if(new_dir!==old_dir){errCode=FS.nodePermissions(old_dir,"w");if(errCode){throw new FS.ErrnoError(errCode)}}FS.hashRemoveNode(old_node);try{old_dir.node_ops.rename(old_node,new_dir,new_name);old_node.parent=new_dir}catch(e){throw e}finally{FS.hashAddNode(old_node)}},rmdir(path){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,true);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.rmdir){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.rmdir(parent,name);FS.destroyNode(node)},readdir(path){var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;var readdir=FS.checkOpExists(node.node_ops.readdir,54);return readdir(node)},unlink(path){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,false);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.unlink){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.unlink(parent,name);FS.destroyNode(node)},readlink(path){var lookup=FS.lookupPath(path);var link=lookup.node;if(!link){throw new FS.ErrnoError(44)}if(!link.node_ops.readlink){throw new FS.ErrnoError(28)}return link.node_ops.readlink(link)},stat(path,dontFollow){var lookup=FS.lookupPath(path,{follow:!dontFollow});var node=lookup.node;var getattr=FS.checkOpExists(node.node_ops.getattr,63);return getattr(node)},fstat(fd){var stream=FS.getStreamChecked(fd);var node=stream.node;var getattr=stream.stream_ops.getattr;var arg=getattr?stream:node;getattr??=node.node_ops.getattr;FS.checkOpExists(getattr,63);return getattr(arg)},lstat(path){return FS.stat(path,true)},doChmod(stream,node,mode,dontFollow){FS.doSetAttr(stream,node,{mode:mode&4095|node.mode&~4095,ctime:Date.now(),dontFollow})},chmod(path,mode,dontFollow){var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}FS.doChmod(null,node,mode,dontFollow)},lchmod(path,mode){FS.chmod(path,mode,true)},fchmod(fd,mode){var stream=FS.getStreamChecked(fd);FS.doChmod(stream,stream.node,mode,false)},doChown(stream,node,dontFollow){FS.doSetAttr(stream,node,{timestamp:Date.now(),dontFollow})},chown(path,uid,gid,dontFollow){var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}FS.doChown(null,node,dontFollow)},lchown(path,uid,gid){FS.chown(path,uid,gid,true)},fchown(fd,uid,gid){var stream=FS.getStreamChecked(fd);FS.doChown(stream,stream.node,false)},doTruncate(stream,node,len){if(FS.isDir(node.mode)){throw new FS.ErrnoError(31)}if(!FS.isFile(node.mode)){throw new FS.ErrnoError(28)}var errCode=FS.nodePermissions(node,"w");if(errCode){throw new FS.ErrnoError(errCode)}FS.doSetAttr(stream,node,{size:len,timestamp:Date.now()})},truncate(path,len){if(len<0){throw new FS.ErrnoError(28)}var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:true});node=lookup.node}else{node=path}FS.doTruncate(null,node,len)},ftruncate(fd,len){var stream=FS.getStreamChecked(fd);if(len<0||(stream.flags&2097155)===0){throw new FS.ErrnoError(28)}FS.doTruncate(stream,stream.node,len)},utime(path,atime,mtime){var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;var setattr=FS.checkOpExists(node.node_ops.setattr,63);setattr(node,{atime,mtime})},open(path,flags,mode=438){if(path===""){throw new FS.ErrnoError(44)}flags=FS_modeStringToFlags(flags);if(flags&64){mode=mode&4095|32768}else{mode=0}var node;var isDirPath;if(typeof path=="object"){node=path}else{isDirPath=path.endsWith("/");var lookup=FS.lookupPath(path,{follow:!(flags&131072),noent_okay:true});node=lookup.node;path=lookup.path}var created=false;if(flags&64){if(node){if(flags&128){throw new FS.ErrnoError(20)}}else if(isDirPath){throw new FS.ErrnoError(31)}else{node=FS.mknod(path,mode|511,0);created=true}}if(!node){throw new FS.ErrnoError(44)}if(FS.isChrdev(node.mode)){flags&=~512}if(flags&65536&&!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}if(!created){var errCode=FS.mayOpen(node,flags);if(errCode){throw new FS.ErrnoError(errCode)}}if(flags&512&&!created){FS.truncate(node,0)}flags&=~(128|512|131072);var stream=FS.createStream({node,path:FS.getPath(node),flags,seekable:true,position:0,stream_ops:node.stream_ops,ungotten:[],error:false});if(stream.stream_ops.open){stream.stream_ops.open(stream)}if(created){FS.chmod(node,mode&511)}return stream},close(stream){if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(stream.getdents)stream.getdents=null;try{if(stream.stream_ops.close){stream.stream_ops.close(stream)}}catch(e){throw e}finally{FS.closeStream(stream.fd)}stream.fd=null},isClosed(stream){return stream.fd===null},llseek(stream,offset,whence){if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(!stream.seekable||!stream.stream_ops.llseek){throw new FS.ErrnoError(70)}if(whence!=0&&whence!=1&&whence!=2){throw new FS.ErrnoError(28)}stream.position=stream.stream_ops.llseek(stream,offset,whence);stream.ungotten=[];return stream.position},read(stream,buffer,offset,length,position){if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.read){throw new FS.ErrnoError(28)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesRead=stream.stream_ops.read(stream,buffer,offset,length,position);if(!seeking)stream.position+=bytesRead;return bytesRead},write(stream,buffer,offset,length,position,canOwn){if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.write){throw new FS.ErrnoError(28)}if(stream.seekable&&stream.flags&1024){FS.llseek(stream,0,2)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesWritten=stream.stream_ops.write(stream,buffer,offset,length,position,canOwn);if(!seeking)stream.position+=bytesWritten;return bytesWritten},mmap(stream,length,position,prot,flags){if((prot&2)!==0&&(flags&2)===0&&(stream.flags&2097155)!==2){throw new FS.ErrnoError(2)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(2)}if(!stream.stream_ops.mmap){throw new FS.ErrnoError(43)}if(!length){throw new FS.ErrnoError(28)}return stream.stream_ops.mmap(stream,length,position,prot,flags)},msync(stream,buffer,offset,length,mmapFlags){if(!stream.stream_ops.msync){return 0}return stream.stream_ops.msync(stream,buffer,offset,length,mmapFlags)},ioctl(stream,cmd,arg){if(!stream.stream_ops.ioctl){throw new FS.ErrnoError(59)}return stream.stream_ops.ioctl(stream,cmd,arg)},readFile(path,opts={}){opts.flags=opts.flags||0;opts.encoding=opts.encoding||"binary";if(opts.encoding!=="utf8"&&opts.encoding!=="binary"){abort(`Invalid encoding type "${opts.encoding}"`)}var stream=FS.open(path,opts.flags);var stat=FS.stat(path);var length=stat.size;var buf=new Uint8Array(length);FS.read(stream,buf,0,length,0);if(opts.encoding==="utf8"){buf=UTF8ArrayToString(buf)}FS.close(stream);return buf},writeFile(path,data,opts={}){opts.flags=opts.flags||577;var stream=FS.open(path,opts.flags,opts.mode);data=FS_fileDataToTypedArray(data);FS.write(stream,data,0,data.byteLength,undefined,opts.canOwn);FS.close(stream)},cwd:()=>FS.currentPath,chdir(path){var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories(){FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices(){FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length,llseek:()=>0});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var randomBuffer=new Uint8Array(1024),randomLeft=0;var randomByte=()=>{if(randomLeft===0){randomFill(randomBuffer);randomLeft=randomBuffer.byteLength}return randomBuffer[--randomLeft]};FS.createDevice("/dev","random",randomByte);FS.createDevice("/dev","urandom",randomByte);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories(){FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount(){var node=FS.createNode(proc_self,"fd",16895,73);node.stream_ops={llseek:MEMFS.stream_ops.llseek};node.node_ops={lookup(parent,name){var fd=+name;var stream=FS.getStreamChecked(fd);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path},id:fd+1};ret.parent=ret;return ret},readdir(){return Array.from(FS.streams.entries()).filter(([k,v])=>v).map(([k,v])=>k.toString())}};return node}},{},"/proc/self/fd")},createStandardStreams(input,output,error){if(input){FS.createDevice("/dev","stdin",input)}else{FS.symlink("/dev/tty","/dev/stdin")}if(output){FS.createDevice("/dev","stdout",null,output)}else{FS.symlink("/dev/tty","/dev/stdout")}if(error){FS.createDevice("/dev","stderr",null,error)}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1)},staticInit(){FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={MEMFS}},init(input,output,error){FS.initialized=true;input??=Module["stdin"];output??=Module["stdout"];error??=Module["stderr"];FS.createStandardStreams(input,output,error)},quit(){FS.initialized=false;for(var stream of FS.streams){if(stream){FS.close(stream)}}},findObject(path,dontResolveLastLink){var ret=FS.analyzePath(path,dontResolveLastLink);if(!ret.exists){return null}return ret.object},analyzePath(path,dontResolveLastLink){try{var lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});path=lookup.path}catch(e){}var ret={isRoot:false,exists:false,error:0,name:null,path:null,object:null,parentExists:false,parentPath:null,parentObject:null};try{var lookup=FS.lookupPath(path,{parent:true});ret.parentExists=true;ret.parentPath=lookup.path;ret.parentObject=lookup.node;ret.name=PATH.basename(path);lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});ret.exists=true;ret.path=lookup.path;ret.object=lookup.node;ret.name=lookup.node.name;ret.isRoot=lookup.path==="/"}catch(e){ret.error=e.errno}return ret},createPath(parent,path,canRead,canWrite){parent=typeof parent=="string"?parent:FS.getPath(parent);var parts=path.split("/").reverse();while(parts.length){var part=parts.pop();if(!part)continue;var current=PATH.join2(parent,part);try{FS.mkdir(current)}catch(e){if(e.errno!=20)throw e}parent=current}return current},createFile(parent,name,properties,canRead,canWrite){var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS_getMode(canRead,canWrite);return FS.create(path,mode)},createDataFile(parent,name,data,canRead,canWrite,canOwn){var path=name;if(parent){parent=typeof parent=="string"?parent:FS.getPath(parent);path=name?PATH.join2(parent,name):parent}var mode=FS_getMode(canRead,canWrite);var node=FS.create(path,mode);if(data){data=FS_fileDataToTypedArray(data);FS.chmod(node,mode|146);var stream=FS.open(node,577);FS.write(stream,data,0,data.length,0,canOwn);FS.close(stream);FS.chmod(node,mode)}},createDevice(parent,name,input,output){var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS_getMode(!!input,!!output);FS.createDevice.major??=64;var dev=FS.makedev(FS.createDevice.major++,0);FS.registerDevice(dev,{open(stream){stream.seekable=false},close(stream){if(output?.buffer?.length){output(10)}},read(stream,buffer,offset,length,pos){var bytesRead=0;for(var i=0;ithis.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]}setDataGetter(getter){this.getter=getter}cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))abort("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)abort("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)abort("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))abort("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}return intArrayFromString(xhr.responseText||"",true)};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")abort("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true}get length(){if(!this.lengthKnown){this.cacheLength()}return this._length}get chunkSize(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}if(globalThis.XMLHttpRequest){if(!ENVIRONMENT_IS_WORKER)abort("Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc");var lazyArray=new LazyUint8Array;var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};for(const[key,fn]of Object.entries(node.stream_ops)){stream_ops[key]=(...args)=>{FS.forceLoadFile(node);return fn(...args)}}function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr,allocated:true}};node.stream_ops=stream_ops;return node}};var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead,ignoreNul):"";var SYSCALLS={currentUmask:18,calculateAt(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=SYSCALLS.getStreamFromFD(dirfd);dir=dirstream.path}if(path.length==0){if(!allowEmpty){throw new FS.ErrnoError(44)}return dir}return dir+"/"+path},writeStat(buf,stat){HEAPU32[buf>>2]=stat.dev;HEAPU32[buf+4>>2]=stat.mode;HEAPU32[buf+8>>2]=stat.nlink;HEAPU32[buf+12>>2]=stat.uid;HEAPU32[buf+16>>2]=stat.gid;HEAPU32[buf+20>>2]=stat.rdev;HEAP64[buf+24>>3]=BigInt(stat.size);HEAP32[buf+32>>2]=4096;HEAP32[buf+36>>2]=stat.blocks;var atime=stat.atime.getTime();var mtime=stat.mtime.getTime();var ctime=stat.ctime.getTime();HEAP64[buf+40>>3]=BigInt(Math.floor(atime/1e3));HEAPU32[buf+48>>2]=atime%1e3*1e3*1e3;HEAP64[buf+56>>3]=BigInt(Math.floor(mtime/1e3));HEAPU32[buf+64>>2]=mtime%1e3*1e3*1e3;HEAP64[buf+72>>3]=BigInt(Math.floor(ctime/1e3));HEAPU32[buf+80>>2]=ctime%1e3*1e3*1e3;HEAP64[buf+88>>3]=BigInt(stat.ino);return 0},writeStatFs(buf,stats){HEAPU32[buf+4>>2]=stats.bsize;HEAPU32[buf+60>>2]=stats.bsize;HEAP64[buf+8>>3]=BigInt(stats.blocks);HEAP64[buf+16>>3]=BigInt(stats.bfree);HEAP64[buf+24>>3]=BigInt(stats.bavail);HEAP64[buf+32>>3]=BigInt(stats.files);HEAP64[buf+40>>3]=BigInt(stats.ffree);HEAPU32[buf+48>>2]=stats.fsid;HEAPU32[buf+64>>2]=stats.flags;HEAPU32[buf+56>>2]=stats.namelen},doMsync(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}if(flags&2){return 0}var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},getStreamFromFD(fd){var stream=FS.getStreamChecked(fd);return stream},varargs:undefined,getStr(ptr){var ret=UTF8ToString(ptr);return ret}};function ___syscall_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=syscallGetVarargI();if(arg<0){return-28}while(FS.streams[arg]){arg++}var newStream;newStream=FS.dupStream(stream,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=syscallGetVarargI();var mask=289792;stream.flags=stream.flags&~mask|arg&mask;return 0}case 12:{var arg=syscallGetVarargP();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 13:case 14:return 0}return-28}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_fstat64(fd,buf){try{return SYSCALLS.writeStat(buf,FS.fstat(fd))}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);function ___syscall_getdents64(fd,dirp,count){try{var stream=SYSCALLS.getStreamFromFD(fd);stream.getdents||=FS.readdir(stream.path);var struct_size=280;var pos=0;var off=FS.llseek(stream,0,1);var startIdx=Math.floor(off/struct_size);var endIdx=Math.min(stream.getdents.length,startIdx+Math.floor(count/struct_size));for(var idx=startIdx;idx>3]=BigInt(id);HEAP64[dirp+pos+8>>3]=BigInt((idx+1)*struct_size);HEAP16[dirp+pos+16>>1]=280;HEAP8[dirp+pos+18]=type;stringToUTF8(name,dirp+pos+19,256);pos+=struct_size}FS.llseek(stream,idx*struct_size,0);return pos}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:{if(!stream.tty)return-59;return 0}case 21505:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcgets){var termios=stream.tty.ops.ioctl_tcgets(stream);var argp=syscallGetVarargP();HEAP32[argp>>2]=termios.c_iflag||0;HEAP32[argp+4>>2]=termios.c_oflag||0;HEAP32[argp+8>>2]=termios.c_cflag||0;HEAP32[argp+12>>2]=termios.c_lflag||0;for(var i=0;i<32;i++){HEAP8[argp+i+17]=termios.c_cc[i]||0}return 0}return 0}case 21510:case 21511:case 21512:{if(!stream.tty)return-59;return 0}case 21506:case 21507:case 21508:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcsets){var argp=syscallGetVarargP();var c_iflag=HEAP32[argp>>2];var c_oflag=HEAP32[argp+4>>2];var c_cflag=HEAP32[argp+8>>2];var c_lflag=HEAP32[argp+12>>2];var c_cc=[];for(var i=0;i<32;i++){c_cc.push(HEAP8[argp+i+17])}return stream.tty.ops.ioctl_tcsets(stream.tty,op,{c_iflag,c_oflag,c_cflag,c_lflag,c_cc})}return 0}case 21519:{if(!stream.tty)return-59;var argp=syscallGetVarargP();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21537:case 21531:{var argp=syscallGetVarargP();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tiocgwinsz){var winsize=stream.tty.ops.ioctl_tiocgwinsz(stream.tty);var argp=syscallGetVarargP();HEAP16[argp>>1]=winsize[0];HEAP16[argp+2>>1]=winsize[1]}return 0}case 21524:{if(!stream.tty)return-59;return 0}case 21515:{if(!stream.tty)return-59;return 0}default:return-28}}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_lstat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.writeStat(buf,FS.lstat(path))}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_newfstatat(dirfd,path,buf,flags){try{path=SYSCALLS.getStr(path);var nofollow=flags&256;var allowEmpty=flags&4096;flags=flags&~6400;path=SYSCALLS.calculateAt(dirfd,path,allowEmpty);return SYSCALLS.writeStat(buf,nofollow?FS.lstat(path):FS.stat(path))}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_openat(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);var mode=varargs?syscallGetVarargI():0;if(flags&64){mode&=~SYSCALLS.currentUmask}return FS.open(path,flags,mode).fd}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_rmdir(path){try{path=SYSCALLS.getStr(path);FS.rmdir(path);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_stat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.writeStat(buf,FS.stat(path))}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_unlinkat(dirfd,path,flags){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);if(!flags){FS.unlink(path)}else if(flags===512){FS.rmdir(path)}else{return-28}return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var __abort_js=()=>abort("");var __emscripten_fs_load_embedded_files=ptr=>{do{var name_addr=HEAPU32[ptr>>2];ptr+=4;var len=HEAPU32[ptr>>2];ptr+=4;var content=HEAPU32[ptr>>2];ptr+=4;var name=UTF8ToString(name_addr);FS.createPath("/",PATH.dirname(name),true,true);FS.createDataFile(name,null,HEAP8.subarray(content,content+len),true,true,true)}while(HEAPU32[ptr>>2])};var __emscripten_throw_longjmp=()=>{throw new EmscriptenSjLj};var INT53_MAX=9007199254740992;var INT53_MIN=-9007199254740992;var bigintToI53Checked=num=>numINT53_MAX?NaN:Number(num);function __gmtime_js(time,tmPtr){time=bigintToI53Checked(time);var date=new Date(time*1e3);HEAP32[tmPtr>>2]=date.getUTCSeconds();HEAP32[tmPtr+4>>2]=date.getUTCMinutes();HEAP32[tmPtr+8>>2]=date.getUTCHours();HEAP32[tmPtr+12>>2]=date.getUTCDate();HEAP32[tmPtr+16>>2]=date.getUTCMonth();HEAP32[tmPtr+20>>2]=date.getUTCFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getUTCDay();var start=Date.UTC(date.getUTCFullYear(),0,1,0,0,0,0);var yday=(date.getTime()-start)/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday}var isLeapYear=year=>year%4===0&&(year%100!==0||year%400===0);var MONTH_DAYS_LEAP_CUMULATIVE=[0,31,60,91,121,152,182,213,244,274,305,335];var MONTH_DAYS_REGULAR_CUMULATIVE=[0,31,59,90,120,151,181,212,243,273,304,334];var ydayFromDate=date=>{var leap=isLeapYear(date.getFullYear());var monthDaysCumulative=leap?MONTH_DAYS_LEAP_CUMULATIVE:MONTH_DAYS_REGULAR_CUMULATIVE;var yday=monthDaysCumulative[date.getMonth()]+date.getDate()-1;return yday};function __localtime_js(time,tmPtr){time=bigintToI53Checked(time);var date=new Date(time*1e3);HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getDay();var yday=ydayFromDate(date)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr+36>>2]=-(date.getTimezoneOffset()*60);var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dst=(summerOffset!=winterOffset&&date.getTimezoneOffset()==Math.min(winterOffset,summerOffset))|0;HEAP32[tmPtr+32>>2]=dst}function __mmap_js(len,prot,flags,fd,offset,allocated,addr){offset=bigintToI53Checked(offset);try{var stream=SYSCALLS.getStreamFromFD(fd);var res=FS.mmap(stream,len,offset,prot,flags);var ptr=res.ptr;HEAP32[allocated>>2]=res.allocated;HEAPU32[addr>>2]=ptr;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function __munmap_js(addr,len,prot,flags,fd,offset){offset=bigintToI53Checked(offset);try{var stream=SYSCALLS.getStreamFromFD(fd);if(prot&2){SYSCALLS.doMsync(addr,stream,len,flags,offset)}}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var __tzset_js=(timezone,daylight,std_name,dst_name)=>{var currentYear=(new Date).getFullYear();var winter=new Date(currentYear,0,1);var summer=new Date(currentYear,6,1);var winterOffset=winter.getTimezoneOffset();var summerOffset=summer.getTimezoneOffset();var stdTimezoneOffset=Math.max(winterOffset,summerOffset);HEAPU32[timezone>>2]=stdTimezoneOffset*60;HEAP32[daylight>>2]=Number(winterOffset!=summerOffset);var extractZone=timezoneOffset=>{var sign=timezoneOffset>=0?"-":"+";var absOffset=Math.abs(timezoneOffset);var hours=String(Math.floor(absOffset/60)).padStart(2,"0");var minutes=String(absOffset%60).padStart(2,"0");return`UTC${sign}${hours}${minutes}`};var winterName=extractZone(winterOffset);var summerName=extractZone(summerOffset);if(summerOffsetperformance.now();var _emscripten_date_now=()=>Date.now();var nowIsMonotonic=1;var checkWasiClock=clock_id=>clock_id>=0&&clock_id<=3;function _clock_time_get(clk_id,ignored_precision,ptime){ignored_precision=bigintToI53Checked(ignored_precision);if(!checkWasiClock(clk_id)){return 28}var now;if(clk_id===0){now=_emscripten_date_now()}else if(nowIsMonotonic){now=_emscripten_get_now()}else{return 52}var nsec=Math.round(now*1e3*1e3);HEAP64[ptime>>3]=BigInt(nsec);return 0}var getHeapMax=()=>2147483648;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var ENV={};var getExecutableName=()=>thisProgram||"./this.program";var getEnvStrings=()=>{if(!getEnvStrings.strings){var lang=(globalThis.navigator?.language??"C").replace("-","_")+".UTF-8";var env={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:lang,_:getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(`${x}=${env[x]}`)}getEnvStrings.strings=strings}return getEnvStrings.strings};var _environ_get=(__environ,environ_buf)=>{var bufSize=0;var envp=0;for(var string of getEnvStrings()){var ptr=environ_buf+bufSize;HEAPU32[__environ+envp>>2]=ptr;bufSize+=stringToUTF8(string,ptr,Infinity)+1;envp+=4}return 0};var _environ_sizes_get=(penviron_count,penviron_buf_size)=>{var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;for(var string of strings){bufSize+=lengthBytesUTF8(string)+1}HEAPU32[penviron_buf_size>>2]=bufSize;return 0};var runtimeKeepaliveCounter=0;var keepRuntimeAlive=()=>noExitRuntime||runtimeKeepaliveCounter>0;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function _fd_fdstat_get(fd,pbuf){try{var rightsBase=0;var rightsInheriting=0;var flags=0;{var stream=SYSCALLS.getStreamFromFD(fd);var type=stream.tty?2:FS.isDir(stream.mode)?3:FS.isLink(stream.mode)?7:4}HEAP8[pbuf]=type;HEAP16[pbuf+2>>1]=flags;HEAP64[pbuf+8>>3]=BigInt(rightsBase);HEAP64[pbuf+16>>3]=BigInt(rightsInheriting);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var doReadv=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function _fd_seek(fd,offset,whence,newOffset){offset=bigintToI53Checked(offset);try{if(isNaN(offset))return 22;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);HEAP64[newOffset>>3]=BigInt(stream.position);if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var doWritev=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var wasmTableMirror=[];var getWasmTableEntry=funcPtr=>{var func=wasmTableMirror[funcPtr];if(!func){wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func};var getCFunc=ident=>{var func=Module["_"+ident];return func};var writeArrayToMemory=(array,buffer)=>{HEAP8.set(array,buffer)};var stackAlloc=sz=>__emscripten_stack_alloc(sz);var stringToUTF8OnStack=str=>{var size=lengthBytesUTF8(str)+1;var ret=stackAlloc(size);stringToUTF8(str,ret,size);return ret};var ccall=(ident,returnType,argTypes,args,opts)=>{var toC={string:str=>{var ret=0;if(str!==null&&str!==undefined&&str!==0){ret=stringToUTF8OnStack(str)}return ret},array:arr=>{var ret=stackAlloc(arr.length);writeArrayToMemory(arr,ret);return ret}};function convertReturnValue(ret){if(returnType==="string"){return UTF8ToString(ret)}if(returnType==="boolean")return Boolean(ret);return ret}var func=getCFunc(ident);var cArgs=[];var stack=0;if(args){for(var i=0;i{var numericArgs=!argTypes||argTypes.every(type=>type==="number"||type==="boolean");var numericRet=returnType!=="string";if(numericRet&&numericArgs&&!opts){return getCFunc(ident)}return(...args)=>ccall(ident,returnType,argTypes,args,opts)};var FS_createPath=(...args)=>FS.createPath(...args);var FS_unlink=(...args)=>FS.unlink(...args);var FS_createLazyFile=(...args)=>FS.createLazyFile(...args);var FS_createDevice=(...args)=>FS.createDevice(...args);FS.createPreloadedFile=FS_createPreloadedFile;FS.preloadFile=FS_preloadFile;FS.staticInit();{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["preloadPlugins"])preloadPlugins=Module["preloadPlugins"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].shift()()}}}Module["addRunDependency"]=addRunDependency;Module["removeRunDependency"]=removeRunDependency;Module["ccall"]=ccall;Module["cwrap"]=cwrap;Module["setValue"]=setValue;Module["getValue"]=getValue;Module["FS_preloadFile"]=FS_preloadFile;Module["FS_unlink"]=FS_unlink;Module["FS_createPath"]=FS_createPath;Module["FS_createDevice"]=FS_createDevice;Module["FS_createDataFile"]=FS_createDataFile;Module["FS_createLazyFile"]=FS_createLazyFile;var _loadDocument,_pageCount,_renderPagePng,_previewRender,_previewRenderRegion,_previewRenderPaginated,_lastRegionCount,_lastRegionPtr,_lastRegionW,_lastRegionH,_lastRegionPage,_lastRegionYTop,_lastRenderPtr,_lastRenderW,_lastRenderH,_lastLayoutJson,_registerAuxFont,_freeDocument,_engineBuildInfo,_free,_malloc,_emscripten_builtin_memalign,_setThrew,__emscripten_stack_restore,__emscripten_stack_alloc,_emscripten_stack_get_current,memory,__indirect_function_table,wasmMemory,wasmTable;function assignWasmExports(wasmExports){_loadDocument=Module["_loadDocument"]=wasmExports["S"];_pageCount=Module["_pageCount"]=wasmExports["U"];_renderPagePng=Module["_renderPagePng"]=wasmExports["V"];_previewRender=Module["_previewRender"]=wasmExports["W"];_previewRenderRegion=Module["_previewRenderRegion"]=wasmExports["X"];_previewRenderPaginated=Module["_previewRenderPaginated"]=wasmExports["Y"];_lastRegionCount=Module["_lastRegionCount"]=wasmExports["Z"];_lastRegionPtr=Module["_lastRegionPtr"]=wasmExports["_"];_lastRegionW=Module["_lastRegionW"]=wasmExports["$"];_lastRegionH=Module["_lastRegionH"]=wasmExports["aa"];_lastRegionPage=Module["_lastRegionPage"]=wasmExports["ba"];_lastRegionYTop=Module["_lastRegionYTop"]=wasmExports["ca"];_lastRenderPtr=Module["_lastRenderPtr"]=wasmExports["da"];_lastRenderW=Module["_lastRenderW"]=wasmExports["ea"];_lastRenderH=Module["_lastRenderH"]=wasmExports["fa"];_lastLayoutJson=Module["_lastLayoutJson"]=wasmExports["ga"];_registerAuxFont=Module["_registerAuxFont"]=wasmExports["ha"];_freeDocument=Module["_freeDocument"]=wasmExports["ia"];_engineBuildInfo=Module["_engineBuildInfo"]=wasmExports["ja"];_free=Module["_free"]=wasmExports["ka"];_malloc=Module["_malloc"]=wasmExports["la"];_emscripten_builtin_memalign=wasmExports["ma"];_setThrew=wasmExports["na"];__emscripten_stack_restore=wasmExports["oa"];__emscripten_stack_alloc=wasmExports["pa"];_emscripten_stack_get_current=wasmExports["qa"];memory=wasmMemory=wasmExports["Q"];__indirect_function_table=wasmTable=wasmExports["T"]}var wasmImports={a:___cxa_throw,i:___syscall_fcntl64,I:___syscall_fstat64,w:___syscall_getdents64,M:___syscall_ioctl,F:___syscall_lstat64,G:___syscall_newfstatat,k:___syscall_openat,u:___syscall_rmdir,H:___syscall_stat64,v:___syscall_unlinkat,K:__abort_js,N:__emscripten_fs_load_embedded_files,s:__emscripten_throw_longjmp,z:__gmtime_js,A:__localtime_js,x:__mmap_js,y:__munmap_js,L:__tzset_js,J:_clock_time_get,l:_emscripten_date_now,t:_emscripten_resize_heap,D:_environ_get,E:_environ_sizes_get,O:_exit,g:_fd_close,C:_fd_fdstat_get,n:_fd_read,B:_fd_seek,m:_fd_write,d:invoke_ii,f:invoke_iii,h:invoke_iiii,j:invoke_iiiii,P:invoke_iiiiiii,o:invoke_iiiiiiiiii,p:invoke_v,c:invoke_vi,b:invoke_vii,e:invoke_viii,r:invoke_viiii,q:invoke_viiiiiiiii};function invoke_iiiii(index,a1,a2,a3,a4){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(!(e instanceof EmscriptenEH))throw e;_setThrew(1,0)}}function invoke_ii(index,a1){var sp=stackSave();try{return getWasmTableEntry(index)(a1)}catch(e){stackRestore(sp);if(!(e instanceof EmscriptenEH))throw e;_setThrew(1,0)}}function invoke_iiii(index,a1,a2,a3){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(!(e instanceof EmscriptenEH))throw e;_setThrew(1,0)}}function invoke_vii(index,a1,a2){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(!(e instanceof EmscriptenEH))throw e;_setThrew(1,0)}}function invoke_viiii(index,a1,a2,a3,a4){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(!(e instanceof EmscriptenEH))throw e;_setThrew(1,0)}}function invoke_viiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(!(e instanceof EmscriptenEH))throw e;_setThrew(1,0)}}function invoke_vi(index,a1){var sp=stackSave();try{getWasmTableEntry(index)(a1)}catch(e){stackRestore(sp);if(!(e instanceof EmscriptenEH))throw e;_setThrew(1,0)}}function invoke_v(index){var sp=stackSave();try{getWasmTableEntry(index)()}catch(e){stackRestore(sp);if(!(e instanceof EmscriptenEH))throw e;_setThrew(1,0)}}function invoke_iii(index,a1,a2){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(!(e instanceof EmscriptenEH))throw e;_setThrew(1,0)}}function invoke_viii(index,a1,a2,a3){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(!(e instanceof EmscriptenEH))throw e;_setThrew(1,0)}}function invoke_iiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(!(e instanceof EmscriptenEH))throw e;_setThrew(1,0)}}function invoke_iiiiiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(!(e instanceof EmscriptenEH))throw e;_setThrew(1,0)}}function run(){if(runDependencies>0){dependenciesFulfilled=run;return}preRun();if(runDependencies>0){dependenciesFulfilled=run;return}function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve?.(Module);Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}var wasmExports;wasmExports=await (createWasm());run();if(runtimeInitialized){moduleRtn=Module}else{moduleRtn=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject})} ;return moduleRtn}export default Module; diff --git a/frontend/public/pdfium-engine.wasm b/frontend/public/pdfium-engine.wasm index b0a054558f5c8adc6efde0c0ebf72745095d6365..a1db614bc61a35498a7c20adfa59a9495976aae9 100644 GIT binary patch delta 612424 zcmbq+2VfM(_4m&1RozKC^?G-Ed#CG7#{FDaVmq-NM{aRT9GmoD$0c^0IB|Ruh%SL( zfu#r`y68oK=p95yAkmxXP4p)E_nSRQfJ^@0_y104cjnETH*em1^WK};JyCtSPzhorC7akRfhrrt zV1!XKgw0}rr8KBI&}K?j!+>N*wT2~X)G;Dq(p@~qWw9u$)dWm|3l*zTfh|gxsFbd( zM$4%{6XPrVpaD1>(Qs}J1pUPiri%}wAw>0vMx`PuEyh4+!Z3Ok^3i}EYsXZz8IUsf zJ1~k)td}ENJ)! zstnQT)EtYJAZrKbQj#tw`k0r>1XQ5`$bw3hQE7h9CMAKhKl~AtFl7^PRLsLH+BK~D zdB{KmIvnQ%F_xwBMOAAg5GbVp>1YU}!gO$tQ^!FmfM{L_{7Lmw6$6t%4@PB#5$L0o zag0RCXdn?fX;#fXmrON5N1fL z_RrvisMKJUWq(FEJ#ax6DENntM5ksGK_d!{dO%V`2O(PMoR**+J(LgIg56W8Fq+Iv zj0X|2d4I7#LNw45dVx1q&8aj{7YC~VwoheNsbPtVLYT){9S5GFMlHzWqXP=Zc`#jS zb&yV!WuG7#tyT}oQY(SnfB?c&A}s9*O+pU%XtE#!%05s#>FAZtPzRa84s;x&C#))i zV;CJVQq^PXD)yr%G43M_$bmSubi!%0nvX$Wuf?j0OH8z~++P_^r`G8)Qlds`ANOLE z(#{}p;6$T^!od*OL2pj;GS)g^R7qe~i{%HAKu_RZodu1lIIL4O(W!JAqmgBZHW4#d zs~`{v3Tqfjq6+zh)oVXx*hGk%A$lDsfSgJ7bCW(%l}Msg7a&PT<%o%}X7&|AodBmv zraHH2HE|eRL%D+xApbaYz|^P~Gc=r$;j|2Ago$A3eWqp78I?+{fiHlGf6l(FfwN%Z zFh>&PG)9OeR{bUvL%Z{J3KO zTD?XOBKWY{bZNaI=Ur$}u{i2bsIQ@hu19yOLyj}j=>Zvb&#-!!Bz%lYt%cK}_;E%f zr)6PFtXc(rI5qr^T1QP9Dwm!yTBCncTQRmWOD(HY8JRee6`uu$=)ZKvsMWv;QnooQ zRUb#bP(7i?KMYA-4Ll7?zI5rd?2~$Eh<%dDf)~JGiXmbmRH&R9lS!g;q7I1so#^#2 zAU$j%@(#6PY84O(CPGbqMbkIjAuk1e)8SU2+mmoy-#0*_>EF{6E z!;pyXUl7{Azfn{M9}m`*E7k<&q*v>c=;+`9(t_K70jdGls4z|R)xx!_IiiDG*V09+ zqZ-CKfCi1wzZybT>rD)&X8=JH6NNJ35W{pj9n67&8FCzoaQ(12YGxWPiA{vipa$yk zSXQs+OlqT19Y>8D>kbXLs{TXus)x)tJ^TUV#a*S>AXwm&$Z6p6=nzmmyeO7qd^%ho zR%QY<#MVzbk^G6ys8$bFXjD+sumc9sg~pQ- zUVTXK)A$H6>cJ#_O==Se#i_I%qOYmr)URPdVmgIT5+p+xJZ=yt?2@g<`n(DOpxc;`G$lBIm&ZQ|XXjz#?>N zeBd+G)aK|aLEO`+;&e>5t`|}LGBGijOdF_{IdG%qV19HKl#MJ%LB&XaP3j;`PwE*( zN{1y3V`LCHG#VYrWR39LPeWbE7~tw@bc45e26M%9Fl(w$y2>E)WV9+vl)$KSl1@XOK{}-I!V??ePN@gbz?N`>wrQkBlmMGZ zpOiK|CQse~Nrp3XIZ>7|L(z0b>AD1jO&#zmBc5zvfQovvb2099O*f!ZPDO*`O2 zB}}~`mNK;s7$M8P{btGl%nCNHX^CW%UCIr58R-(EsS-Z6IJG`XV>&ewSU8hG*NzS@ zM@3f~{I#M&ScaN9AgW3jG5kCXOR)zo6Dr62Xqp6LK!8y*iHS6Mr8yNwFlw|kJgPts zZV7gPj2G^T#ULzl3rI7WMsa0EbTJ{EFr4&CYQ}SVWE#+kl7y-_BoTBNPMxUIV+}zE zYU&VGiB|9g8$rCqqK2G7L#>6Qf~!LIJR;Q`RjCHTWQ{Z|lCXx&&StgoX-q-_t_cZo z_~?@2GBeZ}>bN8wljcPJ;?OH?NoY%iSBr~dVEeFmCJFV-%(#pUwYn7-$f@yZX&P;s zQ|(CQbPQGqla7eOvItdry*>{1qk^Y}`;kYQUo=a9NfR^M`E`C!em8cfOeh~tT9)?M z(=_uSuUnu`d~kymBpV=J2oW}Z7HJ)VO-_E+kjzLAr#lHNy_ddRPnSKz zV0bAnBk~jq)K?(bA^uQD2ntAal#hw#nZTv%qvZqR)94b_aXt*oNy9V#6F?hX@bshH zTFUo9Vl9BHi1f2ej|O7HN4_v#%}bwUs^or-XY)~rE1>HK?hzk2eArp+pPNEkK2*Qd z7?f+QNNo}xAjUDxHZcp^dvG3FDUkJZE!cyKv{GSKnh`d{YIET%Fj=WGD@cg6DeLht zY!_AsbwFGUA}K5?whj;k@PjM>OP$h(G_}|>NRMQDy^tD~s)9{>pjEFo7+Ofd*TQqf zKCCn{J6jr&ZIVi|;Z9?smonk3Rf#5(iQ}VC7Q?-TDWMsg10W;q#=O%3 zMuqw&6}A{ivuP$mZ!P|oa!e^x=Yy*oh5~~@A1&k7zO6L6*_;%P4N|d>^hs_cj`~X# zRkl{D&VAxpnnb}aASwukdOz^MBvM*2l5lWzldF8NBB%w$DzU4a@6blC&PAQZXM^ibP`y*dOeo6OV3YpjiAteK{!mb?A3#_uzQ1e4s*gGI}YX%Xz z&!M?8_Cj!gF#ASxDlGf5*{zr9OzB6;v*dX|8h)7W;W2s~90$k6d>PTjwfH%jBp?j^ z&Ek9#K>{;}FTe#!05vy6czh@rBF(}g`Hb|p_P4^Zl7(DhA*)GXqeN_!wpdc5lk&+M z%seZ36XyO)W`mi$#Z1U2Z!@dIlUM3%uQ3 zM}!+`MPxwUhRosaX4g_zH`giW0Oyv_($K>2;_&>?k>CKw@brefrH;dbEZmK6CXohy z3V$~~(#1d5zc8ngEAmgsskD@lX*pf}z5Rdkb&mRr{mU(qrK{zRxg@#HyxzRdyvAH> zUTt1w9+7`6)EM3tUTj`zUS_V!ACn(BZ|xo`3|$LGib$j>cr+Lp5iBD0`hz+3`qdi&;O*G5gfe8baw z`nvf#`-*&bytlo#ytVAzGT$AU?T$=$M<%-?liZQ< z?#NhoWTZP%>5hzWM=IQra(85yJ2J=}8Q_kTxFg-%kwSOmt}AlK6}jPxTysS(yCP>@ zk>jq&eph6>D^l-@Mz**j>s^u6uE-KsWR@#3%@vvCii~kZ%3YBGu88D{^mj%2xguR% zkuI)CXIG@i6}jt-+;&E8I3vx@$W>?Lyfbpr89D5X>~lsMoRJOANUbxn&>5NGjEr+e zhC3sYGg9h|^mg8I_j2}hmXQm2m(zPVyE(f!o1-Y6BsbC)r7ub^v>(g6;^<~QlYYw4 zlrV-b=Ow-u-;?jok4n93y=A>=yZMKd|jXX5PGTAc8GSM=@Qe_!$8E2{Bdg{h1 z*U^@{#=FK*C|^}yQ;)%Yq~)4=Yx36Q5oo!pUYEQsxxzBsQf{eD9%hLSwG6Qgw#b%2 zmVuVl$*YqGSc)xOES)X=EJc>P=028E%Pn&+%T4ojbF=xH`KtMf`LcOc@~Y(imX*n6 zmcEt}%PnJX3~)`|)6&Dz9nf3GZk9sJZSxKD1>@GFOXiE_3+D6YbLO+=Gv?alCi7|Y zDf3D5)a0qjC(OsA=40lg<|F3A=0oO#=0@`Y^M3O_^Ir2F^KSE?U#^n%A0Fn3tNDo14kuq{B(g!?d zo9E*?&pZ#;x#qdJ)|hL|GtA@jqvP@yqA)stRDOeTiFs`PBJ;@n%KTY6U`HG zonW4TYn8bQ*RkfYxQ;Q8!F9ZOJg(!+3gtPXKH6R)toER)m&^>XMd(7Kavv3d+dB(1Or@{k+iJP)%q~Xm)5;s5&$=v@mx@ zXnJT`Xi@Ie(3EIsa%fU$VrW9BDl|ScE;KeYDtAn1bZAs)WT-OKBh)R_HB=lLntLWV zB2*C?9x4wF3zd>WAOqxn-fgp+2F~PAiY1aAj#1#bq+ zb8iH%2Tul11dj)g1xs=d1rG)rgS~SP1osCgChY@cZ*Wg=Hz2!$JA&JT+k%IKJA+$; z-E-@MTY`1L&B0B;_A zfs26)fpdYgfir=o!0EuLz{$Xgz`2~`fn$NAfg^##fkT0Vfl*0)Rr>?`0(%2{0&{}1 zgR_Fw!I{As!Rf(i!KuM1!O6i%!HL1>gkV*0d~jTFY;a6)bZ}H~WUw+=5gZ;Y4_?n1 z791KJ5*&<@92^uJ7#t9kg8hTdq+hTs*f-cGSQ;z|_73(6_Jj_*2fKmst${6py1>T3 zhQRv3@T7Ht<$+~^n0VI)rWn@*Y6CIRt`4jUtPHFOEQkh{29^XC2Nneu2IdFm1!f0k z1*!ux12Y1*{gVRY1LFc?17iZC1ET_k=8=KQz=%Lapgb@vFf=eEFgPFw1_inXiUVB& zodZRI!oXes9e-({XJBrijMN0?1f~b31*Qh31SSV+awi5R1gZi90|Nqe8aYV{^bhn4 zlm()LlExah=^ONY1APJ~a!LZd1HA%00;7%H1Kk2Q{1^R0m1TI#f75^6-|WBUzv`cr zbj5$!f60Hrf8M`6XIsuWe?v}vPAPlVf5zYBKkeU|bIO0xf5Lyf5d;-zgct0 zf6(9PKj7c*-{;@!-{ary-{s%oFC*Kd{%!sSf4zUJe~Z7)zuCXZztO)fX9Ebwh_3gq z^RM-<@z?rS`&ao_`d9du`O*f0B|gZu;i1N@S|zrUZq z%-`4F$6x9%@ptog_xAAT^(?mNsj`wFi4uKLdT3Y1R~DJZz) zyX?E@>y~rH*CpqS@3iSKTX5c2fR@|YO}^qB6pBbKd(wBpcieZ(chq-Vd)Rl#chJ}9 ziyrXp_cdql_3iQP_8rph@a@mu?yF_D`5JunzB01ax5ZcI+w9xq+vwZiTkl)vTTAoG zgW5H|!`Vl&8`ZU_uJ&E<)ssTwD&I=q3NW|~u%*7`zJu9Ie1*on*^7OPd<%VBv$tgL zG0peYW$)4MHWiV13M9HyThj)bK_hnxnU#YLe*TYxJR%Cbe75lpQ3L${I-pG03x^PZt7RpE& zsmIR^?@ezrDY))!_FnT|^%4bB9fZorTJIWft#_4oWef5J zc7=DXVYzpycbT`$w8XpEyU07!JHtEOyD)2^w_u)kuD8ZpPv&@Md#8D)dTWhSqTb2g zN#40x6TK6>Ro?mDQg)WN+B@Dm&O6pS##>14kQrH{y`#KKwYBWDtP$P{?_$+(U=7JC zWvMw8fsvS96%-7}8tNV59qg681=l@;yw^NeJp~2*v%0Ek*=El`?*Oml?FRw$_xAPn z@s^-e>g^r%_Vo7fcK3GkcJ&r}yLdZ$i@d$E3cYtdcRaT}w>&pJH#`mMT6VFamOY<% z#dFzn$#XvOqUVC=Jnmhx&U(&xnmngHr#vS;Cp^bJ$2>%p%&NJ3C2KUjPQJ#^WO3w&Sg=e^@ z+%wEG)HB30*t0uvkY}K0fJgH5_w@6WdHQ<#cuGAbp5C5bo}Qi_o`%fso^GD5o?=fI zPiIe&r_giPeaC&^XKgd(hqJKH%Q(-sj%y-s9fwhUx8e?*QS*cJ~HT zgPSg~I`?MxCih172KRdRI`>-8pPE_gUgcitUICnC3eL#PrS8S=CncXuZ@Q^AnL!5}QVCHL&?f$k$*e>dWuJ7$`6H5S|<&17KW;KWNAW$r%i zJFsl}6RkDI3KuhaxqG<#x=Y)dT3dR%d%C;3i&S0R=Q4`jUEH1BMef_K!x@J&Zn!uez?d_GcV&9dtFi4!HKY_PX}CcDr`DcDi=B3RTx#sl6RxAKBd)`)2G>N*R#%;Cvul%U zqicg}o$HitjccK6fons?`ivE>#jd%oIj-7_le!6-?ukWYrmJh>6xV#$T34;B#x>hj z?ONuV;aZ(B-E}Z~s%w>NrE9rsk!!MRo-3wTdW{ZmQ8ENS(KW#Z!B@G)yT-X_02u2U z?J8wQxkkD=CstyfBU}}(;jUq>f}ySg6gZIAiVUYO?llowgRc3SD=cH=Uz3kz39=+1H(5APV~$rKx34IP1w3 z=Ve?kIWIadIL|rHI?p(poTr@wGDc{q?#jq<=T&ug165`-ey`)_nzNQIBi-N|4ml4x zk2#Mzk2o(U6dSq%(#>!wVZXC~#sO!evtPzuXII0;gnRQI&hByUa`w%LmSt>mZgg&S zZgK8#ZgH#@gE>z(D!Vb0yoos>f`cp+iEbDeXobB%MgbCq+YlUmFQ=Q3wmMvZfh zbG~zlbFp)tbFOodbAfZYb7@PjSl`*sSNK3-fn5ZLwT92^H$}p%v+JSJa0$(_Vi`A zF3nq#w>@2Ie&|s$S_-r?OFwV-RLW(?CC5d_1;=^EImcPY8Ap@jv^1h!Ms&YppJT6M zk7KuEmt&`+E`7VB!LeSq!?Dd#@7U_t;;3_Mc5HHNbZl^}cdT=)b*ykKcPw+PcC2!& zbgW5V{#Sj=-AIKaLjki!+ox!#<4VgKR3rQTe&YupXE3c z*A%xn{d63?FN~%yNS~iRFMV!$jdHJc%yi5^$LWq~j1P}5XWGL z>=@)2s4((d$=*49?C0(0>}Ty~>`nI5_EYvz>7&x6)JGnTZnAH*Z?Lbo zud}bUSEN^@ud&zKSKC+FSK9k&SJ;=^m)V!vm)MWR9gC|ci|vc-3+)T+4aRx)x%L|S z9Q#oFZ2K&GwSA_2hJCtyntiH$ihZ(ul6|6mg1yQ<-d=1NXCI4ev0;pTw0)F)q`lHU z!d_t?ZZEeFvk$QMjM{tHyW6|jyV{HGUF@ChMRqB@S9>J zc2l?^TocL?`X=;AxGG!`E(@20i^2urtZ*ppjL;-RPYb7nlfnt%xX_qZMve(bg+szY zp;0&>>`&V->=X7P+1n%R7IvrY5_SqZgzdsMVOLs%P!IT4VT({FY!)^N8-)$RdSRWg zR#+p{3af=x!b)LE30)HA z3Y`-Qw#SVXW(#wK=|YV#LzpR43)6(D!W3b$FiDswOc1JsS;BZ>oG?bHPa7?4O&cW? zj1($`5kiG9TqqZY2}6Y;!eC*LFi_~L86XrTNJ4+1pHL?B75WGp(|T&lNU2aF^cH#v zJ%t`ZccGimRVWss>(jaj>(YwB#@aN6jm|=mP@A?o?Ln3cg}b&pw%fK_wwtyaw(GWL zTTG_cY%9~Q+OF7Eq%BXoY^zVZWZRl{(N=K5cHXwbw$paVw##J7L=$O*>~hYdd3WvYoc=N;_pcX`^b`Zrf&Su+`hP+P2vCr`6du z+cw$C$VS@+Tx)bSy7jgi-8$P^T-VrYab2Ib+V;RCR@qkCzCVc-w&k{EwxzZuw#6;% zFN%Wyg|-C>|8&E0Cv(1So^7s;hM+=Yjctx?wry70EE}?nST-EXhpTMWwn?_BwkfvB zwi#*FX)|r5>@?eS+YH;Zw28J`@k+`$-Zs`Y!8XoT7k4v$3@DDaRi=%?@5VSdciTu? zMcQy%wA@x{8(}L?8KGl3KbT2sA($v5xA~j= zYcr}&9+SNT{JJjx&D5Ae^54q^{>T@*DVd{93+VVPP|0$M58~@;A86shd)#=qBsd@U{Gg z_>J*9Qg`#)`EC3reh3v1&LJFqyFJ-HA^ZA+l0)8G(B|DlQrQAm<*D0w}QYmzD>SX0Ug0J9*^OgKC zeh5E+@6Y$+%lK$tz7Jo@m+*u5fxOI5N*&4%=6koycp2ME(}nNMw)BKR_hk)mXtc{X6q*FMwB)Hw%)qVy4G4oydzT%SoT@=Saw-U9H8|F4oT0B5R@buH_C0-L~Aa z+_Xf7rwmW&rz>Sk*^8D^HZnA22&x4IgHv{DAFRkJgMO|w4Wvz|wN*fWjkVUg8c+Hn ztE?-nE3A>_)@9bE)+N?eDXUTzTNhauS{GR7TO)I=HP$)S+1AA=i&Lc1r*fi^@z!zH zvDPux(biGck=9B|0(rHy!aCeqZXISFYHhUawd}NPu&l8(SnB9h4r+&32U}(9AnQQu z0PB9sZp(JdI?HOyR?8-f9N#xZveIjRYou3PuehBY{h5|hFiJg2{e4I@(lf4S3)0jU zr0+xeS<9@EzSch0NU62N8tHBAWsMw*>j{g&tp~lWq>L@IT(Iu3?zR@DM3!2XSPE0_ zCf`Y3Ok1e+FS5+C)L0f;BC{=XEekA>IhJ{r`8k$IwIwps(lfp(VFs=}U0i4^~gOWQna3MV$tk|NKPlR|0dGp`C& zHB&gAdqLHnXGL1>#1p(c&Xaf|-lE92W=StS+uvLkBg?CSDbfnh0ZlAMNi3GGKbzCO zB!=2ELi$-~tnUKmECxj*Y7 zJ^8b5rO1n}%=0@TDbp|=od!=tBNXArTUo=Tf4w-Kd?E=iIklfqrJB}B1uy-SR7>(p zZ<4juO)ve4B^RVOUs+EoB>oq_mcqXrPA9tgmrvy?0uVsW%~oM10^SYL35o`R;Ckz4 zFoDnB3bdfHZD^RlJ+ygmz2S@rlC)}&ij-GDTso2a>PvxBBnFBPz5SG5(W;iJUeOEv zB7)IiXcMyGc}&6ZXOaC9EJ;J(KEp!NJ>PkeR7v&kOu@D2-PgfY!@DI3v_I908VZH= ze)8ULJ18Wnc`BUnEJ{$d4n}U&8+eUkjkF8q-uFC3 zd1_^hvZXBW$uUNGhKih4l%;66Z_=DH>Cx|dqAjjTV89c&dL@UhGzwHSX%HD3BQB&Q zR8+-tX^E;>R90b>mddFTTU1BuD}kdZgli8&;Jy^Fgk#HIfaOt}=LwI}WLiqV%O&7i zgDM zQs4nXq>LLiv75@E%m{5f`|<DnP5Iz#bL!Asx z0X(COEiL@x#vd7$`|hxR&$&d19~jF2m(}`~8NxN*{7Vk? z_?ev^F(~4rPFF0fey5X$tN!K}vj};jM#%#tJX~d-;SvN1a@{ynlJjsFC;d|%+0>ZbEbvT zs4w~=Cd;qAMgpW*>iA`b@z!!0k!|pG2*J}nQDrAgi09Qj0w6l^N$sS`Uz!aQ)mZ7K zP7Y{L`u*Rha-}+hwDrsP!v7H%cqY>+EqowjuruPCE{a#Dz?oS}O(ah;a3ZtN;AW-A z{~l2NjIdF8(J@Vy+;~38FLnHC2>FS0^D8s5gl~(qE~))L=8%QbwSPP$RPBJ5%Aq>y zF?f=)oQQa^9{$3#oQQGox>u@S{^tb3J#6YOUHkeeD>9Ggb`;VUa zW$(0=pXm2KX(!43PLj*jMg84`Y>*~OJ76B22fP`bjN}UQrm2o*Vi|>5Or-o1>IAozV2Q?h3k+bH+wlNLzZ;$Cz2#hNggLVHz#WtfVcK4KcJeZDfSa}{xs-yk;a-=Ww9Bk*u`YYtC@bKGduWZcrF=&W?MNG% z0zPsN?F4P9+8#q|inie?@V)o&&eN8%oiV)2ZDN!Gz>Pt3Xp-b=wHr(?Yv+j=YDh4;{o(3Zj` z>92!Zhi#&e%T2M4=i53e;0^aWUZySOXQc&$v;Q39_ZEe$JQwR&xCIqOk_ve5y^h7S zrRjVOtw$T00zPsNZ2)Z@R-H&=b7$Qi%6YxEG8mOQN-`!q)Dz}UeZx|eb_@f9b%7ZuPR$|l;@Mm z>bJ@>2;BcC!{5er^>7*r92IZj`tOQ|aGhF_nK}aBB#Y8`&tSI{wP_H!9+n0Vdn*C? zJl#%(bG%D9IolG6*wK_WaC?Uh7KTQ2XIytS}id3FPYF0^Aald7tdn%ifRP<#T`B|Pb zOU+X+NF%2^$R=s^^mg(?P7)#|HNSO|Cs}ep9_}U{SC7t0{6qLFMoAF?&6+7VrX>m% zC(CEtxZzYveT7tJTHjxuqoTM0a_jT-2-a=|E?I*pouu83fq-zVGQD4~4 z0QTgfU!<&Hu|*Xd8AXU<$tH?BY|(S1ht#;JeP#nh%*Jvsk_h>HOSmXiuH1Jymn;2k z$=lpi!c;%A*hHj_OUz`tbb84mC2e#|@BKokzO?j9PF-}4F}*K+yt0t&lFqDrO{e6j ztXL_%uxdP6B%NLLJQ*N8yxNBATdQ9t)zza`;|p;MswdaBC#0KnX3ftscd6rX`~g0& zfz;CNAx;yJUKL8OtbO^%8>g>*jU1BLbuW;^(u?cb#~rR`ZFG-uxL*GJk#I`&xOFa~ zj{$Y!;p*+{pC;9hZ9GVf$YYVBjey)1WL0NvW?AyEl)L3|XzZ6;ev|dE0NK0QRP5}T z9J-_Bxt9f!1M}XngVWxs8%K)Q`z4PB9ovbd7CK z0c?B97b5v02G~IXI~0JN^mN=GrOz8)F)btlPWz0|6Dt}&Hg%9THM~QvNoCvZni=q9 z>^E|sHF~?Wce{z4m(FZ=MoR(3!eL+^0wd~QQDJE+Cpv}4IJLOEkuf~V8euXJCnMI^ z;UEHuqp1hoq|Ri)R(Dz|KEjFhjEQE^CQ~U*|F?rE`iK{25Yq@^D#P@}$%k99mf>ry zQRL#Lk8n1Hl${#M=B}39>RCI2)O{S@8OAkrS8ujiD~;Us_zMb`pz+_iREg&|W0%mz z;0(HDxQ{`}vfHWHVBWS)Bx|^i7%*Anq8PseoB|Qs7hq*pN*#B|ha+tW3+i__5rqfkoU`*Pd<_s@G<_P-Pt z+KTN#>2{A--?#sLe&6QoVR2Y?HmXyDKs^wP^8w1QsY7*e|8bV=s!l(6n~f{qNH-$X zf68Z{PRf;@IC7o8WKujWG|b|JLC|7fhq9KQJKziRrXkYnM;o!XX?N_atQqmyiq0I2 zAsq*C&?D5lEJ55WEkEWVE2O4l|G@S4$2;auz!#IK`pR&;!9snkSh)A`N7=_wXp#;d zf0$phr=>cJ7AV5bIW$z^cnhqXzLDRH<~ozgXeSCXJ<2`|P4$#MJ|RSRkb8SaRvbAJ z*3|>+Ifw2h#WOfpRd$uESVB5sRZl0`MjWHT5@Vap`}$Il4*SmUfQ;V{V)LDsZq<7Ce@knzEo_Ic1s2qi+6Rp6;7N7#%*$S-?&hy> z^0?%tN7WyHt4BHCs4l-?AY_hIb@48aFsd#s!(laK!g(Ia?3rCpcRMRz8|czDa%n*CZOnQSuil#6kMYgXwLkyeNhA3lG)M)IcH1h)wC8plH)j zjq;$&rlw@S(^T_qDk&kPOg5yGPUMLERXTZ;{70UWPM(he9rADXBxz+!2FZ{wrjvB} z3{+prCV3e4vuu)`K3LhwAiB}roRL?dCp0EkW|KeYTLwXogdCDr zpqv!7oc9QdB$dM=I#orpRJtnVR0xdNSfxY-L4!7&B3gCIp;D}MfZREUeBdcTUWW9L zM=DIYMW#U?n$V%By0GTx=@TB7mB?|qORmXNa>>(4qluuxZh{jvkFpNj zM#wjFVL8X;pqVU9x&=d1h89(wOgEExM)qcrzZh_$nL}Lw4*F_z7Sd4-+v;Q|KCb!h zqcwx=1bJ{#%_s-NPgcp7^2oEY;K9gqP`6zB&;f*YL`kUk-wVS2I zQ*V+#$n(7;jFZ>m%6;@9GCSs2}K{ zzSkkfJC2MM)B6JEc>k!(14Nhyh|s6f4+wy&F}0J8J{nKgoO?D)}+&AEMeuK|bRrx&Qm#Vu0j6^}ikaMi#GCxPSWpk-~#BlFtT+ z3s#yQB$k|elt_SqpZ?;WJmrEQ$ud8H0RMj<0<3!EyCe+?E|iA@_d#OX2XN&rLGqBY zCW7w~i_*mvA_CZW2{)yovbCYC719KVMbS3&to!N5=PEFd8qjJt;#7<_&&pt+;O1-G1P8!LE4jzg= zB`{#`!Z8oq=ac=b4Gjp z@a9b3lu!D~zkP_Llg0Aa50PP{vz*ore{!o@ex)6pREb>Jj(mfIjoa`SQU@F5hdk!pCSX|>tY~*dhUjr z8&46QJk${t7fzY^6)Yo$_1HkV~?c|Oxl8Lw(Um~TrCHARkVT}drtTG(mm>K;)o2UI zL#+ItUz2b1K1B@1p~F|=v!9EF(Z88!T}4mws9$b{$^83xUihi}i}y*^%dsB!wW5Cv z(S}_cs4WE%yz>GZ;Mo_cF?t2($+BHUh1&9MakL7Wp-#-1rVWR_vD3 zq6iv9TZI@?O)SEuQ{~);pY@~o@;Sug?_mEI>KYmWzf1kU;42aHyZ9}WXtO5#*%rGf zwy+4pL9D^N5H=AMu~lr1d94`t8t;pkm~Y2-NiIl#8@mm|t!;!6L6TG=` zmpp?0?)OL@Zoj~-t^dzM#6pEMG}g8>b~_fk8GnTivi#7mNN?n&i+@Fy$V1;Jeo`sd zzK{J;J9+AFNI6+Azx)A_4eRkL74@BrNvb*X0eMels)^9^Qy%Z&i3g^zreQU2|DI&4 zuzCF3pMht*l#7Ef8tNF+YI$5IQUkib`G{=5&G#{R1-Gw1CXbV`<7a+M@ElvN{wq07 zddtH;A%8|q^(j_wr5yT{Sg{K$_!NH#vr=C5DcMRYYX*Nt+S49ez9s|E5=_!?1OsC9shoR+lBuqZMk2Pmwv2Y&fkB$U(&Zf-p>@pHp}-95(MG*{-F%I z;vWh_=em*%J#ukuMAWA4a>75!*O)_(e_{jh|1pQuZ?L`nk-7hW%;ClVk^RCL`-SAK z7WTiE-|0+p(C^F6%Cz@&R;I1%@;~Z9D8}xmxBQ1<@(Co-((i6DjIz6&*A+J1TmDs7 zh33kxt)hfA)FgHzx9H%X%hY~qp6@{_SyYepB7Qwi>kWe5jlYLQ{)5Cn(wqD-8!);N z)ZrZ;&BvV#0vBTNxP{3UE9C{f$)C(<=NZ6|=>SG6ZYEvO#H#YMB_vFmLmcdnqRnrc1?~Bm{I4<` zp!AkcmnpN(9I4DY+E0<>h<>n`-ty#;O51DwmA3Yyl(zEzq~CW)RY+v&cR-5ZjM~2P$Hn=Vk&0>mpt3$3Mc)3-NT!yNOc<$d)b~ajH5#1C z^5W6pRF;p7MpUho#WB?SWy@HSDfb^kUVi}8nKagn9821>q)0wAp1eoO<)^D~Bvmf= ztwJ!C4+OLpDtU?%o>LB(j)jclK2B zeRCKEUwwX|0~unWoVWoofQqz!#($5B7@{1Ge`k$CufkC z)r0g{#d4<^SXYDOKhGpj0ekFBGB)vM7G2(2F^qS)O}Db-cdM~c-76PW!_qp-ldH)W z2#fqIq{x->(piWdjWU^yy;?($XBc7weyKBZ3j-6M0Y1#a_Pm?3H!eB^4CJpy8-iOJk_Ygeq+p)Z+Rrcn&OXz|%3UX=cu#Io@pfZwttK#}d1b1!)xy_OC8d6Kt zy%9c=ELrgvhhnUtS;sFIVE=typeHes^v0M_TyU?rguH;4sKM?*S-qb8 z0XqI@J>jD}*W-aOeXEph>Eit;0e?skABHt>?Zqx-NX95Bx~=#MLQHrWZ`Bg|f}cU8 z+h2MXk9MTX-k;LbN+61CK|G$LQp!LOTL^?OKBPtu&>=N?5}7Iz*}R^-D8I3Rq=BKo zY#@817>Xvh7ipAzlgshRewS+kMg`O;}wvzeTUE4qPAIBY?@R&#qZB*)#BiTwEyPb#!vnj-&a3+edE zr^ct~I|O?f)2GG=Ern2ck`{C*JV6Ua6dtFAI20bEg?JPmr3F0-j~K-D&*E)RJp7h@ zTZ#Rd?mbxy8$z(8;@`_ax?)X_`(Y5ugRmVrSQL1JJ}B16C0nuW{8V1Dm83_pEyT21 zHE1Y@EOGEqiMlw5GzO0 zg2x~Z8bJ$g2x}NExD4W%V^M;V(;&`x3imvNIIur$au~$fQ)!FcAXXouA{Kzshc?;p z+G;Oa;6Z09Em#fWn8~zY0i=o+%m#6S!bYw^+&79satz|eN?OP^h$|{+AqzNQ<9XJY zX%Mdtrl1V4DAPi^LA)-}LYhI`(2o{U4Ps3hEupU1Y*4K*-9JVo8iPmp4dS2J2iBm7D{cS+ym3` z6v{0OFy9$G*5$=``Ql;tuuA!zBcz?CQH7;eDfc@;z*%tW=#~j8{s0hG}|&(b!s^D)(+e zesW1((nS81I3+;|41D?$)8quBR_@b89P-C!h$VP24z|PNgcfh=VTgx$ym%4sW?waR zVYBIXmH^~gdRj*7D%#N{A2>raWP^O{3^`A_$UDxGLr;J;tS-Vc_;vy14vVLy0tUf; ztOCx#Bj(Unohn~Ehb--q>^V<9O9h)aCq^S)UZ9NP%@{_uUPV6Dc%Edrz$+|`dpJJ)yu4`!e3*lAA7(b_ox@eQ(Y~{>8W$O{f!7Ou08j zmQFF&2KBbg6F|BADo!9b$lI@y*G-FB$l|>}T@=mZ%xi1L2dxp$O2^89|!`+xs0pU<4T>)B_Q)z{i5>Fi%hNp)g4 z*tCiF&`q)XkV`zE_wIu{KS^KQSK5kE;S}^h>4V(#2X>Ps50r*sqr$zrdNC+53^GG@ zH*OFD=)4MEpxZ;Gt^0vwG7=J}1bZt(AzKNHtMSMALj!t}avv&Dp%<~w5OSFfADOBKEdOrDqtW>y)|(aWgT?!j*__;trRpk=(HOI_=xpln-2x283}aG2RAru zqq{z$D4HesiU^$4%DxC7G~U{co2$6pem_yG;CMxI!&hffcmg&(yo*6?vc;h20ZG$% zN^yx>sF~<5s>gY2-VaN(tH42#iGLUfda4fb7s<+fTI4Urh@s(BHUPRA$tO^Z@yvWUEejOw{$ctlD#$zk zb6rkz7yJLYBaOp!GYI#HjDT;EXDM9vQn>zMDO^~JUWSMY%E?u+OyM{zvVSp|L0N{l z!3z^6s?&~O@f9yj0nfq|z`|rvw-7804n&A&VM09%6Xsc%Fweq-dln`F3$rzUVIr_F zTmQZ=k)DN#^e#-)lZ9FE4+|45u472?F;5oe?msU~5_Z4CxJ&b6#K*iiC1Udz=OsV* zs`BI+SftglqBvAQzs5q$XfMUof}*5y9d-+%paFOa^$HLL6l{zr=#_$^s2CnjBMOSd z;NjuC?RjH(j^SRiQ0y@-rqcyQLK?#`-zSGG9|pPH0iI0IG&zrO@Yv?ZwmqiuaiTDU z%CE$UhD9?rRAS|e%^Ct1-?1unFX@1@jZ%p1Ol_?Wyx=X`6DLxYo7Vj}k){+_>B6RF z9+~~d-0TNN@tJBG{M1@xs{NI3X?bgBA-b5UcJd(w7L!i!<;^Fmu~gfQ#rOJRy_g_= zmje<6DTQTmTqozE%|*qa=tT;5SwY-q3!^olUhv^yMPZ^&&EZw3mNSQa&wPJyVewW0WJd#f zA;*)89>x?FHs_BOOK}8&^I|TaHWwC2c-N`IqFjlCci1x?^Jg3G*k&PlXCy;Xm6`8k zw+exVbOTV7BeRHjG33ZWVY=)$!5j>^_(?g1?ZIy^A`(h@IuxD`@g4o)jwpFqpWkO_ zQSoAd+<)tn-YqIvIx(%NC?r-XR!*|0r6{8;=uHp7E{_hE5QkwAwKPSPldJc|(9m&d zA#`heS`dkn*v{oBwWK(yT(W{oiTV;k#dc|8pK{wuE+abWS~*td(}y=i9B8yzH$;lM zLrc&o=a-oHc(>;I8uuXN+r}zv+MI72tC*K5sxk~+y;hux={1gp&e>}m%c2Sx$Hw1( zVjK$%E*r;6V>C5?S0wp*^~2`Jbg;4rN%tl_rmijWwa1+FHy$U=;fE_>OO)jEhds%L zvX9IDb4xBYo>VqJMn^Ee1*__@!)*c-hDZV+2o+f3!W>Y^w^4oS>%3;ZN{XYj>1Y7S z2iU$O2J4SM9CoY1NVis30l5QQbg(LL(RsR9Rdj)}sBtw>MBSn!C=}=G8%cj0a)(fM zH4*9atCLqEK_|0eTyg7CHL*;g!Zk(Vs5^Uwag9A&_d7tCU})E*xfT+xYu0NuMF&MW zKwE2x_*mp-a4vg9;4h;pr;)|DKTn~|8lrO?pB`bK9(lU=YI?1K=v@4v zABMxsWnTDwKL=dbMJ?OfFww{U2qjTLxevhOOuEuQ3~=2JXAv%drg4jUHxyg!3~XOJ zqf#T$3@3PGBb@UIw5*ZXp?pGp8;eTt&|TITh_#d9d6BRD(Whj`P^pWn}T&2 z%N2Lw4lLUPNwr_9z!nDCbt&gX5eAZ|`Oj%f$vt1yt&f_EkKiO^xmt>XUJqw#)k>I= zL;ZPj9mXRV_TZE7-#Bi#To-8HdJ#oCS^>8Z-DxFi<59J>cmt2QtpVn?(YLL|d_3N4 zBjS-cvW*xK-hsV{k{M56{~P;!@r}ahU?sdPX6e?VE@HGIX8TdvYa)`Ljb9Tb`1#&z zqFTUgKg={^FW48oNt-%gtNl!=9YsX&=UXBFguREy+bI^a+P0%87Cu=?1c~e8hTe%+ zjJX}vm`v0WaBnhg?yMg)-_=C-EMp^|j8J>kafdKQ5EfMJ%+weNC)Tl-l(6 z8=_QTZ5Lcluu|qP5k?0G740et25dRvV2mXpoHch+i>@N-DY11Vi>(n7g~itCT}5T+ zMZWARy5!4@{cV}Cx%Y{%Sgq9~$wB5K5#dwLrjvnAIxiAUqiv*xZvcV-!D!2?qcIaK zivH{kG1y9fQydq{Dyu_x(OSWCZ4Yb-WjTG(Q*6WI{a&J@z7O=Nv7Aoy65Ykk->6Y< z5m%utGf|FW0y+nL0WVHDc9KiNxUAn%!Hp=RWSE52Y=| z^byf9`Slg9@3bArFy-9=YT8osJ|Zmez?i$}AAMG1=t=$hfF94GPy2|psM8=gStD=^ zgts{s^(u&?{PQTC?IVUmXWs2?Q9TxWT46LB$$&L9QSh;`U!h}5fAh9@H3l`Lrv@0E z&_oAsT^@sq3N`zRnjur{Z#0b0ocyf@13jA97o^@;+SFG}z@y1KqGQ*kIDW=v)HN;$ zmn$A%kbC0SI*jqi=3grRQgFsx6L9C2eaiYLpdEilf6sIoA3GWRLmvR5@+%d9mkZk> z<1jK_ro4B=53Me!>_UdCBJD0Zdk(6xn4xd7X#Z^Tb#L+ji?)-+ThAt!DBkowis&yU z#s2?>J2f+d4N8 z8UX#W3wvFmfGFQ&at{(?edj>)RK=kh2dpm!iS>%#Fa?K)!QC+H-46h-eW!p|N!N5P?RZF<8*OB(UA|7OK%Smm8`MpK%P`1UFZ}rT)6Buo<0(Wqm2u7vD3}fO7|pd z{70gWU)%*xXcxA`yNsWvf_EsU($tRFaKZXxf_PJ9tNgZ8#AS$IUdR%qBVdQ`V?%dr zW+orl=+DR8VvWfX{T27Zz(mQVs<{*VB!`8X#B}&K`6l}mPa;A*+D4T~ED8Ku*;SOa zKfDbzIn;H}>u!$amY{pnM#hE;a}&KgQ;ZBAfVlzm zfu5#@CX`DpD!N}DgrKmhjod{IZ~KuhL};wAF~emKnQ2>79R0)9A*TM(1l zDA^T^PMI_S9=>TkLQ4S&OTb5>O$uBj8IC_T!G*yn77`0;o942&r1IEX(u!X!Y73iK z6t-QkFk=C$!oP}^EEU!MxiyRy0+)d>$)?n0 zB1~hu6op?t`8ja~eFOw|7t8*?Uo^DRDNCWURov6)OY zw}I68gT`+IMEjN&Z4;eB#$JX_nRVD>f47y(4kMSw*Df9IJ5;rCnK6$lZWr_7FZ|^7 zY0Nn9Q9|d_2fW%q`FFMpsHkb{4p6N2Bh-gA^CtFAi%*qNa2;1SBa4}#Q8a|Kh^-2j2B=!HFEhVmWVkB+No9c;~)_x%E4 zPTYimT9``4vkWe_(d9j&gmQr*_lik{&huVmBJyu2{lH_Ce!%*Eujr!q-?sJ9<^n9p zZL7*YF;;*r-gyA$2;N)W4~Z4Nd7GlmyzSA(bSwS`aTA`06naF=ikcRK)iBs}gMS3M z4P$x?ufq)5eMBsyGp(GZJTjMPD%sjO#3OhaRtPc|Cu}&Tf>ccwGZoNyzK?~1nz>qH z%vQf2#UNEVM^}Ckjf0LbWC0`*<^_-v=cxX#B2yfZl=-VjCEsIM&m)v@Oq`9IufqDq z;c)xekM=Q%er|s@kI9*m=nwOlq~l^Js=j?rl%Rvhg^6Plasr>}&B{F?j_3cr^cw^} z-u#s(h2{NiW#;07^RRD&86ZMhnhc5)!$MRV<2-$HT6~RqL(hmt$`Nbt8L$L+;%#3@ zSnUD?7E6E*Ux0>wcQl-=UjU6ifE|Jqv=;f=~5Fz6E$PLA6jeO^D99| zF3<|t2LgW|)7g(X2QYqk>+LI|l+QfoKF}|vTm^qLj~ZQt$R_(T#g!@@4*GY&RftXM zQa{9uS4LRDdE$%$1FPHD#N=S6S(%M32i*fmQqbQsu8TB!>4u08SZ8=*B=NB=_q3oz z*+IK+h*$M)Om8lrGB-tL)G*0|buu-JXkHK)v9Ksn8e^re45 zR0|*65xvxj3M2=)XH#P==UqsHBCo*)LeyfkyyZ(s>l6`pDA$jKd!Hzwh@V;r_q+8#jHKF#-ah zyx)t20uRsgkxTL7Ccc>cY#Qq;*&g_ouPhb7?L}z^BnvpYggf9vfmE4X!I+I+^rD~a z#9#4r-X=eph~~ff$w((Rm!MG|`pIZ`Oh&oni+`_f2Fn;!?+`3Y;PJIfvYoQOzxRg1Z_V>Z1M#^G;&wDdc{t7`^ zohVtl)F>Fb^#gTilTq#M5Ej+I-ZE71F(!pzrvX^n>l&k(*_0h6lRS|{m_XVaCEG*3 zQ7l?k4$BXDhoxq+-xKnV21d(k$~V+4MyBcBtn3*1rI;8(uf)nX{J*hzZio|ojoP#~ zRu)C&o3XN_x3X2VpbQYo5-J}r2gAwh^LY7^p9|94 zzaee;mu?)jYLt?laJMeaE-hQ)aX3}Br)H@*D&F5)9{+w@*<`(t#v9tJ`+3kjAclsN zLEU-Qq%yLKqGVX#mz6b~$~y9|DjP*)9ObVP{d>S?E6bh_)@on@s>-SW%}cAwQdo|o zRb@MoH=N2=gYV#-;nuopa)@GQ)bM81kgwPo!L{T_t}&yQ%oR$GRlT8%Qmm}H*r6b{ zkXk`+G?D$RMfCymJ>?&u-s6&GpxBJ2@}$J`T5~y4g}P%8=?<~2R`Vr);FyiY{?w$&%y^1466 z2MYv^nZ!rg(ICd~2nO#^_s+5~MR&wG`@*W-QECcJ=qx!jK$Xx!Z{l&F3qZqey0$3f z<;dNND-kLxx7Ii&Q8o<#u>ggiu~$;(#UaP_=|U*!g4PsL3(>fj<%`NjI{vb(RCJ@l zOC{_b4@sruZRyFgC70|ThA6xnsajiE$!{a7LI=jgC`>OpD%PFXM4(jCI0>FjN>*J($-gox5LKo*BKs=@&IYRD{z!PxK!hnPk32tu)*mJg5vQC?^u zP^3kj2g*SqbDpl|@5}WmL!>K*h249jZm{XUF8E{6sU(tsj$ZFa~*3wvg2jTK`Ywa*>PzV9e43{;X*L6hg1v|9T zUyst&Y;fGyso@Bq_UklygiOL?)d*QAW-+Tb5cdQwl&}XexK;kr(dG~2G@ze0gXP>LdAyO!<^mh15NY>4dtIA#6(p}eE0>y$)|4&DojvGOuNN}x?n0l%n#3tf9lK zj&KC`N61)l^HYlc2=>j{)`^c~YZ(tlhDi&2Eo3|P1FM0JjtvUi*U9wKXgNaZYyC7@ zl0e*qK4WD)r3bAaD-XJ-M)1CrkR|g>#TsXr-@(>n9+#mkEgmOdWs$Z7NgXs^MkjtB z!LryB5>cfJIUpK)CSp%>5Q~>7twd}HbsR4fJ*r90v{jRl_~B7a0%qcsP)%l!m)^KN zoCVcn6TE3O{WM+{3cCD5B}jxY408!Ao3E1Z1X-}{E`?_td7<5niij@+U_Dyo>nY6) zkJvL`oyhTr&L6Z@j8C!@^UMz(R+>d!wu%%HTqrv+wglkk-o)5~A+|>KnPF#A?^3bR zExn_Meo-~x4U3(Gk+68s;hO67%t)>7iF%U5OfpIs$F)QsH~U<(2{+_zDsYL$3#9`D zc?Dih$}JEa2E@=YQ@$JO5ubrYW$~G>Ek09dT&AoS-SrO{rWpqT_d{{*p4V4?317~ z2*PlU?X-RkFjyDLT_bNLZU;m#irW-UvgmKaH(T_%Jr==g?2f7AS{V;G*>tT;wD~** zR3kQ)U=Ea3Ndioy;$b5Bq=R5*4pi|nktj8@Y0+9)nOT7Z_L#66-=g@Ng~C|G`R}y@ zzewx2_%LZAJ64wJuY_?jj2N4I9IKqq-h_S?%n6qs9{Y%WnNOFB8Uk0go@?N1>EBz}8E#(!OpoGUz-bOi=YXhgy z)J<3gJ341r7+|w)F$jY_sLKJYFvt;fZ4ug5 zjI?zt_^; zZ*iA}ah5~Bk&Tsh)cG4sh8?l#1jgU{TlpFuhrY#0&9+Kz11?7LrR}ma9&5MDas{(L zWg>;S1t=+%AqkS%2kCbG3w|r`(A^@;Y}Ky~*uYUb7!|gqTHnd&2#-PD0bRX-|u9fkZfCH z8;J%efYAUK-1tr=MX&Hk_Vt|}s^@m*JN)W{Rh`&xBe~)DtM&Ere--W=f<~~`z)DAxu zo%jJEVQ}3czVQW`5{4(+X|981nGcFRG{_EX-agq>LY9?x5Inh#n1 z)I^TURmSFQH{sw$48US3{mlbsYjiKIU{D9zBmiqBqkj zcU=CVK%CN+aVtJEzi&?;cG;=?vcGl5-10B^W!&vkZD{#vnefWfIsa7ikljPS z|MY4ud}?CsK~d2&vd;590TQKW=IrTD!1??4GVj0u2LAR5JI=`J|99We>8xA?vmpH( z=nhy1orAFBB4wVF)theDY`Dd-PFO|%!p<3=cRV*aSl zNB=t1wZ=90V%f0G7UpcB=<_mL&j4iz$Ag^nIKAEI$az_{-qr|Pwa*ftN!-F#HGvKF zP-q^LS4f+d6%cB=J_PSR2`K1;#6#pa$1gS? z8BCvDkVOjEMu(gS9wh^35|s0KwD&S8-1RY=Nhx_z#yWQ?i9Y6?AE^07Ss7Mst9}RT z8uW+srDYdot z`i(O#$%GbOFlAo!@%bxe(|!5e@=G#Wm)_=*rwmJBEUn8B`Tmua~%vyC#9+FkE*W1DZ z96tq6Em@3EjQv5__cw9ckbKWztu||iIn~qn3UX|Yw`qXLIvDE?6~+NNa7%Uz+Q*F` z`a>1g zBE6mAqf+(J{w|6;?~_I~9!jTQ&SHL=i>$0#a?K-i+FQvippRtRXv#<;Ia zbq3P(K&pe`r1HM1aXlnuw%7A zwaSVBVXmZLKeaX3z&HKWl2G<$`Ki^?mtS-kX>0)lD%!krPw^yp_uT(PcoJ+&F?EKw zL_=a3ZoIJD=tz7q%v+83*a-y7w@aX}F#p7suoJNLHMi~dCj2<@lQ-e&jMPrZj_i8= zNF2&f1Ryc~cW*s*>Kj$mQ;WB(1&NH^gpE}$UiG#QMq21qTHUo36U>aiZf+sHh#K!2hgI@q9Hi7D>Wtw08j86Ejx)fB z)yYg6imp*Ox2XF5<9x_$s7SJ%UoTlL9C7Lj(6!qMxE8Fl z`8BH%e9aj&ELp9gd`w%D)n?C(Bf6OSs^^9M@VHh?t%^s<;^6GJQuE?!DSTRbab$i> z8;h&up?p9>|_GI+$KsL?RDtB|6mDu=8$Q`FK* z;57-UI^>&f-(+$08Z9WP)>W=q7fY%mbmc1bE{FD4>66N8GIg$?s@9frYD)#SgD5B@ zhUQVF3hK-8YgWUs!R_#VmU(`?Vq!i$N!%1lt177TJOeB48Pf(lnpMGm0BcqWZAWFs<}pAp;zWXiN==y~i`FF`m}cz+T>KovHx@BJVj}w5*Ui z0Wzp6NopOhx{gJNUiI8RsOuJ>^3&?N>osv|BO#@;BXgM-8@1|$ghLKN>fa2K$0$0zP5T0fq$HJ zRH))NSS{+RZ|goApjlwCI=SBsi?z-+!d`b?(G%bZZ%}r3zk+nBi3*V!z0g#xXA4U; zkG#{t!cu129Dw3lzQql!8a(1p%_}TLshLfUo2gO0Y&+)>eV%Emras%(E#Lp9uM!a9 z4Q!^ild*VPB8%*pj#Cl*Sk&SH;Qa7gAz=-j`jWZ?qhI=x8Vf1sj+fNe%i34DKn3NE zm1_J?kyIVVMC#BIpsNdwYY7Os&dTGIn1nmtTdPOJ_%Q2EYjvf>YZkRri(zaB+NoOw z+#)8mS1T)ftiA2kR2lX#6frQ^GQ_wO%7F~{*G&rUthNlh`Z(iasOR=owhSHAS)Gi| z0=lS$x$m;C11KR1#OrD;Jf^>{vc1r%8@@^O;B|Gm7#l{*-cW}H^PceBM2BA>mSAOa zr%)ud?rOJQvpY1LGP|n5_?UTJ0oh@4^k!f|>r_|lYCzeD_ndK7+FR-s0Vp5eLmm2G zYI48tp}PM;h1(xU9X*z#r9IVh)+^K0#)@^kr`k=SQg_tE$PNE51E22h4XD11ZuJJX zo=!{q05Q$5e(R(5RG|rK(qG*GLQH!XqT01q(RbAkB&=QMzo&B0z3<*ruSM6vVFxe+ ze~LKi2=)W~&ZHRZ+N{HaR0OYGM9n`?BmKtOuo#DJBfay1+FAL6e)vFLpvNPK59hB5ks+XbmikKe`X&??D>rQfjgMcMP zci2zv&QNz?-X;!J!(cJ8c&Pfj^vV20t?Gao9p&~k;(_MB`Uo^>CK(@N+A^sEKZaBL zkJXb909GHVW{SL9w11@P4my0T27fkCSYM3s06iRuQ>qgDHv0#}`^PM6wAxQ7L#+~HVWm{;5?>*c?4j~GKpfz(iZOv> zd7r_tP2eZtaft?xSAS4$Qu7JwN5C3CPEebKuU0^Psw{AVuuFg*WjWUw8a5F-yBn>X zh|PPQ_D@t_O|UIav!pB9-n4Gp=m-*f%(-C~?V*<@;hfK*fs=3!a%trxH6`-rJevbi z5dDKW5W9O*F(%NpNoo3*I|;5-4sz)uPN$MCDc>PZVs8Gt;!rj#iyz*#9uzvkf|yzAU2HLGu1F{-A~A*HShR_ zQnQ($>3+1@%~T%=-MIKhk?t?FHy08(-rVW>cS=>13jP^Q5 z;?8lKcJ%RFu7vP{edntsw3EIr<0#FYuSN#%4$g0T8f}XjCu#S5H9F`=JsQ`_AwB}j zG$Hg%U#NZ(u}@Ka*wi8e42zQFFk0JMm~s>ssVs_vK|9Mwr`j4+NGR|DMQ!2%JMV&% zhOR`E%36)md?;;^ngSE>39SO+Xz(I%yjj-yMQRHfb5r3fEMnaS%sEpLJHj7(rU2v= z5I)?bmdn)E;zTAT>{7#ENvsBm{og-MXmn{AD6z>@c)7aialxJCY9Qn-6E~qn>9WyeZ&Kax{dsW5lDY0dad)dZ?$uTews_+pJ(0p7Kj*MNbS*LFMT;4(GT zN?!{WM_%6HO&(sS9^-VuhvSrXT3-8IG3zk};R>u5j)_gz-0`W8M@^ptl zCOYhSnIoQ;`7w3epuXhyr;!+FlSE{HiM4-&`j(=MwkmxE2r5Qbvj<1>H>w@Qm)_}# z+@vPC9=H3ejHk*go79viy?tk4UY4^Sy!sZk2_X9?Thwf2i&Y{=z3Ei;Tkc)zD3n@D zcdJDduj~WCcaxL@YC&qWUtRC{eNNn;P8Sc@b6oi#aG7$;nsG?2juzI@ z!&rvkqt~A0Vt%7*KdJ8EbI4|jRCt+~E0p}BTIbo?;0Wy6QIz?U>U_2d%C6Adqu`16 z{-{Ph+YY?ae|k$i)#DL7GqC^Nt9JhMU%$#cs>c2Iud4B1Hc9vy_-8o1_Otp5@aUGG z)j2@Goqkbap}U8A9aAF%GOkB;XIC>9v}-WlJEqo){QV2B$jFxi(L+mw@J|2F73@#Uw3^5;ScW~A2@1MJq7&q8TCD-wxPFA ztE<6GC!B!*p&wN~1FG}^wLhc2qg=4|p8*L2Vk`U{PI(SBJqOsEN$;IgZz0wDJcMMK zG!qYXj|S`FImuyfTiq|I?F4;yUfmmnSW&ijzn3GTLznS2UC4Dstzmnjv8f&a9)cdQ z!=0H;8GnEb8b$rCsAd1T0^b(ladcx=%LU#EEl*uh>x1MdepOA40pV?10g76nf-3c3 ziYRx-=F_^dPNAV+21Zh9Ge4CkURBG4FpZA;7#$w|Ndb}_ML%9ui->2cM*pF{^z?XU z`aC^g@kk^3JoH0bcw!I>j6C-vj4S_=ubO0tPYd_cKD2V!We4SQ`hOX0I@%%>6)v%~MaSnC^wQ)Cx|R&a3^5N!F=b>TU%_jli_4|5PKW&mFale-^@C z0X8En4pbc5|5TejsSp=um#Y*jxj8$rk&M~ZO(ybIc?Ro!Z3C{ zuWL1w+Ex*#hFix@P(6Pw4uJg)f2|o%<2rw>9H9M0e{E3w{_kz_O*0QZWJ-qPk2=ka z9NE`KG^?$l0opAUl?N8k=0@ht%t58*ZmsrcocRN`J3Hcttf15otsu~3vk>v^ z2ThC8n)po$ObqbgM(b*n2COsMdND>bgz_bIE~u4#QvMgsDyUU?lK#%hEvPMF?DvUV zW9;{n8@p_aH84Tj&e*SdF|8wGznLXK7a)I5iUx_{XhQE{mQN||iUh*5{!G*6Gp75r ztk#HYpDV9z^-+dcOKNBS3NUyFPTQOYwh3EOobp&wAZ)9%FQ_8q%(IKTf9-1S@hIR} z7%duUu~_fkjkI<^0=pV%A4dEE3fJDj3^y{|CQDEp#!#z6W9=h}N~fD?TS2ufd{HYF zmNBJ>;pgIa0xLvF(08;p)WC{&-44h@Rzhg75nXv4z~vs3C*Z-@N_zrESZS_*88K6qIxuNfD@k!$i)gGfPiFUw2Ou| zEgek*{-u3Ny}Ro%wmOSXb!T*RjHY(io0S^|V@I$!Ls^aWJ6n6RY2&joB^E)8;vxU> zi>*BAor!u$pzG|3dMyP4?%YXwIp_VLXi*D-w1WwHDxGW-r%{W^dQD1uMSBgLUe+sG zY1rg#e?|Klt2d{;7LIRS-(E`wv+!GcEmA!4p}Xxd5of4K2Q3N0i^d(aLO{BGI%x9^ zKqH8_DzNV+tDxIbVn>{TI`mCP)EP_XI%?Hj8`i9lFsn%@U~ZsNozO)Nwd|y&aK}SC zX_3IxGdgL>apMmGLX2g=jld#fm5TB$bZu)QnKGc0+;R(PodE(SP>;@9Y{FOopizpb z#q$hfh4H6t{f(RBCSXhW7#XyrvsTo&6Hx&^#qh2LucdT1_Rw#gwaDldc8rZc4}9k4 zx62x7NMVq{U9=GJNQJv#^G~4kE?R6LziKtc#>Ua+ZCctzD;_(X?UOl^2fP{sZs6t` z90_he{n15BjKBXwjFE;djm5z?42!glKiLBLGFMzgsjq5LfE$fo)e?$4da5p~M|?pY z`QU!b{8lUQVwhk|d{v8vqH+aV2EEVfBEBLI7JIm1nGV0I6-U{lSGAZbHhYiT$b|LUENrv*Io?6#U{L&VSTI=EKtBjpABSN%?R-;f55h3%Ev==w z)Dsoe#S-G891t)9PaLY1kFXGyWuF&PPN^GmX?=5Yl za);V<13=nCLU3YY|bu z+gB?BcDbJs?ZAm7xDV)qC6wAn>yNXtun&;ZZQ9=lnC=)AdRyxhcVjYk9l(Pvy4i+h zZO*fVbz!4|{gnN-Rs_n>oo{QcVS!z+uNJNDmkADP@ncXL_3UdGPVTGqE^{07ENiaT z0gZ7~dImQuY%e6IagQxxF}MW0hBx^Ve$J$_?`UaY7~XhCD+^oi>HK(Leg6({HMFqN z{qgA!tcLxy*F>S8_IN;t)7-bn)=7huS%c7FP!yX2dIYDLOZx|C5yh^-R7<)8u$S^8 zSfc?s1^WWSHf+}23fO>{UCi&4plC`s?GC5H1F_b*)OMhj5`66*wg|^C!lM!%(4r62 zl3R>~BSl?1SeXJVU+i`h6+Iei{)(d$JUVjxJTVDB!43$IeK{(dIjliPmhFLTw>MAX zZX2y8L~YIZkwfF(0|`Y||3O-1C2*lHYb78PVcW75wDEoIbNJ=;{y@VON(j&h~fda!m4CI+jA0B7vAP7cw01Z)^CrUNt|p}-8_juBKi1M~bN zwad_kM;?hX-LT!~D2M0`Vt*ESsj$bsnE}W6AF0AnZLl%Pw}K-Uh1g!;JdH{8%}|_| z9@g2RT9yFX`CtTA3SrAeXzjzt`d1LIWAc3XOkft1MQRU#tk{pV90l6o;~#6iaQ$lI zk=n~}F`YjWFUhgaj?@|mWv5l@6D>l4DQvwlKyDML&ls%)cE_hBQ4Qoc&{Ld=8M7yFOL}S7- ztq@G7w1I}Cs^aCXwVfOhjL-~Gk#aQBR?#j)- zR-VKpt#xc0tN_3&UX?7`HUQJZKRUys#bvaiev<$S+ECUc&^&Eu&m^scs|}7TYMfC} ztW4I* zR4vA*4W<(t{sQ&Q(n9I=sam0;`E!-RbJgG<=gRtMs#aI}Wp4?FN&=SY3+adHfF?Ug zo1u+Xc32Z;Xg8JOwfK_6WH(fzvZVqMZLRvXfJEQcZNwc(a zey3x&6ep?{?$Oa|N6o;c7oL8=*t!FZ)Al*RlS<6iYDJz_TznY$z7{|^Ca6!vpw>Iq z@Y&i91+s}o*?{Id=$&jWF}fcpVFU=S!ws0MIfQ(y94c-RfK!hs0Z% zol<VA3(r2)#VC4hfz~m<3z)6(`?w2GicfWcg8$IP z!v$If8;vZ~xo-8gd(Mw{s_L*Q%rHf$iEX$hv#)(1t>BnYRNR z#Yn5wPVKr<8SnIjqPgME4wlglpzmi)06^1Sw~N~RO&XT|omShurU17ZUt0#k{z(~( zPpiecngn6O-%&BTSh?S6jTCqWb^g`2oYi@c=2Selj^VhcQ0|nF5XubqD@H5!Y7?To zV^Q3J?!b}`fp8&=f+0P!T79ntDqL=(W+PX(C2uTbs>bE?!9MJbY-{R1?Tp9|ZIE$L zdyxM-?~qo6mL9^DX5P35$awLo-$kg$4_eYQ85@7l%I9a?{y}S+|GVMgC%FB_TL_gOkHH>Q=#N@0*b}w>QL7hWn-)lO ziPyBiC%@w7KWcCSvg-V#El^krhk!Gl2>`$;hp^(=^w&`>!9WRP0N9p+LE`O`@-yIX zHZ}NJi)F<=7p{ih#asIB&)U2SdqaTmc;|VvzpRvD4^(8}TyujaAa%ScL=(Zv0#?KK z?H8>?yeHcp30L8kDB~YZ_=iQm@Mg@WUB3cZ&817f;yh$ifn!>~6uVX`jun=Y)zof$ zNh*JdZT-V@lzO(^g<#4ioy!M z*i}$ah6X4!F&aiTyU*f7H_*AWT4m2Ba`Gm^K?Fj|-$WJ8X=6Q~hmceM*?5I6oC7Tf z4fuJWplm9CUQ4lk6|qtRWVST3sn2<>E{fKj*WOiTQSl2}5qtY#P@b?yy!|>~u&00U z1+DV`c3ywIpcUtROv#0XoDCpu=vePe;CJvA7fZMi#m6c zrK$c^EleU!$Nta~qP{=4QkHbssssM%l~419(BXMnTnQ@xp$@n`91A@lb?=h~{PS^x zz0@gBD;D+qVqxy3xp`WIZ)O0L6qv8Qv^7r)gG}Zh*YGM;mTv9jwk*wIK?H-PG!Xz3*^S zdzU8urLD)JHMp-0z!10G#|F%#H9{{(uPORQ%yzuc`~9ut&`rDs5&UjJ{FO~gx-*oT z+_JacTerXg!P@PXR^JmS2L@(z{+9L`GU>Lqsnv$dYWEaCHHcmCNOXZoRQe|x=7CEp zTkIwVH$xPJ07$rkd*F{@9`xilj?vtROXg>OhW4mu<{_UuN(?Z!(&9hyg?p^se`>)J zE=tGlX_L!&@Q(M`0mF}23Bw44x#Hb|9XQp;^`b{6d1u3N(0Z6l<7b-il3!Ipeiklp zZm9;OIK&g&OxJ}z5jcH>)GG$rzNe{ZWv1hDfz{S-sh1PVV(XEn7w33g#hrQ?JUTe_ zRuDXW?$nDl-IcXm&a3ON|K)ATPSodN-z`T>2}d@FUVR?c-Z^g`jG65`WPoG!7CYzR zhVA@p4nuK(l6~|ksiV*(+H-R=!}B{)&*n`ucP{sz>B;l-KhK;-mwogS5Yt5Z>O~MT zv8J!y1Tbu@ul`E#h5$rBWr{4C4>&%|%JtJ{;gcvmKp$v~wSyNUJ{3wqwYMcbP@n38 zkdu>`xrMYiP#+m`UIATY@6AMP&{Rfbz94sK1pGi%E^hD5`eS-ATnD043dP~S*Zv^S3l+Ue119z~5d=r`=P22gMDH5Pr zb2XJMpjU<*u2%s)8jn#0^mgG(6&{PhCgkiz>xSIGm>WbB?}tZFUsZ4YR|aWf)_raz;>B1gX_uj9B~fpNFy2;b4}%jlH=_FgNa z*N*(=7Ivml8lebrQxmTPvz18@m2EDgM?qlmQyD$Ez+wTA!ue0$8C(`SVl}0d)k}(d z>*l-DBx5v!Ef%K_T- zDX)h^Sp9K%JvF#*VjzQ(zyKg_o`hkvv%H@5%nB)@m8yXzNgegC+6Fp zv+Mbv2>q$A{gIG(`x(zp;_8a_B>q}a?~9I_R?_2wmi;0U5G51W0@>B+{YrXl+KE4r z$&(diLK9I9#X;r{2*==ZjUE#Nd7i4(5AZ=NAI6(MT01N0FDbaUG`fmj5M5WQqI0x0 zs-kyOej{99wpRI^zNo5y7xnl%aq`YV>WX!-KVY6k)%4}i>{Fc@dLKB%tgV6FeVKl$ zq5r1zC99^M7O)>TfAZ`HVt%Grw`%GE60yx>9lau^5AlCP-H(fxP~$p!>N72tSflCy zsEWJC=|o*Xv%ci_f}V!?ul9l-CI0+{UVcGu9dsp(Ned`Gc}|SqY2yoe^1o$Xe?cDr zg<`jQ`Y@%?k`wmSb2A>;w?ErSe3>hMtiMuV11$Iyt4;%bt5V>>Y42L`{6c06c=1k2 z08~5NLa3WNvSB6P*2F)+>UzSiB``_Tj&79HFDsYmWGOvTe5cU9M};FOxU~KT#K7u*;gU`q!0c(N}i4yJ`$4w#NNc2kYUdXWN%?t(MH0iOX@&RZ9R?C= zJvOBkwrtQXsbVv1i6!(}GoA0bn$t`l58ZjG7xj1rzhDyx^MV#SE6@+L&@cI;(~TWL z+~iQ{PI_BBMtL5&o=26=cIITydKD5WSM$`3t%DT4c%RASg{0OEQ>j^2%caH`Kmw# zVO@45B;eEv6B6)Em`b#N_YM7ZAi-bWz*fIZ{$2GJ0Qnue>TL_Kreq3NngRlF?I?)7 z>C>CBQIy*iGr5bB-^7kWkkB{vh;Xk5o5uW~;7HqV<1w99zNtsqdb}sy(BU`r(O@7u zz6E$N*IM(I-hiz??{(Ar$Ftn`iRG&U)EZV2e-U$)eHtdOsTUT)8}x1fC-DXr+q%|J ztsVgIN2ps5C}K|%_0XFXz7oX}pXML~@Ms10SJYxR#3)3$0({`RUb~x9$fqY}dJ0wQ ziNgo{-cv8+M&Ke;#e2<4`EiAGya`G-r}-cmO1ITL_4?jWO1OK;e6Uh@GOI_TO>%eh zz;MB;wiR(0J1|i*{}Jwx8BzK8op8b8gwMH zsdyiI+YIl6{=TN4``GCHK_5L@AH5fxkBbue>YO4<~s`xg|?!QhN|ua%BvCU)Df8p=bZZ9^beu0`|3+W=SSdjaL=4s zhq=DuMr^#HnDhlxe=;Iz(>wYVFcQo9>2tA^P5SGL@VL=mZ-c-wjot+^`kLN+S1**{ znOljjU~umJYF2?7tJg}fgH=I5p_+5qYx1Uo3B?St4$8akP5FhFk^ z^khZ3%PBN`fS!sKT{i$YK8Jo9ptCJS+CZIy!E{&^Q)KF*N-A9*q(@Qqsu&-tF+flA zVR8>4+&9s$1MLZYG*B<9j#P1Z79zp3uJT}pUWB^7r%%T3d++J9@q+1t@b%Aj*$!Z# z-urs0pJ)C6gA}^9uu=%Wt`(hsUoYs7iR3T3fb(RL@d1Xqz%oC;JfW*;gYB1Z8mv!6 zs!}>$IcSK!9lvXSsHfwx=RqXMbz}rrg-XKDiER?w1xuLxwSFNX<(Yh_v#C>7U_9TG=CX zg^wnlk+Ay*MOnvs`D49@!1LQt`c*Fb{1d&sD}xQ*al#QHl%mGy-hc>ph8+-rqwaeG zB6P;DGaxMo(>r7IFpjtskSOs!KI9i!P1P6Jv6re#-Zc z=gaS5iXfs0T{3Gc!>Tr4!pvbv}#=bJFbk=6H}i%s^?@zJ~PxWY2tW2 z#>hKS$*f_NjJB5qwhFXqyk01pBc}mQv!TPYQAxs7Mmx^DMS|d1&g5C~}r&&|<#=sI6 zrvNBtQAC!$5Rc7SdXj64l4ysp%?Ah)qhew3DJcj>3tYg#V9g+75$l3fmUD1OxFCdp1-!tACo>Vj=Iyx@ zOM0}=9w%#G+-~YS4$!Ry>8FgcHnqd&(%l`p(6Ll!nm*C*Wzbgq5*RX_n5Nf(R{j&e z*o3&J2E!U0^C1Sr0)Ff&hjEI!OxK(HoB@ObJefydPS@RG3pf&qyXFLuU@g%y!`k8vmGLMfAKI}>O3UICH0Cp`;0$LpSAwTLz z)meJ8|JV+Szvs2v_&>D+5Bt1!RcGt%+x_!6wdXbR286&zY~p|o&s-z-%nW8y-fTTT z^g}ruNr})8k#j&3-l9r#;D&z7hyIlp{6hLe5Y`TJaguF0;JNvJ!~~cefHm@5u{_^X z1cNiwn+`QE7kR{w2XO5Pr!>1Po89!0((Tmt+c2U)%4w&@c-#5dsS~B`B3~pRsXj&V z*?M&PhQfBHeIv7Z=ZvRyfL;GL7-ONUKszzoJ35ZQ1GxN--5q$3$-Cl8JFd`GyQ`lOs)Gr~?x^A<(gsR?Y2fo4&5 zYCYf_r*Nef#&yHpc<&_~&8x#0{otbM+F5HlDIGIdTu=8;q#DUEbkem+9=|9_u@coRx8Pmeh0pvs{?H}3cVeksOn%9^MD43%J? z`QScstWW2I@&i+FZGqlV{Fy;57V7mOf1SP%VBs#^UkLu{HVs__c~%asUZfxPvj>V@ zY}}){3-o&aVhH0-GZQ((=;UJbww5X^(HnDT! zy1f+3z;i6CMl=)+YQ%zSI> zDu|B69>pUN>$qBP4{_E zd9-q!9u>1Q2-;7Ey_FIa>?AA&Y+Zm<6yuCFWK5na zY`DM%cK`*SF>PzJDE6&82+!;=E^#!L2a&E3Ag!3!aeMAhbQpipSEv6U+P*t5ifa3R zXLhsGQYMhz*n|Kfp@V?*p(9;U5$P%*pwdJ{N=^dm9f`~{{ zy7C0%_c?cVL*Vg!@A=1%=+4|bx0iFzIrkK`iU&~a9zFP3O(V(n&WU&x)+<3$xvfyZ zAo5Y#Y3w>2F%bJ(2W|-z zFTe4=3p*yGzVYq=_xQ#2-bym;myBQUeGO0h@_O%GxAY;M-Rw>Eedu#{1u~>}?F_Aw zci^oSyp9N82(!>tU%dIDoqr3s_ndkw7=s^@_AP4v&@TF|_ml+Bx9WCp*Z2>UHMk|l z&9CIkYkrulIsw@iVSY%~PH#2)z;^Ey30#16JO0nqE>3MDwZ|Q|J(lS`A( z8p_}TdyP>DDb9{M0D!8s0l(Xg-PeYSodSPqK8-!)y%W4MXWn-@2#)lFJ?{r^GZ~7A z_s)2Kf+6HnKVp#Q*)N>+E|Y_Y1>pk9hbsojTw({E#~O_u3;$Bgw!!i;xgWyhyY&UN z{@KfJMSK73%@;L-l@cFYhvDgV!7t0=pFtsSqB}o(CnOBzB)L>FTU@$y#B`H_xNc#-?JPs1Id-Sroc2Q>bo*T&MX^Q(6gZuFH)-iDP& z?2iYo0a+Gs%EfCqLX2ea9R|D`$3sjd#f7EFptehQfoZwBc20JbC?;I;)^9Xw#!@#f zZ^z()RMac>`nxCnMKv|fv_jUHwu$t;{cMxwSiy(KB? zrZ=98D1Fnrs<05Xj6f17)JAY}VJ*T*j!EV@mw?M!Hwc!!yFvM8&@FEpv#;P^u=)st z4WO7eZh6OmDboM8H!10^fo91pGm8au29^uekmt6>Jn@~S!!a15XC8X% z4))nOntsO{KhR&@17NO?+uN_4yF5|@vGmu*wyqw3?p4msgIyx0GWeFr^|@;~cN#vY ziOY4M*xz8=z^KfUF^P`0eN~KWezr7IOy9+k!!=On`YL=yM@8XyqLf7wlqvIVDwkdAQCq&4g zn;q_kBr&ju*X-m!z?gs==t+%gC+1rSD*%SMi0zDu1>rJ0E=x92aYA6#uAzN@d)q>0 zs@NlNQeZ#hkvCnt6URulY?3|T5$G$BvX3ezO|`2_iq7PDL$}hNO*ky~D6?@AGzn5- zz~bvy*gHJ57=%QCS064l@QD#D3^CeYz2*IkR zni7gT8EUI5iSRTxKvzoRu~JvkgOV4#!N|6c1Rje0(t|!CZ0e*YXx%O<*F-6Rez3ElQqOWWbnHJm8={NFrFwMLP)fm~ zc~Y>#26O8LEAInTZwgit-O@+)nJ}e>7uNyFQA$;5h21Vn31=PRp3w>h@Q^(xM)C0T z^H}8_4+@D$QmXh4d0A~GTL21QL0=kMH_q(KzeDfYr-`gJY;& zvJ#^&41^;^SXt$SsdcgvuWUvDmSpVAWaR^Hz`YbDj$LczO;zfm-j=DN@xxP<*Bgy= z12hGhc%U74nJ%S3SolQ0Kmg7OPxpg_l>}yYDy{)cN^tz%f&gXY1Fn+5Fk?e@DGJ2a zOB7Jj5}YKK3nepUERwMVH4f9e)T@Bf3}dsafN~xF?CiCLl2dvTjCK97X}cODg#neK!sLCHE`JDdOnaYrnunFawd z2txm-(9X(A6LdSait;gLWmXj>rowcIlZlSvIslNcomqJrK>Y`2w-E zKu}+=qC_W(ffD1Th?HDA1}zy0&FPfCs#0H{ng9)jP;A1f)TgTQoOI9rvZ?}1aSCY` z9KPM9Lza?2%`ByLz9~GIxkd>WK4uq;sA_Fa>Wslc$}Z`m$)UIvbncr)b-?HS5iB^0 z6cF%1{T-?+Z@q#k1Z|FgeORJCNZeW|<{wfpn;x2Q@lNW-Yy&ohjh6*4i9%y zF!&PwO8j3Wq@?5;O1^Q6UEoWC%-NM z%e5U!e346lC0LOr!AT?LK*EZEn`uO09{qy?kr$CJo@Z?cI8zXL;VyYOJSM2I2v4W3 zbpcyO((JlQQZbYs&bh#fip^<(f1*X$`N54EJjDD#SL-S@KqeKhr_{uSNw0d!3$af7 zxVLe^Xn+_}(R(^xPf0;Nf7Mgs3pw?OnRJGRSJE>osPuwTF$#%66bBNEm%wjpKUEbfD&JhG z@i>EQp3G_6pf*Y*XOMHX4H+_8D9KRNJl^*Eo~{5vRjpPo@sjo1ERhGnlQ)gRaJ zzqhPmizh4k|2Eq7AGhx5IekX|m%RQD2fA=eG0^o}D&=xl^j`;heak2N|6eoge&Zhw zw7+HFwR*B;|KA20R&`qc!)2Y`<{yssf;M8bceQ!4PycoBDzyEFquuDOe>mFymep*h zG|#;b9{jfrck)doiSD#h68}-If4u88VuU>gx6a0uPHMy_5>MhxyYdRyBL;&V4k3=wxAPzT5+Mt=};jJ8pD==o(DvY6XW%LI0%?HZs zphRIzuzbb{+_1uznmH5_x}c*Z^FHQ-?O&(!qaQz50|h1=cT&y4N?P!i3TZA^S(ghf z+D?52D_6n6S^G(F5)J-Xsf_o{AA`>AO!Z{jgJ`uSw~Vu(_B;NnNPjPv4F&TWlZ zD&bXVjk>I$3J@Csty2N0kITwkdWE7c#Cn)dwfd-(ECY9uin`Ju^^Hruk)sg<%sdzZ zPAraKehklbeCa0%@ehB9GjdX&6d}YlIJ!3coe28ZoK5YADrr$Yq1#}N2w}T(!RB>F zkUco(6n~rISsC`eNEM?e=|@A0)d?eBs=k zfZ{#w&Yj@7(9FNvmFT z!~*M)3zz1-Qlt#`IV8+Z!Pd@jpTH~F2wHWfMe(oC$Ne{n_#YR!=kUupIE_&H^i=l_JfrGW-@OxS!}5c z=;A?0H7V~YxEho}82fsFB_UbAAP=>fpuCDvwnAXC0F*cn-% zQy}7DarrS5m7~Q!gUd|ytZW!Pm?{3K-=l#KUG zQYr`gld%HFLW-EIw1V_>kIBkPJPJ%v8s=Ryd67GrAq(g)5AJBv<%KkKijoS&&qY&| zTOo@j>~k5+Juux)(ic;eX7Zi0^k^zLCe101l(>?P&nBNR%PsRh$ITFA;Tc$^Vbil* z*##FHcfsDjDaqdcl)Xuhq3@?D2hx`5!UG%2Ah3`)K*X032B1GP5BCoK7cR0OL5>lj zebbdhZMiP4xy&ted%9BIZcEB*666n*jL6ACZWDqA=8zm){*$T846vid(9{{qcsvSz zqO=9iboeJqQsXCPN2>BGrxwOK7{cBa(RD1L)4!K3C)de zBTBUS8n@Y|JD(^8@@74_hoq>V2SISFo7+WZDxbrHn0;gxGiqv5vDwOr=y#cWlEMq451GZuU4dqP9oE(Yb-R4DBUBI$wjrU*j%W{l@-zo)RGiwM2Ku z(}VfS%Me3)@iVYBv*@$Wl*`f?d;I5$EW;bt#Dz+!=po`Jsig99Pf^! zIiHs-ZtGttpUSZ<+0ju*51(1X@eP=U=dn-?J;|m{1V9E<@>-uIYD>!DLavJ!+s?~hB=SJ z2bJ1*#2!*w$|&xGLrTH2->5!rhS`Oc<=B@WU|h6`*gR%gh7C@LWv_jqd<%~Imtb?9 zedLg0N@&Z4!#GK%QspB`ee`$a5v4os3+^8Q5Wi2q9#y`>=zV!i4C20Hz$+Kp*N-Xp zB`6nMJ+7>UX!>U-lrorxQzw-65kKlFiM&4&8T7fpcnZTMZ{Bu>%ubnPUV+G` zeJ#Kqea+Vx6QCl=+FzAl72Fulxvumq#)X*cgp@BoeWsbtJ>%WSVe!sT*y*fY{f07K z_Ra_eYxx@ebW4GaLi*`8?hx+NquWaFcr;t{ud zu!l-3FFN4EboY3u^bV@c0bjB&#aGBz)K|(^y0c~Ss6f7hFPgo+BBC(~42iFKkTevn zEdYU9aMLwkD&*jSMZk1MF}FHV+Cp30YUSd{oWhw?d>NdX8LzprntTgx1;MOnAa7BR zx{*hf3lBp_prC+KO>RU&AZIY**{}iTL(~@=q<&b6^Y9ErIRXNKcMN?9&jyFZyO@m- zACKs^AaxN8iM;1k_v2ATQP<;fUr|#rqCQpa2e>gyRolqY40@=mF}@i>u3yJyjAd>% ze!B4w1XL7rI=)Rc)W|$|!LBOn11xZfO*_(VX{dYTn6G}~B~~2YoJ*~OysIn0N^R9p zHB$Q2emN8q7qtNQuB=YO+a0hsO+F~aLw<8~q}na@i&1VafpG;_@+%_AtdSzh@KtaM zeT9xjtM5X>w?>Sbu5Cn#Rulb)((z!K&wscISAM~qGdEc^2u*HA2q9WrbL4>8DhcwOOSCYI)C(5{Qq=1=M)wtz`ic@}ozm5m;^a-nts>4QyvTAj{_{CWIXws!^Jl27;B2!A?VK+$^jF=WFuX5{R+F`lWk3VA@kf89 ztKU$c76=(J7N3sAs&yb|lNhJYKxj>znjeqTaVqP% zqNV|O9Eev7;_*kkn!mOvU&sixm)y{Iu+IOB4cqagZwbORJ+?#y%LC&~P7XqsS;0pr z#`seLXYl}+yP2WZlisI_Mbvcc<_<;FN*Wi$g0FXIaS^pB?J0y$3+a>RO{lNfrP9<- zr6@;lCC*S>7`_FP^ZcDI7E#}}s}{q?kSRn5ENi`#C zJ)+qft%73&D&PuG1mF-CuLeooOQE#Kw$WbqAZWWC0LRmD3v!K#oKp>D^ zIbTq3&IbTxR*3x8ZMJ8#h;@?8ajmlEG$jqKwdPoZ5a&sCi=9D8r6iG$W@6^ zu~-iTLK~tTf}!CA|HK`y)Ah$;%qAZLXd**v1OOsiCeHxb3cb! zrA;+g!#E!hYB4oKofc)VnRL^RFRN~3TOy~*p@0MS^X1hxk~EX1 zKd;VHZp;+(W7nvlX1TFwk5*R4Nz3VtD%e_U?8Q~o;i&^N$2b-?VFskP^+18|&>>Dl zm=)@ec;I;G47H;C5qBJin>~B-=tD{y+LVxRg8(<&dHRd~z!k#WJG*DdrKzBV)mlu>%9=-IY+AGJys`|7C zP|z7lZqJ`Fy?UVi0T-h5_nT^5PG(4^Jn23ZnZe?F>^77$L(agTh#J3@D>L+q|1Gon zbY@r|4P?&Fof*m@|CTxE>CEtC=TBtcd`n#|L2h+nN40R|<^z>|K>?ens(BJJ6aY*Y zJF4(KYLD%tx^vrs{(;0hHn zFt>e;HuY2^qjElkh!1E5r=}}C)mAu9>-JKgleXIfda2-5ZL_O<0NAB&k72!pW+~Jz zwPapE=-X1-`jJ|-@*TDW&0GMpcQT)YP!Wv5V;dMQ@F&MYWDrop(F$=i(7H$=>^nlm z0m#Eis?tv_3)jD#5#nmw5}-WB=PeW!%*_awVQ>RB~;6ELF{j35T2o z(pZ1c;{L#j*3*vuY9IMw7Fh$-8T>pmK#hfmHfSEQl9eL#jwooL`UcXzJrKCtdOG1e z>SU@vvwhMcgMhkhq6UM2MLtsP*uiSHRQpgg&Ke)hVHUs%u;7?T z<|GC&f=|hCS#T1>$C~u$W3?HSx0(&X`G1r84N+gjJ9Sd*>~!BIV#@(xuSCoVIg z?dN(y$SE)^^O#?n=BTigAXFAaKmB6F(~O~NXrAF=IACwU-5qc;3^CL^peTj1$I$tq z>MEd|Gz?wbN#5b=^U`9w+Hf^k3VmD54){!L6L1p!^raDn6RrCQHNpM1d6cq7sF6wR z!2?IskA?wQiqd^hh~=l_-h;j!0Sw}8x-vpdu9Qn7g{=`t#=US^cckC5ApPzEh3hhR zgrsOFZ+8fGtxybE!>rY7RA;2>7laoPLTo*gKRI&OL-=)s5QLvMQuPbMi%5vAuY#1k zqp^h}$rVFZd&iQK@m5 z0FkK%(tkQmt&SU-pT+^3nL$b8)uOuSKkKAjqnF34&pC=Q92UJ#&FMUB%<=N?M~lX* zg~Nr!K1ZSF%`u`Q?F-{UU0BN{;H7?jttSczWGLc-YI9vI9PIkK%zFzAZW?6TCCIcd zouDQ-;_^?;K^FZuK@Et&LxbxzoaX~cpQtVXvwP)4U_eXgkBPv`mQbllYONq)UYFVh z8F^{^B$XX?@0_GQr@@@1wf}F)+CkpQYD(}9o?omyh-UVsGLzLbu&i25R$IIZo3&-- zeh#F7_$CA}aRSLg2vcGX=@3jYCRrf$Sno`A07{mXd*N~7HeR|pdAqT6rjr+x^B4-9 zq9$R17oDOODlXEsbkenCfm5!5MNTD$x_A@8dyw@M5}&X@@$nROAk>lFQ`L;f2e`>8 zCN$oMKr+>z!mcq@^+>^Yu{)Tr=P{vi!#Zg4ksunBCowPmT_?OOeMM?Rz;f_37x{sNb$4C2agoWaPN*E&v8wwfM+Qe^?v(c?hjcGYZE zl0u+IhvD;ANDb$xD*!hi%~2b`jC6y!>S@_lXn%q)nyh)CTISN7dFnWi4I9ifBCccs zT0CD(mmjEfe7^b%v^O?MDUd zxJPV^38zUGrF@Hdx^4IU7FJP_rT;c{jC;uH^0cCnFNxB(tEDj{t+%TmKxyu5=fr6H zx9w_eDXyh+gvbq<)bx4MWwnOO)wkwka9JDfP-FOe8DOdKoZKv@E2Yb|IU-awP zoP6(VTDxDJi`(Ot2h<-qSm~gOp|RT>1QE~S?T1j}Y5Tz;a1*5ScF7}ZA4ytAi;k-8 zVxho-)1(2Kh$Smbyzv^7%!797F?F~se;iE9`m516owpxXi+CnLc#l@}2jgZv87I_< zT>R`4YHxW&5N$pa7C{*&@$D#CClSXjJE@L@oNtX&X!b+;;1s6wAqX^@dlHzG?0Q?b+PSYZ0=Ph%jLlY9oCZaFPF16&7E%pp<=JvyUC z%IgfOdseMUEq+v6=Uup*6ec`cG;HV?MWg3=dl-wLPyU zc)p8~tI?K6C5zJ1^J)YRo~`HAKf&OHHjf4S)==en8*8t_1+`en&{*dpBo(>g#OVUq zMayZ&1;Fv;3yfc|qaRZ6MfKN^6OMN(i7oghuv_E2IX;n>LzLmbuWElc@1(2hT>9)c zwHwNh`W@po2(CZWB6RR~^*iVNYEe*?FmEc1)s?^-fCa}4hRv_QavrlPmt0SS_ z?$gK=180OacqxntL1g{rHFdc#&wJu8wK*1D$@@58exWA!)wKY8`U8;9OJH*hk;kax z19e91E*1g6l!1ZiKoPvfU}hIm+(T4BUpKi^CHyM#x1fQJ1_ zZ(Ygv0`>Q5>&w*eNqlfM0oNEd7lFU=k&08+f(dtFAd@vqD)DiQwjmnRcbeW&wC7;K zd!C}b3vii7)xN}Ii>gH+@_SXwfGKKO(++}de?-$t6n#nq$T@0(eX@lsR&WV8c*CFr zCn%w7EifpfbuBiSO@Jg|xq>9vWG~k>Ns^A!N(1%HpmxDps`LpB3)bEY{zBu%@%j6O zM&X6wmb_ct@HDCvq7?-i(m6!i?Ajuw`abkb&_Sc(z4o-5ahI z#+oV-u9Y@H1#&i&T%M=*OX?Y}y(8Z@=zO@AhD1glbY(hK&7-A2Riu3$t(0#u>Sn3| zJ;53V5}zhl!#yLnm7QXor&XM1FIQpO7Y41le`c-zJZ?KKn?6REL>h)R)Kvb3LCM{0@Ee!E|!wpmi9 zg4(n00~|+V)r_MLqfyX2njWpSiai&!BpEa<2nQLh_AvtkdIYk$pTc6e1S%S%eJa`H zOVlFirx*z}MLiMqP-@zdRTGsYKva+~m5X&(&7jC#D4h%GzeJ=R$xHLg5 zNs%gnRg!{HsmzDy5}FsUwTsDVQ2!LuX_9Oe<0d5}XqEH&zdrGyYl8Nb)5lM8cW-;5 zRtTyvza?r#0Bz%vv{!)fbWg&t%%t&2+N)IILP7)YMp^%3@fP3MW}Eg5t>{`dl}2 zbFGA5aW4FH5Cbm&q=4p}{oZR@JyyhA`?|Iagx=dN(f-}`mX_K`H^ejQx7D5x8XS*t zqZ<|EWH{Y*GdG{M7DfNQr@xD0(+{PJ#k7WOZ`3Epvm{oG{dqC1vYLh%_)+C;?DKnJ zEQM{t_i$!AAmt1h%u?2kv1F9f5+m9{-3d`pd}4o7GHyn1jI}G3)7(<^ukxg5f9G+5 zi;qh|CZbSE7Qmqp`DQB;901S%2)qz1@TABVLp?b@#qGEPW7&;C<+Z4RnjZN*5(OI+;zax)y%`;Pevin)5&1%{OM#a^w=kC z`RQpC^}N&FhFItvm+}2 zIEey;0c8&t;0Oj$x9~_IqVI9WL==Egg0HcP8br+OZz^iva$w-P*TuL%r8hnPWr;7CfJ+ojc`;TO`#@QN&>_+ zHD3hqmcJ9QKpx_Uff&C*4(6>U7?m>iswP@(Pq|?bY_uR=#wxUswMdi8aKADGOPy_R ziI4@A;hYn|+D0W`)}onP_`=JWh~f0s%h<%b>kU>l_=(3p31)w4yDriiPBDr?mkX?$8daZXu0mr=_{iDn7c}LF*U( zJ1A}+imb%aRXATQ>eEph5WWs)(UZ|iCyY`yRqLef&719a1LEUM$SIpJVf(iBocv%j z9NB19!ms^Xsh_>g<4Hfit!+0cm{BBlM^dc|x0FtF*4~13$tqp6*YTLqMLQw=Y4_|3 zvyCKIYgqThOCGph7BAt3l_{z;4Qq&afrX6`;w2P{ z+ylj1h+$=k7ibB&tV4R8kYH=Fh``>TCSG({_!KW1pn-US&CmJb1!%xR@#2MZW$^;< z&dbFM{12=WFYInvyugs%X7PfXr=6SP?VsP%-gV=ISlv!*3^SSD_F4tFgtOXfzrlHA z@i#DId#TeKTJ?@&S#VdF1;P!VIZVep2#h2=buO?2Eegj6^U|1U?ooAh^2?bXmMVY>}A!@$>=c&fHNDl+US6Jmly zVWHzGG`yEq160YLUf3SDDC2#th_T0;g68tkHHqH)GEY&&XT7hj!nsAi?aYt{Q z#iecQ10Xn{H4gUCe#Pb8nh&+|p??Ay-3R>NIkxo|%; zF+a%D0bm42-;&JIc-Cra)mN()^l%GngwE}&RX2u)J7=rQ+DO;?YHfnH-Qi+i`bgUl z{E8^jT8WBUQ9?hhAe@fW=%?i`bZsnz$ssrH16_urW~_vHoBWmE__CFaH&6$ri@NsH z(&hcyq+QDk(1w0mneZFeV=K5Yzi3JVX8y)?lKX4#Mfjt63M2SVM|>IDUuzfm0;+_) zuKK_H)n99!`->;=#qK&lgWUgQdN@$41vpS6Q!AJxEIi}J5X>>|k4GH$Y=qa!^g#}C zc%}du+cPyEbgnOEYSoKem?Pv=fgW4@>J#$dOkNm32V~2t&9bwD+BSw#AMDr<0`^p$ zJ{}}$+%*ViNcPmhfal*(*}*s+zM)qJYd(AYV6CJa69aP9+I!wFU=A$$GUM}Ry1i)# zP>RSQxXI==DnPTnUGSrIG2uyQn06inVCEtqevZa=jb~|W&-jlT+n9Y^YCxid(RW7Y z>|?`$C`(Ihb)@!w5LRHvodDb`Xyi`p=kICjPOacT>}Td^KuFVt8P*!INei59Ky@y< zgRHIRSV9F{QV5UY^AVWrahoHU`dR7Uu#$fxnJyR;Go5wvJ@bP+ovm9dASff5Sv=`W zeh&EWS>thBXTu%4H`N|B4wC}sB#Xxb+w5w;o`u21>`t7ZRg)&$M<(DHlYX$TP690! zdWvac8G;34aDj5lPMD(g^#BW}>Dp8ocyh6s+8hKA&IGZGyvd(xyF7SaovT6nV=`_0 zOzTaV^R+hu?=3##cN_7b!YA81Ki9z4J8VB(pbeI(M{lKt$Cf2GU0>r%v={BwA|*yT#AV#zc_@RrjFyVQG5d)65&t@wy?(Ur3 zH0w)lJFcOsN0=zD&tSBbO=l;ZIPw%a*?IjN+p4YEQiX&724!PWpmV>lEl7lBTfzBM!;ye?8$dGip zU=b>GRBP-M1sN%Oz)|2|fF0A1Y1@&p&G#S}v+eocBR2S4I18L&Gnr?@Y5k$R8FcqJ zph-4Gp3ufIQqNd93dUI-sH@45ym8V5`|t?>U9{@qDXl$}Dd0T@wtFW=(uC96F?n31 z-RX?>FL&rU$NE0@BCgQS+3)?Nm6S>aB4rWjqyT?{*;cH<12~(L0zr}S@OkYu#tn*G z)M~41pw7)8mR7v2=cTb1wbXKf47d^&xAp!o%XE5*ySyphr{Y250??ncFJ08?xuKX{ z>Wa1-kB3*Zm*ruRcC)KECLv9r{RTD+WR&0dU2BDS+WNcpV&vZu!5}$#;O|B7=<(Yf zDtujgDf0K6h|3Wyn9iSmp$XTuNf^=sH-P(P+v{&=UrXVE27}4rG?)h8!miCG^|n|q zMQ>yCX4AX3#bkeZTRR|+iKKydw888r1{cVD((shvm)bQi7%}xlm#mf` zfgn8(Si|Lm^!GwfhYO4!{t-YIWYgC{`W#3|uJG#V)JWIa2P)oc;2!9%SMQ0?O+&9w zs}%h`=lwGu98@1e)d_i>RudsN^S`oFz&Is2h^RC>SM^6jw1;u;Z-!eRx%wF_I9=(Lr ziwfn{o8yt0S5L#^i@bUPJPzRD=>%6>?u6I-6~R#mY(5oX&rh{KN>y2qMs3?xA!{kDM*gh z>jd5#=X#%+>-}=9o{}2yKf^@1aApBy;@>>Z6pzzqpn~0Ty5B3!Gt$Jx>zNp_PvUik zW1Hjk_(ZYpFlJ-D?3IQ6Py!g%4fq?s7q2HGWmJORG3__jBoO30=zD<9vT`$Dz!4Dg zR1?B2?1_fUO?|T5=DAbWd?IDh+$q`iC&0Piy%0)m;G_khb-{Bf3NMnV=L

c_95% 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໭=kktbk6j|ofp{5q1!)QGe1r9L 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 *sWP` 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 z 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�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:m437CxoTu7&kol(BjXpQeMk!lPy@?x96JM~; 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
    gz^I)9 z7)_V+17!i8@$^9(WFOn%WCKRi`;H6!`-!s@#0&b05^fLBvfdKF$*i@Xf$3#AfW*RWkzCg>OH#gAW7 zoh@lW7rXRuTM*z-!yX@YxEGQj&61(@aBM>OYePr)swEbnZXeUp6LGTYALEU zTurN<{~+_?gQvUB6U^oB;E8qKcOlV($vn75riESR z-p`Z4ga(`Y$J>w)%#HHk-us+6A29Q)OAZOf4&|uXNHuQMnER;y7?WLKQ(#NxGH>5Q z3Vg2GDf>CR$|-yJ35FgxGvP94bISHW3XI5H=2A}C6iBfp3F|o}H;@ARaUAdA6qt+I z^}sF+M>$4uQcfUg0f%QeWlkUkyrIi{gPWKgNSVyxbWX_%q>Sfq0lzgmkTM(zD>z{& z9?;}L2tMX`KPUb&f_)r+NsO`!bVtHbPUz|s=#1b3$Kko3D*(lBzQx~Unjdkd`HAC= z@nBoTy3Fs!KpK^aI0n8Jg30_oA96yxGJ@$GXJQxevIy32JdG(>BtVK~ZsP>u#Myh- zagN73@n{5hIj-OV9}Gi~#c?L;=Ilcap!94C%N#%6HVI{wAQ(K31#CfG<`qB*+ZsYm zsFcx}?Jh!HM%x?u@X>0n1FZIfVBPF-syW66)~;hfeCE>mG3sLow6SW+sCj_?axND> zFxPnk6UjGyniAW`n+HeB%5rFrP52zMANi^tn6^shTW?Fa^bdkk$sB*oRq(ib>x0y9 zAsY)6Sho2=>ehz^ZrpV#qXH#vCl!=kRJ-8md`!X9$EgKRSJ2_WD<{^<1&^0eQy}&7 zgO|R1pYvhvq(eCF_Q5`z`|7MM?kf}bE~~~kb)%=;I*9)am$}Iqf3JXW0m1@auMicO zzaPV3N1Is^79_@=QnJ(%Ql%O3-z8JmEcLE5(|Tr-nk6N2E#^*44ZjbZgeP99IITqY z-&DIwm8k1m>e`4a_&DQRw#GCeA)Tzw2WtFyvf2ZIK1FTqW0X`HZ~DxeG-8U{BlWPv z&3o`7-^T*ZwIv*D+;l2i4?#u9RJFFsB_Y@no@Pmgs|A$lCgVJC7)RqF*P`dAf~~wr z^QNjv@r)MH4sI$xGN-|kn|%6es`^|ISB%eBnWpMK?i2R}eWGsD)NAqF(`W`%pF*K% z*@6YO6v0KW`PBhn1+MzlvIr7W)dmQjO;zh4Sd^+(NAPW`TIq?sW2*6S2FQp)&A@YS z5&W|09n8G+AZ!GH=?57>oWW`ChiM@NX&!SgJzZS&m)H}ilW&_2JQvgnX(;N1m>C^d zQd}(-^B_M^4R?GG?Jce@#{FhJOQ@A$>%ZPE2Wewd8Fl}l~Wf$j8`9UD}JzUiR03)ztX5vL2623G8- z7I#laUbnG9rLFND)!&0*&NA{j@cl5lxZPd7Ai-qv)Qjp!M7#7*zYl^r*kgUwuQ5j9 zuc)vP%hUR zHb7kjlP5|T4BmK@RdcZV2U|c@8>)KTpc-qvuKIzdy1uSvM?_*EVF|-JD87iSTekYE zbcxzcSN9gZ6yf;HgiaA_2F7leDC2FlR?!O)Six9E*lz&5z(C_^N)kfA#WN;s=wISIKVr0hOV5+)YIrVdY{y;o{tkCCh9U< z6;y7HnjDoAEN8m0KCxizVjttoiTXHdUouB6myiQ*Q#gYCur74Ssq4ZV2tO85++4_i zzMv6vRUwb|(&D*leQMrQ`w_G(?tBrMF9>ywdF(55X z9?lAKfQQ79uKd$o`4ykx#t%m_{L)Q78x>3C7pNtRv|$l3Zjgh;Ro4ZW%fr^f1!{E} zQp2N*)I1QE%NMJsFvAmb)HIzhhKBIJ2$W}XZ-rxQ7#E5tMz|JItgmxG;8El|>M-wE zz8sD8AYTbuG1VL8;W#b1<8A=iSokuCA>hPvnR*Fz$G)ppDLP4Eg|pE4s$W}bywG~) zUA1VC^qJM~0|?ZlQ`Xj1YB32zba=J;SMyFfFji^V_OeuerqT&2Jw!ub#yHxJVkk<> zY(eQnam2H`tJVkPbzQ5jaieCts)se(RXwcPuIgdUc2y5+#&RoM zbI0}S699449B+NLUhOA8-iZr5sGK7YtF}M$YiP}S%Lq7N&F-F@K^!C9HY)#J*9!tGx5F{F~CEL30I?Swze6tNxj@MlzNyLt=~ zDtU)mTsml_?ND3GV0&NtT04zen)NPW9KKko&Q%mBB3ZTD}fp9BtbLKpjUv z?o!7|W2_##)d9iMF6;ZRRPsu1SOdOR`|vTe(?@_yahhPnQS~?2yAJ$TeGiy8@fgN( zAN4$@Ht`)yVEmWOE|#sZ8QMg)#Gg4+>x3YGrWPkp^H7~`?h~}*7`PK0x;v)+;+BTe zzT-gVL+SQ$v|^~0bVB`7LiEDPg4C2Erpy}?+B(##c}l%1;e(~lsQr?L>fx-nD(7Op zXef+r>bL)1OfFQc4b8c&_NI(`s%|a6qb>=?%1yMzg;U)mEd~lojSw5av`6rtRVi(8_bJup^yyjDI?ant`$XDWJ+`pW&6s?c_4j8T=}s$*z4)G?I)^lRtw z?wM5WsD#tVn@VVPpe4IqLX$bVxwMu-acNp@SRU!6v^YvHqXk(5(m3}k8e9s6k6If_ zX~7&t;f^rM<#HCvL3+NAMwZp)puV_rS{z5qr)wXfs&(a2?H+m9=Zqe(TjL+B8lJtF862`c>14N%H;#r}%>E zoVC|N@h4!zXuV!j%jP(Wj*#Rp1ErSM(Mn0!<@eV`-+-Bmm|C@zQG9n+m=664!~*bY z!@!CO!CuT5zpJUmNHCj!wVo#6Rf6hiT@kxe50J8iM18Fqh?%GBYuyoSsjnS@we8yt zwANUX=Nf47FeRYB8ft?;Nep;g+lvDlwHj&V;ydxjfSi25@xUFdeZ`7^{ri|k7@YYu zyOCBl;v5daVUJ}|g{&;F@*45b=8$NjRgwD8Q%$rI2u3u~D&otzO|)1{#TQMq4hZz7 zsO7j-wW-!f0t-K~nKo2E&Dhts&_TWBh_%qLoCrEz8EuohZ*9HxK2g_fLP+lq5V z2DW3N$BHON+Og0&*#ZL=e2$g(IJ?lWrM3ZT;y+qysTd-yl{QtvLHsqXwU;m(aZhS> zp=59Wq-KE{h-{-lIAN7;qn+WY3Tvm8jAP;e^7hbjt{BtJY$LXIn6OjRcBpSIz0yvb zjtZowwC6x#bbm@ak~m7hQ%{y9ksG< z$vjST_yJ5UeSrrW^|Uq*J9%OUeBd}8>V(DcR0qv)o^_hnK?^TP7o7pZM>=RD;z_Yr zf5UKS^h6NjIZrv42UX%D=_rm*aDWedh`L|v^sw%YozQ9fI}BI$=TPl5G&8sJ!)CI4 z#%?CJmz!y|env}{r7qO>S**C3G~rpTUUC;m%tCM&uh#=A3x;y?+_PFu zlowsK4>G!dz=;H@+yyigYej`^S772C36ny|SOpRY#-fSr&>au-XIJg7h%S628gGUs z@v9W{oHqV%Y1W+QwC5!(le^Dr6+qvlch_EHqlfoj)LO^&L3_Lg-(qKYVBm#WW%X^c$g4C@d-f^C;jkLcov~T2?MBt5&_VBsu=uAn*^&ZNM(znPcM`)Gcwkth0SPx!G$ct6E)7IW<)xi8NC%p0hsbrJY7g{vOU+w8Ff2*%^pg&_FL6#zSz%E%{-H))&^P zO@{)f^`T`$ZG89TP+W2Y6gv#idw>oM!{%{-E)T;n9U$LpSVspa<26lBIKYQ6 zh}=h12&(|V=>h8XnwA1%zO2`@X29b|UIWV6PtmVy_0YlguVX7aO&`9l9ZG^wM0k=Q zI89aNMKc%r1Y04Pa}=d3EgBB&e}{Gt*LpUex{>V~uwk21_=$}b0n4zR;FEG5<+6?4 z`TkfKwSN`rh?vc+)i?@(36af7C+sJapQq64Becd|c*a6I_l(ez8(l~MKska?PpZoX zWZn}DcD%x38=RUl58`0|yCDXzZy91NSy=~W81@fEKy`6~OM#)pNUaS{X^a|)vFu6T zjl{_QM2$yb*p|~vqqLFo}wYKilbvv4FxW)PJldT4vp<%8n&)JQ&nY8U&qS!UJuUl9VgK$O|z? z;%`rrEb*x&j&^gnfz&p7;}n>6wbt#UMj#b~s}yooaQ zfM9TUzX9rGCFQ@N#f-Xs(q6KOrUazC^^grDi6#h6^JH!yf=MtJuM0$6dZ@`f@W!iw z1h7-M`6&O_D{J=!=?YcmQ9+6-1IvuE5}_GKFwYSiIw5{_#)7Tl@|0t-%T;iRFrIco{h zaaInt8VumT36fa^=LxW8i}W@>I*XWH(F#;#y8;es!e|aBhI*SHoQLI#ox7p%XFQaZ zrA7FxuW@=Gxo*Jdk}SJ%d$Y8;Qg3>GlJ+8KseO~Qw0|@eHKUohAB3A3{vVo$c5(B< z|JgRQ$!=TtKN^NgJzOc?Drl8tZogI4r&IqowPTp|rf+E}QSXN89;|pAG7Q2Of=MGKrJzj+F~{yF?_{l9^k={QBkLV{e6p4vz5Zbi^d|O8 z^C)YwR=(KilBkVWoe8G}cE)eCZ?aZ4J`Wv-X~SelzuC=Uu|V!S6fs3p3qZEt zJxdLy0DMnV-zh*Va9S`0VE&`eKuhgK$Lox7$Q3#|MSBC1;LcOEQYD=Nyb?qJF(_9O zoHrZ;x&Yo;HC2079{Ur8PXm3EPsOKc{Xl`vo~D`JoP6F8k52=UyOP>vYfpwgKq-`) z4FX~@#ZTATW5ez@9R%xQ%9*Yu7T*Sx;$RLNxje)iW|)WsRTBX!>0LTCT}v)@DS^kF zg-qQUynY>^v|JES7Zf3&1_t<)W2+G;qDv(D*70@Zr-L z)3d@4fX-@(JD;w0o4+2G%^2kbeZO84txxdYRiuV15zTrGB#liR&;?rF}oU+P>q8G^<@a@)em>?zGD z-Mnz}o(IVd3Ma#1hEuqfliJ*AQ1ya3DiLj}MSJlRo@F z+YW|z;3_bZ%c;a_V58+UU^S!>%dKx$Ye(IYD-}LF;}Qyx7aPTHUZ?ezR$6HvY7vrE zdV@Asrt6lr7RP)SZ_;W(FLHd7wun#tzWxa$3u|c2C)#RSfq`>x8Y|NcBPzxNa_J&< zyC2e&%54G0pGobvXf+dekHrcpWHikEWn74|tG0k*J4<<6v@T#|%Wu`nAn3GJ>w|-K zd$&TQaFH%<1wV6^ifq$1DR64a%M6}&!$Bn|M2ENGeWF|2ATg>;)3$>j{+P5KpyMyX zZwhewZtAlG4AVuLv_m_GeWCAXcHZRAw8?3<+eiW6fa4coo3z?1SQ&=X{Jfa4Kk=P> ze^BP$7Nj#=7lEFv+g0Z)PEF7+58&i;uOAbUP zSgZGHMVMV?-kHAMr)4y^CneSCyzMVoMsc1K$zTG5Hz-{~@9|mo&}Vz_dRUwn73boV zJ5OWFFCmdTOJ~2t#AQ>f{QwuDDf=Ol&ZbNIwQ5l_-8%LqdsJb6=~2#F6~4k&UzFSo zgAk~dZK#8S3&WJ|IiPKV3T(_lZA>)Wi!tP&uK{y&Nf}15d0J}pjW7R^(}SMK1H_D{ zK6x0-Pib17mJq`SEwF*W9Ti%O%?}6S%};3y7kr0~=V^@--nLK#fW=TyIdDjWM=pnI zAJPu6qa_bUG3qd2e+%Uv*6MjbfpiW4|9ns>5?_OrK1waW)|$r5fLpkS&;$qvp!T3O zUxO@JN4rr1_M%Zo045((_aj<6`NL4^cP=A=jvUdN1_xvLgBzukZ@|7Dr4HYKD?31| zztQ4+W5Pfnvt1I-MZm90G>xnu7l{#Cmr|_~l{l)U$urU@Z-JZu4Atu>M)iBjKMK&_ zVBr!v2nIJ(zhhd@@Q)Q#1EC^>aLR|)Psad$(rhd9du^};sNDF2R<`-kLk~C$fIWP> z@L_hq5wNI0aDZn6n<3AJ?cxInOBrTU?{Jl1yNhAXl!iPgIMh3CAJQJ3!ZuV_gm--wCY=V(m_7RqPRjk2`j-W(%fR7twtrDxU$IF`x8WF$vmHFPHW(uKymmFsOSJ2JxIC) zrr`jUx&$mcmwvvay@ysz{~2?=*n0nGt)0Y^QlWhmCdEp;qA3z4waitmX{_BEj3y5v zkPfDu2a(>osy&XGI(!w|(P9$UAZq=Ps$N6SZ_>DHS{C-Gum@PjAPc7gtzND(Rv~CqcZ17wm`C4Fy=9)p2E9*m*^Im)a zq$)m*1a|0yJbPQGF>jRD=+rH3ZR8}h#r6@&R}bFs(Yl>!G4$bWtelN><~C;P7A4)$ zN(6371lBE!3HqE`-qDguzU2kduw_5&0^+OD*hvJ$@HoO^W9-0G9;fMdv>4wRFGPMU zI|0?d?v7Th)S=>qPrUGw^eV>{Hsrymmvcp%auddzD1P-01ZgYi*|})+<=?fU2KOb1 z?WFjq4cAlXqz%A?0aN<}yVh?s@()do%02{-d|?27ED^@PS7`Mg+JTXg5HmKSydZV<}i-Lk!u$#mo| zt$Nrluxx>pn-qT!vdA22b5C0onR_@%$ajmlz-M^jEEu=@hLxm}_qCD{zwjwmAUoK) zdCdjX`96@~TlD&U%}guETESVf@1xK%Jn=9ugE#~hU`Ph7*n`g92T=SnD^}Dl^$Xhy zaD6Z{@R~>N;fdvQ{L*B+BJKqQqSi7HqxZ6whK`{r^z}R^1hNq<)WvdQ-ST*aRdtb_T7RB2Qb!4lOSsbY-T#!x!BE*Nz zc)Ky)K9XQ&BaJlEAd!}ot;=h}M?w9f7zD;!E;Ed+fqMb7X5hGojA~5CBjLFRjJDUo z8Ys5n0aE2nS@{s^c*$z70Lodp704bHdZ_|dCnr;@rEx_lGY*sWIrZe=S9&{6l#2K+m=|^kL!nPOOM8));H2^4 zG>Eu^V3-41&j-^TB450y3<@zbUg(A248@zT#6Nfw=hPp0GZ=5Kix)NU=9PF6AHE;U z8Q2+$J_&#nl^|lI=~Oa7w8Waeo*>fc-2}j#{ahu23xl5E(Fg)PwAn4#!P9{Gohm#AW zN>}d4rpnSIpIzLsuC)tMXI-R9H?1+c_)UUE;1ItkUFp*a*mvWdcCi%1W*)H3al7oJ zr1q12z}A*@uuJhHezA;3r;1hq+BSp^rizOHO}S@Kt$o_-0WQTlR9xVC<%v|bqsWfwNMc*|nNTko_LVEJqcT@nqO=HMo0w8%#H&yb<&*(j^r4Mq z#1vG}pscu0ucV`IJ*^kY3*bGtrl=t5OE+jq1yK>fkqW|uCM33^Xd1Nr9gN?0nqE=( zLx9{L#EL(XBNxi?hv0<=<@_N~f)t8*ABhD$5(|DL7V=0e^pRMRM`FH5VquTO!XJr6 z6pm3u6%h*8q48BjGvMX!Rm2+zey)POqNi1+D)2d0_!HH{44BxRttN1@bUpf}2G&zO z(rSv`&~g1#Q!E7MJHD1^gy4Q{5kqHciR0L}w$?^1xfYT-Neu@xiqgY?O^cbk_TtDc z&^a$`-uE#Adja7?9V&Y$6AesmfoK@U=p=~k_68xCL5u4Oc93b+6$8Pwl)H}(3{gh_@gJH zRcIHlIa9`gc@Y0U(!mBIA@*D_i&cY6NYf0Tef|L2?mcv`fry8EG_Ii-4^m)3L!oCJ zPh?9Cb{-I}{KggpU|uqrlHoQmvjBYfL!y133RL1~)76F;oHr=_aq&HDKSCRcA<$Zm zX(Y-ApTmafQ!ZFmBLPnnCunjLu}}KZYTr~e^~e)^^v)9^11`>vJb{HVmCCool9*~e z)lxtxvxt6cCAxE=POU*0O{HP2MJk3BZ7Hh{4|4}X@_~qp~3Bh z7CakQYF2=hWaJ>+rn$bIcndI4_bIfJ`m`5409N0$7cWZk8O3^{6Lv&AP+n(|LdQFb zeqiugbP{7EY@G)?gIruq*E);T? zy6qvh_M;wRoogNe6-RA*{s-<|S%>y-?p;d+s>K}vE@dpXM2s8nuC^VXeUg=F)@c+O zrN?_K!DOcrm5tH|x+^K|=#`*iF=R#ITu?g)Hj#_uyeQp>XqOo2Q`o3{k1hNhT}Q5E z_2?qjN+~~_056fo^qk`!&xTdVcv$R{LzTLUPO-VW?ftX=69hmOb8O?@of&8T`GOvpdN|JxI%;*ylvaB8W5sx$QP{twzsb zo?vaf=Xudr1xMtAI-v}oVrPjr^cmPvvEhI@P%c?*y9-DZV6pRph#S>IXJQfCWOoGf zHe);M>U5+l`&Y)|Wa7LB0x+DN5gfjazQU6`_w{K;16+i82fj*-Z#-^`VCzbJx0O?< z6{tD85O`m{$;ZQ95t<{goiS2_7e#Cdcxhy67nr62f_G~3(H8p)Sf4Bfut%~7jeb!K z_sj}YuFmcu(m{ik?;+}71L)gBGzZLn+5_|iLSIjTs||Wu@x8HpVXxWYWf0|v*Xk?o zA&&bju%j=r{QX6=6ulTsG)OMSe8QpeBC&i2V1HR+9UTC?i*T?{SvV%q0q{FP0g(GqEZb1QnvN?V` z7Z^BLq&fx0BW(_+@$zBSd5&G+!@tLqs)sf6$CG z7fO*b6!h`}sy-An@hv#}65FU}Y5hgKnpaw{!mkFE)f3>@ zf&MtE__=}pv$WItrlGzt2+@Jf^*0gRYp&NuP^*PryxDvSBA$Irf@*$eyDYIR_R>`>am86(4jBkU)X|zTNjx74 zDc$Y2#h=!;);egoM`0r(02`?gASpXTt^66-+eoz4%ZAhEQku(E)rFO6PNNQOb-m~} z5UH~9APcBK1G<&fG`X!_Rh|_^(~ne1pc8HN#>LN_4CpvP&jU^K{Z62P8z(8VLV$eD z+_RibQ`+iDZt%g>coxvs*R*t&nCLkn%bpq8Euv{`JN8STdaKEDtfB6cP?xZ(L=6;rQ zDGCS|oBd|D(daP1yPJ!4)R7HZI9t>$ay8g@S(byrqEZ*hU5cvA2U#|UKAjIiz#O_b zACNhRd<(F4DnXTneKKNG_+8nTE8Bh~;M>2{vwA}qFTLUfA34FF&+6@QZ-$uc2194f z?5+2cq;9liB@oa}IaUn?58*Gevul8eMp!!i0?D`UNACrR~b!Uj=8 z9urNUZ4l*)j^n)wgUM$gAU|lOWKx|8`atW(1~D-RD0JATVr$X-y@|b}iEL65_Oa!L|feDEA;8#Ij-`Xrbipbx`rCHzu)1W=p$6G|75P4Y`ZF@DLyR_As zvRk|iX_)oL7viuCjPcbzpxtG(|4WesOR$rN%1ofX`^80h^nQB!E3jW%tqEU=#xe{7 z4jvR~-dh0A3|f~b;;raBAX%V__J_n(majGZTI^4@^MR`wZ3__$zCti%lnig)TdCX; zartj0f*vVhRsKc+%_;L#U9w_r&s{K9on>^Zb1{CR5s`CR##RsvYXF#Ao zh;2M8=F(VQw(a1@ zqrAiKKpvwE0exuaVVibR`2ne{#B{+%R(4YPVF-P3Q`9SBzl(hu@nXM;amIT;V)ia>4rFoZV!CaRZu$L3b@L$DXJBp&KI=RKEs@21PH!DXJ9$DK&HB?N|} z#w{R^O4Q*N2Kz2eyd|Eg&)+t0<{ml>N(xuC!Z;9ed!VZ;Q1YQO4?FC13N$1-%(5}N z(flxkO5PSVoKC>V4DsH##Ybhhac=WrkwSpy5fsU$-W z%e{sk+*V}pf%>s`V456g?5w4KYb;YIXe>#;i|91FVay2*@fB1Q7{k9cyY}y*8mi=G zo^}FqNsp{6K2NX{5h>K~#^p#Km`4WSNyi28_g#VqNg zhWEtK@Tq@cIlGw?@G2v$efO{w!~SFiE)HyOmI1j}?hD-C_)!HtTn^7FSWf_wKdG6l z*ML@kf~*S!%VpibV%jI`Pk9$wfO)I9TYt}srMN#tk1vw{TY`Q4jG1p;3({%^9>*(K{7uizwE@arBcV_bfoR{)p0Xr%uj|1nks&Y#LgsmqB5` z=cAdOpU}(Z>C7-rk_MS~-Tix+JDsx7I2I~<%$iwDKk2PE{@VgDzWGhT(~+YEPt#`F z;QqCG` zt4y+bBn@$nYM_-JIh=_r5Qyx2W3|NYS>_XK@DFiXre|CHj=W6fJ=8-hL$quQ;=ogWZJ- zF5^mGET^|f{AVRA0+rBLxOE_!@!i4Y@odUL_vU22rotqH#;AN?KBKQzx@;M-Tc#e5BdCFR*-@$G!GNFrxXzVcs-Wxt(<{a4Km|P+AGusX zPx#;8h@y@a_0F`rs-7OZ;(9_a6IKvbF0EF~)68?WCtm(l~2NXT6;qvn>kLiKE`WBHQ{fU^uIFSN#C4 zaNBO_&*@1r2&UTIbqzK=&ve)Q7?UyG^&iX9oU(vP;AT|tY@OCZwI4+WQDw{hsrotP;{tn zR_SkVBJ7$XuvxhOZl{|6z)tmUz)sa=^%<&9z^u{DVc?ffTBlwE1CE|F84eb0CuI)@ zzkHSs498c`QsfA%kh4^Fg#H{xXW9t86u733N9eDF^bg2msBa7ZMC@%#I{J$Nri(n` zuZE!B6aLyr9L+I{Ha+35BwG^Yb@Z!oKPA~a7NC`#cf#oluqGO z`m04wz(D|f#^byLNfY#9wD=8u3ay)B=bXMA71`Waj1xDuMC@`1gYwj@V z{b|%hJwD&562@`cq7nz8XQ7H+7xPW$A7Hhc|mn0#x>=sgv}B z&YNv)jZ--n?pbaeYkDk3pRJm!R4do zevp#)gU!()N+{fs6rgi{J8Fg=4U})cEf(le^vhH|KILH*I2xz8^|1f`&MVXOa5Qbi zG+le7X%8#UDBCEU>48FI1JtKLB4(JmKvIJLuU2)<)=QLp*s8I(sgyBL5q6q5E<|z2 z5#Gig?NcSA&y`ec7nSJ3be*(^?U>+Hso=f9Hu`9V2Bpl@ zqsnq)!yse!LE})UK@IN|FvR_Z&9AL%-MSUzZ7}| z2&Oq-hI!^gV)oHFtMq>Y!9vp00Ped2dnQRudl+$7Fl{b+8O# zq`@QBZoaPnYs$?`e_uBi-q;~Q`h9jGsFcv0D3G)8>mNpEo{veU(u?)| zl1aB0gR;r7cIQAw2{NwwQoW^gkg}HQ?6W`X9mMX^v3K-Z2x6A$We}K;1ap_^-Kg#X zqmwjd<4Ob9IA_zJ9~gmMe zi4>WQITaN>T8ca5vy+0HcHzZM+l7?0i2A=4J)2Ny_^)Y^R~icamtqPDA+S z*fbo*1R9v%m+7VD{~s{^zYQ3F{0G1=mg`+*=!M6-_uru@HH5vbF6)eT`ds&FZ^RkCGrDA8qN?A*24-0$uqUeQ)Vk!8GpKq<`uS zI>dIshpb6k_21+`gb-=)l>rcC- zEbF_ydNVH!D~lgwh2=!*lm~IyM0zg|D$os9vqO3d30B*44+9rZvbG%7%gN}-dG4~)-=gN<>Yee)_rKLY4k@A(1eOG@G1hyL(D95UPMptEwPWvDA?}MVICfi)63ohkBrMs1>xKEUOe1Ya)1rgp#T zpGVGOzmP6vrW4GtO5TK=!AXT)lT%r)7;;O`Laozp=~57Q^%-|{81#>`zWPgVg|k}? z!_%b@QXUUq2HvmMo`^9aH<`~3Req&9jlz>ZN-=tPq`B4`9wRT5Mkz+=Q5Pgw_sS!r z;m(nH_8kg#9RKh^?A3VWFr%cUvzvy>R! zGvm_-bqZIK_dz7U$+~ETV%)<#ERQl8Ah;4`RL4LSFKSGYLzl|EaQNGe>o!_{9a)SH zL>oJb{(>l=Q-*S0Nl^{bx;3tMK=%O7X_4tgu>{U@ViCS}hZea+0z(3isGz z?_q%pc+}-IImsAaZ(&H`dom!jz+av({$~WU76J1K-AY*(`-$|hPM8ys?J+>FC5vFv zNhE|~`t8zoG`h3_;1Fz6ooX9aoA)JU1skPf#w2B|8{dF(2F`6n5RD;`JPI*fKMCKw zrZm_HHAglN$As&Wc^`j*F?7gsv&kyo9^w`dy$QE@kMmkW^@IiK=23^N2Xb+CaLN)K zoQH>fV@(M*<^1NL-Y|IaqA1K>gx4|khm_TRrB_AACKi%*`w|O2wE#05^q2eU;X@-R z%Ft*#HS#cc(~S#NOSZQK<_z0_TxZ~-4R%N(ygXnMgb#rM8_d2s zP{?*|adn2Qr1R3b5sxwbdX6YJigO&rKtm%04MD~Dv8(24b+=QU7uDHjMXCKWj`+gTb zL(dvJR3<4m=OGY;mg*Q%@EYt)8|}Q#Y7;65e7u|_+boy4u8dEm3A9#c^uBV za9M6PG*LXGIExe+_cB%(>G2qg*cP7F#XehKw4nwmzEPJ0p!gnkQnb2QzRd z^GwXPRfrzvG!ST3ZYt{(p=+^0!k!#hBshC91tlBV(I&j_FvMZi;0HcAURGvXE0c{M zB#2oisD|Nl1fOhkHk;O~#vN=St2CpmjQsC(;|b{lE6p(0^3HNB)mRpSjekTrW4t^H zQn+B}5Q5vc#En&p?%B)GhQSb47sxzhK$&cN=`36x7<9$3J>=NSh#?Y)iS;&R3|Msl zY1$@foC2}7fx|m!I4>y-m*lJy93!b3gSika>I#F(#=}Y=_Ps({euzP%7K*S~cW*PS z4=>9ryM#3_T zc|hCCGmP5Sy+tqp^z!_5vuZ3AxI^l^+v>1FoRpkIsKguvX=vCF|9iEGpu?ZqZ246l~)hHF7{YX-LHRE*@o>|R!GWs=G;PO(!@O#l3 zHnY7%e^dh>)XNgpjVJlc^1#Q8Pf@;n4I?FdfWw`6`NqnN)S-qkGqIq%K}mWLPzhIq z9SEiXODVXfF`!8HhkU_33@?{j<7yf&N#!BZhh75!inR$XoN@_n(@al)1JW17*7FrB zcB<`W2vXF<+Quw=Kex8gIz10Omfc<+I~k(={~EQ**D+)%CKoO6J(#OJCq|FgHCD6m zw65_xZWXJKZ#zVF|uwVg%Mwj%oCObA}|F`nkD>+3W#=ELgc`(_v)&=fDVHBzZ* z3*&Pb;4kB36%6Jb! zDGR~5HfY**3)Rc~ncnS;j)?AVXDF;m!qBR(=RpgA*F9g>BiY`325ZwyS0I(ZOYZeY9}$W``PM`syr z-7tG8GuL<-Uz;-*UEfbP<{FdGhS%pA<2*qNHQ|TzW6xqEuc*lj7|LG>vL&1rli-o$ zr^Ut#I6mDx$LNk=U5-&3!Sx)Y9)gNXj9pk&ca|6)Z@WJfdt-9J;Nl{VN5Oc&2L2;( zX9huwSp=?{L1zNvViRJ7XfL26p*Brpf~J8Vpl0Z_%W>)Kf))yfHdgE9Mg`WXvE~by zABzt4U!u2H7;SNk|H2BxgiNZ``+(-V)b)L!o9%1GF{tM1_A16+#HDqx%RNj7ySm#W zsL(SwrobU;9JHIf)$m_42j^iJfy?GHdO5=ACoQl}L>Mil*2|oXM00`j0LZcnW8c71 z;6%Ph&t-Bs;D``xAwW4ylDL7a9~OPMneN_cRE#hU$hvU2A(+X!*;ddlqX@Q1E9wiZ z5}@!#dyFnP7yjNJ2+3Zfygd*CeQ90YV=RT=LCW3_3C}kAbiaYy;^_2#LqK9DeT8MV zm5P65>`XI3^I)*d;~;vN_Q7N-`CdCwLh@)YC~VN!8z}04k#1GHf>n&AlXKNj!6|IJ z3Q?Rd_Z}oYrU$BkT*|+0oY7|3Oadm2F#t$8TbUS$x|JK0pKo-9o7&bl0P8obZ*CZi zr81`BMLX=R5uKFm80@cM?Z+fEeo~b!$uJ2F0zzE;t8vo%r_0K}Wu%6L9t6t8 za=PQ8{8`oGsPcW|Nb)vVO#ztz9)W3@+5(h1?3VJ{tW^JFwfH63Ax+)BgObBRjE2Zh zvcJS;B(M%+nhf+vhI0p28dsHUdMIPLCuHN}2n zN=ESwf;52vP+(%1uEGz1kdNcFKojvM{>bod&l$X}x(7kc2sQvm_H2p(55nz`GAJ%E z;f(Y6(appRKQTZ$MpFPt2E$ov{l$R_D(tNuODBJ*UQ9YY>%PAPQc6hv0|T9&OdW>5(Pt#?O{dQ5W zzXtMJc>SeN-NcXMmApe&VJfUEDTrL|hWekOsYU!l zouo7>@AEH1`VpT$7fEk~`Jas28-*dW(QgQw*>s034Kzgc=~|e-a;&m7;%IJ3jWiDUn&y_y2wnzJ~Nw5Z38|zO8 z=06$hUn}axcsSY3;^c^Tj<*4;UdH)C*Z||?*9~Xq$T2=RVmZhI*bq#-F{gcUt3#*8+p-VXd zrxoynALBD++cPYS77G=?>0Zn#IGoKf&W$5jcP{0)Riq}yC3&}fs$ba^R#4<&9mlQ8 zwfs?1s9i)t5mHhgi(CFeZNIz7SqPR;51UoIlId@WZqxQme?{YRs4fHOvH2(_JkMg= zU>4Tq&as?P{#}YT{k_nSQKmlugErUnF96k4wH`*}5_PHPUxe6S_52jEg%RNBT8wIH zNHXOum!qvQkNaQp!i?nSX8vZNH>)=Hzm6jZA2#=IwJx;AOkoPAxA9+)tXA!@7~q^n z>fm1q$H1#P_`9p;6ghg7%px*j-`9!3mdyF7Y$vOXk!jrpkyym~0nP)R_&2ck&0M_} zC`s8Q!LhGwuFI9#W*;X3WOIY_>?($5R}EJjg5Vl1_$EM|xHbh*oR``dW!1hYvgC^6 z54)5#5>HbIHViP-APa)=pK&1P9mDj1Lck&7f&{w#vpA{D15RjXr+(k9oSk|9SZni53p_zg208m!(-4xiEZ!#ac>UGM}^q zlOKk5g97U67z{p)Q>Rz`eSyyQz3QJAo?F!6;draYg=s_lBc-mCKg9o7_5E;Ya8JP| zgDudVsq83{Dk%cw6^vmo1 zN+F}CqEc6qL9JUCDMp#Y{W=b1JvSUc?VWl8ckWnmBmIB5J)e}sWBgcuum_&(!~}mi z?@d_q5v`oyuT9e?_+z~XF5?Crj<@liyAsH%Lzxr(#k@DUBF?HqvnTqKy;tm4t%?4& z-gA>VOUc4p1j}3du{OP%_5X-_5BR8xu77;*?q*XqAsaTmz$SZl?+t_|poD(u2vS5v zMJf7NC<@q7G(k|h6axpT0TJnhdg%l~ML+=s3_TzS2nr}FD&_Y*bMJ08h|lkN-{1fJ zzaKtiXXZ{lbLPyMGv}N^h36)4ym*B7zVInO;Hf3oSc)I%eF}or*GGbe9-vNRyy=tQ z^M=88zGyTE<@;qR%Z-!^Who;mZVd8lDN8wKO97qCK_99 zRJ4;bRR$+LV2rmF+!5rD1>{yztx4V#x;fSx4iUac8XA&x$Vl22lC;i9`YiG&wE}wees_6-o#bo*#qcUDJGt|EA8vEcsUC2 z%pT`e9VH$S?mEt!S(eXTF5i10`8Fb7X0X1Bq4`)w1koaJGl}+%_QuD?MMKEP4VrCa zj`wD!1k1+VR+a`&^kzndWPcag%eus7QDt4Ix2nAA>3D*QE(%{mPgKhVeH>l+MaQXLif!!Fv%NuSb{iQe{QLx$War0O=4%c>soK4{+R(@!6GGYqHu!QK*Rv-`6*~(9oJVG)-GZ}rcd!k zQ0EW4)&5=G-mCNP9k9dLPL(S9*HGHtq7DeH(assKGIneJ7igB`I&N6_UD zylJq|V5~K2`P=~Rvolcp0s|NMb{8h}VwgK-c$G5h1nZ$P+$p0@QGz;U+j*dzA=sjo zuZ7tK3}aMZEgY_fQT0f=Z>G0S=7*r6k*<1RH8CYoSxgGBE!)y!EbO%3qHU!)GrbLQ zbA4i_H$i%vew_*J(u+Ct`%K`+MXEC^6#H3}UPd(f&hn})gS`;-*|ln}5?dzDLSU5~ zZO|&ns*sU#=OppfkZq&sycdNbhy<}=$t*BTC1fr1)<{|v;8|?iE{Ro9Y&ZwwjM=8p zn*~zPx6qqgu{O@WfC5g=Fh_}wa%!t+O(7OVDV5+eek#uPc1>DaR?{d)u%=?lpN*P| z>BHIHdPy70@{A~Jt&o1^JcX1n$NMNaw*hm!s-LrZY&Vl|!Z!AcNTX|ox55GV8WOIH zUeYep<~iO0FtVxfAsF~#z3qqI8CKYHpZ&-y1D?_IK%#!4YV$h&UZ-da%7s{1RzrA$1vdcd9)`I7W zlOKCwzMxvI=-bF7iq4~1#`{~R4!>di6cl)Gm z_SP|WyoDXT%v%N8b@wduR&H=^5|r-86L>Q#Zb}DgwOc`#3qynR3{e)^tVNs!wR&Qi z(R&MwUMoD4Zua$DDtkgg4~^3ftkHPSu4HGrP=z^ zE4|$$X$~w>y+f^MRHqjGU*3HhCv;4;=ybSJB+VzczV0CYe24YxXcg z)C89fy*7J0OU|o@!dSGQRRb3wo3@C$PH*uRa9t4HGAiN_!gY-(4%CH9o7?O9so2}t zDy^q#+q~P#0m}B0tNG;Z-i}c5>9ifE6!_WQ?k$x@>4l$qPuf~yfOu;YjaXf?*sqqs z=d)bc$_IwBW5P_hnlKJ82KqAs^YH+51h3=Gbf?kDFT6cN>&wUa=AY{;#dY34*7xpC zh{4X#s-0dfHqb**F__&&(va8Wc%GM5;`A~@SZSfJ=ta&hZ)$Y8e%-&zyHG4jF+_O& zjWoMNabJ29($NRY9s9twX}i4}|GBLH_Oce}@iyguf-DHgn66{H}P9J>C>C93dSy!JMp5-{XB)Y8X<_EnENw;LLX~@MP92i>7eOSCj)Muaf5xBM-veA~Vm+tfGHV0~TW?E?Wg2aTH{MZaCLlU3u zLNEa}#)d*IzZgxT)>8+(9iU!S>!3FkHb>Be0x9aLzxpj!Q^NOgz$ar(Lx~a9!lB6b zw6DZF0vBPA9`bg_4zTW!H?!-Et571bn_3^$EKF;0=D zBJ2=rd>{gESlWm{1Q)(q{uHWp*qd0T#0KdOA23|CVOS3DdB7=%=pp*S#Uakch{WSP z=`iep0}p$jz}4CQ!`?a7i?P)J7A6Rg4;3!PUdN~BDoh!S3?em4qNNkvskHfs_gunO zg}ugro`Um{+06jLOY{v#y$(q_qD(&FRoEalE3% z4n&5gFba7=v$2i-pU1uFD4uk}8-r@dSBk|w5_)Agny#l+rQYq*2pV+K`-(JLzkJeL zQL?|A5o4k2r@@oT-$9-@LVx@_??nl}_nz@a*?ix6A*pzrp_|1LA>PtDK_nLEfY=6O z{ZV|@2c7f2ZM6>DN;iJ+UJqZrhUIdPues>`R01ve>_=}L3|{n-OWt1g3hP-c2oF<6 z9%_9RoA)T{bJd&eDgOzkWtL35wt&nRPXLKrkJHMl-s9nanvp5LdOwf)#;6AUqo|AC zOnuaE-U(LhHR*pqynRUT`Uf`6K$9npy!sn|dTo(n@rLnQlbE)Ji8VrkjG-Bv<#Q)!146!;qm}5OBcdr=9zpEa`k_#4IJW|5$J9aO^%S?!;ddge()c2 zC*0OsUeoCB+DOrQt0=h^T7NA{zTYKwDp>xTo2N_c%#ahZni1b18hhaF8K{<1UW^$& z?v%TWPMAOk0~G@FG6pIPbLqs|6SPIZpNZShXjzRN%}={AK8#2g=&(LoZU-Xbh><(7 zUT4<|atE-*^D4*#k(wMUH*K`DbL(;yII!Ox|*}Tq&!dMv}Z&TDvA$PA!)siJni9Zw9O2c3z=cY4WQuwOo)U zr-1zF+tTHG*zfU>E^=nndwVUOIHq}zlfRPOIdWzeqL8^huIE>hzm=r7^@lz3Gq|DF zi!vmNh!nwO| zlJVeTw!Bi3e$cyBmESh2=!Iz0YVz}(ysWwm@#x#Mw4a=+53b3H#e`-r(zkcX#n$uD zdVjAxv|QWLdHqdv0q&>tjd#mdUT(asVj&!bX5eqM`yROudT~!Zd8t`hrXF5jUIVv^ z`sM~91Nm1Y4CJ^NNK5^EBe^N(sL}*gf^>C%Nv`5p4afj2k2jUaSsfo3Nbqwrxt72y ztFf5DTC?vn;LP|1)`T+Fns3^`nDdUs5@x}fJlkB(F2kzHe#R=ju(|AJbXwIypp&ho z+#TrjL`ykCl1lXaR&uO_$sO2Het=H5mODmnkg_T=eXK}5+sSEq*EU$MQVAhrGs^2A zzf8^V#WaW#A4(hahug`ClJ#6PNy+kabgaGnh_rTcvOJLH-zOJ@lw(9gBPc>BvW50{ zl6wPl8h4hX7=zm0e`gF@+$9u)2pE*8r#~QXW;8nTpnT8@?9i(`E1%^D`A!U!ThRwT zITjX*@)01`?r!pPoU{Jpas?<7>ur0==WuzX-}Qw282^rYQl4X?M?Zb$Q}QSni|X0W zB9ry|Xeu2jx1`AHvXh?dBkx0s{$)4WCdJ_%Ie-(;p=F%7x1anZngN!zw(*psL*4ZU z2goCN%F!#Rk{;I|dQlF)ed^zPNshHTJ~n3R!&l@zQU%PYU{VDOh$oDHa3?T6+>rxJ zA?E}+gHF6A*NK|3ohA6Z&M0G$TqpT(Tviyie>XPsFmPJq!BRj^$@M$+9wawplh8qO zZ4lGLgXC7+CX6p}Q6AhK5@=Ig1t)cQT~0@)zOT!b9_Ee-{USW93T=WuH7wSwXn~q# zAEH?W9DiPU0s1s>uE5rtR@a}Az+otSpaJN~Kq&y+a2rD;50`j!@^$%%r~_Z{{)^{A z9;f@>kf$>z%(oD%MVQ5GT#x1cG;$A?Gwt}|oZQ==NF4^t;n|$e>iQ#+^>T$@Rik6V zEv(I09>!OyJXlVM;KH1rrVW-mM*RpR|c4@N3)&teG}%%B>~eW`?Niaq+T^ zaI59PQF}WWqO5`V&vyu>yXpz9)~j{?{0JF^!$0j%^Fh;L{W2 zIv&g*%V2m0nH@7`P;&jwPb7htkZ9l_8G>VA0H*WO1Q`!}Q^rJdI-w-Opkuv?ffF&| zMPSvi*%#556UAQo{Y2SQ=`?fGfr*LYi3wrKN09u!IWbmaV#dEOPmcoxVT5$s)OhIe z0y$l<)L9NX*{njm{(gZxPJ+`)?_?9uRs+z3C!2t_0?>OW%dMpFn>=vF+bMF3P}+w> zRS4|^pD=ogT$u@+Lu{M#4T`yL3U8a#=6hMARv%!01GE1DHkcK3a%Wf_-7!_}W=C3s zxpJ;PZKe!6ts;GIq1?xYUnG8uIJrNTGx<2jUV6%N|A`@-51%KH2j?B!&GJQRbX)jA zG`98t5!MHNj13g@wqQQ+qDa3wU+%(NW1EH88jFE%6$~)ExKQ>-?#x0|Y>jgk%B8mO z65QJ`V^~-$-%VSV%9Fy;9#VAb^po6|j%}64QkNq6l31{k$Hg=3(u%0la=DoRoKUHB z)v!SQ#Mm^tIem>C$@jq#V-ZQ)WjEbeE>|{If!)=Q4mo|Y-e!fon}Lw9TK*h8KDL^7 z<;gT!m+OTuxM1`sHrCgbGS|ukGPiCmW4zc$GxhrGQn8&i15m^N*bo1?oJ&RxNg=nb~Z zUK={q<8vH7iuKu_%QmYqh`S?QTDf5~*eSmYo*?FTxxRjVr|b)ig)o*T`rEtZUhEDO z1L~nS{|fkI@EVU$@gCWM4;b1*l(AR-1-?V$_Q{jVhPUKvIXFB$?;C7N=0pj;FAP&~ z-!8XIQx1fVEG%SXI}eDFRX7+rvNi|h*KJ`y1l3BNkISk0$wRW851k#3%6C!n5&0uW zu~qEv4ITnL$T}rVP4U zCqw4H@hSOJ$5Egw7H#Bra&4<)ow53wosoN$9rPuZI)on3adHZ!DW&w~8M(46xP(WV z=ym-}U=54S$YaZb0ja^%K-?0q7kw`$@uJ#sR(=R5TKSx@s9ro@eo@UlFIO&GR5Ux@ z7q1^aFV84jSv_yc#nkvG*%?4X9V;t1PjTF69Dr0g%@?wu;zFiq+GR0CTP2^HcBlEw zwG`&+r>9(z&+{^}U6nVYkHuGIm6uWdaNljqDDO9%Mc&qde_vXqQvJvu%)OM-Ouz3T zO1UoYMtG~@tEng6KvTiBgPsSGi(;zy`sy=o{nyvdo05;$4!Uoy9q##GlCN>t9lN?W z-1k(l2cSxs^mv4CVGwo|B7NrrIR2jzBW`?_GqiD#pjwR0&KTd*__?p1&qoyFi{VB* z@mi6-D8{ETAb+gjOADn(I|KN5BG$LU8a^BzxhP4*7~CvKAd%m=vXBm5RHIn z{VCGpTL%~=+;sU__4&};# zkQkhGAYX9B@O6Y>GX5_#`)_#1!vxJ%^_w}qL`J^9cOA%=PM_c9Yx_409BWL??%KX! zuL!u7MiO{v*D6q4j4zR_^?dglSbA3wLEo)+X9RtM z+Sm6z8^F@_^?lW>;Zse5_ftb(m+kS4xUv7<8#QPJ-v zNAN;ZUyJ%cy@320a?pPxZ-xw{yu6t%H}zFwg6|qlZq279BM4EEr$%!Nq;2BIV(Fgd zx~ccmd|}c*e;(P)=eFU#bVf^GEBu^p>6;F*+>lnjr0^AEgqVzGwer0pjiJQWzN+3a z<}f}pzNJDY8-qu;v^NK8VFm+zkp9)$=Yz@ksMfwOr1WbQ#g)2{%Ej|Bl;tB+cNt7b z|F*uA@RdeHAJ^8`!JfCl^azTZmJyKVY3DXAf`LdsgsvjQ$O&@^h;|{7H6Gc}t{Lef zl{KFr@f88@t19y{P3YnyT{?BBGgAsWL5=Q`mQ_XB{a z1gkYREpwH+!GYaYH$F4lXn(G;Y-iN}L z`8HrFY+dH7i;I@um-#Z`4j`q-hm&;?y|Ucb45{mu`)cFo$K}34B)+%8R}ZnzSBR|V zSD<@Ht+omqzk>S|wS<4Vc)w`(98v&!uwWsR?C)Ss|) zgTPsG{izRJrel+@5wf+|qu4w^iVwV_HjpJG*p1Kik%y zor>SMfBEZLrPTjPso{Cfb^51#e=Al%yWtbZDQy7l%C zMdFFx{>P+#)Uc1g9)4c#&Sh&uixZMB7v=y`uXn$j_40Q z=La)2m)?5bpNUL`&-*>nTz%{F{=1|KrMOe%xwGK%0HBu!_~UO+^A7Mgvkr@(X9oBi z;8&kJz<-KSZ}7!&@Y`7=FI7*FrDbPy^ z`&i4-epx^Fk{?HfB0c&Qe;U`VzUsfHW}t6Y(Kn-`Y=9eH&4T+~%??~KdvD~>$Gz(R zK;o`e8Dw<%`9c2glLWQKd_BOE)`IVCfFWqO@9X{s!Ldrl_*cE|_izOV@$DZAs&DvZ z&eY)zf6IT&wD1l8=i=HxND^3z{^nqRvNS`VJlLNF(+%E2c>hO7w!P(l5o5k5&#&O8 zM;<^>N^j)(C;VgO-hBUdAF2>Fu!ILeM_Lw|!b=>2NW%6p3Z!+09zDeWu9R2?gRmd~ zvh1{ZxW7s`5FUE;MRe$GzsC?l*r;=Y%?&61FmFi3m(-#D1ZKfn2*xwvu7lB@NNVt&KfPQoYgsOc#KYML<)FUuJ^ux~>8H;c?}zBTh>lP2 z*Fb|6CL-F8?wg3_LH{TEy#j66+NOpjf;?ONDM&v!(Z7h96PU7JDTz#=P7C~Yn)SXv z8${%b_XRD9IbA=E@(cV!d3s^EM#~oZQzBR#iHToNRVImCpH1?E?Vmi^Up2;rwdmeb zz20Ph6@w*&CKq!BjLiecKOC)JqX5LB0kONL_-EWHXFNsh^H-(xss1)BbO?hHd>Gv# ze^o;S5rLw!ruy3%Md7lJ8{xURHcd2=KRwWh3pv+M_b-+N_6PD|@DM#A2HVyE_Jd3S z`$y08M@t4$yGcun#}kIL{8x;GXsV_A-TKBtKP;;b>%KXDDADYN=Olj)REM6Q>u<$i zb!O3kpWX5FjCbZo{sQSR)t-kLDxxRn`M<C++O=06cAgXXc8)q?OOv zm+o2RX90Hk?C-7ew+vJqdVt}Id#?$pm{9RVy{_(0m4u*KWCrSBw(M!_paII3f!Tse zmTLxX`=V&-V1Fu2T>qD@4x@dOD#TNlE!eV-=wr6{pOPe9|GC)T zC4jY{NZ@+4BH9;abJNqGh;8-cCxKxxlBaF+w`NE`xgCfi@G!6_?ldw;TY-nGKJ~Y- z;vW5v&;3=U*uzjnRINQhxFx}Q)c(Ss6lU)0<)vMNzw#UWsZd@K(k??lL`%Q&vpC@A zuL51e!AsBC<6j?`o512Q2;7Yi{FRfKu{~plClM&^*g|;Sd-sXD-rEJ$^+yp z@lWG1i;pI|MSDv8MUcGbAM)4WZ{XT`LZ3_&q|Jx?U!mf$hsCN{au^uYk9HjPZ^g`2BUUOp>`#j52cnb*0|(l=B`kq@9`id;>X~Eys;Qz` zyH?1Rv!)tXE?%lxPK%HE??EP2cRXa_H*Hm$r_)NF^lkujCn>S*{5ca^_&t9XZdYY;5@j4#}C4BQgN z!EmFX36ZQJ>JxWc0zb`5hYxqT89&eNa+@5fAT_()gRCZ(7+5sa^RyoqW?#9h34yh! z$j-=VR&?_#QBku!Ar;|SL$|8W-Q(`dlrdZzT!Rn$#+{9VOTTgVGim(t(^5P&IpL3_hWp*{kxUg1xGTHFv|3S{ z)A?x_yOdg%Z_lJl``oo?+yQsPzcqI80NQL$VFyDSyL8a4pu*xh6_TmgFGW(lZ`~UM zpr1(BzjgCgl2hVlZhCZyn;HG>CGI+Yw9Y;a?0jFSIr=zjz~nL@9tM6{U8^#%lBu*^ zQUX-u5OdPuLv9(BnX4o9u-lIqRtGD9JbKujMMn;~y?4wY_9e-+hI$@$r&!ly&~wlG zdgTo>?5VRXARQyvcp675i#Enc6@Ezgg%l6805#6a+i!f9$Rlzink-}pq6mLwn{sWj znH7GtEn1No!J|?T2Cz^e-S~k$G2PeSH(qc3ogdsIPi&Bv2F|bw&Y;s^1dq^^)BeY3 zMC;t)IY9PFky4bojtuKg6lA%CYJCj1QFsSbP*_j}CX9Tw*ZF(FO%C`TRTk-<3)s@j z^nJ{&AkqF^wCgktK8Fo)gkJMU|9L6<-#0Y(zu8b28u~Mm-aH4s)oh78ZE|}=j10rg zq1APahPTPpl0FE7cn7yzYyZ-`o6Zg zcqVB(Y3*`Pht+8rV^5_E&8(@^qJ3^#kd61+=l%lwx3}-h?V9|x#GaO*GTsg>)qQM3 z=fLJX&aPj%FSi*xN2}8@w<~_ebj(!}0PiDCJbhz*)1X{{xF>>+ji~-W%W!K`{>M~* z0AG|^IP@C-G9v&rI(8Kz^&RN3^#&=C_^5#-B*IM^hin6!xD74-uzrfnpCJz7?3p$j zR|*CSrq+fh7hKH4EgG#HS-nc4aP5Kw95kU?ZyNw_Md&~MGO~JA7sY_2Fx0?#u$ynH zG41ZQ@R1FqG+YwFBGM`r7jNfcVKL)Sy|=0{c>ey?X>Z!(qUJ-3tLRNJn3fb)d)I z8#`ASh}lszgV+ub<5|F$gjTVG%dsOYs0?tB3jpX`MG)xtYR`&fj}h>OHPC~HCk(ln z5Qe~QlV*tqZ(!5`4I5A=bGAf5n^Dl-5t9S_!sIYy4R^_02m+PIjY?P@L3Bi@>BIxT zAaE?yaWDvlBdK_N$4SV+5a#z}0UNOVRjjT|_#g#PV7Ra{lf|Lzp$9fKD+@eMx^Sx| zjGAGe6*Xz>btwy6rzf%WPVU5|75H)n?XeGiyZ;xNrF3>d=U&hf&DFjSAjNKn~uGkj7m# zfdtIcBxijK)&PG>W{gS(Mq%Y(Ml>l4sD_CF21cV_!qyc`J79$*;4iysqMmO>*KURw zOM{U`SVdTvU128&V&2Sj$?5tz4y{Mv8XpGGu@;Fl!RMP zNO$_U{)(e}c2}=Y-yW_0a`qONK}4+l%q|RWjkn9WXVz>hUhPH;ppMUEg2iu}o=jUh zM90z0W7XT{omy(e#@RqD=i^I-sk6ndMHyx^Fm8^;GXH6ckmT2 z9Nz_Vu&6MQD2D5y1-XdN{<7RfzTgR(sVf&eVym|tD^yAgvpao9l zFX<8bC|WszpLb%E0rX;I4S(d^%@U-P7Cd=BC$dJp6htv@yXz$0qhY6M9D(CH*L3=Y zbFV&%yk)r&{fFEyMj`jY1Lblf`VYB(;oN#@x!j2UL+%%y$o<#qa{Wg1A9DY~xks>0 zZdtz({fFEyM%VEAhFGPrJS{IX%-U4Dnr}FUVcMr%HzFLoOyf`2KN$XLMkcm_FuEC| zJRm)ycd4M@$h?g<#VR=tR%F$R=u)hb4Uu_@OKC{MeNh?of=lrr;r(F3MVC?u2?HuB zGS!Gv5{zuU;*?CJ4vhCbUtO)E!hB=XV>c%xY zoEo^5AwZE$ZsiT=s^-QkEFhR4uT;U$H}Q%O_p^V*EBE4Civ;CaBrZu1-%1jc%J_CI zL3tUR&GU&0+}0G)+$3cdep)0eGb1C>o@+rxR~0&$tYixY+_Y($Suxb6Nt!`*Qk180 z(3qBj_L|ey6o6+Uok~#}NzHXnssiD|M5>ae3`Erx(v@V0P*$fY50}N1bmcLy8pG0+ z-l84T(BeBsK--25E#L?%DMP96f%g_1Q89%ed2CBnD#0^^#fGgPN5p7XPa2q^-0ucw z$6HDmNLM5T0eDq(1|7`6MDCqjNvUeh|BXj6YdaUziP(o#;i!_dUS5~f8G~33l2XFQz zl4~*b_bB&U^RLi0548F5`wHM>B9W-T>#Y(U4RJ)HbB?M z?Ad^wConY#YLAQKrldhp%dfPG?+JbL7-*@ZQsxIOLb<2EU#Z4|WXwL~qL^9LQb`z- zunfGvqIl_`UrA>{bSASl;43q~lT#|NsSI|QmVjO)GI|xIb_&-E-wh}zPA!(i(0Z!D zhrkR)S>5dOiYNm)H~bB{R#pTi@sPO|rm^_qx(feJSOLJ@n#L+AQ@cl}vHc3Qds?A_ zFwCRs*-Fkm+&lIHg<_KHS6pWT6G8w4zKi?B3bA(AJ5f9gCb~s5AzP^~jy(b#EV$p5 ztvm#48Zd*%BvK=gTC0k}7neU)0YSS&(N&ellufowweMVqT*xvi&ft1I=&R#v17C}yY#un|Wk zpw#U~96Z5{IIJW3Pz|MJa!)oz0!m4ELxlSvzA{3rtf9yeSE2L>iS#@=QA5dQ^e|RM zv{oqfMKy5ag}1Cg*D`BjZ(5;`uBk*bt)5a#`5Zqz?^3FZ13J&kq`QhZj~ zGI2g+IA!4+lm)d0i&%nH?^al^y2ss0UdB-XNoWi)lJcX;HDPh()N=Z8D*bi0@;-8o zyGLm(#u}{R&^?Tx`lNcGb;wYZtMq|$qqO><#b>EAe!|Z>P$Q}sTVEL}D4QTqg0h9G zg)o{ifhsXLaHebx8z>=mgBaALop_|-tp-ZJ>^Va3A5_aVwIZ)#j0g)khb^;II2~p} z8_5qrd_eAo%4e{qKip992_zR55_ka|u5pFx0fvpuj}bLQpBRl-X{4l?0)25luwe*Y z8Yy>2G&fg=KB1A4FT`uYaF7Xy>sPAWR0-<9LoY)tc?=DO+cZ_`3fPJ27?DRdRhnSQ z1Car8k;+THq^Yt*Dx!BavE)C}47@Xp6CExrVpAloHB%C#U#V*|?2ldw+&R9liZImKLg57PEOy(9mk$6WGQz~|fNZ0Vm_o5DHMBdBn zOA%$aQ&x+8M$o57*VHJ}ED5~=0|~k;tN}#nFxM4m%nQ6Xtds;Y<2cVEFGhD?d*wNv zE{N@{0qds1_DU9}D^AR_zO%j3$cAPkIw_BWcYCsv!b<29Iw`I2Q_@MPlF7xfNF!@l z#Hl_KDt&Aj0KJ}Aa6NIZcgfpXnJT~=$$AT6+~r77JMHU?*|=zfJ2!bjeZU6N7le13o3?3ketQ1F82h_I*Q)xDdu8PPo*_7cIq$K=}Nr>t3A8F@(@k!rD*taw3lKS z97Qm6qj(WV7)@qAp(Lc|J3LVWMcDndX>i1r>Y~5<`Y3MNGC+~V^f-+QFg@b!BnCYg zizrNwGcY|)Oi#=}Fq1v$!V`)tx*jDg-^DJJ@nk6QyFID&62Ra5w6Y&P{pn?;8WlgI zbVS(otn#ipRJi1}Xy1K5VI^2aK5z$;E`POLB;j&oXasG3R%sYIPY@%Us9?^M+?zM6 z->6m}B_2xYazC=)X^w_G|DViJ0ZRXGO;5{# z|H1Thu#xR$#mx&O`DNt*x{EntEgnoy6Po{u(kgV0wm|}R+Z>4`V~!GERm3F}v&>+6 z7FJ{nV%LZmlZg>V?9c!o_$oO2g*5V2rHYtQ^9=U4e8*l@e*L!@uD_=6#rMuZ;*gT| zy7=j5{A_w1+-MQyzM&k9folQ_Ykoi^g*BT#SZNnHa6dO#sZEcU`X7WJqLrl(#LuJ4 zrT!*3Vb(n9--J4nB=iPScRL zl(BecJ~j`B>#yn2Jmq7!%&_KzDAd;P%~y(Tc*xH=4Acy|clfQXA9_b=Ceei9iqAT1 zB5fL}RHB2!mCe@tvWS~LAE8`@d%2Axm3>ySjaBZmj+v<6KUNuOwT_sm9~-Civ(lPp zb02`-n6r0o3Qp3#-nsXK+kUNgZl-jUruNRQo^~`J@PJ_cD7aiJJgVYy08wbxK?;;V zAUBEr?47$K`HYY`!dpG^@VoE0-mZyfk#^AGKDm#^9)QRUWs31^OPFg?%-j2H@o9n|fFWdU&u7(5KLH%;?i9S-Jnnj4oZQ3`qv0rZM==oKJcnjA++D8=o zT<$|jW5=-z!1@+$X7o5o2;$(2gf>5yTP=FIFId3>TK!z^-7(*9FtS6*M>|f}p38kP z?NV(~2saYih1#Ne3w8s%%;s84?>?W~IA-z>PyjTWD4>1M=YARU{_L_=C(xq)x%ImT zL~q=;U8We0bxnnXz9`JTc>T-pCg5@{z+2XwhEosgkrX-1sB1|i^JW;$-B$RHMK8ce zSzPO=Xkcy?_qwEzsW1g{n2MwEWwQW>iaZ0cn1KNUbDQ7-T$I%BTyh%~y^xy{QXK9Y z1I3TMklR0LO;T2jW@#TI*#t;Dv7$RfPrjI&<0faYJA6IF@npi=X&)b{Cj{u8a8sHZ4&6r3l5p2Jn*K`e`%!CSu3NM0o(zCJ_to6Rw$*<~^yr}6 z9P4U$v3esn8LVF6pxkZr^K0dLoWMQy2$ZWlC^uJ{Pmc`Bt!ACynBv~ZO)?6F0tlCW zf&AlN&ux$}X^2&f=_qbyG%H_7L}`Cf>Fc@G@j6lL8(1A?yZ60Wu3hlpfqGtlGq*0x z9kFyK(SX7KVTnEPvU%@Yxs9wtC(y{Za=q|dz2vRjlnR4zcY$X*?zXna&vn}WR&G5U zSYz^XWjHmsD=!!77bh!&*cD(|vXjXk^k+W4ZKTRWu!J|z3qx}6k~Zic4Z%EEi!0Kf zLvx4VSATODCv2!lU%#8%C5K&F!k_vW!BZ@UjV3Q%*M^F)rc7avOevmJrJ>6!6FebxUS7fnhmvs+Rmr1C*;;jo!=P8FRP$5;)xEHwpk5IlQA)Oh15&mI}!5{ z#&q})*$Q&2+9cO-Dw?Xi>KMvyUx!n*X-amp;Ze>{!mJT*N~S+==IU73DYSRtNuUbO z!FFw<5eQ?IWlnKutBe32P^L^%9x;3mz`L61*~Sop=#{4{8CFO;8_iUjRS38Z0tzGnyEZ&9U4Pnvy>W@#;?V=A_LCAy$(G+ADrZPnlN8^51xQL z3xu?GmZNrf%MXkc7aT7=jkDxh!fCCUVNDf(!M@;rCkZtU9@qvPJC zN=I|+)?ZtyG_!J+$iUcy*P)$7N)y(!GS-eh}g2a~V1xGt+y^_N!72q^b;EVOjzeG$N^XqO+NLy=@^%z}I`W2d7T@{0a_nhdgRYtuJk*WNa;V$s z2CTHBxTZ0#43Xc$FwVarer^EN4&*T4|IvPnGYTFDKVM>VwNG_cv1o+|V#nuW7N=_S zzg}c>{ooMknpC4c^!&vKE;g*J*!Z$yoT}|QV{sja;On(dl^Wsu_lY`aDMwc0*$V!0 z8j;3D#uu&hGnB!VCMw^LAXmN@Bj7$|ShRgeGW*0qGvnTTQa@8N9b-ft)bTSMYq!%= zpDCWa?I_Cy=kHl!%d}wo#={+bUG1R)Hy$a1xIY0BXH<)kZbS%33%9yl!fc9zW>Z{B z+wmtxu@dfmIkMl3a2+C(s6$kLw>8gbhkGyrnalU!@>gaLx?8lh!5*v%_JB*6J>a0( z11_b#H^l1dpr)Jy*MygR4(*@^%icfJRRbZ7&B!4~}wZ{12RXj)O~Xaqub!w~Bt}^AN{_ob=Gf zaPZ6e2uO+nt#Pw#BX^}m+mk@eNzJE3xaxj5Y!`3 zB(N9}G(pY9w2QwO2zQYqBd!{eYaE%IXU2wof!KC4GM*zB&B%0)jQ`arG@m1j&B!W_ z>@_3B94R#;yE$^%jFfOB-&}*IIWpdiT>fH7o=qz>W7qjpks09;8mP!4)OZ*SAO20PxSY0&5o{xezUI&l4i#}|KZn+GsFXt= zbLb+64sqxjhd$xZFs^ZoK#lPn{N^`Cg6SMQw1RPKJ_n~C;ovF`-eQC(-ofLaK9N&* zbLyOi2yy3)p>yXo?jR4Hf1T01VP4&f$@sxxM1EW&_9!D#8u~>qD*J*<6Y#imVnjAX zpdyG!xW!Y~l4FVF0DdFHODC?$Zfv+hAAmSyXd>WM58h{mLps+i8uO)6J$|F1Uy+HY z(RgD*OxsUieW@fj-ambbtreVl;2Mhg2|Ry-HqU(my-+F5H8TQ|Y*tsxa+t-ipQU;N z5!4}nrF0M0jw_V1+B^T2kX$3Ab{q-u(o8&GQAD$w7tsq*T_A$$j(r7wdKN|R0n;*z z>hDoXss*}^6HKsj^cm-wNIVFLopr7O(y8tzv{mNgI+ zEJ!8$A%2=g-UG_x_!)9Q*(fd48y{3)6ER2c|1I<#z?*$gqSV9B*Cmj$!jJADu$QIO z`jFzqk-P69X5g`ym@iTsBjNwnkHpB^WbRL4adF6#;??<5%hw~s2V62>f(1Sg4OFGSI&Eghk? zjtMxbq=t)6kV)M)@ue3PmFk{Qas%Vo9UMm%Ej^*UB`u|DrI6N%!8D_!lgeF*OYStHc|0QS z5e75h-aC#%%7ng_CyZ6hpcBBl{T6K_o)iz)%(20xkMZ;*I=a!CSphxG5^2(m%?kZr5qdbkXE9 zN(We4bUdS+fyzeR@0DJ7ZHm5EzJa1{-?K^vF3Lup#nG{VP)}?6`7BON1vDT@O^A>J zv(V$5Qa`cC%_p;PZR2D^n@z{9wYHj;orC67bF!aDGyAB^d7#yY^y+yW$X3zn^GX&f z8yKsqlYda23-r$%^5`GXzZF#c14=$mu@{tMc+>623%KW>P3H&8+2#AJMlJxAkqf(YF;p;_!sN{iJlr*yqNBW*9&O+-=&O?2$WM`wenW)(xCT z{eQv%Wi>7SDcE89{U_W>t)||Wl)HkY2TJ#4i*MN_B{?@pdO+5~K}Dge!SXTJUN>kO zgoM#qff9lU1tM76na|skDCsg*?n!$2vhoVrx_DV>3{N;!uRtqHtb`=Du?{jLtU`y^ zf){8D=;B|>!YJGyL$?pZSxb38Lw2vz+Ut-h|M0s~Gx`Yhgma=3Sp_GDmfuj)Ddra? zTfO$_BEtp+@3?B00}&S@=K>MPG__JH`bDW+$EYNpE1B|HNWt-e2p1d?h;YID&uIBC zxHdgPdw+r6+J+lSJ<7PMI5||2TsJ|Ea;W}Ir8$1yx(Q^v6#N+dtI{fB74%|2UM0%8 zg+<)t*Mu+{{;T40&9tXNHUWxb>{F}g<6m($o9JcX>Du)BY7>5=DD2EmTC`Txu z92$JC{{_N#gvS4+uq6@*eUjT)#D7EYm5_%PUI$jMXRL0QbQB03Z6Gw3n816@>Y7H^ ze^zp-%MC?_?BTT=(9tq7;*krn7Rxejyn)-7X>{ubBt+Aw|5ae-n>Q6F!Xtr*_}O<8 zJMC)9zNK_<6_&@h)inGT@a+SEZxwc6sff!4=t*l^C-qP(XBv$Pgxia+;3?}{n7r&Z zvswjeriS%#894>2g^pv=m+GTt*98IvzeyNlV@aJ<%_vxgMu8lzbtW1G62SdT1A%@F zV*wJia}+ZEtY-Y_RE9bx)EOtrqt3c=s1uTnb@0oh4yNXoO%0;iKjf{CiJWdn+ehg~ zyIK!F{xDU=JyFLnwR*<4L!hG#{TEk36kN74CtGUm!!PU}wSq~Q8m8t-AJIOSnogI) z)OhDdVbDR!zysX)80%0o!>6-C9wPTR)S8g?KkHBx=z5KJsH}&&#-Z}=ebAv+joIWj zhWan;#|2a&T&)kTs%^O1Gy`v61-rnTIS#kzLd4zJu2J#4aP=;9;ZV4$a_eD%)}tfT z)L`q?BCvd>Q`ZR5dR~NR{i6ucI&(90I6_SfcA*8vP(U{#L>KBssvB|l@N1;{C_3IL zN=-tKdnKsVW8RN4ddvz^1vEWDt)Da(yN`BRJVgjPDdcisyf*7sqg1CPO`067_DcEI z20(`4HW(HRfGpI7!MUQC*wM{eS>H8_lNKd$C-lqFYDGyRlux1b3Tms6N^tvsdnE}p zzJi)!^eWa9DBm&y+9_*18i-f)$vE{m$-ETT5H1z3Yxh zP=tF7Ai-j(91_$lH~-Ob<>x<(9*R>dU>cr^6Vos@?r%s?{C6anN3AN}5edel3qS(A zJrYEMgB4XZCj<#3Z19k>=Gr`f?-)Eo3A-GG;Gj2vmOzZOSmO)CWyoOxxBGxw^lz~H z&S(s4f2^Tq$3Zz?FGanxZ~XhAYGFj4K0aZ>m}eEYYP@ z^;zWRtuHRH^?@E0g2HMrl1~$hO`JM*q^YUWFp8^>9kg*Z40#n*N>}UJhXE4QzZo(P zqgT__mpc9E0C+(Dc3WK=Y$k)D6=U9UU;(#|?0q4VMlr;S@i)h^3l=}F{SuD1VETDo zv6(P@67jsdwo`))H4UxR@~8>aFGKBS{V0Zd<*IHvnt^T%qq7+p@hWOqNv(_6?1pL< zekN5?-vup7udJ3p-Qm~DY6GK+rXZZ{y=n_kAH3vL2{}({>b>O26eFsdDeCBzDeAzN zB$}3~_UEHwQ}rDhpQU!Of9B4$!55C|4fM(5RjdBL)+&8Yn|-Q3w8{vEp2Z2zMR`?J zZ{{qvfrB-uwij=LPmACalC~YV&q>?ysQ6UUt|st!dR|T7Gs1+w5*QqL)x=ObWveem zht3S`%~l_hc2K9BvR23Bs7?My+Oh9)H;`!GuL(hz{f{*T3(WsthJ?2uzxp*HIAIu1 z27O&!9d0hYl)z$QrCYh9uMC8X&4BMRx=!x+Rd6kNV>D8B-Hm^%QDd zQ>{YHa@FQ#OV!{D%9iTl+&eAR`e0Y?v`T%nv%cD?(`9q1;@R;6y@A@qI{$C$^w3FriPU} zKV;=TfNytLw7Z(915gJRZAm1rSKg}k(va$EJVmz<4GU_5E>`{T)r$3cxj9x&*;u)v zhg+ys;>AL>;3Y<5ZGMa8IwNFl)^;A{9ZM@g`s%;0QXj=$Pv{WG1lSdro}-d$Bh_rL zJ{&G2B6uT+M^dgHF`Oq`sZIW;n%~<}eJGNbF@y4dtpmH=|H}~g(rxg4{{(?;?bW$u zB*0|e{}U2mZq)xk0zNQEKwby+UPzgCc2H+Smea4J`i9h(&UIAZ1nxcEN%c5NuPnrE z7L7p|NlQAZ9q@C#lbQk9QEF#3EAGQ6*d1Bg#K0&O$32T{J>7?7WE#*}9V+dltDV7f z@1#oiWB1%i7avs99Y+m8UxkO%*Wl=`X%}^lG?c1zRja4&cbji_yAWy%#DE;Z%w6>X zwEz_qKcK#jp9T+#TAzBbtk(TywOWTdNb7>FcGg4cs6gG{2J1%1tlKPux*KJK>piy~ zeDI7YZNw(ychwgBE{f6iZxz3WWc?VGbX5l?9y6}MndjhZU;ORkDsfPcht(8^QLX;g z!)l>~&Q^XDY{4mN{HS_BT14^P)O`F*>ZXp5Sri4hVUVh|scV*g*JJ93R^&g}U0sQv zQ9aZ;_}PM=u${=(nmYGT6ZHx`)dHJ!dXzr*Np-X&E!Jy3rS_F7&x#7FJFI$Fs5@La zAUW5wo+L~)w$YY+TWz}hqB^9)wjZF74t6M$QNu>s$_i;T^(D2pW40Zdw5$^#W%1+ARxP=?mu7HAfV8Ch~fQm$;@-8fwl;3iuro z@0=j9BFHU6S&s|0W2Vjn=FK&mn!b$byi7M@>;K`iPqV6`s-Vi{*!4?c?;KJY4r=1;M zV)}y#?>azz8pSscP#>4x)6)m4P`|oL)n8Em1uus0zo4c|>HlVUbM7#_yg7EKGdO;` zGn?&c1S&8*?5y;|a69ETY|a9JLLs}QnZ>CFiq3&*iMZJ@^CBiJP6b@i#y`}1ChzYR z->J?sobC2HbMDwz?H0eR1RMlbK{A$K25uT**k~+yLv4H8cPNjReRmB8A>B^h2CJ3Y9TuK4#5b6~0L`&z zTZ>)jpZ>qmYXY{PM z)LL*VPY>j&ZEzVhBTwCL#Byk6KG0Ak$#f!LJ%#j7hN$yXi$?!}vGCOoexi-ah0CO_ zH0*8laO1;*P2;{@V@F0t-^HDrc(H=l${9O!76VBzDq zh^0}e-s1X>&JR_qQIlb6l6AO)HV#!&-M1vXERL_YBv&xkJHv3|Sx5_psdI!5uEVmlE7Dmve>Tn)6afEsTKB!(8i6iT<1scM~Tb zDKqF91}#2K$0s~Fx@5HaByPsy#;7^aLvK6=2f}aYr7`M9(cL|4!EEmXpmitTSoLL) ztZ8G_c&pT%ipD|g(47vB!!T!4%6Rn_Mw>DL`}8i_FacEse-b9D-K(z*WAeX(B?^#f z9>i2S)5`jTZ+tC%kQd7WmfL?hQLUBqc??LYkkN*@c8G^)ndXe5g!k3w(>KQp0;O6@ zp?9yE3U{TFN2+I4Y1#W~l`I{4z5qEe8hhXfDNf?{pa#gIK8A#AKK=DR7=;CtQlP#T zp+{jdEVu;upg&F;+*O{mmeB8&1N)e}`z2dPZ8Kzd2dGAW1Fs-cwX~ zI&MMu&4O^%24}DBr>euGPD`MQh{4th3|0W5n&l`U!2XrXU~-guC8+8sM=DBejIl

    a^b>kzoF{Hgrws2l({}Begi0C6# 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 Sr53?ru`FY?G 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 G%l;pqa4T#8 diff --git a/frontend/src/lib/pdfiumEngine.ts b/frontend/src/lib/pdfiumEngine.ts index f119155..dede7e8 100644 --- a/frontend/src/lib/pdfiumEngine.ts +++ b/frontend/src/lib/pdfiumEngine.ts @@ -18,7 +18,7 @@ function getModule(): Promise { if (!modulePromise) { modulePromise = (async () => { try { - const V = '20260625-tier2b'; + const V = '20260626-auxfont'; const resp = await fetch(`/pdfium-engine.mjs?v=${V}`, { cache: 'no-store' }); if (!resp.ok) throw new Error(`pdfium-engine.mjs ${resp.status}`); const blobUrl = URL.createObjectURL(new Blob([await resp.text()], { type: 'text/javascript' })); diff --git a/gateway/app/routers/documents.py b/gateway/app/routers/documents.py deleted file mode 100644 index ea86df8..0000000 --- a/gateway/app/routers/documents.py +++ /dev/null @@ -1,900 +0,0 @@ -import hashlib - -from fastapi import APIRouter, File, HTTPException, Response, UploadFile, status -from pydantic import BaseModel - -from app.services import engine -from app.services.store import document_store - -router = APIRouter(prefix="/documents", tags=["documents"]) - - -class PageInfoResponse(BaseModel): - index: int - width: float - height: float - - -class PermissionsResponse(BaseModel): - isEncrypted: bool = False - encryption: str = "None" - securityRevision: int = -1 - ownerUnlocked: bool = False - canPrint: bool = True - canPrintHighRes: bool = True - canModify: bool = True - canCopy: bool = True - canAnnotate: bool = True - canFillForms: bool = True - canExtractForAccessibility: bool = True - canAssemble: bool = True - - -class DocumentInfoResponse(BaseModel): - id: str - filename: str - sizeBytes: int - totalPages: int - pageWidth: float - pageHeight: float - uploadedAt: str - status: str - pages: list[PageInfoResponse] = [] - permissions: PermissionsResponse = PermissionsResponse() - - -def make_document_response(d: dict) -> DocumentInfoResponse: - pages_list = [] - if "doc_instance" in d: - doc = d["doc_instance"] - for i in range(doc.page_count): - try: - page = doc.get_page(i) - pages_list.append(PageInfoResponse(index=i, width=page.width, height=page.height)) - except Exception: - pass - perms = d.get("permissions") - return DocumentInfoResponse( - id=d["id"], - filename=d["filename"], - sizeBytes=d["sizeBytes"], - totalPages=d["totalPages"], - pageWidth=d.get("pageWidth", 612.0), - pageHeight=d.get("pageHeight", 792.0), - uploadedAt=d["uploadedAt"], - status=d["status"], - pages=pages_list, - permissions=PermissionsResponse(**perms) if perms else PermissionsResponse(), - ) - - -@router.post("", response_model=DocumentInfoResponse, status_code=status.HTTP_201_CREATED) -async def upload_document(file: UploadFile = File(...), password: str = "") -> DocumentInfoResponse: - if not engine.is_available(): - raise HTTPException( - status_code=status.HTTP_501_NOT_IMPLEMENTED, - detail="Engine bridge (bindings/python) not yet available.", - ) - - bytes_data = await file.read() - try: - pdfengine = engine.require() - doc = pdfengine.PdfDocument.load_from_memory(bytes_data, password) - info = document_store.add_document(file.filename, bytes_data, doc) - return make_document_response(info) - except ValueError as e: - detail = str(e) - if "Password required" in detail: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, detail="Password required" - ) - elif "Invalid password" in detail: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid password") - else: - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=detail) - except Exception as e: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, detail=f"Failed to load PDF: {e!s}" - ) - - -@router.get("", response_model=list[DocumentInfoResponse]) -def list_documents() -> list[DocumentInfoResponse]: - if not engine.is_available(): - raise HTTPException( - status_code=status.HTTP_501_NOT_IMPLEMENTED, - detail="Engine bridge (bindings/python) not yet available.", - ) - - docs = document_store.list_documents() - return [make_document_response(d) for d in docs] - - -@router.get("/{document_id}", response_model=DocumentInfoResponse) -def get_document(document_id: str) -> DocumentInfoResponse: - if not engine.is_available(): - raise HTTPException( - status_code=status.HTTP_501_NOT_IMPLEMENTED, - detail="Engine bridge (bindings/python) not yet available.", - ) - - d = document_store.get_document(document_id) - if not d: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document not found") - - return make_document_response(d) - - -@router.delete("/{document_id}") -def delete_document(document_id: str): - if not engine.is_available(): - raise HTTPException( - status_code=status.HTTP_501_NOT_IMPLEMENTED, - detail="Engine bridge (bindings/python) not yet available.", - ) - - deleted = document_store.delete_document(document_id) - if not deleted: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document not found") - - return {"success": True} - - -class DocumentMetadataResponse(BaseModel): - title: str - author: str - creator: str - producer: str - creation_date: str - modification_date: str - - -@router.get("/{document_id}/metadata", response_model=DocumentMetadataResponse) -def get_document_metadata(document_id: str) -> DocumentMetadataResponse: - if not engine.is_available(): - raise HTTPException( - status_code=status.HTTP_501_NOT_IMPLEMENTED, - detail="Engine bridge (bindings/python) not yet available.", - ) - - d = document_store.get_document(document_id) - if not d: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document not found") - - try: - doc = d["doc_instance"] - meta = doc.metadata - return DocumentMetadataResponse( - title=meta.title, - author=meta.author, - creator=meta.creator, - producer=meta.producer, - creation_date=meta.creation_date, - modification_date=meta.modification_date, - ) - except Exception as e: - raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)) - - -class FontInfoResponse(BaseModel): - fontName: str - type: str - isEmbedded: bool - isSubset: bool - isVertical: bool - encoding: str - hasToUnicode: bool - cmapName: str - cidSystemInfo: str - subsetTag: str - sourceType: str - substitutedFrom: str - substitutedTo: str - normalizedFamily: str - internalFontId: str - flags: int - ascent: float - descent: float - capHeight: float - - -@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]: - 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"] - fonts = doc.get_fonts(start_page, end_page) - return [ - FontInfoResponse( - fontName=f.font_name, - type=f.type, - isEmbedded=f.is_embedded, - isSubset=f.is_subset, - isVertical=f.is_vertical, - encoding=f.encoding, - hasToUnicode=f.has_to_unicode, - cmapName=f.cmap_name, - cidSystemInfo=f.cid_system_info, - subsetTag=f.subset_tag, - sourceType=f.source_type, - substitutedFrom=f.substituted_from, - substitutedTo=f.substituted_to, - normalizedFamily=f.normalized_family, - internalFontId=f.internal_font_id, - flags=f.flags, - ascent=f.ascent, - descent=f.descent, - capHeight=f.cap_height, - ) - for f in fonts - ] - 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_400_BAD_REQUEST, detail=str(e)) - - -_SFNT_TTF_MAGIC = (b"\x00\x01\x00\x00", b"true", b"ttcf") -_SFNT_OTF_MAGIC = b"OTTO" - - -@router.get("/{document_id}/font") -def get_font_bytes(document_id: str, internal_font_id: str) -> Response: - """Raw embedded font bytes for an in-place-editing preview. - - Returns the font only when it's a browser-loadable sfnt (TrueType / OpenType-CFF). - Type1/PFB, non-embedded, and unknown fonts return 404 so the frontend falls back to - a base-14 CSS font. The lookup is sandboxed to fonts inside the loaded document. - """ - if not engine.is_available(): - raise HTTPException( - status_code=status.HTTP_501_NOT_IMPLEMENTED, - detail="Engine bridge (bindings/python) not yet available.", - ) - - if not internal_font_id or len(internal_font_id) > 256: - return Response(status_code=status.HTTP_204_NO_CONTENT) - - doc_info = document_store.get_document(document_id) - if not doc_info: - return Response(status_code=status.HTTP_204_NO_CONTENT) - - try: - data = bytes(doc_info["doc_instance"].get_font_data(internal_font_id)) - except Exception: - data = b"" - if not data: - return Response(status_code=status.HTTP_204_NO_CONTENT) - - magic = data[:4] - if magic in _SFNT_TTF_MAGIC: - media_type = "font/ttf" - elif magic == _SFNT_OTF_MAGIC: - media_type = "font/otf" - else: - return Response(status_code=status.HTTP_204_NO_CONTENT) - - etag = '"' + hashlib.sha256(data).hexdigest()[:32] + '"' - return Response( - content=data, - media_type=media_type, - headers={"Cache-Control": "public, max-age=31536000, immutable", "ETag": etag}, - ) - -@router.get("/{document_id}/font-reconstructed") -def get_reconstructed_font_bytes(document_id: str, internal_font_id: str) -> Response: - """Tier-2: a cmap-augmented copy of an embedded font (original glyph program + synthesized - Unicode cmap) so the WASM live preview can reuse the document's real glyphs and match the - saved result. 204 when reconstruction isn't possible -> frontend falls back to Tier-1.""" - if not engine.is_available(): - return Response(status_code=status.HTTP_204_NO_CONTENT) - if not internal_font_id or len(internal_font_id) > 256: - return Response(status_code=status.HTTP_204_NO_CONTENT) - doc_info = document_store.get_document(document_id) - if not doc_info: - return Response(status_code=status.HTTP_204_NO_CONTENT) - try: - data = bytes(doc_info["doc_instance"].get_reconstructed_font_data(internal_font_id)) - except Exception: - data = b"" - if not data or data[:4] not in _SFNT_TTF_MAGIC: - return Response(status_code=status.HTTP_204_NO_CONTENT) - etag = '"' + hashlib.sha256(data).hexdigest()[:32] + '"' - return Response( - content=data, - media_type="font/ttf", - headers={"Cache-Control": "public, max-age=31536000, immutable", "ETag": etag}, - ) - - -@router.get("/{document_id}/raw") -def get_document_raw(document_id: str) -> Response: - """Raw PDF bytes of the current version — loaded into the in-browser WASM engine for the - pixel-identical live-edit preview. Gated on copy permission (same as export).""" - 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") - perms = doc_info.get("permissions") or {} - if perms.get("canCopy", True) is False: - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not permitted (canCopy).") - data = doc_info.get("bytes_data") - if not data: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document bytes unavailable") - return Response(content=bytes(data), media_type="application/pdf") - - -class SearchRect(BaseModel): - x: float - y: float - w: float - h: float - - -class SearchMatch(BaseModel): - pageIndex: int - rects: list[SearchRect] - text: str - - -@router.get("/{document_id}/search", response_model=list[SearchMatch]) -def search_document( - document_id: str, - q: str, - case_sensitive: bool = False, - whole_words: bool = False, -) -> list[SearchMatch]: - if not engine.is_available(): - raise HTTPException( - status_code=status.HTTP_501_NOT_IMPLEMENTED, - detail="Engine bridge (bindings/python) not yet available.", - ) - - if not q: - return [] - - 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") - - def _is_word_char(ch: str) -> bool: - return ch.isalnum() or ch == "_" - - def _find_all(haystack: str, needle: str) -> list[int]: - """Return start indices of all non-overlapping occurrences of needle in haystack.""" - results: list[int] = [] - start = 0 - needle_len = len(needle) - while True: - pos = haystack.find(needle, start) - if pos == -1: - break - if whole_words: - before_ok = pos == 0 or not _is_word_char(haystack[pos - 1]) - after_ok = (pos + needle_len) >= len(haystack) or not _is_word_char( - haystack[pos + needle_len] - ) - if before_ok and after_ok: - results.append(pos) - else: - results.append(pos) - start = pos + 1 - return results - - try: - doc = doc_info["doc_instance"] - matches = [] - search_needle = q if case_sensitive else q.lower() - query_len = len(search_needle) - - for page_idx in range(doc.page_count): - page = doc.get_page(page_idx) - glyphs = page.extract_text_with_bounds() - if not glyphs: - continue - - text_str = "" - char_to_glyph: list[int] = [] - for i, g in enumerate(glyphs): - s = g.get("text", "") - start_len = len(text_str) - text_str += s - for _ in range(len(text_str) - start_len): - char_to_glyph.append(i) - - search_text = text_str if case_sensitive else text_str.lower() - - for idx in _find_all(search_text, search_needle): - if idx + query_len - 1 >= len(char_to_glyph): - continue - - start_glyph_idx = char_to_glyph[idx] - end_glyph_idx = char_to_glyph[idx + query_len - 1] - - rects = [] - current_rect = None - - for g_idx in range(start_glyph_idx, end_glyph_idx + 1): - g = glyphs[g_idx] - dom_y = page.height - (g["y"] + g["h"]) - - if current_rect is None: - current_rect = {"x": g["x"], "y": dom_y, "w": g["w"], "h": g["h"]} - else: - if abs(dom_y - current_rect["y"]) < g.get("fontSize", 12) * 0.5: - max_x = max(current_rect["x"] + current_rect["w"], g["x"] + g["w"]) - current_rect["w"] = max_x - current_rect["x"] - current_rect["y"] = min(current_rect["y"], dom_y) - current_rect["h"] = max(current_rect["h"], g["h"]) - else: - rects.append(SearchRect(**current_rect)) - current_rect = {"x": g["x"], "y": dom_y, "w": g["w"], "h": g["h"]} - - if current_rect: - rects.append(SearchRect(**current_rect)) - - matches.append( - SearchMatch( - pageIndex=page_idx, rects=rects, text=text_str[idx : idx + query_len] - ) - ) - - return matches - - 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 - page_object_index: int = -1 - - -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 - object_indices: list[int] = [] - color: str = "#000000" - font_fidelity: str = "exact" - - -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 line in p.lines: - runs = [] - for r in line.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, - page_object_index=g.page_object_index, - ) - ) - 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, - object_indices=r.object_indices, - color=getattr(r, "fill_color", "#000000") or "#000000", - font_fidelity=getattr(r, "font_fidelity", "exact") or "exact", - ) - ) - lines.append( - TextLineModel( - runs=runs, - baseline_y=line.baseline_y, - x=line.x, - y=line.y, - w=line.w, - h=line.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)) - -class AnnotationResponse(BaseModel): - id: str - type: str - x: float - y: float - width: float - height: float - color: str - author: str - content: str - timestamp: str | None = None - pageIndex: int - paths: list[list[dict[str, float]]] = [] - - fieldName: str | None = None - fieldValue: str | None = None - fieldType: str | None = None - fieldFlags: int | None = None - fieldOptions: list[str] | None = None - -@router.get("/{document_id}/annotations", response_model=list[AnnotationResponse]) -def get_document_annotations(document_id: str) -> list[AnnotationResponse]: - 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"] - all_annots = [] - for i in range(doc.page_count): - try: - page = doc.get_page(i) - annots = page.extract_annotations() - for a in annots: - all_annots.append(AnnotationResponse( - id=a.id, - type=a.type, - x=a.x, - y=a.y, - width=a.width, - height=a.height, - color=a.color, - author=a.author, - content=a.content, - timestamp=getattr(a, "timestamp", None), - pageIndex=a.page_index, - paths=[[{"x": p.x, "y": p.y} for p in stroke] for stroke in getattr(a, "paths", [])], - fieldName=getattr(a, "field_name", None), - fieldValue=getattr(a, "field_value", None), - fieldType=getattr(a, "field_type", None), - fieldFlags=getattr(a, "field_flags", None), - fieldOptions=getattr(a, "field_options", None), - )) - except Exception: - pass - return all_annots - except Exception as e: - raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)) - - -class OutlineItemResponse(BaseModel): - title: str - pageIndex: int - level: int - - -@router.get("/{document_id}/outline", response_model=list[OutlineItemResponse]) -def get_document_outline(document_id: str) -> list[OutlineItemResponse]: - if not engine.is_available(): - raise HTTPException( - status_code=status.HTTP_501_NOT_IMPLEMENTED, - detail="Engine bridge (bindings/python) not yet available.", - ) - - d = document_store.get_document(document_id) - if not d: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document not found") - - try: - doc = d["doc_instance"] - items = doc.extract_outline() - return [ - OutlineItemResponse(title=it["title"], pageIndex=it["pageIndex"], level=it["level"]) - for it in items - ] - except Exception as e: - raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)) - - -@router.get("/{document_id}/export") -def export_document(document_id: str): - if not engine.is_available(): - raise HTTPException( - status_code=status.HTTP_501_NOT_IMPLEMENTED, - detail="Engine bridge (bindings/python) not yet available.", - ) - - d = document_store.get_document(document_id) - if not d: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document not found") - - perms = d.get("permissions") or {} - if perms.get("canCopy", True) is False: - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail="Exporting is not permitted by this document's restrictions (canCopy).", - ) - - try: - doc = d["doc_instance"] - bytes_data = doc.save_full() - filename = d["filename"] - if not filename.endswith(".pdf"): - filename += ".pdf" - - import sys - import os - roaming_path = os.path.join(os.environ.get("APPDATA", "C:\\Users\\azeem\\AppData\\Roaming"), "Python", "Python312", "site-packages") - if roaming_path not in sys.path: - sys.path.append(roaming_path) - - import pypdf - import io - - reader = pypdf.PdfReader(io.BytesIO(bytes_data)) - writer = pypdf.PdfWriter() - writer.append(reader) - - acro_form = writer.root_object.get("/AcroForm") - if acro_form is not None: - acro_form_dict = acro_form.get_object() - acro_form_dict[pypdf.generic.NameObject("/NeedAppearances")] = pypdf.generic.BooleanObject(True) - - out_stream = io.BytesIO() - writer.write(out_stream) - bytes_data = out_stream.getvalue() - - return Response( - content=bytes_data, - media_type="application/pdf", - headers={ - "Content-Disposition": f'attachment; filename="{filename}"', - "Content-Length": str(len(bytes_data)), - }, - ) - except Exception as e: - raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)) - -class TextObjectResponse(BaseModel): - text: str - fontName: str - fontSize: float - tm: list[float] - -@router.get("/{document_id}/pages/{page_index}/text_objects", response_model=list[TextObjectResponse]) -def get_text_objects(document_id: str, page_index: int) -> list[TextObjectResponse]: - if not engine.is_available(): - raise HTTPException(status_code=501, detail="Engine not available") - - doc_info = document_store.get_document(document_id) - if not doc_info: - raise HTTPException(status_code=404, detail="Document not found") - - _perms = doc_info.get("permissions") or {} - if _perms.get("canModify", True) is False: - raise HTTPException(status_code=403, detail="Raw Text editing is not permitted by this document's restrictions (canModify).") - - pdfengine = engine.require() - - import os - import tempfile - - with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp: - tmp.write(doc_info["bytes_data"]) - tmp_path = tmp.name - - try: - editor = pdfengine.StreamEditor(tmp_path) - objects = editor.extract_text_objects(page_index) - - result = [] - for obj in objects: - text_str = obj["text"].decode("latin-1") if isinstance(obj["text"], bytes) else obj["text"] - result.append(TextObjectResponse( - text=text_str, - fontName=obj["fontName"], - fontSize=obj["fontSize"], - tm=obj["tm"] - )) - return result - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - finally: - if os.path.exists(tmp_path): - os.remove(tmp_path) - - -class UpdateTextObjectRequest(BaseModel): - new_text: str - - -@router.put("/{document_id}/pages/{page_index}/text_objects/{object_index}") -def replace_text_object(document_id: str, page_index: int, object_index: int, req: UpdateTextObjectRequest) -> dict: - if not engine.is_available(): - raise HTTPException(status_code=501, detail="Engine not available") - - doc_info = document_store.get_document(document_id) - if not doc_info: - raise HTTPException(status_code=404, detail="Document not found") - - _perms = doc_info.get("permissions") or {} - if _perms.get("canModify", True) is False: - raise HTTPException(status_code=403, detail="Raw Text editing is not permitted by this document's restrictions (canModify).") - - pdfengine = engine.require() - - import tempfile - import os - - with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp: - tmp.write(doc_info["bytes_data"]) - tmp_path = tmp.name - - out_path = tmp_path + ".out.pdf" - - try: - editor = pdfengine.StreamEditor(tmp_path) - try: - new_text_bytes = req.new_text.encode("latin-1") - except UnicodeEncodeError as enc_err: - raise HTTPException( - status_code=400, - detail="Some characters can't be encoded in this run's font. Raw Text supports same-charset edits only — use Edit text to add new characters.", - ) from enc_err - success = editor.replace_text_object(page_index, object_index, new_text_bytes, out_path) - if not success: - raise HTTPException(status_code=400, detail="Failed to replace text object (not found or identical)") - - with open(out_path, "rb") as f: - new_bytes = f.read() - new_doc = pdfengine.PdfDocument.load_from_memory(new_bytes, "") - new_info = document_store.add_document( - filename=doc_info["filename"], bytes_data=new_bytes, doc_instance=new_doc, - permissions=doc_info.get("permissions"), - ) - return {"success": True, "newDocumentId": new_info["id"]} - except HTTPException: - raise - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) from e - finally: - if os.path.exists(tmp_path): - os.remove(tmp_path) - if os.path.exists(out_path): - os.remove(out_path) - -@router.get("/{document_id}/pages/{page_index}/display_list") -def get_display_list(document_id: str, page_index: int): - if not engine.is_available(): - raise HTTPException(status_code=501, detail="Engine not available") - - doc_info = document_store.get_document(document_id) - if not doc_info: - raise HTTPException(status_code=404, detail="Document not found") - - try: - doc = doc_info["doc_instance"] - page = doc.get_page(page_index) - json_str = page.extract_display_list() - return Response(content=json_str, media_type="application/json") - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - -@router.get("/{document_id}/pages/{page_index}/xobjects/{name}") -def get_image_xobject(document_id: str, page_index: int, name: str): - if not engine.is_available(): - raise HTTPException(status_code=501, detail="Engine not available") - - doc_info = document_store.get_document(document_id) - if not doc_info: - raise HTTPException(status_code=404, detail="Document not found") - - try: - doc = doc_info["doc_instance"] - page = doc.get_page(page_index) - img_bytes = page.extract_image_xobject(name) - if not img_bytes: - raise HTTPException(status_code=404, detail="Image not found") - return Response(content=img_bytes, media_type="image/png") - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) diff --git a/gateway/app/routers/documents/__init__.py b/gateway/app/routers/documents/__init__.py new file mode 100644 index 0000000..c61dc40 --- /dev/null +++ b/gateway/app/routers/documents/__init__.py @@ -0,0 +1,20 @@ +"""Documents resource router. + +Previously a single 900-line ``documents.py``. Split into focused sub-routers, +all mounted under the unchanged ``/documents`` prefix so every public URL is +preserved exactly. ``main.py`` still does ``app.include_router(documents.router)``. + +The ``crud`` module owns the base router (it holds the prefix), and the other +sub-routers mount into it. +""" + +from app.schemas.font import FontInfoResponse + +from . import content, export, fonts, metadata, text_objects +from .crud import router + +router.include_router(metadata.router) +router.include_router(fonts.router) +router.include_router(content.router) +router.include_router(export.router) +router.include_router(text_objects.router) diff --git a/gateway/app/routers/documents/content.py b/gateway/app/routers/documents/content.py new file mode 100644 index 0000000..64dbfb1 --- /dev/null +++ b/gateway/app/routers/documents/content.py @@ -0,0 +1,163 @@ +from fastapi import APIRouter, HTTPException, Response, status + +from app.schemas.annotation import AnnotationResponse +from app.schemas.page_model import PageModelResponse +from app.schemas.search import SearchMatch +from app.services import engine +from app.services.page_model import build_page_model_response +from app.services.search import compute_search_matches +from app.services.store import document_store + +router = APIRouter(tags=["documents"]) + + +@router.get("/{document_id}/raw") +def get_document_raw(document_id: str) -> Response: + """Raw PDF bytes of the current version — loaded into the in-browser WASM engine for the + pixel-identical live-edit preview. Gated on copy permission (same as export).""" + 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") + perms = doc_info.get("permissions") or {} + if perms.get("canCopy", True) is False: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not permitted (canCopy).") + data = doc_info.get("bytes_data") + if not data: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document bytes unavailable") + return Response(content=bytes(data), media_type="application/pdf") + + +@router.get("/{document_id}/search", response_model=list[SearchMatch]) +def search_document( + document_id: str, + q: str, + case_sensitive: bool = False, + whole_words: bool = False, +) -> list[SearchMatch]: + if not engine.is_available(): + raise HTTPException( + status_code=status.HTTP_501_NOT_IMPLEMENTED, + detail="Engine bridge (bindings/python) not yet available.", + ) + + if not q: + return [] + + 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"] + return compute_search_matches(doc, q, case_sensitive, whole_words) + except Exception as e: + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)) + + +@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() + return build_page_model_response(model) + 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)) + + +@router.get("/{document_id}/annotations", response_model=list[AnnotationResponse]) +def get_document_annotations(document_id: str) -> list[AnnotationResponse]: + 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"] + all_annots = [] + for i in range(doc.page_count): + try: + page = doc.get_page(i) + annots = page.extract_annotations() + for a in annots: + all_annots.append(AnnotationResponse( + id=a.id, + type=a.type, + x=a.x, + y=a.y, + width=a.width, + height=a.height, + color=a.color, + author=a.author, + content=a.content, + timestamp=getattr(a, "timestamp", None), + pageIndex=a.page_index, + paths=[[{"x": p.x, "y": p.y} for p in stroke] for stroke in getattr(a, "paths", [])], + fieldName=getattr(a, "field_name", None), + fieldValue=getattr(a, "field_value", None), + fieldType=getattr(a, "field_type", None), + fieldFlags=getattr(a, "field_flags", None), + fieldOptions=getattr(a, "field_options", None), + )) + except Exception: + pass + return all_annots + except Exception as e: + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)) + + +@router.get("/{document_id}/pages/{page_index}/display_list") +def get_display_list(document_id: str, page_index: int): + if not engine.is_available(): + raise HTTPException(status_code=501, detail="Engine not available") + + doc_info = document_store.get_document(document_id) + if not doc_info: + raise HTTPException(status_code=404, detail="Document not found") + + try: + doc = doc_info["doc_instance"] + page = doc.get_page(page_index) + json_str = page.extract_display_list() + return Response(content=json_str, media_type="application/json") + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/{document_id}/pages/{page_index}/xobjects/{name}") +def get_image_xobject(document_id: str, page_index: int, name: str): + if not engine.is_available(): + raise HTTPException(status_code=501, detail="Engine not available") + + doc_info = document_store.get_document(document_id) + if not doc_info: + raise HTTPException(status_code=404, detail="Document not found") + + try: + doc = doc_info["doc_instance"] + page = doc.get_page(page_index) + img_bytes = page.extract_image_xobject(name) + if not img_bytes: + raise HTTPException(status_code=404, detail="Image not found") + return Response(content=img_bytes, media_type="image/png") + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) diff --git a/gateway/app/routers/documents/crud.py b/gateway/app/routers/documents/crud.py new file mode 100644 index 0000000..3a6f09d --- /dev/null +++ b/gateway/app/routers/documents/crud.py @@ -0,0 +1,107 @@ +from fastapi import APIRouter, File, HTTPException, UploadFile, status + +from app.schemas.document import ( + DocumentInfoResponse, + PageInfoResponse, + PermissionsResponse, +) +from app.services import engine +from app.services.store import document_store + +router = APIRouter(prefix="/documents", tags=["documents"]) + +def make_document_response(d: dict) -> DocumentInfoResponse: + pages_list = [] + if "doc_instance" in d: + doc = d["doc_instance"] + for i in range(doc.page_count): + try: + page = doc.get_page(i) + pages_list.append(PageInfoResponse(index=i, width=page.width, height=page.height)) + except Exception: + pass + perms = d.get("permissions") + return DocumentInfoResponse( + id=d["id"], + filename=d["filename"], + sizeBytes=d["sizeBytes"], + totalPages=d["totalPages"], + pageWidth=d.get("pageWidth", 612.0), + pageHeight=d.get("pageHeight", 792.0), + uploadedAt=d["uploadedAt"], + status=d["status"], + pages=pages_list, + permissions=PermissionsResponse(**perms) if perms else PermissionsResponse(), + ) + + +@router.post("", response_model=DocumentInfoResponse, status_code=status.HTTP_201_CREATED) +async def upload_document(file: UploadFile = File(...), password: str = "") -> DocumentInfoResponse: + if not engine.is_available(): + raise HTTPException( + status_code=status.HTTP_501_NOT_IMPLEMENTED, + detail="Engine bridge (bindings/python) not yet available.", + ) + + bytes_data = await file.read() + try: + pdfengine = engine.require() + doc = pdfengine.PdfDocument.load_from_memory(bytes_data, password) + info = document_store.add_document(file.filename, bytes_data, doc) + return make_document_response(info) + except ValueError as e: + detail = str(e) + if "Password required" in detail: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, detail="Password required" + ) + elif "Invalid password" in detail: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid password") + else: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=detail) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, detail=f"Failed to load PDF: {e!s}" + ) + + +@router.get("", response_model=list[DocumentInfoResponse]) +def list_documents() -> list[DocumentInfoResponse]: + if not engine.is_available(): + raise HTTPException( + status_code=status.HTTP_501_NOT_IMPLEMENTED, + detail="Engine bridge (bindings/python) not yet available.", + ) + + docs = document_store.list_documents() + return [make_document_response(d) for d in docs] + + +@router.get("/{document_id}", response_model=DocumentInfoResponse) +def get_document(document_id: str) -> DocumentInfoResponse: + if not engine.is_available(): + raise HTTPException( + status_code=status.HTTP_501_NOT_IMPLEMENTED, + detail="Engine bridge (bindings/python) not yet available.", + ) + + d = document_store.get_document(document_id) + if not d: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document not found") + + return make_document_response(d) + + +@router.delete("/{document_id}") +def delete_document(document_id: str): + if not engine.is_available(): + raise HTTPException( + status_code=status.HTTP_501_NOT_IMPLEMENTED, + detail="Engine bridge (bindings/python) not yet available.", + ) + + deleted = document_store.delete_document(document_id) + if not deleted: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document not found") + + return {"success": True} \ No newline at end of file diff --git a/gateway/app/routers/documents/export.py b/gateway/app/routers/documents/export.py new file mode 100644 index 0000000..7239d11 --- /dev/null +++ b/gateway/app/routers/documents/export.py @@ -0,0 +1,46 @@ +from fastapi import APIRouter, HTTPException, Response, status + +from app.services import engine +from app.services.export import apply_need_appearances +from app.services.store import document_store + +router = APIRouter(tags=["documents"]) + +@router.get("/{document_id}/export") +def export_document(document_id: str): + if not engine.is_available(): + raise HTTPException( + status_code=status.HTTP_501_NOT_IMPLEMENTED, + detail="Engine bridge (bindings/python) not yet available.", + ) + + d = document_store.get_document(document_id) + if not d: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document not found") + + perms = d.get("permissions") or {} + if perms.get("canCopy", True) is False: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Exporting is not permitted by this document's restrictions (canCopy).", + ) + + try: + doc = d["doc_instance"] + bytes_data = doc.save_full() + filename = d["filename"] + if not filename.endswith(".pdf"): + filename += ".pdf" + + bytes_data = apply_need_appearances(bytes_data) + + return Response( + content=bytes_data, + media_type="application/pdf", + headers={ + "Content-Disposition": f'attachment; filename="{filename}"', + "Content-Length": str(len(bytes_data)), + }, + ) + except Exception as e: + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)) diff --git a/gateway/app/routers/documents/fonts.py b/gateway/app/routers/documents/fonts.py new file mode 100644 index 0000000..8af989c --- /dev/null +++ b/gateway/app/routers/documents/fonts.py @@ -0,0 +1,109 @@ +import hashlib + +from fastapi import APIRouter, HTTPException, Response, status + +from app.schemas.font import FontInfoResponse +from app.services import engine +from app.services.font import font_info_to_response +from app.services.store import document_store + +router = APIRouter(tags=["documents"]) + +_SFNT_TTF_MAGIC = (b"\x00\x01\x00\x00", b"true", b"ttcf") +_SFNT_OTF_MAGIC = b"OTTO" + + +@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]: + 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"] + fonts = doc.get_fonts(start_page, end_page) + return [font_info_to_response(f) for f in fonts] + 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_400_BAD_REQUEST, detail=str(e)) + + +@router.get("/{document_id}/font") +def get_font_bytes(document_id: str, internal_font_id: str) -> Response: + """Raw embedded font bytes for an in-place-editing preview. + + Returns the font only when it's a browser-loadable sfnt (TrueType / OpenType-CFF). + Type1/PFB, non-embedded, and unknown fonts return 404 so the frontend falls back to + a base-14 CSS font. The lookup is sandboxed to fonts inside the loaded document. + """ + if not engine.is_available(): + raise HTTPException( + status_code=status.HTTP_501_NOT_IMPLEMENTED, + detail="Engine bridge (bindings/python) not yet available.", + ) + + if not internal_font_id or len(internal_font_id) > 256: + return Response(status_code=status.HTTP_204_NO_CONTENT) + + doc_info = document_store.get_document(document_id) + if not doc_info: + return Response(status_code=status.HTTP_204_NO_CONTENT) + + try: + data = bytes(doc_info["doc_instance"].get_font_data(internal_font_id)) + except Exception: + data = b"" + if not data: + return Response(status_code=status.HTTP_204_NO_CONTENT) + + magic = data[:4] + if magic in _SFNT_TTF_MAGIC: + media_type = "font/ttf" + elif magic == _SFNT_OTF_MAGIC: + media_type = "font/otf" + else: + return Response(status_code=status.HTTP_204_NO_CONTENT) + + etag = '"' + hashlib.sha256(data).hexdigest()[:32] + '"' + return Response( + content=data, + media_type=media_type, + headers={"Cache-Control": "public, max-age=31536000, immutable", "ETag": etag}, + ) + + +@router.get("/{document_id}/font-reconstructed") +def get_reconstructed_font_bytes(document_id: str, internal_font_id: str) -> Response: + """Tier-2: a cmap-augmented copy of an embedded font (original glyph program + synthesized + Unicode cmap) so the WASM live preview can reuse the document's real glyphs and match the + saved result. 204 when reconstruction isn't possible -> frontend falls back to Tier-1.""" + if not engine.is_available(): + return Response(status_code=status.HTTP_204_NO_CONTENT) + if not internal_font_id or len(internal_font_id) > 256: + return Response(status_code=status.HTTP_204_NO_CONTENT) + doc_info = document_store.get_document(document_id) + if not doc_info: + return Response(status_code=status.HTTP_204_NO_CONTENT) + try: + data = bytes(doc_info["doc_instance"].get_reconstructed_font_data(internal_font_id)) + except Exception: + data = b"" + if not data or data[:4] not in _SFNT_TTF_MAGIC: + return Response(status_code=status.HTTP_204_NO_CONTENT) + etag = '"' + hashlib.sha256(data).hexdigest()[:32] + '"' + return Response( + content=data, + media_type="font/ttf", + headers={"Cache-Control": "public, max-age=31536000, immutable", "ETag": etag}, + ) diff --git a/gateway/app/routers/documents/metadata.py b/gateway/app/routers/documents/metadata.py new file mode 100644 index 0000000..a07615c --- /dev/null +++ b/gateway/app/routers/documents/metadata.py @@ -0,0 +1,56 @@ +from fastapi import APIRouter, HTTPException, status + +from app.schemas.document import DocumentMetadataResponse, OutlineItemResponse +from app.services import engine +from app.services.store import document_store + +router = APIRouter(tags=["documents"]) + +@router.get("/{document_id}/metadata", response_model=DocumentMetadataResponse) +def get_document_metadata(document_id: str) -> DocumentMetadataResponse: + if not engine.is_available(): + raise HTTPException( + status_code=status.HTTP_501_NOT_IMPLEMENTED, + detail="Engine bridge (bindings/python) not yet available.", + ) + + d = document_store.get_document(document_id) + if not d: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document not found") + + try: + doc = d["doc_instance"] + meta = doc.metadata + return DocumentMetadataResponse( + title=meta.title, + author=meta.author, + creator=meta.creator, + producer=meta.producer, + creation_date=meta.creation_date, + modification_date=meta.modification_date, + ) + except Exception as e: + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)) + + +@router.get("/{document_id}/outline", response_model=list[OutlineItemResponse]) +def get_document_outline(document_id: str) -> list[OutlineItemResponse]: + if not engine.is_available(): + raise HTTPException( + status_code=status.HTTP_501_NOT_IMPLEMENTED, + detail="Engine bridge (bindings/python) not yet available.", + ) + + d = document_store.get_document(document_id) + if not d: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document not found") + + try: + doc = d["doc_instance"] + items = doc.extract_outline() + return [ + OutlineItemResponse(title=it["title"], pageIndex=it["pageIndex"], level=it["level"]) + for it in items + ] + except Exception as e: + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)) diff --git a/gateway/app/routers/documents/text_objects.py b/gateway/app/routers/documents/text_objects.py new file mode 100644 index 0000000..007cd8b --- /dev/null +++ b/gateway/app/routers/documents/text_objects.py @@ -0,0 +1,106 @@ +import os +import tempfile + +from fastapi import APIRouter, HTTPException + +from app.schemas.text_object import TextObjectResponse, UpdateTextObjectRequest +from app.services import engine +from app.services.store import document_store + +router = APIRouter(tags=["documents"]) + + +@router.get( + "/{document_id}/pages/{page_index}/text_objects", + response_model=list[TextObjectResponse], +) +def get_text_objects(document_id: str, page_index: int) -> list[TextObjectResponse]: + if not engine.is_available(): + raise HTTPException(status_code=501, detail="Engine not available") + + doc_info = document_store.get_document(document_id) + if not doc_info: + raise HTTPException(status_code=404, detail="Document not found") + + _perms = doc_info.get("permissions") or {} + if _perms.get("canModify", True) is False: + raise HTTPException(status_code=403, detail="Raw Text editing is not permitted by this document's restrictions (canModify).") + + pdfengine = engine.require() + + with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp: + tmp.write(doc_info["bytes_data"]) + tmp_path = tmp.name + + try: + editor = pdfengine.StreamEditor(tmp_path) + objects = editor.extract_text_objects(page_index) + + result = [] + for obj in objects: + text_str = obj["text"].decode("latin-1") if isinstance(obj["text"], bytes) else obj["text"] + result.append(TextObjectResponse( + text=text_str, + fontName=obj["fontName"], + fontSize=obj["fontSize"], + tm=obj["tm"] + )) + return result + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + finally: + if os.path.exists(tmp_path): + os.remove(tmp_path) + + +@router.put("/{document_id}/pages/{page_index}/text_objects/{object_index}") +def replace_text_object(document_id: str, page_index: int, object_index: int, req: UpdateTextObjectRequest) -> dict: + if not engine.is_available(): + raise HTTPException(status_code=501, detail="Engine not available") + + doc_info = document_store.get_document(document_id) + if not doc_info: + raise HTTPException(status_code=404, detail="Document not found") + + _perms = doc_info.get("permissions") or {} + if _perms.get("canModify", True) is False: + raise HTTPException(status_code=403, detail="Raw Text editing is not permitted by this document's restrictions (canModify).") + + pdfengine = engine.require() + + with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp: + tmp.write(doc_info["bytes_data"]) + tmp_path = tmp.name + + out_path = tmp_path + ".out.pdf" + + try: + editor = pdfengine.StreamEditor(tmp_path) + try: + new_text_bytes = req.new_text.encode("latin-1") + except UnicodeEncodeError as enc_err: + raise HTTPException( + status_code=400, + detail="Some characters can't be encoded in this run's font. Raw Text supports same-charset edits only — use Edit text to add new characters.", + ) from enc_err + success = editor.replace_text_object(page_index, object_index, new_text_bytes, out_path) + if not success: + raise HTTPException(status_code=400, detail="Failed to replace text object (not found or identical)") + + with open(out_path, "rb") as f: + new_bytes = f.read() + new_doc = pdfengine.PdfDocument.load_from_memory(new_bytes, "") + new_info = document_store.add_document( + filename=doc_info["filename"], bytes_data=new_bytes, doc_instance=new_doc, + permissions=doc_info.get("permissions"), + ) + return {"success": True, "newDocumentId": new_info["id"]} + except HTTPException: + raise + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) from e + finally: + if os.path.exists(tmp_path): + os.remove(tmp_path) + if os.path.exists(out_path): + os.remove(out_path) diff --git a/gateway/app/routers/render.py b/gateway/app/routers/render.py index 22a1bad..fe56e46 100644 --- a/gateway/app/routers/render.py +++ b/gateway/app/routers/render.py @@ -2,8 +2,9 @@ from typing import Annotated from fastapi import APIRouter, HTTPException, Path, Query, Response, status -from app.routers.documents import FontInfoResponse +from app.schemas.font import FontInfoResponse from app.services import engine +from app.services.font import font_info_to_response from app.services.store import document_store router = APIRouter(prefix="/documents/{document_id}/pages", tags=["render"]) @@ -179,30 +180,7 @@ def get_page_fonts(document_id: str, page_index: int) -> list[FontInfoResponse]: doc = doc_info["doc_instance"] page = doc.get_page(page_index) fonts = page.get_fonts() - return [ - FontInfoResponse( - fontName=f.font_name, - type=f.type, - isEmbedded=f.is_embedded, - isSubset=f.is_subset, - isVertical=f.is_vertical, - encoding=f.encoding, - hasToUnicode=f.has_to_unicode, - cmapName=f.cmap_name, - cidSystemInfo=f.cid_system_info, - subsetTag=f.subset_tag, - sourceType=f.source_type, - substitutedFrom=f.substituted_from, - substitutedTo=f.substituted_to, - normalizedFamily=f.normalized_family, - internalFontId=f.internal_font_id, - flags=f.flags, - ascent=f.ascent, - descent=f.descent, - capHeight=f.cap_height, - ) - for f in fonts - ] + return [font_info_to_response(f) for f in fonts] except IndexError: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Page index out of range") except Exception as e: diff --git a/gateway/app/schemas/__init__.py b/gateway/app/schemas/__init__.py new file mode 100644 index 0000000..b1a3fd2 --- /dev/null +++ b/gateway/app/schemas/__init__.py @@ -0,0 +1,6 @@ +"""Pydantic request/response schemas for the gateway API. + +Extracted from the router modules so they can be shared across routers without +forcing cross-router imports (which previously created an import cycle between +``render`` and ``documents``). +""" diff --git a/gateway/app/schemas/annotation.py b/gateway/app/schemas/annotation.py new file mode 100644 index 0000000..0e9ee5e --- /dev/null +++ b/gateway/app/schemas/annotation.py @@ -0,0 +1,22 @@ +from pydantic import BaseModel + + +class AnnotationResponse(BaseModel): + id: str + type: str + x: float + y: float + width: float + height: float + color: str + author: str + content: str + timestamp: str | None = None + pageIndex: int + paths: list[list[dict[str, float]]] = [] + + fieldName: str | None = None + fieldValue: str | None = None + fieldType: str | None = None + fieldFlags: int | None = None + fieldOptions: list[str] | None = None diff --git a/gateway/app/schemas/document.py b/gateway/app/schemas/document.py new file mode 100644 index 0000000..8f9c5af --- /dev/null +++ b/gateway/app/schemas/document.py @@ -0,0 +1,50 @@ +from pydantic import BaseModel + + +class PageInfoResponse(BaseModel): + index: int + width: float + height: float + + +class PermissionsResponse(BaseModel): + isEncrypted: bool = False + encryption: str = "None" + securityRevision: int = -1 + ownerUnlocked: bool = False + canPrint: bool = True + canPrintHighRes: bool = True + canModify: bool = True + canCopy: bool = True + canAnnotate: bool = True + canFillForms: bool = True + canExtractForAccessibility: bool = True + canAssemble: bool = True + + +class DocumentInfoResponse(BaseModel): + id: str + filename: str + sizeBytes: int + totalPages: int + pageWidth: float + pageHeight: float + uploadedAt: str + status: str + pages: list[PageInfoResponse] = [] + permissions: PermissionsResponse = PermissionsResponse() + + +class DocumentMetadataResponse(BaseModel): + title: str + author: str + creator: str + producer: str + creation_date: str + modification_date: str + + +class OutlineItemResponse(BaseModel): + title: str + pageIndex: int + level: int diff --git a/gateway/app/schemas/font.py b/gateway/app/schemas/font.py new file mode 100644 index 0000000..7bbdefc --- /dev/null +++ b/gateway/app/schemas/font.py @@ -0,0 +1,23 @@ +from pydantic import BaseModel + + +class FontInfoResponse(BaseModel): + fontName: str + type: str + isEmbedded: bool + isSubset: bool + isVertical: bool + encoding: str + hasToUnicode: bool + cmapName: str + cidSystemInfo: str + subsetTag: str + sourceType: str + substitutedFrom: str + substitutedTo: str + normalizedFamily: str + internalFontId: str + flags: int + ascent: float + descent: float + capHeight: float diff --git a/gateway/app/schemas/page_model.py b/gateway/app/schemas/page_model.py new file mode 100644 index 0000000..10730cf --- /dev/null +++ b/gateway/app/schemas/page_model.py @@ -0,0 +1,59 @@ +from pydantic import BaseModel + + +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 + page_object_index: int = -1 + + +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 + object_indices: list[int] = [] + color: str = "#000000" + font_fidelity: str = "exact" + + +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 diff --git a/gateway/app/schemas/search.py b/gateway/app/schemas/search.py new file mode 100644 index 0000000..ec333e9 --- /dev/null +++ b/gateway/app/schemas/search.py @@ -0,0 +1,13 @@ +from pydantic import BaseModel + +class SearchRect(BaseModel): + x: float + y: float + w: float + h: float + + +class SearchMatch(BaseModel): + pageIndex: int + rects: list[SearchRect] + text: str diff --git a/gateway/app/schemas/text_object.py b/gateway/app/schemas/text_object.py new file mode 100644 index 0000000..234b1de --- /dev/null +++ b/gateway/app/schemas/text_object.py @@ -0,0 +1,12 @@ +from pydantic import BaseModel + + +class TextObjectResponse(BaseModel): + text: str + fontName: str + fontSize: float + tm: list[float] + + +class UpdateTextObjectRequest(BaseModel): + new_text: str diff --git a/gateway/app/services/export.py b/gateway/app/services/export.py new file mode 100644 index 0000000..55b4e82 --- /dev/null +++ b/gateway/app/services/export.py @@ -0,0 +1,44 @@ +"""Export pipeline: full-save + a pypdf pass that sets /NeedAppearances. + +NOTE: ``pypdf`` is intentionally imported lazily inside the function. It is not a +declared gateway dependency and may only be resolvable via the roaming +site-packages path appended below, so importing it at module load would prevent +the whole app from starting on machines without it. Behavior here is preserved +verbatim from the original ``routers.documents.export_document``; if pypdf is +later added to ``pyproject.toml`` dependencies, the sys.path augmentation can be +removed and the import hoisted to module scope. +""" + + +def apply_need_appearances(bytes_data: bytes) -> bytes: + """Round-trip the PDF through pypdf to set AcroForm /NeedAppearances=true.""" + import os + import sys + + roaming_path = os.path.join( + os.environ.get("APPDATA", "C:\\Users\\azeem\\AppData\\Roaming"), + "Python", + "Python312", + "site-packages", + ) + if roaming_path not in sys.path: + sys.path.append(roaming_path) + + import io + + import pypdf + + reader = pypdf.PdfReader(io.BytesIO(bytes_data)) + writer = pypdf.PdfWriter() + writer.append(reader) + + acro_form = writer.root_object.get("/AcroForm") + if acro_form is not None: + acro_form_dict = acro_form.get_object() + acro_form_dict[pypdf.generic.NameObject("/NeedAppearances")] = pypdf.generic.BooleanObject( + True + ) + + out_stream = io.BytesIO() + writer.write(out_stream) + return out_stream.getvalue() diff --git a/gateway/app/services/font.py b/gateway/app/services/font.py new file mode 100644 index 0000000..f422d78 --- /dev/null +++ b/gateway/app/services/font.py @@ -0,0 +1,32 @@ +"""Font helpers shared by the documents and render routers.""" + +from app.schemas.font import FontInfoResponse + + +def font_info_to_response(f) -> FontInfoResponse: + """Map an engine font-info object to the API response model. + + Previously this mapping was duplicated verbatim in both + ``routers.documents.get_document_fonts`` and ``routers.render.get_page_fonts``. + """ + return FontInfoResponse( + fontName=f.font_name, + type=f.type, + isEmbedded=f.is_embedded, + isSubset=f.is_subset, + isVertical=f.is_vertical, + encoding=f.encoding, + hasToUnicode=f.has_to_unicode, + cmapName=f.cmap_name, + cidSystemInfo=f.cid_system_info, + subsetTag=f.subset_tag, + sourceType=f.source_type, + substitutedFrom=f.substituted_from, + substitutedTo=f.substituted_to, + normalizedFamily=f.normalized_family, + internalFontId=f.internal_font_id, + flags=f.flags, + ascent=f.ascent, + descent=f.descent, + capHeight=f.cap_height, + ) diff --git a/gateway/app/services/page_model.py b/gateway/app/services/page_model.py new file mode 100644 index 0000000..35b3f3c --- /dev/null +++ b/gateway/app/services/page_model.py @@ -0,0 +1,77 @@ +"""Map an engine page model to the API ``PageModelResponse``. + +Extracted verbatim from ``routers.documents.get_page_model``. +""" + +from app.schemas.page_model import ( + GlyphModel, + PageModelResponse, + ParagraphModel, + TextLineModel, + TextRunModel, +) + + +def build_page_model_response(model) -> PageModelResponse: + paragraphs = [] + for p in model.paragraphs: + lines = [] + for line in p.lines: + runs = [] + for r in line.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, + page_object_index=g.page_object_index, + ) + ) + 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, + object_indices=r.object_indices, + color=getattr(r, "fill_color", "#000000") or "#000000", + font_fidelity=getattr(r, "font_fidelity", "exact") or "exact", + ) + ) + lines.append( + TextLineModel( + runs=runs, + baseline_y=line.baseline_y, + x=line.x, + y=line.y, + w=line.w, + h=line.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, + ) diff --git a/gateway/app/services/search.py b/gateway/app/services/search.py new file mode 100644 index 0000000..e212fdd --- /dev/null +++ b/gateway/app/services/search.py @@ -0,0 +1,97 @@ +"""Full-text search over a loaded document's glyph bounds. + +Extracted verbatim from ``routers.documents.search_document`` so the router stays +a thin HTTP wrapper. The router remains responsible for the engine-availability / +document-existence checks and for translating exceptions into HTTP errors. +""" + +from app.schemas.search import SearchMatch, SearchRect + + +def compute_search_matches( + doc, + q: str, + case_sensitive: bool = False, + whole_words: bool = False, +) -> list[SearchMatch]: + def _is_word_char(ch: str) -> bool: + return ch.isalnum() or ch == "_" + + def _find_all(haystack: str, needle: str) -> list[int]: + """Return start indices of all non-overlapping occurrences of needle in haystack.""" + results: list[int] = [] + start = 0 + needle_len = len(needle) + while True: + pos = haystack.find(needle, start) + if pos == -1: + break + if whole_words: + before_ok = pos == 0 or not _is_word_char(haystack[pos - 1]) + after_ok = (pos + needle_len) >= len(haystack) or not _is_word_char( + haystack[pos + needle_len] + ) + if before_ok and after_ok: + results.append(pos) + else: + results.append(pos) + start = pos + 1 + return results + + matches = [] + search_needle = q if case_sensitive else q.lower() + query_len = len(search_needle) + + for page_idx in range(doc.page_count): + page = doc.get_page(page_idx) + glyphs = page.extract_text_with_bounds() + if not glyphs: + continue + + text_str = "" + char_to_glyph: list[int] = [] + for i, g in enumerate(glyphs): + s = g.get("text", "") + start_len = len(text_str) + text_str += s + for _ in range(len(text_str) - start_len): + char_to_glyph.append(i) + + search_text = text_str if case_sensitive else text_str.lower() + + for idx in _find_all(search_text, search_needle): + if idx + query_len - 1 >= len(char_to_glyph): + continue + + start_glyph_idx = char_to_glyph[idx] + end_glyph_idx = char_to_glyph[idx + query_len - 1] + + rects = [] + current_rect = None + + for g_idx in range(start_glyph_idx, end_glyph_idx + 1): + g = glyphs[g_idx] + dom_y = page.height - (g["y"] + g["h"]) + + if current_rect is None: + current_rect = {"x": g["x"], "y": dom_y, "w": g["w"], "h": g["h"]} + else: + if abs(dom_y - current_rect["y"]) < g.get("fontSize", 12) * 0.5: + max_x = max(current_rect["x"] + current_rect["w"], g["x"] + g["w"]) + current_rect["w"] = max_x - current_rect["x"] + current_rect["y"] = min(current_rect["y"], dom_y) + current_rect["h"] = max(current_rect["h"], g["h"]) + else: + rects.append(SearchRect(**current_rect)) + current_rect = {"x": g["x"], "y": dom_y, "w": g["w"], "h": g["h"]} + + if current_rect: + rects.append(SearchRect(**current_rect)) + + matches.append( + SearchMatch( + pageIndex=page_idx, rects=rects, text=text_str[idx : idx + query_len] + ) + ) + + return matches diff --git a/wasm/bindings/pdf_engine_facade.cpp b/wasm/bindings/pdf_engine_facade.cpp deleted file mode 100644 index 4944d43..0000000 --- a/wasm/bindings/pdf_engine_facade.cpp +++ /dev/null @@ -1,461 +0,0 @@ -#include "pdf_engine_facade.hpp" -#include "wasm_rasterizer.hpp" -#include -#include -#include -#include - - -WasmMockPage::WasmMockPage(int pageIndex) : m_pageIndex(pageIndex) { - m_displayList.fillRect(0.0f, 0.0f, 800.0f, 1100.0f); - - float gridSpacing = 50.0f; - for (float x = gridSpacing; x < 800.0f; x += gridSpacing) { - m_displayList.fillRect(x, 0.0f, 1.0f, 1100.0f); - } - for (float y = gridSpacing; y < 1100.0f; y += gridSpacing) { - m_displayList.fillRect(0.0f, y, 800.0f, 1.0f); - } - - float borderWidth = 4.0f; - m_displayList.fillRect(0.0f, 0.0f, 800.0f, borderWidth); - m_displayList.fillRect(0.0f, 1100.0f - borderWidth, 800.0f, borderWidth); - m_displayList.fillRect(0.0f, 0.0f, borderWidth, 1100.0f); - m_displayList.fillRect(800.0f - borderWidth, 0.0f, borderWidth, 1100.0f); - - float centerX = 400.0f; - float centerY = 550.0f; - float size = 120.0f; - - if (m_pageIndex == 0) { - m_displayList.fillRect(centerX - size / 2.0f, centerY - size / 2.0f, size, size); - } else { - m_displayList.saveState(); - - m_displayList.setTransform(pdfengine::Matrix(1.0f, 0.0f, 0.0f, 1.0f, centerX, centerY)); - - float angle = 45.0f * 3.14159265f / 180.0f; - float cosVal = std::cos(angle); - float sinVal = std::sin(angle); - m_displayList.setTransform(pdfengine::Matrix(cosVal, sinVal, -sinVal, cosVal, 0.0f, 0.0f)); - - m_displayList.fillRect(-size / 2.0f, -size / 2.0f, size, size); - - m_displayList.restoreState(); - } - - m_displayList.drawText("Page " + std::to_string(m_pageIndex + 1), 60.0f, 80.0f); -} - -std::expected WasmMockPage::render(int dpi) const { - (void)dpi; - return std::unexpected(pdfengine::EngineError::Unknown); -} - -std::expected WasmMockPage::extractText() const { - return "Mock text on page " + std::to_string(m_pageIndex + 1); -} - -std::expected, pdfengine::EngineError> WasmMockPage::extractTextWithBounds() const { - std::vector glyphs; - std::string text = "Page " + std::to_string(m_pageIndex + 1); - - double startX = 60.0; - double startY = 80.0; - for (size_t i = 0; i < text.length(); ++i) { - pdfengine::GlyphBounds gb; - gb.text = std::string(1, text[i]); - gb.x = startX + i * 6.0; - gb.y = startY; - gb.w = 6.0; - gb.h = 8.0; - gb.fontSize = 12.0; - glyphs.push_back(gb); - } - return glyphs; -} - -std::expected WasmMockPage::extractDocumentModel() const { - pdfengine::PageModel model; - model.width = width(); - model.height = height(); - model.pageIndex = m_pageIndex; - - pdfengine::Paragraph p; - p.x = 60.0; - p.y = 80.0; - p.w = 100.0; - p.h = 20.0; - - pdfengine::TextLine line; - line.x = 60.0; - line.y = 80.0; - line.w = 100.0; - line.h = 20.0; - line.baselineY = 80.0; - - pdfengine::TextRun run; - run.text = "Page " + std::to_string(m_pageIndex + 1); - run.fontName = "Helvetica"; - run.fontSize = 12.0; - run.x = 60.0; - run.y = 80.0; - run.w = 100.0; - run.h = 20.0; - - double startX = 60.0; - double startY = 80.0; - for (size_t i = 0; i < run.text.length(); ++i) { - pdfengine::Glyph g; - g.text = std::string(1, run.text[i]); - g.unicode = run.text[i]; - g.fontName = "Helvetica"; - g.fontSize = 12.0; - g.originX = startX + i * 6.0; - g.originY = startY; - g.bboxX = g.originX; - g.bboxY = g.originY; - g.bboxW = 6.0; - g.bboxH = 8.0; - run.glyphs.push_back(g); - } - - line.runs.push_back(run); - p.lines.push_back(line); - model.paragraphs.push_back(p); - - return model; -} - -std::expected, pdfengine::EngineError> WasmMockPage::extractAnnotationsText() const { - return std::vector(); -} - -std::expected, pdfengine::EngineError> WasmMockPage::extractAnnotations() const { - return std::vector(); -} - -std::expected WasmMockPage::getGlyphWidth(const std::string& fontName, uint32_t charcode, double fontSize) const { - (void)fontName; (void)charcode; - return 6.0 * (fontSize / 12.0); -} - -std::expected, pdfengine::EngineError> WasmMockPage::getFonts() const { - pdfengine::FontInfo info; - info.fontName = "Helvetica"; - info.type = "Type1"; - info.isEmbedded = false; - info.isSubset = false; - info.isVertical = false; - info.encoding = "WinAnsiEncoding"; - info.hasToUnicode = false; - info.sourceType = "Substituted"; - info.substitutedFrom = "Helvetica"; - info.substitutedTo = "Liberation Sans Regular"; - info.normalizedFamily= "Arial"; - info.internalFontId = "mock-page-" + std::to_string(m_pageIndex) + "-helvetica"; - info.flags = 32; - info.ascent = 718.0; - info.descent = -207.0; - info.capHeight = 718.0; - return std::vector{info}; -} - -pdfengine::DevicePoint WasmMockPage::pageToDevice(const pdfengine::Point2D& pagePoint, int deviceWidth, int deviceHeight, int rotate) const noexcept { - (void)rotate; - double scaleX = static_cast(deviceWidth) / width(); - double scaleY = static_cast(deviceHeight) / height(); - int dx = static_cast(pagePoint.x * scaleX); - int dy = static_cast((height() - pagePoint.y) * scaleY); - return {dx, dy}; -} - -pdfengine::Point2D WasmMockPage::deviceToPage(const pdfengine::DevicePoint& devicePoint, int deviceWidth, int deviceHeight, int rotate) const noexcept { - (void)rotate; - double scaleX = width() / static_cast(deviceWidth); - double scaleY = height() / static_cast(deviceHeight); - double px = devicePoint.x * scaleX; - double py = height() - (devicePoint.y * scaleY); - return {px, py}; -} - - -WasmMockDocument::WasmMockDocument(int pageCount, std::vector data) - : m_pageCount(pageCount), m_data(std::move(data)) {} - -pdfengine::DocumentMetadata WasmMockDocument::metadata() const noexcept { - pdfengine::DocumentMetadata meta; - meta.title = "WASM Mock Document"; - meta.author = "Emscripten Engine"; - meta.creator = "PdfEngine SDK"; - meta.producer = "WebAssembly Facade"; - meta.creationDate = "D:20260601090000"; - meta.modificationDate = "D:20260601090000"; - return meta; -} - -std::expected, pdfengine::EngineError> WasmMockDocument::getPage(int pageIndex) { - if (pageIndex < 0 || pageIndex >= m_pageCount) { - return std::unexpected(pdfengine::EngineError::PageOutOfBounds); - } - - auto it = m_pages.find(pageIndex); - if (it == m_pages.end()) { - auto page = std::make_shared(pageIndex); - m_pages[pageIndex] = page; - return page; - } - return it->second; -} - -std::expected, pdfengine::EngineError> WasmMockDocument::getFonts(int startPage, int endPage) const { - int last = (endPage < 0) ? m_pageCount - 1 : std::min(endPage, m_pageCount - 1); - std::vector result; - - for (int i = startPage; i <= last; ++i) { - bool found = false; - for (const auto& existing : result) { - if (existing.fontName == "Helvetica") { found = true; break; } - } - if (!found) { - pdfengine::FontInfo info; - info.fontName = "Helvetica"; - info.type = "Type1"; - info.isEmbedded = false; - info.isSubset = false; - info.isVertical = false; - info.encoding = "WinAnsiEncoding"; - info.hasToUnicode = false; - info.sourceType = "Substituted"; - info.substitutedFrom = "Helvetica"; - info.substitutedTo = "Liberation Sans Regular"; - info.normalizedFamily= "Arial"; - info.internalFontId = "mock-doc-helvetica"; - info.flags = 32; - info.ascent = 718.0; - info.descent = -207.0; - info.capHeight = 718.0; - result.push_back(info); - } - } - return result; -} - -std::expected WasmMockDocument::applyEdits(const std::string& editsJson) { - (void)editsJson; - return {}; -} - -std::expected, pdfengine::EngineError> WasmMockDocument::saveIncremental() const { - return m_data; -} - -std::expected, pdfengine::EngineError> WasmMockDocument::getFontData(const std::string& internalFontId) const { - (void)internalFontId; - return std::unexpected(pdfengine::EngineError::Unknown); -} - -std::expected, std::string> WasmMockDocument::getResolvedFont(const pdfengine::FontInfo& fontInfo) { - (void)fontInfo; - return std::unexpected("Not implemented in WASM mock"); -} - -std::expected, pdfengine::EngineError> WasmMockDocument::saveFull() const { - return m_data; -} - - -PdfEngineFacade::PdfEngineFacade() : m_nextHandle(1) {} - -PdfEngineFacade::~PdfEngineFacade() = default; - -int PdfEngineFacade::loadDocument(const uint8_t* buffer, int size) { - if (!buffer || size <= 0) { - return 0; - } - - int pages = 0; - if (size > 4 && buffer[0] == '%' && buffer[1] == 'P' && buffer[2] == 'D' && buffer[3] == 'F') { - std::string_view sv(reinterpret_cast(buffer), size); - size_t pos = 0; - while ((pos = sv.find("/Type /Page", pos)) != std::string_view::npos) { - if (pos + 11 < sv.size() && sv[pos + 11] != 's') { - pages++; - } - pos += 11; - } - } - - if (pages == 0) { - pages = 3; - } - - std::vector docBytes(buffer, buffer + size); - auto doc = std::make_shared(pages, std::move(docBytes)); - - int handle = m_nextHandle++; - m_documents[handle] = std::move(doc); - return handle; -} - -bool PdfEngineFacade::renderPage(int docHandle, int pageIndex, float scale, uint8_t* outputBuffer, int width, int height) { - auto it = m_documents.find(docHandle); - if (it == m_documents.end()) { - return false; - } - - const auto& doc = it->second; - auto pageRes = doc->getPage(pageIndex); - if (!pageRes.has_value()) { - return false; - } - - auto page = *pageRes; - auto mockPage = std::dynamic_pointer_cast(page); - if (!mockPage) { - return false; - } - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - int idx = (y * width + x) * 4; - outputBuffer[idx + 0] = 250; - outputBuffer[idx + 1] = 250; - outputBuffer[idx + 2] = 245; - outputBuffer[idx + 3] = 255; - } - } - - WasmRasterizer rasterizer(outputBuffer, width, height, scale); - mockPage->getDisplayList().replay(rasterizer); - - return true; -} - -void PdfEngineFacade::freeDocument(int docHandle) { - m_documents.erase(docHandle); -} - -const char* PdfEngineFacade::buildInfo() { - return "PdfEngine WASM Facade (Phase 1/2 Enabled)"; -} - -bool PdfEngineFacade::hasSkia() { - return false; -} - - -namespace { -std::string fontInfosToJsonLocal(const std::vector& fonts) { - std::string json = "["; - for (size_t i = 0; i < fonts.size(); ++i) { - const auto& f = fonts[i]; - if (i > 0) json += ","; - json += "{"; - json += "\"fontName\":\"" + f.fontName + "\","; - json += "\"type\":\"" + f.type + "\","; - json += "\"isEmbedded\":" + std::string(f.isEmbedded ? "true" : "false") + ","; - json += "\"isSubset\":" + std::string(f.isSubset ? "true" : "false") + ","; - json += "\"isVertical\":" + std::string(f.isVertical ? "true" : "false") + ","; - json += "\"encoding\":\"" + f.encoding + "\","; - json += "\"hasToUnicode\":" + std::string(f.hasToUnicode ? "true" : "false") + ","; - json += "\"cmapName\":\"" + f.cmapName + "\","; - json += "\"cidSystemInfo\":\"" + f.cidSystemInfo + "\","; - json += "\"subsetTag\":\"" + f.subsetTag + "\","; - json += "\"sourceType\":\"" + f.sourceType + "\","; - json += "\"substitutedFrom\":\"" + f.substitutedFrom + "\","; - json += "\"substitutedTo\":\"" + f.substitutedTo + "\","; - json += "\"normalizedFamily\":\"" + f.normalizedFamily + "\","; - json += "\"internalFontId\":\"" + f.internalFontId + "\","; - json += "\"ascent\":" + std::to_string(f.ascent) + ","; - json += "\"descent\":" + std::to_string(f.descent) + ","; - json += "\"capHeight\":" + std::to_string(f.capHeight); - json += "}"; - } - json += "]"; - return json; -} - -std::string escapeJsonString(const std::string& input) { - std::string output; - for (char c : input) { - if (c == '"') output += "\\\""; - else if (c == '\\') output += "\\\\"; - else if (c == '\b') output += "\\b"; - else if (c == '\f') output += "\\f"; - else if (c == '\n') output += "\\n"; - else if (c == '\r') output += "\\r"; - else if (c == '\t') output += "\\t"; - else if (static_cast(c) < 32) { - char buf[16]; - snprintf(buf, sizeof(buf), "\\u%04x", c); - output += buf; - } else { - output += c; - } - } - return output; -} - -std::string glyphBoundsToJsonLocal(const std::vector& glyphs) { - std::string json = "["; - for (size_t i = 0; i < glyphs.size(); ++i) { - const auto& g = glyphs[i]; - if (i > 0) json += ","; - json += "{"; - json += "\"text\":\"" + escapeJsonString(g.text) + "\","; - json += "\"x\":" + std::to_string(g.x) + ","; - json += "\"y\":" + std::to_string(g.y) + ","; - json += "\"w\":" + std::to_string(g.w) + ","; - json += "\"h\":" + std::to_string(g.h) + ","; - json += "\"fontSize\":" + std::to_string(g.fontSize); - json += "}"; - } - json += "]"; - return json; -} -} - -std::string PdfEngineFacade::getDocumentFonts(int docHandle, int startPage, int endPage) { - auto it = m_documents.find(docHandle); - if (it == m_documents.end()) { - return "[]"; - } - auto result = it->second->getFonts(startPage, endPage); - if (!result.has_value()) { - return "[]"; - } - return fontInfosToJsonLocal(*result); -} - -std::string PdfEngineFacade::getPageFonts(int docHandle, int pageIndex) { - auto it = m_documents.find(docHandle); - if (it == m_documents.end()) { - return "[]"; - } - auto pageRes = it->second->getPage(pageIndex); - if (!pageRes.has_value()) { - return "[]"; - } - auto result = (*pageRes)->getFonts(); - if (!result.has_value()) { - return "[]"; - } - return fontInfosToJsonLocal(*result); -} - -std::string PdfEngineFacade::getPageTextJson(int docHandle, int pageIndex) { - auto it = m_documents.find(docHandle); - if (it == m_documents.end()) { - return "[]"; - } - auto pageRes = it->second->getPage(pageIndex); - if (!pageRes.has_value()) { - return "[]"; - } - auto result = (*pageRes)->extractTextWithBounds(); - if (!result.has_value()) { - return "[]"; - } - return glyphBoundsToJsonLocal(*result); -} diff --git a/wasm/bindings/pdf_engine_facade.hpp b/wasm/bindings/pdf_engine_facade.hpp deleted file mode 100644 index 5247330..0000000 --- a/wasm/bindings/pdf_engine_facade.hpp +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -class WasmMockPage : public pdfengine::PdfPage { -public: - explicit WasmMockPage(int pageIndex); - ~WasmMockPage() override = default; - - [[nodiscard]] double width() const noexcept override { return 800.0; } - [[nodiscard]] double height() const noexcept override { return 1100.0; } - - [[nodiscard]] std::expected render(int dpi = 96) const override; - [[nodiscard]] std::expected extractText() const override; - [[nodiscard]] std::expected, pdfengine::EngineError> extractTextWithBounds() const override; - [[nodiscard]] std::expected extractDocumentModel() const override; - [[nodiscard]] std::expected, pdfengine::EngineError> getFonts() const override; - [[nodiscard]] std::expected, pdfengine::EngineError> extractAnnotationsText() const override; - [[nodiscard]] std::expected, pdfengine::EngineError> extractAnnotations() const override; - [[nodiscard]] std::expected getGlyphWidth(const std::string& fontName, uint32_t charcode, double fontSize) const override; - - [[nodiscard]] pdfengine::DevicePoint pageToDevice(const pdfengine::Point2D& pagePoint, int deviceWidth, int deviceHeight, int rotate = 0) const noexcept override; - [[nodiscard]] pdfengine::Point2D deviceToPage(const pdfengine::DevicePoint& devicePoint, int deviceWidth, int deviceHeight, int rotate = 0) const noexcept override; - - [[nodiscard]] const pdfengine::DisplayList& getDisplayList() const { return m_displayList; } - -private: - int m_pageIndex; - pdfengine::DisplayList m_displayList; -}; - -class WasmMockDocument : public pdfengine::PdfDocument { -public: - WasmMockDocument(int pageCount, std::vector data); - ~WasmMockDocument() override = default; - - [[nodiscard]] int pageCount() const noexcept override { return m_pageCount; } - [[nodiscard]] pdfengine::DocumentMetadata metadata() const noexcept override; - [[nodiscard]] std::expected, pdfengine::EngineError> extractOutline() const override { return std::vector{}; } - [[nodiscard]] std::expected, pdfengine::EngineError> getPage(int pageIndex) override; - [[nodiscard]] std::expected, pdfengine::EngineError> getFonts(int startPage = 0, int endPage = -1) const override; - - std::expected applyEdits(const std::string& editsJson) override; - [[nodiscard]] std::expected, pdfengine::EngineError> saveIncremental() const override; - [[nodiscard]] std::expected, pdfengine::EngineError> getFontData(const std::string& internalFontId) const override; - [[nodiscard]] std::expected, std::string> getResolvedFont(const pdfengine::FontInfo& fontInfo) override; - [[nodiscard]] std::expected, pdfengine::EngineError> saveFull() const override; - -private: - int m_pageCount; - std::vector m_data; - std::unordered_map> m_pages; -}; - -class PdfEngineFacade { -public: - PdfEngineFacade(); - ~PdfEngineFacade(); - - int loadDocument(const uint8_t* buffer, int size); - bool renderPage(int docHandle, int pageIndex, float scale, uint8_t* outputBuffer, int width, int height); - void freeDocument(int docHandle); - - std::string getDocumentFonts(int docHandle, int startPage, int endPage); - std::string getPageFonts(int docHandle, int pageIndex); - std::string getPageTextJson(int docHandle, int pageIndex); - - static const char* buildInfo(); - static bool hasSkia(); - -private: - int m_nextHandle; - std::unordered_map> m_documents; -}; diff --git a/wasm/bindings/wasm_engine.cpp b/wasm/bindings/wasm_engine.cpp index af0b667..51f75b4 100644 --- a/wasm/bindings/wasm_engine.cpp +++ b/wasm/bindings/wasm_engine.cpp @@ -10,11 +10,16 @@ namespace { struct DocEntry { std::shared_ptr doc; - std::vector bytes; + std::vector bytes; + std::unordered_map> auxFonts; }; std::unordered_map g_docs; int g_nextHandle = 1; +void applyAuxFonts(pdfengine::PdfDocument& fresh, const DocEntry& entry) { + for (const auto& [fid, bytes] : entry.auxFonts) fresh.registerAuxFont(fid, bytes); +} + std::vector g_lastPng; int g_lastW = 0, g_lastH = 0; std::string g_lastLayout; @@ -63,6 +68,7 @@ EMSCRIPTEN_KEEPALIVE int previewRender(int handle, int pageIndex, int dpi, const if (it == g_docs.end()) return -1; auto fresh = pdfengine::PdfDocument::loadFromMemory(it->second.bytes, ""); if (!fresh) return -1; + applyAuxFonts(**fresh, it->second); g_lastLayout.clear(); if (editsJson && editsJson[0]) { auto r = (*fresh)->applyEdits(editsJson); @@ -78,6 +84,7 @@ EMSCRIPTEN_KEEPALIVE int previewRenderRegion(int handle, int pageIndex, int dpi, if (it == g_docs.end()) return -1; auto fresh = pdfengine::PdfDocument::loadFromMemory(it->second.bytes, ""); if (!fresh) return -1; + applyAuxFonts(**fresh, it->second); g_lastLayout.clear(); if (editsJson && editsJson[0]) { auto r = (*fresh)->applyEdits(editsJson); @@ -100,6 +107,7 @@ EMSCRIPTEN_KEEPALIVE int previewRenderPaginated(int handle, int pageIndex, int d if (it == g_docs.end()) return -1; auto fresh = pdfengine::PdfDocument::loadFromMemory(it->second.bytes, ""); if (!fresh) return -1; + applyAuxFonts(**fresh, it->second); g_lastLayout.clear(); g_regions.clear(); bool overflowed = false; @@ -149,10 +157,11 @@ EMSCRIPTEN_KEEPALIVE void registerAuxFont(int handle, const char* fid, const uin if (it == g_docs.end() || !fid || !data || size <= 0) return; std::vector bytes(data, data + size); it->second.doc->registerAuxFont(std::string(fid), bytes); + it->second.auxFonts[std::string(fid)] = std::move(bytes); } EMSCRIPTEN_KEEPALIVE void freeDocument(int handle) { g_docs.erase(handle); } EMSCRIPTEN_KEEPALIVE const char* engineBuildInfo() { return "pdfengine-wasm+pdfium"; } -} +} \ No newline at end of file