@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/react.cjs CHANGED
@@ -42,6 +42,7 @@ function _interopNamespace(e) {
42
42
  var DOMPurify6__default = /*#__PURE__*/_interopDefault(DOMPurify6);
43
43
  var pdfjsLib__namespace = /*#__PURE__*/_interopNamespace(pdfjsLib);
44
44
  var docx__namespace = /*#__PURE__*/_interopNamespace(docx);
45
+ var fflate__namespace = /*#__PURE__*/_interopNamespace(fflate);
45
46
  var XLSX__namespace = /*#__PURE__*/_interopNamespace(XLSX);
46
47
  var hljs__default = /*#__PURE__*/_interopDefault(hljs);
47
48
  var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
@@ -700,9 +701,11 @@ var ToolbarController = class {
700
701
  type: "button",
701
702
  "data-action-id": id
702
703
  });
703
- const svg = ICON_MAP[iconHtml] || ICON_MAP[id] || (iconHtml && iconHtml.startsWith("<svg") ? iconHtml : null);
704
- if (svg) {
705
- btn.innerHTML = sanitizeSVG(svg);
704
+ const internalSvg = ICON_MAP[iconHtml] || ICON_MAP[id];
705
+ if (internalSvg) {
706
+ btn.innerHTML = internalSvg;
707
+ } else if (iconHtml && iconHtml.startsWith("<svg")) {
708
+ btn.innerHTML = sanitizeSVG(iconHtml);
706
709
  } else {
707
710
  btn.textContent = title || id;
708
711
  }
@@ -5702,8 +5705,17 @@ var DocPlugin = class {
5702
5705
  let scale = 1;
5703
5706
  let extractedRawText = "";
5704
5707
  let isFallback = false;
5708
+ let chartSvg = "";
5705
5709
  try {
5706
5710
  const cfbf = new CfbfReader(ctx.buffer);
5711
+ try {
5712
+ const pkg = cfbf.readStream("package_stream");
5713
+ if (pkg && pkg.length > 100) {
5714
+ chartSvg = this.parseOdfChartToSvg(pkg);
5715
+ }
5716
+ } catch (chartErr) {
5717
+ console.warn("[DocPlugin] Chart stream parsing info:", chartErr);
5718
+ }
5707
5719
  const wordDocStream = cfbf.readStream("WordDocument");
5708
5720
  if (!wordDocStream || wordDocStream.length < 512) {
5709
5721
  throw new Error("WordDocument stream not found or invalid in CFBF archive");
@@ -5721,7 +5733,7 @@ var DocPlugin = class {
5721
5733
  extractedRawText = fallback;
5722
5734
  isFallback = true;
5723
5735
  }
5724
- const rawPages = this.splitIntoPages(extractedRawText);
5736
+ const rawPages = this.splitIntoPages(extractedRawText, chartSvg);
5725
5737
  const totalPages = Math.max(1, rawPages.length);
5726
5738
  let currentPage = 1;
5727
5739
  const pageCards = [];
@@ -5963,7 +5975,7 @@ var DocPlugin = class {
5963
5975
  for (const run of [...ansiRuns, ...utf16Runs]) {
5964
5976
  const trimmed = run.trim();
5965
5977
  if (trimmed.length >= 4 && /[a-zA-Z]/.test(trimmed) && !seen.has(trimmed)) {
5966
- 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)) {
5978
+ 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)) {
5967
5979
  seen.add(trimmed);
5968
5980
  candidateLines.push(trimmed);
5969
5981
  }
@@ -5974,9 +5986,114 @@ var DocPlugin = class {
5974
5986
  heuristicTextExtraction(buffer) {
5975
5987
  return this.extractStringsFromBytes(new Uint8Array(buffer));
5976
5988
  }
5977
- cleanWordDocFields(text) {
5989
+ /**
5990
+ * Parses an embedded OpenDocument Chart package into a vector SVG bar/column chart
5991
+ */
5992
+ parseOdfChartToSvg(zipBytes) {
5993
+ try {
5994
+ const unzipped = fflate__namespace.unzipSync(zipBytes);
5995
+ const contentXml = unzipped["content.xml"] ? new TextDecoder("utf-8").decode(unzipped["content.xml"]) : "";
5996
+ if (!contentXml) return "";
5997
+ const rowsMatch = contentXml.match(/<table:table-row[\s\S]*?<\/table:table-row>/g) || [];
5998
+ if (rowsMatch.length < 2) return "";
5999
+ const headers = [];
6000
+ const firstRow = rowsMatch[0];
6001
+ const headerCells = firstRow ? firstRow.match(/<text:p>([^<]+)<\/text:p>/g) || [] : [];
6002
+ for (const h of headerCells) {
6003
+ headers.push(h.replace(/<\/?text:p>/g, "").trim());
6004
+ }
6005
+ const categories = [];
6006
+ const seriesValues = headers.map(() => []);
6007
+ for (let r = 1; r < rowsMatch.length; r++) {
6008
+ const rowStr = rowsMatch[r];
6009
+ if (!rowStr) continue;
6010
+ const cells = rowStr.match(/<table:table-cell[\s\S]*?<\/table:table-cell>/g) || [];
6011
+ if (cells.length > 0 && cells[0]) {
6012
+ const catMatch = cells[0].match(/<text:p>([^<]+)<\/text:p>/);
6013
+ categories.push(catMatch ? catMatch[1] : "Row " + r);
6014
+ for (let c = 1; c < cells.length && c - 1 < headers.length; c++) {
6015
+ const cellStr = cells[c];
6016
+ if (!cellStr) continue;
6017
+ const valMatch = cellStr.match(/office:value="([0-9.]+)"/) || cellStr.match(/<text:p>([0-9.]+)<\/text:p>/);
6018
+ const series = seriesValues[c - 1];
6019
+ if (series) {
6020
+ series.push(valMatch ? parseFloat(valMatch[1]) : 0);
6021
+ }
6022
+ }
6023
+ }
6024
+ }
6025
+ const colors = ["#004586", "#ff420e", "#ffd320", "#579d1c", "#7e0021"];
6026
+ const colorMatches = contentXml.matchAll(/draw:fill-color="(#[0-9a-fA-F]{6})"/g);
6027
+ let cIdx = 0;
6028
+ for (const cm of colorMatches) {
6029
+ if (cIdx < colors.length) colors[cIdx] = cm[1];
6030
+ cIdx++;
6031
+ }
6032
+ let maxVal = 10;
6033
+ for (const s of seriesValues) {
6034
+ for (const v of s) {
6035
+ if (v > maxVal) maxVal = v;
6036
+ }
6037
+ }
6038
+ maxVal = Math.ceil(maxVal * 1.15);
6039
+ const width = 560;
6040
+ const height = 280;
6041
+ const padLeft = 45;
6042
+ const padRight = 100;
6043
+ const padTop = 20;
6044
+ const padBottom = 40;
6045
+ const chartW = width - padLeft - padRight;
6046
+ const chartH = height - padTop - padBottom;
6047
+ 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);">`;
6048
+ for (let step = 0; step <= 4; step++) {
6049
+ const yVal = (maxVal / 4 * step).toFixed(1);
6050
+ const yPos = padTop + chartH - step / 4 * chartH;
6051
+ svg += `<line x1="${padLeft}" y1="${yPos}" x2="${padLeft + chartW}" y2="${yPos}" stroke="#e2e8f0" stroke-dasharray="2,2" />`;
6052
+ svg += `<text x="${padLeft - 8}" y="${yPos + 4}" font-size="11" fill="#64748b" text-anchor="end">${yVal}</text>`;
6053
+ }
6054
+ const numCats = categories.length;
6055
+ const numSeries = headers.length;
6056
+ const groupW = chartW / numCats;
6057
+ const barW = Math.max(8, groupW * 0.7 / numSeries);
6058
+ const groupPad = (groupW - barW * numSeries) / 2;
6059
+ for (let catIdx = 0; catIdx < numCats; catIdx++) {
6060
+ const groupX = padLeft + catIdx * groupW + groupPad;
6061
+ for (let sIdx = 0; sIdx < numSeries; sIdx++) {
6062
+ const val = seriesValues[sIdx][catIdx] || 0;
6063
+ const barH = val / maxVal * chartH;
6064
+ const barX = groupX + sIdx * barW;
6065
+ const barY = padTop + chartH - barH;
6066
+ const col = colors[sIdx % colors.length];
6067
+ svg += `<rect x="${barX}" y="${barY}" width="${barW - 2}" height="${barH}" fill="${col}" rx="2"><title>${headers[sIdx]}: ${val}</title></rect>`;
6068
+ }
6069
+ const catX = padLeft + catIdx * groupW + groupW / 2;
6070
+ svg += `<text x="${catX}" y="${padTop + chartH + 18}" font-size="11" fill="#475569" text-anchor="middle">${categories[catIdx]}</text>`;
6071
+ }
6072
+ let legendY = padTop + 20;
6073
+ for (let sIdx = 0; sIdx < numSeries; sIdx++) {
6074
+ const col = colors[sIdx % colors.length];
6075
+ svg += `<rect x="${padLeft + chartW + 15}" y="${legendY}" width="12" height="12" fill="${col}" rx="2" />`;
6076
+ svg += `<text x="${padLeft + chartW + 32}" y="${legendY + 10}" font-size="11" fill="#334155">${headers[sIdx]}</text>`;
6077
+ legendY += 20;
6078
+ }
6079
+ svg += "</svg>";
6080
+ return svg;
6081
+ } catch (e) {
6082
+ console.warn("[DocPlugin] Error generating chart SVG:", e);
6083
+ return "";
6084
+ }
6085
+ }
6086
+ cleanWordDocFields(text, chartSvg = "") {
5978
6087
  if (!text) return "";
5979
6088
  let cleaned = text.replace(
6089
+ /\x13\s*EMBED\b[\s\S]*?\x15/gi,
6090
+ () => chartSvg ? `
6091
+
6092
+ ${chartSvg}
6093
+
6094
+ ` : ""
6095
+ );
6096
+ cleaned = cleaned.replace(
5980
6097
  /\x13\s*HYPERLINK\s*"?([^"\x14]+)"?\s*\x14([\s\S]*?)\x15/gi,
5981
6098
  (_match, url, label) => {
5982
6099
  const cleanUrl = url.trim();
@@ -5984,18 +6101,23 @@ var DocPlugin = class {
5984
6101
  return `<a href="${cleanUrl}" target="_blank" rel="noopener noreferrer" style="color: #2563eb; text-decoration: underline;">${cleanLabel}</a>`;
5985
6102
  }
5986
6103
  );
5987
- cleaned = cleaned.replace(/\x13[^\x14\x15]*\x14([^\x15]*)\x15/g, "$1");
6104
+ cleaned = cleaned.replace(/\x13[^\x14\x15]*\x14([^\x15]*)\x15/g, (_m, res) => {
6105
+ if (/[\x00-\x1F]/.test(res)) return "";
6106
+ return res.trim();
6107
+ });
5988
6108
  cleaned = cleaned.replace(/\x13[^\x15]*\x15/g, "");
5989
6109
  cleaned = cleaned.replace(/[\x13\x14\x15]/g, "");
6110
+ cleaned = cleaned.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/g, "");
6111
+ cleaned = cleaned.replace(/EMBED\s+LibreOffice\.ChartDocument\.[0-9]+/gi, chartSvg || "");
5990
6112
  return cleaned;
5991
6113
  }
5992
- splitIntoPages(text) {
6114
+ splitIntoPages(text, chartSvg = "") {
5993
6115
  if (!text) return [""];
5994
- const cleanedText = this.cleanWordDocFields(text);
6116
+ const cleanedText = this.cleanWordDocFields(text, chartSvg);
5995
6117
  const normalized = cleanedText.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x07\n/g, "\n").replace(/\x07/g, " ");
5996
6118
  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);
5997
6119
  if (explicitParts.length === 0) explicitParts.push(normalized);
5998
- const maxLinesPerPage = 32;
6120
+ const maxLinesPerPage = 34;
5999
6121
  const charsPerLine = 80;
6000
6122
  const finalPages = [];
6001
6123
  for (const part of explicitParts) {
@@ -6003,8 +6125,8 @@ var DocPlugin = class {
6003
6125
  let currentLines = [];
6004
6126
  let count = 0;
6005
6127
  for (const line of lines) {
6006
- const plainLine = line.replace(/<[^>]+>/g, "");
6007
- const vLines = Math.max(1, Math.ceil((plainLine.length || 1) / charsPerLine));
6128
+ const isSvg = line.includes("<svg");
6129
+ const vLines = isSvg ? 12 : Math.max(1, Math.ceil((line.replace(/<[^>]+>/g, "").length || 1) / charsPerLine));
6008
6130
  if (count + vLines > maxLinesPerPage && currentLines.length > 0) {
6009
6131
  finalPages.push(currentLines.join("\n"));
6010
6132
  currentLines = [];
@@ -6044,9 +6166,44 @@ var DocPlugin = class {
6044
6166
  tableLines = [];
6045
6167
  }
6046
6168
  };
6169
+ const sanitizeOptions = {
6170
+ ADD_TAGS: ["a", "svg", "g", "path", "line", "rect", "circle", "text", "title"],
6171
+ ADD_ATTR: [
6172
+ "href",
6173
+ "target",
6174
+ "rel",
6175
+ "style",
6176
+ "viewBox",
6177
+ "width",
6178
+ "height",
6179
+ "x",
6180
+ "y",
6181
+ "x1",
6182
+ "y1",
6183
+ "x2",
6184
+ "y2",
6185
+ "fill",
6186
+ "stroke",
6187
+ "stroke-width",
6188
+ "stroke-dasharray",
6189
+ "rx",
6190
+ "font-size",
6191
+ "text-anchor"
6192
+ ]
6193
+ };
6047
6194
  let i = 0;
6048
6195
  while (i < lines.length) {
6049
6196
  let line = lines[i];
6197
+ if (line.includes("<svg")) {
6198
+ if (inList) {
6199
+ html += "</ul>";
6200
+ inList = false;
6201
+ }
6202
+ flushTable();
6203
+ html += line;
6204
+ i++;
6205
+ continue;
6206
+ }
6050
6207
  let tabCount = (line.match(/\t/g) || []).length;
6051
6208
  if (tabCount > 0) {
6052
6209
  let j = i;
@@ -6080,7 +6237,6 @@ var DocPlugin = class {
6080
6237
  i++;
6081
6238
  continue;
6082
6239
  }
6083
- const sanitizeOptions = { ADD_TAGS: ["a"], ADD_ATTR: ["href", "target", "rel", "style"] };
6084
6240
  if (line.length < 60 && !line.endsWith(".") && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#"))) {
6085
6241
  if (inList) {
6086
6242
  html += "</ul>";