diff --git a/bindings/python/pdfengine_py.cpp b/bindings/python/pdfengine_py.cpp index 27bcee7..27f371d 100644 --- a/bindings/python/pdfengine_py.cpp +++ b/bindings/python/pdfengine_py.cpp @@ -305,7 +305,8 @@ PYBIND11_MODULE(pdfengine, m) { .def_readonly("h", &pdfengine::TextRun::h) .def_readonly("object_indices", &pdfengine::TextRun::objectIndices) .def_readonly("fill_color", &pdfengine::TextRun::fillColor) - .def_readonly("para_id", &pdfengine::TextRun::paraId); + .def_readonly("para_id", &pdfengine::TextRun::paraId) + .def_readonly("font_fidelity", &pdfengine::TextRun::fontFidelity); py::class_(m, "TextLine") .def_readonly("runs", &pdfengine::TextLine::runs) @@ -474,6 +475,13 @@ PYBIND11_MODULE(pdfengine, m) { } return py::bytes(reinterpret_cast(res->data()), res->size()); }, py::arg("internal_font_id")) + .def("get_reconstructed_font_data", [](pdfengine::PdfDocument& self, const std::string& internal_font_id) { + auto res = self.getReconstructedFontData(internal_font_id); + if (!res || res->empty()) { + return py::bytes(); + } + return py::bytes(reinterpret_cast(res->data()), res->size()); + }, py::arg("internal_font_id")) .def("apply_edits", [](pdfengine::PdfDocument& self, const std::string& editsJson) { get_or_throw(self.applyEdits(editsJson)); }, py::arg("edits_json")) diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 6c7b183..2c50d0b 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -1,7 +1,3 @@ -# pdfengine — the C++23 PDF SDK core. -# - -# Generate the version header from the project version. configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/include/pdfengine/version.hpp.in" "${CMAKE_CURRENT_BINARY_DIR}/generated/pdfengine/version.hpp" @@ -34,6 +30,8 @@ add_library(pdfengine STATIC src/fonts/pdf_fonts/font_descriptor.cpp src/fonts/pdf_fonts/font_fallback.cpp src/fonts/pdf_fonts/font_subset.cpp + src/fonts/pdf_fonts/font_cmap_builder.cpp + src/fonts/pdf_fonts/embedded_font_reconstructor.cpp src/fonts/pdf_fonts/encoding/encoding.cpp src/fonts/pdf_fonts/encoding/tounicode_parser.cpp src/fonts/pdf_fonts/encoding/cjk_collection_db.cpp @@ -64,9 +62,6 @@ if(PDFENGINE_WITH_PDFIUM) target_compile_definitions(pdfengine PRIVATE PDFENGINE_WITH_PDFIUM) endif() -# Directory of the bundled fallback fonts (Carlito/Tinos). Under WASM they're embedded into the -# module's in-memory FS at /fonts (see wasm/CMakeLists.txt --embed-file); natively the engine reads -# them straight from the source assets dir. font_fallback.cpp prefers these over OS fonts. if(EMSCRIPTEN) target_compile_definitions(pdfengine PRIVATE PDFENGINE_FONT_DIR="/fonts") else() @@ -89,6 +84,7 @@ if(PDFENGINE_WITH_QPDF) endif() target_sources(pdfengine PRIVATE src/qpdf/qpdf_extractor.cpp + src/qpdf/qpdf_font_extractor.cpp src/qpdf/qpdf_writer.cpp src/qpdf/qpdf_resource_resolver.cpp src/core/image_decoder.cpp @@ -109,17 +105,10 @@ if(PDFENGINE_BUILD_TESTS) add_subdirectory(tests) endif() -# --- Fuzzing ----------------------------------------------------------------- -# Coverage-instrument the engine and build the libFuzzer harness. Requires Clang -# (enforced at the top level). PDFENGINE_FUZZ_SANITIZERS lets callers drop ASan -# when the PDFium static lib isn't ASan-compatible (coverage-only fuzzing). if(PDFENGINE_FUZZING) set(PDFENGINE_FUZZ_SANITIZERS "fuzzer,address,undefined" CACHE STRING "Sanitizer set for fuzzing (e.g. 'fuzzer,address,undefined' or just 'fuzzer')") - # Split the set into the libFuzzer driver ('fuzzer', linked only into the - # harness exe) and the runtime sanitizers (address/undefined/...), which must - # instrument the engine library itself to catch bugs in its code. string(REPLACE "," ";" _fuzz_sans "${PDFENGINE_FUZZ_SANITIZERS}") set(_fuzz_runtime_sans "") foreach(_s IN LISTS _fuzz_sans) @@ -129,8 +118,6 @@ if(PDFENGINE_FUZZING) endforeach() list(JOIN _fuzz_runtime_sans "," _fuzz_runtime_str) - # Coverage-instrument the engine; apply ASan/UBSan to it too so its own code - # is checked, not just the harness. target_compile_options(pdfengine PRIVATE -fsanitize=fuzzer-no-link -fno-omit-frame-pointer) if(_fuzz_runtime_str) target_compile_options(pdfengine PRIVATE -fsanitize=${_fuzz_runtime_str}) diff --git a/engine/include/pdfengine/pdf_document.hpp b/engine/include/pdfengine/pdf_document.hpp index 5bb2c0b..9d2d67a 100644 --- a/engine/include/pdfengine/pdf_document.hpp +++ b/engine/include/pdfengine/pdf_document.hpp @@ -118,6 +118,7 @@ struct Glyph { double bboxX = 0.0, bboxY = 0.0, bboxW = 0.0, bboxH = 0.0; double angle = 0.0; int pageObjectIndex = -1; + int srcIndex = 0; }; struct TextRun { @@ -133,6 +134,7 @@ struct TextRun { double x = 0.0, y = 0.0, w = 0.0, h = 0.0; std::string fillColor = "#000000"; std::string paraId; + std::string fontFidelity = "exact"; }; struct TextLine { @@ -243,9 +245,14 @@ public: [[nodiscard]] virtual std::expected, EngineError> getFonts(int startPage = 0, int endPage = -1) const = 0; - [[nodiscard]] virtual std::expected, EngineError> + [[nodiscard]] virtual std::expected, EngineError> getFontData(const std::string& internalFontId) const = 0; + [[nodiscard]] virtual std::expected, EngineError> + getReconstructedFontData(const std::string& internalFontId) { (void)internalFontId; return std::unexpected(EngineError::Unknown); } + + virtual void registerAuxFont(const std::string& internalFontId, const std::vector& sfnt) { (void)internalFontId; (void)sfnt; } + [[nodiscard]] virtual std::expected, std::string> getResolvedFont(const FontInfo& fontInfo) = 0; diff --git a/engine/src/fonts/face/font_face.cpp b/engine/src/fonts/face/font_face.cpp index d3325b5..f0d5e53 100644 --- a/engine/src/fonts/face/font_face.cpp +++ b/engine/src/fonts/face/font_face.cpp @@ -114,6 +114,20 @@ bool FontFace::coversUnicode(uint32_t codepoint) const { return gid != 0; } +std::vector FontFace::coveredCodepoints() const { + std::vector out; + if (!face_) return out; + std::lock_guard lock(*mutex_); + FT_CharMap prev = face_->charmap; + if (FT_Select_Charmap(face_, FT_ENCODING_UNICODE) == 0) { + FT_UInt gid = 0; + FT_ULong cp = FT_Get_First_Char(face_, &gid); + while (gid != 0) { out.push_back(static_cast(cp)); cp = FT_Get_Next_Char(face_, cp, &gid); } + } + if (prev) FT_Set_Charmap(face_, prev); + return out; +} + uint64_t FontFace::getId() const { return font_id_; } diff --git a/engine/src/fonts/face/font_face.hpp b/engine/src/fonts/face/font_face.hpp index 8794ca7..90937c8 100644 --- a/engine/src/fonts/face/font_face.hpp +++ b/engine/src/fonts/face/font_face.hpp @@ -32,6 +32,8 @@ public: bool coversUnicode(uint32_t codepoint) const; + std::vector coveredCodepoints() const; + std::optional renderGlyph(unsigned int glyphIndex, unsigned int fontSize); private: diff --git a/engine/src/fonts/pdf_fonts/embedded_font_reconstructor.cpp b/engine/src/fonts/pdf_fonts/embedded_font_reconstructor.cpp new file mode 100644 index 0000000..6ee276b --- /dev/null +++ b/engine/src/fonts/pdf_fonts/embedded_font_reconstructor.cpp @@ -0,0 +1,44 @@ +#include "fonts/pdf_fonts/embedded_font_reconstructor.hpp" + +#include + +#include "fonts/pdf_fonts/font_cmap_builder.hpp" + +namespace pdfengine::fonts::pdf_fonts { + +ReconstructedFont EmbeddedFontReconstructor::reconstruct( + const std::vector& program, + const std::unordered_map& codeToUnicode, + bool identityCidToGid, + const std::unordered_map& codeToGid) { + ReconstructedFont out; + if (program.size() < 12 || codeToUnicode.empty()) return out; + + std::map unicodeToGid; + for (const auto& [code, uni] : codeToUnicode) { + if (uni == 0) continue; + uint32_t gid; + if (identityCidToGid) { + gid = code; + } else { + auto it = codeToGid.find(code); + if (it == codeToGid.end()) continue; + gid = it->second; + } + if (gid == 0 || gid > 0xFFFF) continue; + unicodeToGid.emplace(uni, static_cast(gid)); + } + if (unicodeToGid.empty()) return out; + + std::vector cmap = FontCmapBuilder::buildCmapTable(unicodeToGid); + if (cmap.empty()) return out; + std::vector sfnt = FontCmapBuilder::spliceCmapIntoSfnt(program, cmap); + if (sfnt.empty()) return out; + + out.sfnt = std::move(sfnt); + for (const auto& [uni, gid] : unicodeToGid) out.coveredUnicode.insert(uni); + out.ok = true; + return out; +} + +} \ No newline at end of file diff --git a/engine/src/fonts/pdf_fonts/embedded_font_reconstructor.hpp b/engine/src/fonts/pdf_fonts/embedded_font_reconstructor.hpp new file mode 100644 index 0000000..3fccf7b --- /dev/null +++ b/engine/src/fonts/pdf_fonts/embedded_font_reconstructor.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include +#include + +namespace pdfengine::fonts::pdf_fonts { + +struct ReconstructedFont { + bool ok = false; + std::vector sfnt; + std::set coveredUnicode; +}; + +class EmbeddedFontReconstructor { +public: + static ReconstructedFont reconstruct(const std::vector& program, + const std::unordered_map& codeToUnicode, + bool identityCidToGid, + const std::unordered_map& codeToGid); +}; + +} \ No newline at end of file diff --git a/engine/src/fonts/pdf_fonts/font_cmap_builder.cpp b/engine/src/fonts/pdf_fonts/font_cmap_builder.cpp new file mode 100644 index 0000000..22803a4 --- /dev/null +++ b/engine/src/fonts/pdf_fonts/font_cmap_builder.cpp @@ -0,0 +1,202 @@ +#include "fonts/pdf_fonts/font_cmap_builder.hpp" + +#include +#include + +namespace pdfengine::fonts::pdf_fonts { + +namespace { + +void put16(std::vector& b, uint16_t v) { + b.push_back(static_cast(v >> 8)); + b.push_back(static_cast(v & 0xFF)); +} +void put32(std::vector& b, uint32_t v) { + b.push_back(static_cast(v >> 24)); + b.push_back(static_cast((v >> 16) & 0xFF)); + b.push_back(static_cast((v >> 8) & 0xFF)); + b.push_back(static_cast(v & 0xFF)); +} +uint16_t read16(const std::vector& b, size_t o) { + return static_cast((b[o] << 8) | b[o + 1]); +} +uint32_t read32(const std::vector& b, size_t o) { + return (static_cast(b[o]) << 24) | (static_cast(b[o + 1]) << 16) | + (static_cast(b[o + 2]) << 8) | static_cast(b[o + 3]); +} + +void pow2le(uint32_t n, uint32_t& powTimes16, uint32_t& sel) { + uint32_t p = 1, s = 0; + while (p * 2 <= n) { p *= 2; ++s; } + powTimes16 = p; sel = s; +} + +uint32_t tableChecksum(const uint8_t* data, size_t len) { + uint32_t sum = 0; + size_t i = 0; + for (; i + 4 <= len; i += 4) + sum += (static_cast(data[i]) << 24) | (static_cast(data[i + 1]) << 16) | + (static_cast(data[i + 2]) << 8) | static_cast(data[i + 3]); + if (i < len) { // tail < 4 bytes, pad with zeros + uint32_t last = 0; + for (size_t j = 0; j < 4; ++j) last = (last << 8) | (i + j < len ? data[i + j] : 0); + sum += last; + } + return sum; +} + +std::vector buildFormat4(const std::vector>& bmp) { + std::vector endC, startC, idDelta, idRange; + for (const auto& [u, g] : bmp) { + if (u == 0xFFFF) continue; + startC.push_back(u); + endC.push_back(u); + idDelta.push_back(static_cast((static_cast(g) - static_cast(u)) & 0xFFFF)); + idRange.push_back(0); + } + startC.push_back(0xFFFF); endC.push_back(0xFFFF); idDelta.push_back(1); idRange.push_back(0); + + uint16_t segCount = static_cast(endC.size()); + uint32_t srPow, sel; pow2le(segCount, srPow, sel); + uint16_t segX2 = static_cast(segCount * 2); + uint16_t searchRange = static_cast(srPow * 2); + uint16_t rangeShift = static_cast(segX2 - searchRange); + + std::vector t; + put16(t, 4); + put16(t, 0); + put16(t, 0); + put16(t, segX2); + put16(t, searchRange); + put16(t, static_cast(sel)); + put16(t, rangeShift); + for (auto v : endC) put16(t, v); + put16(t, 0); + for (auto v : startC) put16(t, v); + for (auto v : idDelta) put16(t, v); + for (auto v : idRange) put16(t, v); + t[2] = static_cast(t.size() >> 8); + t[3] = static_cast(t.size() & 0xFF); + return t; +} + +std::vector buildFormat12(const std::map& all) { + std::vector t; + put16(t, 12); put16(t, 0); + put32(t, 0); + put32(t, 0); + put32(t, static_cast(all.size())); + for (const auto& [u, g] : all) { + put32(t, u); put32(t, u); put32(t, g); + } + uint32_t len = static_cast(t.size()); + t[4] = static_cast(len >> 24); t[5] = static_cast((len >> 16) & 0xFF); + t[6] = static_cast((len >> 8) & 0xFF); t[7] = static_cast(len & 0xFF); + return t; +} + +} + +std::vector FontCmapBuilder::buildCmapTable(const std::map& unicodeToGid) { + if (unicodeToGid.empty()) return {}; + std::vector> bmp; + bool hasSupp = false; + for (const auto& [u, g] : unicodeToGid) { + if (u <= 0xFFFF) bmp.emplace_back(static_cast(u), g); + else hasSupp = true; + } + + std::vector sub4 = bmp.empty() ? std::vector{} : buildFormat4(bmp); + std::vector sub12 = hasSupp ? buildFormat12(unicodeToGid) : std::vector{}; + + uint16_t numTables = static_cast((sub4.empty() ? 0 : 1) + (sub12.empty() ? 0 : 1)); + if (numTables == 0) return {}; + + std::vector out; + put16(out, 0); + put16(out, numTables); + uint32_t recBase = 4; + uint32_t dataBase = recBase + numTables * 8u; + uint32_t off4 = dataBase; + uint32_t off12 = dataBase + static_cast(sub4.size()); + if (!sub4.empty()) { put16(out, 3); put16(out, 1); put32(out, off4); } + if (!sub12.empty()) { put16(out, 3); put16(out, 10); put32(out, off12); } + out.insert(out.end(), sub4.begin(), sub4.end()); + out.insert(out.end(), sub12.begin(), sub12.end()); + return out; +} + +std::vector FontCmapBuilder::spliceCmapIntoSfnt(const std::vector& sfnt, + const std::vector& cmapTable) { + if (sfnt.size() < 12 || cmapTable.empty()) return {}; + uint32_t sfntVersion = read32(sfnt, 0); + uint16_t numTables = read16(sfnt, 4); + if (12u + static_cast(numTables) * 16u > sfnt.size()) return {}; + + struct Tab { uint32_t tag; std::vector data; }; + std::vector tabs; + bool hadCmap = false; + for (uint16_t i = 0; i < numTables; ++i) { + size_t rec = 12 + static_cast(i) * 16; + uint32_t tag = read32(sfnt, rec); + uint32_t off = read32(sfnt, rec + 8); + uint32_t len = read32(sfnt, rec + 12); + if (static_cast(off) + len > sfnt.size()) return {}; + Tab t; t.tag = tag; + if (tag == 0x636D6170u /* 'cmap' */) { t.data = cmapTable; hadCmap = true; } + else t.data.assign(sfnt.begin() + off, sfnt.begin() + off + len); + tabs.push_back(std::move(t)); + } + if (!hadCmap) tabs.push_back({0x636D6170u, cmapTable}); + + std::sort(tabs.begin(), tabs.end(), [](const Tab& a, const Tab& b) { return a.tag < b.tag; }); + + const uint16_t n = static_cast(tabs.size()); + const uint32_t headerSize = 12u + static_cast(n) * 16u; + + std::vector offsets(n), lengths(n), checksums(n); + uint32_t cursor = headerSize; + int headIdx = -1; + for (uint16_t i = 0; i < n; ++i) { + offsets[i] = cursor; + lengths[i] = static_cast(tabs[i].data.size()); + while (tabs[i].data.size() % 4 != 0) tabs[i].data.push_back(0); + if (tabs[i].tag == 0x68656164u /* 'head' */) { + headIdx = i; + if (tabs[i].data.size() >= 12) { // zero checkSumAdjustment (offset 8) + tabs[i].data[8] = tabs[i].data[9] = tabs[i].data[10] = tabs[i].data[11] = 0; + } + } + checksums[i] = tableChecksum(tabs[i].data.data(), tabs[i].data.size()); + cursor += static_cast(tabs[i].data.size()); + } + + uint32_t srPow, sel; pow2le(n, srPow, sel); + std::vector out; + put32(out, sfntVersion); + put16(out, n); + put16(out, static_cast(srPow * 16)); + put16(out, static_cast(sel)); + put16(out, static_cast(n * 16 - srPow * 16)); + for (uint16_t i = 0; i < n; ++i) { + put32(out, tabs[i].tag); + put32(out, checksums[i]); + put32(out, offsets[i]); + put32(out, lengths[i]); + } + for (uint16_t i = 0; i < n; ++i) + out.insert(out.end(), tabs[i].data.begin(), tabs[i].data.end()); + + if (headIdx >= 0) { + uint32_t whole = tableChecksum(out.data(), out.size()); + uint32_t adj = 0xB1B0AFBAu - whole; + size_t pos = offsets[headIdx] + 8; + out[pos] = static_cast(adj >> 24); + out[pos + 1] = static_cast((adj >> 16) & 0xFF); + out[pos + 2] = static_cast((adj >> 8) & 0xFF); + out[pos + 3] = static_cast(adj & 0xFF); + } + return out; +} + +} \ No newline at end of file diff --git a/engine/src/fonts/pdf_fonts/font_cmap_builder.hpp b/engine/src/fonts/pdf_fonts/font_cmap_builder.hpp new file mode 100644 index 0000000..fd1a95d --- /dev/null +++ b/engine/src/fonts/pdf_fonts/font_cmap_builder.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include + +namespace pdfengine::fonts::pdf_fonts { + +class FontCmapBuilder { +public: + static std::vector buildCmapTable(const std::map& unicodeToGid); + + static std::vector spliceCmapIntoSfnt(const std::vector& sfnt, + const std::vector& cmapTable); +}; + +} \ No newline at end of file diff --git a/engine/src/fonts/pdf_fonts/font_fallback.cpp b/engine/src/fonts/pdf_fonts/font_fallback.cpp index b28f74c..4125f90 100644 --- a/engine/src/fonts/pdf_fonts/font_fallback.cpp +++ b/engine/src/fonts/pdf_fonts/font_fallback.cpp @@ -135,6 +135,21 @@ std::string FontFallback::getFallbackFontPath(const std::string& fontName, bool stylePattern += "-italic"; } + #if defined(_WIN32) + { + auto has = [&](const char* s) { return lowerName.find(s) != std::string::npos; }; + const bool serif = has("times") || has("serif") || has("roman") || has("georgia") || + has("garamond") || has("minion") || has("cambria") || has("tinos") || + has("book antiqua") || has("palatino"); + const std::string sysBase = serif ? "times" : "arial"; + const std::string sysSfx = (bold && italic) ? "bi" : bold ? "bd" : italic ? "i" : ""; + const std::string sysPath = "C:\\Windows\\Fonts\\" + sysBase + sysSfx + ".ttf"; + if (std::filesystem::exists(sysPath)) { + return sysPath; + } + } + #endif + std::lock_guard lock(rules_mutex_); for (const auto& rule : custom_rules_) { diff --git a/engine/src/parser/pdfium_document.cpp b/engine/src/parser/pdfium_document.cpp index 46ccddb..2a519e0 100644 --- a/engine/src/parser/pdfium_document.cpp +++ b/engine/src/parser/pdfium_document.cpp @@ -20,6 +20,7 @@ #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" @@ -208,6 +209,22 @@ std::string makeInternalFontId(const pdfengine::FontInfo& f) { 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('_'); @@ -1045,7 +1062,8 @@ std::expected PdfiumPage::extractDocumentModel() const { 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); @@ -1117,7 +1135,8 @@ std::expected PdfiumPage::extractDocumentModel() const { for (auto& line : lines) { std::sort(line.glyphs.begin(), line.glyphs.end(), [](const Glyph& a, const Glyph& b) { - return a.originX < b.originX; + if (a.originX != b.originX) return a.originX < b.originX; + return a.srcIndex < b.srcIndex; }); std::vector gaps; @@ -1133,6 +1152,17 @@ std::expected PdfiumPage::extractDocumentModel() const { 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]; @@ -1143,6 +1173,7 @@ std::expected PdfiumPage::extractDocumentModel() const { 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; @@ -1152,9 +1183,10 @@ std::expected PdfiumPage::extractDocumentModel() const { const auto& currG = line.glyphs[i]; double gap = currG.bboxX - (prevG.bboxX + prevG.bboxW); - double spaceThreshold = currG.fontSize * 0.2; + double em = (std::max)(static_cast(currG.fontSize), lineEm); + double spaceThreshold = em * 0.2; if (p25Gap > spaceThreshold) { - spaceThreshold = (std::min)(p25Gap * 1.5, currG.fontSize * 0.38); + spaceThreshold = (std::min)(p25Gap * 1.5, em * 0.38); } bool addSpace = gap > spaceThreshold && prevG.text != " " && currG.text != " "; @@ -1192,6 +1224,7 @@ std::expected PdfiumPage::extractDocumentModel() const { 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); @@ -1211,6 +1244,7 @@ std::expected PdfiumPage::extractDocumentModel() const { currentRun.internalFontId = it->second.internalFontId; currentRun.isEmbedded = it->second.isEmbedded; currentRun.type = it->second.type; + currentRun.fontFidelity = classifyFontFidelity(it->second); } } @@ -1236,21 +1270,35 @@ std::expected PdfiumPage::extractDocumentModel() const { } 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); - - if (vGap > fontSize * 1.5) { + + bool styleChanged = lineStyleKey(currLine) != lineStyleKey(prevLine); + if (vGap > fontSize * 1.5 || styleChanged) { paragraphs.push_back(std::move(currentPara)); currentPara = Paragraph(); } @@ -1322,17 +1370,18 @@ std::expected PdfiumPage::extractDocumentModel() const { }; auto computeRunColor = [&](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) return; - 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); + 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; } }; @@ -2078,6 +2127,69 @@ 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, @@ -2148,7 +2260,7 @@ PdfiumDocument::EmissionFont PdfiumDocument::loadEmissionFont( cacheKey += "#" + std::to_string(h); } - if (useEmbedded && reuseFont) { + if (useEmbedded && reuseFont && !isSubsetFont) { out.font = reuseFont; if (auto perObj = getFontDataFromObjects(pageIndex, srcObjects, internalFontId); perObj.has_value() && !perObj->empty()) { @@ -2168,6 +2280,35 @@ PdfiumDocument::EmissionFont PdfiumDocument::loadEmissionFont( } 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); @@ -2178,9 +2319,8 @@ PdfiumDocument::EmissionFont PdfiumDocument::loadEmissionFont( sourceBytes = std::move(fontDataRes.value()); } if (!sourceBytes.empty()) { - auto subset = fonts::pdf_fonts::FontSubset::buildSubsetByUnicode(sourceBytes, codepoints); std::lock_guard lock(loadedFontsMutex_); - loadedFontDataBuffers_[cacheKey] = !subset.empty() ? std::move(subset) : std::move(sourceBytes); + 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); } @@ -2191,9 +2331,8 @@ PdfiumDocument::EmissionFont PdfiumDocument::loadEmissionFont( if (fs) { std::vector fileBytes((std::istreambuf_iterator(fs)), std::istreambuf_iterator()); if (!fileBytes.empty()) { - std::vector subsetBytes = fonts::pdf_fonts::FontSubset::buildSubsetByUnicode(fileBytes, codepoints); std::lock_guard lock(loadedFontsMutex_); - loadedFontDataBuffers_[cacheKey] = !subsetBytes.empty() ? std::move(subsetBytes) : std::move(fileBytes); + 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); } @@ -2248,6 +2387,7 @@ std::expected PdfiumDocument::applyEdits(const std::string& e 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"]; @@ -2261,6 +2401,7 @@ std::expected PdfiumDocument::applyEdits(const std::string& e 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"]) { @@ -2505,13 +2646,31 @@ std::expected PdfiumDocument::applyEdits(const std::string& e 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 (hasOrigBounds && std::abs(deltaX) > 0.001) { + 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; @@ -2576,6 +2735,32 @@ std::expected PdfiumDocument::applyEdits(const std::string& e } } + 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); @@ -2598,18 +2783,20 @@ std::expected PdfiumDocument::applyEdits(const std::string& e if (fs) { std::vector fileBytes((std::istreambuf_iterator(fs)), std::istreambuf_iterator()); if (!fileBytes.empty()) { - std::vector subsetBytes = - fonts::pdf_fonts::FontSubset::buildSubsetByUnicode(fileBytes, unicodeCodepoints); + // 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] = - (!subsetBytes.empty()) ? std::move(subsetBytes) : std::move(fileBytes); + 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 {} CID system font '{}' ({} -> {} bytes) from '{}'", - bytes.size() < fullSize ? "SUBSET" : "FULL", - matchedFontInfo->fontName, fullSize, bytes.size(), fontPath); + spdlog::info("Font Engine: Embedded FULL CID system font '{}' ({} bytes) from '{}'", + matchedFontInfo->fontName, fullSize, fontPath); } } } else { @@ -2632,16 +2819,164 @@ std::expected PdfiumDocument::applyEdits(const std::string& e } if (font) { - 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, a, b, c, d, e, f); - - FPDFPage_InsertObjectAtIndex(page, newTextObj, minIndex); + 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 { - spdlog::error("Failed to create new text object"); + 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"); + } + } } } @@ -2788,6 +3123,46 @@ std::expected PdfiumDocument::applyEdits(const std::string& e 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); @@ -4332,7 +4707,7 @@ std::expected, std::string> PdfiumDocume } std::expected PdfiumPage::extractDisplayListJson() const { -#ifdef PDFENGINE_WITH_PDFIUM +#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); diff --git a/engine/src/parser/pdfium_document.hpp b/engine/src/parser/pdfium_document.hpp index af08d35..5511971 100644 --- a/engine/src/parser/pdfium_document.hpp +++ b/engine/src/parser/pdfium_document.hpp @@ -13,6 +13,7 @@ #include #include #include +#include "fonts/pdf_fonts/embedded_font_reconstructor.hpp" namespace pdfengine::fonts::loader { class FontResolver; } namespace pdfengine::fonts { class FontFace; } @@ -146,6 +147,15 @@ private: double fontSize, const std::vector& codepoints, const std::vector& srcObjects = {}, FPDF_FONT reuseFont = nullptr); + + mutable std::unordered_map reconstructedFonts_; + mutable std::mutex reconstructedFontsMutex_; + void registerAuxFont(const std::string& internalFontId, const std::vector& sfnt) override; + std::expected, EngineError> getReconstructedFontData(const std::string& internalFontId) override; + const fonts::pdf_fonts::ReconstructedFont* lookupReconFont(const std::string& internalFontId); +#ifdef PDFENGINE_WITH_QPDF + const fonts::pdf_fonts::ReconstructedFont* getReconstructedEmbeddedFont(const std::string& internalFontId); +#endif #endif }; diff --git a/engine/src/qpdf/qpdf_font_extractor.cpp b/engine/src/qpdf/qpdf_font_extractor.cpp new file mode 100644 index 0000000..e52d04f --- /dev/null +++ b/engine/src/qpdf/qpdf_font_extractor.cpp @@ -0,0 +1,107 @@ +#include "qpdf_font_extractor.hpp" + +#include "fonts/pdf_fonts/encoding/tounicode_parser.hpp" + +#ifdef PDFENGINE_WITH_QPDF +#include +#include +#include +#include +#include +#endif + +#include + +namespace pdfengine::qpdf_layer { + +#ifdef PDFENGINE_WITH_QPDF +namespace { + +std::string decodeStream(QPDFObjectHandle stream) { + try { + Pl_Buffer pl("tounicode"); + stream.pipeStreamData(&pl, 0, qpdf_dl_all, false); + auto buf = pl.getBufferSharedPointer(); + if (buf) return std::string(reinterpret_cast(buf->getBuffer()), buf->getSize()); + } catch (...) {} + return {}; +} + +bool baseFontMatches(QPDFObjectHandle font, const std::string& want) { + if (!font.isDictionary() || !font.hasKey("/BaseFont")) return false; + QPDFObjectHandle bf = font.getKey("/BaseFont"); + if (!bf.isName()) return false; + std::string n = bf.getName(); + if (!n.empty() && n[0] == '/') n.erase(0, 1); + return n == want; +} + +} +#endif + +std::expected +QpdfFontExtractor::extractMapping(const std::vector& pdf, const std::string& baseFontName) const { +#ifdef PDFENGINE_WITH_QPDF + try { + ::QPDF qpdf; + qpdf.processMemoryFile("memory", reinterpret_cast(pdf.data()), pdf.size()); + + for (QPDFObjectHandle obj : qpdf.getAllObjects()) { + if (!obj.isDictionary()) continue; + if (!obj.hasKey("/Type") || !obj.getKey("/Type").isName() || + obj.getKey("/Type").getName() != "/Font") + continue; + if (!baseFontMatches(obj, baseFontName)) continue; + + EmbeddedFontMapping m; + m.subtype = obj.hasKey("/Subtype") && obj.getKey("/Subtype").isName() + ? obj.getKey("/Subtype").getName() : ""; + + if (obj.hasKey("/ToUnicode") && obj.getKey("/ToUnicode").isStream()) { + std::string s = decodeStream(obj.getKey("/ToUnicode")); + if (!s.empty() && fonts::pdf_fonts::ToUnicodeParser::parse(s, m.codeToUnicode)) + m.hasToUnicode = !m.codeToUnicode.empty(); + } + + if (m.subtype == "/Type0" && obj.hasKey("/DescendantFonts")) { + QPDFObjectHandle df = obj.getKey("/DescendantFonts"); + QPDFObjectHandle cid = df.isArray() && df.getArrayNItems() > 0 ? df.getArrayItem(0) + : QPDFObjectHandle(); + if (cid.isDictionary() && cid.hasKey("/CIDToGIDMap")) { + QPDFObjectHandle c2g = cid.getKey("/CIDToGIDMap"); + if (c2g.isStream()) { + std::string s = decodeStream(c2g); + m.identityCidToGid = false; + for (size_t i = 0; i + 1 < s.size(); i += 2) { + uint16_t gid = static_cast((static_cast(s[i]) << 8) | + static_cast(s[i + 1])); + if (gid != 0) m.codeToGid[static_cast(i / 2)] = gid; + } + } else { + m.identityCidToGid = true; + } + } else { + m.identityCidToGid = true; + } + } else { + m.identityCidToGid = true; + } + + m.ok = m.hasToUnicode; + return m; + } + return std::unexpected(QpdfError::Unknown); + } catch (const QPDFExc& e) { + fprintf(stderr, "QpdfFontExtractor QPDFExc: %s\n", e.what()); + return std::unexpected(QpdfError::InvalidFormat); + } catch (const std::exception& e) { + fprintf(stderr, "QpdfFontExtractor exception: %s\n", e.what()); + return std::unexpected(QpdfError::Unknown); + } +#else + (void)pdf; (void)baseFontName; + return std::unexpected(QpdfError::NotSupported); +#endif +} + +} \ No newline at end of file diff --git a/engine/src/qpdf/qpdf_font_extractor.hpp b/engine/src/qpdf/qpdf_font_extractor.hpp new file mode 100644 index 0000000..c6e85ad --- /dev/null +++ b/engine/src/qpdf/qpdf_font_extractor.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "qpdf_extractor.hpp" + +namespace pdfengine::qpdf_layer { + +struct EmbeddedFontMapping { + bool ok = false; + bool hasToUnicode = false; + std::unordered_map codeToUnicode; + bool identityCidToGid = true; + std::unordered_map codeToGid; + std::string subtype; +}; + +class QpdfFontExtractor { +public: + std::expected + extractMapping(const std::vector& pdf, const std::string& baseFontName) const; +}; + +} \ No newline at end of file diff --git a/engine/tests/fixtures/custom-font.pdf b/engine/tests/fixtures/custom-font.pdf new file mode 100644 index 0000000..477eac2 Binary files /dev/null and b/engine/tests/fixtures/custom-font.pdf differ diff --git a/frontend/public/pdfium-engine.mjs b/frontend/public/pdfium-engine.mjs index c91e7c3..4e1ce70 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["S"]();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 _emscripten_get_heap_max=()=>getHeapMax();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,_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["U"];_pageCount=Module["_pageCount"]=wasmExports["V"];_renderPagePng=Module["_renderPagePng"]=wasmExports["W"];_previewRender=Module["_previewRender"]=wasmExports["X"];_previewRenderRegion=Module["_previewRenderRegion"]=wasmExports["Y"];_previewRenderPaginated=Module["_previewRenderPaginated"]=wasmExports["Z"];_lastRegionCount=Module["_lastRegionCount"]=wasmExports["_"];_lastRegionPtr=Module["_lastRegionPtr"]=wasmExports["$"];_lastRegionW=Module["_lastRegionW"]=wasmExports["aa"];_lastRegionH=Module["_lastRegionH"]=wasmExports["ba"];_lastRegionPage=Module["_lastRegionPage"]=wasmExports["ca"];_lastRegionYTop=Module["_lastRegionYTop"]=wasmExports["da"];_lastRenderPtr=Module["_lastRenderPtr"]=wasmExports["ea"];_lastRenderW=Module["_lastRenderW"]=wasmExports["fa"];_lastRenderH=Module["_lastRenderH"]=wasmExports["ga"];_lastLayoutJson=Module["_lastLayoutJson"]=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["R"];__indirect_function_table=wasmTable=wasmExports["T"]}var wasmImports={a:___cxa_throw,i:___syscall_fcntl64,J:___syscall_fstat64,x:___syscall_getdents64,N:___syscall_ioctl,G:___syscall_lstat64,H:___syscall_newfstatat,k:___syscall_openat,v:___syscall_rmdir,I:___syscall_stat64,w:___syscall_unlinkat,L:__abort_js,O:__emscripten_fs_load_embedded_files,s:__emscripten_throw_longjmp,A:__gmtime_js,B:__localtime_js,y:__mmap_js,z:__munmap_js,M:__tzset_js,K:_clock_time_get,l:_emscripten_date_now,u:_emscripten_get_heap_max,t:_emscripten_resize_heap,E:_environ_get,F:_environ_sizes_get,P:_exit,g:_fd_close,D:_fd_fdstat_get,n:_fd_read,C:_fd_seek,m:_fd_write,d:invoke_ii,f:invoke_iii,h:invoke_iiii,j:invoke_iiiii,Q: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["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})} ;return moduleRtn}export default Module; diff --git a/frontend/public/pdfium-engine.wasm b/frontend/public/pdfium-engine.wasm index 86bee67..b0a0545 100644 Binary files a/frontend/public/pdfium-engine.wasm and b/frontend/public/pdfium-engine.wasm differ diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index cc37359..ec46732 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -295,7 +295,7 @@ function App() { setActiveTool('select'); }; - const handleEditText = (pageIndex: number, run: EditableRun, newText: string) => { + const handleEditText = (pageIndex: number, run: EditableRun, newText: string, disableJustify?: boolean) => { if (!can('canModify')) { denyToast('Editing text'); setActiveTool('select'); return; } applyOps([{ id: rid('edit'), type: 'replace_text', pageIndex, @@ -304,6 +304,7 @@ function App() { text: newText, internalFontId: run.internalFontId, fontSize: run.fontSize, + disableJustify: !!disableJustify, }, }], 'Text updated'); setActiveTool('select'); diff --git a/frontend/src/lib/colorUtils.ts b/frontend/src/lib/colorUtils.ts new file mode 100644 index 0000000..0a18d95 --- /dev/null +++ b/frontend/src/lib/colorUtils.ts @@ -0,0 +1,20 @@ +const NEAR_WHITE_THRESHOLD = 0xf0; + +function parseHex(color: string): [number, number, number] | null { + const m = /^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.exec(color.trim()); + if (!m) return null; + let h = m[1]; + if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2]; + return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)]; +} + +export function sanitizeTextColor(color: unknown, fallback = '#000000'): string { + if (typeof color !== 'string') return fallback; + const rgb = parseHex(color); + if (!rgb) return fallback; + const [r, g, b] = rgb; + if (r >= NEAR_WHITE_THRESHOLD && g >= NEAR_WHITE_THRESHOLD && b >= NEAR_WHITE_THRESHOLD) { + return '#000000'; + } + return color; +} \ No newline at end of file diff --git a/frontend/src/lib/gatewayService.ts b/frontend/src/lib/gatewayService.ts index beedd58..600706a 100644 --- a/frontend/src/lib/gatewayService.ts +++ b/frontend/src/lib/gatewayService.ts @@ -237,6 +237,7 @@ export interface ReplaceTextData { text: string; internalFontId: string; fontSize: number; + disableJustify?: boolean; } export interface ReflowFragment { @@ -465,6 +466,18 @@ class GatewayService { } } + async getReconstructedFontData(documentId: string, internalFontId: string): Promise { + try { + const url = `${this.baseUrl}/documents/${documentId}/font-reconstructed?internal_font_id=${encodeURIComponent(internalFontId)}`; + const response = await fetch(url); + if (!response.ok || response.status === 204) return null; + const buf = await response.arrayBuffer(); + return buf.byteLength ? buf : null; + } catch { + return null; + } + } + async applyEdits(documentId: string, operations: EditOperation[]): Promise<{ success: boolean; newDocumentId: string }> { let response: Response; try { diff --git a/frontend/src/lib/pdfiumEngine.ts b/frontend/src/lib/pdfiumEngine.ts index 68d7ea7..f119155 100644 --- a/frontend/src/lib/pdfiumEngine.ts +++ b/frontend/src/lib/pdfiumEngine.ts @@ -1,3 +1,5 @@ +import { gatewayService } from './gatewayService'; + interface PdfiumModule { _malloc(n: number): number; _free(p: number): void; @@ -16,7 +18,7 @@ function getModule(): Promise { if (!modulePromise) { modulePromise = (async () => { try { - const V = '20260618f-allfonts'; + const V = '20260625-tier2b'; 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' })); @@ -63,6 +65,33 @@ export function wasmHasDocument(documentId: string): boolean { return docHandles.has(documentId); } +const auxFontEnsured = new Map>(); +export async function wasmRegisterAuxFont(documentId: string, internalFontId: string, bytes: ArrayBuffer): Promise { + const M = await getModule(); + if (!M) return; + const h = docHandles.get(documentId); + if (h === undefined) return; + const u8 = new Uint8Array(bytes); + const ptr = M._malloc(u8.length); + M.HEAPU8.set(u8, ptr); + M.ccall('registerAuxFont', null, ['number', 'string', 'number', 'number'], [h, internalFontId, ptr, u8.length]); + M._free(ptr); +} + +export function wasmEnsureAuxFont(documentId: string, internalFontId: string): Promise { + if (!internalFontId) return Promise.resolve(); + const key = `${documentId}::${internalFontId}`; + let p = auxFontEnsured.get(key); + if (!p) { + p = (async () => { + const bytes = await gatewayService.getReconstructedFontData(documentId, internalFontId); + if (bytes && bytes.byteLength) await wasmRegisterAuxFont(documentId, internalFontId, bytes); + })().catch(() => {}); + auxFontEnsured.set(key, p); + } + return p; +} + function lastRenderBlob(M: PdfiumModule, len: number): Blob | null { if (len <= 0) return null; const ptr = M.ccall('lastRenderPtr', 'number', [], []) as number; diff --git a/frontend/src/viewer/PDFViewer.tsx b/frontend/src/viewer/PDFViewer.tsx index 2bdd310..a8a835e 100644 --- a/frontend/src/viewer/PDFViewer.tsx +++ b/frontend/src/viewer/PDFViewer.tsx @@ -38,7 +38,7 @@ interface PDFViewerProps { onPageVisible?: (pageIndex: number) => void; onRedactArea?: (pageIndex: number, bounds: Rect) => void; onPlaceText?: (pageIndex: number, rectPts: Rect, text: string) => void; - onEditText?: (pageIndex: number, run: EditableRun, newText: string) => void; + onEditText?: (pageIndex: number, run: EditableRun, newText: string, disableJustify?: boolean) => void; onReflowParagraph?: (pageIndex: number, payload: ReflowParagraphPayload) => void; onStreamDocumentChanged?: (newDocumentId: string) => void; onPlaceStamp?: (pageIndex: number, pointPts: { x: number; y: number }) => void; @@ -98,6 +98,8 @@ export const PDFViewer = React.forwardRef(({ const [pageTexts, setPageTexts] = useState>({}); const renderedDocIdRef = useRef(''); const prevDocumentIdRef = useRef(documentId); + const inFlightRenderRef = useRef>(new Set()); + const inFlightTextRef = useRef>(new Set()); useEffect(() => { setPageTexts({}); const prev = prevDocumentIdRef.current; @@ -225,63 +227,70 @@ export const PDFViewer = React.forwardRef(({ }, [primaryVisiblePage, onPageVisible]); useEffect(() => { - let active = true; const docChanged = renderedDocIdRef.current !== documentId; const fetchPageImages = async () => { - const missingPages = docChanged + const base = docChanged ? visiblePages : visiblePages.filter((page) => !renderedPages[page.index]); + const missingPages = base.filter((page) => !inFlightRenderRef.current.has(page.index)); if (missingPages.length === 0) return; + missingPages.forEach((page) => inFlightRenderRef.current.add(page.index)); - const renders = await Promise.all( - missingPages.map(async (page) => { - const url = await gatewayService.renderPage({ - documentId, - pageIndex: page.index, - zoom, - rotation: 0, + try { + const renders = await Promise.all( + missingPages.map(async (page) => { + const url = await gatewayService.renderPage({ + documentId, + pageIndex: page.index, + zoom, + rotation: 0, + }); + return { index: page.index, url }; + }) + ); + + if (documentIdRef.current !== documentId) return; + + renderedDocIdRef.current = documentId; + setRenderedPages((prev) => { + const next = docChanged ? [] : [...prev]; + renders.forEach(({ index, url }) => { + next[index] = url; }); - return { index: page.index, url }; - }) - ); - - if (!active) return; - - renderedDocIdRef.current = documentId; - setRenderedPages((prev) => { - const next = docChanged ? [] : [...prev]; - renders.forEach(({ index, url }) => { - next[index] = url; + return next; }); - return next; - }); + } finally { + missingPages.forEach((page) => inFlightRenderRef.current.delete(page.index)); + } }; fetchPageImages(); - return () => { - active = false; - }; }, [visiblePages, documentId, zoom, renderedPages]); const textToolActive = activeTool === 'select' || activeTool === 'highlight' || activeTool === 'underline' || activeTool === 'strikeout' || activeTool === 'squiggly'; useEffect(() => { if (!textToolActive || !documentId) return; - let active = true; const fetchTexts = async () => { - const missing = visiblePages.filter((p) => !(p.index in pageTexts)); + const missing = visiblePages + .filter((p) => !(p.index in pageTexts)) + .filter((p) => !inFlightTextRef.current.has(p.index)); if (missing.length === 0) return; - const results = await Promise.all( - missing.map(async (p) => ({ index: p.index, page: await gatewayService.getPageText(documentId, p.index) })), - ); - if (!active) return; - setPageTexts((prev) => { - const next = { ...prev }; - results.forEach(({ index, page }) => { next[index] = page.glyphs; }); - return next; - }); + missing.forEach((p) => inFlightTextRef.current.add(p.index)); + try { + const results = await Promise.all( + missing.map(async (p) => ({ index: p.index, page: await gatewayService.getPageText(documentId, p.index) })), + ); + if (documentIdRef.current !== documentId) return; + setPageTexts((prev) => { + const next = { ...prev }; + results.forEach(({ index, page }) => { next[index] = page.glyphs; }); + return next; + }); + } finally { + missing.forEach((p) => inFlightTextRef.current.delete(p.index)); + } }; fetchTexts(); - return () => { active = false; }; }, [textToolActive, visiblePages, documentId, pageTexts]); const handleTextSelection = (text: string, bbox: Rect, lines: Rect[], pageIndex: number) => { diff --git a/frontend/src/viewer/ParagraphEditor.tsx b/frontend/src/viewer/ParagraphEditor.tsx index ae34d52..d9281e9 100644 --- a/frontend/src/viewer/ParagraphEditor.tsx +++ b/frontend/src/viewer/ParagraphEditor.tsx @@ -1,12 +1,13 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { gatewayService } from '../lib/gatewayService'; -import { wasmLoadDocument, wasmHasDocument, wasmPreviewRenderPaginated } from '../lib/pdfiumEngine'; +import { wasmLoadDocument, wasmHasDocument, wasmPreviewRenderPaginated, wasmEnsureAuxFont } from '../lib/pdfiumEngine'; import type { ReflowLayout } from '../lib/pdfiumEngine'; export interface OverflowPreviewRegion { pageIndex: number; yTopPt: number; dataUrl: string; } export interface OverflowCaret { pageIndex: number; left: number; top: number; height: number; } import type { ReflowParagraphPayload, CommitFrame } from './TextEditLayer'; import type { ReflowFragment } from '../lib/gatewayService'; +import { sanitizeTextColor } from '../lib/colorUtils'; interface SeedRun { text: string; fid: string; size: number; color: string; fontName: string; advances?: number[]; } interface OrigLine { frags: { text: string; fid: string; size: number; color: string; advances?: number[] }[]; x: number; baselineY: number; } @@ -66,8 +67,21 @@ function median(xs: number[]): number { const s = [...xs].sort((a, b) => a - b); return s[Math.floor(s.length / 2)]; } +function paraEffSize(lines: any[]): number { + const heights: number[] = []; + let nominal = 0; + for (const line of lines) for (const r of (line.runs ?? [])) { + nominal = Math.max(nominal, r.font_size ?? 0); + for (const g of (r.glyphs ?? [])) if ((g.bbox_h ?? 0) > 0.1) heights.push(g.bbox_h); + } + if (!heights.length) return nominal || 12; + heights.sort((a, b) => a - b); + const p75 = heights[Math.floor(heights.length * 0.75)]; + return Math.max(nominal, p75 / 0.7); +} function computeLayout(para: any): ParagraphLayout { const lines = para?.lines ?? []; + const effSize = paraEffSize(lines); let columnLeft = Infinity, columnRight = -Infinity, firstBaselineY = -Infinity; const baselines: number[] = [], rightEdges: number[] = [], objectIndices: number[] = []; const seedRuns: SeedRun[] = []; @@ -91,8 +105,9 @@ function computeLayout(para: any): ParagraphLayout { if (prev && !/\s$/.test(prev) && !/^\s/.test(text)) text = ' ' + text; } const adv = perRun[ri]; - seedRuns.push({ text, fid: r.internal_font_id ?? '', size: r.font_size ?? 12, color: typeof r.color === 'string' ? r.color : '#000000', fontName: r.font_name ?? '', advances: adv }); - if (orig) lineFrags.push({ text: orig, fid: r.internal_font_id ?? '', size: r.font_size ?? 12, color: typeof r.color === 'string' ? r.color : '#000000', advances: adv }); + const safeColor = sanitizeTextColor(r.color); + seedRuns.push({ text, fid: r.internal_font_id ?? '', size: effSize, color: safeColor, fontName: r.font_name ?? '', advances: adv }); + if (orig) lineFrags.push({ text: orig, fid: r.internal_font_id ?? '', size: effSize, color: safeColor, advances: adv }); } if (lineFrags.length) origLines.push({ frags: lineFrags, x: anchorX, baselineY: line.baseline_y ?? 0 }); } @@ -349,6 +364,10 @@ export const ParagraphEditor: React.FC = ({ const renderPreview = async () => { const el = editRef.current; if (!el) return; + // Tier-2 parity: register cmap-augmented embedded fonts with WASM so the reflow preview + // reuses the document's real glyphs (matching the native save). + const fids = new Set([dominantFid, ...layout.seedRuns.map((r) => r.fid)].filter(Boolean)); + await Promise.all([...fids].map((f) => wasmEnsureAuxFont(documentId, f))); const dpi = Math.round(72 * zoom); const origLines = editedRef.current ? undefined : layout.origLines; const opJson = buildOpJson(extractFlatRuns(el, dominantFid, domSize, domColor), origLines); diff --git a/frontend/src/viewer/TextEditLayer.tsx b/frontend/src/viewer/TextEditLayer.tsx index a9560a2..dea1205 100644 --- a/frontend/src/viewer/TextEditLayer.tsx +++ b/frontend/src/viewer/TextEditLayer.tsx @@ -1,6 +1,8 @@ import React, { useEffect, useRef, useState } from 'react'; import { gatewayService } from '../lib/gatewayService'; import { loadPdfFont, releaseDocumentFonts } from '../lib/fontFaceLoader'; +import { sanitizeTextColor } from '../lib/colorUtils'; +import { wasmEnsureAuxFont, wasmHasDocument, wasmLoadDocument, wasmPreviewRenderRegion } from '../lib/pdfiumEngine'; import { ParagraphEditor } from './ParagraphEditor'; import type { ReflowAlign } from './ParagraphEditor'; @@ -19,6 +21,7 @@ export interface EditableRun { paraIndex: number; lineIndex: number; runIndex: number; + fontFidelity: string; // "exact" | "partial" | "substituted" } export interface ReflowFragment { @@ -162,6 +165,49 @@ function isFlowingParagraph(para: any): boolean { return filled && consistentLeft; } +function tableCells(line: any): any[] { + return (line?.runs ?? []).filter((r: any) => (r.text ?? '').trim()); +} +function paraEm(para: any): number { + let capH = 0; + for (const l of (para?.lines ?? [])) for (const r of (l.runs ?? [])) for (const g of (r.glyphs ?? [])) { + if ((g.bbox_h ?? 0) > capH) capH = g.bbox_h; + } + return capH > 0 ? capH / 0.7 : 10; +} +function lineLargeGaps(line: any, em: number): number { + const cells = tableCells(line); + let n = 0; + for (let k = 0; k + 1 < cells.length; k++) { + if (cells[k + 1].x - (cells[k].x + cells[k].w) > em * 0.6) n++; + } + return n; +} +function isTableParagraph(para: any, model: any): boolean { + const lines = para?.lines ?? []; + if (!lines.length) return false; + const em = paraEm(para); + if (lines.length > 1) { + let columnar = 0; + for (const l of lines) if (tableCells(l).length >= 2 && lineLargeGaps(l, em) >= 1) columnar++; + return columnar >= Math.max(2, Math.ceil(lines.length * 0.6)); + } + const cells = tableCells(lines[0]); + if (cells.length < 2) return false; + const gaps = lineLargeGaps(lines[0], em); + if (gaps >= 2) return true; + if (gaps < 1) return false; + const cols = cells.map((c: any) => c.x); + for (const p of (model?.paragraphs ?? [])) { + if (p === para) continue; + for (const l of (p.lines ?? [])) { + const oc = tableCells(l); + if (oc.length >= 2 && cols.filter((x: number) => oc.some((r: any) => Math.abs(r.x - x) <= em * 0.6)).length >= 2) return true; + } + } + return false; +} + interface TextEditLayerProps { documentId: string; pageIndex: number; @@ -170,7 +216,7 @@ interface TextEditLayerProps { height: number; zoom: number; pageImageUrl?: string; - onEditText?: (pageIndex: number, run: EditableRun, newText: string) => void; + onEditText?: (pageIndex: number, run: EditableRun, newText: string, disableJustify?: boolean) => void; onReflowParagraph?: (pageIndex: number, payload: ReflowParagraphPayload) => void; onOverflowPreview?: (regions: import('./ParagraphEditor').OverflowPreviewRegion[]) => void; onOverflowCaret?: (caret: import('./ParagraphEditor').OverflowCaret | null) => void; @@ -196,7 +242,7 @@ function caretIndexFromX(text: string, cssFont: string, x: number): number { function fallbackFamily(fontName: string): string { const n = (fontName || '').toLowerCase(); if (n.includes('times') || (n.includes('serif') && !n.includes('sans'))) { - return 'Georgia, "Times New Roman", Times, serif'; + return '"Times New Roman", Times, Georgia, serif'; } if (n.includes('courier') || n.includes('mono')) { return '"Courier New", Courier, monospace'; @@ -225,8 +271,9 @@ function flattenRuns(model: any): EditableRun[] { objectIndices, internalFontId: r.internal_font_id ?? '', fontName: r.font_name ?? '', - color: typeof r.color === 'string' ? r.color : '#000000', + color: sanitizeTextColor(r.color), paraIndex: pi, lineIndex: li, runIndex: ri, + fontFidelity: r.font_fidelity ?? 'exact', }); } } @@ -241,6 +288,14 @@ function median(xs: number[]): number { return s[Math.floor(s.length / 2)]; } +function displayFontSize(r: EditableRun): number { + const t = r.text || ''; + const hasDescender = /[gjpqy(),;\[\]{}₀-₉]/.test(t); + const hasAscender = /[bdfhklt]/.test(t); + const frac = hasDescender ? 0.92 : hasAscender ? 0.75 : 0.70; + return Math.max(r.fontSize, r.h / frac); +} + function buildReflowPayload(model: any, run: EditableRun, newText: string): ReflowParagraphPayload | null { const para = model?.paragraphs?.[run.paraIndex]; if (!para || !Array.isArray(para.lines)) return null; @@ -327,6 +382,11 @@ export const TextEditLayer: React.FC = ({ anchorPageIndex?: number; } | null>(null); const [caretClick, setCaretClick] = useState<{ x: number; y: number } | null>(null); + const previewCanvasRef = useRef(null); + const [cellPreviewReady, setCellPreviewReady] = useState(false); + const cellRafRef = useRef(null); + const cellRenderingRef = useRef(false); + const cellPendingRef = useRef<{ run: EditableRun; text: string } | null>(null); useEffect(() => { let cancelled = false; @@ -413,7 +473,7 @@ export const TextEditLayer: React.FC = ({ const openEditor = (i: number, clickX: number, clientX?: number, clientY?: number) => { const run = runs[i]; const para = modelRef.current?.paragraphs?.[run.paraIndex]; - if (Array.isArray(para?.lines) && para.lines.length >= 1 && onReflowParagraph) { + if (Array.isArray(para?.lines) && para.lines.length >= 1 && onReflowParagraph && !isTableParagraph(para, modelRef.current)) { const click = clientX != null && clientY != null ? { x: clientX, y: clientY } : null; if (para.lines.length > 1) { if (isFlowingParagraph(para)) { @@ -447,11 +507,12 @@ export const TextEditLayer: React.FC = ({ } committedRef.current = false; const fb = fallbackFamily(run.fontName); - caretIdxRef.current = caretIndexFromX(run.text, `${run.fontSize * zoom}px ${fb}`, clickX); + caretIdxRef.current = caretIndexFromX(run.text, `${displayFontSize(run) * zoom}px ${fb}`, clickX); setEditing(i); setValue(run.text); setFontFamily(fb); - if (run.internalFontId) { + const isSubsetFont = /^[A-Z]{6}\+/.test(run.internalFontId || ''); + if (run.internalFontId && !isSubsetFont) { loadPdfFont(documentId, run.internalFontId).then((family) => { if (family) setFontFamily(`'${family}', ${fb}`); }); @@ -462,39 +523,17 @@ export const TextEditLayer: React.FC = ({ if (editing === null || committedRef.current) return; const run = runs[editing]; const next = value; - - if (next !== run.text) { - const canvas = document.createElement('canvas'); - const ctx = canvas.getContext('2d'); - let originalWidth = run.w * zoom; - let newWidth = originalWidth; - - if (ctx) { - ctx.font = `${run.fontSize * zoom}px sans-serif`; - originalWidth = ctx.measureText(run.text).width; - newWidth = ctx.measureText(next).width; - } else { - originalWidth = run.text.length; - newWidth = next.length; - } - - if (newWidth > originalWidth * 1.2) { - if (!window.confirm('Warning: The new text is significantly wider (> 120%) than the original. This might cause overlap or layout issues. Continue anyway?')) { - return; - } - } - } + const para = modelRef.current?.paragraphs?.[run.paraIndex]; + const isTable = isTableParagraph(para, modelRef.current); committedRef.current = true; setEditing(null); if (next === run.text) return; - - const para = modelRef.current?.paragraphs?.[run.paraIndex]; - if (onReflowParagraph && isFlowingParagraph(para)) { + if (onReflowParagraph && !isTable && isFlowingParagraph(para)) { const payload = buildReflowPayload(modelRef.current, run, next); if (payload) { onReflowParagraph(pageIndex, payload); return; } } - onEditText?.(pageIndex, run, next); + onEditText?.(pageIndex, run, next, shouldDisableJustify(para)); }; const cancel = () => { @@ -502,6 +541,84 @@ export const TextEditLayer: React.FC = ({ setEditing(null); }; + const cellBand = (r: EditableRun) => { + const box = rectOf(r); + const pad = displayFontSize(r) * zoom * 0.45 + 4; + const topScreen = Math.max(0, box.top - pad); + const botScreen = Math.min(height, box.top + box.height + pad); + return { topScreen, heightScreen: Math.max(1, botScreen - topScreen) }; + }; + + const shouldDisableJustify = (para: any): boolean => { + if (isTableParagraph(para, modelRef.current)) return true; + const lines = para?.lines ?? []; + if (lines.length === 1) { + const n = (lines[0].runs ?? []).filter((r: any) => (r.text ?? '').trim()).length; + if (n > 1) return true; + } + return false; + }; + + const renderCellPreview = async (r: EditableRun, text: string) => { + if (!wasmHasDocument(documentId)) return; + if (r.internalFontId && r.fontFidelity !== 'exact') await wasmEnsureAuxFont(documentId, r.internalFontId); + const para = modelRef.current?.paragraphs?.[r.paraIndex]; + const op = JSON.stringify({ + version: '1.0', + operations: [{ + id: 'cellpreview', type: 'replace_text', pageIndex, + data: { + objectIndices: r.objectIndices, text, + internalFontId: r.internalFontId || '', fontSize: -1.0, disableJustify: shouldDisableJustify(para), + }, + }], + }); + const dpi = Math.round(72 * zoom); + const { topScreen, heightScreen } = cellBand(r); + const res = await wasmPreviewRenderRegion(documentId, pageIndex, dpi, op, topScreen / zoom, heightScreen / zoom); + if (!res.rgba || res.width <= 0 || res.height <= 0) return; + const cv = previewCanvasRef.current; + if (!cv) return; + if (cv.width !== res.width) cv.width = res.width; + if (cv.height !== res.height) cv.height = res.height; + const ctx = cv.getContext('2d'); + if (ctx) { const img = ctx.createImageData(res.width, res.height); img.data.set(res.rgba); ctx.putImageData(img, 0, 0); } + setCellPreviewReady(true); + }; + + const scheduleCellRender = (r: EditableRun, text: string) => { + cellPendingRef.current = { run: r, text }; + if (cellRafRef.current != null || cellRenderingRef.current) return; + cellRafRef.current = window.requestAnimationFrame(async () => { + cellRafRef.current = null; + cellRenderingRef.current = true; + let job = cellPendingRef.current; + while (job) { + cellPendingRef.current = null; + try { await renderCellPreview(job.run, job.text); } catch { /* keep DOM fallback */ } + job = cellPendingRef.current; + } + cellRenderingRef.current = false; + }); + }; + + useEffect(() => { + if (editing === null) { setCellPreviewReady(false); return; } + const r = runs[editing]; + if (!r) return; + let cancelled = false; + (async () => { + if (!wasmHasDocument(documentId)) { + const bytes = await gatewayService.getDocumentRaw(documentId); + if (bytes && !cancelled) await wasmLoadDocument(documentId, bytes); + } + if (!cancelled) scheduleCellRender(r, value); + })(); + return () => { cancelled = true; }; + }, [editing, value, documentId, pageIndex, zoom]); + + useEffect(() => () => { if (cellRafRef.current != null) window.cancelAnimationFrame(cellRafRef.current); }, []); + const run = editing !== null ? runs[editing] : null; return ( @@ -543,16 +660,37 @@ export const TextEditLayer: React.FC = ({ })} {run && (() => { - const fpx = run.fontSize * zoom; + const fpx = displayFontSize(run) * zoom; const baselineScreen = (heightPts - run.baselineY) * zoom; const inputTop = baselineScreen - 0.8 * fpx; const box = rectOf(run); + const band = cellBand(run); + const fidelityNote = run.fontFidelity === 'substituted' + ? "Original font isn't embedded for editing — using the closest match." + : run.fontFidelity === 'partial' + ? 'Reusing the document font; brand-new characters may use a close match.' + : ''; return ( <> + {/* White cover for the original cell until the engine preview is ready. */}
+ {fidelityNote && ( +
+ {fidelityNote} +
+ )} + {/* Engine-rendered preview band (covers the whole row; only the edited cell changes). */} + = ({ lineHeight: `${fpx}px`, fontFamily, fontSize: `${fpx}px`, - color: run.color, + color: cellPreviewReady ? 'transparent' : run.color, caretColor: run.color, }} /> diff --git a/gateway/app/routers/documents.py b/gateway/app/routers/documents.py index 052a4be..ea86df8 100644 --- a/gateway/app/routers/documents.py +++ b/gateway/app/routers/documents.py @@ -294,6 +294,31 @@ def get_font_bytes(document_id: str, internal_font_id: str) -> Response: 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: @@ -463,6 +488,7 @@ class TextRunModel(BaseModel): h: float object_indices: list[int] = [] color: str = "#000000" + font_fidelity: str = "exact" class TextLineModel(BaseModel): @@ -547,6 +573,7 @@ def get_page_model(document_id: str, page_index: int) -> PageModelResponse: 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( diff --git a/gateway/app/routers/edits.py b/gateway/app/routers/edits.py index 7f23e42..3846692 100644 --- a/gateway/app/routers/edits.py +++ b/gateway/app/routers/edits.py @@ -214,6 +214,7 @@ class ReplaceTextData(BaseModel): text: str internalFontId: str fontSize: float + disableJustify: bool = False class ReplaceTextOperation(BaseModel): diff --git a/wasm/CMakeLists.txt b/wasm/CMakeLists.txt index d1639d4..927a3fa 100644 --- a/wasm/CMakeLists.txt +++ b/wasm/CMakeLists.txt @@ -55,7 +55,7 @@ target_link_options(pdfengine_wasm PRIVATE "-sALLOW_MEMORY_GROWTH=1" "-sWASM_BIGINT" "-sSTACK_SIZE=5MB" - "-sEXPORTED_FUNCTIONS=['_loadDocument','_pageCount','_renderPagePng','_previewRender','_previewRenderRegion','_previewRenderPaginated','_lastRegionCount','_lastRegionPtr','_lastRegionW','_lastRegionH','_lastRegionPage','_lastRegionYTop','_lastRenderPtr','_lastRenderW','_lastRenderH','_lastLayoutJson','_freeDocument','_engineBuildInfo','_malloc','_free']" + "-sEXPORTED_FUNCTIONS=['_loadDocument','_pageCount','_renderPagePng','_previewRender','_previewRenderRegion','_previewRenderPaginated','_lastRegionCount','_lastRegionPtr','_lastRegionW','_lastRegionH','_lastRegionPage','_lastRegionYTop','_lastRenderPtr','_lastRenderW','_lastRenderH','_lastLayoutJson','_registerAuxFont','_freeDocument','_engineBuildInfo','_malloc','_free']" "-sEXPORTED_RUNTIME_METHODS=['ccall','cwrap','getValue','setValue','HEAPU8']" ) diff --git a/wasm/bindings/wasm_engine.cpp b/wasm/bindings/wasm_engine.cpp index 0521fef..af0b667 100644 --- a/wasm/bindings/wasm_engine.cpp +++ b/wasm/bindings/wasm_engine.cpp @@ -144,6 +144,13 @@ EMSCRIPTEN_KEEPALIVE int lastRenderW() { return g_lastW; } EMSCRIPTEN_KEEPALIVE int lastRenderH() { return g_lastH; } EMSCRIPTEN_KEEPALIVE const char* lastLayoutJson() { return g_lastLayout.c_str(); } +EMSCRIPTEN_KEEPALIVE void registerAuxFont(int handle, const char* fid, const uint8_t* data, int size) { + auto it = g_docs.find(handle); + if (it == g_docs.end() || !fid || !data || size <= 0) return; + std::vector bytes(data, data + size); + it->second.doc->registerAuxFont(std::string(fid), bytes); +} + EMSCRIPTEN_KEEPALIVE void freeDocument(int handle) { g_docs.erase(handle); } EMSCRIPTEN_KEEPALIVE const char* engineBuildInfo() { return "pdfengine-wasm+pdfium"; }