@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/vue.js CHANGED
@@ -2,6 +2,7 @@ import { defineComponent, ref, onMounted, watch, onBeforeUnmount, h } from 'vue'
2
2
  import DOMPurify6 from 'dompurify';
3
3
  import * as pdfjsLib from 'pdfjs-dist';
4
4
  import * as docx from 'docx-preview';
5
+ import * as fflate from 'fflate';
5
6
  import { unzipSync, strFromU8, unzip } from 'fflate';
6
7
  import * as XLSX from 'xlsx';
7
8
  import hljs from 'highlight.js';
@@ -666,9 +667,11 @@ var ToolbarController = class {
666
667
  type: "button",
667
668
  "data-action-id": id
668
669
  });
669
- const svg = ICON_MAP[iconHtml] || ICON_MAP[id] || (iconHtml && iconHtml.startsWith("<svg") ? iconHtml : null);
670
- if (svg) {
671
- btn.innerHTML = sanitizeSVG(svg);
670
+ const internalSvg = ICON_MAP[iconHtml] || ICON_MAP[id];
671
+ if (internalSvg) {
672
+ btn.innerHTML = internalSvg;
673
+ } else if (iconHtml && iconHtml.startsWith("<svg")) {
674
+ btn.innerHTML = sanitizeSVG(iconHtml);
672
675
  } else {
673
676
  btn.textContent = title || id;
674
677
  }
@@ -5668,8 +5671,17 @@ var DocPlugin = class {
5668
5671
  let scale = 1;
5669
5672
  let extractedRawText = "";
5670
5673
  let isFallback = false;
5674
+ let chartSvg = "";
5671
5675
  try {
5672
5676
  const cfbf = new CfbfReader(ctx.buffer);
5677
+ try {
5678
+ const pkg = cfbf.readStream("package_stream");
5679
+ if (pkg && pkg.length > 100) {
5680
+ chartSvg = this.parseOdfChartToSvg(pkg);
5681
+ }
5682
+ } catch (chartErr) {
5683
+ console.warn("[DocPlugin] Chart stream parsing info:", chartErr);
5684
+ }
5673
5685
  const wordDocStream = cfbf.readStream("WordDocument");
5674
5686
  if (!wordDocStream || wordDocStream.length < 512) {
5675
5687
  throw new Error("WordDocument stream not found or invalid in CFBF archive");
@@ -5687,7 +5699,7 @@ var DocPlugin = class {
5687
5699
  extractedRawText = fallback;
5688
5700
  isFallback = true;
5689
5701
  }
5690
- const rawPages = this.splitIntoPages(extractedRawText);
5702
+ const rawPages = this.splitIntoPages(extractedRawText, chartSvg);
5691
5703
  const totalPages = Math.max(1, rawPages.length);
5692
5704
  let currentPage = 1;
5693
5705
  const pageCards = [];
@@ -5929,7 +5941,7 @@ var DocPlugin = class {
5929
5941
  for (const run of [...ansiRuns, ...utf16Runs]) {
5930
5942
  const trimmed = run.trim();
5931
5943
  if (trimmed.length >= 4 && /[a-zA-Z]/.test(trimmed) && !seen.has(trimmed)) {
5932
- 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)) {
5944
+ 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)) {
5933
5945
  seen.add(trimmed);
5934
5946
  candidateLines.push(trimmed);
5935
5947
  }
@@ -5940,9 +5952,114 @@ var DocPlugin = class {
5940
5952
  heuristicTextExtraction(buffer) {
5941
5953
  return this.extractStringsFromBytes(new Uint8Array(buffer));
5942
5954
  }
5943
- cleanWordDocFields(text) {
5955
+ /**
5956
+ * Parses an embedded OpenDocument Chart package into a vector SVG bar/column chart
5957
+ */
5958
+ parseOdfChartToSvg(zipBytes) {
5959
+ try {
5960
+ const unzipped = fflate.unzipSync(zipBytes);
5961
+ const contentXml = unzipped["content.xml"] ? new TextDecoder("utf-8").decode(unzipped["content.xml"]) : "";
5962
+ if (!contentXml) return "";
5963
+ const rowsMatch = contentXml.match(/<table:table-row[\s\S]*?<\/table:table-row>/g) || [];
5964
+ if (rowsMatch.length < 2) return "";
5965
+ const headers = [];
5966
+ const firstRow = rowsMatch[0];
5967
+ const headerCells = firstRow ? firstRow.match(/<text:p>([^<]+)<\/text:p>/g) || [] : [];
5968
+ for (const h2 of headerCells) {
5969
+ headers.push(h2.replace(/<\/?text:p>/g, "").trim());
5970
+ }
5971
+ const categories = [];
5972
+ const seriesValues = headers.map(() => []);
5973
+ for (let r = 1; r < rowsMatch.length; r++) {
5974
+ const rowStr = rowsMatch[r];
5975
+ if (!rowStr) continue;
5976
+ const cells = rowStr.match(/<table:table-cell[\s\S]*?<\/table:table-cell>/g) || [];
5977
+ if (cells.length > 0 && cells[0]) {
5978
+ const catMatch = cells[0].match(/<text:p>([^<]+)<\/text:p>/);
5979
+ categories.push(catMatch ? catMatch[1] : "Row " + r);
5980
+ for (let c = 1; c < cells.length && c - 1 < headers.length; c++) {
5981
+ const cellStr = cells[c];
5982
+ if (!cellStr) continue;
5983
+ const valMatch = cellStr.match(/office:value="([0-9.]+)"/) || cellStr.match(/<text:p>([0-9.]+)<\/text:p>/);
5984
+ const series = seriesValues[c - 1];
5985
+ if (series) {
5986
+ series.push(valMatch ? parseFloat(valMatch[1]) : 0);
5987
+ }
5988
+ }
5989
+ }
5990
+ }
5991
+ const colors = ["#004586", "#ff420e", "#ffd320", "#579d1c", "#7e0021"];
5992
+ const colorMatches = contentXml.matchAll(/draw:fill-color="(#[0-9a-fA-F]{6})"/g);
5993
+ let cIdx = 0;
5994
+ for (const cm of colorMatches) {
5995
+ if (cIdx < colors.length) colors[cIdx] = cm[1];
5996
+ cIdx++;
5997
+ }
5998
+ let maxVal = 10;
5999
+ for (const s of seriesValues) {
6000
+ for (const v of s) {
6001
+ if (v > maxVal) maxVal = v;
6002
+ }
6003
+ }
6004
+ maxVal = Math.ceil(maxVal * 1.15);
6005
+ const width = 560;
6006
+ const height = 280;
6007
+ const padLeft = 45;
6008
+ const padRight = 100;
6009
+ const padTop = 20;
6010
+ const padBottom = 40;
6011
+ const chartW = width - padLeft - padRight;
6012
+ const chartH = height - padTop - padBottom;
6013
+ 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);">`;
6014
+ for (let step = 0; step <= 4; step++) {
6015
+ const yVal = (maxVal / 4 * step).toFixed(1);
6016
+ const yPos = padTop + chartH - step / 4 * chartH;
6017
+ svg += `<line x1="${padLeft}" y1="${yPos}" x2="${padLeft + chartW}" y2="${yPos}" stroke="#e2e8f0" stroke-dasharray="2,2" />`;
6018
+ svg += `<text x="${padLeft - 8}" y="${yPos + 4}" font-size="11" fill="#64748b" text-anchor="end">${yVal}</text>`;
6019
+ }
6020
+ const numCats = categories.length;
6021
+ const numSeries = headers.length;
6022
+ const groupW = chartW / numCats;
6023
+ const barW = Math.max(8, groupW * 0.7 / numSeries);
6024
+ const groupPad = (groupW - barW * numSeries) / 2;
6025
+ for (let catIdx = 0; catIdx < numCats; catIdx++) {
6026
+ const groupX = padLeft + catIdx * groupW + groupPad;
6027
+ for (let sIdx = 0; sIdx < numSeries; sIdx++) {
6028
+ const val = seriesValues[sIdx][catIdx] || 0;
6029
+ const barH = val / maxVal * chartH;
6030
+ const barX = groupX + sIdx * barW;
6031
+ const barY = padTop + chartH - barH;
6032
+ const col = colors[sIdx % colors.length];
6033
+ svg += `<rect x="${barX}" y="${barY}" width="${barW - 2}" height="${barH}" fill="${col}" rx="2"><title>${headers[sIdx]}: ${val}</title></rect>`;
6034
+ }
6035
+ const catX = padLeft + catIdx * groupW + groupW / 2;
6036
+ svg += `<text x="${catX}" y="${padTop + chartH + 18}" font-size="11" fill="#475569" text-anchor="middle">${categories[catIdx]}</text>`;
6037
+ }
6038
+ let legendY = padTop + 20;
6039
+ for (let sIdx = 0; sIdx < numSeries; sIdx++) {
6040
+ const col = colors[sIdx % colors.length];
6041
+ svg += `<rect x="${padLeft + chartW + 15}" y="${legendY}" width="12" height="12" fill="${col}" rx="2" />`;
6042
+ svg += `<text x="${padLeft + chartW + 32}" y="${legendY + 10}" font-size="11" fill="#334155">${headers[sIdx]}</text>`;
6043
+ legendY += 20;
6044
+ }
6045
+ svg += "</svg>";
6046
+ return svg;
6047
+ } catch (e) {
6048
+ console.warn("[DocPlugin] Error generating chart SVG:", e);
6049
+ return "";
6050
+ }
6051
+ }
6052
+ cleanWordDocFields(text, chartSvg = "") {
5944
6053
  if (!text) return "";
5945
6054
  let cleaned = text.replace(
6055
+ /\x13\s*EMBED\b[\s\S]*?\x15/gi,
6056
+ () => chartSvg ? `
6057
+
6058
+ ${chartSvg}
6059
+
6060
+ ` : ""
6061
+ );
6062
+ cleaned = cleaned.replace(
5946
6063
  /\x13\s*HYPERLINK\s*"?([^"\x14]+)"?\s*\x14([\s\S]*?)\x15/gi,
5947
6064
  (_match, url, label) => {
5948
6065
  const cleanUrl = url.trim();
@@ -5950,18 +6067,23 @@ var DocPlugin = class {
5950
6067
  return `<a href="${cleanUrl}" target="_blank" rel="noopener noreferrer" style="color: #2563eb; text-decoration: underline;">${cleanLabel}</a>`;
5951
6068
  }
5952
6069
  );
5953
- cleaned = cleaned.replace(/\x13[^\x14\x15]*\x14([^\x15]*)\x15/g, "$1");
6070
+ cleaned = cleaned.replace(/\x13[^\x14\x15]*\x14([^\x15]*)\x15/g, (_m, res) => {
6071
+ if (/[\x00-\x1F]/.test(res)) return "";
6072
+ return res.trim();
6073
+ });
5954
6074
  cleaned = cleaned.replace(/\x13[^\x15]*\x15/g, "");
5955
6075
  cleaned = cleaned.replace(/[\x13\x14\x15]/g, "");
6076
+ cleaned = cleaned.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/g, "");
6077
+ cleaned = cleaned.replace(/EMBED\s+LibreOffice\.ChartDocument\.[0-9]+/gi, chartSvg || "");
5956
6078
  return cleaned;
5957
6079
  }
5958
- splitIntoPages(text) {
6080
+ splitIntoPages(text, chartSvg = "") {
5959
6081
  if (!text) return [""];
5960
- const cleanedText = this.cleanWordDocFields(text);
6082
+ const cleanedText = this.cleanWordDocFields(text, chartSvg);
5961
6083
  const normalized = cleanedText.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x07\n/g, "\n").replace(/\x07/g, " ");
5962
6084
  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);
5963
6085
  if (explicitParts.length === 0) explicitParts.push(normalized);
5964
- const maxLinesPerPage = 32;
6086
+ const maxLinesPerPage = 34;
5965
6087
  const charsPerLine = 80;
5966
6088
  const finalPages = [];
5967
6089
  for (const part of explicitParts) {
@@ -5969,8 +6091,8 @@ var DocPlugin = class {
5969
6091
  let currentLines = [];
5970
6092
  let count = 0;
5971
6093
  for (const line of lines) {
5972
- const plainLine = line.replace(/<[^>]+>/g, "");
5973
- const vLines = Math.max(1, Math.ceil((plainLine.length || 1) / charsPerLine));
6094
+ const isSvg = line.includes("<svg");
6095
+ const vLines = isSvg ? 12 : Math.max(1, Math.ceil((line.replace(/<[^>]+>/g, "").length || 1) / charsPerLine));
5974
6096
  if (count + vLines > maxLinesPerPage && currentLines.length > 0) {
5975
6097
  finalPages.push(currentLines.join("\n"));
5976
6098
  currentLines = [];
@@ -6010,9 +6132,44 @@ var DocPlugin = class {
6010
6132
  tableLines = [];
6011
6133
  }
6012
6134
  };
6135
+ const sanitizeOptions = {
6136
+ ADD_TAGS: ["a", "svg", "g", "path", "line", "rect", "circle", "text", "title"],
6137
+ ADD_ATTR: [
6138
+ "href",
6139
+ "target",
6140
+ "rel",
6141
+ "style",
6142
+ "viewBox",
6143
+ "width",
6144
+ "height",
6145
+ "x",
6146
+ "y",
6147
+ "x1",
6148
+ "y1",
6149
+ "x2",
6150
+ "y2",
6151
+ "fill",
6152
+ "stroke",
6153
+ "stroke-width",
6154
+ "stroke-dasharray",
6155
+ "rx",
6156
+ "font-size",
6157
+ "text-anchor"
6158
+ ]
6159
+ };
6013
6160
  let i = 0;
6014
6161
  while (i < lines.length) {
6015
6162
  let line = lines[i];
6163
+ if (line.includes("<svg")) {
6164
+ if (inList) {
6165
+ html += "</ul>";
6166
+ inList = false;
6167
+ }
6168
+ flushTable();
6169
+ html += line;
6170
+ i++;
6171
+ continue;
6172
+ }
6016
6173
  let tabCount = (line.match(/\t/g) || []).length;
6017
6174
  if (tabCount > 0) {
6018
6175
  let j = i;
@@ -6046,7 +6203,6 @@ var DocPlugin = class {
6046
6203
  i++;
6047
6204
  continue;
6048
6205
  }
6049
- const sanitizeOptions = { ADD_TAGS: ["a"], ADD_ATTR: ["href", "target", "rel", "style"] };
6050
6206
  if (line.length < 60 && !line.endsWith(".") && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#"))) {
6051
6207
  if (inList) {
6052
6208
  html += "</ul>";