wysiwygido 1.3.0 → 1.4.0
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/CHANGELOG.md +6 -0
- package/README.md +4 -2
- package/dist/adapter.js +1 -1
- package/dist/{chart-D61cP2Zp.js → chart-CEPd_jSz.js} +2 -139
- package/dist/chart-CEPd_jSz.js.map +1 -0
- package/dist/crop-oLrgb-A3.js +140 -0
- package/dist/crop-oLrgb-A3.js.map +1 -0
- package/dist/{data-table-CgmN4xc4.js → data-table-C4bo747f.js} +19 -8
- package/dist/{data-table-CgmN4xc4.js.map → data-table-C4bo747f.js.map} +1 -1
- package/dist/{data-table-app-B2_xsug1.js → data-table-app-BCG8N37n.js} +2 -2
- package/dist/{data-table-app-B2_xsug1.js.map → data-table-app-BCG8N37n.js.map} +1 -1
- package/dist/data-table.d.ts +13 -0
- package/dist/data-table.js +2 -2
- package/dist/docx.js +3 -2
- package/dist/docx.js.map +1 -1
- package/dist/{grid-formulas-CFGWbz2E.js → grid-formulas-DcdcPnK7.js} +21 -1
- package/dist/grid-formulas-DcdcPnK7.js.map +1 -0
- package/dist/grid-formulas.d.ts +2 -0
- package/dist/{office-chrome-BmnDYfog.js → office-chrome-BTBP-K4h.js} +4 -1
- package/dist/office-chrome-BTBP-K4h.js.map +1 -0
- package/dist/presentation-app.d.ts +11 -2
- package/dist/presentation-pptx.d.ts +15 -0
- package/dist/presentation.js +1496 -5
- package/dist/presentation.js.map +1 -1
- package/dist/react.js +1 -1
- package/dist/spreadsheet-app.d.ts +17 -0
- package/dist/spreadsheet-dialog.d.ts +39 -0
- package/dist/spreadsheet-features.d.ts +112 -0
- package/dist/spreadsheet-model.d.ts +40 -0
- package/dist/spreadsheet-view.d.ts +10 -0
- package/dist/spreadsheet.js +1736 -131
- package/dist/spreadsheet.js.map +1 -1
- package/dist/{src-D4NV1D9Y.js → src-CYQYge8P.js} +6 -5
- package/dist/{src-D4NV1D9Y.js.map → src-CYQYge8P.js.map} +1 -1
- package/dist/style.css +26 -1
- package/dist/vue.js +1 -1
- package/dist/wysiwygido.js +6 -5
- package/package.json +1 -1
- package/dist/chart-D61cP2Zp.js.map +0 -1
- package/dist/grid-formulas-CFGWbz2E.js.map +0 -1
- package/dist/office-chrome-BmnDYfog.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses [Semantic Versioning](https://semver.org/). Before 1.0.0 minor versions could contain breaking changes. From 1.0.0 on, only a major version may break the public API (see "Stability and versioning" in the README); changes are listed under "Changed" or "Removed".
|
|
4
4
|
|
|
5
|
+
## 1.4.0 - 2026-10-25
|
|
6
|
+
|
|
7
|
+
- **Presentation: PowerPoint (.pptx) import and export.** File menu entries "Open .pptx…" and "Download as PowerPoint (.pptx)"; `toPptx()` and `loadPptx(file)`; an `import` event (`{ slides, skipped }`). Strict zip and XML reader (size, entry count and compression limits, no DOCTYPE or entities, path checks); everything read goes through `cleanDeck`.
|
|
8
|
+
|
|
9
|
+
- **Spreadsheet: merged cells, conditional formats and charts.** Merge, merge across and unmerge (the selection, the arrow keys, the editor and printing treat a merge as one cell); conditional formatting with highlight rules, top and bottom, above and below average, duplicates, blanks, errors, data bars, colour scales and formula rules, and a rules list; column, line and pie charts you can move, resize, edit and delete. All saved in the workbook, followed through inserted and deleted rows and columns, undoable, limited in number (2,000 merges, 100 rules and 50 charts per sheet) and checked when loaded. New API: `merge`, `unmerge`, `addConditionalFormat`, `clearConditionalFormats`, `addChart`, `removeChart`.
|
|
10
|
+
|
|
5
11
|
## 1.3.0 - 2026-10-25
|
|
6
12
|
|
|
7
13
|
- **Security audit, second part**: pasted HTML can no longer carry tracked-change authors, mention ids or comment ids; an embed from the page's own origin never gets `allow-same-origin`; `encryptedDraftStore`, `deriveDraftKey` and `IndexedDBDraftStore.clear()` for drafts on shared computers; a regex replacement keeps lookbehind and anchors (`$1` is expanded from the match as found); adding a comment finds its words again across bold or italic runs; rejected pictures and unreadable files are reported (`image-rejected`, `error`, `import` events, no more silent failures); pictures over 25 MB are refused; optional peers `katex >=0.18.2`, `mermaid >=12.1.0`, `vue >=3.5.0`; `publint` and `attw` are pinned in `check:package`.
|
package/README.md
CHANGED
|
@@ -623,7 +623,9 @@ sheet.getValue('C1'); // 14400 + 1200
|
|
|
623
623
|
sheet.toCSV(); sheet.getData(); sheet.setMenu('floating');
|
|
624
624
|
```
|
|
625
625
|
|
|
626
|
-
The two **menus** share the same commands. The classic ribbon has tabs with labelled groups; the floating menu is a card with a header (document name, save status, your own `end` element for avatars or a Share button), a menu bar with drop-downs, and a one-row toolbar whose last items fold into "⋯" when the window is narrow. `menu` also takes `'menubar'` (the card with only its drop-down menus), `'formula'` (only the formula editor) and `'none'`; `setMenu(mode)` changes it while running and `setFormulaBar(false)` hides the formula editor. Options: `theme` (`'light'`, `'dark'`, `'auto'` or any colour theme of the editor), `readOnly`, `height`, `formulaBar`, `sheetTabs`, `statusBar`, `gridlines`, `headings`, `currency`, `locale`, `onChange`. Not included:
|
|
626
|
+
The two **menus** share the same commands. The classic ribbon has tabs with labelled groups; the floating menu is a card with a header (document name, save status, your own `end` element for avatars or a Share button), a menu bar with drop-downs, and a one-row toolbar whose last items fold into "⋯" when the window is narrow. `menu` also takes `'menubar'` (the card with only its drop-down menus), `'formula'` (only the formula editor) and `'none'`; `setMenu(mode)` changes it while running and `setFormulaBar(false)` hides the formula editor. Options: `theme` (`'light'`, `'dark'`, `'auto'` or any colour theme of the editor), `readOnly`, `height`, `formulaBar`, `sheetTabs`, `statusBar`, `gridlines`, `headings`, `currency`, `locale`, `onChange`. Not included: defined names in the UI, `LAMBDA` and its helpers, complex-number, bond, cube and web functions (those give `#NAME?`), and pivot tables.
|
|
627
|
+
|
|
628
|
+
**Merged cells, conditional formats and charts.** Merge cells from the alignment group (Merge cells, Merge across, Unmerge); the top left value stays and the rest is emptied; selections and arrow keys treat a merge as one cell, and rows or columns inserted or deleted move it. Conditional formatting (Home, Styles) has highlight rules (greater than, less than, between, equal to, text that contains, duplicate or unique values, blanks, errors), top and bottom N or N percent, above and below average, data bars, two- and three-colour scales and formula rules, with a list to manage them. Charts (Insert, Chart) are columns, lines or pies drawn from a range (first row names the series, first column the labels); drag to move, drag the corner to resize, double-click to edit, Delete to remove. Everything is saved with the workbook, follows inserted rows and columns, is checked on load, and prints (charts after the table). From code: `sheet.merge('A1:C1')`, `sheet.addConditionalFormat('B2:B20', { type: 'cell', op: '>', a: '100', style: { bg: '#fecaca' } })`, `sheet.addChart({ range: 'A1:C13', type: 'line', title: 'Sales' })`. Copy and paste does not carry merges, rules or charts to another place.
|
|
627
629
|
|
|
628
630
|
## Presentation
|
|
629
631
|
|
|
@@ -644,7 +646,7 @@ deck.present(0); // deck.present(0, true) opens the presente
|
|
|
644
646
|
const page = deck.toHTML(); // everything in one file
|
|
645
647
|
```
|
|
646
648
|
|
|
647
|
-
Everything that comes from outside (a loaded file, pasted text, a dropped picture) is cleaned: text keeps a short list of tags and styles, links keep `http(s)`, `mailto` and `tel`, pictures must be `data:` images or `http(s)`, so a deck from someone else cannot run code.
|
|
649
|
+
Everything that comes from outside (a loaded file, pasted text, a dropped picture) is cleaned: text keeps a short list of tags and styles, links keep `http(s)`, `mailto` and `tel`, pictures must be `data:` images or `http(s)`, so a deck from someone else cannot run code. **PowerPoint files** open and save (File, Open .pptx / Download as PowerPoint, or `deck.loadPptx(file)` and `deck.toPptx()`): text boxes and shapes with their text formatting, bullets, links, pictures (png, jpeg, gif, webp on import; svg is drawn to png on export), notes, backgrounds, hidden slides and fade, slide and zoom transitions. Tables, charts, SmartArt, video, audio and animations are skipped and listed in `skipped` (and the `import` event). Gradients become their first colour, master text styles are not read, and a file is opened in a strict reader: no more than 2,000 parts or 150 MB unpacked, no entity declarations, and links only to `http(s)`, `mailto` and `tel`. Files written here read back faithfully and open in a strict parser (python-pptx); they have not been opened in PowerPoint, Keynote or Google Slides.
|
|
648
650
|
|
|
649
651
|
## Themes
|
|
650
652
|
|
package/dist/adapter.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as createEditor } from "./src-
|
|
1
|
+
import { t as createEditor } from "./src-CYQYge8P.js";
|
|
2
2
|
// src/adapter.ts
|
|
3
3
|
/**
|
|
4
4
|
* Framework-neutral binding for anything that needs a "value in, value out" control: Angular's `ControlValueAccessor`, Svelte, Solid,
|
|
@@ -1,140 +1,3 @@
|
|
|
1
|
-
// src/url.ts
|
|
2
|
-
/** Where a link may point: http(s), mailto, tel, an in-page anchor or a same-site path. `//host` (protocol-relative) is not allowed. */
|
|
3
|
-
var HREF = /^(?:https?:|mailto:|tel:|#|\/(?![\/\\\t\r\n]))/i;
|
|
4
|
-
/** Where an image may come from: http(s), a same-site path, or a base64 raster data URL (not SVG). */
|
|
5
|
-
var SRC = /^(?:https?:|data:image\/(?:png|jpe?g|gif|webp|bmp|avif);base64,|\/(?![\/\\\t\r\n]))/i;
|
|
6
|
-
var isSafeHref = (u) => typeof u === "string" && HREF.test(u);
|
|
7
|
-
var isSafeSrc = (u) => typeof u === "string" && SRC.test(u);
|
|
8
|
-
/** Make a URL safe to place inside Markdown `(...)`: characters that end or split the destination are percent-encoded. */
|
|
9
|
-
var markdownUrl = (u) => u.replace(/[()\s<>\\]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`);
|
|
10
|
-
// src/crop.ts
|
|
11
|
-
/** The smallest visible share of the image on either axis (so a crop can never collapse to nothing). */
|
|
12
|
-
var MIN_VISIBLE = .05;
|
|
13
|
-
var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
|
14
|
-
var round = (v) => Math.round(v * 1e4) / 1e4;
|
|
15
|
-
/** Clamp a crop into a valid range; null means "no crop". Junk values are treated as 0. */
|
|
16
|
-
function normalizeCrop(input) {
|
|
17
|
-
if (!input || typeof input !== "object") return null;
|
|
18
|
-
const n = (v) => typeof v === "number" && Number.isFinite(v) ? clamp(v, 0, .95) : 0;
|
|
19
|
-
let { left, top, right, bottom } = {
|
|
20
|
-
left: n(input.left),
|
|
21
|
-
top: n(input.top),
|
|
22
|
-
right: n(input.right),
|
|
23
|
-
bottom: n(input.bottom)
|
|
24
|
-
};
|
|
25
|
-
const fit = (a, b) => {
|
|
26
|
-
const excess = a + b - .95;
|
|
27
|
-
if (excess <= 0) return [a, b];
|
|
28
|
-
return a >= b ? [a - excess, b] : [a, b - excess];
|
|
29
|
-
};
|
|
30
|
-
[left, right] = fit(left, right);
|
|
31
|
-
[top, bottom] = fit(top, bottom);
|
|
32
|
-
const c = {
|
|
33
|
-
left: round(left),
|
|
34
|
-
top: round(top),
|
|
35
|
-
right: round(right),
|
|
36
|
-
bottom: round(bottom)
|
|
37
|
-
};
|
|
38
|
-
return c.left || c.top || c.right || c.bottom ? c : null;
|
|
39
|
-
}
|
|
40
|
-
/** Natural size of the visible region (what a crop leaves), in natural px. */
|
|
41
|
-
function visibleSize(crop, nat) {
|
|
42
|
-
const c = crop ?? {
|
|
43
|
-
left: 0,
|
|
44
|
-
top: 0,
|
|
45
|
-
right: 0,
|
|
46
|
-
bottom: 0
|
|
47
|
-
};
|
|
48
|
-
return {
|
|
49
|
-
w: nat.w * (1 - c.left - c.right),
|
|
50
|
-
h: nat.h * (1 - c.top - c.bottom)
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
/** Lay out a crop so the visible box is `displayWidth` px wide. */
|
|
54
|
-
function cropLayout(crop, nat, displayWidth) {
|
|
55
|
-
const vw = 1 - crop.left - crop.right;
|
|
56
|
-
const vh = 1 - crop.top - crop.bottom;
|
|
57
|
-
const width = displayWidth;
|
|
58
|
-
const height = width * (vh * nat.h) / (vw * nat.w);
|
|
59
|
-
const imgWidth = width / vw;
|
|
60
|
-
const imgHeight = height / vh;
|
|
61
|
-
return {
|
|
62
|
-
width,
|
|
63
|
-
height,
|
|
64
|
-
imgWidth,
|
|
65
|
-
imgHeight,
|
|
66
|
-
offsetX: -crop.left * imgWidth,
|
|
67
|
-
offsetY: -crop.top * imgHeight
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
/** Serialize as "left,top,right,bottom" for a data attribute. */
|
|
71
|
-
var cropToString = (c) => `${c.left},${c.top},${c.right},${c.bottom}`;
|
|
72
|
-
function cropFromString(s) {
|
|
73
|
-
if (!s) return null;
|
|
74
|
-
const p = s.split(",").map(Number);
|
|
75
|
-
if (p.length !== 4 || p.some((x) => !Number.isFinite(x) || x < 0 || x >= 1)) return null;
|
|
76
|
-
return normalizeCrop({
|
|
77
|
-
left: p[0],
|
|
78
|
-
top: p[1],
|
|
79
|
-
right: p[2],
|
|
80
|
-
bottom: p[3]
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Apply a drag of (dx, dy) fractions of the full image to a crop rectangle. Edges move independently,
|
|
85
|
-
* `move` slides the whole rectangle without resizing it. Result is always valid.
|
|
86
|
-
*/
|
|
87
|
-
function dragCrop(start, handle, dx, dy) {
|
|
88
|
-
let { left, top, right, bottom } = start;
|
|
89
|
-
if (handle === "move") {
|
|
90
|
-
const w = 1 - left - right;
|
|
91
|
-
const h = 1 - top - bottom;
|
|
92
|
-
left = clamp(left + dx, 0, 1 - w);
|
|
93
|
-
top = clamp(top + dy, 0, 1 - h);
|
|
94
|
-
right = 1 - w - left;
|
|
95
|
-
bottom = 1 - h - top;
|
|
96
|
-
} else {
|
|
97
|
-
if (handle.includes("w")) left = clamp(left + dx, 0, 1 - right - MIN_VISIBLE);
|
|
98
|
-
if (handle.includes("e")) right = clamp(right - dx, 0, 1 - left - MIN_VISIBLE);
|
|
99
|
-
if (handle.includes("n")) top = clamp(top + dy, 0, 1 - bottom - MIN_VISIBLE);
|
|
100
|
-
if (handle.includes("s")) bottom = clamp(bottom - dy, 0, 1 - top - MIN_VISIBLE);
|
|
101
|
-
}
|
|
102
|
-
return {
|
|
103
|
-
left: round(left),
|
|
104
|
-
top: round(top),
|
|
105
|
-
right: round(right),
|
|
106
|
-
bottom: round(bottom)
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Render the cropped region of an image to PNG bytes (browser only: needs canvas). Returns null when canvas
|
|
111
|
-
* is unavailable, so callers can fall back to the full image.
|
|
112
|
-
*/
|
|
113
|
-
async function cropBytes(data, crop) {
|
|
114
|
-
try {
|
|
115
|
-
if (typeof createImageBitmap !== "function") return null;
|
|
116
|
-
const bmp = await createImageBitmap(new Blob([data]));
|
|
117
|
-
const sx = Math.round(bmp.width * crop.left);
|
|
118
|
-
const sy = Math.round(bmp.height * crop.top);
|
|
119
|
-
const sw = Math.max(1, Math.round(bmp.width * (1 - crop.left - crop.right)));
|
|
120
|
-
const sh = Math.max(1, Math.round(bmp.height * (1 - crop.top - crop.bottom)));
|
|
121
|
-
const canvas = typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(sw, sh) : Object.assign(document.createElement("canvas"), {
|
|
122
|
-
width: sw,
|
|
123
|
-
height: sh
|
|
124
|
-
});
|
|
125
|
-
const ctx = canvas.getContext("2d");
|
|
126
|
-
if (!ctx) return null;
|
|
127
|
-
ctx.drawImage(bmp, sx, sy, sw, sh, 0, 0, sw, sh);
|
|
128
|
-
const blob = "convertToBlob" in canvas ? await canvas.convertToBlob({ type: "image/png" }) : await new Promise((r) => canvas.toBlob(r, "image/png"));
|
|
129
|
-
return blob ? {
|
|
130
|
-
data: new Uint8Array(await blob.arrayBuffer()),
|
|
131
|
-
width: sw,
|
|
132
|
-
height: sh
|
|
133
|
-
} : null;
|
|
134
|
-
} catch {
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
1
|
// src/chart.ts
|
|
139
2
|
var TYPES = [
|
|
140
3
|
"bar",
|
|
@@ -282,6 +145,6 @@ function chartSVG(spec, width = 560, height = 320) {
|
|
|
282
145
|
if (legendH) parts.push(legend(spec.series.map((s) => s.name)));
|
|
283
146
|
return `${head}${parts.join("")}</svg>`;
|
|
284
147
|
}
|
|
285
|
-
export {
|
|
148
|
+
export { specFromRows as a, parseNumber as i, chartSVG as n, cleanChart as r, CHART_COLORS as t };
|
|
286
149
|
|
|
287
|
-
//# sourceMappingURL=chart-
|
|
150
|
+
//# sourceMappingURL=chart-CEPd_jSz.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chart-CEPd_jSz.js","names":[],"sources":["../src/chart.ts"],"sourcesContent":["export type ChartType = 'bar' | 'line' | 'pie';\nexport interface ChartSpec { type: ChartType; title: string; labels: string[]; series: { name: string; values: number[] }[] }\n\nconst TYPES: ChartType[] = ['bar', 'line', 'pie'];\nexport const CHART_COLORS = ['#2563eb', '#ea580c', '#16a34a', '#9333ea', '#dc2626', '#0891b2', '#ca8a04', '#db2777'];\nconst esc = (s: string) => s.replace(/[&<>\"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '\"': '"', \"'\": ''' })[c]!);\nconst str = (v: unknown, max: number) => (typeof v === 'string' ? v.replace(/[\\u0000-\\u001f]/g, ' ').slice(0, max) : '');\n\n/** A well-formed spec from untrusted input (JSON text or an object), or null when there is nothing to draw. */\nexport function cleanChart(input: unknown): ChartSpec | null {\n let o: any = input;\n if (typeof input === 'string') { try { o = JSON.parse(input); } catch { return null; } }\n if (!o || typeof o !== 'object') return null;\n const labels = (Array.isArray(o.labels) ? o.labels : []).slice(0, 50).map((l: unknown) => str(l, 40));\n const series = (Array.isArray(o.series) ? o.series : []).slice(0, 8).map((s: any) => ({\n name: str(s?.name, 40),\n values: labels.map((_: string, i: number) => { const n = Number(s?.values?.[i]); return Number.isFinite(n) ? Math.max(-1e12, Math.min(1e12, n)) : 0; }),\n }));\n if (!labels.length || !series.length) return null;\n return { type: TYPES.includes(o.type) ? o.type : 'bar', title: str(o.title, 120), labels, series };\n}\n\n/** Parse \"1,234.5\", \"12%\", \"$40\" … ; NaN when it is not a number. */\nexport function parseNumber(text: string): number {\n const t = text.trim().replace(/[\\s$€£¥%]/g, '').replace(/,(?=\\d{3}(\\D|$))/g, '');\n return t && /^-?\\d*\\.?\\d+(e[+-]?\\d+)?$/i.test(t) ? Number(t) : NaN;\n}\n\n/** Chart data from a table: first row = series names, first column = labels, the rest numbers (a first row of numbers means no header). */\nexport function specFromRows(rows: string[][], type: ChartType = 'bar'): ChartSpec | null {\n if (rows.length < 2 || rows[0].length < 2) return null;\n const header = rows[0];\n const body = rows.slice(1);\n const cols = header.length - 1;\n const series = Array.from({ length: cols }, (_, c) => ({ name: header[c + 1] || `Series ${c + 1}`, values: body.map((r) => { const n = parseNumber(r[c + 1] ?? ''); return Number.isNaN(n) ? 0 : n; }) }));\n if (!series.some((s) => s.values.some((v) => v !== 0))) return null;\n return cleanChart({ type, title: '', labels: body.map((r) => r[0] ?? ''), series });\n}\n\nconst nice = (max: number) => { if (max <= 0) return 1; const p = 10 ** Math.floor(Math.log10(max)); const f = max / p; return (f <= 1 ? 1 : f <= 2 ? 2 : f <= 5 ? 5 : 10) * p; };\nconst fmt = (n: number) => (Math.abs(n) >= 1e6 ? `${+(n / 1e6).toFixed(1)}M` : Math.abs(n) >= 1e4 ? `${+(n / 1e3).toFixed(1)}k` : String(+n.toFixed(2)));\n\n/** Self-contained SVG (text in `currentColor`, fixed series colours) with an accessible title and description. */\nexport function chartSVG(spec: ChartSpec, width = 560, height = 320): string {\n const W = width, H = height;\n const head = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 ${W} ${H}\" width=\"${W}\" height=\"${H}\" role=\"img\" aria-label=\"${esc(spec.title || `${spec.type} chart`)}\" font-family=\"system-ui, sans-serif\" font-size=\"12\">`;\n const parts: string[] = [`<title>${esc(spec.title || `${spec.type} chart`)}</title>`];\n const top = spec.title ? 34 : 14;\n if (spec.title) parts.push(`<text x=\"${W / 2}\" y=\"22\" text-anchor=\"middle\" font-size=\"15\" font-weight=\"700\" fill=\"currentColor\">${esc(spec.title)}</text>`);\n const legendH = spec.type === 'pie' || spec.series.length > 1 ? 24 : 0;\n const color = (i: number) => CHART_COLORS[i % CHART_COLORS.length];\n const legend = (items: string[]) => {\n let x = 12;\n return items.map((n, i) => { const g = `<rect x=\"${x}\" y=\"${H - 18}\" width=\"10\" height=\"10\" rx=\"2\" fill=\"${color(i)}\"/><text x=\"${x + 14}\" y=\"${H - 9}\" fill=\"currentColor\">${esc(n)}</text>`; x += 26 + n.length * 7; return g; }).join('');\n };\n if (spec.type === 'pie') {\n const vals = spec.series[0].values.map((v) => Math.max(0, v));\n const total = vals.reduce((a, b) => a + b, 0) || 1;\n const cx = W / 2, cy = top + (H - top - legendH - 8) / 2, r = Math.min(W / 2 - 20, (H - top - legendH - 16) / 2);\n let a0 = -Math.PI / 2;\n vals.forEach((v, i) => {\n if (v <= 0) return;\n const a1 = a0 + (v / total) * Math.PI * 2;\n const big = a1 - a0 > Math.PI ? 1 : 0;\n const p = (a: number) => `${(cx + r * Math.cos(a)).toFixed(2)} ${(cy + r * Math.sin(a)).toFixed(2)}`;\n parts.push(v / total >= 0.9999 ? `<circle cx=\"${cx}\" cy=\"${cy}\" r=\"${r}\" fill=\"${color(i)}\"/>` : `<path d=\"M${cx} ${cy} L${p(a0)} A${r} ${r} 0 ${big} 1 ${p(a1)} Z\" fill=\"${color(i)}\" stroke=\"#fff\" stroke-width=\"1.5\"/>`);\n if (v / total > 0.06) { const am = (a0 + a1) / 2; parts.push(`<text x=\"${(cx + r * 0.62 * Math.cos(am)).toFixed(1)}\" y=\"${(cy + r * 0.62 * Math.sin(am) + 4).toFixed(1)}\" text-anchor=\"middle\" fill=\"#fff\" font-weight=\"700\">${Math.round((v / total) * 100)}%</text>`); }\n a0 = a1;\n });\n parts.push(legend(spec.labels));\n return `${head}${parts.join('')}</svg>`;\n }\n const left = 46, right = 14, bottom = 28 + legendH;\n const pw = W - left - right, ph = H - top - bottom;\n const all = spec.series.flatMap((s) => s.values);\n const max = nice(Math.max(0, ...all)), min = Math.min(0, ...all) < 0 ? -nice(-Math.min(...all)) : 0;\n const y = (v: number) => top + ph - ((v - min) / (max - min)) * ph;\n for (let i = 0; i <= 4; i++) {\n const v = min + ((max - min) * i) / 4, yy = y(v);\n parts.push(`<line x1=\"${left}\" x2=\"${W - right}\" y1=\"${yy.toFixed(1)}\" y2=\"${yy.toFixed(1)}\" stroke=\"currentColor\" stroke-opacity=\".15\"/><text x=\"${left - 6}\" y=\"${(yy + 4).toFixed(1)}\" text-anchor=\"end\" fill=\"currentColor\" fill-opacity=\".7\">${fmt(v)}</text>`);\n }\n const n = spec.labels.length, slot = pw / n;\n spec.labels.forEach((l, i) => { if (n <= 12 || i % Math.ceil(n / 12) === 0) parts.push(`<text x=\"${(left + slot * (i + 0.5)).toFixed(1)}\" y=\"${top + ph + 16}\" text-anchor=\"middle\" fill=\"currentColor\" fill-opacity=\".8\">${esc(l.length > 12 ? `${l.slice(0, 11)}…` : l)}</text>`); });\n if (spec.type === 'bar') {\n const bw = Math.min(40, (slot * 0.76) / spec.series.length);\n spec.series.forEach((s, si) => s.values.forEach((v, i) => {\n const x = left + slot * (i + 0.5) - (bw * spec.series.length) / 2 + bw * si;\n parts.push(`<rect x=\"${x.toFixed(1)}\" y=\"${Math.min(y(v), y(0)).toFixed(1)}\" width=\"${bw.toFixed(1)}\" height=\"${Math.abs(y(v) - y(0)).toFixed(1)}\" rx=\"2\" fill=\"${color(si)}\"><title>${esc(s.name)} · ${esc(spec.labels[i])}: ${v}</title></rect>`);\n }));\n } else {\n spec.series.forEach((s, si) => {\n const pts = s.values.map((v, i) => `${(left + slot * (i + 0.5)).toFixed(1)},${y(v).toFixed(1)}`);\n parts.push(`<polyline points=\"${pts.join(' ')}\" fill=\"none\" stroke=\"${color(si)}\" stroke-width=\"2.5\" stroke-linejoin=\"round\"/>`);\n s.values.forEach((v, i) => parts.push(`<circle cx=\"${(left + slot * (i + 0.5)).toFixed(1)}\" cy=\"${y(v).toFixed(1)}\" r=\"3.5\" fill=\"${color(si)}\"><title>${esc(s.name)} · ${esc(spec.labels[i])}: ${v}</title></circle>`));\n });\n }\n parts.push(`<line x1=\"${left}\" x2=\"${W - right}\" y1=\"${y(0).toFixed(1)}\" y2=\"${y(0).toFixed(1)}\" stroke=\"currentColor\" stroke-opacity=\".5\"/>`);\n if (legendH) parts.push(legend(spec.series.map((s) => s.name)));\n return `${head}${parts.join('')}</svg>`;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// src/url.ts
|
|
2
|
+
/** Where a link may point: http(s), mailto, tel, an in-page anchor or a same-site path. `//host` (protocol-relative) is not allowed. */
|
|
3
|
+
var HREF = /^(?:https?:|mailto:|tel:|#|\/(?![\/\\\t\r\n]))/i;
|
|
4
|
+
/** Where an image may come from: http(s), a same-site path, or a base64 raster data URL (not SVG). */
|
|
5
|
+
var SRC = /^(?:https?:|data:image\/(?:png|jpe?g|gif|webp|bmp|avif);base64,|\/(?![\/\\\t\r\n]))/i;
|
|
6
|
+
var isSafeHref = (u) => typeof u === "string" && HREF.test(u);
|
|
7
|
+
var isSafeSrc = (u) => typeof u === "string" && SRC.test(u);
|
|
8
|
+
/** Make a URL safe to place inside Markdown `(...)`: characters that end or split the destination are percent-encoded. */
|
|
9
|
+
var markdownUrl = (u) => u.replace(/[()\s<>\\]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`);
|
|
10
|
+
// src/crop.ts
|
|
11
|
+
/** The smallest visible share of the image on either axis (so a crop can never collapse to nothing). */
|
|
12
|
+
var MIN_VISIBLE = .05;
|
|
13
|
+
var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
|
14
|
+
var round = (v) => Math.round(v * 1e4) / 1e4;
|
|
15
|
+
/** Clamp a crop into a valid range; null means "no crop". Junk values are treated as 0. */
|
|
16
|
+
function normalizeCrop(input) {
|
|
17
|
+
if (!input || typeof input !== "object") return null;
|
|
18
|
+
const n = (v) => typeof v === "number" && Number.isFinite(v) ? clamp(v, 0, .95) : 0;
|
|
19
|
+
let { left, top, right, bottom } = {
|
|
20
|
+
left: n(input.left),
|
|
21
|
+
top: n(input.top),
|
|
22
|
+
right: n(input.right),
|
|
23
|
+
bottom: n(input.bottom)
|
|
24
|
+
};
|
|
25
|
+
const fit = (a, b) => {
|
|
26
|
+
const excess = a + b - .95;
|
|
27
|
+
if (excess <= 0) return [a, b];
|
|
28
|
+
return a >= b ? [a - excess, b] : [a, b - excess];
|
|
29
|
+
};
|
|
30
|
+
[left, right] = fit(left, right);
|
|
31
|
+
[top, bottom] = fit(top, bottom);
|
|
32
|
+
const c = {
|
|
33
|
+
left: round(left),
|
|
34
|
+
top: round(top),
|
|
35
|
+
right: round(right),
|
|
36
|
+
bottom: round(bottom)
|
|
37
|
+
};
|
|
38
|
+
return c.left || c.top || c.right || c.bottom ? c : null;
|
|
39
|
+
}
|
|
40
|
+
/** Natural size of the visible region (what a crop leaves), in natural px. */
|
|
41
|
+
function visibleSize(crop, nat) {
|
|
42
|
+
const c = crop ?? {
|
|
43
|
+
left: 0,
|
|
44
|
+
top: 0,
|
|
45
|
+
right: 0,
|
|
46
|
+
bottom: 0
|
|
47
|
+
};
|
|
48
|
+
return {
|
|
49
|
+
w: nat.w * (1 - c.left - c.right),
|
|
50
|
+
h: nat.h * (1 - c.top - c.bottom)
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** Lay out a crop so the visible box is `displayWidth` px wide. */
|
|
54
|
+
function cropLayout(crop, nat, displayWidth) {
|
|
55
|
+
const vw = 1 - crop.left - crop.right;
|
|
56
|
+
const vh = 1 - crop.top - crop.bottom;
|
|
57
|
+
const width = displayWidth;
|
|
58
|
+
const height = width * (vh * nat.h) / (vw * nat.w);
|
|
59
|
+
const imgWidth = width / vw;
|
|
60
|
+
const imgHeight = height / vh;
|
|
61
|
+
return {
|
|
62
|
+
width,
|
|
63
|
+
height,
|
|
64
|
+
imgWidth,
|
|
65
|
+
imgHeight,
|
|
66
|
+
offsetX: -crop.left * imgWidth,
|
|
67
|
+
offsetY: -crop.top * imgHeight
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** Serialize as "left,top,right,bottom" for a data attribute. */
|
|
71
|
+
var cropToString = (c) => `${c.left},${c.top},${c.right},${c.bottom}`;
|
|
72
|
+
function cropFromString(s) {
|
|
73
|
+
if (!s) return null;
|
|
74
|
+
const p = s.split(",").map(Number);
|
|
75
|
+
if (p.length !== 4 || p.some((x) => !Number.isFinite(x) || x < 0 || x >= 1)) return null;
|
|
76
|
+
return normalizeCrop({
|
|
77
|
+
left: p[0],
|
|
78
|
+
top: p[1],
|
|
79
|
+
right: p[2],
|
|
80
|
+
bottom: p[3]
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Apply a drag of (dx, dy) fractions of the full image to a crop rectangle. Edges move independently,
|
|
85
|
+
* `move` slides the whole rectangle without resizing it. Result is always valid.
|
|
86
|
+
*/
|
|
87
|
+
function dragCrop(start, handle, dx, dy) {
|
|
88
|
+
let { left, top, right, bottom } = start;
|
|
89
|
+
if (handle === "move") {
|
|
90
|
+
const w = 1 - left - right;
|
|
91
|
+
const h = 1 - top - bottom;
|
|
92
|
+
left = clamp(left + dx, 0, 1 - w);
|
|
93
|
+
top = clamp(top + dy, 0, 1 - h);
|
|
94
|
+
right = 1 - w - left;
|
|
95
|
+
bottom = 1 - h - top;
|
|
96
|
+
} else {
|
|
97
|
+
if (handle.includes("w")) left = clamp(left + dx, 0, 1 - right - MIN_VISIBLE);
|
|
98
|
+
if (handle.includes("e")) right = clamp(right - dx, 0, 1 - left - MIN_VISIBLE);
|
|
99
|
+
if (handle.includes("n")) top = clamp(top + dy, 0, 1 - bottom - MIN_VISIBLE);
|
|
100
|
+
if (handle.includes("s")) bottom = clamp(bottom - dy, 0, 1 - top - MIN_VISIBLE);
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
left: round(left),
|
|
104
|
+
top: round(top),
|
|
105
|
+
right: round(right),
|
|
106
|
+
bottom: round(bottom)
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Render the cropped region of an image to PNG bytes (browser only: needs canvas). Returns null when canvas
|
|
111
|
+
* is unavailable, so callers can fall back to the full image.
|
|
112
|
+
*/
|
|
113
|
+
async function cropBytes(data, crop) {
|
|
114
|
+
try {
|
|
115
|
+
if (typeof createImageBitmap !== "function") return null;
|
|
116
|
+
const bmp = await createImageBitmap(new Blob([data]));
|
|
117
|
+
const sx = Math.round(bmp.width * crop.left);
|
|
118
|
+
const sy = Math.round(bmp.height * crop.top);
|
|
119
|
+
const sw = Math.max(1, Math.round(bmp.width * (1 - crop.left - crop.right)));
|
|
120
|
+
const sh = Math.max(1, Math.round(bmp.height * (1 - crop.top - crop.bottom)));
|
|
121
|
+
const canvas = typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(sw, sh) : Object.assign(document.createElement("canvas"), {
|
|
122
|
+
width: sw,
|
|
123
|
+
height: sh
|
|
124
|
+
});
|
|
125
|
+
const ctx = canvas.getContext("2d");
|
|
126
|
+
if (!ctx) return null;
|
|
127
|
+
ctx.drawImage(bmp, sx, sy, sw, sh, 0, 0, sw, sh);
|
|
128
|
+
const blob = "convertToBlob" in canvas ? await canvas.convertToBlob({ type: "image/png" }) : await new Promise((r) => canvas.toBlob(r, "image/png"));
|
|
129
|
+
return blob ? {
|
|
130
|
+
data: new Uint8Array(await blob.arrayBuffer()),
|
|
131
|
+
width: sw,
|
|
132
|
+
height: sh
|
|
133
|
+
} : null;
|
|
134
|
+
} catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
export { cropToString as a, visibleSize as c, markdownUrl as d, cropLayout as i, isSafeHref as l, cropBytes as n, dragCrop as o, cropFromString as r, normalizeCrop as s, MIN_VISIBLE as t, isSafeSrc as u };
|
|
139
|
+
|
|
140
|
+
//# sourceMappingURL=crop-oLrgb-A3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crop-oLrgb-A3.js","names":[],"sources":["../src/url.ts","../src/crop.ts"],"sourcesContent":["/** Where a link may point: http(s), mailto, tel, an in-page anchor or a same-site path. `//host` (protocol-relative) is not allowed. */\nconst HREF = /^(?:https?:|mailto:|tel:|#|\\/(?![\\/\\\\\\t\\r\\n]))/i;\n/** Where an image may come from: http(s), a same-site path, or a base64 raster data URL (not SVG). */\nconst SRC = /^(?:https?:|data:image\\/(?:png|jpe?g|gif|webp|bmp|avif);base64,|\\/(?![\\/\\\\\\t\\r\\n]))/i;\n\nexport const isSafeHref = (u: unknown): u is string => typeof u === 'string' && HREF.test(u);\nexport const isSafeSrc = (u: unknown): u is string => typeof u === 'string' && SRC.test(u);\n\n/** Make a URL safe to place inside Markdown `(...)`: characters that end or split the destination are percent-encoded. */\nexport const markdownUrl = (u: string): string => u.replace(/[()\\s<>\\\\]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0')}`);\n","/**\n * Image cropping math. A crop is the fraction of the *natural* image removed from each side, so it survives\n * resizing and is independent of how large the image is shown.\n */\nexport interface Crop { left: number; top: number; right: number; bottom: number }\nexport interface Natural { w: number; h: number }\n\n/** The smallest visible share of the image on either axis (so a crop can never collapse to nothing). */\nexport const MIN_VISIBLE = 0.05;\n\nconst clamp = (v: number, lo: number, hi: number) => Math.max(lo, Math.min(hi, v));\nconst round = (v: number) => Math.round(v * 10000) / 10000;\n\n/** Clamp a crop into a valid range; null means \"no crop\". Junk values are treated as 0. */\nexport function normalizeCrop(input: Partial<Crop> | null | undefined): Crop | null {\n if (!input || typeof input !== 'object') return null;\n const n = (v: unknown) => (typeof v === 'number' && Number.isFinite(v) ? clamp(v, 0, 1 - MIN_VISIBLE) : 0);\n let { left, top, right, bottom } = { left: n(input.left), top: n(input.top), right: n(input.right), bottom: n(input.bottom) };\n // Keep at least MIN_VISIBLE of each axis: shrink the larger side first.\n const fit = (a: number, b: number): [number, number] => {\n const excess = a + b - (1 - MIN_VISIBLE);\n if (excess <= 0) return [a, b];\n return a >= b ? [a - excess, b] : [a, b - excess];\n };\n [left, right] = fit(left, right);\n [top, bottom] = fit(top, bottom);\n const c = { left: round(left), top: round(top), right: round(right), bottom: round(bottom) };\n return c.left || c.top || c.right || c.bottom ? c : null;\n}\n\n/** Natural size of the visible region (what a crop leaves), in natural px. */\nexport function visibleSize(crop: Crop | null, nat: Natural): Natural {\n const c = crop ?? { left: 0, top: 0, right: 0, bottom: 0 };\n return { w: nat.w * (1 - c.left - c.right), h: nat.h * (1 - c.top - c.bottom) };\n}\n\nexport interface CropLayout {\n /** Size of the visible box. */\n width: number;\n height: number;\n /** Size and offset of the full image inside the box. */\n imgWidth: number;\n imgHeight: number;\n offsetX: number;\n offsetY: number;\n}\n\n/** Lay out a crop so the visible box is `displayWidth` px wide. */\nexport function cropLayout(crop: Crop, nat: Natural, displayWidth: number): CropLayout {\n const vw = 1 - crop.left - crop.right;\n const vh = 1 - crop.top - crop.bottom;\n const width = displayWidth;\n const height = (width * (vh * nat.h)) / (vw * nat.w);\n const imgWidth = width / vw;\n const imgHeight = height / vh;\n return { width, height, imgWidth, imgHeight, offsetX: -crop.left * imgWidth, offsetY: -crop.top * imgHeight };\n}\n\n/** Serialize as \"left,top,right,bottom\" for a data attribute. */\nexport const cropToString = (c: Crop) => `${c.left},${c.top},${c.right},${c.bottom}`;\n\nexport function cropFromString(s: string | null): Crop | null {\n if (!s) return null;\n const p = s.split(',').map(Number);\n // Data from HTML is untrusted: out-of-range values mean the markup is bogus, so reject it instead of clamping.\n if (p.length !== 4 || p.some((x) => !Number.isFinite(x) || x < 0 || x >= 1)) return null;\n return normalizeCrop({ left: p[0], top: p[1], right: p[2], bottom: p[3] });\n}\n\nexport type Handle = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw' | 'move';\n\n/**\n * Apply a drag of (dx, dy) fractions of the full image to a crop rectangle. Edges move independently,\n * `move` slides the whole rectangle without resizing it. Result is always valid.\n */\nexport function dragCrop(start: Crop, handle: Handle, dx: number, dy: number): Crop {\n let { left, top, right, bottom } = start;\n if (handle === 'move') {\n const w = 1 - left - right;\n const h = 1 - top - bottom;\n left = clamp(left + dx, 0, 1 - w);\n top = clamp(top + dy, 0, 1 - h);\n right = 1 - w - left;\n bottom = 1 - h - top;\n } else {\n if (handle.includes('w')) left = clamp(left + dx, 0, 1 - right - MIN_VISIBLE);\n if (handle.includes('e')) right = clamp(right - dx, 0, 1 - left - MIN_VISIBLE);\n if (handle.includes('n')) top = clamp(top + dy, 0, 1 - bottom - MIN_VISIBLE);\n if (handle.includes('s')) bottom = clamp(bottom - dy, 0, 1 - top - MIN_VISIBLE);\n }\n return { left: round(left), top: round(top), right: round(right), bottom: round(bottom) };\n}\n\n/**\n * Render the cropped region of an image to PNG bytes (browser only: needs canvas). Returns null when canvas\n * is unavailable, so callers can fall back to the full image.\n */\nexport async function cropBytes(data: Uint8Array, crop: Crop): Promise<{ data: Uint8Array; width: number; height: number } | null> {\n try {\n if (typeof createImageBitmap !== 'function') return null;\n const bmp = await createImageBitmap(new Blob([data as Uint8Array<ArrayBuffer>]));\n const sx = Math.round(bmp.width * crop.left);\n const sy = Math.round(bmp.height * crop.top);\n const sw = Math.max(1, Math.round(bmp.width * (1 - crop.left - crop.right)));\n const sh = Math.max(1, Math.round(bmp.height * (1 - crop.top - crop.bottom)));\n const canvas = typeof OffscreenCanvas !== 'undefined' ? new OffscreenCanvas(sw, sh) : Object.assign(document.createElement('canvas'), { width: sw, height: sh });\n const ctx = canvas.getContext('2d') as CanvasRenderingContext2D | null;\n if (!ctx) return null;\n ctx.drawImage(bmp, sx, sy, sw, sh, 0, 0, sw, sh);\n const blob: Blob | null = 'convertToBlob' in canvas ? await canvas.convertToBlob({ type: 'image/png' }) : await new Promise((r) => (canvas as HTMLCanvasElement).toBlob(r, 'image/png'));\n return blob ? { data: new Uint8Array(await blob.arrayBuffer()), width: sw, height: sh } : null;\n } catch {\n return null;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1284,8 +1284,7 @@ async function inflateRaw(data, limit) {
|
|
|
1284
1284
|
}
|
|
1285
1285
|
return out;
|
|
1286
1286
|
}
|
|
1287
|
-
|
|
1288
|
-
function openZip(bytes) {
|
|
1287
|
+
function openZip(bytes, strict) {
|
|
1289
1288
|
const v = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
1290
1289
|
let end = -1;
|
|
1291
1290
|
for (let i = bytes.length - 22; i >= Math.max(0, bytes.length - 22 - 65535); i--) if (v.getUint32(i, true) === 101010256) {
|
|
@@ -1294,6 +1293,7 @@ function openZip(bytes) {
|
|
|
1294
1293
|
}
|
|
1295
1294
|
if (end < 0) throw new Error("This is not a spreadsheet file.");
|
|
1296
1295
|
const count = v.getUint16(end + 10, true);
|
|
1296
|
+
if (strict && count > strict.maxEntries) throw new Error(strict.message);
|
|
1297
1297
|
let at = v.getUint32(end + 16, true);
|
|
1298
1298
|
const entries = /* @__PURE__ */ new Map();
|
|
1299
1299
|
let total = 0;
|
|
@@ -1303,7 +1303,12 @@ function openZip(bytes) {
|
|
|
1303
1303
|
const nl = v.getUint16(at + 28, true), el = v.getUint16(at + 30, true), cl = v.getUint16(at + 32, true), offset = v.getUint32(at + 42, true);
|
|
1304
1304
|
const name = new TextDecoder().decode(bytes.subarray(at + 46, at + 46 + nl));
|
|
1305
1305
|
total += usize;
|
|
1306
|
-
if (
|
|
1306
|
+
if (strict) {
|
|
1307
|
+
if (/(^|[\\/])\.\.([\\/]|$)|^[\\/]/.test(name) || name.includes("\0")) throw new Error(strict.message);
|
|
1308
|
+
if (total > strict.maxTotal || usize > 1048576 && usize > Math.max(1, csize) * strict.maxRatio) throw new Error(strict.message);
|
|
1309
|
+
}
|
|
1310
|
+
if (usize > MAX_UNZIPPED && !strict) throw new Error("The file is too large to open.");
|
|
1311
|
+
if (total > MAX_UNZIPPED * 2 && !strict) throw new Error("The file is too large to open.");
|
|
1307
1312
|
entries.set(name, {
|
|
1308
1313
|
method,
|
|
1309
1314
|
csize,
|
|
@@ -1314,14 +1319,20 @@ function openZip(bytes) {
|
|
|
1314
1319
|
}
|
|
1315
1320
|
return {
|
|
1316
1321
|
names: [...entries.keys()],
|
|
1322
|
+
size: (name) => entries.get(name)?.usize ?? null,
|
|
1317
1323
|
async read(name) {
|
|
1324
|
+
const d = await this.readBytes(name);
|
|
1325
|
+
return d ? new TextDecoder().decode(d) : null;
|
|
1326
|
+
},
|
|
1327
|
+
async readBytes(name) {
|
|
1318
1328
|
const e = entries.get(name);
|
|
1319
1329
|
if (!e) return null;
|
|
1330
|
+
if (e.offset + 30 > bytes.length || v.getUint32(e.offset, true) !== 67324752) throw new Error("This is not a valid zip file.");
|
|
1320
1331
|
const start = e.offset + 30 + v.getUint16(e.offset + 26, true) + v.getUint16(e.offset + 28, true);
|
|
1321
1332
|
const raw = bytes.subarray(start, start + e.csize);
|
|
1322
|
-
const data = e.method === 0 ? raw : e.method === 8 ? await inflateRaw(raw, Math.min(MAX_UNZIPPED, e.usize + 1024)) : null;
|
|
1323
|
-
if (!data) throw new Error("This
|
|
1324
|
-
return
|
|
1333
|
+
const data = e.method === 0 ? raw : e.method === 8 ? await inflateRaw(raw, Math.min(strict ? strict.maxTotal : MAX_UNZIPPED, e.usize + 1024)) : null;
|
|
1334
|
+
if (!data) throw new Error("This file uses a compression method that is not supported.");
|
|
1335
|
+
return data;
|
|
1325
1336
|
}
|
|
1326
1337
|
};
|
|
1327
1338
|
}
|
|
@@ -1438,6 +1449,6 @@ async function parseXlsx(input) {
|
|
|
1438
1449
|
sheet: first.getAttribute("name") ?? "Sheet1"
|
|
1439
1450
|
};
|
|
1440
1451
|
}
|
|
1441
|
-
export {
|
|
1452
|
+
export { FORMULA_FUNCTIONS as C, zipStore as S, specFromRows as _, PAGE_SIZES as a, toXlsx as b, coerceCell as c, normalizeRows as d, openZip as f, ruleLooks as g, queryRows as h, MAX_ROWS as i, formatCell as l, parseXlsx as m, COLUMN_TYPES as n, aggregate as o, parseCSV as p, LOOKS as r, cleanSpec as s, AGGREGATES as t, groupDisplay as u, toCSV as v, checkFormula as w, withFormulas as x, toPrintHTML as y };
|
|
1442
1453
|
|
|
1443
|
-
//# sourceMappingURL=data-table-
|
|
1454
|
+
//# sourceMappingURL=data-table-C4bo747f.js.map
|