From ffbae50b5c11c99fdf3aea99dc825564376d87c3 Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Mon, 28 Sep 2026 19:46:19 +0200 Subject: [PATCH 1/2] docs(design): plan cell formatting and cell ranges in the sheet editor The plan for the stack that follows: the xlsx reader reads what the writer will write, a setCellStyle op writes into .ods and .xlsx, the sheet editor formats a rectangle of cells, and the bindings take Sheet::set_cell_style. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01NXnz6EZY8YpyiyyE1GpsWy --- docs/design/README.md | 4 +- docs/design/spreadsheet-editing.md | 76 +++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/docs/design/README.md b/docs/design/README.md index 2dfff2743..8a38f0d40 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -15,8 +15,8 @@ log, and C++ replays it on save. Elements are addressed by stable ids. - [Document editing design](document-editing.md): the editor of the document view. Runs, paragraphs and inline formatting. -- [Spreadsheet editing design](spreadsheet-editing.md): cells edited by - position, and the formula plan. +- [Spreadsheet editing design](spreadsheet-editing.md): cells edited and + formatted by position, and the formula plan. - [Plain-text editing design](txt-editing.md): the editor of the plain-text view. One `setContent` op. diff --git a/docs/design/spreadsheet-editing.md b/docs/design/spreadsheet-editing.md index 4916ab975..9795821fe 100644 --- a/docs/design/spreadsheet-editing.md +++ b/docs/design/spreadsheet-editing.md @@ -193,6 +193,79 @@ classes and the raise wrapper avoids an overlay open over a cell the other script lowers. The read-only view does not carry the editor, so the two stay separate scripts. The coordinates are the ones an op names, never a DOM index. +## Cell formatting + +Status: planned. The steps land as a stack, in this order: + +1. The xlsx reader reads what the writer writes: a solid fill from `fgColor`, + theme colours with their `tint`, italic, underline and strikethrough, and + left, right and justified alignment. +2. `Sheet::set_cell_style` and the `setCellStyle` op, written into `.ods`. +3. The same op written into `.xlsx`. +4. The sheet editor formats a selection of cells, and a selection can be a + rectangle. +5. `Sheet::set_cell_style` in the python, java, objective-c and npm bindings. + +### 9. One op per cell, the same keys as a run + +```json +{"op": "setCellStyle", "sheet": 0, "column": 1, "row": 2, + "style": {"fill": "#ffff00", "bold": true, "align": "center"}} +``` + +| Key | Value | `.ods` cell style | `.xlsx` | +|---|---|---|---| +| `fill` | `#rrggbb` or null | `fo:background-color` in `style:table-cell-properties`, null is `transparent` | a `patternFill` `solid` with `fgColor`, null is `none` | +| `bold`, `italic`, `underline`, `strikethrough` | a bool | `style:text-properties`, as for a run | `b`, `i`, `u`, `strike` in a `font` | +| `color` | `#rrggbb` | `fo:color` | `font/color/@rgb` | +| `size` | a length | `fo:font-size` | `font/sz`, in points | +| `align` | `left`, `center`, `right`, `justify` or null | `fo:text-align` in `style:paragraph-properties` and `style:text-align-source="fix"`; null is `value-type` | `alignment/@horizontal`; null is `general` | + +The text keys are the ones of `setTextStyle`, and off is written, never +removed (`document-editing.md` decision 9). A cell has no `highlight`: `fill` is the +cell's ground. + +**Why one op per cell:** a position is how every other sheet op addresses a +cell, and coalescing stays a map keyed by position where the later keys win. +A rectangle is as many ops as the page shows cells, so a selection never +reaches past the rendered extent. The writer keeps one new style per distinct +result for the length of a replay, so a thousand ops add one style. + +### 10. A style edit writes a copy, never the style it read + +An `.ods` cell style and an `.xlsx` `xf` are shared by every cell naming them. + +- `.ods`: the writer claims the cell (`claim_cell` or `grow_to_cell`), adds a + `style:family="table-cell"` automatic style that copies the one the cell + shows (its own, else the row's or the column's default) with the delta + applied, and points `table:style-name` at it. A formula cell and a rich cell + take a style: nothing in their content changes. +- `.xlsx`: the writer appends a `font`, a `fill` and an `xf`, each only where + no equal one exists, with `applyFont`, `applyFill` and `applyAlignment` set, + sets `c/@s`, and saves `styles.xml`. A cell the file does not state is made + as for a value (`insert_cell`). + +### 11. The API takes the two style types the read side returns + +`Sheet::set_cell_style(column, row, TableCellStyle, TextStyle)`. The set +fields are the delta, and a fill of alpha 0 is `fill: null`, as a highlight +is for a run. + +### 12. A selection is a rectangle, and the pin is one corner of it + +`spreadsheet.js` owns it as it owns the pin: shift with a click or an arrow +key, and a drag with a mouse, span it from the pin. `odr.sheet.selection()` +answers `{columns: [first, last], rows: [first, last]}`, and a header click +selects its row or its column across the rendered extent. The editor reports +the keys the selected cells agree on through `onSelectionChange`, as the +document editor does, and `odr.editing.format` and `toggle` act on every +unlocked cell of it. A lock refuses a value, not a style. + +The editor patches the `td` and the text inside it with the declarations the +renderer writes, and a fill also sets `--odr-dark-fill` with the same mapping +as `html::dark_fill`, so a fill made in the dark scheme shows. Undo holds each +cell's inline style before the gesture, and one gesture is one undo step. + ## Formulas, read side - `internal/formula` parses `of:=SUM([.A1:.B2])` (`table:formula`) and @@ -219,7 +292,8 @@ separate scripts. The coordinates are the ones an op names, never a DOM index. input in the editor comes with it. - Number formats (`number:number-style`, `numFmt`), dates and booleans as their own kinds. This also fixes `.xlsx` serials on the read side. -- Multi-line cells, cell style edits, insert and delete of rows and columns. +- Multi-line cells, insert and delete of rows and columns. +- A style for a whole row or column, so cells past the rendered extent take it. - `.csv` save. - Sheets past `spreadsheet_limit` or `spreadsheet_cell_limit` are not in the page; the mode should say so where a view reports a `sheet_cut`. From 5fbc691b21e63de87db7a3aaa1fbb28042a63782 Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Mon, 28 Sep 2026 19:46:51 +0200 Subject: [PATCH 2/2] docs(design): align takes left, center or right HorizontalAlign has no justify, and a new value would move the ordinals the bindings map. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01NXnz6EZY8YpyiyyE1GpsWy --- docs/design/spreadsheet-editing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/design/spreadsheet-editing.md b/docs/design/spreadsheet-editing.md index 9795821fe..defa628a4 100644 --- a/docs/design/spreadsheet-editing.md +++ b/docs/design/spreadsheet-editing.md @@ -199,7 +199,7 @@ Status: planned. The steps land as a stack, in this order: 1. The xlsx reader reads what the writer writes: a solid fill from `fgColor`, theme colours with their `tint`, italic, underline and strikethrough, and - left, right and justified alignment. + left and right alignment. 2. `Sheet::set_cell_style` and the `setCellStyle` op, written into `.ods`. 3. The same op written into `.xlsx`. 4. The sheet editor formats a selection of cells, and a selection can be a @@ -219,7 +219,7 @@ Status: planned. The steps land as a stack, in this order: | `bold`, `italic`, `underline`, `strikethrough` | a bool | `style:text-properties`, as for a run | `b`, `i`, `u`, `strike` in a `font` | | `color` | `#rrggbb` | `fo:color` | `font/color/@rgb` | | `size` | a length | `fo:font-size` | `font/sz`, in points | -| `align` | `left`, `center`, `right`, `justify` or null | `fo:text-align` in `style:paragraph-properties` and `style:text-align-source="fix"`; null is `value-type` | `alignment/@horizontal`; null is `general` | +| `align` | `left`, `center`, `right` or null | `fo:text-align` in `style:paragraph-properties` and `style:text-align-source="fix"`; null is `value-type` | `alignment/@horizontal`; null is `general` | The text keys are the ones of `setTextStyle`, and off is written, never removed (`document-editing.md` decision 9). A cell has no `highlight`: `fill` is the