@files-preview-app/preview-file 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -38,6 +38,7 @@ function _interopNamespace(e) {
38
38
  var DOMPurify6__default = /*#__PURE__*/_interopDefault(DOMPurify6);
39
39
  var pdfjsLib__namespace = /*#__PURE__*/_interopNamespace(pdfjsLib);
40
40
  var docx__namespace = /*#__PURE__*/_interopNamespace(docx);
41
+ var fflate__namespace = /*#__PURE__*/_interopNamespace(fflate);
41
42
  var XLSX__namespace = /*#__PURE__*/_interopNamespace(XLSX);
42
43
  var hljs__default = /*#__PURE__*/_interopDefault(hljs);
43
44
  var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
@@ -781,9 +782,11 @@ var ToolbarController = class {
781
782
  type: "button",
782
783
  "data-action-id": id
783
784
  });
784
- const svg = ICON_MAP[iconHtml] || ICON_MAP[id] || (iconHtml && iconHtml.startsWith("<svg") ? iconHtml : null);
785
- if (svg) {
786
- btn.innerHTML = sanitizeSVG(svg);
785
+ const internalSvg = ICON_MAP[iconHtml] || ICON_MAP[id];
786
+ if (internalSvg) {
787
+ btn.innerHTML = internalSvg;
788
+ } else if (iconHtml && iconHtml.startsWith("<svg")) {
789
+ btn.innerHTML = sanitizeSVG(iconHtml);
787
790
  } else {
788
791
  btn.textContent = title || id;
789
792
  }
@@ -5783,8 +5786,17 @@ var DocPlugin = class {
5783
5786
  let scale = 1;
5784
5787
  let extractedRawText = "";
5785
5788
  let isFallback = false;
5789
+ let chartSvg = "";
5786
5790
  try {
5787
5791
  const cfbf = new CfbfReader(ctx.buffer);
5792
+ try {
5793
+ const pkg = cfbf.readStream("package_stream");
5794
+ if (pkg && pkg.length > 100) {
5795
+ chartSvg = this.parseOdfChartToSvg(pkg);
5796
+ }
5797
+ } catch (chartErr) {
5798
+ console.warn("[DocPlugin] Chart stream parsing info:", chartErr);
5799
+ }
5788
5800
  const wordDocStream = cfbf.readStream("WordDocument");
5789
5801
  if (!wordDocStream || wordDocStream.length < 512) {
5790
5802
  throw new Error("WordDocument stream not found or invalid in CFBF archive");
@@ -5802,7 +5814,7 @@ var DocPlugin = class {
5802
5814
  extractedRawText = fallback;
5803
5815
  isFallback = true;
5804
5816
  }
5805
- const rawPages = this.splitIntoPages(extractedRawText);
5817
+ const rawPages = this.splitIntoPages(extractedRawText, chartSvg);
5806
5818
  const totalPages = Math.max(1, rawPages.length);
5807
5819
  let currentPage = 1;
5808
5820
  const pageCards = [];
@@ -6044,7 +6056,7 @@ var DocPlugin = class {
6044
6056
  for (const run of [...ansiRuns, ...utf16Runs]) {
6045
6057
  const trimmed = run.trim();
6046
6058
  if (trimmed.length >= 4 && /[a-zA-Z]/.test(trimmed) && !seen.has(trimmed)) {
6047
- if (!trimmed.includes("Normal.dot") && !trimmed.includes("Microsoft Word") && !trimmed.includes("Times New Roman") && !trimmed.startsWith("\xD0\xCF\xE0\xA1\xB1\xE1") && !/^[\W_0-9]+$/.test(trimmed)) {
6059
+ if (!trimmed.includes("Normal.dot") && !trimmed.includes("Microsoft Word") && !trimmed.includes("Times New Roman") && !trimmed.startsWith("\xD0\xCF\xE0\xA1\xB1\xE1") && !/^EMBED\b/i.test(trimmed) && !trimmed.includes("ChartDocument") && !/^[\W_0-9]+$/.test(trimmed)) {
6048
6060
  seen.add(trimmed);
6049
6061
  candidateLines.push(trimmed);
6050
6062
  }
@@ -6055,9 +6067,114 @@ var DocPlugin = class {
6055
6067
  heuristicTextExtraction(buffer) {
6056
6068
  return this.extractStringsFromBytes(new Uint8Array(buffer));
6057
6069
  }
6058
- cleanWordDocFields(text) {
6070
+ /**
6071
+ * Parses an embedded OpenDocument Chart package into a vector SVG bar/column chart
6072
+ */
6073
+ parseOdfChartToSvg(zipBytes) {
6074
+ try {
6075
+ const unzipped = fflate__namespace.unzipSync(zipBytes);
6076
+ const contentXml = unzipped["content.xml"] ? new TextDecoder("utf-8").decode(unzipped["content.xml"]) : "";
6077
+ if (!contentXml) return "";
6078
+ const rowsMatch = contentXml.match(/<table:table-row[\s\S]*?<\/table:table-row>/g) || [];
6079
+ if (rowsMatch.length < 2) return "";
6080
+ const headers = [];
6081
+ const firstRow = rowsMatch[0];
6082
+ const headerCells = firstRow ? firstRow.match(/<text:p>([^<]+)<\/text:p>/g) || [] : [];
6083
+ for (const h of headerCells) {
6084
+ headers.push(h.replace(/<\/?text:p>/g, "").trim());
6085
+ }
6086
+ const categories = [];
6087
+ const seriesValues = headers.map(() => []);
6088
+ for (let r = 1; r < rowsMatch.length; r++) {
6089
+ const rowStr = rowsMatch[r];
6090
+ if (!rowStr) continue;
6091
+ const cells = rowStr.match(/<table:table-cell[\s\S]*?<\/table:table-cell>/g) || [];
6092
+ if (cells.length > 0 && cells[0]) {
6093
+ const catMatch = cells[0].match(/<text:p>([^<]+)<\/text:p>/);
6094
+ categories.push(catMatch ? catMatch[1] : "Row " + r);
6095
+ for (let c = 1; c < cells.length && c - 1 < headers.length; c++) {
6096
+ const cellStr = cells[c];
6097
+ if (!cellStr) continue;
6098
+ const valMatch = cellStr.match(/office:value="([0-9.]+)"/) || cellStr.match(/<text:p>([0-9.]+)<\/text:p>/);
6099
+ const series = seriesValues[c - 1];
6100
+ if (series) {
6101
+ series.push(valMatch ? parseFloat(valMatch[1]) : 0);
6102
+ }
6103
+ }
6104
+ }
6105
+ }
6106
+ const colors = ["#004586", "#ff420e", "#ffd320", "#579d1c", "#7e0021"];
6107
+ const colorMatches = contentXml.matchAll(/draw:fill-color="(#[0-9a-fA-F]{6})"/g);
6108
+ let cIdx = 0;
6109
+ for (const cm of colorMatches) {
6110
+ if (cIdx < colors.length) colors[cIdx] = cm[1];
6111
+ cIdx++;
6112
+ }
6113
+ let maxVal = 10;
6114
+ for (const s of seriesValues) {
6115
+ for (const v of s) {
6116
+ if (v > maxVal) maxVal = v;
6117
+ }
6118
+ }
6119
+ maxVal = Math.ceil(maxVal * 1.15);
6120
+ const width = 560;
6121
+ const height = 280;
6122
+ const padLeft = 45;
6123
+ const padRight = 100;
6124
+ const padTop = 20;
6125
+ const padBottom = 40;
6126
+ const chartW = width - padLeft - padRight;
6127
+ const chartH = height - padTop - padBottom;
6128
+ let svg = `<svg viewBox="0 0 ${width} ${height}" width="100%" height="auto" style="max-width: 560px; height: 280px; margin: 16px auto; display: block; font-family: Calibri, sans-serif; background: #ffffff; border: 1px solid #e2e8f0; border-radius: 6px; box-shadow: 0 1px 4px rgba(0,0,0,0.05);">`;
6129
+ for (let step = 0; step <= 4; step++) {
6130
+ const yVal = (maxVal / 4 * step).toFixed(1);
6131
+ const yPos = padTop + chartH - step / 4 * chartH;
6132
+ svg += `<line x1="${padLeft}" y1="${yPos}" x2="${padLeft + chartW}" y2="${yPos}" stroke="#e2e8f0" stroke-dasharray="2,2" />`;
6133
+ svg += `<text x="${padLeft - 8}" y="${yPos + 4}" font-size="11" fill="#64748b" text-anchor="end">${yVal}</text>`;
6134
+ }
6135
+ const numCats = categories.length;
6136
+ const numSeries = headers.length;
6137
+ const groupW = chartW / numCats;
6138
+ const barW = Math.max(8, groupW * 0.7 / numSeries);
6139
+ const groupPad = (groupW - barW * numSeries) / 2;
6140
+ for (let catIdx = 0; catIdx < numCats; catIdx++) {
6141
+ const groupX = padLeft + catIdx * groupW + groupPad;
6142
+ for (let sIdx = 0; sIdx < numSeries; sIdx++) {
6143
+ const val = seriesValues[sIdx][catIdx] || 0;
6144
+ const barH = val / maxVal * chartH;
6145
+ const barX = groupX + sIdx * barW;
6146
+ const barY = padTop + chartH - barH;
6147
+ const col = colors[sIdx % colors.length];
6148
+ svg += `<rect x="${barX}" y="${barY}" width="${barW - 2}" height="${barH}" fill="${col}" rx="2"><title>${headers[sIdx]}: ${val}</title></rect>`;
6149
+ }
6150
+ const catX = padLeft + catIdx * groupW + groupW / 2;
6151
+ svg += `<text x="${catX}" y="${padTop + chartH + 18}" font-size="11" fill="#475569" text-anchor="middle">${categories[catIdx]}</text>`;
6152
+ }
6153
+ let legendY = padTop + 20;
6154
+ for (let sIdx = 0; sIdx < numSeries; sIdx++) {
6155
+ const col = colors[sIdx % colors.length];
6156
+ svg += `<rect x="${padLeft + chartW + 15}" y="${legendY}" width="12" height="12" fill="${col}" rx="2" />`;
6157
+ svg += `<text x="${padLeft + chartW + 32}" y="${legendY + 10}" font-size="11" fill="#334155">${headers[sIdx]}</text>`;
6158
+ legendY += 20;
6159
+ }
6160
+ svg += "</svg>";
6161
+ return svg;
6162
+ } catch (e) {
6163
+ console.warn("[DocPlugin] Error generating chart SVG:", e);
6164
+ return "";
6165
+ }
6166
+ }
6167
+ cleanWordDocFields(text, chartSvg = "") {
6059
6168
  if (!text) return "";
6060
6169
  let cleaned = text.replace(
6170
+ /\x13\s*EMBED\b[\s\S]*?\x15/gi,
6171
+ () => chartSvg ? `
6172
+
6173
+ ${chartSvg}
6174
+
6175
+ ` : ""
6176
+ );
6177
+ cleaned = cleaned.replace(
6061
6178
  /\x13\s*HYPERLINK\s*"?([^"\x14]+)"?\s*\x14([\s\S]*?)\x15/gi,
6062
6179
  (_match, url, label) => {
6063
6180
  const cleanUrl = url.trim();
@@ -6065,18 +6182,23 @@ var DocPlugin = class {
6065
6182
  return `<a href="${cleanUrl}" target="_blank" rel="noopener noreferrer" style="color: #2563eb; text-decoration: underline;">${cleanLabel}</a>`;
6066
6183
  }
6067
6184
  );
6068
- cleaned = cleaned.replace(/\x13[^\x14\x15]*\x14([^\x15]*)\x15/g, "$1");
6185
+ cleaned = cleaned.replace(/\x13[^\x14\x15]*\x14([^\x15]*)\x15/g, (_m, res) => {
6186
+ if (/[\x00-\x1F]/.test(res)) return "";
6187
+ return res.trim();
6188
+ });
6069
6189
  cleaned = cleaned.replace(/\x13[^\x15]*\x15/g, "");
6070
6190
  cleaned = cleaned.replace(/[\x13\x14\x15]/g, "");
6191
+ cleaned = cleaned.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/g, "");
6192
+ cleaned = cleaned.replace(/EMBED\s+LibreOffice\.ChartDocument\.[0-9]+/gi, chartSvg || "");
6071
6193
  return cleaned;
6072
6194
  }
6073
- splitIntoPages(text) {
6195
+ splitIntoPages(text, chartSvg = "") {
6074
6196
  if (!text) return [""];
6075
- const cleanedText = this.cleanWordDocFields(text);
6197
+ const cleanedText = this.cleanWordDocFields(text, chartSvg);
6076
6198
  const normalized = cleanedText.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x07\n/g, "\n").replace(/\x07/g, " ");
6077
6199
  const explicitParts = normalized.split(/[\x0C\f]|\n\s*[-=_]{3,}\s*(?:PAGE|Page|page break)[\s\d\w-]*[-=_]{3,}\s*\n/i).map((p) => p.trim()).filter((p) => p.length > 0);
6078
6200
  if (explicitParts.length === 0) explicitParts.push(normalized);
6079
- const maxLinesPerPage = 32;
6201
+ const maxLinesPerPage = 34;
6080
6202
  const charsPerLine = 80;
6081
6203
  const finalPages = [];
6082
6204
  for (const part of explicitParts) {
@@ -6084,8 +6206,8 @@ var DocPlugin = class {
6084
6206
  let currentLines = [];
6085
6207
  let count = 0;
6086
6208
  for (const line of lines) {
6087
- const plainLine = line.replace(/<[^>]+>/g, "");
6088
- const vLines = Math.max(1, Math.ceil((plainLine.length || 1) / charsPerLine));
6209
+ const isSvg = line.includes("<svg");
6210
+ const vLines = isSvg ? 12 : Math.max(1, Math.ceil((line.replace(/<[^>]+>/g, "").length || 1) / charsPerLine));
6089
6211
  if (count + vLines > maxLinesPerPage && currentLines.length > 0) {
6090
6212
  finalPages.push(currentLines.join("\n"));
6091
6213
  currentLines = [];
@@ -6125,9 +6247,44 @@ var DocPlugin = class {
6125
6247
  tableLines = [];
6126
6248
  }
6127
6249
  };
6250
+ const sanitizeOptions = {
6251
+ ADD_TAGS: ["a", "svg", "g", "path", "line", "rect", "circle", "text", "title"],
6252
+ ADD_ATTR: [
6253
+ "href",
6254
+ "target",
6255
+ "rel",
6256
+ "style",
6257
+ "viewBox",
6258
+ "width",
6259
+ "height",
6260
+ "x",
6261
+ "y",
6262
+ "x1",
6263
+ "y1",
6264
+ "x2",
6265
+ "y2",
6266
+ "fill",
6267
+ "stroke",
6268
+ "stroke-width",
6269
+ "stroke-dasharray",
6270
+ "rx",
6271
+ "font-size",
6272
+ "text-anchor"
6273
+ ]
6274
+ };
6128
6275
  let i = 0;
6129
6276
  while (i < lines.length) {
6130
6277
  let line = lines[i];
6278
+ if (line.includes("<svg")) {
6279
+ if (inList) {
6280
+ html += "</ul>";
6281
+ inList = false;
6282
+ }
6283
+ flushTable();
6284
+ html += line;
6285
+ i++;
6286
+ continue;
6287
+ }
6131
6288
  let tabCount = (line.match(/\t/g) || []).length;
6132
6289
  if (tabCount > 0) {
6133
6290
  let j = i;
@@ -6161,7 +6318,6 @@ var DocPlugin = class {
6161
6318
  i++;
6162
6319
  continue;
6163
6320
  }
6164
- const sanitizeOptions = { ADD_TAGS: ["a"], ADD_ATTR: ["href", "target", "rel", "style"] };
6165
6321
  if (line.length < 60 && !line.endsWith(".") && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#"))) {
6166
6322
  if (inList) {
6167
6323
  html += "</ul>";