/* Entry points for johnmcm.com's Publisher and CorelDRAW viewers: libmspub, * libcdr (CorelDRAW .cdr and .cmx) and librevenge, compiled to WebAssembly. * * Based on the OpenImprenta libmspub-wasm spike * (https://github.com/RicardoNigrelli/libmspub-wasm, MPL-2.0); this version * adds libcdr and chooses the reader by a format argument. * * SPDX-License-Identifier: MPL-2.0 * * Every entry point returns a plain object { ok, ..., error } instead of * throwing, so the JS side never deals with WebAssembly.Exception. Traps * (out-of-bounds, abort, stack overflow) still surface as a JS RuntimeError; * the page then discards the instance. * * view = inputBuffer(n) Uint8Array over n bytes of wasm memory; fill it at once * isSupportedInput(fmt) fmt: 0 Publisher, 1 CorelDRAW (.cdr), 2 Corel exchange (.cmx) * r = parseInput(fmt) { ok, pages, json, blobs, error?, unsupported? } * blob(i) Uint8Array view of picture i; copy it out before the next call * release() frees the blobs and any pending input */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "JSONDrawingGenerator.h" using emscripten::val; extern "C" const unsigned char oi_icudata_subset[]; namespace { // Register the filtered ICU data (icu/make-icudata.sh) before anything uses // ICU. Without it only algorithmic converters (UTF-8/16) are available. struct IcuDataInit { UErrorCode status = U_ZERO_ERROR; IcuDataInit() { udata_setCommonData(oi_icudata_subset, &status); } } g_icuDataInit; enum Format { PUB = 0, CDR = 1, CMX = 2 }; bool supported(int format, librevenge::RVNGInputStream *input) { switch (format) { case PUB: return libmspub::MSPUBDocument::isSupported(input); case CDR: return libcdr::CDRDocument::isSupported(input); case CMX: return libcdr::CMXDocument::isSupported(input); default: return false; } } bool parse(int format, librevenge::RVNGInputStream *input, librevenge::RVNGDrawingInterface *painter) { switch (format) { case PUB: return libmspub::MSPUBDocument::parse(input, painter); case CDR: return libcdr::CDRDocument::parse(input, painter); case CMX: return libcdr::CMXDocument::parse(input, painter); default: return false; } } val fail(const std::string &msg) { val r = val::object(); r.set("ok", false); r.set("error", msg); return r; } struct PendingState { unsigned char *input = nullptr; size_t inputSize = 0; std::vector blobs; } g_state; void freeInput() { std::free(g_state.input); g_state.input = nullptr; g_state.inputSize = 0; } void release() { freeInput(); std::vector().swap(g_state.blobs); } val inputBuffer(unsigned size) { release(); if (size == 0) return val::null(); g_state.input = static_cast(std::malloc(size)); if (!g_state.input) return val::null(); g_state.inputSize = size; return val(emscripten::typed_memory_view(size, g_state.input)); } bool isSupportedInput(int format) { if (!g_state.input) return false; try { librevenge::RVNGStringStream input(g_state.input, static_cast(g_state.inputSize)); return supported(format, &input); } catch (...) { return false; } } val parseInput(int format) { if (!g_state.input) return fail("no input: call inputBuffer() first"); try { std::unique_ptr input( new librevenge::RVNGStringStream(g_state.input, static_cast(g_state.inputSize))); freeInput(); // the stream keeps its own copy if (!supported(format, input.get())) { val r = fail("unsupported"); r.set("unsupported", true); return r; } input->seek(0, librevenge::RVNG_SEEK_SET); oi::JSONDrawingGenerator generator; bool parsed = parse(format, input.get(), &generator); input.reset(); // free the file before serializing std::string json = generator.result(); g_state.blobs = generator.takeBlobs(); val r = val::object(); r.set("ok", parsed); r.set("pages", generator.pageCount()); r.set("json", json); r.set("blobs", static_cast(g_state.blobs.size())); if (!parsed) r.set("error", std::string("parse() returned false")); return r; } catch (const std::exception &e) { release(); return fail(std::string("C++ exception: ") + e.what()); } catch (...) { release(); return fail("unknown C++ exception"); } } val blob(unsigned index) { if (index >= g_state.blobs.size()) return val::null(); const std::vector &d = g_state.blobs[index].data; return val(emscripten::typed_memory_view(d.size(), d.data())); } double heapSize() { return static_cast(emscripten_get_heap_size()); } } // anonymous namespace EMSCRIPTEN_BINDINGS(revenge) { emscripten::function("inputBuffer", &inputBuffer); emscripten::function("isSupportedInput", &isSupportedInput); emscripten::function("parseInput", &parseInput); emscripten::function("blob", &blob); emscripten::function("release", &release); emscripten::function("heapSize", &heapSize); }