+ > );
+}
+```
+
+
+
+## Functions
+
+:::caution pass
+
+**Column processing must be explicitly enabled!**
+
+:::
+
+Functions creating worksheet objects are not guaranteed to generate the `!cols`
+array. Writers are not guaranteed to export column metadata.
+
+#### Reading Files
+
+[`read` and `readFile`](/docs/api/parse-options) accept an options argument. The
+`cellStyles` option must be set to `true` to generate column properties:
+
+```js
+var wb = XLSX.read(data, {/* ... other options , */ cellStyles: true});
+```
+
+#### Writing Files
+
+[`write` and `writeFile`](/docs/api/write-options) accept an options argument.
+The `cellStyles` option must be set to `true` to export column properties:
+
+```js
+XLSX.writeFile(wb, "SheetSColProps.xlsx", {/* ...opts , */ cellStyles: true});
+```
+
+#### Exporting Data
+
+[`sheet_to_csv`](/docs/api/utilities/csv#delimiter-separated-output) and
+[`sheet_to_json`](/docs/api/utilities/array#array-output) accept options. If the
+`skipHidden` option is set to true, hidden columns will not be exported:
+
+```js
+var ws = wb.Sheets[wb.SheetNames[0]]; // first worksheet
+var csv = XLSX.utils.sheet_to_csv(ws, {/* ...opts, */ skipHidden: true});
+```
+
+## Storage
+
+The `!cols` property in a sheet object stores column-level metadata. If present,
+it is expected to be an array of column objects.
+
+:::info pass
+
+As explained in ["Addresses and Ranges"](/docs/csf/general#columns), SheetJS uses
+zero-indexed columns. The column metadata for Excel column "T" is stored at index
+19 of the `!cols` array.
+
+:::
+
+When performing operations, it is strongly recommended to test for the existence
+of the column structure.
+
+This snippet checks the `!cols` array and the specific column object, creating
+them if they do not exist, before setting the `hidden` property of column "C":
+
+```js
+/* Excel column "C" -> SheetJS column index 2 == XLSX.utils.decode_col("C") */
+var COL_INDEX = 2;
+
+/* create !cols array if it does not exist */
+if(!ws["!cols"]) ws["!cols"] = [];
+
+/* create column metadata object if it does not exist */
+if(!ws["!cols"][COL_INDEX]) ws["!cols"][COL_INDEX] = {wch: 8};
+
+/* set column to hidden */
+ws["!cols"][COL_INDEX].hidden = true;
+```
+
+### Column Widths
+
+Column widths can be specified in three ways:
+
+| Property | Description | Excel UI |
+|:---------|:------------------------|:---------|
+| `wpx` | Width in screen pixels | Pixels |
+| `wch` | "inner width" in MDW ** | Width |
+| `width` | "outer width" in MDW ** | |
+
+:::note pass
+
+When resizing a column, Excel will show a tooltip:
+
+![Resize tooltip](pathname:///colprops/xlwidth.png)
+
+`wpx` stores the "pixels" field (`65` in the diagram) for certain computer and
+font settings.
+
+:::
+
+
MDW (Max Digit Width) (click to show)
+
+**`MDW`**
+
+"MDW" stands for "Max Digit Width", the maximum width of the numeric characters
+(`0`, `1`, ..., `9`) using the first font specified in the file. For most common
+fonts and text scaling settings, this is the width of `0` measured in pixels.
+
+Parsers will save the estimated pixel width of the `0` digit to the `MDW`
+property of the column object. It is always a positive integer.
+
+**`width`**
+
+`width` is the distance from "gridline before the current column" to "gridline
+before the next column" divided by MDW and rounded to the nearest `1/256`.
+
+**`wch`**
+
+Table cells in Excel include 2 pixels of padding on each side. The vertical
+gridline is one pixel wide. In total, the `width` includes 5 pixels of padding.
+
+`wch` is the "inner width", calculated by subtracting the 5 pixels from `width`.
+`wch` is also measured in MDW units rounded to the nearest `1/256`.
+
+**Diagram**
+
+The following diagram depicts the Excel box model and the relationship between
+`width`, `wpx`, `MDW` and the displayed grid:
+
+![Box diagram](pathname:///colprops/xlbox.png)
+
+The distance between the two red lines is `width * MDW = 15` pixels. That span
+includes one gridline width (1 pixel) and two padding blocks (2 pixels each).
+
+The space available for content is `wch * MDW = 15 - 5 = 10` pixels.
+
+
+
+The following snippet sets the width of column "C" to 50 pixels:
+
+```js
+const COL_WIDTH = 50;
+
+/* Excel column "C" -> SheetJS column index 2 == XLSX.utils.decode_col("C") */
+var COL_INDEX = 2;
+
+/* create !cols array if it does not exist */
+if(!ws["!cols"]) ws["!cols"] = [];
+
+/* create column metadata object if it does not exist */
+if(!ws["!cols"][COL_INDEX]) ws["!cols"][COL_INDEX] = {wch: 8};
+
+/* set column width */
+ws["!cols"][COL_INDEX].wpx = COL_WIDTH;
+```
+
+### Column Visibility
+
+The `hidden` property controls visibility.
+
+The following snippet hides column "D":
+
+```js
+/* Excel column "D" -> SheetJS column index 3 == XLSX.utils.decode_col("D") */
+var COL_INDEX = 3;
+
+/* create !cols array if it does not exist */
+if(!ws["!cols"]) ws["!cols"] = [];
+
+/* create column metadata object if it does not exist */
+if(!ws["!cols"][COL_INDEX]) ws["!cols"][COL_INDEX] = {wch: 8};
+
+/* set column to hidden */
+ws["!cols"][COL_INDEX].hidden = true;
+```
+
+### Outline Levels
+
+The `level` property controls outline level / grouping. It is expected to be a
+number between `0` and `7` inclusive.
+
+:::note pass
+
+The Excel UI displays outline levels above the row labels. The base level
+shown in the application is `1`.
+
+SheetJS is zero-indexed: the default (base) level is `0`.
+
+:::
+
+The following snippet sets the level of column "F" to Excel 2 / SheetJS 1:
+
+```js
+/* Excel level 2 -> SheetJS level 2 - 1 = 1 */
+var LEVEL = 1;
+
+/* Excel column "F" -> SheetJS column index 5 == XLSX.utils.decode_col("F") */
+var COL_INDEX = 5;
+
+/* create !cols array if it does not exist */
+if(!ws["!cols"]) ws["!cols"] = [];
+
+/* create column metadata object if it does not exist */
+if(!ws["!cols"][COL_INDEX]) ws["!cols"][COL_INDEX] = {wch: 8};
+
+/* set level */
+ws["!cols"][COL_INDEX].level = LEVEL;
+```
+
+### Grouping Columns
+
+Applications treat consecutive columns with the same level as part of a "group".
+
+The "Group" command typically increments the level of each column in the range:
+
+```js
+/* start_col and end_col are SheetJS 0-indexed column indices */
+function grouper(ws, start_col, end_col) {
+ /* create !cols array if it does not exist */
+ if(!ws["!cols"]) ws["!cols"] = [];
+ /* loop over every column index */
+ for(var i = start_col; i <= end_col; ++i) {
+ /* create column metadata object if it does not exist */
+ if(!ws["!cols"][i]) ws["!cols"][i] = {wch: 8};
+ /* increment level */
+ ws["!cols"][i].level = 1 + (ws["!cols"][i].level || 0);
+ }
+}
+```
+
+The "Ungroup" command typically decrements the level of each column in the range:
+
+```js
+/* start_col and end_col are SheetJS 0-indexed column indices */
+function aufheben(ws, start_col, end_col) {
+ /* create !cols array if it does not exist */
+ if(!ws["!cols"]) ws["!cols"] = [];
+ /* loop over every column index */
+ for(var i = start_col; i <= end_col; ++i) {
+ /* if column metadata does not exist, the level is zero -> skip */
+ if(!ws["!cols"][i]) continue;
+ /* if column level is not specified, the level is zero -> skip */
+ if(!ws["!cols"][i].level) continue;
+ /* decrement level */
+ --ws["!cols"][i].level;
+ }
+}
+```
+
+#### Grouping Symbol
+
+By default, Excel displays the group collapse button on the column after the
+data. In the UI, this option is named "Summary columns to right of detail".
+
+SheetJS exposes this option in the `left` property of the `"!outline"` property
+of worksheet objects. Setting this property to `true` effectively "unchecks" the
+"Summary columns to right of detail" option in Excel:
+
+```js
+if(!ws["outline"]) ws["!outline"] = {};
+ws["!outline"].left = true; // show summary to left of detail
+```
+
+## Implementation Details
+
+
Details (click to show)
+
+**Three Width Types**
+
+There are three different width types corresponding to the three different ways
+spreadsheets store column widths:
+
+SYLK and other plain text formats use raw character count. Contemporaneous tools
+like Visicalc and Multiplan were character based. Since the characters had the
+same width, it sufficed to store a count. This tradition was continued into the
+BIFF formats.
+
+SpreadsheetML (2003) tried to align with HTML by standardizing on screen pixel
+count throughout the file. Column widths, row heights, and other measures use
+pixels. When the pixel and character counts do not align, Excel rounds values.
+
+XLSX internally stores column widths in a nebulous "Max Digit Width" form. The
+Max Digit Width is the width of the largest digit when rendered (generally the
+"0" character is the widest). The internal width must be an integer multiple of
+the width divided by 256. ECMA-376 describes a formula for converting between
+pixels and the internal width. This represents a hybrid approach.
+
+Read functions attempt to populate all three properties. Write functions will
+try to cycle specified values to the desired type. In order to avoid potential
+conflicts, manipulation should delete the other properties first. For example,
+when changing the pixel width, delete the `wch` and `width` properties.
+
+**Column Width Priority**
+
+Even though all of the information is made available, writers are expected to
+follow the priority order:
+
+1) use `width` field if available
+
+2) use `wpx` pixel width if available
+
+3) use `wch` character count if available
+
+
diff --git a/docz/docs/07-csf/07-features/index.md b/docz/docs/07-csf/07-features/index.md
index d969e29..ca9633a 100644
--- a/docz/docs/07-csf/07-features/index.md
+++ b/docz/docs/07-csf/07-features/index.md
@@ -17,124 +17,3 @@ The following topics are covered in sub-pages:
{item.label}{cP?.summary && (" - " + cP.summary)}
);
})}
-
-## Row and Column Properties
-
-
- Format Support (click to show)
-
-**Row Properties**: XLSX/M, XLSB, BIFF8 XLS, XLML, SYLK, DOM, ODS
-
-**Column Properties**: XLSX/M, XLSB, BIFF8 XLS, XLML, SYLK, DOM
-
-
-
-
-Row and Column properties are not extracted by default when reading from a file
-and are not persisted by default when writing to a file. The option
-`cellStyles: true` must be passed to the relevant read or write function.
-
-_Column Properties_
-
-The `!cols` array in each worksheet, if present, is a collection of `ColInfo`
-objects which have the following properties:
-
-```typescript
-type ColInfo = {
- /* visibility */
- hidden?: boolean; // if true, the column is hidden
-
- /* column width is specified in one of the following ways: */
- wpx?: number; // width in screen pixels
- width?: number; // width in Excel "Max Digit Width", width*256 is integral
- wch?: number; // width in characters
-
- /* other fields for preserving features from files */
- level?: number; // 0-indexed outline / group level
- MDW?: number; // Excel "Max Digit Width" unit, always integral
-};
-```
-
-_Row Properties_
-
-The `!rows` array in each worksheet, if present, is a collection of `RowInfo`
-objects which have the following properties:
-
-```typescript
-type RowInfo = {
- /* visibility */
- hidden?: boolean; // if true, the row is hidden
-
- /* row height is specified in one of the following ways: */
- hpx?: number; // height in screen pixels
- hpt?: number; // height in points
-
- level?: number; // 0-indexed outline / group level
-};
-```
-
-_Outline / Group Levels Convention_
-
-The Excel UI displays the base outline level as `1` and the max level as `8`.
-Following JS conventions, SheetJS uses 0-indexed outline levels wherein the base
-outline level is `0` and the max level is `7`.
-
-
- Why are there three width types? (click to show)
-
-There are three different width types corresponding to the three different ways
-spreadsheets store column widths:
-
-SYLK and other plain text formats use raw character count. Contemporaneous tools
-like Visicalc and Multiplan were character based. Since the characters had the
-same width, it sufficed to store a count. This tradition was continued into the
-BIFF formats.
-
-SpreadsheetML (2003) tried to align with HTML by standardizing on screen pixel
-count throughout the file. Column widths, row heights, and other measures use
-pixels. When the pixel and character counts do not align, Excel rounds values.
-
-XLSX internally stores column widths in a nebulous "Max Digit Width" form. The
-Max Digit Width is the width of the largest digit when rendered (generally the
-"0" character is the widest). The internal width must be an integer multiple of
-the width divided by 256. ECMA-376 describes a formula for converting between
-pixels and the internal width. This represents a hybrid approach.
-
-Read functions attempt to populate all three properties. Write functions will
-try to cycle specified values to the desired type. In order to avoid potential
-conflicts, manipulation should delete the other properties first. For example,
-when changing the pixel width, delete the `wch` and `width` properties.
-
-
-
-
- Implementation details (click to show)
-
-_Row Heights_
-
-Excel internally stores row heights in points. The default resolution is 72 DPI
-or 96 PPI, so the pixel and point size should agree. For different resolutions
-they may not agree, so the library separates the concepts.
-
-Even though all of the information is made available, writers are expected to
-follow the priority order:
-
-1) use `hpx` pixel height if available
-2) use `hpt` point height if available
-
-_Column Widths_
-
-Given the constraints, it is possible to determine the `MDW` without actually
-inspecting the font! The parsers guess the pixel width by converting from width
-to pixels and back, repeating for all possible `MDW` and selecting the value
-that minimizes the error. XLML actually stores the pixel width, so the guess
-works in the opposite direction.
-
-Even though all of the information is made available, writers are expected to
-follow the priority order:
-
-1) use `width` field if available
-2) use `wpx` pixel width if available
-3) use `wch` character count if available
-
-
diff --git a/docz/docs/08-api/07-utilities/01-array.md b/docz/docs/08-api/07-utilities/01-array.md
index b7e3ce5..a27c0b1 100644
--- a/docz/docs/08-api/07-utilities/01-array.md
+++ b/docz/docs/08-api/07-utilities/01-array.md
@@ -141,6 +141,7 @@ The function takes an options argument:
|`sheetStubs` | false | Create cell objects of type `z` for `null` values |
|`nullError` | false | If true, emit `#NULL!` error cells for `null` values |
|`UTC` | false | If true, dates are interpreted using UTC methods ** |
+|`dense` | false | Emit [dense sheet object](docs/csf/sheet#dense-mode) |
[UTC option is explained in "Dates"](/docs/csf/features/dates#utc-option)
@@ -279,6 +280,7 @@ default column order is determined by the first appearance of the field using
|`skipHeader` | false | If true, do not include header row in output |
|`nullError` | false | If true, emit `#NULL!` error cells for `null` values |
|`UTC` | false | If true, dates are interpreted using UTC methods ** |
+|`dense` | false | Emit [dense sheet object](docs/csf/sheet#dense-mode) |
[UTC option is explained in "Dates"](/docs/csf/features/dates#utc-option)
@@ -524,6 +526,7 @@ an options argument:
|`dateNF` | FMT 14 | Use specified date format in string output |
|`defval` | | Use specified value in place of null or undefined |
|`blankrows` | ** | Include blank lines in the output ** |
+|`skipHidden` | false | Do not generate objects for hidden rows/columns |
|`UTC` | false | If true, dates will be correct in UTC ** |
- `raw` only affects cells which have a format code (`.z`) field or a formatted
diff --git a/docz/static/colprops/mac.png b/docz/static/colprops/mac.png
new file mode 100644
index 0000000..3a160f0
Binary files /dev/null and b/docz/static/colprops/mac.png differ
diff --git a/docz/static/colprops/win.png b/docz/static/colprops/win.png
new file mode 100644
index 0000000..ce1b42e
Binary files /dev/null and b/docz/static/colprops/win.png differ
diff --git a/docz/static/colprops/xlbox.png b/docz/static/colprops/xlbox.png
new file mode 100644
index 0000000..f4cdeeb
Binary files /dev/null and b/docz/static/colprops/xlbox.png differ
diff --git a/docz/static/colprops/xlwidth.png b/docz/static/colprops/xlwidth.png
new file mode 100644
index 0000000..0067512
Binary files /dev/null and b/docz/static/colprops/xlwidth.png differ
diff --git a/docz/static/rowprops/mac.png b/docz/static/rowprops/mac.png
new file mode 100644
index 0000000..97976bb
Binary files /dev/null and b/docz/static/rowprops/mac.png differ
diff --git a/docz/static/rowprops/win.png b/docz/static/rowprops/win.png
new file mode 100644
index 0000000..ce5386f
Binary files /dev/null and b/docz/static/rowprops/win.png differ