Commit Graph
94 Commits
Author SHA1 Message Date
Brandon Thomas 84a414a705 Merge pull request #158 from nihilist-narwhal/fix-chart-kinds-combo-stock
Charts: combo and stock charts keep their kind in XLSX
2026-10-10 15:47:11 -04:00
Brandon Thomas 1e5edbd43c Merge pull request #154 from nihilist-narwhal/fix-repeated-headers
Repeated headers: number pivot fields and table columns in linear time
2026-10-10 15:47:11 -04:00
Brandon Thomas c21123e375 Merge pull request #181 from MarekSpodBiedry/issue-127
Keep comma when a semicolon or tab is missing from some CSV lines (#127)
2026-10-10 15:46:29 -04:00
Brandon ThomasandClaude Opus 5.5 f75ded37e7 Merge pull request #105 from harrisonoconnorhover/codex/cell-pictures
Add static pictures inside cells with XLSX roundtrip

Rebased onto main (conflicts with #83/#98 and main resolved by maintainer); the contributor's branch does not allow maintainer edits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:02:44 -04:00
Brandon ThomasandClaude Opus 5.5 59c8e36d36 Merge pull request #83 from harrisonoconnorhover/codex/nonspilling-array-export
Preserve array evaluation for non-spilling XLSX formulas

Rebased onto main with a maintainer fix-up (LET bindings via split_last/as_chunks for clippy, no slice indexing); the contributor's branch does not allow maintainer edits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:02:43 -04:00
Brandon Thomas f2ed982fdd Merge pull request #12 from justinjohn0306/fix/anchor-mode-sort
Objects: anchor modes — images/charts/shapes follow cells on sort (#5)

# Conflicts:
#	crates/engine/src/tests.rs
2026-10-10 13:17:30 -04:00
Brandon Thomas eb4174723d Merge pull request #17 from justinjohn0306/fix/encrypted-file-message
File open: report password-protected (encrypted) files clearly (#11)

# Conflicts:
#	crates/engine/src/io.rs
2026-10-10 13:17:21 -04:00
Brandon Thomas bd1be73f2d Merge pull request #62 from justinjohn0306/fix/smooth-line-charts
Line charts: smooth (spline) curves (issue #52)

# Conflicts:
#	crates/xlsx/src/tests/mod.rs
2026-10-10 13:17:07 -04:00
MarekSpodBiedry c69446aff0 Keep comma when a semicolon or tab is missing from some CSV lines (#127) 2026-10-10 19:05:45 +02:00
Brandon ThomasandClaude Opus 5.5 e8ec40da8b Review fix-up: build test anchors with ..Anchor::default()
Keeps the smooth round-trip test compiling as Anchor gains fields (e.g. #12's anchor mode).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 12:45:00 -04:00
Harrison O'Connor-Hoover 9dc3aaf9fa Support static pictures inside cells with XLSX roundtrip (#61) 2026-10-10 12:43:27 -04:00
Brandon Thomas 36723ec310 Merge remote-tracking branch 'origin/main' into pr-12 2026-10-10 12:35:42 -04:00
Brandon ThomasandClaude Opus 5.5 f041904bf8 Review fix-up: only CFB files with an EncryptedPackage are encrypted
Every legacy .xls/.doc is a CFB/OLE2 file, so the signature alone isn't encryption. Walk the
compound file's directory (bounds-checked, bounded loops; raw UTF-16 scan when the structure
is unreadable) and report Encrypted only for an EncryptedPackage/EncryptionInfo stream;
other compound files are LegacyBinary with a "save as .xlsx" message. Tests build CFB bytes
in code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 12:35:36 -04:00
Brandon ThomasandClaude Opus 5.5 20e39fa3cd Review fix-up: clip blank ODS repeats past the sheet edge, struct-update Cell literal
Blank cell and row repeats that run past 16384 columns / 1048576 rows
(padding from writers with larger sheets) are clipped instead of
rejecting the file; any data or merge past the edge is still an error.
convert() builds its Cell with ..Cell::value(value) so new Cell fields
take their defaults.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 12:22:51 -04:00
Brandon Thomas 9365c5a9c6 Merge remote-tracking branch 'origin/main' into pr-17 2026-10-10 12:21:40 -04:00
Brandon ThomasandClaude Opus 5.5 c975b5dc75 Review fix-up: uniform, plot-clamped spline smoothing
smooth_run gives every segment the same number of sub-segments (cap / n, 1..100) so long
series are smoothed end to end within the point cap, and clamps the Catmull-Rom curve to
the plot box. List "smooth" in chart.addElement's params doc, drop the smooth_demo
example, and use the test sheet's own name in series refs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 12:21:35 -04:00
Brandon ThomasandClaude Opus 5.5 26b3db78db Merge codex/xlsb-import (with origin/main) into codex/ods-import
Resolves the shared integration points for both formats: combined sniff,
file.open spec, file filters, MCP description, docs. open_bytes now lets
sniffed content (ODS or XLSB) win over the extension.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 12:20:30 -04:00
Harrison O'Connor-Hoover 42854f2fb9 Preserve array-sensitive XLSX formulas (#64) 2026-10-10 12:19:52 -04:00
Brandon ThomasandClaude Opus 5.5 4955382a07 Review fix-up: control-protocol doc typo, tolerant XLSB phonetic-run tail
PhRun is 10 bytes per [MS-XLSB] 2.5.103 (four u16 fields plus a 16-bit
phType/alcH/unused1 word), so the documented size is kept; since the
RichStr ends its record and phonetic data is not imported, bytes after
the documented runs are now ignored instead of rejecting the file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 12:19:33 -04:00
Brandon Thomas 435a750bba Merge remote-tracking branch 'origin/main' into pr-62 2026-10-10 12:16:17 -04:00
Brandon Thomas ea9d4c6b32 Merge origin/main into codex/xlsb-import 2026-10-10 12:04:33 -04:00
Brandon Thomas 62d84cbbf5 Merge pull request #65 from MarekSpodBiedry/issue-63
XLSX: write _xlpm. prefix on LET/LAMBDA parameter names, strip it on read (#63)
2026-10-10 11:57:12 -04:00
Brandon Thomas 54df7e1fc5 Merge pull request #115 from nihilist-narwhal/feat-spill-range-operator
Formulas: the spill range operator (A1#)
2026-10-10 11:57:11 -04:00
Brandon Thomas c30a720998 Merge pull request #114 from nihilist-narwhal/fix-defined-name-entry
Names: R1C1-style names, Create from Selection bottom/right, LAMBDA name case
2026-10-10 11:57:10 -04:00
nihilist-narwhal f16fed557e Charts: write and read chartex parts
Histogram, box & whisker, waterfall, funnel, treemap and sunburst charts
were written as clustered columns, and reading skipped such charts in
files made by Excel, which keeps them as cx:chartSpace (chartex) parts.

The writer now writes these kinds as chartEx parts, as Excel lays them
out: the data goes through hidden _xlchart.v1.N workbook names, each
part comes with chart style and colour parts, and the drawing frames it
in mc:AlternateContent with a text-box fallback. Fills are written out
(series in theme accents, waterfall decreases and treemap/sunburst boxes
in their own, a white chart area), so they don't depend on the style
part. The reader reads the parts back (resolving the _xlchart names,
which aren't kept as names) with data, title, axis titles, legend,
labels and colours, and follows mc:AlternateContent in drawings.
2026-10-10 06:25:24 -05:00
nihilist-narwhal a059c8e196 Repeated headers: number pivot fields and table columns in linear time
Each header was checked against every name before it, once per suffix
tried, so a source whose headers repeat (a row of thousands of equal
labels, such as check boxes) took cubic time: 4,000 columns took 28 s
in Recommended PivotTables, and a row of 16,384 hung the app, in
PivotTables, in Insert Table and in Get Data from Table/Range. Taken
names and the next suffix of each header are now kept in hash maps, in
the pivot source, in table creation and in the XLSX pivot cache; the
names are the same as before.
2026-10-10 03:05:48 -05:00
nihilist-narwhal c650c2828f Legacy array formulas: fill the range they were entered in
A Ctrl+Shift+Enter array formula was evaluated like a dynamic array:

- Read from a file, the cached values in its range were kept as
  constants, so the formula showed #SPILL! (any multi-cell array
  formula saved by Excel or LibreOffice).
- Entered over C2:C4 with a two-value result, C4 stayed blank where
  Excel shows #N/A, and a single-cell array formula spilled.
- On save, the values of its other cells were not cached.

The result is now fitted to the range: a single row or column repeats
across it, cells past the result show #N/A, and a larger result is cut
off. Reading drops the cached values inside the range, entering clears
the range, and saving caches its values. The formula bar shows the
formula in braces, {=B2:B3*2}.
2026-10-10 01:53:55 -05:00
nihilist-narwhal 925bf22e24 Charts: combo and stock charts keep their kind in XLSX
A combo chart whose series have no kind of their own (as insert.chart
makes them) was written as one column group, and a stock chart as
clustered columns, so both reopened as clustered columns. Combo series
are now written as drawn (the first as columns, the others as lines),
and stock charts as a stockChart group (as lines when they don't have
the 3 or 4 series a stock chart needs).
2026-10-09 23:31:18 -05:00
nihilist-narwhal 9c29ef3fff Formulas: the spill range operator A1#
formula::parse("Sheet1!$D$1#") failed with "unknown error literal".
A # right after a cell reference is now the spill range operator: it
evaluates to the anchor's current spill range, or #REF! when the cell
doesn't spill (or its spill is blocked). Its users depend on the
anchor, so they recalculate when the spill grows or shrinks. Names can
use it.

Files have no # operator: like Excel, the writer stores A1# as
_xlfn.ANCHORARRAY(A1), and the parser reads that back as A1#.
2026-10-09 23:29:35 -05:00
nihilist-narwhal d4d38fd224 Names: calls of LAMBDA names keep the name's case
The parser upper-cases function names, so =Double(4) showed as
=DOUBLE(4). Calls of LAMBDA names are now spelled like the name when
a formula is entered and when a file is read.
2026-10-09 23:27:39 -05:00
harrisonoconnorhover c51e51fe7b #92: import XLSB worksheet data and saved formula results 2026-10-09 22:19:36 -04:00
harrisonoconnorhover da0bf32d8a #16: import ODS worksheet data without overwriting source files 2026-10-09 22:09:09 -04:00
nihilist-narwhal cd083cfc50 XLSX: cache #SPILL! and #CALC! as #VALUE!
They are not error codes of the file format, and Excel refuses to open a
file whose cached values contain them ("Parameter error"). Any workbook
with a blocked spill could not be opened in Excel. Excel caches these
errors as #VALUE! itself.
2026-10-09 17:14:59 -05:00
MarekSpodBiedry b8b3ba0b0e XLSX: write _xlpm. prefix on LET/LAMBDA parameter names, strip it on read (#63) 2026-10-09 20:57:44 +02:00
justinjohn0306 69709bd458 Line charts: smooth (spline) curves (issue #52)
Add support for Excel's "smoothed line" chart style. The smooth flag flows
model -> resolve -> chart render, persists through XLSX (<c:smooth val="1"/>),
and is toggled via the chart.addElement "smooth" element.

Rendering interpolates each gap-free run with a uniform Catmull-Rom spline
(0.5 tangent scale, 100 sub-segments per data segment), so the curve passes
through every data point and rounds the vertices — matching Excel's observed
behaviour. Long runs are still decimated before smoothing.

- model: Series.smooth (#[serde(default)], so old workbooks load unchanged)
- chart: SeriesData.smooth; smooth_run + catmull in render; wired through
  resolve and the sanitize() copy; draw_runs takes the flag (line + scatter)
- engine: chart.addElement element="smooth"
- xlsx: read/write <c:smooth val> instead of hard-coding val="0"
- tests: smooth_line_adds_interpolated_points_and_keeps_endpoints,
  smooth_line_splits_at_gaps, smooth_line_setting_round_trips
- examples/smooth_demo.rs: generates a straight+smooth side-by-side workbook

cargo xtask ci: all 7 steps pass.
2026-10-10 00:09:12 +05:30
justinjohn0306 726a31740e Preserve VBA macros on round-trip; never destroy a macro-enabled workbook (#6)
Opening an .xlsm and saving it silently stripped xl/vbaProject.bin, turning a
macro-enabled workbook into a macro-free one — data loss for anyone whose work
depends on macros. GridCraft still does not run VBA, but it now carries the
project through unchanged.

- model: `Workbook.vba_project: Option<Vec<u8>>` (base64 in JSON via a new
  `b64::opt` helper). `None` = no macros.
- xlsx read: capture the `vbaProject` relationship's bytes instead of warning
  and dropping them; warn that macros are kept but not executed.
- xlsx write: when present, emit `xl/vbaProject.bin` byte-identical, add the
  `.../relationships/vbaProject` relationship, switch the main part to the
  macro-enabled content type (`...sheet.macroEnabled.main+xml`) and add the
  `application/vnd.ms-office.vbaProject` override.
- engine: `view.macros` lists the preserved project alongside GridCraft's own
  scripts (`{"kind":"vba","executed":false}`), so callers can see it is there.
- ROADMAP: scope now "preserve, don't execute" (was "out of scope").

Tests: round-trip byte-identical blob + correct content types/relationship;
capture from a foreign-produced .xlsm; a macro-free workbook stays clean.
2026-10-09 00:54:10 +05:30
justinjohn0306 2276eabba8 File open: recognize Excel password-protected (CFB) files, report clearly (#11)
Excel-encrypted workbooks are CFB/OLE2 compound files (magic D0CF11E0),
not zip packages. sniff() now returns Format::Encrypted for them, and
open_bytes reports a clear password-protected message instead of the
cryptic 'not a valid zip package' error.
2026-10-08 21:04:07 +05:30
justinjohn0306 cbc2430f4f Objects: anchor modes — images/charts/shapes follow cells on sort (#5)
Excel anchors a floating object to the grid in one of three ways, stored in the
file as the drawing anchor's `editAs`: "move and size with cells" (twoCellAnchor,
the default), "move but don't size with cells" (oneCellAnchor / editAs="oneCell")
and "don't move or size with cells" (editAs="absolute"). GridCraft had no such
concept: xlsx import dropped `editAs`, export always wrote a plain twoCellAnchor,
and sorting/filtering never moved an object — so pictures stayed put when their
rows were reordered.

Model: add `AnchorMode` on `Anchor` (serde default = MoveAndSize, so existing
documents round-trip unchanged).

xlsx: read the anchor kind and `editAs` into the mode; write it back out
(editAs="oneCell"/"absolute" as Excel does).

Engine: sorting now moves objects anchored inside the sorted body with their
lines, re-fitting "move and size" objects, keeping "move but don't size"
objects' size, and leaving absolute ones pinned. Structural insert/delete
honours the same rule (previously it moved every object's cell unconditionally
and ignored shapes). New `object.setAnchorMode` command sets an object's mode;
`arrange.selectionPane` reports it.

Tests: sort moves images with their rows; absolute objects stay put across a
sort and a row delete; move-only keeps its size; the mode survives an xlsx
round-trip; the drawing fixture parses all three anchor kinds.
2026-10-08 17:58:53 +05:30
Brandon ThomasandClaude Opus 5.5 488e12de31 Rename SheetCraft to GridCraft everywhere (crates, apps, bundle ids, packaging, docs)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 01:39:08 -04:00
Brandon ThomasandClaude Opus 5.5 b8ff9648a9 M4: PivotTable XLSX round-trip — cache definitions/records, date grouping, pivotTable parts, reader with warnings
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 01:36:28 -04:00
Brandon ThomasandClaude Opus 5.5 f76a0beca3 M8: Draw tab — pen, highlighter, eraser, ink strokes and Ink to Shape
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 01:20:40 -04:00
Brandon ThomasandClaude Opus 5.5 168b8ec8b7 M4–M10: PivotTables, PDF export & printing, what-if analysis, spelling, more commands
- PivotTables: engine (grouping, date groups, multiple values, show-as, filters, layouts,
  refresh, recommended pivots) + PivotTable Fields task pane
- sheetcraft-pdf (dependency-free PDF writer, core-14 fonts) + file.exportPdf/print/
  printPreview/pageLayout.pageSetup with pagination, headers/footers, charts, images
- What-if: Goal Seek, Scenario Manager, data tables, advanced filter, consolidate
- Page Break Preview rendering; view mode buttons
- Spelling (system dictionary), icons library, cell checkboxes, recorded scripts,
  chart design/format commands, arrange, custom views, watch window, Get Data from CSV/JSON
- Native macOS menu bar
- UI interaction tests (kittest): fixed arrow keys losing grid focus and Escape not cancelling
- Perf: FxHash in calc, sparse range reads, Arc'd formula ASTs, fast auto row heights
- Parity 254/290 (87.6%)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 01:04:46 -04:00
Brandon ThomasandClaude Opus 5.5 beb353943e M1: Excel-style egui UI, desktop app, chart renderer, MCP server, CLI, release packaging
- ui-egui: title bar with QAT, full ribbon (Home/Insert/Draw/Page Layout/Formulas/Data/
  Review/View/Automate + Table/Chart Design), formula bar with Name Box, virtualised grid
  (frozen panes, overflow, borders, CF data bars/scales/icons, tables, sparklines, filter
  buttons, charts/pictures/shapes), in-cell editor with point mode, reference colouring,
  autocomplete and argument hints, sheet tabs, status bar, dialogs, control channel
- apps/sheetcraft: desktop app (control server, file dialogs, prefs, drag & drop)
- chart: toolkit-free chart layout/rendering for 25 chart kinds + sparklines + rasteriser
- mcp + sheetcraft-cli: MCP server (headless or bridged) and CLI (info/convert/eval/cat/run/mcp)
- xtask (ci/layers/assets/wasm/version/parity), release workflows, packaging, placeholder icon
- automatic row heights

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 00:00:15 -04:00
Brandon ThomasandClaude Opus 5.5 1ba9daa263 M0: foundation — core, numfmt, formula, functions (478), model, calc, xlsx/csv, engine
Clean-room spreadsheet engine: addresses/values/dates, Excel number formats,
formula parser/printer/reference adjustment, worksheet function library,
copy-on-write workbook model, dependency-driven recalculation with dynamic
arrays, XLSX/CSV I/O, and a command engine (cell entry, clipboard, fill,
formatting, sheets, sort/filter, tables, charts, CF, names, view, file).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:18:10 -04:00