Files
pdf/wasm/bindings/wasm_engine.cpp
T
2026-07-30 16:48:46 +05:30

176 lines
6.8 KiB
C++

#include <emscripten/emscripten.h>
#include <pdfengine/pdf_document.hpp>
#include <spdlog/spdlog.h>
#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;
std::unordered_map<std::string, std::vector<uint8_t>> auxFonts;
};
std::unordered_map<int, DocEntry> g_docs;
int g_nextHandle = 1;
void applyAuxFonts(pdfengine::PdfDocument& fresh, const DocEntry& entry) {
for (const auto& [fid, bytes] : entry.auxFonts) fresh.registerAuxFont(fid, bytes);
}
std::vector<uint8_t> g_lastPng;
int g_lastW = 0, g_lastH = 0;
std::string g_lastLayout;
struct PreviewRegion { std::vector<uint8_t> rgba; int w = 0, h = 0; int pageIndex = 0; double yTopPt = 0.0; };
std::vector<PreviewRegion> g_regions;
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());
}
}
extern "C" {
EMSCRIPTEN_KEEPALIVE int loadDocument(const uint8_t* buffer, int size) {
static const bool logInit = [] { spdlog::set_level(spdlog::level::err); return true; }();
(void)logInit;
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();
}
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);
}
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;
applyAuxFonts(**fresh, it->second);
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 int previewRenderRegion(int handle, int pageIndex, int dpi,
const char* editsJson, double yTopPt, double heightPt) {
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;
applyAuxFonts(**fresh, it->second);
g_lastLayout.clear();
if (editsJson && editsJson[0]) {
auto r = (*fresh)->applyEdits(editsJson);
if (!r) return -1;
g_lastLayout = (*fresh)->lastReflowLayout();
}
auto page = (*fresh)->getPage(pageIndex);
if (!page) return -1;
auto img = (*page)->renderRegionRaw(dpi, yTopPt, heightPt);
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());
}
EMSCRIPTEN_KEEPALIVE int previewRenderPaginated(int handle, int pageIndex, int dpi,
const char* editsJson, double yTopPt, int maxFollow) {
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;
applyAuxFonts(**fresh, it->second);
g_lastLayout.clear();
g_regions.clear();
bool overflowed = false;
if (editsJson && editsJson[0]) {
auto r = (*fresh)->applyEdits(editsJson);
if (!r) return -1;
g_lastLayout = (*fresh)->lastReflowLayout();
overflowed = (*fresh)->lastReflowOverflowed();
}
{
auto page = (*fresh)->getPage(pageIndex);
if (!page) return -1;
auto img = (*page)->renderRegionRaw(dpi, yTopPt, 0.0);
if (!img) return -1;
g_regions.push_back({std::move(img->data), img->width, img->height, pageIndex, yTopPt});
}
if (overflowed) {
int total = (*fresh)->pageCount();
int last = pageIndex + (maxFollow > 0 ? maxFollow : 4);
for (int pg = pageIndex + 1; pg < total && pg <= last; ++pg) {
auto page = (*fresh)->getPage(pg);
if (!page) break;
auto img = (*page)->renderRegionRaw(dpi, 0.0, 0.0);
if (!img) break;
g_regions.push_back({std::move(img->data), img->width, img->height, pg, 0.0});
}
}
return static_cast<int>(g_regions.size());
}
EMSCRIPTEN_KEEPALIVE int lastRegionCount() { return static_cast<int>(g_regions.size()); }
EMSCRIPTEN_KEEPALIVE const uint8_t* lastRegionPtr(int i) {
return (i >= 0 && i < static_cast<int>(g_regions.size())) ? g_regions[i].rgba.data() : nullptr;
}
EMSCRIPTEN_KEEPALIVE int lastRegionW(int i) { return (i >= 0 && i < static_cast<int>(g_regions.size())) ? g_regions[i].w : 0; }
EMSCRIPTEN_KEEPALIVE int lastRegionH(int i) { return (i >= 0 && i < static_cast<int>(g_regions.size())) ? g_regions[i].h : 0; }
EMSCRIPTEN_KEEPALIVE int lastRegionPage(int i) { return (i >= 0 && i < static_cast<int>(g_regions.size())) ? g_regions[i].pageIndex : -1; }
EMSCRIPTEN_KEEPALIVE double lastRegionYTop(int i) { return (i >= 0 && i < static_cast<int>(g_regions.size())) ? g_regions[i].yTopPt : 0.0; }
EMSCRIPTEN_KEEPALIVE const uint8_t* lastRenderPtr() { return g_lastPng.data(); }
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<uint8_t> bytes(data, data + size);
it->second.doc->registerAuxFont(std::string(fid), bytes);
it->second.auxFonts[std::string(fid)] = std::move(bytes);
}
EMSCRIPTEN_KEEPALIVE void freeDocument(int handle) { g_docs.erase(handle); }
EMSCRIPTEN_KEEPALIVE const char* engineBuildInfo() { return "pdfengine-wasm+pdfium"; }
std::string g_lastValidation;
EMSCRIPTEN_KEEPALIVE const char* debugValidateLayout(int handle, int pageIndex, const char* jsonStr) {
auto it = g_docs.find(handle);
if (it == g_docs.end() || !jsonStr) return "{}";
g_lastValidation = it->second.doc->validateLayout(pageIndex, jsonStr);
return g_lastValidation.c_str();
}
}