styled-exceljs 0.21.0 → 0.21.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.
@@ -26,6 +26,7 @@ const wb = XLSX.read(data, {
26
26
  const html = XLSX.utils.sheet_to_html(ws, {
27
27
  cellStyles: true,
28
28
  browserPixels: true,
29
+ autoFit: true,
29
30
  charts: true,
30
31
  drawings: true
31
32
  });
@@ -35,6 +36,8 @@ const html = XLSX.utils.sheet_to_html(ws, {
35
36
  | --- | --- | --- |
36
37
  | `cellStyles` | read / HTML | Resolves full styles into `cell.s`, row styles, and column styles. |
37
38
  | `browserPixels` | read / HTML | Converts XLSX/XLS column widths and row heights to CSS pixels. |
39
+ | `autoFit` | HTML / utilities | Measures formatted cell text and computes best-fit column widths. |
40
+ | `overflow` | HTML | Controls unwrapped text overflow (`excel` / `visible` / `clip` / `hidden`). |
38
41
  | `charts` | read / HTML | Parses chart models and renders supported chart types as inline SVG. |
39
42
  | `drawings` | read / HTML | Parses image/drawing anchors and renders supported images as `<img>`. |
40
43
  | `validateMerges` | read | Throws on invalid, duplicate, overlapping, or out-of-bounds merge ranges. |
@@ -116,6 +119,34 @@ XLSX.utils.row_height_to_px(points);
116
119
  XLSX.utils.px_to_row_height(px);
117
120
  ```
118
121
 
122
+ Best-fit widths can be computed with browser text measurement:
123
+
124
+ ```js
125
+ XLSX.utils.auto_fit_columns(ws, {
126
+ set: true,
127
+ measureText(text, font, style) {
128
+ ctx.font = font;
129
+ return ctx.measureText(text).width;
130
+ }
131
+ });
132
+ ```
133
+
134
+ `auto_fit_columns` scans formatted display text (`cell.w` after formatting),
135
+ composes column, row, and cell styles, and applies Excel-like layout rules:
136
+
137
+ - `alignment.wrapText` measures the longest explicit line or word segment
138
+ instead of expanding to the full paragraph width.
139
+ - `alignment.shrinkToFit` does not force a wider column when an existing width
140
+ is available.
141
+ - merged cells distribute additional width across the merged column span.
142
+ - widths are clamped to the Excel column width range and converted through MDW.
143
+
144
+ `XLSX.utils.measure_text_width(text, style, opts)` is also public. In browsers
145
+ it uses `opts.measureText` or Canvas `measureText`; in Node or headless
146
+ environments it uses a deterministic font-aware approximation. For pixel-close
147
+ Excel rendering, pass a browser Canvas callback using the same font stack as the
148
+ rendered table.
149
+
119
150
  ### Drawings
120
151
 
121
152
  When `drawings:true` is set, worksheets may expose `ws["!drawings"]`:
@@ -210,11 +241,11 @@ Tolerant reads attach non-fatal errors to `ws["!mergeErrors"]`. Reads with
210
241
  ## HTML Output
211
242
 
212
243
  `XLSX.utils.sheet_to_html(ws, {cellStyles:true, browserPixels:true,
213
- charts:true, drawings:true})` emits:
244
+ autoFit:true, charts:true, drawings:true})` emits:
214
245
 
215
246
  - `<colgroup>` entries with browser pixel widths
216
247
  - inline row heights
217
- - font, fill, border, alignment, wrap, shrink, and rotation CSS
248
+ - font, fill, border, alignment, wrap, shrink-to-fit, overflow, and rotation CSS
218
249
  - hidden row/column behavior
219
250
  - `rowspan` / `colspan` for merged cells
220
251
  - an absolutely positioned drawing layer with images and chart SVG
@@ -222,6 +253,11 @@ charts:true, drawings:true})` emits:
222
253
  The renderer does not materialize empty grid cells for row/column styles. Row
223
254
  and column styles are composed with real cell styles at render time.
224
255
 
256
+ When `autoFit` is enabled, the generated table uses fixed layout and a computed
257
+ `<colgroup>`. Unwrapped text defaults to Excel-like visible overflow. Use
258
+ `overflow:"clip"` or `overflow:"hidden"` when the host page must constrain cell
259
+ content to the calculated column width.
260
+
225
261
  ## Completeness Report
226
262
 
227
263
  The release includes a local report helper:
@@ -25,6 +25,7 @@ const ws = wb.Sheets[wb.SheetNames[0]];
25
25
  const html = XLSX.utils.sheet_to_html(ws, {
26
26
  cellStyles: true,
27
27
  browserPixels: true,
28
+ autoFit: true,
28
29
  charts: true,
29
30
  drawings: true
30
31
  });
@@ -39,6 +40,8 @@ const html = XLSX.utils.sheet_to_html(ws, {
39
40
  | --- | --- | --- |
40
41
  | `cellStyles` | 读取 / HTML | 将完整样式解析到 `cell.s`,并读取行样式和列样式。 |
41
42
  | `browserPixels` | 读取 / HTML | 按 XLSX / XLS 规范将列宽、行高换算为浏览器 CSS 像素。 |
43
+ | `autoFit` | HTML / 工具函数 | 按格式化后的文字宽度计算最优列宽。 |
44
+ | `overflow` | HTML | 控制非换行文字溢出方式:`excel` / `visible` / `clip` / `hidden`。 |
42
45
  | `charts` | 读取 / HTML | 解析图表模型,并将支持的图表类型以内联 SVG 渲染。 |
43
46
  | `drawings` | 读取 / HTML | 解析图片、绘图锚点,支持的内嵌图片渲染为 `<img>`。 |
44
47
  | `validateMerges` | 读取 | 读取时校验合并单元格,发现非法、重复、重叠或越界范围时抛错。 |
@@ -207,6 +210,32 @@ XLSX.utils.row_height_to_px(points);
207
210
  XLSX.utils.px_to_row_height(px);
208
211
  ```
209
212
 
213
+ 自动列宽可以接入浏览器真实文字测量:
214
+
215
+ ```js
216
+ XLSX.utils.auto_fit_columns(ws, {
217
+ set: true,
218
+ measureText(text, font, style) {
219
+ ctx.font = font;
220
+ return ctx.measureText(text).width;
221
+ }
222
+ });
223
+ ```
224
+
225
+ `auto_fit_columns` 会扫描格式化后的显示文字(`cell.w`),合成列、行、
226
+ 单元格样式,并尽量遵循 Excel 的布局行为:
227
+
228
+ - `alignment.wrapText` 开启时,按最长显式行或最长词段计算,而不是按整段
229
+ 文本强行拉宽列。
230
+ - `alignment.shrinkToFit` 开启且已有列宽时,不再强制扩大列宽。
231
+ - 合并单元格会把额外宽度分摊到覆盖的列。
232
+ - 最终列宽会限制在 Excel 允许范围内,并通过 MDW 公式转换。
233
+
234
+ `XLSX.utils.measure_text_width(text, style, opts)` 也可单独使用。在浏览器中
235
+ 会优先使用 `opts.measureText` 或 Canvas `measureText`;在 Node / 无 DOM
236
+ 环境中使用稳定的字体近似算法。若需要尽量贴近 Excel,请传入与页面实际
237
+ 字体一致的 Canvas 测量回调。
238
+
210
239
  ## 图片和绘图
211
240
 
212
241
  开启 `drawings:true` 后,工作表可能暴露 `ws["!drawings"]`:
@@ -329,6 +358,7 @@ interface MergeError {
329
358
  XLSX.utils.sheet_to_html(ws, {
330
359
  cellStyles: true,
331
360
  browserPixels: true,
361
+ autoFit: true,
332
362
  charts: true,
333
363
  drawings: true
334
364
  });
@@ -339,7 +369,7 @@ XLSX.utils.sheet_to_html(ws, {
339
369
  - `<colgroup>` 列宽
340
370
  - 行高 CSS
341
371
  - 字体、字号、颜色、填充、边框
342
- - 水平 / 垂直对齐、换行、缩进、旋转
372
+ - 水平 / 垂直对齐、换行、缩进、旋转、收缩适应、溢出控制
343
373
  - 隐藏行列
344
374
  - 合并单元格的 `rowspan` / `colspan`
345
375
  - 绝对定位图片层
@@ -348,6 +378,10 @@ XLSX.utils.sheet_to_html(ws, {
348
378
  渲染器不会为了行样式或列样式物化整张空白网格。真实单元格渲染时会按
349
379
  cell XF > row XF > column XF > default XF 的思路合成最终样式。
350
380
 
381
+ 开启 `autoFit` 后,HTML 表格会使用固定布局和计算后的 `<colgroup>`。
382
+ 未换行文本默认使用接近 Excel 的可见溢出效果;如果宿主页面必须把内容
383
+ 限制在列宽内,可以设置 `overflow:"clip"` 或 `overflow:"hidden"`。
384
+
351
385
  ## 完整度报告
352
386
 
353
387
  仓库提供本地完整度报告脚本:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "styled-exceljs",
3
- "version": "0.21.0",
3
+ "version": "0.21.1",
4
4
  "author": "sheetjs",
5
5
  "description": "SheetJS Spreadsheet data parser and writer",
6
6
  "keywords": [
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.1';
8
8
  var current_codepage = 1200, current_ansi = 1252;
9
9
  /*global cptable:true, window */
10
10
  var $cptable;
@@ -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,6 +16752,8 @@ 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;
@@ -23708,8 +23895,6 @@ function html_cell_style(cell, opts) {
23708
23895
  var alignment = s.alignment || {};
23709
23896
  if(alignment.horizontal) css.push("text-align:" + alignment.horizontal);
23710
23897
  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
23898
  if(alignment.textRotation != null && alignment.textRotation !== 0) {
23714
23899
  var deg = alignment.textRotation == 255 ? 90 : alignment.textRotation > 90 ? 90 - alignment.textRotation : alignment.textRotation;
23715
23900
  css.push("transform:rotate(" + deg + "deg)");
@@ -23722,12 +23907,29 @@ function html_cell_style(cell, opts) {
23722
23907
  add_html_border(css, "bottom", border.bottom);
23723
23908
  return css.join(";");
23724
23909
  }
23910
+ function html_cell_layout_style(cell, opts, C, CS, cols) {
23911
+ if(!opts || !cell) return "";
23912
+ var s = cell.s || {}, alignment = s.alignment || {}, css = [];
23913
+ if(opts.browserPixels || opts.autoFit) css.push("box-sizing:border-box;padding:0 2px;min-width:0");
23914
+ if(alignment.wrapText) css.push("white-space:pre-wrap;overflow-wrap:normal;word-break:normal");
23915
+ else {
23916
+ css.push("white-space:pre");
23917
+ if(alignment.shrinkToFit || opts.overflow == "clip" || opts.overflow == "hidden") css.push("overflow:hidden;text-overflow:clip");
23918
+ else css.push("overflow:visible");
23919
+ }
23920
+ if(alignment.shrinkToFit && cols && C != null) {
23921
+ var have = 0, span = CS || 1;
23922
+ for(var j = 0; j < span; ++j) have += html_col_width(cols[C+j]);
23923
+ var need = measure_cell_text_width(cell, s, opts);
23924
+ if(have > 0 && need > have) {
23925
+ var scale = Math.max(0.25, Math.min(1, have / need));
23926
+ css.push("font-size:" + Math.max(1, style_font_size_pt(s) * scale) + "pt");
23927
+ }
23928
+ }
23929
+ return css.join(";");
23930
+ }
23725
23931
  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;
23932
+ return col2px(col);
23731
23933
  }
23732
23934
  function html_row_height(row) {
23733
23935
  if(!row) return 20;
@@ -23742,7 +23944,7 @@ function make_html_row(ws, r, R, o) {
23742
23944
  var sp = ({});
23743
23945
  var dense = ws["!data"] != null;
23744
23946
  var row = (ws["!rows"]||[])[R];
23745
- var cols = ws["!cols"] || [];
23947
+ var cols = o && o._htmlCols || ws["!cols"] || [];
23746
23948
  for(var C = r.s.c; C <= r.e.c; ++C) {
23747
23949
  var RS = 0, CS = 0;
23748
23950
  for(var j = 0; j < M.length; ++j) {
@@ -23785,6 +23987,8 @@ function make_html_row(ws, r, R, o) {
23785
23987
  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
23988
  }
23787
23989
  var cstyle = html_cell_style(stylecell, o);
23990
+ var lstyle = html_cell_layout_style(stylecell, o, C, CS, cols);
23991
+ if(lstyle) cstyle = cstyle ? cstyle + ";" + lstyle : lstyle;
23788
23992
  if(cstyle) sp.style = cstyle;
23789
23993
  sp.id = (o.id || "sjs") + "-" + coord;
23790
23994
  oo.push(writextag('td', w, sp));
@@ -23816,9 +24020,9 @@ function html_to_workbook(str, opts) {
23816
24020
  }
23817
24021
 
23818
24022
  var HTML_EMU_PER_PIXEL = 9525;
23819
- function html_anchor_pos(ws, anchor) {
24023
+ function html_anchor_pos(ws, anchor, opts) {
23820
24024
  var out = {left:0, top:0, width:480, height:288};
23821
- var cols = ws["!cols"] || [], rows = ws["!rows"] || [];
24025
+ var cols = opts && opts._htmlCols || ws["!cols"] || [], rows = ws["!rows"] || [];
23822
24026
  var from = anchor && anchor.from || {col:0,row:0,colOff:0,rowOff:0};
23823
24027
  var to = anchor && anchor.to;
23824
24028
  for(var C = 0; C < (from.col||0); ++C) out.left += html_col_width(cols[C]);
@@ -23931,11 +24135,11 @@ function render_html_drawings(ws, opts) {
23931
24135
  var out = [], drawings = ws["!drawings"] || {}, charts = ws["!charts"] || [];
23932
24136
  if(opts && opts.drawings && drawings.images) drawings.images.forEach(function(img) {
23933
24137
  if(!img || !img.dataURI) return;
23934
- var pos = html_anchor_pos(ws, img.anchor);
24138
+ var pos = html_anchor_pos(ws, img.anchor, opts);
23935
24139
  out.push('<img class="sjs-drawing-image" src="' + img.dataURI + '" style="' + html_abs_style(pos) + '"/>');
23936
24140
  });
23937
24141
  if(opts && opts.charts) charts.forEach(function(chart) {
23938
- var pos = html_anchor_pos(ws, chart.anchor);
24142
+ var pos = html_anchor_pos(ws, chart.anchor, opts);
23939
24143
  out.push('<div class="sjs-chart" style="' + html_abs_style(pos) + '">' + render_chart_svg(chart, pos.width, pos.height) + '</div>');
23940
24144
  });
23941
24145
  if(!out.length) return "";
@@ -23943,11 +24147,16 @@ function render_html_drawings(ws, opts) {
23943
24147
  }
23944
24148
  function make_html_preamble(ws, R, o) {
23945
24149
  var out = [];
23946
- var table = '<table' + (o && o.id ? ' id="' + o.id + '"' : "") + '>';
23947
- if(o && o.browserPixels && ws["!cols"]) {
24150
+ var tattr = ({}), tstyle = [];
24151
+ if(o && o.id) tattr.id = o.id;
24152
+ if(o && (o.browserPixels || o.autoFit)) tstyle.push("border-collapse:collapse;table-layout:fixed");
24153
+ if(tstyle.length) tattr.style = tstyle.join(";");
24154
+ var table = writextag("table", "", tattr).replace(/<\/table>$/, "");
24155
+ var cols = o && o._htmlCols || ws["!cols"];
24156
+ if(o && (o.browserPixels || o.autoFit) && cols) {
23948
24157
  out.push("<colgroup>");
23949
24158
  for(var C = R.s.c; C <= R.e.c; ++C) {
23950
- var col = ws["!cols"][C], style = [];
24159
+ var col = cols[C], style = [];
23951
24160
  style.push("width:" + html_col_width(col) + "px");
23952
24161
  if(col && col.hidden) style.push("display:none");
23953
24162
  out.push(writextag("col", null, {style:style.join(";")}));
@@ -23958,7 +24167,16 @@ function make_html_preamble(ws, R, o) {
23958
24167
  }
23959
24168
 
23960
24169
  function sheet_to_html(ws, opts/*, wb:?Workbook*/) {
23961
- var o = opts || {};
24170
+ var o = {};
24171
+ if(opts) for(var k in opts) if(Object.prototype.hasOwnProperty.call(opts, k)) o[k] = opts[k];
24172
+ if(o.autoFit) {
24173
+ var af = {set:false};
24174
+ if(typeof o.autoFit == "object") for(k in o.autoFit) if(Object.prototype.hasOwnProperty.call(o.autoFit, k)) af[k] = o.autoFit[k];
24175
+ if(o.measureText && af.measureText == null) af.measureText = o.measureText;
24176
+ if(o.canvas && af.canvas == null) af.canvas = o.canvas;
24177
+ o._htmlCols = auto_fit_columns(ws, af);
24178
+ if(o.browserPixels == null) o.browserPixels = true;
24179
+ }
23962
24180
  var header = o.header != null ? o.header : HTML_BEGIN;
23963
24181
  var footer = o.footer != null ? o.footer : HTML_END;
23964
24182
  var out = [header];
@@ -29186,6 +29404,9 @@ var utils = {
29186
29404
  sheet_to_formulae: sheet_to_formulae,
29187
29405
  sheet_to_row_object_array: sheet_to_json,
29188
29406
  validate_merges: validate_merges,
29407
+ measure_text_width: measure_text_width,
29408
+ auto_fit_columns: auto_fit_columns,
29409
+ autofit_columns: auto_fit_columns,
29189
29410
  col_width_to_px: width2px,
29190
29411
  px_to_col_width: function(px) { return char2width(px2char(px)); },
29191
29412
  row_height_to_px: pt2px_browser,