styled-exceljs 0.21.0 → 0.21.2

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.
@@ -1,5 +1,9 @@
1
1
  # XLSX / XLS Visual Fidelity Extensions
2
2
 
3
+ `styled-exceljs` is built from a fork of the original SheetJS Community Edition
4
+ source code. The fork keeps SheetJS-compatible APIs and extends the original
5
+ reader / writer foundation with visual parsing and browser rendering features.
6
+
3
7
  This document describes the browser rendering metadata exposed by this build.
4
8
  The public model extends the existing SheetJS worksheet object and keeps default
5
9
  read behavior compatible. Expensive visual parsing is enabled with explicit
@@ -26,6 +30,7 @@ const wb = XLSX.read(data, {
26
30
  const html = XLSX.utils.sheet_to_html(ws, {
27
31
  cellStyles: true,
28
32
  browserPixels: true,
33
+ autoFit: true,
29
34
  charts: true,
30
35
  drawings: true
31
36
  });
@@ -35,6 +40,8 @@ const html = XLSX.utils.sheet_to_html(ws, {
35
40
  | --- | --- | --- |
36
41
  | `cellStyles` | read / HTML | Resolves full styles into `cell.s`, row styles, and column styles. |
37
42
  | `browserPixels` | read / HTML | Converts XLSX/XLS column widths and row heights to CSS pixels. |
43
+ | `autoFit` | HTML / utilities | Measures formatted cell text and computes best-fit column widths. |
44
+ | `overflow` | HTML | Controls unwrapped text overflow (`excel` / `visible` / `clip` / `hidden`). |
38
45
  | `charts` | read / HTML | Parses chart models and renders supported chart types as inline SVG. |
39
46
  | `drawings` | read / HTML | Parses image/drawing anchors and renders supported images as `<img>`. |
40
47
  | `validateMerges` | read | Throws on invalid, duplicate, overlapping, or out-of-bounds merge ranges. |
@@ -116,6 +123,34 @@ XLSX.utils.row_height_to_px(points);
116
123
  XLSX.utils.px_to_row_height(px);
117
124
  ```
118
125
 
126
+ Best-fit widths can be computed with browser text measurement:
127
+
128
+ ```js
129
+ XLSX.utils.auto_fit_columns(ws, {
130
+ set: true,
131
+ measureText(text, font, style) {
132
+ ctx.font = font;
133
+ return ctx.measureText(text).width;
134
+ }
135
+ });
136
+ ```
137
+
138
+ `auto_fit_columns` scans formatted display text (`cell.w` after formatting),
139
+ composes column, row, and cell styles, and applies Excel-like layout rules:
140
+
141
+ - `alignment.wrapText` measures the longest explicit line or word segment
142
+ instead of expanding to the full paragraph width.
143
+ - `alignment.shrinkToFit` does not force a wider column when an existing width
144
+ is available.
145
+ - merged cells distribute additional width across the merged column span.
146
+ - widths are clamped to the Excel column width range and converted through MDW.
147
+
148
+ `XLSX.utils.measure_text_width(text, style, opts)` is also public. In browsers
149
+ it uses `opts.measureText` or Canvas `measureText`; in Node or headless
150
+ environments it uses a deterministic font-aware approximation. For pixel-close
151
+ Excel rendering, pass a browser Canvas callback using the same font stack as the
152
+ rendered table.
153
+
119
154
  ### Drawings
120
155
 
121
156
  When `drawings:true` is set, worksheets may expose `ws["!drawings"]`:
@@ -210,11 +245,11 @@ Tolerant reads attach non-fatal errors to `ws["!mergeErrors"]`. Reads with
210
245
  ## HTML Output
211
246
 
212
247
  `XLSX.utils.sheet_to_html(ws, {cellStyles:true, browserPixels:true,
213
- charts:true, drawings:true})` emits:
248
+ autoFit:true, charts:true, drawings:true})` emits:
214
249
 
215
250
  - `<colgroup>` entries with browser pixel widths
216
251
  - inline row heights
217
- - font, fill, border, alignment, wrap, shrink, and rotation CSS
252
+ - font, fill, border, alignment, wrap, shrink-to-fit, overflow, and rotation CSS
218
253
  - hidden row/column behavior
219
254
  - `rowspan` / `colspan` for merged cells
220
255
  - an absolutely positioned drawing layer with images and chart SVG
@@ -222,6 +257,11 @@ charts:true, drawings:true})` emits:
222
257
  The renderer does not materialize empty grid cells for row/column styles. Row
223
258
  and column styles are composed with real cell styles at render time.
224
259
 
260
+ When `autoFit` is enabled, the generated table uses fixed layout and a computed
261
+ `<colgroup>`. Unwrapped text defaults to Excel-like visible overflow. Use
262
+ `overflow:"clip"` or `overflow:"hidden"` when the host page must constrain cell
263
+ content to the calculated column width.
264
+
225
265
  ## Completeness Report
226
266
 
227
267
  The release includes a local report helper:
@@ -1,5 +1,8 @@
1
1
  # XLSX / XLS 视觉保真扩展
2
2
 
3
+ `styled-exceljs` 基于 SheetJS Community Edition 原始代码 fork 并继续增强。
4
+ 本 fork 保持 SheetJS 兼容 API 和读写基础,在此之上扩展视觉解析和浏览器渲染能力。
5
+
3
6
  本文档说明本版本在 SheetJS 兼容工作表模型上新增的浏览器渲染元数据。
4
7
  默认读取行为保持轻量和兼容;完整视觉信息需要通过显式选项启用。
5
8
 
@@ -25,6 +28,7 @@ const ws = wb.Sheets[wb.SheetNames[0]];
25
28
  const html = XLSX.utils.sheet_to_html(ws, {
26
29
  cellStyles: true,
27
30
  browserPixels: true,
31
+ autoFit: true,
28
32
  charts: true,
29
33
  drawings: true
30
34
  });
@@ -39,6 +43,8 @@ const html = XLSX.utils.sheet_to_html(ws, {
39
43
  | --- | --- | --- |
40
44
  | `cellStyles` | 读取 / HTML | 将完整样式解析到 `cell.s`,并读取行样式和列样式。 |
41
45
  | `browserPixels` | 读取 / HTML | 按 XLSX / XLS 规范将列宽、行高换算为浏览器 CSS 像素。 |
46
+ | `autoFit` | HTML / 工具函数 | 按格式化后的文字宽度计算最优列宽。 |
47
+ | `overflow` | HTML | 控制非换行文字溢出方式:`excel` / `visible` / `clip` / `hidden`。 |
42
48
  | `charts` | 读取 / HTML | 解析图表模型,并将支持的图表类型以内联 SVG 渲染。 |
43
49
  | `drawings` | 读取 / HTML | 解析图片、绘图锚点,支持的内嵌图片渲染为 `<img>`。 |
44
50
  | `validateMerges` | 读取 | 读取时校验合并单元格,发现非法、重复、重叠或越界范围时抛错。 |
@@ -207,6 +213,32 @@ XLSX.utils.row_height_to_px(points);
207
213
  XLSX.utils.px_to_row_height(px);
208
214
  ```
209
215
 
216
+ 自动列宽可以接入浏览器真实文字测量:
217
+
218
+ ```js
219
+ XLSX.utils.auto_fit_columns(ws, {
220
+ set: true,
221
+ measureText(text, font, style) {
222
+ ctx.font = font;
223
+ return ctx.measureText(text).width;
224
+ }
225
+ });
226
+ ```
227
+
228
+ `auto_fit_columns` 会扫描格式化后的显示文字(`cell.w`),合成列、行、
229
+ 单元格样式,并尽量遵循 Excel 的布局行为:
230
+
231
+ - `alignment.wrapText` 开启时,按最长显式行或最长词段计算,而不是按整段
232
+ 文本强行拉宽列。
233
+ - `alignment.shrinkToFit` 开启且已有列宽时,不再强制扩大列宽。
234
+ - 合并单元格会把额外宽度分摊到覆盖的列。
235
+ - 最终列宽会限制在 Excel 允许范围内,并通过 MDW 公式转换。
236
+
237
+ `XLSX.utils.measure_text_width(text, style, opts)` 也可单独使用。在浏览器中
238
+ 会优先使用 `opts.measureText` 或 Canvas `measureText`;在 Node / 无 DOM
239
+ 环境中使用稳定的字体近似算法。若需要尽量贴近 Excel,请传入与页面实际
240
+ 字体一致的 Canvas 测量回调。
241
+
210
242
  ## 图片和绘图
211
243
 
212
244
  开启 `drawings:true` 后,工作表可能暴露 `ws["!drawings"]`:
@@ -329,6 +361,7 @@ interface MergeError {
329
361
  XLSX.utils.sheet_to_html(ws, {
330
362
  cellStyles: true,
331
363
  browserPixels: true,
364
+ autoFit: true,
332
365
  charts: true,
333
366
  drawings: true
334
367
  });
@@ -339,7 +372,7 @@ XLSX.utils.sheet_to_html(ws, {
339
372
  - `<colgroup>` 列宽
340
373
  - 行高 CSS
341
374
  - 字体、字号、颜色、填充、边框
342
- - 水平 / 垂直对齐、换行、缩进、旋转
375
+ - 水平 / 垂直对齐、换行、缩进、旋转、收缩适应、溢出控制
343
376
  - 隐藏行列
344
377
  - 合并单元格的 `rowspan` / `colspan`
345
378
  - 绝对定位图片层
@@ -348,6 +381,10 @@ XLSX.utils.sheet_to_html(ws, {
348
381
  渲染器不会为了行样式或列样式物化整张空白网格。真实单元格渲染时会按
349
382
  cell XF > row XF > column XF > default XF 的思路合成最终样式。
350
383
 
384
+ 开启 `autoFit` 后,HTML 表格会使用固定布局和计算后的 `<colgroup>`。
385
+ 未换行文本默认使用接近 Excel 的可见溢出效果;如果宿主页面必须把内容
386
+ 限制在列宽内,可以设置 `overflow:"clip"` 或 `overflow:"hidden"`。
387
+
351
388
  ## 完整度报告
352
389
 
353
390
  仓库提供本地完整度报告脚本:
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "styled-exceljs",
3
- "version": "0.21.0",
3
+ "version": "0.21.2",
4
4
  "author": "sheetjs",
5
- "description": "SheetJS Spreadsheet data parser and writer",
5
+ "description": "SheetJS CE fork with enhanced XLS/XLSX styling and browser rendering",
6
6
  "keywords": [
7
7
  "excel",
8
8
  "xls",
package/types/index.d.ts CHANGED
@@ -629,6 +629,38 @@ export interface CellStyle extends FillStyle {
629
629
  bgColor?: StyleColor;
630
630
  }
631
631
 
632
+ export interface TextMeasureOpts {
633
+ /** Browser-compatible text measurement callback. Receives text, CSS font string, and resolved style. */
634
+ measureText?: (text: string, font: string, style: CellStyle) => number;
635
+ /** Optional canvas-like object used for browser measureText fallback */
636
+ canvas?: any;
637
+ /** Maximum digit width in CSS pixels for Excel width conversion */
638
+ MDW?: number;
639
+ /** Additional pixel padding used when measuring cell text */
640
+ padding?: number;
641
+ }
642
+
643
+ export interface AutoFitColumnOpts extends TextMeasureOpts {
644
+ /** Range to scan. Defaults to worksheet !ref */
645
+ range?: string | Range;
646
+ /** Minimum column width in Excel characters */
647
+ min?: number;
648
+ /** Maximum column width in Excel characters */
649
+ max?: number;
650
+ /** Minimum column width in browser pixels */
651
+ minPx?: number;
652
+ /** Maximum column width in browser pixels */
653
+ maxPx?: number;
654
+ /** Include merged cells when measuring width. Defaults to true */
655
+ includeMerged?: boolean;
656
+ /** Skip hidden rows and columns */
657
+ skipHidden?: boolean;
658
+ /** If false, return computed columns without mutating worksheet !cols */
659
+ set?: boolean;
660
+ }
661
+
662
+ export type HTMLOverflowMode = "excel" | "visible" | "clip" | "hidden";
663
+
632
664
  /** Drawing relationship entry */
633
665
  export interface DrawingRelationship {
634
666
  Type?: string;
@@ -1119,6 +1151,18 @@ export interface Sheet2HTMLOpts {
1119
1151
 
1120
1152
  /** If true, render parsed drawing images */
1121
1153
  drawings?: boolean;
1154
+
1155
+ /** If true or an options object, auto-fit columns using measured text widths */
1156
+ autoFit?: boolean | AutoFitColumnOpts;
1157
+
1158
+ /** Unwrapped text overflow behavior for browser rendering */
1159
+ overflow?: HTMLOverflowMode;
1160
+
1161
+ /** Browser-compatible text measurement callback used by autoFit and shrink-to-fit */
1162
+ measureText?: (text: string, font: string, style: CellStyle) => number;
1163
+
1164
+ /** Optional canvas-like object used for text measurement */
1165
+ canvas?: any;
1122
1166
  }
1123
1167
 
1124
1168
  export interface Sheet2JSONOpts extends DateNFOption {
@@ -1260,6 +1304,15 @@ export interface XLSX$Utils {
1260
1304
  /** Validate worksheet merge ranges */
1261
1305
  validate_merges(worksheet: WorkSheet, opts?: CommonOptions): MergeError[];
1262
1306
 
1307
+ /** Measure text width in browser pixels using Canvas when available */
1308
+ measure_text_width(text: string, style?: CellStyle, opts?: TextMeasureOpts): number;
1309
+
1310
+ /** Auto-fit worksheet columns based on formatted cell text */
1311
+ auto_fit_columns(worksheet: WorkSheet, opts?: AutoFitColumnOpts): ColInfo[];
1312
+
1313
+ /** Alias for auto_fit_columns */
1314
+ autofit_columns(worksheet: WorkSheet, opts?: AutoFitColumnOpts): ColInfo[];
1315
+
1263
1316
  /** Convert XLSX column width to browser pixels */
1264
1317
  col_width_to_px(width: number): number;
1265
1318
 
package/xlsx.js CHANGED
@@ -4,7 +4,7 @@
4
4
  /*global global, exports, module, require:false, process:false, Buffer:false, ArrayBuffer:false, DataView:false, Deno:false, Set:false, Float32Array:false */
5
5
  var XLSX = {};
6
6
  function make_xlsx_lib(XLSX){
7
- XLSX.version = '0.21.0';
7
+ XLSX.version = '0.21.2';
8
8
  var current_codepage = 1200, current_ansi = 1252;
9
9
  /*global cptable:true, window */
10
10
  var $cptable;
@@ -11110,7 +11110,7 @@ function resolve_style_obj_color(obj, themes) {
11110
11110
 
11111
11111
  /* 18.3.1.13 width calculations */
11112
11112
  /* [MS-OI29500] 2.1.595 Column Width & Formatting */
11113
- var DEF_MDW = 6, MAX_MDW = 15, MIN_MDW = 1, MDW = DEF_MDW;
11113
+ var DEF_MDW = 7, MAX_MDW = 15, MIN_MDW = 1, MDW = DEF_MDW;
11114
11114
  function width2px(width) { return Math.floor(( width + (Math.round(128/MDW))/256 )* MDW ); }
11115
11115
  function px2char(px) { return (Math.floor((px - 5)/MDW * 100 + 0.5))/100; }
11116
11116
  function char2width(chr) { return (Math.round((chr * MDW + 5)/MDW*256))/256; }
@@ -11152,6 +11152,188 @@ function process_col(coll) {
11152
11152
  if(coll.customWidth) delete coll.customWidth;
11153
11153
  }
11154
11154
 
11155
+ var XLSX_COL_WIDTH_MAX = 255, XLSX_COL_WIDTH_PADDING = 5;
11156
+ function clamp_col_width(width) {
11157
+ return width > XLSX_COL_WIDTH_MAX ? XLSX_COL_WIDTH_MAX : width < 0 ? 0 : width;
11158
+ }
11159
+ function px2col(px) {
11160
+ var width = char2width(px2char(Math.max(0, px)));
11161
+ return clamp_col_width(width);
11162
+ }
11163
+ function col2px(col) {
11164
+ if(!col) return 64;
11165
+ if(col.wpx != null) return col.wpx;
11166
+ if(col.width != null) return width2px(col.width);
11167
+ if(col.wch != null) return width2px(char2width(col.wch));
11168
+ return 64;
11169
+ }
11170
+ function set_col_width_from_px(col, px) {
11171
+ if(!col) col = ({});
11172
+ col.wpx = Math.max(0, Math.ceil(px));
11173
+ col.wch = px2char(col.wpx);
11174
+ col.width = px2col(col.wpx);
11175
+ col.MDW = MDW;
11176
+ col.bestFit = true;
11177
+ col.customWidth = true;
11178
+ return col;
11179
+ }
11180
+ function style_font_size_pt(style) {
11181
+ var font = style && style.font || {};
11182
+ var sz = +font.sz;
11183
+ return sz > 0 ? sz : 11;
11184
+ }
11185
+ function style_font_family(style) {
11186
+ var font = style && style.font || {};
11187
+ return font.name || "Calibri";
11188
+ }
11189
+ function css_font_from_style(style) {
11190
+ var font = style && style.font || {};
11191
+ var parts = [];
11192
+ if(font.italic) parts.push("italic");
11193
+ if(font.bold) parts.push("bold");
11194
+ parts.push(style_font_size_pt(style) + "pt");
11195
+ var name = style_font_family(style);
11196
+ if(/[,\s'"]/.test(name)) name = '"' + String(name).replace(/"/g, '\\"') + '"';
11197
+ parts.push(name);
11198
+ return parts.join(" ");
11199
+ }
11200
+ function fallback_char_width(ch, fpx) {
11201
+ var cc = ch.charCodeAt(0);
11202
+ if(cc == 9 || cc == 32) return fpx * 0.33;
11203
+ if(cc >= 48 && cc <= 57) return fpx * 0.52;
11204
+ if(cc >= 65 && cc <= 90) return fpx * 0.62;
11205
+ if(cc >= 97 && cc <= 122) {
11206
+ if("iljtfr".indexOf(ch) > -1) return fpx * 0.28;
11207
+ if("mw".indexOf(ch) > -1) return fpx * 0.82;
11208
+ return fpx * 0.50;
11209
+ }
11210
+ if(cc >= 0x2E80) return fpx;
11211
+ return fpx * 0.52;
11212
+ }
11213
+ function fallback_text_width(text, style) {
11214
+ var fpx = pt2px_browser(style_font_size_pt(style)), width = 0;
11215
+ for(var i = 0; i < text.length; ++i) width += fallback_char_width(text.charAt(i), fpx);
11216
+ return width;
11217
+ }
11218
+ function measure_text_width(text, style, opts) {
11219
+ var o = opts || {};
11220
+ var str = text == null ? "" : String(text);
11221
+ var lines = str.split(/\r\n|\n|\r/g), max = 0, i = 0, w = 0;
11222
+ if(o.measureText) for(i = 0; i < lines.length; ++i) {
11223
+ w = +o.measureText(lines[i], css_font_from_style(style), style || {});
11224
+ if(isFinite(w) && w > max) max = w;
11225
+ }
11226
+ else {
11227
+ var canvas = o.canvas, ctx = null;
11228
+ if(!canvas && typeof document !== "undefined" && document.createElement) try { canvas = document.createElement("canvas"); } catch(e) {}
11229
+ try { ctx = canvas && canvas.getContext && canvas.getContext("2d"); } catch(e) { ctx = null; }
11230
+ if(ctx && ctx.measureText) {
11231
+ ctx.font = css_font_from_style(style);
11232
+ for(i = 0; i < lines.length; ++i) {
11233
+ w = ctx.measureText(lines[i]).width;
11234
+ if(isFinite(w) && w > max) max = w;
11235
+ }
11236
+ } else for(i = 0; i < lines.length; ++i) {
11237
+ w = fallback_text_width(lines[i], style || {});
11238
+ if(isFinite(w) && w > max) max = w;
11239
+ }
11240
+ }
11241
+ return max;
11242
+ }
11243
+ function auto_fit_cell_text(cell, opts) {
11244
+ if(!cell || cell.v == null) return "";
11245
+ if(cell.w == null && cell.t != 'z') format_cell(cell);
11246
+ return String(cell.w != null ? cell.w : cell.v);
11247
+ }
11248
+ function text_width_segments(text, wrap) {
11249
+ var lines = String(text == null ? "" : text).split(/\r\n|\n|\r/g), out = [], i = 0, j = 0, parts = null;
11250
+ if(!wrap) return lines;
11251
+ for(i = 0; i < lines.length; ++i) {
11252
+ parts = lines[i].split(/[\t \u00A0\-\/]+/);
11253
+ for(j = 0; j < parts.length; ++j) if(parts[j]) out.push(parts[j]);
11254
+ if(!parts.length || (parts.length == 1 && !parts[0])) out.push(lines[i]);
11255
+ }
11256
+ return out.length ? out : [""];
11257
+ }
11258
+ function effective_cell_style(ws, cell, R, C) {
11259
+ var out = ({}), cols = ws["!cols"] || [], rows = ws["!rows"] || [];
11260
+ if(cols[C] && cols[C].s) extend_style_obj(out, cols[C].s);
11261
+ if(rows[R] && rows[R].s) extend_style_obj(out, rows[R].s);
11262
+ if(cell && cell.s) extend_style_obj(out, cell.s);
11263
+ return out;
11264
+ }
11265
+ function measure_cell_text_width(cell, style, opts) {
11266
+ var o = opts || {}, text = auto_fit_cell_text(cell, o), align = style && style.alignment || {};
11267
+ var wrap = !!align.wrapText, segments = text_width_segments(text, wrap), max = 0, w = 0;
11268
+ for(var i = 0; i < segments.length; ++i) {
11269
+ w = measure_text_width(segments[i], style, o);
11270
+ if(w > max) max = w;
11271
+ }
11272
+ var indent = +align.indent || 0;
11273
+ if(indent > 0) max += indent * 3 * Math.max(1, MDW);
11274
+ var rotation = +align.textRotation || 0;
11275
+ if(rotation == 255) rotation = 90;
11276
+ if(rotation > 90) rotation = 90 - rotation;
11277
+ if(rotation < 0) rotation = -rotation;
11278
+ if(rotation > 0 && rotation < 90) {
11279
+ var rad = rotation * Math.PI / 180, fpx = pt2px_browser(style_font_size_pt(style));
11280
+ max = Math.abs(max * Math.cos(rad)) + Math.abs(fpx * Math.sin(rad));
11281
+ } else if(rotation >= 90) max = Math.max(MDW + XLSX_COL_WIDTH_PADDING, pt2px_browser(style_font_size_pt(style)) + 2);
11282
+ return max + (o.padding != null ? +o.padding : XLSX_COL_WIDTH_PADDING);
11283
+ }
11284
+ function merge_start_map(merges) {
11285
+ var out = {};
11286
+ for(var i = 0; i < merges.length; ++i) out[merges[i].s.r + ":" + merges[i].s.c] = merges[i];
11287
+ return out;
11288
+ }
11289
+ function is_covered_merge(merges, R, C) {
11290
+ for(var i = 0; i < merges.length; ++i) {
11291
+ var m = merges[i];
11292
+ if(m.s.r <= R && R <= m.e.r && m.s.c <= C && C <= m.e.c) return !(m.s.r == R && m.s.c == C);
11293
+ }
11294
+ return false;
11295
+ }
11296
+ function auto_fit_columns(ws, opts) {
11297
+ var o = opts || {}, oldMDW = MDW;
11298
+ if(o.MDW) MDW = +o.MDW || MDW;
11299
+ var r = o.range ? (typeof o.range == "string" ? decode_range(o.range) : o.range) : decode_range(ws["!ref"] || "A1");
11300
+ var base = ws["!cols"] || [], rows = ws["!rows"] || [], cols = [], widths = [];
11301
+ var minpx = o.minPx != null ? +o.minPx : o.min != null ? width2px(char2width(+o.min)) : 0;
11302
+ var maxpx = o.maxPx != null ? +o.maxPx : width2px(XLSX_COL_WIDTH_MAX);
11303
+ var merges = o.includeMerged === false ? [] : (ws["!merges"] || []);
11304
+ var starts = merge_start_map(merges), dense = ws["!data"] != null;
11305
+ var C = 0, R = 0, span = 0, m = null, j = 0, cell = null, style = null, need = 0, have = 0;
11306
+ for(C = r.s.c; C <= r.e.c; ++C) {
11307
+ cols[C] = dup(base[C] || {});
11308
+ if(cols[C] && cols[C].hidden && o.skipHidden) continue;
11309
+ widths[C] = cols[C] && (cols[C].wpx != null || cols[C].width != null || cols[C].wch != null) ? col2px(cols[C]) : minpx;
11310
+ }
11311
+ for(R = r.s.r; R <= r.e.r; ++R) {
11312
+ if(rows[R] && rows[R].hidden && o.skipHidden) continue;
11313
+ for(C = r.s.c; C <= r.e.c; ++C) {
11314
+ if(cols[C] && cols[C].hidden && o.skipHidden) continue;
11315
+ if(is_covered_merge(merges, R, C)) continue;
11316
+ cell = dense ? (ws["!data"][R]||[])[C] : ws[encode_cell({r:R,c:C})];
11317
+ if(!cell || cell.v == null) continue;
11318
+ style = effective_cell_style(ws, cell, R, C);
11319
+ need = measure_cell_text_width(cell, style, o);
11320
+ if(style && style.alignment && style.alignment.shrinkToFit && widths[C]) need = Math.min(need, widths[C]);
11321
+ m = starts[R + ":" + C];
11322
+ span = m ? m.e.c - m.s.c + 1 : 1;
11323
+ if(span > 1) {
11324
+ have = 0;
11325
+ for(j = 0; j < span; ++j) have += widths[C+j] || minpx;
11326
+ if(need > have) for(j = 0; j < span; ++j) widths[C+j] = Math.min(maxpx, Math.max(widths[C+j] || minpx, (widths[C+j] || minpx) + (need - have) / span));
11327
+ } else widths[C] = Math.min(maxpx, Math.max(widths[C] || minpx, need));
11328
+ }
11329
+ }
11330
+ for(C = r.s.c; C <= r.e.c; ++C) if(widths[C] != null) cols[C] = set_col_width_from_px(cols[C] || {}, Math.min(maxpx, Math.max(minpx, widths[C])));
11331
+ if(o.set === false) { MDW = oldMDW; return cols; }
11332
+ ws["!cols"] = cols;
11333
+ MDW = oldMDW;
11334
+ return cols;
11335
+ }
11336
+
11155
11337
  var DEF_PPI = 96, PPI = DEF_PPI;
11156
11338
  function px2pt(px) { return px * 96 / PPI; }
11157
11339
  function pt2px(pt) { return pt * PPI / 96; }
@@ -16208,8 +16390,11 @@ function col_obj_w(C, col) {
16208
16390
  else if(col.wch != null) wch = col.wch;
16209
16391
  if(wch > -1) { p.width = char2width(wch); p.customWidth = 1; }
16210
16392
  else if(col.width != null) p.width = col.width;
16393
+ if(p.width != null) p.width = clamp_col_width(p.width);
16211
16394
  if(col.hidden) p.hidden = true;
16212
16395
  if(col.level != null) { p.outlineLevel = p.level = col.level; }
16396
+ if(col.bestFit || col.bestfit) p.bestFit = true;
16397
+ if(col.customWidth || col.customwidth) p.customWidth = 1;
16213
16398
  return p;
16214
16399
  }
16215
16400
 
@@ -16567,10 +16752,13 @@ function parse_ws_xml_cols(columns, cols) {
16567
16752
  for(var coli = 0; coli != cols.length; ++coli) {
16568
16753
  var coll = parsexmltag(cols[coli], true);
16569
16754
  if(coll.hidden) coll.hidden = parsexmlbool(coll.hidden);
16755
+ if(coll.bestFit) coll.bestFit = parsexmlbool(coll.bestFit);
16756
+ if(coll.customWidth) coll.customWidth = parsexmlbool(coll.customWidth);
16570
16757
  var colm=parseInt(coll.min, 10)-1, colM=parseInt(coll.max,10)-1;
16571
16758
  if(coll.outlineLevel) coll.level = (+coll.outlineLevel || 0);
16572
16759
  delete coll.min; delete coll.max; coll.width = +coll.width;
16573
- if(!seencol && coll.width) { seencol = true; find_mdw_colw(coll.width); }
16760
+ /* OOXML widths share the workbook Normal-font MDW; do not infer a different scale per sheet. */
16761
+ if(!seencol && coll.width) { seencol = true; MDW = DEF_MDW; }
16574
16762
  process_col(coll);
16575
16763
  while(colm <= colM) columns[colm++] = dup(coll);
16576
16764
  }
@@ -17740,7 +17928,7 @@ function parse_ws_bin(data, _opts, idx, rels, wb, themes, styles) {
17740
17928
  if(!opts.cellStyles) break;
17741
17929
  while(val.e >= val.s) {
17742
17930
  colinfo[val.e--] = { width: val.w/256, hidden: !!(val.flags & 0x01), level: val.level };
17743
- if(!seencol) { seencol = true; find_mdw_colw(val.w/256); }
17931
+ if(!seencol) { seencol = true; MDW = DEF_MDW; }
17744
17932
  process_col(colinfo[val.e+1]);
17745
17933
  }
17746
17934
  break;
@@ -21280,7 +21468,7 @@ wb.opts.Date1904 = Workbook.WBProps.date1904 = val; break;
21280
21468
  while(val.e >= val.s) {
21281
21469
  colinfo[val.e--] = { width: val.w/256, level: (val.level || 0), hidden: !!(val.flags & 1) };
21282
21470
  if(val.ixfe != null && XFs[val.ixfe]) colinfo[val.e+1].s = resolve_xls_style(XFs[val.ixfe], val.ixfe);
21283
- if(!seencol) { seencol = true; find_mdw_colw(val.w/256); }
21471
+ if(!seencol) { seencol = true; MDW = DEF_MDW; }
21284
21472
  process_col(colinfo[val.e+1]);
21285
21473
  }
21286
21474
  } break;
@@ -23708,8 +23896,6 @@ function html_cell_style(cell, opts) {
23708
23896
  var alignment = s.alignment || {};
23709
23897
  if(alignment.horizontal) css.push("text-align:" + alignment.horizontal);
23710
23898
  if(alignment.vertical) css.push("vertical-align:" + alignment.vertical);
23711
- if(alignment.wrapText) css.push("white-space:normal");
23712
- if(alignment.shrinkToFit) css.push("font-size:smaller");
23713
23899
  if(alignment.textRotation != null && alignment.textRotation !== 0) {
23714
23900
  var deg = alignment.textRotation == 255 ? 90 : alignment.textRotation > 90 ? 90 - alignment.textRotation : alignment.textRotation;
23715
23901
  css.push("transform:rotate(" + deg + "deg)");
@@ -23722,12 +23908,29 @@ function html_cell_style(cell, opts) {
23722
23908
  add_html_border(css, "bottom", border.bottom);
23723
23909
  return css.join(";");
23724
23910
  }
23911
+ function html_cell_layout_style(cell, opts, C, CS, cols) {
23912
+ if(!opts || !cell) return "";
23913
+ var s = cell.s || {}, alignment = s.alignment || {}, css = [];
23914
+ if(opts.browserPixels || opts.autoFit) css.push("box-sizing:border-box;padding:0 2px;min-width:0");
23915
+ if(alignment.wrapText) css.push("white-space:pre-wrap;overflow-wrap:normal;word-break:normal");
23916
+ else {
23917
+ css.push("white-space:pre");
23918
+ if(alignment.shrinkToFit || opts.overflow == "clip" || opts.overflow == "hidden") css.push("overflow:hidden;text-overflow:clip");
23919
+ else css.push("overflow:visible");
23920
+ }
23921
+ if(alignment.shrinkToFit && cols && C != null) {
23922
+ var have = 0, span = CS || 1;
23923
+ for(var j = 0; j < span; ++j) have += html_col_width(cols[C+j]);
23924
+ var need = measure_cell_text_width(cell, s, opts);
23925
+ if(have > 0 && need > have) {
23926
+ var scale = Math.max(0.25, Math.min(1, have / need));
23927
+ css.push("font-size:" + Math.max(1, style_font_size_pt(s) * scale) + "pt");
23928
+ }
23929
+ }
23930
+ return css.join(";");
23931
+ }
23725
23932
  function html_col_width(col) {
23726
- if(!col) return 64;
23727
- if(col.wpx != null) return col.wpx;
23728
- if(col.width != null) return width2px(col.width);
23729
- if(col.wch != null) return width2px(char2width(col.wch));
23730
- return 64;
23933
+ return col2px(col);
23731
23934
  }
23732
23935
  function html_row_height(row) {
23733
23936
  if(!row) return 20;
@@ -23742,7 +23945,7 @@ function make_html_row(ws, r, R, o) {
23742
23945
  var sp = ({});
23743
23946
  var dense = ws["!data"] != null;
23744
23947
  var row = (ws["!rows"]||[])[R];
23745
- var cols = ws["!cols"] || [];
23948
+ var cols = o && o._htmlCols || ws["!cols"] || [];
23746
23949
  for(var C = r.s.c; C <= r.e.c; ++C) {
23747
23950
  var RS = 0, CS = 0;
23748
23951
  for(var j = 0; j < M.length; ++j) {
@@ -23785,6 +23988,8 @@ function make_html_row(ws, r, R, o) {
23785
23988
  if(cell.l && (cell.l.Target || "#").charAt(0) != "#" && (!o.sanitizeLinks || (cell.l.Target || "").slice(0, 11).toLowerCase() != 'javascript:')) w = '<a href="' + escapehtml(cell.l.Target) +'">' + w + '</a>';
23786
23989
  }
23787
23990
  var cstyle = html_cell_style(stylecell, o);
23991
+ var lstyle = html_cell_layout_style(stylecell, o, C, CS, cols);
23992
+ if(lstyle) cstyle = cstyle ? cstyle + ";" + lstyle : lstyle;
23788
23993
  if(cstyle) sp.style = cstyle;
23789
23994
  sp.id = (o.id || "sjs") + "-" + coord;
23790
23995
  oo.push(writextag('td', w, sp));
@@ -23816,9 +24021,9 @@ function html_to_workbook(str, opts) {
23816
24021
  }
23817
24022
 
23818
24023
  var HTML_EMU_PER_PIXEL = 9525;
23819
- function html_anchor_pos(ws, anchor) {
24024
+ function html_anchor_pos(ws, anchor, opts) {
23820
24025
  var out = {left:0, top:0, width:480, height:288};
23821
- var cols = ws["!cols"] || [], rows = ws["!rows"] || [];
24026
+ var cols = opts && opts._htmlCols || ws["!cols"] || [], rows = ws["!rows"] || [];
23822
24027
  var from = anchor && anchor.from || {col:0,row:0,colOff:0,rowOff:0};
23823
24028
  var to = anchor && anchor.to;
23824
24029
  for(var C = 0; C < (from.col||0); ++C) out.left += html_col_width(cols[C]);
@@ -23931,11 +24136,11 @@ function render_html_drawings(ws, opts) {
23931
24136
  var out = [], drawings = ws["!drawings"] || {}, charts = ws["!charts"] || [];
23932
24137
  if(opts && opts.drawings && drawings.images) drawings.images.forEach(function(img) {
23933
24138
  if(!img || !img.dataURI) return;
23934
- var pos = html_anchor_pos(ws, img.anchor);
24139
+ var pos = html_anchor_pos(ws, img.anchor, opts);
23935
24140
  out.push('<img class="sjs-drawing-image" src="' + img.dataURI + '" style="' + html_abs_style(pos) + '"/>');
23936
24141
  });
23937
24142
  if(opts && opts.charts) charts.forEach(function(chart) {
23938
- var pos = html_anchor_pos(ws, chart.anchor);
24143
+ var pos = html_anchor_pos(ws, chart.anchor, opts);
23939
24144
  out.push('<div class="sjs-chart" style="' + html_abs_style(pos) + '">' + render_chart_svg(chart, pos.width, pos.height) + '</div>');
23940
24145
  });
23941
24146
  if(!out.length) return "";
@@ -23943,11 +24148,16 @@ function render_html_drawings(ws, opts) {
23943
24148
  }
23944
24149
  function make_html_preamble(ws, R, o) {
23945
24150
  var out = [];
23946
- var table = '<table' + (o && o.id ? ' id="' + o.id + '"' : "") + '>';
23947
- if(o && o.browserPixels && ws["!cols"]) {
24151
+ var tattr = ({}), tstyle = [];
24152
+ if(o && o.id) tattr.id = o.id;
24153
+ if(o && (o.browserPixels || o.autoFit)) tstyle.push("border-collapse:collapse;table-layout:fixed");
24154
+ if(tstyle.length) tattr.style = tstyle.join(";");
24155
+ var table = writextag("table", "", tattr).replace(/<\/table>$/, "");
24156
+ var cols = o && o._htmlCols || ws["!cols"];
24157
+ if(o && (o.browserPixels || o.autoFit) && cols) {
23948
24158
  out.push("<colgroup>");
23949
24159
  for(var C = R.s.c; C <= R.e.c; ++C) {
23950
- var col = ws["!cols"][C], style = [];
24160
+ var col = cols[C], style = [];
23951
24161
  style.push("width:" + html_col_width(col) + "px");
23952
24162
  if(col && col.hidden) style.push("display:none");
23953
24163
  out.push(writextag("col", null, {style:style.join(";")}));
@@ -23958,7 +24168,16 @@ function make_html_preamble(ws, R, o) {
23958
24168
  }
23959
24169
 
23960
24170
  function sheet_to_html(ws, opts/*, wb:?Workbook*/) {
23961
- var o = opts || {};
24171
+ var o = {};
24172
+ if(opts) for(var k in opts) if(Object.prototype.hasOwnProperty.call(opts, k)) o[k] = opts[k];
24173
+ if(o.autoFit) {
24174
+ var af = {set:false};
24175
+ if(typeof o.autoFit == "object") for(k in o.autoFit) if(Object.prototype.hasOwnProperty.call(o.autoFit, k)) af[k] = o.autoFit[k];
24176
+ if(o.measureText && af.measureText == null) af.measureText = o.measureText;
24177
+ if(o.canvas && af.canvas == null) af.canvas = o.canvas;
24178
+ o._htmlCols = auto_fit_columns(ws, af);
24179
+ if(o.browserPixels == null) o.browserPixels = true;
24180
+ }
23962
24181
  var header = o.header != null ? o.header : HTML_BEGIN;
23963
24182
  var footer = o.footer != null ? o.footer : HTML_END;
23964
24183
  var out = [header];
@@ -25893,7 +26112,8 @@ var numbers_lut_new = function() {
25893
26112
  function numbers_format_cell(cell, t, flags, ofmt, nfmt) {
25894
26113
  var _a, _b, _c, _d;
25895
26114
  var ctype = t & 255, ver = t >> 8;
25896
- var fmt = ver >= 5 ? nfmt : ofmt;
26115
+ /* Missing legacy/new format tables mean the default format. */
26116
+ var fmt = (ver >= 5 ? nfmt : ofmt) || [];
25897
26117
  dur:
25898
26118
  if (flags & (ver > 4 ? 8 : 4) && cell.t == "n" && ctype == 7) {
25899
26119
  var dstyle = ((_a = fmt[7]) == null ? void 0 : _a[0]) ? varint_to_i32(fmt[7][0].data) : -1;
@@ -27808,6 +28028,7 @@ function drawing_mime(path) {
27808
28028
  case "bmp": return "image/bmp";
27809
28029
  case "svg": return "image/svg+xml";
27810
28030
  case "jpg": case "jpeg": return "image/jpeg";
28031
+ case "tif": case "tiff": return "image/tiff";
27811
28032
  default: return "application/octet-stream";
27812
28033
  }
27813
28034
  }
@@ -27823,7 +28044,9 @@ function parse_sheet_drawing(sheet, type, zip, path, idx, opts, wb) {
27823
28044
  if(!img || !img.target) return;
27824
28045
  var ipath = resolve_path(img.target, dfile);
27825
28046
  var ibin = getzipbin(zip, ipath, true);
27826
- if(ibin) img.dataURI = "data:" + drawing_mime(ipath) + ";base64," + Base64_encode_arr(ibin);
28047
+ var contentType = drawing_mime(ipath);
28048
+ if(ibin) img.dataURI = "data:" + contentType + ";base64," + Base64_encode_arr(ibin);
28049
+ img.contentType = contentType;
27827
28050
  img.path = ipath;
27828
28051
  });
27829
28052
  if(opts.charts && draw.charts && draw.charts.length) {
@@ -29186,6 +29409,9 @@ var utils = {
29186
29409
  sheet_to_formulae: sheet_to_formulae,
29187
29410
  sheet_to_row_object_array: sheet_to_json,
29188
29411
  validate_merges: validate_merges,
29412
+ measure_text_width: measure_text_width,
29413
+ auto_fit_columns: auto_fit_columns,
29414
+ autofit_columns: auto_fit_columns,
29189
29415
  col_width_to_px: width2px,
29190
29416
  px_to_col_width: function(px) { return char2width(px2char(px)); },
29191
29417
  row_height_to_px: pt2px_browser,