Files
pdf/wasm/bindings/wasm_engine.cpp
T

89 lines
3.3 KiB
C++

// 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 <emscripten/emscripten.h>
#include <pdfengine/pdf_document.hpp>
#include <unordered_map>
#include <memory>
#include <vector>
#include <string>
#include <cstdint>
namespace {
struct DocEntry {
std::shared_ptr<pdfengine::PdfDocument> doc;
std::vector<uint8_t> bytes; // kept so a preview can re-load a clean copy
};
std::unordered_map<int, DocEntry> g_docs;
int g_nextHandle = 1;
// Holds the most recent render output (PNG) so JS can read it via lastRender* accessors.
std::vector<uint8_t> 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<int>(g_lastPng.size());
}
} // namespace
extern "C" {
EMSCRIPTEN_KEEPALIVE int loadDocument(const uint8_t* buffer, int size) {
std::vector<uint8_t> 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"; }
}