patx/makeitpdf

/* DOCX/XLSX parsing is isolated from the UI thread. No network URLs are used;
   importScripts only reads the vendored files beside this application. */

"use strict";

const MAX_CELLS = 50000;

self.addEventListener("message", async (event) => {
  const { id, kind, buffer } = event.data || {};
  try {
    if (kind === "docx") {
      importScripts("vendor/mammoth.browser.min.js");
      const result = await self.mammoth.convertToHtml(
        { arrayBuffer: buffer },
        { externalFileAccess: false }
      );
      self.postMessage({
        id,
        ok: true,
        result: {
          html: result.value,
          warnings: result.messages.map((message) => message.message || String(message)),
        },
      });
      return;
    }

    if (kind === "xlsx") {
      importScripts("vendor/xlsx.full.min.js");
      const workbook = self.XLSX.read(buffer, {
        type: "array",
        cellDates: false,
        cellNF: true,
        cellStyles: true,
      });
      const sheetMeta = new Map(
        (workbook.Workbook?.Sheets || []).map((sheet) => [sheet.name, sheet])
      );
      const sheets = [];
      let cellCount = 0;

      for (const name of workbook.SheetNames) {
        if (sheetMeta.get(name)?.Hidden) continue;
        const worksheet = workbook.Sheets[name];
        const ref = worksheet["!ref"];
        if (!ref) {
          sheets.push({ name, rows: [], merges: [], widths: [] });
          continue;
        }
        const range = self.XLSX.utils.decode_range(ref);
        const rowCount = range.e.r - range.s.r + 1;
        const columnCount = range.e.c - range.s.c + 1;
        cellCount += rowCount * columnCount;
        if (cellCount > MAX_CELLS) {
          throw new Error(`has more than ${MAX_CELLS.toLocaleString()} rendered cells.`);
        }

        const rows = [];
        for (let rowIndex = range.s.r; rowIndex <= range.e.r; rowIndex += 1) {
          const row = [];
          for (let columnIndex = range.s.c; columnIndex <= range.e.c; columnIndex += 1) {
            const address = self.XLSX.utils.encode_cell({ r: rowIndex, c: columnIndex });
            const cell = worksheet[address];
            if (!cell) row.push("");
            else if (cell.w !== undefined) row.push(String(cell.w));
            else if (cell.v !== undefined && cell.v !== null) row.push(String(cell.v));
            else if (cell.f) row.push(`=${cell.f}`);
            else row.push("");
          }
          rows.push(row);
        }

        const merges = (worksheet["!merges"] || []).map((merge) => ({
          startRow: merge.s.r - range.s.r,
          startColumn: merge.s.c - range.s.c,
          rowSpan: merge.e.r - merge.s.r + 1,
          colSpan: merge.e.c - merge.s.c + 1,
        }));
        const widths = Array.from({ length: columnCount }, (_, index) => {
          const column = worksheet["!cols"]?.[range.s.c + index];
          return column?.wpx || (column?.wch ? column.wch * 7 : null);
        });
        sheets.push({ name, rows, merges, widths });
      }
      if (!sheets.length) throw new Error("has no visible worksheets.");
      self.postMessage({ id, ok: true, result: { sheets, cellCount } });
      return;
    }

    throw new Error("uses an unsupported Office format.");
  } catch (error) {
    self.postMessage({ id, ok: false, error: error.message || String(error) });
  }
});