XLSX headerFooter kept only oddHeader/oddFooter: a workbook whose header is on a
different first page (differentFirst + firstHeader) or on even pages showed no
header, and saving dropped it. The model now keeps the first/even headers and
footers and the differentFirst / differentOddEven / scaleWithDoc / alignWithMargins
flags; XLSX reads and writes them; the PDF and the Page Layout view use the right
pair per page; in-place editing edits the page's own pair, and the Header & Footer
tab has Different First Page and Different Odd & Even Pages. pageLayout.headerFooter
takes the variants and a page: odd|even|first for one-section edits.
Insert › Header & Footer now switches to Page Layout view and edits the first
page's centre header, as in Excel, instead of opening the codes dialog.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On macOS the UI font (SF) has no Hebrew and no Hebrew fallback face was
found, while cells in Arial drew it: the formula bar showed boxes. The
interface families now carry the cell face ahead of the script fallbacks,
and the macOS Hebrew candidates include ArialHB.ttc / SFHebrew.ttf /
LucidaGrande.ttc.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Text boxes ignored a:rPr (size, bold, italic, typeface, fill, outline),
a:pPr algn, a:bodyPr anchor and spPr/a:ln/a:noFill. They are now read into
Shape::format, drawn on screen and in PDF, and written back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Re-protecting a password-protected sheet would drop its hash and let a
plain Unprotect succeed; it is now refused. The sheet tab menu offers
Unprotect Sheet on a protected sheet, like the ribbon.
Refs #346
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since #209, printing from the UI hands the print PDF to Services::print, which only
the web build set: on the desktop, Ctrl+P, More › Print, the macOS menu and the
Backstage Print button produced the PDF and dropped it. The desktop app now writes
it to the temporary folder and opens it in the system PDF viewer, whose Print
reaches the printer dialog.
Save As has a Save as type chooser (xlsx, csv, html, pdf); file.save and
file.saveAs with a .pdf path write a PDF of the sheet (they wrote XLSX bytes into
the .pdf and made it the workbook's file). The desktop save picker honours the
suggested type instead of always proposing .xlsx. The macOS Properties item opens
File › Info instead of running file.properties with no fields.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sheet protection stored an internal FNV hash that was never written to
XLSX, and an Excel sheet password was dropped on import, so imported
protected sheets could be unprotected without it. Protect Sheet now
stores a salted SHA-512 hash (spin count 100000) via PasswordHash; the
XLSX sheetProtection password (iterated or legacy) is read and written,
and Unprotect Sheet really verifies it. Hashes from older GridCraft JSON
workbooks are still read and verified.
Refs #346
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Grouped rows and columns now get Excel's outline band beside the headers:
a bracket per group, a -/+ button on the summary line and level buttons
in the corner. The buttons run data.hideDetail / data.showDetail and the
new data.showLevels, so they undo. Hide Detail marks the summary line
collapsed, Show Detail keeps inner groups that were collapsed on their
own, and Group / Ungroup take by: rows|cols. Show Detail and Hide Detail
are on the Data tab and in the Data menu, the group shortcuts are listed
as Alt+Shift+Right / Left, and shortcut lookup reads navigation keys by
their logical key so the numpad arrows work.
Fixes#373
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ribbon button toggled structure protection silently. It now opens a
Protect Structure and Windows dialog with an optional password; when a
password was set, unprotecting asks for it and really verifies it
(SHA-512, spin count 100000, salted). The password hash is read from and
written to XLSX workbookProtection (workbookAlgorithmName/HashValue/
SaltValue/SpinCount or the legacy workbookPassword); an unreadable one
stays locked. File › Info uses the same flow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The model read topLeftCell but the grid always opened at A1 and never wrote
the current scroll back. Sheets now open scrolled to their saved first
visible cell; saves record the current one (and the active cell) without
dirtying the workbook. Frozen panes use the pane's topLeftCell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cells with shrinkToFit drew at full size: text spilled or was clipped and
numbers showed ####. Too-wide content now gets a smaller font that fits the
cell or merged area, matching the PDF/print path.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
XLOOKUP returned only values, so it couldn't end a range or feed
functions that need a reference: SUM(XLOOKUP("b",A1:A4,B1:B4):
XLOOKUP("d",A1:A4,B1:B4)) and SUM(B1:XLOOKUP(…)) gave #VALUE!, and so
did CELL("address",XLOOKUP(…)). A match on an empty cell gave 0, so
ISBLANK(XLOOKUP(…)) was FALSE and "x"&XLOOKUP(…) gave "x0" (#374). In
Excel XLOOKUP returns a reference to the matching row (or column) of
its result range. The evaluator now does so, finding the position with
XMATCH; arrays of lookup values, array arguments and errors still go
through the library function.
Excel stores a merged range's outline on the cells along each edge; the grid
only used the anchor cell's borders, so a boxed merge showed only top and left.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Validation formulas were evaluated as written, at every cell of the
range, and against the cell's old value. A Custom rule =A1>10 on A1:A9
rejected 20 typed into A1 (A1 was still empty) and checked A1, not A5,
when typing into A5; a Whole number rule "<= B1" on C1:C9 compared C2
with B1. As in Excel, the formulas are now relative to the first cell
of the range and see the value being entered.
A name whose references have no $ was evaluated as if they were
absolute. In Excel such references are relative: defined from B2,
=Sheet1!A2 means "the cell to the left", and the file stores it
relative to A1 (Sheet1!XFD1, wrapping around the sheet's edge).
Name formulas with relative references are now moved by the using
cell's offset from A1, wrapping at the edges, and formulas.defineName
takes relative references as relative to the active cell, as Excel's
Name Manager does, and stores them relative to A1.
INDEX took one row and one column number, so INDEX(A1:C3,{1;3},{1,3})
gave a single value and SUM(INDEX(A1:C3,{1,2,3},2)) summed one cell.
In Excel, arrays of positions return an array of the values at each
(broadcast) position. A position that picks a whole row or column gives
#CALC! there, as arrays can't nest.
These were missing from the list of functions newer than the original
file format, so they were saved without the prefix, and Excel shows
#NAME? for such formulas.
A formula calling a built-in function with too few or too many
arguments (=DATEDIF(a,b), =SUM(), =DATE(2024,1)) was stored and showed
#VALUE! with no hint about what was wrong. Entry now refuses it with
the function's signature, e.g. "DATEDIF has too few arguments. Use
DATEDIF(start_date, end_date, unit)", and leaves the cell unchanged.
Only registered functions are checked: special forms the evaluator
handles itself (IF, LET, LAMBDA...), LAMBDA names and unknown functions
are skipped. Paste and Find & Replace keep turning input that fails
entry into text. DATEDIF's description now lists its units.
Fixes#363
Claude-Session: https://claude.ai/code/session_01BGBBGBC2gRpQ7GDuwQ6WPt