89 lines
3.3 KiB
C++
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"; }
|
|
|
|
}
|