@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/angular.js CHANGED
@@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common';
3
3
  import DOMPurify6 from 'dompurify';
4
4
  import * as pdfjsLib from 'pdfjs-dist';
5
5
  import * as docx from 'docx-preview';
6
+ import * as fflate from 'fflate';
6
7
  import { unzipSync, strFromU8, unzip } from 'fflate';
7
8
  import * as XLSX from 'xlsx';
8
9
  import hljs from 'highlight.js';
@@ -714,9 +715,11 @@ var ToolbarController = class {
714
715
  type: "button",
715
716
  "data-action-id": id
716
717
  });
717
- const svg = ICON_MAP[iconHtml] || ICON_MAP[id] || (iconHtml && iconHtml.startsWith("<svg") ? iconHtml : null);
718
- if (svg) {
719
- btn.innerHTML = sanitizeSVG(svg);
718
+ const internalSvg = ICON_MAP[iconHtml] || ICON_MAP[id];
719
+ if (internalSvg) {
720
+ btn.innerHTML = internalSvg;
721
+ } else if (iconHtml && iconHtml.startsWith("<svg")) {
722
+ btn.innerHTML = sanitizeSVG(iconHtml);
720
723
  } else {
721
724
  btn.textContent = title || id;
722
725
  }
@@ -5716,8 +5719,17 @@ var DocPlugin = class {
5716
5719
  let scale = 1;
5717
5720
  let extractedRawText = "";
5718
5721
  let isFallback = false;
5722
+ let chartSvg = "";
5719
5723
  try {
5720
5724
  const cfbf = new CfbfReader(ctx.buffer);
5725
+ try {
5726
+ const pkg = cfbf.readStream("package_stream");
5727
+ if (pkg && pkg.length > 100) {
5728
+ chartSvg = this.parseOdfChartToSvg(pkg);
5729
+ }
5730
+ } catch (chartErr) {
5731
+ console.warn("[DocPlugin] Chart stream parsing info:", chartErr);
5732
+ }
5721
5733
  const wordDocStream = cfbf.readStream("WordDocument");
5722
5734
  if (!wordDocStream || wordDocStream.length < 512) {
5723
5735
  throw new Error("WordDocument stream not found or invalid in CFBF archive");
@@ -5735,7 +5747,7 @@ var DocPlugin = class {
5735
5747
  extractedRawText = fallback;
5736
5748
  isFallback = true;
5737
5749
  }
5738
- const rawPages = this.splitIntoPages(extractedRawText);
5750
+ const rawPages = this.splitIntoPages(extractedRawText, chartSvg);
5739
5751
  const totalPages = Math.max(1, rawPages.length);
5740
5752
  let currentPage = 1;
5741
5753
  const pageCards = [];
@@ -5977,7 +5989,7 @@ var DocPlugin = class {
5977
5989
  for (const run of [...ansiRuns, ...utf16Runs]) {
5978
5990
  const trimmed = run.trim();
5979
5991
  if (trimmed.length >= 4 && /[a-zA-Z]/.test(trimmed) && !seen.has(trimmed)) {
5980
- 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)) {
5992
+ 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)) {
5981
5993
  seen.add(trimmed);
5982
5994
  candidateLines.push(trimmed);
5983
5995
  }
@@ -5988,9 +6000,114 @@ var DocPlugin = class {
5988
6000
  heuristicTextExtraction(buffer) {
5989
6001
  return this.extractStringsFromBytes(new Uint8Array(buffer));
5990
6002
  }
5991
- cleanWordDocFields(text) {
6003
+ /**
6004
+ * Parses an embedded OpenDocument Chart package into a vector SVG bar/column chart
6005
+ */
6006
+ parseOdfChartToSvg(zipBytes) {
6007
+ try {
6008
+ const unzipped = fflate.unzipSync(zipBytes);
6009
+ const contentXml = unzipped["content.xml"] ? new TextDecoder("utf-8").decode(unzipped["content.xml"]) : "";
6010
+ if (!contentXml) return "";
6011
+ const rowsMatch = contentXml.match(/<table:table-row[\s\S]*?<\/table:table-row>/g) || [];
6012
+ if (rowsMatch.length < 2) return "";
6013
+ const headers = [];
6014
+ const firstRow = rowsMatch[0];
6015
+ const headerCells = firstRow ? firstRow.match(/<text:p>([^<]+)<\/text:p>/g) || [] : [];
6016
+ for (const h of headerCells) {
6017
+ headers.push(h.replace(/<\/?text:p>/g, "").trim());
6018
+ }
6019
+ const categories = [];
6020
+ const seriesValues = headers.map(() => []);
6021
+ for (let r = 1; r < rowsMatch.length; r++) {
6022
+ const rowStr = rowsMatch[r];
6023
+ if (!rowStr) continue;
6024
+ const cells = rowStr.match(/<table:table-cell[\s\S]*?<\/table:table-cell>/g) || [];
6025
+ if (cells.length > 0 && cells[0]) {
6026
+ const catMatch = cells[0].match(/<text:p>([^<]+)<\/text:p>/);
6027
+ categories.push(catMatch ? catMatch[1] : "Row " + r);
6028
+ for (let c = 1; c < cells.length && c - 1 < headers.length; c++) {
6029
+ const cellStr = cells[c];
6030
+ if (!cellStr) continue;
6031
+ const valMatch = cellStr.match(/office:value="([0-9.]+)"/) || cellStr.match(/<text:p>([0-9.]+)<\/text:p>/);
6032
+ const series = seriesValues[c - 1];
6033
+ if (series) {
6034
+ series.push(valMatch ? parseFloat(valMatch[1]) : 0);
6035
+ }
6036
+ }
6037
+ }
6038
+ }
6039
+ const colors = ["#004586", "#ff420e", "#ffd320", "#579d1c", "#7e0021"];
6040
+ const colorMatches = contentXml.matchAll(/draw:fill-color="(#[0-9a-fA-F]{6})"/g);
6041
+ let cIdx = 0;
6042
+ for (const cm of colorMatches) {
6043
+ if (cIdx < colors.length) colors[cIdx] = cm[1];
6044
+ cIdx++;
6045
+ }
6046
+ let maxVal = 10;
6047
+ for (const s of seriesValues) {
6048
+ for (const v of s) {
6049
+ if (v > maxVal) maxVal = v;
6050
+ }
6051
+ }
6052
+ maxVal = Math.ceil(maxVal * 1.15);
6053
+ const width = 560;
6054
+ const height = 280;
6055
+ const padLeft = 45;
6056
+ const padRight = 100;
6057
+ const padTop = 20;
6058
+ const padBottom = 40;
6059
+ const chartW = width - padLeft - padRight;
6060
+ const chartH = height - padTop - padBottom;
6061
+ 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);">`;
6062
+ for (let step = 0; step <= 4; step++) {
6063
+ const yVal = (maxVal / 4 * step).toFixed(1);
6064
+ const yPos = padTop + chartH - step / 4 * chartH;
6065
+ svg += `<line x1="${padLeft}" y1="${yPos}" x2="${padLeft + chartW}" y2="${yPos}" stroke="#e2e8f0" stroke-dasharray="2,2" />`;
6066
+ svg += `<text x="${padLeft - 8}" y="${yPos + 4}" font-size="11" fill="#64748b" text-anchor="end">${yVal}</text>`;
6067
+ }
6068
+ const numCats = categories.length;
6069
+ const numSeries = headers.length;
6070
+ const groupW = chartW / numCats;
6071
+ const barW = Math.max(8, groupW * 0.7 / numSeries);
6072
+ const groupPad = (groupW - barW * numSeries) / 2;
6073
+ for (let catIdx = 0; catIdx < numCats; catIdx++) {
6074
+ const groupX = padLeft + catIdx * groupW + groupPad;
6075
+ for (let sIdx = 0; sIdx < numSeries; sIdx++) {
6076
+ const val = seriesValues[sIdx][catIdx] || 0;
6077
+ const barH = val / maxVal * chartH;
6078
+ const barX = groupX + sIdx * barW;
6079
+ const barY = padTop + chartH - barH;
6080
+ const col = colors[sIdx % colors.length];
6081
+ svg += `<rect x="${barX}" y="${barY}" width="${barW - 2}" height="${barH}" fill="${col}" rx="2"><title>${headers[sIdx]}: ${val}</title></rect>`;
6082
+ }
6083
+ const catX = padLeft + catIdx * groupW + groupW / 2;
6084
+ svg += `<text x="${catX}" y="${padTop + chartH + 18}" font-size="11" fill="#475569" text-anchor="middle">${categories[catIdx]}</text>`;
6085
+ }
6086
+ let legendY = padTop + 20;
6087
+ for (let sIdx = 0; sIdx < numSeries; sIdx++) {
6088
+ const col = colors[sIdx % colors.length];
6089
+ svg += `<rect x="${padLeft + chartW + 15}" y="${legendY}" width="12" height="12" fill="${col}" rx="2" />`;
6090
+ svg += `<text x="${padLeft + chartW + 32}" y="${legendY + 10}" font-size="11" fill="#334155">${headers[sIdx]}</text>`;
6091
+ legendY += 20;
6092
+ }
6093
+ svg += "</svg>";
6094
+ return svg;
6095
+ } catch (e) {
6096
+ console.warn("[DocPlugin] Error generating chart SVG:", e);
6097
+ return "";
6098
+ }
6099
+ }
6100
+ cleanWordDocFields(text, chartSvg = "") {
5992
6101
  if (!text) return "";
5993
6102
  let cleaned = text.replace(
6103
+ /\x13\s*EMBED\b[\s\S]*?\x15/gi,
6104
+ () => chartSvg ? `
6105
+
6106
+ ${chartSvg}
6107
+
6108
+ ` : ""
6109
+ );
6110
+ cleaned = cleaned.replace(
5994
6111
  /\x13\s*HYPERLINK\s*"?([^"\x14]+)"?\s*\x14([\s\S]*?)\x15/gi,
5995
6112
  (_match, url, label) => {
5996
6113
  const cleanUrl = url.trim();
@@ -5998,18 +6115,23 @@ var DocPlugin = class {
5998
6115
  return `<a href="${cleanUrl}" target="_blank" rel="noopener noreferrer" style="color: #2563eb; text-decoration: underline;">${cleanLabel}</a>`;
5999
6116
  }
6000
6117
  );
6001
- cleaned = cleaned.replace(/\x13[^\x14\x15]*\x14([^\x15]*)\x15/g, "$1");
6118
+ cleaned = cleaned.replace(/\x13[^\x14\x15]*\x14([^\x15]*)\x15/g, (_m, res) => {
6119
+ if (/[\x00-\x1F]/.test(res)) return "";
6120
+ return res.trim();
6121
+ });
6002
6122
  cleaned = cleaned.replace(/\x13[^\x15]*\x15/g, "");
6003
6123
  cleaned = cleaned.replace(/[\x13\x14\x15]/g, "");
6124
+ cleaned = cleaned.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/g, "");
6125
+ cleaned = cleaned.replace(/EMBED\s+LibreOffice\.ChartDocument\.[0-9]+/gi, chartSvg || "");
6004
6126
  return cleaned;
6005
6127
  }
6006
- splitIntoPages(text) {
6128
+ splitIntoPages(text, chartSvg = "") {
6007
6129
  if (!text) return [""];
6008
- const cleanedText = this.cleanWordDocFields(text);
6130
+ const cleanedText = this.cleanWordDocFields(text, chartSvg);
6009
6131
  const normalized = cleanedText.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x07\n/g, "\n").replace(/\x07/g, " ");
6010
6132
  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);
6011
6133
  if (explicitParts.length === 0) explicitParts.push(normalized);
6012
- const maxLinesPerPage = 32;
6134
+ const maxLinesPerPage = 34;
6013
6135
  const charsPerLine = 80;
6014
6136
  const finalPages = [];
6015
6137
  for (const part of explicitParts) {
@@ -6017,8 +6139,8 @@ var DocPlugin = class {
6017
6139
  let currentLines = [];
6018
6140
  let count = 0;
6019
6141
  for (const line of lines) {
6020
- const plainLine = line.replace(/<[^>]+>/g, "");
6021
- const vLines = Math.max(1, Math.ceil((plainLine.length || 1) / charsPerLine));
6142
+ const isSvg = line.includes("<svg");
6143
+ const vLines = isSvg ? 12 : Math.max(1, Math.ceil((line.replace(/<[^>]+>/g, "").length || 1) / charsPerLine));
6022
6144
  if (count + vLines > maxLinesPerPage && currentLines.length > 0) {
6023
6145
  finalPages.push(currentLines.join("\n"));
6024
6146
  currentLines = [];
@@ -6058,9 +6180,44 @@ var DocPlugin = class {
6058
6180
  tableLines = [];
6059
6181
  }
6060
6182
  };
6183
+ const sanitizeOptions = {
6184
+ ADD_TAGS: ["a", "svg", "g", "path", "line", "rect", "circle", "text", "title"],
6185
+ ADD_ATTR: [
6186
+ "href",
6187
+ "target",
6188
+ "rel",
6189
+ "style",
6190
+ "viewBox",
6191
+ "width",
6192
+ "height",
6193
+ "x",
6194
+ "y",
6195
+ "x1",
6196
+ "y1",
6197
+ "x2",
6198
+ "y2",
6199
+ "fill",
6200
+ "stroke",
6201
+ "stroke-width",
6202
+ "stroke-dasharray",
6203
+ "rx",
6204
+ "font-size",
6205
+ "text-anchor"
6206
+ ]
6207
+ };
6061
6208
  let i = 0;
6062
6209
  while (i < lines.length) {
6063
6210
  let line = lines[i];
6211
+ if (line.includes("<svg")) {
6212
+ if (inList) {
6213
+ html += "</ul>";
6214
+ inList = false;
6215
+ }
6216
+ flushTable();
6217
+ html += line;
6218
+ i++;
6219
+ continue;
6220
+ }
6064
6221
  let tabCount = (line.match(/\t/g) || []).length;
6065
6222
  if (tabCount > 0) {
6066
6223
  let j = i;
@@ -6094,7 +6251,6 @@ var DocPlugin = class {
6094
6251
  i++;
6095
6252
  continue;
6096
6253
  }
6097
- const sanitizeOptions = { ADD_TAGS: ["a"], ADD_ATTR: ["href", "target", "rel", "style"] };
6098
6254
  if (line.length < 60 && !line.endsWith(".") && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#"))) {
6099
6255
  if (inList) {
6100
6256
  html += "</ul>";