diff --git a/ROADMAP.md b/ROADMAP.md index 070552d07..65fea18b7 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **Stage: alpha** · next: beta, ~25 points of "ready for real work" and ~160–240 h away -> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-12 · **Change:** minor (rows auto-fit oversized fonts on open and whole-row/column formatting) · **Target:** Microsoft Excel (Microsoft 365) +> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-21 · **Change:** minor (Page Setup paper sizes: JIS B4/B5/B6, the Japanese postcards and envelopes, and A6) · **Target:** Microsoft Excel (Microsoft 365) GridCraft aims at full Microsoft Excel parity, and at being better in places: faster, open (XLSX is the native format), fully scriptable by agents (MCP, control channel, CLI), and on the web. @@ -132,6 +132,7 @@ Ranked; estimates in Opus 5.5 agent-hours. Detail and the full beta-gate list in | Date | What landed | |---|---| +| 2026-10-21 | Page Setup paper sizes match Japanese Excel: `B4`/`B5`/`B6` are the JIS sizes (ISO B4/B5/B6 stay reachable by name), so a Japanese B5 worksheet prints at 182×257 mm; Japanese Postcard (100×148 mm), Double Postcard (148×200 mm), Envelope #3/#4 (120×235/90×225 mm), Kaku #2/#3 (240×332/216×277 mm) and A6 are added, both sizes reach the PDF page (`file.exportPdf`/`file.print` MediaBox) and the size survives an XLSX round-trip; the Page Layout Size menu lists them. `B4`/`B5` used to be the ISO sizes, which is changed here | | 2026-10-12 | Rows auto-fit oversized fonts: opening a file and whole-row/column formatting grow non-custom rows to their tallest font (#833) | | 2026-10-12 | Number formats: parsing a format code is linear in its length (long runs of `[`, `,` or `/` no longer take quadratic time), and an unclosed `[` is a literal when splitting sections as well as when tokenizing (#810) | | 2026-10-12 | Number formats: a fraction denominator with 65,536 zeros no longer crashes; format codes are capped at 255 characters like Excel (longer codes display as General, `TEXT` gives `#VALUE!`) (#807) | @@ -193,6 +194,7 @@ Ranked; estimates in Opus 5.5 agent-hours. Detail and the full beta-gate list in | Date | Change | Summary | |---|---|---| +| 2026-10-21 | minor | Page Setup paper sizes: JIS B4/B5/B6 (ISO by name), Japanese postcards and envelopes, A6; PDF page size follows, XLSX round-trips | | 2026-10-12 | minor | Rows auto-fit oversized fonts on open and whole-row/column formatting (#833); progress log row | | 2026-10-12 | minor | Progress log: multi-area row/column delete (#758) | | 2026-10-12 | trivial | Progress log: XLSX chart sheets kept on save (#727) | diff --git a/crates/engine/src/cmd/print.rs b/crates/engine/src/cmd/print.rs index 8350e56d7..663e59a49 100644 --- a/crates/engine/src/cmd/print.rs +++ b/crates/engine/src/cmd/print.rs @@ -50,7 +50,7 @@ pub fn specs() -> Vec { "Page Setup…", ["Page Layout", "Page Setup"], None, - "{sheet?, orientation?: portrait|landscape, paper?: Letter|Legal|A4|A3|A5|Tabloid|Executive|Ledger|B5, margins?: {left,right,top,bottom,header,footer} (inches; also accepted as top-level keys), scale?: 10..400, fitWidth?, fitHeight? (pages; 0 = automatic), fitToPage?: bool, centerH?, centerV?, gridlines?, headings?, header?: \"&L&A&RPage &P of &N\", footer?, printArea?: \"A1:F50\" (\"\" clears), titleRows?: \"1:2\", titleCols?: \"A:A\", rowBreaks?: [46, 91], colBreaks?: [\"K\"]} → the sheet's print settings", + "{sheet?, orientation?: portrait|landscape, paper?: Letter|Legal|A4|A3|A5|A6|Tabloid|Executive|Ledger|B4|B5|B6|B4 (ISO)|B5 (ISO)|B6 (ISO)|Japanese Postcard|Japanese Double Postcard|Japanese Envelope #3|Japanese Envelope #4|Japanese Envelope Kaku #2|Japanese Envelope Kaku #3 (B4/B5/B6 are the JIS sizes), margins?: {left,right,top,bottom,header,footer} (inches; also accepted as top-level keys), scale?: 10..400, fitWidth?, fitHeight? (pages; 0 = automatic), fitToPage?: bool, centerH?, centerV?, gridlines?, headings?, header?: \"&L&A&RPage &P of &N\", footer?, printArea?: \"A1:F50\" (\"\" clears), titleRows?: \"1:2\", titleCols?: \"A:A\", rowBreaks?: [46, 91], colBreaks?: [\"K\"]} → the sheet's print settings", has_doc, page_setup ), @@ -71,19 +71,35 @@ pub fn paper_size(name: &str) -> (f32, f32) { known_paper(name).unwrap_or((612.0, 792.0)) } +/// Paper sizes in points, portrait. `B4`/`B5`/`B6` are the **JIS** sizes Japanese Excel and its +/// e-mail attachments use (257×364, 182×257 and 128×182 mm); the ISO B series stays reachable as +/// `B4 (ISO)`/`B5 (ISO)`/`B6 (ISO)`. The Japanese postcards and envelopes are the JIS ones too. fn known_paper(name: &str) -> Option<(f32, f32)> { - let n = name.trim().to_ascii_lowercase().replace([' ', '-', '_'], ""); + // Spaces, hyphens, underscores and parentheses don't matter: `B5 (JIS)`, `b5-jis` and `b5` + // are one name. + let n = name.trim().to_ascii_lowercase().replace([' ', '-', '_', '(', ')'], ""); Some(match n.as_str() { "letter" | "usletter" => (612.0, 792.0), "legal" | "uslegal" => (612.0, 1008.0), "a4" => (595.28, 841.89), "a3" => (841.89, 1190.55), "a5" => (419.53, 595.28), + "a6" => (297.64, 419.53), "tabloid" | "11x17" => (792.0, 1224.0), "ledger" => (1224.0, 792.0), "executive" => (522.0, 756.0), - "b5" => (498.9, 708.66), - "b4" => (708.66, 1000.63), + "b4" | "b4jis" => (728.50, 1031.81), + "b5" | "b5jis" => (515.91, 728.50), + "b6" | "b6jis" => (362.83, 515.91), + "b4iso" => (708.66, 1000.63), + "b5iso" => (498.90, 708.66), + "b6iso" => (354.33, 498.90), + "japanesepostcard" | "postcard" => (283.46, 419.53), + "japanesedoublepostcard" | "doublepostcard" => (419.53, 566.93), + "japaneseenvelope#3" | "chou#3" => (340.16, 666.14), + "japaneseenvelope#4" | "chou#4" => (255.12, 637.80), + "japaneseenvelopekaku#2" | "kaku#2" => (680.31, 941.10), + "japaneseenvelopekaku#3" | "kaku#3" => (612.28, 785.20), _ => return None, }) } @@ -2061,6 +2077,76 @@ mod tests { assert!(s.doc().unwrap().wb.active().unwrap().print.print_area.is_some()); } + /// The Japanese paper sizes are the JIS ones: `B4`/`B5`/`B6` changed meaning from ISO, and the + /// ISO B series stays reachable by name. The postcards and envelopes are the JIS sizes too. + #[test] + fn japanese_paper_sizes_are_jis() { + for (name, w, h) in [ + ("A4", 595.28, 841.89), // unchanged + ("B4", 728.50, 1031.81), + ("B5", 515.91, 728.50), + ("B6", 362.83, 515.91), + ("B4 (JIS)", 728.50, 1031.81), + ("b5-jis", 515.91, 728.50), + ("B4 (ISO)", 708.66, 1000.63), + ("B5 (ISO)", 498.90, 708.66), + ("B6 (ISO)", 354.33, 498.90), + ("A6", 297.64, 419.53), + ("Japanese Postcard", 283.46, 419.53), + ("japanese double postcard", 419.53, 566.93), + ("Japanese Envelope #3", 340.16, 666.14), + ("chou #4", 255.12, 637.80), + ("Japanese Envelope Kaku #2", 680.31, 941.10), + ("kaku #3", 612.28, 785.20), + ] { + let got = paper_size(name); + assert!((got.0 - w).abs() < 0.01 && (got.1 - h).abs() < 0.01, "{name}: {got:?}"); + } + // Unknown names keep the old Letter fallback. + assert_eq!(paper_size("Napkin"), (612.0, 792.0)); + } + + /// Page Setup's paper reaches the PDF page: the MediaBox is the paper's, A4 is unchanged, B5 is + /// JIS B5, and the Japanese postcard prints at 283.46×419.53 pt. Landscape swaps the sides and + /// fit-to-page keeps the paper size. + #[test] + fn page_setup_paper_sizes_reach_the_pdf_page() { + let mut s = s(); + s.execute("range.setValues", json!({"range": "A1", "values": [["x"]]})).unwrap(); + let close = |v: &Json, want: f64| (v.as_f64().unwrap() - want).abs() < 0.01; + for (paper, w, h) in [ + ("A4", 595.28f64, 841.89f64), + ("B5", 515.91, 728.50), + ("Japanese Postcard", 283.46, 419.53), + ("Japanese Double Postcard", 419.53, 566.93), + ("Japanese Envelope Kaku #2", 680.31, 941.10), + ] { + s.execute("pageLayout.pageSetup", json!({"paper": paper, "orientation": "portrait"})).unwrap(); + let pv = s.execute("file.printPreview", json!({})).unwrap(); + let page = &pv["pageList"][0]; + assert_eq!(page["paper"], paper); + assert!(close(&page["width"], w) && close(&page["height"], h), "{paper}: {page}"); + let (pdf, pages) = pdf_of(&mut s, json!({})); + assert_eq!(pages, 1); + let want = format!("/MediaBox [0 0 {w} {h}]"); + assert!(contains(&pdf, &want), "{paper}: wanted {want}"); + } + // Landscape swaps the sides, and fit-to-page does not change the paper. + s.execute("pageLayout.pageSetup", json!({"paper": "B5", "orientation": "landscape"})).unwrap(); + let (pdf, pages) = pdf_of(&mut s, json!({})); + assert_eq!(pages, 1); + assert!(contains(&pdf, "/MediaBox [0 0 728.5 515.91]")); + s.execute("pageLayout.pageSetup", json!({"paper": "B5", "orientation": "portrait"})).unwrap(); + let (pdf, _) = pdf_of(&mut s, json!({"fitToPage": true})); + assert!(contains(&pdf, "/MediaBox [0 0 515.91 728.5]")); + // `pageLayout.size` still takes any name, and an unknown one prints on Letter as before. + s.execute("pageLayout.size", json!({"paper": "Napkin"})).unwrap(); + let pv = s.execute("file.printPreview", json!({})).unwrap(); + assert!(close(&pv["pageList"][0]["width"], 612.0) && close(&pv["pageList"][0]["height"], 792.0)); + // Page Setup refuses an unknown name. + assert!(s.execute("pageLayout.pageSetup", json!({"paper": "Napkin"})).is_err()); + } + #[test] fn hostile_settings_never_panic_and_cap_pages() { let mut s = s(); diff --git a/crates/engine/src/cmd/view.rs b/crates/engine/src/cmd/view.rs index edcacf57d..cbaa59bec 100644 --- a/crates/engine/src/cmd/view.rs +++ b/crates/engine/src/cmd/view.rs @@ -35,7 +35,15 @@ pub fn specs() -> Vec { }), cmd!("view.zoomToSelection", "Zoom to Selection", ["View", "Zoom"], None, "{viewWidth?, viewHeight?}", has_doc, zoom_selection), cmd!("pageLayout.orientation", "Orientation", ["Page Layout", "Page Setup"], None, "{orientation: portrait|landscape}", has_doc, orientation), - cmd!("pageLayout.size", "Size", ["Page Layout", "Page Setup"], None, "{paper: Letter|Legal|A4|A3|A5|Tabloid|Executive}", has_doc, paper), + cmd!( + "pageLayout.size", + "Size", + ["Page Layout", "Page Setup"], + None, + "{paper: Letter|Legal|A4|A3|A5|A6|Tabloid|Executive|Ledger|B4|B5|B6|B4 (ISO)|B5 (ISO)|B6 (ISO)|Japanese Postcard|Japanese Double Postcard|Japanese Envelope #3|Japanese Envelope #4|Japanese Envelope Kaku #2|Japanese Envelope Kaku #3 (B4/B5/B6 are the JIS sizes)}", + has_doc, + paper + ), cmd!( "pageLayout.margins", "Margins", diff --git a/crates/ui-egui/src/ribbon.rs b/crates/ui-egui/src/ribbon.rs index a0ab5a339..513274869 100644 --- a/crates/ui-egui/src/ribbon.rs +++ b/crates/ui-egui/src/ribbon.rs @@ -1663,7 +1663,28 @@ fn page_layout(app: &mut SheetApp, ui: &mut Ui) { let s = big_button(ui, Icon::Paper, "Size", "Paper Size", true); control_keytip(app, ui, &s, "pageLayout.size"); egui::Popup::menu(&s).show(|ui| { - for p in ["Letter", "Legal", "Tabloid", "Executive", "A3", "A4", "A5"] { + // B4/B5/B6 are the JIS sizes Japanese Excel uses; the ISO ones stay selectable by name. + for p in [ + "Letter", + "Legal", + "Tabloid", + "Executive", + "A3", + "A4", + "A5", + "A6", + "B4", + "B5", + "B6", + "Japanese Postcard", + "Japanese Double Postcard", + "Japanese Envelope #3", + "Japanese Envelope #4", + "Japanese Envelope Kaku #2", + "Japanese Envelope Kaku #3", + "B4 (ISO)", + "B5 (ISO)", + ] { if ui.button(p).clicked() { act(app, "pageLayout.size", json!({"paper": p})); } diff --git a/crates/xlsx/src/tables.rs b/crates/xlsx/src/tables.rs index fe7cbdc42..cf14d5b14 100644 --- a/crates/xlsx/src/tables.rs +++ b/crates/xlsx/src/tables.rs @@ -64,8 +64,10 @@ pub const INDEXED: [u32; 64] = [ 0x003366, 0x339966, 0x003300, 0x333300, 0x993300, 0x993366, 0x333399, 0x333333, // 56-63 ]; -/// Paper sizes (`pageSetup paperSize`) we name. -const PAPERS: [(u32, &str); 14] = [ +/// Paper sizes (`pageSetup paperSize`) we name. ID 12/13 are the JIS B4/B5 that +/// `pageLayout.pageSetup` calls `B4`/`B5`; 42 is ISO B4, and 43–88 are the Japanese postcards, +/// envelopes and B6. The engine's `known_paper` uses the same names and sizes. +const PAPERS: [(u32, &str); 23] = [ (1, "Letter"), (3, "Tabloid"), (4, "Ledger"), @@ -80,14 +82,40 @@ const PAPERS: [(u32, &str); 14] = [ (20, "Envelope #10"), (27, "Envelope DL"), (34, "Envelope B5"), + (42, "B4 (ISO)"), + (43, "Japanese Postcard"), + (69, "Japanese Double Postcard"), + (70, "A6"), + (71, "Japanese Envelope Kaku #2"), + (72, "Japanese Envelope Kaku #3"), + (73, "Japanese Envelope #3"), + (74, "Japanese Envelope #4"), + (88, "B6"), ]; pub fn paper_name(id: u32) -> Option<&'static str> { PAPERS.iter().find(|(i, _)| *i == id).map(|(_, n)| *n) } +/// The file ID for a paper name. Spelling is loose like `known_paper`'s — spaces, hyphens, +/// underscores and parentheses don't matter, and the JIS/ISO and Japanese-envelope spellings the +/// page-setup commands take are folded onto the name in [`PAPERS`]. pub fn paper_id(name: &str) -> Option { - PAPERS.iter().find(|(_, n)| n.eq_ignore_ascii_case(name.trim())).map(|(i, _)| *i) + let norm = |s: &str| s.trim().to_ascii_lowercase().replace([' ', '-', '_', '(', ')'], ""); + let normalized = norm(name); + let alias = match normalized.as_str() { + "b4jis" => "b4", + "b5jis" => "b5", + "b6jis" => "b6", + "postcard" => "japanesepostcard", + "doublepostcard" => "japanesedoublepostcard", + "chou#3" => "japaneseenvelope#3", + "chou#4" => "japaneseenvelope#4", + "kaku#2" => "japaneseenvelopekaku#2", + "kaku#3" => "japaneseenvelopekaku#3", + other => other, + }; + PAPERS.iter().find(|(_, n)| norm(n) == alias).map(|(i, _)| *i) } /// Functions introduced after Excel 2007 that files store with the `_xlfn.` prefix. Entries with @@ -339,5 +367,26 @@ mod tests { assert_eq!(builtin_id(builtin_format(5).unwrap()), None); assert_eq!(paper_name(9), Some("A4")); assert_eq!(paper_id("a4"), Some(9)); + // 12/13 are the JIS B4/B5 Japanese Excel uses; 42 is ISO B4. + assert_eq!(paper_name(12), Some("B4")); + assert_eq!(paper_id("b5"), Some(13)); + assert_eq!(paper_id("B5 (JIS)"), Some(13)); + assert_eq!(paper_id("B4 (ISO)"), Some(42)); + // The Japanese papers page setup names. + assert_eq!(paper_name(43), Some("Japanese Postcard")); + assert_eq!(paper_name(88), Some("B6")); + for (name, id) in [ + ("Japanese Postcard", 43), + ("japanese double postcard", 69), + ("A6", 70), + ("Japanese Envelope Kaku #2", 71), + ("kaku #3", 72), + ("Japanese Envelope #3", 73), + ("chou #4", 74), + ("B6 (JIS)", 88), + ] { + assert_eq!(paper_id(name), Some(id), "{name}"); + } + assert_eq!(paper_id("Napkin"), None); } } diff --git a/crates/xlsx/src/tests/roundtrip.rs b/crates/xlsx/src/tests/roundtrip.rs index 7c7c19056..81de38d67 100644 --- a/crates/xlsx/src/tests/roundtrip.rs +++ b/crates/xlsx/src/tests/roundtrip.rs @@ -1165,3 +1165,35 @@ fn doc_props_keep_boundary_whitespace() { let (back, _) = read_xlsx(&write_xlsx(&wb).unwrap()).unwrap(); assert_eq!(back.props, wb.props); } + +/// Japanese paper sizes survive an XLSX save and reopen. IDs 12/13 are the JIS B4/B5; the +/// Japanese postcards and envelopes have their own IDs, and `B4 (ISO)` keeps ISO B4. The ISO B5/B6 +/// names only exist in the engine: ECMA-376 has no `paperSize` id for them (only ISO B4), so they +/// are not part of the round-trip. +#[test] +fn japanese_paper_sizes_round_trip() { + for paper in [ + "B4", + "B5", + "B6", + "B4 (ISO)", + "A6", + "Japanese Postcard", + "Japanese Double Postcard", + "Japanese Envelope #3", + "Japanese Envelope #4", + "Japanese Envelope Kaku #2", + "Japanese Envelope Kaku #3", + ] { + let mut wb = Workbook::new(); + wb.sheet_mut(0).unwrap().print.paper = paper.into(); + let (back, report) = read_xlsx(&write_xlsx(&wb).unwrap()).unwrap(); + assert!(report.warnings.is_empty(), "{paper}: {:?}", report.warnings); + assert_eq!(back.sheet(0).unwrap().print.paper, paper); + } + // An alias is written as the file's own name and read back as that name. + let mut wb = Workbook::new(); + wb.sheet_mut(0).unwrap().print.paper = "B5 (JIS)".into(); + let (back, _) = read_xlsx(&write_xlsx(&wb).unwrap()).unwrap(); + assert_eq!(back.sheet(0).unwrap().print.paper, "B5"); +} diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index b623124d9..56449a3f3 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1,6 +1,6 @@ # Target-app parity: GridCraft vs Microsoft Excel -> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-12 · **Change:** minor (rows auto-fit oversized fonts on open and row formatting) · **Target:** Microsoft Excel (Microsoft 365) +> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-21 · **Change:** minor (Page Setup paper sizes match Japanese Excel: B4/B5/B6 are the JIS sizes, with ISO B4/B5/B6 by name and the Japanese postcards, envelopes and A6 added; no score change) · **Target:** Microsoft Excel (Microsoft 365) This is the authoritative parity assessment. [`ROADMAP.md`](../ROADMAP.md) summarizes it, [`gaps.md`](gaps.md) lists every shortfall, and the area checklists hold the detail: @@ -86,7 +86,7 @@ performance 8, stability 8, localization 4, hardware 2, platforms 2, ecosystem 2 | Sort, filter, tables, validation, outline | 8 | ~82% | ~70% | 10–16 | Multi-level sort, AutoFilter, Advanced Filter, tables with totals, validation, outline, subtotals, consolidate. Bugs: subtotal header (#131), validation edits (#129), table style panic caught (#130); no slicers on tables | | PivotTables | 7 | ~65% | ~45% | 25–40 | Insert/Recommended, field list, three layouts, values on rows or columns, date grouping, show-values-as, refresh, calculated fields, XLSX round-trip. Missing: slicers, timelines, calculated items, PivotCharts, value/label filters, number grouping, GETPIVOTDATA, data-model pivots. Round-trip never checked in Excel | | Charts and sparklines | 8 | ~50% | ~30% | 40–60 | 25 chart kinds, all 2-D. The chart model holds title, legend position, a data-labels flag, gridlines flag and axis titles; no axis scaling, trendlines, error bars, per-point formatting, chart sheets, 3-D, surface, map, Pareto. Several kinds don't survive XLSX round trip (open PRs #158, #161). [chart-parity.md](chart-parity.md) | -| Page layout, print, PDF | 5 | ~80% | ~55% | 12–20 | Page setup, print titles, breaks, Page Break Preview, headers/footers, PDF export and printing. No true Page Layout view; the PDF writer uses only the 14 standard fonts without embedding (no CJK or other non-Latin text in PDF); web print only exports (#141, #143) | +| Page layout, print, PDF | 5 | ~80% | ~55% | 12–20 | Page setup, print titles, breaks, Page Break Preview, headers/footers, PDF export and printing. Paper sizes match Japanese Excel: B4/B5/B6 are the JIS sizes, ISO B4/B5/B6 stay reachable by name, and Japanese Postcard (はがき), Japanese Double Postcard (往復はがき), the chou/kaku envelopes and A6 are available. No true Page Layout view; the PDF writer uses only the 14 standard fonts without embedding (no CJK or other non-Latin text in PDF); web print only exports (#141, #143) | | What-if and analysis | 4 | ~50% | ~40% | 22–35 | Goal Seek, Scenario Manager, data tables, Analysis ToolPak (13 of 19 tools, no charts), Solver (Simplex LP, a local nonlinear method and Evolutionary; Answer and Limits reports; no Sensitivity report or options dialog). Missing: Forecast Sheet, Analyze Data | | Get & Transform, external data, data model | 5 | ~15% | ~10% | 60–100 | Import of CSV/TSV, JSON, HTML. No Power Query editor, no From Web, database or folder sources, no connections or refresh, no relationships or data model, no external workbook links (dropped with a warning) | | Insert objects | 3 | ~55% | ~45% | 25–40 | Pictures, 8 shape kinds (Excel has ~170), text boxes, own icon set, ink with Ink to Shape, hyperlinks, checkboxes. Missing: SmartArt, WordArt, equations, signature line, objects, 3-D models, screenshot, pictures in cells (PR #105) | @@ -332,6 +332,7 @@ The inventory carried over from the 2026-10-07 ROADMAP.md, updated for what land | Date | Change | Summary | |---|---|---| +| 2026-10-21 | minor | Page Setup paper sizes: `B4`/`B5`/`B6` are the JIS sizes Japanese Excel uses (ISO B4/B5/B6 by name), and Japanese Postcard, Japanese Double Postcard, the chou/kaku envelopes and A6 are added; PDF MediaBox and `file.print` follow, and the Size menu lists them | | 2026-10-12 | minor | Essentials AutoFit evidence: rows auto-fit oversized fonts on open and row formatting (#833) | | 2026-10-12 | minor | Sheet tabs: reveal the selected tab on navigation and resize while preserving manual scrolling (#681); no score change | | 2026-10-12 | minor | PivotTables: values on rows (Σ Values in Rows at any position, all three layouts; `pivot.moveField` and the field pane move it; `dataOnRows` round-trips) (no score change) |