view markdown_converter/wasm/markdown_to_html_wasm.js @ 181:a2720eac50ce

[NPC] Adding JSON RPC protocol for MPC endpoints
author June Park <parkjune1995@gmail.com>
date Fri, 23 Jan 2026 21:07:08 -0800
parents cd35e600ae34
children
line wrap: on
line source

/**
 * Markdown to HTML Converter - WASM FFI Wrapper
 * Loads the C implementation via WebAssembly and provides JavaScript API
 *
 * Build the WASM with: bazel build //markdown_converter:markdown_to_html_wasm
 */

class MarkdownConverterWasm {
  constructor() {
    this.wasm = undefined;
    this.ready = false;
  }

  /**
   * Initialize the WASM module
   * @param {string} wasmPath - Path to the .wasm file
   * @returns {Promise<void>}
   */
  async init(wasmPath = '/markdown_to_html_wasm.wasm') {
    if (this.ready) return;

    try {
      this.wasm = await WebAssembly.instantiateStreaming(fetch(wasmPath), {
        env: {}
      });
      this.ready = true;
    } catch (err) {
      console.error('Failed to load markdown WASM module:', err);
      throw err;
    }
  }

  /**
   * Write string to WASM memory
   * @private
   */
  _writeString(str) {
    const encoder = new TextEncoder();
    const bytes = encoder.encode(str + '\0');
    const ptr = this.wasm.instance.exports.malloc(bytes.length);
    const memory = new Uint8Array(this.wasm.instance.exports.memory.buffer);
    memory.set(bytes, ptr);
    return ptr;
  }

  /**
   * Read string from WASM memory
   * @private
   */
  _readString(ptr) {
    const memory = new Uint8Array(this.wasm.instance.exports.memory.buffer);
    let end = ptr;
    while (memory[end] !== 0) end++;
    const bytes = memory.slice(ptr, end);
    const decoder = new TextDecoder();
    return decoder.decode(bytes);
  }

  /**
   * Reset the WASM heap (call between conversions to reclaim memory)
   */
  resetHeap() {
    if (this.ready) {
      this.wasm.instance.exports.heap_reset();
    }
  }

  /**
   * Convert markdown string to HTML string using WASM
   * @param {string} markdown - The markdown text to convert
   * @returns {string} The converted HTML
   */
  convert(markdown) {
    if (!this.ready) {
      throw new Error('WASM module not initialized. Call init() first.');
    }

    // Reset heap before each conversion to reclaim memory
    this.resetHeap();

    // Write markdown to WASM memory
    const inputPtr = this._writeString(markdown);

    // Call the C function
    const outputPtr = this.wasm.instance.exports.markdown_to_html(inputPtr);

    // Read the result
    const html = this._readString(outputPtr);

    return html;
  }

  /**
   * Convert markdown to DOM elements (compatible with original API)
   * @param {string} markdown - The markdown text to convert
   * @returns {Array<HTMLElement>} Array of DOM elements
   */
  convertToElements(markdown) {
    const html = this.convert(markdown);
    const template = document.createElement('template');
    template.innerHTML = html;
    return Array.from(template.content.children);
  }
}

// Singleton instance
const markdownWasm = new MarkdownConverterWasm();

/**
 * Convert markdown to DOM elements (WASM version)
 * Compatible with original markdownConverter() API
 * @param {string} value - Markdown string
 * @returns {Promise<Array<HTMLElement>>} Array of DOM elements
 */
async function markdownConverterWasm(value) {
  if (!markdownWasm.ready) {
    await markdownWasm.init();
  }
  return markdownWasm.convertToElements(value);
}

/**
 * Render markdown to a container element (WASM version)
 * Compatible with original renderMarkdown() API
 * @param {HTMLElement} container - Container element
 * @param {string} markdown - Markdown string
 */
async function renderMarkdownWasm(container, markdown) {
  if (!markdownWasm.ready) {
    await markdownWasm.init();
  }
  container.innerHTML = markdownWasm.convert(markdown);
}

// Export for use in other files
if (typeof module !== 'undefined' && module.exports) {
  module.exports = {
    MarkdownConverterWasm,
    markdownWasm,
    markdownConverterWasm,
    renderMarkdownWasm
  };
}