// Real engine bindings for WASM: load a PDF, render a page (PNG), and render a PREVIEW of // an edit (e.g. reflow_paragraph) without mutating the loaded document. Rendering uses the // SAME C++ engine path as the gateway (PdfDocument::render), so the browser preview is // pixel-identical to the server-rendered page. #include #include #include #include #include #include #include namespace { struct DocEntry { std::shared_ptr doc; std::vector bytes; // kept so a preview can re-load a clean copy }; std::unordered_map g_docs; int g_nextHandle = 1; // Holds the most recent render output (PNG) so JS can read it via lastRender* accessors. std::vector g_lastPng; int g_lastW = 0, g_lastH = 0; // Per-character caret layout (JSON) from the most recent preview reflow, read via lastLayoutJson(). std::string g_lastLayout; int renderInto(pdfengine::PdfDocument& doc, int pageIndex, int dpi) { auto page = doc.getPage(pageIndex); if (!page) return -1; auto img = (*page)->render(dpi); if (!img) return -1; g_lastPng = std::move(img->data); g_lastW = img->width; g_lastH = img->height; return static_cast(g_lastPng.size()); } } // namespace extern "C" { EMSCRIPTEN_KEEPALIVE int loadDocument(const uint8_t* buffer, int size) { std::vector bytes(buffer, buffer + size); auto res = pdfengine::PdfDocument::loadFromMemory(bytes, ""); if (!res) return -1; int h = g_nextHandle++; g_docs[h] = DocEntry{*res, std::move(bytes)}; return h; } EMSCRIPTEN_KEEPALIVE int pageCount(int handle) { auto it = g_docs.find(handle); return it == g_docs.end() ? -1 : it->second.doc->pageCount(); } // Render the loaded page as-is (PNG). Returns byte length (read via lastRenderPtr()). EMSCRIPTEN_KEEPALIVE int renderPagePng(int handle, int pageIndex, int dpi) { auto it = g_docs.find(handle); if (it == g_docs.end()) return -1; return renderInto(*it->second.doc, pageIndex, dpi); } // Render a PREVIEW of an edit: load a fresh copy from the original bytes, apply the edits // (e.g. a reflow_paragraph op), render the page — the loaded document is untouched. EMSCRIPTEN_KEEPALIVE int previewRender(int handle, int pageIndex, int dpi, const char* editsJson) { auto it = g_docs.find(handle); if (it == g_docs.end()) return -1; auto fresh = pdfengine::PdfDocument::loadFromMemory(it->second.bytes, ""); if (!fresh) return -1; g_lastLayout.clear(); if (editsJson && editsJson[0]) { auto r = (*fresh)->applyEdits(editsJson); if (!r) return -1; g_lastLayout = (*fresh)->lastReflowLayout(); } return renderInto(**fresh, pageIndex, dpi); } EMSCRIPTEN_KEEPALIVE const uint8_t* lastRenderPtr() { return g_lastPng.data(); } EMSCRIPTEN_KEEPALIVE int lastRenderW() { return g_lastW; } EMSCRIPTEN_KEEPALIVE int lastRenderH() { return g_lastH; } // Caret layout JSON for the most recent previewRender (empty if it had no reflow). EMSCRIPTEN_KEEPALIVE const char* lastLayoutJson() { return g_lastLayout.c_str(); } EMSCRIPTEN_KEEPALIVE void freeDocument(int handle) { g_docs.erase(handle); } EMSCRIPTEN_KEEPALIVE const char* engineBuildInfo() { return "pdfengine-wasm+pdfium"; } }