references: Insert Table of Figures dialog with a caption label choice (#3013)

Insert Table of Figures now opens a Table of Figures dialog (caption label,
include label and number, page numbers, right margin, tab leader) instead of
always building a list of Figures. The label list offers the built-in labels,
the user's own and those the document's captions use, so a document can have
a list of figures beside a list of tables.

references.tableOfFigures takes includeLabel, pageNumbers, rightAlign and
tabLeader and writes the TOC switches for them (ECMA-376 17.16.5.68: \c or \a,
\n, \p). Captions are matched on their whole label (SEQ identifier), a Word
table of figures with its switches in any order (TOC \h \z \c "Figure") is
recognised, and Update Table keeps each list's leader. The table of contents
code treats any \c or \a TOC as a table of figures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Tyrel HavemanandClaude Opus 5.5 committed 2026-10-18 16:04:44 -07:00
1 parent d588e0b873
commit 0d48f236e5
31 files changed
+421 -41

No files matched your search

+3 -2
View File
@@ -2,8 +2,8 @@
**Stage: alpha** · next: beta, ~15 points (60% → 75% ready for real work) and ~150–230 h away
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** trivial (clearing a no-match font query keeps the recent heading visible, #2068; previously trivial: a word after a slash wraps whole, like Word, #2963; previously trivial: progress log: a header-cell picture watermark sits behind the text, #1775; previously trivial: symbols keep their font when the text font changes, #2036; previously trivial: opening a DOCX no longer copies its pictures; previously trivial: Add Font imports every selected font file, #1789); previously trivial (RTF export writes sections (gap 19); previously trivial: ODT export writes sections (gap 19); previously trivial: Read Aloud speaks each sentence in its own language, #1843; previously trivial: hint-reliant CJK outlines; previously trivial: Ctrl+wheel zoom no longer scrolls the document, #1743; previously trivial: progress log: Turkish case mapping in AutoCorrect, #1923; previously trivial: a mouse drag on a table leaves the view where it is, #1738; previously trivial: progress log: Hebrew field results and smart quotes, #1670; previously trivial: Backspace removes a selected table, rows or columns, #1844; previously trivial: progress log: mixed Hebrew/Latin text keeps its order in Word, #1543; previously trivial: progress log: web typing in non-Latin text no longer repeats, #1480; previously trivial: progress log: font family caches bounded and math font lookups follow font changes, #1354; previously trivial: `-->` AutoCorrect replacement, #2223; previously trivial: numbered headings, #1793; previously trivial: Navigation Find exposes Results, #2096; previously trivial: progress log: Columns on selected text, #2024; previously trivial: progress log: legacy outline, shadow, emboss and imprint text effects drawn, #2534; previously trivial: progress log: Indonesian proofing, #498; previously trivial: progress log: DOCX glossary documents (building blocks); previously trivial: progress log: Read Mode keeps the keyboard and shows whole, unobscured columns, #1772); previously trivial: progress log: font list entries of tall-ascent fonts no longer cut off, #2570); previously trivial: progress log: Split Cells and Convert Text to Table dialogs follow the Word check, #2592; previously trivial: a new source's Tag is made like Word's, three letters of the author and two digits of the year with a number added when taken, and an edit keeps the Tag, #2094; previously trivial: Web Layout fills the window width, #1876; previously trivial: progress log: Different First Page headers reachable from page 1, #2826; previously trivial: Language dialog keeps inherited proofing language and no-proof choices (#2717); previously trivial: progress log: web preferences kept between visits, #2132; previously trivial: progress log: undrawable private-use list bullets show the level's default bullet, #1683; previously trivial: progress log: `w:eastAsianLayout` kept on save; previously trivial: table style editor formats every part, #714); previously trivial: VML freeforms read and kept on save; previously trivial: progress log: consistent preferred cell widths; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: page-break-only list paragraphs hide their labels; previously trivial: progress log: grouped `flowChartOr` shapes draw their circle and crossing spokes; previously trivial: progress log: copies keep their formatting in other programs, #1523; previously trivial: progress log: privacy options on save, #1062; previously trivial: progress log: equation pictures in theme colours on Windows, #1002; previously trivial: symbol fonts, #776; previously trivial: the save prompt answers from the keyboard, #861; previously trivial: table column and row markers on the rulers, #700; previously trivial: table borders and the resize handle drag on the page, #217; previously trivial: formatting revisions landed, #41; previously trivial: Draw tab ink, #307, and Draw Table and Eraser, #303, landed: catalog 402/434; previously minor: readiness table with hours per audience; full number back to the additive weighted sum, 60%; mainstream and essentials numbers added; alpha gate checked; previously major: full re-measure against Word for Mac 16.113.4; restructured to the craftrules progress-docs standard) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** trivial (clearing a no-match font query keeps the recent heading visible, #2068; previously trivial: a word after a slash wraps whole, like Word, #2963; previously trivial: RTF export writes sections (gap 19); previously trivial: ODT export writes sections (gap 19); previously trivial: progress log: pasted blockquotes are indented paragraphs, #1270; previously trivial: progress log: print-area strings translated in every catalog; previously trivial: progress log: comments readable in the dark theme, #2045; previously trivial: progress log: ODT sections, #1379; previously trivial: progress log: table deletes, Change Case and Unlink Fields respect Track Changes and locked content controls, #1329; previously trivial: progress log: rounded rectangle corners, #1476; previously trivial: progress log: Save As type list, #1399; previously trivial: progress log: privacy options on save, #1062; previously trivial: progress log: equation pictures in theme colours on Windows, #1002; previously trivial: symbol fonts, #776; previously trivial: the save prompt answers from the keyboard, #861; previously trivial: table column and row markers on the rulers, #700; previously trivial: table borders and the resize handle drag on the page, #217; previously trivial: formatting revisions landed, #41; previously trivial: Draw tab ink, #307, and Draw Table and Eraser, #303, landed: catalog 402/434; previously minor: readiness table with hours per audience; full number back to the additive weighted sum, 60%; mainstream and essentials numbers added; alpha gate checked; previously major: full re-measure against Word for Mac 16.113.4; restructured to the craftrules progress-docs standard) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** trivial (Insert Table of Figures dialog for any caption label, #3013; previously trivial: clearing a no-match font query keeps the recent heading visible, #2068; previously trivial: a word after a slash wraps whole, like Word, #2963; previously trivial: progress log: a header-cell picture watermark sits behind the text, #1775; previously trivial: symbols keep their font when the text font changes, #2036; previously trivial: opening a DOCX no longer copies its pictures; previously trivial: Add Font imports every selected font file, #1789); previously trivial (RTF export writes sections (gap 19); previously trivial: ODT export writes sections (gap 19); previously trivial: Read Aloud speaks each sentence in its own language, #1843; previously trivial: hint-reliant CJK outlines; previously trivial: Ctrl+wheel zoom no longer scrolls the document, #1743; previously trivial: progress log: Turkish case mapping in AutoCorrect, #1923; previously trivial: a mouse drag on a table leaves the view where it is, #1738; previously trivial: progress log: Hebrew field results and smart quotes, #1670; previously trivial: Backspace removes a selected table, rows or columns, #1844; previously trivial: progress log: mixed Hebrew/Latin text keeps its order in Word, #1543; previously trivial: progress log: web typing in non-Latin text no longer repeats, #1480; previously trivial: progress log: font family caches bounded and math font lookups follow font changes, #1354; previously trivial: `-->` AutoCorrect replacement, #2223; previously trivial: numbered headings, #1793; previously trivial: Navigation Find exposes Results, #2096; previously trivial: progress log: Columns on selected text, #2024; previously trivial: progress log: legacy outline, shadow, emboss and imprint text effects drawn, #2534; previously trivial: progress log: Indonesian proofing, #498; previously trivial: progress log: DOCX glossary documents (building blocks); previously trivial: progress log: Read Mode keeps the keyboard and shows whole, unobscured columns, #1772); previously trivial: progress log: font list entries of tall-ascent fonts no longer cut off, #2570); previously trivial: progress log: Split Cells and Convert Text to Table dialogs follow the Word check, #2592; previously trivial: a new source's Tag is made like Word's, three letters of the author and two digits of the year with a number added when taken, and an edit keeps the Tag, #2094; previously trivial: Web Layout fills the window width, #1876; previously trivial: progress log: Different First Page headers reachable from page 1, #2826; previously trivial: Language dialog keeps inherited proofing language and no-proof choices (#2717); previously trivial: progress log: web preferences kept between visits, #2132; previously trivial: progress log: undrawable private-use list bullets show the level's default bullet, #1683; previously trivial: progress log: `w:eastAsianLayout` kept on save; previously trivial: table style editor formats every part, #714); previously trivial: VML freeforms read and kept on save; previously trivial: progress log: consistent preferred cell widths; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: page-break-only list paragraphs hide their labels; previously trivial: progress log: grouped `flowChartOr` shapes draw their circle and crossing spokes; previously trivial: progress log: copies keep their formatting in other programs, #1523; previously trivial: progress log: privacy options on save, #1062; previously trivial: progress log: equation pictures in theme colours on Windows, #1002; previously trivial: symbol fonts, #776; previously trivial: the save prompt answers from the keyboard, #861; previously trivial: table column and row markers on the rulers, #700; previously trivial: table borders and the resize handle drag on the page, #217; previously trivial: formatting revisions landed, #41; previously trivial: Draw tab ink, #307, and Draw Table and Eraser, #303, landed: catalog 402/434; previously minor: readiness table with hours per audience; full number back to the additive weighted sum, 60%; mainstream and essentials numbers added; alpha gate checked; previously major: full re-measure against Word for Mac 16.113.4; restructured to the craftrules progress-docs standard) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** trivial (Insert Table of Figures dialog for any caption label, #3013; previously trivial: clearing a no-match font query keeps the recent heading visible, #2068; previously trivial: a word after a slash wraps whole, like Word, #2963; previously trivial: RTF export writes sections (gap 19); previously trivial: ODT export writes sections (gap 19); previously trivial: progress log: pasted blockquotes are indented paragraphs, #1270; previously trivial: progress log: print-area strings translated in every catalog; previously trivial: progress log: comments readable in the dark theme, #2045; previously trivial: progress log: ODT sections, #1379; previously trivial: progress log: table deletes, Change Case and Unlink Fields respect Track Changes and locked content controls, #1329; previously trivial: progress log: rounded rectangle corners, #1476; previously trivial: progress log: Save As type list, #1399; previously trivial: progress log: privacy options on save, #1062; previously trivial: progress log: equation pictures in theme colours on Windows, #1002; previously trivial: symbol fonts, #776; previously trivial: the save prompt answers from the keyboard, #861; previously trivial: table column and row markers on the rulers, #700; previously trivial: table borders and the resize handle drag on the page, #217; previously trivial: formatting revisions landed, #41; previously trivial: Draw tab ink, #307, and Draw Table and Eraser, #303, landed: catalog 402/434; previously minor: readiness table with hours per audience; full number back to the additive weighted sum, 60%; mainstream and essentials numbers added; alpha gate checked; previously major: full re-measure against Word for Mac 16.113.4; restructured to the craftrules progress-docs standard) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
> **Printing update:** 2026-10-12 · direct desktop printing through craft-print; details below.
WordCraft aims for complete parity with Microsoft Word, then goes further on speed, openness and
@@ -139,6 +139,7 @@ Imported Word 97–2003 `.doc`/`.dot` files now ask for a supported DOCX target
- 2026-10-15: Find opens Navigation on Results; editing the query restores Results after manual Headings/Pages navigation. Typed and selected-text searches retain document contents (#2096).
| Date | What landed |
| 2026-10-18 | Insert Table of Figures opens its dialog (#3013): pick the caption label (Figure, Table, Equation or your own), with or without label and number, page numbers and leader, so a document can have a list of figures and a list of tables. The list is a `TOC \c`/`\a` field (ECMA-376 §17.16.5.68); a Word table of figures with its switches in any order updates too, and Update Table keeps each list's label and leader. |
| 2026-10-18 | Clearing a font query with no matches keeps Recently Used Fonts visible at the top of a scrollable list. The initial scroll position no longer skips its heading; measured keyboard scrolling is unchanged (#2068). |
| 2026-10-17 | Edit Recipient List finds records (#1576): a Find in list box hides rows with no cell containing the text (any case) and counts the matches, and "Show only records that will be merged" hides the rows the filter leaves out; both only change the view. Rows are striped, and edits and removes go to the right record however the grid is narrowed |
| 2026-10-14 | DOCX row table border exceptions render and survive saving. Explicit cell borders win; a row exception does not change other rows. |
+225 -20
View File
@@ -8,6 +8,7 @@ use wordcraft_doc::para::InlineObject;
use wordcraft_doc::props::{CharProps, TabAlign, TabLeader, TabStop};
use wordcraft_doc::{Block, Document, Paragraph, Path, Pos, Source, StoryRef};
use super::references::FieldCode;
use super::{delete_selection, sel_result};
use crate::{CmdError, CmdResult, CommandSpec, Selection, Session, p};
@@ -312,12 +313,9 @@ pub fn specs() -> Vec<CommandSpec> {
sel_result(s)
})
.params(r#"{"title"?: string}"#),
CommandSpec::new("references.tableOfFigures", "Insert Table of Figures", "References › Captions", |s, v| {
let label = p::str(v, "label").unwrap_or("Figure").to_string();
generated_list(s, &format!("TOC \\c \"{label}\""), "")?;
sel_result(s)
})
.params(r#"{"label"?: "Figure|Table|Equation"}"#),
CommandSpec::new("references.tableOfFigures", "Insert Table of Figures", "References › Captions", table_of_figures).params(
r#"{"label"?: "Figure|Table|Equation" or a caption label of your own, "includeLabel"?: bool, "pageNumbers"?: bool, "rightAlign"?: bool, "tabLeader"?: "dot|hyphen|underscore|none"}"#,
),
CommandSpec::new("references.updateFigures", "Update Table", "References › Captions", |s, _| {
update_generated(s)?;
sel_result(s)
@@ -771,9 +769,51 @@ fn refresh_citations(s: &mut Session) -> Result<(), CmdError> {
Ok(())
}
/// The TOC field for `references.tableOfFigures` (ECMA-376 §17.16.5.68): `\c "<label>"` lists the
/// captions that label's SEQ field numbers, `\a "<label>"` lists them without their label and
/// number; `\n` leaves page numbers out and `\p " "` puts them right after the text.
pub fn figures_instr(v: &Value) -> String {
let label: String = p::str(v, "label").unwrap_or("Figure").chars().filter(|c| !matches!(c, '"' | '\\') && !c.is_control()).take(40).collect();
let ident = seq_ident(&label);
let ident = if ident.is_empty() { "Figure".to_string() } else { ident };
let on = |k: &str| p::bool(v, k).unwrap_or(true);
let mut s = format!("TOC \\{} \"{ident}\"", if on("includeLabel") { 'c' } else { 'a' });
if !on("pageNumbers") {
s.push_str(" \\n");
} else if !on("rightAlign") {
s.push_str(" \\p \" \"");
}
s
}
/// The SEQ identifier of a caption label: Word joins a multi-word label with underscores.
fn seq_ident(label: &str) -> String {
label.split_whitespace().collect::<Vec<_>>().join("_")
}
fn table_of_figures(s: &mut Session, v: &Value) -> CmdResult {
let at = generated_list(s, &figures_instr(v), "")?;
// The leader lives in the entries' tab stops (as in a table of contents); updates keep it.
if let Some(l) = p::str(v, "tabLeader").map(super::references::leader) {
let mut i = at + 1;
while let Ok(p) = s.doc.para_mut(StoryRef::Body, &Path::top(i)) {
if p.props.style.as_deref() != Some(ENTRY_STYLE) {
break;
}
if let Some(t) = p.props.tabs.as_mut().and_then(|t| t.last_mut()) {
t.leader = l;
p.touch();
}
i += 1;
}
}
sel_result(s)
}
/// Generated lists (BIBLIOGRAPHY, INDEX, table of figures, TOA): a heading paragraph holding
/// the field, then entry paragraphs marked with the "GeneratedEntry" style family.
pub(crate) fn generated_list(s: &mut Session, instr: &str, title: &str) -> Result<(), CmdError> {
/// the field, then entry paragraphs marked with the "GeneratedEntry" style family. Returns the
/// heading's body block.
pub(crate) fn generated_list(s: &mut Session, instr: &str, title: &str) -> Result<usize, CmdError> {
let at = delete_selection(s)?;
if at.story != StoryRef::Body || at.path.depth() > 0 {
return Err(CmdError::Failed("lists go in the main text".into()));
@@ -787,19 +827,73 @@ pub(crate) fn generated_list(s: &mut Session, instr: &str, title: &str) -> Resul
)?;
let i = if s.doc.para_at(&at).is_some_and(|p| p.is_empty()) { at.path.last() } else { s.doc.split_paragraph(&at)?.path.last() };
s.doc.insert_block(StoryRef::Body, &Path::top(i), Block::Para(head))?;
update_generated(s)
update_generated(s)?;
Ok(i)
}
fn list_kind(p: &Paragraph) -> Option<String> {
p.objects.iter().find_map(|o| match o {
InlineObject::Field { instr, .. } => {
let t = instr.trim_start();
(t.starts_with("BIBLIOGRAPHY") || t.starts_with("INDEX") || t.starts_with("TOA") || t.starts_with("TOC \\c")).then(|| t.to_string())
(t.starts_with("BIBLIOGRAPHY") || t.starts_with("INDEX") || t.starts_with("TOA") || figures_label(t).is_some()).then(|| t.to_string())
}
_ => None,
})
}
/// The caption label (SEQ identifier) a table-of-figures field lists, `\c` or `\a`.
fn figures_label(instr: &str) -> Option<String> {
let f = FieldCode::parse(instr);
if !f.is_table_of_figures() {
return None;
}
Some(f.arg('c').or_else(|| f.arg('a')).map_or_else(|| "Figure".to_string(), seq_ident))
}
/// Whether `p` is a caption numbered by SEQ identifier `ident` (any case, as Word matches it).
fn captioned_as(p: &Paragraph, ident: &str) -> bool {
p.props.style.as_deref() == Some("Caption") && caption_label(p, &p.plain_text()).is_some_and(|l| seq_ident(&l).eq_ignore_ascii_case(ident))
}
/// A caption's text without its label and number (`\a`): what follows its SEQ field, or for a
/// caption typed by hand what follows the label and the number, less the separator.
fn caption_body(p: &Paragraph, label: &str) -> String {
let seq_end = p.object_offsets().into_iter().find_map(|o| match p.object_at(o) {
Some(InlineObject::Field { instr, .. }) if FieldCode::parse(instr).name == "SEQ" => Some(o + wordcraft_doc::para::OBJ.len_utf8()),
_ => None,
});
let rest = match seq_end {
Some(end) => {
let mut k = 0;
let mut rest = String::new();
for (at, c) in p.text.char_indices() {
if c == wordcraft_doc::para::OBJ {
if at >= end
&& let Some(o) = p.objects.get(k)
{
rest.push_str(o.plain_text());
}
k += 1;
} else if at >= end {
rest.push(c);
}
}
rest
}
None => {
let text = p.plain_text();
let after = text.trim_start().strip_prefix(label).unwrap_or(&text).trim_start();
// The number: the word after the label, when it starts with a digit.
match after.find(char::is_whitespace) {
Some(n) if after.starts_with(|c: char| c.is_ascii_digit()) => after[n..].to_string(),
_ if after.starts_with(|c: char| c.is_ascii_digit()) => String::new(),
_ => after.to_string(),
}
}
};
rest.trim_start_matches(|c: char| c.is_whitespace() || matches!(c, ':' | '.' | '-' | '\u{2013}' | '\u{2014}')).trim_end().to_string()
}
const ENTRY_STYLE: &str = "ListEntry";
/// Rebuild every generated list.
@@ -817,7 +911,15 @@ pub fn update_generated(s: &mut Session) -> Result<(), CmdError> {
i += 1;
continue;
}
// Remove old entries.
// Remove old entries, keeping their tab leader.
let leader = s
.doc
.body
.get(i + 1)
.and_then(|b| b.as_para())
.filter(|p| p.props.style.as_deref() == Some(ENTRY_STYLE))
.and_then(|p| p.props.tabs.as_ref()?.last().map(|t| t.leader))
.unwrap_or(TabLeader::Dot);
while s.doc.body.get(i + 1).and_then(|b| b.as_para()).is_some_and(|p| p.props.style.as_deref() == Some(ENTRY_STYLE)) {
s.doc.remove_block(StoryRef::Body, &Path::top(i + 1))?;
}
@@ -830,7 +932,7 @@ pub fn update_generated(s: &mut Session) -> Result<(), CmdError> {
para.props.indent_first = Some(-36.0);
}
if text.contains('\t') {
para.props.tabs = Some(vec![TabStop { pos: width - 0.5, align: TabAlign::Right, leader: TabLeader::Dot }]);
para.props.tabs = Some(vec![TabStop { pos: width - 0.5, align: TabAlign::Right, leader }]);
}
s.doc.insert_block(StoryRef::Body, &Path::top(i + 1 + k), Block::Para(para))?;
}
@@ -874,12 +976,14 @@ fn entries_for(s: &mut Session, kind: &str) -> Vec<(String, bool)> {
}
// Collect marks with positions.
let mut marks: Vec<(String, Pos)> = Vec::new();
let caption_label = kind.split('"').nth(1).unwrap_or("Figure").to_string();
let figures = figures_label(kind);
let bare = FieldCode::parse(kind).has('a');
for path in s.doc.para_paths(StoryRef::Body) {
let Some(p) = s.doc.para(StoryRef::Body, &path) else { continue };
if kind.starts_with("TOC \\c") {
if p.props.style.as_deref() == Some("Caption") && p.plain_text().starts_with(&caption_label) {
marks.push((p.plain_text(), Pos { story: StoryRef::Body, path: path.clone(), off: 0 }));
if let Some(ident) = &figures {
if captioned_as(p, ident) {
let text = if bare { caption_body(p, &ident.replace('_', " ")) } else { p.plain_text() };
marks.push((text, Pos { story: StoryRef::Body, path: path.clone(), off: 0 }));
}
continue;
}
@@ -900,8 +1004,8 @@ fn entries_for(s: &mut Session, kind: &str) -> Vec<(String, bool)> {
let mut v = Vec::new();
for path in s.doc.para_paths(StoryRef::Body) {
let Some(p) = s.doc.para(StoryRef::Body, &path) else { continue };
if kind.starts_with("TOC \\c") {
if p.props.style.as_deref() == Some("Caption") && p.plain_text().starts_with(&caption_label) {
if let Some(ident) = &figures {
if captioned_as(p, ident) {
v.push(Pos { story: StoryRef::Body, path: path.clone(), off: 0 });
}
continue;
@@ -923,8 +1027,11 @@ fn entries_for(s: &mut Session, kind: &str) -> Vec<(String, bool)> {
x.1 = pg;
}
}
if kind.starts_with("TOC \\c") {
return with_pages.into_iter().map(|(t, p)| (format!("{t}\t{p}"), false)).collect();
if figures.is_some() {
// `\n` without page numbers; `\p` the separator before them (a tab by default).
let f = FieldCode::parse(kind);
let sep = f.arg('p').unwrap_or("\t");
return with_pages.into_iter().map(|(t, p)| (if f.has('n') { t } else { format!("{t}{sep}{p}") }, false)).collect();
}
// Index / TOA: group by entry, sort, list pages.
let lang = super::default_lang(s);
@@ -1297,6 +1404,104 @@ mod tests {
assert!(s.doc.plain_text(StoryRef::Body).ends_with("See Photo Plate I"), "{}", s.doc.plain_text(StoryRef::Body));
}
/// The entries of every generated list, in order, with their last tab stop's leader.
fn list_entries(s: &Session) -> Vec<(String, Option<TabLeader>)> {
s.doc
.body
.iter()
.filter_map(|b| b.as_para())
.filter(|p| p.props.style.as_deref() == Some(ENTRY_STYLE))
.map(|p| (p.plain_text(), p.props.tabs.as_ref().and_then(|t| t.last()).map(|t| t.leader)))
.collect()
}
/// #3013: a table of figures for any caption label, so a document can have a list of figures
/// and a list of tables; each keeps its own label and leader when updated.
#[test]
fn table_of_figures_for_each_caption_label() {
let mut s = Session::new(wordcraft_doc::Document::from_text("Kiln\nPress\nRock\nTablet\n"));
for (text, label) in [("Kiln", "Figure"), ("Press", "Table"), ("Rock", "Photo Plate"), ("Tablet", "Figure")] {
s.run("select.text", &json!({"text": text})).unwrap();
s.run("references.caption", &json!({"label": label, "text": text})).unwrap();
}
s.run("caret.docEnd", &json!({})).unwrap();
s.run("references.tableOfFigures", &json!({"label": "Table", "tabLeader": "hyphen"})).unwrap();
assert_eq!(list_entries(&s), [("Table 1: Press\t1".to_string(), Some(TabLeader::Hyphen))]);
s.run("caret.docEnd", &json!({})).unwrap();
s.run("text.newParagraph", &json!({})).unwrap();
s.run("references.tableOfFigures", &json!({"label": "Photo Plate"})).unwrap();
s.run("caret.docEnd", &json!({})).unwrap();
s.run("text.newParagraph", &json!({})).unwrap();
s.run("references.tableOfFigures", &json!({})).unwrap();
let instrs: Vec<String> = fields(&s).into_iter().map(|(i, _)| i).filter(|i| i.starts_with("TOC")).collect();
assert_eq!(instrs, [r#"TOC \c "Table""#, r#"TOC \c "Photo_Plate""#, r#"TOC \c "Figure""#]);
let want = [
("Table 1: Press\t1", TabLeader::Hyphen),
("Photo Plate 1: Rock\t1", TabLeader::Dot),
("Figure 1: Kiln\t1", TabLeader::Dot),
("Figure 2: Tablet\t1", TabLeader::Dot),
];
let want: Vec<(String, Option<TabLeader>)> = want.iter().map(|(t, l)| (t.to_string(), Some(*l))).collect();
assert_eq!(list_entries(&s), want);
// Update Table rebuilds each list with its own label and keeps the leader.
s.run("references.updateFigures", &json!({})).unwrap();
assert_eq!(list_entries(&s), want);
// A table of figures is not the table of contents.
assert!(s.run("references.updateToc", &json!({})).is_ok());
assert_eq!(list_entries(&s), want);
}
/// #3013: Insert Table of Figures' options as TOC switches: `\a` without the label and number,
/// `\n` without page numbers, `\p " "` with the page right after the text.
#[test]
fn table_of_figures_options() {
assert_eq!(figures_instr(&json!({})), r#"TOC \c "Figure""#);
assert_eq!(figures_instr(&json!({"label": "Table", "includeLabel": false})), r#"TOC \a "Table""#);
assert_eq!(figures_instr(&json!({"pageNumbers": false})), r#"TOC \c "Figure" \n"#);
assert_eq!(figures_instr(&json!({"rightAlign": false})), r#"TOC \c "Figure" \p " ""#);
assert_eq!(figures_instr(&json!({"label": "\"\\ "})), r#"TOC \c "Figure""#, "an empty label is Figure");
for (params, entry) in [
(json!({"includeLabel": false}), "The kiln\t1"),
(json!({"pageNumbers": false}), "Figure 1: The kiln"),
(json!({"rightAlign": false, "includeLabel": false}), "The kiln 1"),
] {
let mut s = Session::new(wordcraft_doc::Document::from_text("Kiln\n"));
s.run("select.text", &json!({"text": "Kiln"})).unwrap();
s.run("references.caption", &json!({"label": "Figure", "text": "The kiln"})).unwrap();
s.run("caret.docEnd", &json!({})).unwrap();
s.run("references.tableOfFigures", &params).unwrap();
let entries = list_entries(&s);
assert_eq!(entries.iter().map(|(t, _)| t.as_str()).collect::<Vec<_>>(), [entry], "{params}");
assert_eq!(entries.first().and_then(|(_, l)| *l).is_some(), entry.contains('\t'), "a tab stop only for a tab");
}
}
/// #3013: a table of figures from Word (`TOC \h \z \c "Figure"`, switches in any order) and
/// hand-typed captions are listed; a caption whose label only starts with another's is not.
#[test]
fn table_of_figures_from_word_and_typed_captions() {
let mut d = wordcraft_doc::Document::from_text("Figure 1: Typed\nFigures 2: Not a figure\nFigure 3 \u{2014} Dashed\nList");
for path in d.para_paths(StoryRef::Body).into_iter().take(3) {
d.para_mut(StoryRef::Body, &path).unwrap().props.style = Some("Caption".into());
}
let mut s = Session::new(d);
s.run("caret.docEnd", &json!({})).unwrap();
s.run("text.newParagraph", &json!({})).unwrap();
let at = s.sel.focus.clone();
let field = InlineObject::Field {
instr: r#"TOC \h \z \a "Figure""#.into(),
result: String::new(),
locked: false,
dirty: false,
code: false,
form: String::new(),
};
s.doc.insert_object(&at, field, &CharProps::default()).unwrap();
s.run("references.updateFigures", &json!({})).unwrap();
let entries: Vec<String> = list_entries(&s).into_iter().map(|(t, _)| t).collect();
assert_eq!(entries, ["Typed\t1", "Dashed\t1"]);
}
/// #2940: a caption may start with its chapter number (`STYLEREF`) and carry its label text
/// between that and the `SEQ` number; the text is the label, not the `SEQ` identifier.
#[test]
+3 -1
View File
@@ -149,7 +149,9 @@ fn insert_index(s: &mut Session, v: &Value) -> CmdResult {
}
para.touch();
}
None => super::citations::generated_list(s, &instr, "Index")?,
None => {
super::citations::generated_list(s, &instr, "Index")?;
}
}
if let Some((i, _)) = index_field(s) {
rebuild(s, i, p::str(v, "tabLeader").map(leader))?;
+9 -4
View File
@@ -149,6 +149,11 @@ impl FieldCode {
self.switches.iter().any(|(k, _)| *k == c)
}
/// A table of figures (`TOC \c` or `TOC \a`, ECMA-376 §17.16.5.68), not a table of contents.
pub fn is_table_of_figures(&self) -> bool {
self.name == "TOC" && (self.has('c') || self.has('a'))
}
/// The argument of switch `c`, if the switch is there with one.
pub fn arg(&self, c: char) -> Option<&str> {
self.switches.iter().find(|(k, _)| *k == c).and_then(|(_, a)| a.as_deref())
@@ -360,7 +365,7 @@ fn convertible_toc_range(s: &Session, r: &wordcraft_doc::FieldRange) -> bool {
let field = FieldCode::parse(&r.instr);
toc_range(s).is_none()
&& field.name == "TOC"
&& !field.has('c')
&& !field.is_table_of_figures()
&& r.depth == 0
&& !r.locked
&& r.start.path.depth() == 0
@@ -425,7 +430,7 @@ fn toc_field(p: &Paragraph) -> Option<String> {
p.objects.iter().find_map(|o| match o {
InlineObject::Field { instr, .. } => {
let f = FieldCode::parse(instr);
(f.name == "TOC" && !f.has('c')).then(|| instr.clone())
(f.name == "TOC" && !f.is_table_of_figures()).then(|| instr.clone())
}
_ => None,
})
@@ -436,7 +441,7 @@ fn cached_toc_tabs(s: &Session) -> Vec<(String, Vec<TabStop>)> {
let span = toc_range(s).map(|(start, end)| (start.saturating_add(1), end)).or_else(|| {
s.doc.field_ranges(StoryRef::Body).into_iter().find_map(|r| {
let f = FieldCode::parse(&r.instr);
(r.depth == 0 && !r.locked && r.start.path.depth() == 0 && r.end.path.depth() == 0 && f.name == "TOC" && !f.has('c'))
(r.depth == 0 && !r.locked && r.start.path.depth() == 0 && r.end.path.depth() == 0 && f.name == "TOC" && !f.is_table_of_figures())
.then_some((r.start.path.last(), r.end.path.last()))
})
});
@@ -560,7 +565,7 @@ fn regenerate_toc(s: &mut Session, leader: Option<TabLeader>, tabs: &[(String, V
for o in &mut p.objects {
if let InlineObject::Field { instr, result, .. } = o {
let f = FieldCode::parse(instr);
if f.name == "TOC" && !f.has('c') {
if f.name == "TOC" && !f.is_table_of_figures() {
result.clear();
}
}
+122 -6
View File
@@ -1,8 +1,9 @@
//! References dialogs (#402): Caption (with New Label and Numbering), Cross-reference (#916),
//! Index and Mark Index Entry, Custom Table of Contents (with Options and the TOC styles), and the
//! Source Manager with Create / Edit Source. Each fills a form from the session and ends in a
//! command (`references.caption`, `insert.crossReference`, `references.index`,
//! `references.markEntry`, `references.toc`, `references.sources`, `references.citation`), so
//! Index and Mark Index Entry, Custom Table of Contents (with Options and the TOC styles), Table of
//! Figures (#3013), and the Source Manager with Create / Edit Source. Each fills a form from the
//! session and ends in a command (`references.caption`, `insert.crossReference`, `references.index`,
//! `references.markEntry`, `references.toc`, `references.tableOfFigures`, `references.sources`,
//! `references.citation`), so
//! agents get the same result without the dialog. Shown through [`crate::dialogs::Dialog::Refs`].
use egui::{Sense, Ui, vec2};
@@ -44,6 +45,7 @@ pub enum RefsDialog {
form: TocForm,
level: u8,
},
Figures(FiguresForm),
Sources {
selected: Option<usize>,
},
@@ -62,6 +64,7 @@ impl RefsDialog {
RefsDialog::Toc(_) => "toc",
RefsDialog::TocOptions { .. } => "tocOptions",
RefsDialog::TocStyles { .. } => "tocStyles",
RefsDialog::Figures(_) => "tableOfFigures",
RefsDialog::Sources { .. } => "sourceManager",
RefsDialog::Source(f) if f.edit => "editSource",
RefsDialog::Source(_) => "createSource",
@@ -80,6 +83,7 @@ impl RefsDialog {
RefsDialog::Toc(_) => "Table of Contents",
RefsDialog::TocOptions { .. } => "Table of Contents Options",
RefsDialog::TocStyles { .. } => "Table of Contents Styles",
RefsDialog::Figures(_) => "Table of Figures",
RefsDialog::Sources { .. } => "Source Manager",
RefsDialog::Source(f) if f.edit => "Edit Source",
RefsDialog::Source(_) => "Create Source",
@@ -88,7 +92,7 @@ impl RefsDialog {
}
/// The dialog `ui.dialog` opens by `name`: `caption`, `crossReference`, `index`, `markEntry`, `toc`,
/// `sourceManager` or `createSource` (which inserts a citation of the new source).
/// `tableOfFigures`, `sourceManager` or `createSource` (which inserts a citation of the new source).
pub fn open(name: &str, app: &WordApp) -> Option<RefsDialog> {
Some(match name {
"caption" => RefsDialog::Caption(CaptionForm::read(app)),
@@ -96,6 +100,7 @@ pub fn open(name: &str, app: &WordApp) -> Option<RefsDialog> {
"index" => RefsDialog::Index(IndexForm::read(app)),
"markEntry" => RefsDialog::MarkEntry(MarkForm::read(app)),
"toc" => RefsDialog::Toc(TocForm::read(app)),
"tableOfFigures" => RefsDialog::Figures(FiguresForm::read(app)),
"sourceManager" => RefsDialog::Sources { selected: None },
"createSource" => RefsDialog::Source(Box::new(SourceForm::new(true, false))),
_ => return None,
@@ -114,6 +119,7 @@ pub fn body(app: &mut WordApp, ui: &mut Ui, d: &mut RefsDialog) -> bool {
RefsDialog::Toc(f) => toc_ui(app, ui, f),
RefsDialog::TocOptions { form, was } => toc_options_ui(app, ui, form, was),
RefsDialog::TocStyles { form, level } => toc_styles_ui(app, ui, form, level),
RefsDialog::Figures(f) => figures_ui(app, ui, f),
RefsDialog::Sources { selected } => sources_ui(app, ui, selected),
RefsDialog::Source(f) => source_ui(app, ui, f),
};
@@ -518,7 +524,7 @@ pub struct IndexForm {
fn body_field(app: &WordApp, name: &str) -> Option<FieldCode> {
app.session.doc.body.iter().filter_map(|b| b.as_para()).flat_map(|p| p.objects.iter()).find_map(|o| match o {
wordcraft_doc::InlineObject::Field { instr, .. } => {
Some(FieldCode::parse(instr)).filter(|f| f.name == name && (name != "TOC" || !f.has('c')))
Some(FieldCode::parse(instr)).filter(|f| f.name == name && (name != "TOC" || !f.is_table_of_figures()))
}
_ => None,
})
@@ -901,6 +907,85 @@ fn toc_styles_ui(app: &mut WordApp, ui: &mut Ui, f: &mut TocForm, level: &mut u8
if ok || cancel { go(RefsDialog::Toc(f.clone())) } else { Next::Stay }
}
// ---------------------------------------------------------------------------------------------
// Table of Figures
/// The Table of Figures dialog's fields (#3013).
#[derive(Clone, Debug, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FiguresForm {
/// The caption label whose captions are listed.
pub label: String,
/// Built-in labels, the user's own and any the document's captions use.
pub labels: Vec<String>,
pub include_label: bool,
pub page_numbers: bool,
pub right_align: bool,
pub leader: String,
}
impl FiguresForm {
pub fn read(app: &WordApp) -> FiguresForm {
let mut labels: Vec<String> = BUILT_IN_LABELS.iter().map(|l| l.to_string()).collect();
for l in app.ui.caption_labels.iter().cloned().chain(wordcraft_engine::cmd::citations::caption_labels(&app.session)) {
if !labels.contains(&l) {
labels.push(l);
}
}
FiguresForm { label: "Figure".into(), labels, include_label: true, page_numbers: true, right_align: true, leader: "dot".into() }
}
/// `references.tableOfFigures` parameters.
pub fn params(&self) -> Value {
json!({
"label": self.label,
"includeLabel": self.include_label,
"pageNumbers": self.page_numbers,
"rightAlign": self.right_align,
"tabLeader": self.leader,
})
}
}
fn figures_ui(app: &mut WordApp, ui: &mut Ui, f: &mut FiguresForm) -> Next {
let lines: Vec<(usize, String, String)> = [(1, "1"), (2, "3")]
.iter()
.map(|(n, page)| {
let text = if f.include_label { format!("{} {n}: {}", tl!(&f.label), tl!("Text")) } else { tl!("Text").to_string() };
(0, text, if f.page_numbers { page.to_string() } else { String::new() })
})
.collect();
sample(ui, &lines, f.right_align, leader_char(&f.leader));
ui.add_space(6.0);
ui.horizontal(|ui| {
ui.label(tl!("Label:"));
let labels: Vec<(String, &str)> = f.labels.iter().map(|l| (l.clone(), l.as_str())).collect();
egui::ComboBox::from_id_salt("tof_label").selected_text(tl!(&f.label)).width(150.0).show_ui(ui, |ui| {
for (v, l) in &labels {
ui.selectable_value(&mut f.label, v.clone(), tl!(l));
}
});
});
ui.checkbox(&mut f.include_label, tl!("Include label and number"));
ui.checkbox(&mut f.page_numbers, tl!("Include page numbers"));
ui.add_enabled_ui(f.page_numbers, |ui| {
ui.checkbox(&mut f.right_align, tl!("Page numbers at the right margin"));
ui.horizontal(|ui| {
ui.label(tl!("Tab leader:"));
ui.add_enabled_ui(f.right_align, |ui| {
let opts: Vec<(String, &str)> = LEADERS.iter().map(|(v, l)| (v.to_string(), *l)).collect();
choice(ui, "tof_leader", &mut f.leader, &opts);
});
});
});
ui.add_space(4.0);
let (ok, cancel) = buttons(ui, tl!("OK"));
if ok && let Err(e) = app.run("references.tableOfFigures", f.params()) {
app.status(e);
}
if ok || cancel { Next::Close } else { Next::Stay }
}
// ---------------------------------------------------------------------------------------------
// Sources
@@ -1356,6 +1441,37 @@ mod tests {
assert_eq!(TocForm::read(&a), f);
}
/// #3013: Insert Table of Figures opens its dialog, which offers the document's own caption
/// labels, so a list of tables (or of a custom label) can be built next to a list of figures.
#[test]
fn table_of_figures_dialog_picks_the_caption_label() {
let mut a = app("Kiln\nPress\nRock\n");
for (text, label) in [("Kiln", "Figure"), ("Press", "Table"), ("Rock", "Specimen")] {
a.run("select.text", json!({"text": text})).unwrap();
a.run("references.caption", json!({"label": label, "text": text})).unwrap();
}
a.run("caret.docEnd", json!({})).unwrap();
assert_eq!(a.run("references.tableOfFigures", json!({})).unwrap(), json!({"pending": "tableOfFigures"}));
let RefsDialog::Figures(mut f) = form(&a) else { panic!() };
assert_eq!(f.label, "Figure");
assert_eq!(f.labels, ["Equation", "Figure", "Table", "Specimen"]);
f.label = "Table".into();
f.leader = "underscore".into();
a.run("references.tableOfFigures", f.params()).unwrap();
assert!(instrs(&a).contains(&r#"TOC \c "Table""#.to_string()), "{:?}", instrs(&a));
let text = a.session.doc.plain_text(StoryRef::Body);
assert!(text.contains("Table 1: Press\t1") && !text.contains("Figure 1: Kiln\t"), "{text}");
a.run("caret.docEnd", json!({})).unwrap();
a.run("ui.dialog", json!({"name": "tableOfFigures"})).unwrap();
let RefsDialog::Figures(mut f) = form(&a) else { panic!() };
f.label = "Specimen".into();
f.include_label = false;
f.right_align = false;
a.run("references.tableOfFigures", f.params()).unwrap();
assert!(instrs(&a).contains(&r#"TOC \a "Specimen" \p " ""#.to_string()), "{:?}", instrs(&a));
assert!(a.session.doc.plain_text(StoryRef::Body).contains("Rock 1"));
}
/// #402: Insert Citation without a source opens Create Source; the type picks the fields,
/// "show all" adds the rest, and Edit Source from the Source Manager updates the citation.
#[test]
+1
View File
@@ -238,6 +238,7 @@ fn untranslated_interface_text() {
"index",
"markEntry",
"toc",
"tableOfFigures",
"sourceManager",
"createSource",
];
+2
View File
@@ -2687,3 +2687,5 @@
Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Въведете размер в произволна единица, например 5 cm или 300 px. Другата страна се променя съответно, така че картината запазва пропорциите си.
Import… Импортиране…
# #3013: Insert Table of Figures
Include label and number Включване на етикета и номера
Can't render this file because it contains an unexpected character in line 7 and column 25.
+2
View File
@@ -2690,3 +2690,5 @@ preview Title Ukázka
Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Zadejte velikost v libovolné jednotce, například 5 cm nebo 300 px. Druhá strana se přizpůsobí, takže obrázek zachová své proporce.
Import… Importovat…
# #3013: Insert Table of Figures
Include label and number Zahrnout popisek a číslo
Can't render this file because it contains an unexpected character in line 7 and column 25.
+2
View File
@@ -2287,3 +2287,5 @@
Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Geben Sie eine Größe in einer beliebigen Einheit ein, z. B. 5 cm oder 300 px. Die andere Seite passt sich an, sodass das Bild seine Proportionen behält.
Import… Importieren…
# #3013: Insert Table of Figures
Include label and number Bezeichnung und Nummer einbeziehen
Can't render this file because it contains an unexpected character in line 6 and column 25.
+2
View File
@@ -2654,3 +2654,5 @@ preview Title Título
Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Escribe un tamaño en cualquier unidad, como 5 cm o 300 px. El otro lado se ajusta, así que la imagen conserva sus proporciones.
Import… Importar…
# #3013: Insert Table of Figures
Include label and number Incluir rótulo y número
Can't render this file because it contains an unexpected character in line 6 and column 25.
+2
View File
@@ -2322,3 +2322,5 @@
Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Sisesta suurus mis tahes ühikus, näiteks 5 cm või 300 px. Teine külg muutub kaasa, nii et pilt säilitab oma proportsioonid.
Import… Impordi…
# #3013: Insert Table of Figures
Include label and number Lisa silt ja number
Can't render this file because it contains an unexpected character in line 847 and column 23.
+2
View File
@@ -2700,3 +2700,5 @@ template Letter Lettre
Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Tapez une taille dans n'importe quelle unité, par exemple 5 cm ou 300 px. L'autre côté suit, et l'image garde ses proportions.
Import… Importer…
# #3013: Insert Table of Figures
Include label and number Inclure l’étiquette et le numéro
Can't render this file because it contains an unexpected character in line 7 and column 25.
+2
View File
@@ -2698,3 +2698,5 @@ preview Title Naslov
Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Upišite veličinu u bilo kojoj jedinici, npr. 5 cm ili 300 px. Druga se strana prilagođava pa slika zadržava svoje proporcije.
Import… Uvezi…
# #3013: Insert Table of Figures
Include label and number Uključi oznaku i broj
Can't render this file because it contains an unexpected character in line 6 and column 25.
+2
View File
@@ -2694,3 +2694,5 @@
Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Írj be egy méretet bármilyen mértékegységben, például 5 cm vagy 300 px. A másik oldal követi, így a kép megtartja az arányait.
Import… Importálás…
# #3013: Insert Table of Figures
Include label and number Címkével és számmal
Can't render this file because it contains an unexpected character in line 6 and column 25.
+2
View File
@@ -2707,3 +2707,5 @@ preview Title Esempio
Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Scrivi una dimensione in qualsiasi unità, ad esempio 5 cm o 300 px. L'altro lato si adegua, così l'immagine mantiene le proporzioni.
Import… Importa…
# #3013: Insert Table of Figures
Include label and number Includi etichetta e numero
Can't render this file because it contains an unexpected character in line 6 and column 25.
+2
View File
@@ -2288,3 +2288,5 @@
Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. 5 cm や 300 px など、任意の単位でサイズを入力します。もう一方の辺も合わせて変わるため、図の縦横比は保たれます。
Import… インポート…
# #3013: Insert Table of Figures
Include label and number ラベルと番号を含める
Can't render this file because it contains an unexpected character in line 925 and column 23.
+2
View File
@@ -2542,3 +2542,5 @@ preview Title 제목
Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. 5 cm 또는 300 px처럼 원하는 단위로 크기를 입력하세요. 다른 쪽도 함께 바뀌어 그림의 비율이 유지됩니다.
Import… 가져오기…
# #3013: Insert Table of Figures
Include label and number 레이블 및 번호 포함
Can't render this file because it contains an unexpected character in line 931 and column 23.
+2
View File
@@ -2703,3 +2703,5 @@ preview Title Tytuł
Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Wpisz rozmiar w dowolnej jednostce, np. 5 cm lub 300 px. Druga strona dopasuje się, więc obraz zachowa proporcje.
Import… Importuj…
# #3013: Insert Table of Figures
Include label and number Dołącz etykietę i numer
Can't render this file because it contains an unexpected character in line 6 and column 25.
+2
View File
@@ -2300,3 +2300,5 @@
Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Digite um tamanho em qualquer unidade, como 5 cm ou 300 px. O outro lado acompanha, e a imagem mantém as proporções.
Import… Importar…
# #3013: Insert Table of Figures
Include label and number Incluir rótulo e número
Can't render this file because it contains an unexpected character in line 6 and column 25.
+2
View File
@@ -2696,3 +2696,5 @@ preview Title Пример
Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Введите размер в любых единицах, например 5 cm или 300 px. Другая сторона изменится вместе с ней, и рисунок сохранит пропорции.
Import… Импорт…
# #3013: Insert Table of Figures
Include label and number Включить подпись и номер
Can't render this file because it contains an unexpected character in line 7 and column 25.
+2
View File
@@ -2298,3 +2298,5 @@
Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Unesite veličinu u bilo kojoj jedinici, na primer 5 cm ili 300 px. Druga strana se prilagođava, pa slika zadržava svoje proporcije.
Import… Uvezi…
# #3013: Insert Table of Figures
Include label and number Uključi oznaku i broj
Can't render this file because it contains an unexpected character in line 6 and column 25.
+2
View File
@@ -2302,3 +2302,5 @@
Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Унесите величину у било којој јединици, на пример 5 cm или 300 px. Друга страна се прилагођава, па слика задржава своје пропорције.
Import… Увези…
# #3013: Insert Table of Figures
Include label and number Укључи ознаку и број
Can't render this file because it contains an unexpected character in line 11 and column 25.
+2
View File
@@ -2713,3 +2713,5 @@ preview Title Titel
Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Skriv en storlek i valfri enhet, till exempel 5 cm eller 300 px. Den andra sidan följer med, så att bilden behåller sina proportioner.
Import… Importera…
# #3013: Insert Table of Figures
Include label and number Ta med etikett och nummer
Can't render this file because it contains an unexpected character in line 7 and column 25.
+2
View File
@@ -2695,3 +2695,5 @@ preview Title Örnek
Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Herhangi bir birimde bir boyut yazın, örneğin 5 cm veya 300 px. Diğer kenar da buna uyar, böylece resim oranlarını korur.
Import… İçe aktar…
# #3013: Insert Table of Figures
Include label and number Etiketi ve numarayı ekle
Can't render this file because it contains an unexpected character in line 799 and column 23.
+2
View File
@@ -2300,3 +2300,5 @@
Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Введіть розмір у будь-яких одиницях, наприклад 5 cm або 300 px. Інша сторона зміниться разом із нею, тож зображення збереже пропорції.
Import… Імпортувати…
# #3013: Insert Table of Figures
Include label and number Додавати мітку й номер
Can't render this file because it contains an unexpected character in line 6 and column 31.
+2
View File
@@ -2738,3 +2738,5 @@ template Letter Thư
Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Nhập kích thước theo đơn vị bất kỳ, ví dụ 5 cm hoặc 300 px. Cạnh còn lại thay đổi theo, nên ảnh giữ nguyên tỷ lệ.
Import… Nhập…
# #3013: Insert Table of Figures
Include label and number Bao gồm nhãn và số
Can't render this file because it contains an unexpected character in line 7 and column 25.
+2
View File
@@ -2300,3 +2300,5 @@
Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. 可用任意单位输入大小,例如 5 cm 或 300 px。另一边随之调整,图片保持原有比例。
Import… 导入…
# #3013: Insert Table of Figures
Include label and number 包含标签和编号
Can't render this file because it contains an unexpected character in line 7 and column 25.
+2
View File
@@ -2289,3 +2289,5 @@
Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. 可用任何單位輸入大小,例如 5 cm 或 300 px。另一邊隨之調整,圖片保持原有比例。
Import… 匯入…
# #3013: Insert Table of Figures
Include label and number 包含標籤和編號
Can't render this file because it contains an unexpected character in line 926 and column 23.
+8 -4
View File
@@ -2550,10 +2550,14 @@ fn input_dialog(id: &str, params: &Value) -> Option<&'static str> {
"format.enclose" if params.get("shape").is_none() => Some("enclose"),
// Footnote and Endnote without settings shows its dialog (#385).
"references.noteOptions" if params.as_object().is_none_or(|m| m.is_empty()) => Some("noteOptions"),
// References dialogs (#402): Insert Caption, Insert Index, Mark Entry, Manage Sources and
// Insert Citation without a source.
"references.caption" | "references.index" if params.as_object().is_none_or(|m| m.is_empty()) => {
Some(if id == "references.caption" { "caption" } else { "index" })
// References dialogs (#402): Insert Caption, Insert Index, Insert Table of Figures (#3013),
// Mark Entry, Manage Sources and Insert Citation without a source.
"references.caption" | "references.index" | "references.tableOfFigures" if params.as_object().is_none_or(|m| m.is_empty()) => {
Some(match id {
"references.caption" => "caption",
"references.index" => "index",
_ => "tableOfFigures",
})
}
"references.markEntry" if !has("entry") => Some("markEntry"),
// Cross-reference without a target shows its dialog (#916); scripts get the target list.
+3 -2
View File
@@ -1,6 +1,6 @@
# WordCraft parity with Microsoft Word
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-16 · **Change:** trivial (shapes inserted in a row are all kept, #2019; previously trivial: East Asian typography: `w:eastAsianLayout` kept on save, not laid out; previously trivial: Ink to Shape recognises arrows; previously trivial: Windows Magnifier follows the caret onto a new line, #1119; previously trivial: table border drags with Ctrl and Alt, from the left edge and of selected cells, #711; previously trivial: table column and row markers on the rulers, #700; previously trivial: table borders and the resize handle drag on the page, #217; previously trivial: all 12 bibliography styles, #383; previously trivial: formatting revisions landed, #41; previously trivial: Draw Table and Eraser landed, #303; previously minor: readiness table with hours per audience; full number back to the additive weighted sum, 60%; mainstream and essentials numbers added; alpha gate checked; previously major: full re-measure against Word for Mac 16.113.4; replaces the parity tables that lived in ROADMAP.md) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** trivial (Insert Table of Figures lists any caption label, #3013; previously trivial: shapes inserted in a row are all kept, #2019; previously trivial: East Asian typography: `w:eastAsianLayout` kept on save, not laid out; previously trivial: Ink to Shape recognises arrows; previously trivial: Windows Magnifier follows the caret onto a new line, #1119; previously trivial: table border drags with Ctrl and Alt, from the left edge and of selected cells, #711; previously trivial: table column and row markers on the rulers, #700; previously trivial: table borders and the resize handle drag on the page, #217; previously trivial: all 12 bibliography styles, #383; previously trivial: formatting revisions landed, #41; previously trivial: Draw Table and Eraser landed, #303; previously minor: readiness table with hours per audience; full number back to the additive weighted sum, 60%; mainstream and essentials numbers added; alpha gate checked; previously major: full re-measure against Word for Mac 16.113.4; replaces the parity tables that lived in ROADMAP.md) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
The authoritative assessment of how close WordCraft is to Microsoft Word. [`ROADMAP.md`](../ROADMAP.md)
summarizes it; [`gaps.md`](gaps.md) itemizes every shortfall; the deep checklists are
@@ -64,7 +64,7 @@ full parity for the area.
| Page layout and sections | 7 | 70% | 72% | 12–20 | Margins, size, columns, breaks, page borders, line numbers, vertical alignment, drop caps, hyphenation. Columns don't balance; RTL sections missing. Vertical sections (tbRl/btLr) landed (#491). Detail: [layout-parity.md](layout-parity.md). |
| Headers, footers, page numbers, fields | 5 | 70% | 70% | 8–12 | First/even/odd, link to previous, TOC as an updatable field (#52), cross-references to real bookmarks (#227). Field coverage is a subset of Word's ~90 field codes; Field dialog missing. |
| Footnotes and endnotes | 3 | 60% | 60% | 3–5 | Placed and editable, numbers from Word files (#103); long notes continue onto the next page under the continuation separator (#352). Footnote and Endnote dialog with options per section: below text, end of section, number format, start at, restart each section or page, custom mark, convert (#385). |
| References (TOC, citations, bibliography, captions, index, TOA) | 5 | 65% | 60% | 12–20 | Working first versions, Zotero integration (#189), Zotero/Mendeley `ADDIN` fields round-trip. Caption, Index, Mark Entry, Custom TOC and Create/Edit Source dialogs; updates honour the TOC (`\o \u \t \f \n \p \h`), INDEX (`\c \e \h \r`), XE (`\b \i \t \r`) and SEQ/STYLEREF chapter switches (#402); Insert Caption… on the right-click menu of pictures, shapes, text boxes and charts (#1230). All 12 of Word's bibliography styles (APA, MLA, Chicago, IEEE, Harvard, ISO 690 author-date and numerical, Turabian, GB/T 7714, GOST name and title sort, SIST02; #383); bibliography sources saved in DOCX (#790). Manage Sources imports Word's bibliography file (`b:Sources`, `Sources.xml`, #2640). |
| References (TOC, citations, bibliography, captions, index, TOA) | 5 | 65% | 60% | 12–20 | Working first versions, Zotero integration (#189), Zotero/Mendeley `ADDIN` fields round-trip. Caption, Index, Mark Entry, Custom TOC, Table of Figures and Create/Edit Source dialogs; updates honour the TOC (`\o \u \t \f \n \p \h`), table of figures (`\c \a \n \p`, any caption label, so a list of tables sits beside a list of figures; #3013), INDEX (`\c \e \h \r`), XE (`\b \i \t \r`) and SEQ/STYLEREF chapter switches (#402); Insert Caption… on the right-click menu of pictures, shapes, text boxes and charts (#1230). All 12 of Word's bibliography styles (APA, MLA, Chicago, IEEE, Harvard, ISO 690 author-date and numerical, Turabian, GB/T 7714, GOST name and title sort, SIST02; #383); bibliography sources saved in DOCX (#790). Manage Sources imports Word's bibliography file (`b:Sources`, `Sources.xml`, #2640). |
| Review (proofing UI, comments, track changes, compare, protect) | 8 | 72% | 75% | 15–25 | Comment balloons, tracked insert/delete incl. paragraph marks (#244, #125), Compare / Combine with comparison settings (case, white space, formatting, tables, word or character level, #412), Restrict Editing pane with formatting and editing restrictions, exceptions for everyone and a password (#412). Formatting revisions (#41): kept in DOCX, recorded while tracking, accepted/rejected, "Formatted: …" balloons and change bars. Moves (#400): kept in DOCX, recorded on a tracked cut and paste, accepted/rejected at either end, green markup and "Moved from/to" balloons. Show revisions in balloons takes tracked deletions in the body out of the text into "Deleted: …" balloons (insertions stay inline, as in Word; deletions in headers, footers, notes and text boxes, and in print and PDF, stay inline). Missing: drag-and-drop moves, insert/delete balloons, Translate, Block Authors, modern comment threads/mentions. Lowered: these gaps are confirmed by source (no `rPrChange` anywhere). |
| Proofing (spelling, grammar, thesaurus, languages) | 5 | 44% | (in Review) | 20–40 | English, Polish, Portuguese, Italian and Czech (system Hunspell, #1048), chosen per run from its language (`w:lang`): English word list (Moby plus our supplement of modern words such as "desktop", #520) and rule-based grammar; Polish against the installed Hunspell dictionary through a Hunspell-style affix engine, Polish grammar rules and TeX hyphenation patterns (2026-10-10); Portuguese (pt-BR) against the installed VERO Hunspell dictionary — the affix engine reads its `FLAG UTF-8` flags, continuation classes and forbidden spellings — with accent-first suggestions, conservative grammar rules and TeX patterns (2026-10-12); Italian (it-IT) against the installed Hunspell dictionary through the same affix engine, with accent-first suggestions and conservative grammar rules. Other languages are left unmarked. The custom dictionary persists across sessions (#40). Word ships 120 proofing tools for ~50 languages plus Editor (style refinements, similarity). Issues #25, #40, #100. |
| Mailings | 2 | 70% | 70% | 6–10 | Mail merge from CSV, fields, rules (IF, SKIPIF, NEXT, NEXTIF, evaluated alike in preview and merge), fields in headers/footers (one section per letter), preview, envelopes, labels. Select Recipients takes a typed list or CSV (#247); no Excel/Outlook/Contacts data sources, no email merge. |
@@ -251,6 +251,7 @@ can be used without committing Word output.
| Date | Change | Summary |
|---|---|---|
| 2026-10-18 | trivial | References: Insert Table of Figures dialog; a table of figures lists any caption label (`TOC \c`), without label and number (`\a`), without page numbers (`\n`) or with them after the text (`\p`); Word's switch order is recognised and the leader kept on update (#3013); no percentage change |
| 2026-10-15 | trivial | Pictures, shapes, text boxes: Insert › Shapes adds the new shape beside the selection, as Word draws it, so a second shape no longer replaces the first (still selected after its insert) and selected text stays; a picture still replaces a selected picture, as in Word (#2019) |
| 2026-10-15 | trivial | Pictures, shapes, text boxes: Picture Styles, Picture Border and Artistic Effects draw again from the picture under them, so a second style replaces the first instead of framing the frame (Thick Frame then Simple Frame is a simple frame), a border replaces the border and keeps the style, a style replaces the border, and an effect replaces the effect (None removes it), as in Word; transparency was already absolute (#2052) |
| 2026-10-18 | trivial | References row: Manage Sources imports Word's `Sources.xml` bibliography file (#2640); no percentage change |
+3 -2
View File
@@ -1,6 +1,6 @@
# UI parity with Microsoft Word
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** trivial (clearing a no-match font query keeps the recent heading visible, #2068; previously trivial: dragging the vertical scrollbar names the page at the top of the window, #2551; previously trivial: Add Font imports every selected font file, #1789); previously trivial (File › Close keeps the window, #1712; previously trivial: the Spike: Ctrl+F3 and Ctrl+Shift+F3; previously trivial: Ctrl+wheel zoom no longer scrolls the document, #1743; previously trivial: a mouse drag on a table leaves the view where it is, #1738; previously trivial: numbered headings, #1793; previously trivial: Navigation Find exposes Results, #2096; previously trivial: the tab strip never draws tabs over Comments / Editing / Share, #2026; previously trivial: Columns on selected text, #2024; previously trivial: Different First Page headers reachable from page 1, #2826; previously trivial: a copied equation also goes in the clipboard's MathML formats, #1297; previously trivial: Read Mode keeps the keyboard, shows whole columns and keeps text clear of the page-turn bar, #1772); previously trivial: font list entries of tall-ascent fonts sit in their row, not cut off, #2570); previously trivial: Split Cells and Convert Text to Table dialogs follow Word's merged-cell limits and Initial column width, #2592; previously trivial: a new source's Tag is made like Word's, three letters of the author and two digits of the year with a number added when taken, and an edit keeps the Tag, #2094; previously trivial: Web Layout text fills the window width, #1876; previously trivial: Language dialog keeps inherited proofing language and no-proof choices (#2717)); previously trivial: comment balloons readable in the dark theme, #2045; previously trivial: Recently Used Fonts section at the top of the font list, #2068; previously trivial: web preferences kept between visits, #2132; previously trivial: dialog close control: a Windows-style box at the title bar's right end, the whole box clickable, red on hover, #1885); previously trivial (Modify Table Style formats every table style part, #714); previously trivial: a click on the ribbon chrome hands the keyboard to the page in the same frame, #1093 follow-up; previously trivial: Page Setup vertical alignment, #1320; previously trivial: the app opens on the Home tab every launch, as in Word; previously trivial: no document behind the Start screen, #1129; previously trivial: equation pictures share a few textures, #1002; previously trivial: selection bar in the left margin, #856; previously trivial: table column and row markers on the rulers, #700; previously trivial: table borders and the resize handle drag on the page, #217; previously major: first version; ribbon, dialogs, shortcuts and on-canvas interaction measured from `crates/ui-egui` and the command registry) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** trivial (Insert Table of Figures dialog: any caption label, #3013; previously trivial: clearing a no-match font query keeps the recent heading visible, #2068; previously trivial: dragging the vertical scrollbar names the page at the top of the window, #2551; previously trivial: Add Font imports every selected font file, #1789); previously trivial (File › Close keeps the window, #1712; previously trivial: the Spike: Ctrl+F3 and Ctrl+Shift+F3; previously trivial: Ctrl+wheel zoom no longer scrolls the document, #1743; previously trivial: a mouse drag on a table leaves the view where it is, #1738; previously trivial: numbered headings, #1793; previously trivial: Navigation Find exposes Results, #2096; previously trivial: the tab strip never draws tabs over Comments / Editing / Share, #2026; previously trivial: Columns on selected text, #2024; previously trivial: Different First Page headers reachable from page 1, #2826; previously trivial: a copied equation also goes in the clipboard's MathML formats, #1297; previously trivial: Read Mode keeps the keyboard, shows whole columns and keeps text clear of the page-turn bar, #1772); previously trivial: font list entries of tall-ascent fonts sit in their row, not cut off, #2570); previously trivial: Split Cells and Convert Text to Table dialogs follow Word's merged-cell limits and Initial column width, #2592; previously trivial: a new source's Tag is made like Word's, three letters of the author and two digits of the year with a number added when taken, and an edit keeps the Tag, #2094; previously trivial: Web Layout text fills the window width, #1876; previously trivial: Language dialog keeps inherited proofing language and no-proof choices (#2717)); previously trivial: comment balloons readable in the dark theme, #2045; previously trivial: Recently Used Fonts section at the top of the font list, #2068; previously trivial: web preferences kept between visits, #2132; previously trivial: dialog close control: a Windows-style box at the title bar's right end, the whole box clickable, red on hover, #1885); previously trivial (Modify Table Style formats every table style part, #714); previously trivial: a click on the ribbon chrome hands the keyboard to the page in the same frame, #1093 follow-up; previously trivial: Page Setup vertical alignment, #1320; previously trivial: the app opens on the Home tab every launch, as in Word; previously trivial: no document behind the Start screen, #1129; previously trivial: equation pictures share a few textures, #1002; previously trivial: selection bar in the left margin, #856; previously trivial: table column and row markers on the rulers, #700; previously trivial: table borders and the resize handle drag on the page, #217; previously major: first version; ribbon, dialogs, shortcuts and on-canvas interaction measured from `crates/ui-egui` and the command registry) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
How WordCraft looks and feels next to Word: ribbon, dialogs, panes, keyboard, mouse and touch.
Typing behaviour has its own checklist ([`typing-parity.md`](typing-parity.md)). All assets are our
@@ -29,7 +29,7 @@ Document title opens Rename Document; the account badge opens Options › Genera
| Modal dialogs | **56** (`Dialog` enum: Discard Recovery, Font, Enclose Characters, Paragraph, Tabs, Borders and Shading, Find, Go To, Insert Table, Table Properties, Page Setup, Link, Bookmark, Word Count, Zoom, Watermark, New Style, Modify Style, Manage Styles, Table Style, Commands, Icons, Templates and Add-ins, Organizer, Compare and Combine, AutoCorrect Options, Paste Special, About, Save Changes, Password, Encrypt with Password, Define New Multilevel List, Track Changes Options; Columns, Symbol with Special Characters, Field (#321); Field code (#339); mail merge Recipients (Filter and Sort, #778; Find in list and a merged-only view, #1576), Insert Merge Field, Find Recipient, Rules, Match Fields, Check for Errors; Caption with New Label and Numbering, Index, Mark Index Entry, Custom Table of Contents with Options and TOC styles, Source Manager, Create/Edit Source (#402); Cross-reference (#916); Line Numbers, Hyphenation with Manual Hyphenation, Language, Envelopes, Labels (#407); Page Setup has a Paper size list and custom width and height, opened from Layout › Size › More Paper Sizes… (#1433)) plus panes and menus for the rest | ~100 (Options panes…) | ~45% | 11–19 |
| Task panes (Navigation, Styles, Comments, Reviewing, Format Picture/Shape, Thesaurus, Accessibility, Clipboard, Selection) | Navigation (long headings truncate with an ellipsis instead of expanding the pane, #759), Styles, Comments, Thesaurus, Restrict Editing, Accessibility (grouped findings, jump-to, fixes) and Alt Text (#386), Read Aloud, Zotero; Style Inspector (#236); Clipboard pane (#264) open | all | 70% | 4–6 |
| Keyboard shortcuts | **152** commands bound in `CommandSpec::key`, 172 key combinations (counted from the registry on Linux, 2026-10-13); styles, spacing, symbols, fields, views, mail merge (#330); field keys Alt+F9, Shift+F9, Ctrl+Shift+F9, Ctrl+F11, Ctrl+Shift+F11, Shift+F11 (#339), Next Field on Alt+F1 and Alt+Shift+F1 (Word's other keys for it) since F11 went to full screen (#980; GNOME and KDE may keep Alt+F1 for their own overview or application menu, and then Next Field has no key there; Previous Field keeps Shift+F11); Alt+Shift+arrows (move paragraphs, promote/demote), Shift+F5 Go Back and Alt+X (#343); Outline view: Alt+Shift+Plus/Minus expand/collapse, Alt+Shift+1–9 and Alt+Shift+A Show Level (#493); Alt+Q to "Tell me" command search (#819, not on macOS); on macOS, Word for Mac's own keys: ⌘⇧S Save As, ⌘⌥E Insert Endnote, ⌘⌥A New Comment, ⌃H Replace, ⌘⌥R collapse the ribbon. The Spike: Ctrl+F3 cuts to it, Ctrl+Shift+F3 inserts it and empties it (⌘F3, ⌘⇧F3 on macOS). Missing: en dash on the numeric keypad Insert Hyperlink has Word's Link to: Existing File or Web Page / Place in This Document (top of the document, headings, bookmarks) (#1234). | ~250 default shortcuts | ~70% | 2–4 |
| Modal dialogs | **60** (`Dialog` enum: Discard Recovery, Font, Enclose Characters, Paragraph, Tabs, Borders and Shading, Find, Go To, Insert Table, Table Properties, Page Setup, Link, Bookmark, Word Count, Zoom, Watermark, New Style, Modify Style, Manage Styles, Table Style, Commands, Icons, Templates and Add-ins, Organizer, Compare and Combine, AutoCorrect Options, Paste Special, About, Save Changes, Password, Encrypt with Password, Define New Multilevel List, Track Changes Options; Columns, Symbol with Special Characters, Field (#321); Field code (#339); mail merge Recipients, Insert Merge Field, Find Recipient, Rules, Match Fields, Check for Errors; Caption with New Label and Numbering, Index, Mark Index Entry, Custom Table of Contents with Options and TOC styles, Source Manager, Create/Edit Source (#402); Cross-reference (#916); Drop Cap with Position, Font, Lines to drop and Distance from text, opened from Insert › Drop Cap › Drop Cap Options… (#2601); Line Numbers, Hyphenation with Manual Hyphenation, Language, Envelopes, Labels (#407); Page Setup has a Paper size list and custom width and height, opened from Layout › Size › More Paper Sizes… (#1433); every dialog's close control is a box at the title bar's right end, the full title-bar height and 1.6× as wide, all of it clickable, red with a white ✕ on hover as a Windows caption button (#1885); Split Cells (columns, rows, merge before split; defaults follow the selection as in Word: twice its columns, its rows, Merge offered for several cells or one merged across columns; a merged cell splits into 1 to its height rows, a number that doesn't divide its height refused with a divisor message, more than its height with the range message; 1 × 1 changes nothing; parts split across columns are equal and other rows keep their grid lines) and Convert Text to Table (live column count, read-only rows; Initial column width Auto or 0 to 22 in, AutoFit to contents/window; Paragraphs/Commas/Tabs/Other "-" separators) (#2592); Size, from Picture Format › Size's launcher: height and width in the interface unit (a size typed in any unit, `300 px` too), scale of the original or the opening size, Lock aspect ratio, the original size with its pixels and Reset (#2573)) plus panes and menus for the rest | ~100 (Options panes…) | ~45% | 11–19 |
| Modal dialogs | **60** (`Dialog` enum: Discard Recovery, Font, Enclose Characters, Paragraph, Tabs, Borders and Shading, Find, Go To, Insert Table, Table Properties, Page Setup, Link, Bookmark, Word Count, Zoom, Watermark, New Style, Modify Style, Manage Styles, Table Style, Commands, Icons, Templates and Add-ins, Organizer, Compare and Combine, AutoCorrect Options, Paste Special, About, Save Changes, Password, Encrypt with Password, Define New Multilevel List, Track Changes Options; Columns, Symbol with Special Characters, Field (#321); Field code (#339); mail merge Recipients, Insert Merge Field, Find Recipient, Rules, Match Fields, Check for Errors; Caption with New Label and Numbering, Index, Mark Index Entry, Custom Table of Contents with Options and TOC styles, Table of Figures (#3013), Source Manager, Create/Edit Source (#402); Cross-reference (#916); Drop Cap with Position, Font, Lines to drop and Distance from text, opened from Insert › Drop Cap › Drop Cap Options… (#2601); Line Numbers, Hyphenation with Manual Hyphenation, Language, Envelopes, Labels (#407); Page Setup has a Paper size list and custom width and height, opened from Layout › Size › More Paper Sizes… (#1433); every dialog's close control is a box at the title bar's right end, the full title-bar height and 1.6× as wide, all of it clickable, red with a white ✕ on hover as a Windows caption button (#1885); Split Cells (columns, rows, merge before split; defaults follow the selection as in Word: twice its columns, its rows, Merge offered for several cells or one merged across columns; a merged cell splits into 1 to its height rows, a number that doesn't divide its height refused with a divisor message, more than its height with the range message; 1 × 1 changes nothing; parts split across columns are equal and other rows keep their grid lines) and Convert Text to Table (live column count, read-only rows; Initial column width Auto or 0 to 22 in, AutoFit to contents/window; Paragraphs/Commas/Tabs/Other "-" separators) (#2592); Size, from Picture Format › Size's launcher: height and width in the interface unit (a size typed in any unit, `300 px` too), scale of the original or the opening size, Lock aspect ratio, the original size with its pixels and Reset (#2573)) plus panes and menus for the rest | ~100 (Options panes…) | ~45% | 11–19 |
| Task panes (Navigation, Styles, Comments, Reviewing, Format Picture/Shape, Thesaurus, Accessibility, Clipboard, Selection) | Navigation (long headings truncate with an ellipsis instead of expanding the pane, #759), Styles, Comments (balloons and cards read in both themes, #2045), Thesaurus, Restrict Editing, Accessibility (grouped findings, jump-to, fixes) and Alt Text (#386), Read Aloud, Zotero; Style Inspector (#236); Clipboard pane (#264) open | all | 70% | 4–6 |
| Context menus (right-click) | text, spelling suggestions, fields (Update Field, Edit Field…, Toggle Field Codes, #339; Update Field on a table of contents entry updates the table, #1197, and after a save and reopen, #2182); right-click selects a picture, shape or text box and offers Insert Caption… (#1230) and Wrap Text (styles, Move with Text / Fix Position on Page, #513); tables, the items following the selection as in Word (Insert, Delete › Cells, Columns, Rows or Table whatever is selected (#1198), Merge and Split Cells, Distribute, AutoFit, Text Direction, Insert Caption…, Table Properties…; no Border Styles yet) | rich per context | 65% | 2–4 |
| Task panes (Navigation, Styles, Comments, Reviewing, Format Picture/Shape, Thesaurus, Accessibility, Clipboard, Selection) | Navigation (long headings truncate with an ellipsis instead of expanding the pane, #759), Styles, Comments (typed comment and reply text stays with its document, is written before Save, Export, Print, Close, New or Open, Escape discards it, and the fields are read-only where comments can't change, #1348), Thesaurus, Restrict Editing, Accessibility (grouped findings, jump-to, fixes) and Alt Text (#386), Read Aloud, Zotero; Style Inspector (#236); Clipboard pane (#264) open | all | 70% | 4–6 |
@@ -74,6 +74,7 @@ Footnote and endnote editing keeps body and note text at normal opacity, on ligh
| Date | Change | Summary |
|---|---|---|
| 2026-10-18 | trivial | Insert Table of Figures opens a Table of Figures dialog: caption label (Figure, Table, Equation, the user's own and those the document's captions use), Include label and number, page numbers, right margin and tab leader, with a preview; the ribbon button, KeyTip and command search reach it (#3013) |
| 2026-10-18 | trivial | Font list starts at its first row after clearing a no-match query; recent heading remains visible (#2068) |
| 2026-10-16 | trivial | Dragging the vertical scrollbar shows a tip beside the pointer with the page at the top of the window ("Page 7 of 10"); nothing said where the drag would land (#2551) |
| 2026-10-17 | trivial | Recipient List dialog: Find in list box with a match count, "Show only records that will be merged", striped rows; hidden rows keep their record for edit and remove (#1576) |