Right-click on a picture or shape: Wrap Text submenu (#513) (#521)

Right-clicking a picture, shape, text box or chart selects it (as a
click would) and its context menu offers Wrap Text: In Line with Text,
Square, Tight, Through, Top and Bottom, Behind Text, In Front of Text
(current one checked), then Move with Text / Fix Position on Page.

The latter two are a new engine command, arrange.moveWithText, which
re-anchors a floating object's vertical position to its paragraph or
the page without moving it. The menu items live in one shared function
(ribbon::wrap_text_items, WRAP_STYLES) for the ribbon to reuse.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
Valentin Klein 2026-10-12 01:11:31 +02:00 committed by GitHub
parent b0431eadc2
commit 88a45329b2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 203 additions and 10 deletions

View File

@ -131,6 +131,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm
| Date | What landed |
|---|---|
| 2026-10-11 | Right-click Wrap Text submenu on pictures, shapes and text boxes: wrapping styles, Move with Text / Fix Position on Page (#513) |
| 2026-10-11 | Paragraph dialog options (#512): mirror indents (`w:mirrorIndents`, laid out on even pages), same-style spacing, suppress line numbers, don't hyphenate |
| 2026-10-11 | Screen readers can read the document canvas (#488): paragraphs, headings, lists, tables, links, pictures and the caret exposed through AccessKit |
| 2026-10-11 | Compare / Combine dialog with comparison settings, Restrict Editing pane (exceptions, SHA-512 password, locked styles; DOCX round trip), AutoCorrect Options dialog saved per user (#412) |

View File

@ -265,6 +265,9 @@ pub fn specs() -> Vec<CommandSpec> {
})
.params(r#"{"side": "bothSides|left|right|largest"} (the sides of the selected Square, Tight or Through object text flows on; largest: the wider side, line by line)"#)
.when(has_movable),
CommandSpec::new("arrange.moveWithText", "Move with Text", "Layout › Arrange › Wrap Text", |s, v| move_with_text(s, p::bool(v, "value").unwrap_or(true)))
.params(r#"{"value": bool} (true: Move with Text, the floating object's vertical position follows its paragraph; false: Fix Position on Page. It stays where it is)"#)
.when(has_floating),
CommandSpec::new("arrange.position", "Position", "Layout › Arrange", |s, v| {
let preset = p::str(v, "preset").unwrap_or("middleCenter").to_string();
let (x, y) = (p::f32(v, "x"), p::f32(v, "y"));
@ -766,6 +769,34 @@ fn unalign(s: &mut Session) -> Result<(), CmdError> {
Ok(())
}
/// Whether a floating object moves with its text (Word's "Move with Text"): its vertical position
/// follows its paragraph (as layout places any anchor but the page and its margins), rather than
/// being fixed on the page ("Fix Position on Page").
pub fn moves_with_text(f: &Float) -> bool {
use wordcraft_doc::para::Anchor;
!matches!(f.v_rel, Anchor::Page | Anchor::Margin | Anchor::TopMargin | Anchor::BottomMargin | Anchor::InsideMargin | Anchor::OutsideMargin)
}
/// Arrange › Wrap Text › Move with Text / Fix Position on Page: anchor the selected floating
/// object's vertical position to its paragraph or to the page, keeping it where it is now.
fn move_with_text(s: &mut Session, with_text: bool) -> CmdResult {
use wordcraft_doc::para::Anchor;
let (pos, obj) = selected(s).ok_or_else(|| CmdError::Disabled("no picture or shape selected".into()))?;
let Some((_, _, float)) = obj.frame() else { return Err(CmdError::Disabled("select a floating picture or shape first".into())) };
if moves_with_text(float) == with_text {
return Ok(json!({"object": serde_json::to_value(&obj).unwrap_or(Value::Null)}));
}
let hit = s.layout().object(&pos, s.page_hint).ok_or_else(|| CmdError::Failed("the object isn't laid out".into()))?;
// Paragraph offsets start at the paragraph's top (the hit's origin), page offsets at the page's.
let y = if with_text { hit.rect.y - hit.origin.y } else { hit.rect.y };
let o = edit_float(s, &pos, |f| {
f.v_rel = if with_text { Anchor::Paragraph } else { Anchor::Page };
f.v_align = None;
f.y = y.clamp(-MAX_OFFSET, MAX_OFFSET);
})?;
Ok(json!({"object": serde_json::to_value(o).unwrap_or(Value::Null)}))
}
/// The top-level body paragraph under `y` on `page`: the last one starting at or above it (else
/// the first starting on the page).
pub(crate) fn anchor_paragraph(layout: &wordcraft_layout::DocLayout, page: usize, y: f32) -> Option<wordcraft_doc::Path> {
@ -1409,6 +1440,35 @@ mod tests {
assert!(s.run("picture.crop", &json!({"left": 0.1})).is_err());
}
/// #513: Wrap Text › Fix Position on Page / Move with Text re-anchor a floating picture
/// vertically without moving it; one undo step each; an inline picture can't.
#[test]
fn move_with_text_and_fix_position_keep_the_picture_in_place() {
use wordcraft_doc::para::Anchor;
let mut s = Session::new(wordcraft_doc::Document::new());
s.run("text.insert", &json!({"text": "First paragraph\nSecond"})).unwrap();
let data = super::super::insert::base64_encode(&png(20, 10));
s.run("insert.picture", &json!({"data": data, "width": 100})).unwrap();
assert!(s.run("arrange.moveWithText", &json!({"value": false})).is_err(), "inline: no anchoring");
s.run("arrange.wrap", &json!({"wrap": "square"})).unwrap();
let v_rel = |s: &Session| selected(s).unwrap().1.frame().unwrap().2.v_rel;
let with_text = |s: &Session| moves_with_text(selected(s).unwrap().1.frame().unwrap().2);
let rect = |s: &mut Session| {
let pos = selected(s).unwrap().0;
s.layout().object(&pos, 0).unwrap().rect
};
let before = rect(&mut s);
s.run("arrange.moveWithText", &json!({"value": false})).unwrap();
assert_eq!(v_rel(&s), Anchor::Page);
assert!(!with_text(&s));
assert!((rect(&mut s).y - before.y).abs() < 0.01, "it stays put");
s.run("arrange.moveWithText", &json!({"value": true})).unwrap();
assert!(with_text(&s));
assert!((rect(&mut s).y - before.y).abs() < 0.01, "it stays put");
s.run("edit.undo", &json!({})).unwrap();
assert_eq!(v_rel(&s), Anchor::Page);
}
/// #332: Rotate and Flip turn the frame (an angle and mirroring), never the pixels; an exact
/// angle is normalised; each is one undo step; the rotated frame takes its rotated room.
#[test]

View File

@ -728,18 +728,23 @@ pub fn show(app: &mut WordApp, ui: &mut Ui) {
None => {}
}
}
// Right-click on a picture, shape or text box selects it (its menu offers Wrap Text).
if resp.secondary_clicked()
&& app.session.math.is_none()
&& let Some(p) = resp.interact_pointer_pos()
&& let Some((page, x, y)) = page_at(&rects, &layout, geo.scale, p)
&& let Some(pos) = layout.hit(page, x, y, app.session.sel.focus.story)
{
let (a, b) = app.session.sel.ordered();
if !(a <= pos && pos <= b) || app.session.sel.is_collapsed() {
app.session.sel = wordcraft_engine::Selection::caret(pos);
if crate::objects::select_at(app, &layout, &rects, geo.scale, p) {
app.canvas.context_issue = None;
app.canvas.context_synonyms = None;
} else if let Some(pos) = layout.hit(page, x, y, app.session.sel.focus.story) {
let (a, b) = app.session.sel.ordered();
if !(a <= pos && pos <= b) || app.session.sel.is_collapsed() {
app.session.sel = wordcraft_engine::Selection::caret(pos);
}
app.canvas.context_issue = app.session.run("review.suggestions", &json!({})).ok().filter(|v| !v.is_null());
app.canvas.context_synonyms = app.session.run("review.thesaurus", &json!({})).ok().and_then(|v| v.get("synonyms").cloned());
}
app.canvas.context_issue = app.session.run("review.suggestions", &json!({})).ok().filter(|v| !v.is_null());
app.canvas.context_synonyms = app.session.run("review.thesaurus", &json!({})).ok().and_then(|v| v.get("synonyms").cloned());
}
resp.context_menu(|ui| context_menu(app, ui));
// Stand down while the context menu owns the pointer so the two never double up;
@ -1665,16 +1670,24 @@ fn context_menu(app: &mut WordApp, ui: &mut Ui) {
item(ui, app, tl!("Edit Field…"), "fields.setCode", json!({}));
item(ui, app, tl!("Toggle Field Codes"), "fields.toggleCode", json!({}));
}
let object = wordcraft_engine::cmd::objects::selected(&app.session).is_some();
if object || app.session.sel.focus.path.cell().is_some() {
let selected_object = wordcraft_engine::cmd::objects::selected(&app.session).is_some();
if selected_object || app.session.sel.focus.path.cell().is_some() {
item(ui, app, "View Alt Text…", "view.altTextPane", json!({"value": true}));
}
if crate::ribbon::has_picture_selected(&app.session) {
// A selected picture, shape, text box or chart: its own items, then Wrap Text (#513).
let picture = crate::ribbon::has_picture_selected(&app.session);
let object = wordcraft_engine::cmd::objects::object_selection(&app.session).is_some();
if picture || object {
ui.separator();
}
if picture {
item(ui, app, "Change Picture…", "ui.changePicture", json!({}));
item(ui, app, "Reset Picture", "picture.reset", json!({}));
item(ui, app, "Size and Crop…", "ui.tab", json!({"tab": "Picture Format"}));
}
if object {
ui.menu_button(tl!("Wrap Text"), |ui| crate::ribbon::wrap_text_items(ui, app));
}
if app.session.sel.focus.path.cell().is_some() {
ui.separator();
ui.menu_button(tl!("Insert"), |ui| {
@ -1821,6 +1834,53 @@ mod tests {
assert_eq!(h.state().session.doc.plain_text(StoryRef::Part(part)), "Original");
}
/// #513: right-clicking a picture selects it and offers Wrap Text, as in Word; choosing
/// Square there wraps text around it.
#[test]
fn right_click_on_a_picture_offers_wrap_text() {
use egui_kittest::kittest::Queryable;
let mut a = WordApp::new(wordcraft_engine::Session::new(wordcraft_doc::Document::new()), Default::default());
a.run("text.insert", json!({"text": "Text before the picture "})).unwrap();
let mut png = Vec::new();
let img = image::RgbaImage::from_pixel(40, 20, image::Rgba([200, 30, 30, 255]));
image::DynamicImage::ImageRgba8(img).write_to(&mut std::io::Cursor::new(&mut png), image::ImageFormat::Png).unwrap();
a.run("insert.picture", json!({"data": wordcraft_engine::cmd::insert::base64_encode(&png), "width": 160})).unwrap();
let pos = wordcraft_engine::cmd::objects::selected(&a.session).unwrap().0;
a.session.sel = wordcraft_engine::Selection::caret(Pos::body(0, 0));
let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(1440.0, 900.0)).build_ui_state(
|ui, app: &mut WordApp| {
let ctx = ui.ctx().clone();
app.logic(&ctx);
app.ui(ui);
},
a,
);
for _ in 0..6 {
h.step();
}
let rect = h.state_mut().session.layout().object(&pos, 0).expect("the picture is laid out").rect;
let at = crate::frame::to_screen(h.state().canvas.page_rects[0], h.state().canvas.scale, rect).center();
h.event(egui::Event::PointerMoved(at));
for pressed in [true, false] {
h.event(egui::Event::PointerButton { pos: at, button: egui::PointerButton::Secondary, pressed, modifiers: Default::default() });
h.step();
}
h.step();
let picked = wordcraft_engine::cmd::objects::object_selection(&h.state().session).map(|(p, _)| p);
assert_eq!(picked, Some(pos.clone()), "the right-click selected the picture");
h.get_by_label_contains("Wrap Text").hover();
for _ in 0..3 {
h.step();
}
assert!(h.query_by_label_contains("Fix Position on Page").is_some(), "the anchoring choices are offered");
h.get_by_label_contains("Square").click();
for _ in 0..3 {
h.step();
}
let o = wordcraft_engine::cmd::objects::selected(&h.state().session).unwrap().1;
assert_eq!(o.frame().unwrap().2.wrap, wordcraft_doc::para::Wrap::Square);
}
#[test]
fn only_web_and_email_links_are_followed() {
for ok in ["https://example.com/a?b#c", "http://example.com", "HTTPS://EXAMPLE.COM", "mailto:someone@example.com"] {

View File

@ -1734,6 +1734,8 @@
Rewind Rebobinar
Forward Avanzar
Stroke {n} of {total} Trazo {n} de {total}
Move with Text Mover con el texto
Fix Position on Page Fijar posición en la página
# WordArt and connectors (#416).
WordArt Styles Estilos de WordArt

Can't render this file because it contains an unexpected character in line 6 and column 25.

View File

@ -1697,6 +1697,8 @@
Rewind Keri algusesse
Forward Edasi
Stroke {n} of {total} Joon {n}/{total}
Move with Text Liigu tekstiga
Fix Position on Page Fikseeri asukoht lehel
# WordArt and connectors (#416).
WordArt Styles WordArti laadid

Can't render this file because it has a wrong number of fields in line 12.

View File

@ -1718,6 +1718,8 @@
Rewind 巻き戻し
Forward 早送り
Stroke {n} of {total} ストローク {n}/{total}
Move with Text 文字列と一緒に移動する
Fix Position on Page ページ上で位置を固定する
# WordArt and connectors (#416).
WordArt Styles ワードアートのスタイル

Can't render this file because it has a wrong number of fields in line 3.

View File

@ -1730,6 +1730,8 @@
Rewind Retroceder
Forward Avançar
Stroke {n} of {total} Traço {n} de {total}
Move with Text Mover com o texto
Fix Position on Page Fixar posição na página
# WordArt and connectors (#416).
WordArt Styles Estilos de WordArt

Can't render this file because it contains an unexpected character in line 6 and column 25.

View File

@ -2100,6 +2100,9 @@ preview Title Пример
Apply Применить
Convert Notes Преобразовать сноски
# Wrap Text context menu (#513).
Move with Text Перемещать вместе с текстом
Fix Position on Page Зафиксировать положение на странице
# Paragraph dialog options (#512).
Formatting exceptions Исключения форматирования
Inside: Внутри:

Can't render this file because it contains an unexpected character in line 7 and column 25.

View File

@ -1728,6 +1728,8 @@
Rewind Na početak
Forward Napred
Stroke {n} of {total} Potez {n} od {total}
Move with Text Pomeraj sa tekstom
Fix Position on Page Fiksiraj položaj na stranici
# WordArt and connectors (#416).
WordArt Styles WordArt stilovi

Can't render this file because it contains an unexpected character in line 6 and column 25.

View File

@ -1732,6 +1732,8 @@
Rewind На почетак
Forward Напред
Stroke {n} of {total} Потез {n} од {total}
Move with Text Померај са текстом
Fix Position on Page Фиксирај положај на страници
# WordArt and connectors (#416).
WordArt Styles WordArt стилови

Can't render this file because it contains an unexpected character in line 11 and column 25.

View File

@ -1722,6 +1722,9 @@
Apply Uygula
Convert Notes Notları Dönüştür
# Wrap Text context menu (#513).
Move with Text Metinle birlikte taşı
Fix Position on Page Sayfadaki konumu sabitle
# Paragraph dialog options (#512).
Formatting exceptions Biçimlendirme özel durumları
Inside: İç:

Can't render this file because it has a wrong number of fields in line 18.

View File

@ -1730,6 +1730,8 @@
Rewind На початок
Forward Вперед
Stroke {n} of {total} Штрих {n} з {total}
Move with Text Переміщувати з текстом
Fix Position on Page Зафіксувати положення на сторінці
# WordArt and connectors (#416).
WordArt Styles Стилі WordArt

Can't render this file because it contains an unexpected character in line 6 and column 31.

View File

@ -1730,6 +1730,8 @@
Rewind 倒回
Forward 前进
Stroke {n} of {total} 第 {n} 笔,共 {total} 笔
Move with Text 随文字移动
Fix Position on Page 在页面上的位置固定
# WordArt and connectors (#416).
WordArt Styles 艺术字样式

Can't render this file because it contains an unexpected character in line 7 and column 25.

View File

@ -1719,6 +1719,8 @@
Rewind 倒轉
Forward 快轉
Stroke {n} of {total} 第 {n} 筆,共 {total} 筆
Move with Text 隨文字移動
Fix Position on Page 固定於頁面上的位置
# WordArt and connectors (#416).
WordArt Styles 文字藝術師樣式

Can't render this file because it has a wrong number of fields in line 4.

View File

@ -205,6 +205,22 @@ fn link_click(app: &mut WordApp, layout: &DocLayout, pages: &[Rect], scale: f32,
}
}
/// Right-click at `at`: the picture, shape or text box under it becomes the selection (as a click
/// would, without a drag), so the context menu acts on it. Returns whether there was one.
pub fn select_at(app: &mut WordApp, layout: &DocLayout, pages: &[Rect], scale: f32, at: Pos2) -> bool {
if crate::canvas::editing_header_footer(app, layout) {
return false;
}
let Some((object, _)) = grab_at(app, layout, pages, scale, at) else { return false };
let pos = object.pos();
// Already selected (alone or among several picked for Group): keep the selection.
if !wordcraft_engine::cmd::objects::picked_objects(&app.session).contains(&pos) {
let end = Pos { off: pos.off + OBJ.len_utf8(), ..pos.clone() };
let _ = app.run("select.range", json!({"anchor": pos, "focus": end}));
}
true
}
/// What a press at `at` would grab: a handle of the shown frame, else an object.
fn grab_at(app: &WordApp, layout: &DocLayout, pages: &[Rect], scale: f32, at: Pos2) -> Option<(ObjectHit, Grab)> {
let handle = active(app, layout).filter(|(o, _)| o.story == StoryRef::Body && !fixed(app, o)).and_then(|(o, _)| {

View File

@ -327,6 +327,37 @@ fn mi_check(ui: &mut Ui, app: &mut WordApp, label: &str, checked: bool, id: &str
}
}
/// Word's text wrapping styles in menu order: label, model value, `arrange.wrap` parameter.
pub(crate) const WRAP_STYLES: [(&str, wordcraft_doc::para::Wrap, &str); 7] = {
use wordcraft_doc::para::Wrap;
[
("In Line with Text", Wrap::Inline, "inline"),
("Square", Wrap::Square, "square"),
("Tight", Wrap::Tight, "tight"),
("Through", Wrap::Through, "through"),
("Top and Bottom", Wrap::TopAndBottom, "topAndBottom"),
("Behind Text", Wrap::BehindText, "behindText"),
("In Front of Text", Wrap::InFrontOfText, "inFrontOfText"),
]
};
/// The Wrap Text menu for the selected picture, shape or text box: the seven styles (the
/// current one checked), then Move with Text / Fix Position on Page for a floating one. The
/// canvas context menu shows it; the ribbon's Wrap Text button can share it.
pub(crate) fn wrap_text_items(ui: &mut Ui, app: &mut WordApp) {
use wordcraft_engine::cmd::objects::{moves_with_text, selected};
let state = selected(&app.session).and_then(|(_, o)| o.frame().map(|(_, _, f)| (f.wrap, moves_with_text(f))));
let wrap = state.map(|(w, _)| w);
for (label, w, v) in WRAP_STYLES {
mi_check(ui, app, label, wrap == Some(w), "arrange.wrap", json!({"wrap": v}));
}
ui.separator();
// An inline object is neither: it's part of the text.
let with_text = state.filter(|(w, _)| *w != wordcraft_doc::para::Wrap::Inline).map(|(_, m)| m);
mi_check(ui, app, "Move with Text", with_text == Some(true), "arrange.moveWithText", json!({"value": true}));
mi_check(ui, app, "Fix Position on Page", with_text == Some(false), "arrange.moveWithText", json!({"value": false}));
}
fn home(app: &mut WordApp, ui: &mut Ui) {
let st = app.session.run("format.state", &json!({})).unwrap_or_default();
let flag = |k: &str| st.get(k).and_then(Value::as_bool).unwrap_or(false);

View File

@ -17,8 +17,8 @@ own (`AGENTS.md`: no Microsoft iconography, ever).
| Customize Ribbon / Customize Keyboard | Customize Keyboard (#368): categories, a command's keys, press a combination to see which command uses it, Assign / Remove / Reset All; custom keys override built-ins, removed built-ins do nothing, saved per user; `tools.customizeKeyboard` for agents. Customize Ribbon (#379): show, hide and reorder tabs, custom tabs and custom groups (on any tab) holding any commands, rename, remove, Reset; right-click on the ribbon; saved per user; `tools.customizeRibbon` for agents. Built-in tabs can't be renamed; no import/export file | ✅ | 85% | 1–2 |
| Keyboard shortcuts | **125** commands bound in `CommandSpec::key`, 144 key combinations (measured on Linux/Windows); styles, spacing, symbols, fields, views, mail merge (#330); field keys Alt+F9, Shift+F9, Ctrl+Shift+F9, Ctrl+F11, Ctrl+Shift+F11, F11, Shift+F11 (#339); Alt+Shift+arrows (move paragraphs, promote/demote), Shift+F5 Go Back and Alt+X (#343). Missing: en dash on the numeric keypad | ~250 default shortcuts | ~70% | 2–4 |
| Modal dialogs | **52** (`Dialog` enum: 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, 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); Line Numbers, Hyphenation with Manual Hyphenation, Language, Envelopes, Labels (#407)) plus panes and menus for the rest | ~100 (Options panes…) | ~45% | 11–19 |
| Context menus (right-click) | text, spelling suggestions, fields (Update Field, Edit Field…, Toggle Field Codes, #339); right-click selects a picture, shape or text box and offers Wrap Text (styles, Move with Text / Fix Position on Page, #513); tables partial | rich per context | 60% | 3–5 |
| Task panes (Navigation, Styles, Comments, Reviewing, Format Picture/Shape, Thesaurus, Accessibility, Clipboard, Selection) | Navigation, 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 |
| Context menus (right-click) | text, spelling suggestions, fields (Update Field, Edit Field…, Toggle Field Codes, #339); objects and tables partial | rich per context | 55% | 3–5 |
| Backstage (Home, New, Open, Info, Save As, Print, Share, Export, Options) | Home and New separate (#254), Info properties kept (#268) | | 68% | 4–6 |
| Rulers: indents, tabs, margins, table columns | indents draggable (one undo, #62); tabs; table column drag pending (#49) | ✅ | 70% | 2–3 |
| On-canvas objects: select, drag, resize handles, nudge | ✅ tested (#228) | ✅ | 75% | — |
@ -37,6 +37,7 @@ own (`AGENTS.md`: no Microsoft iconography, ever).
| Date | Change | Summary |
|---|---|---|
| 2026-10-11 | trivial | Right-click on a picture, shape or text box selects it and offers Wrap Text: the seven wrapping styles (current one checked), Move with Text and Fix Position on Page (`arrange.moveWithText`) (#513) |
| 2026-10-11 | trivial | Paragraph dialog: Mirror indents (Inside/Outside labels), Don't add space between paragraphs of the same style, Formatting exceptions (Suppress line numbers, Don't hyphenate) (#512) |
| 2026-10-11 | trivial | Screen readers: the document canvas exposes paragraphs, headings, lists, tables, links, pictures and the caret through AccessKit (#488); 25% → 50% |
| 2026-10-11 | trivial | Compare / Combine Documents (original and revised document, label changes with, comparison settings, word or character level, show changes in) and AutoCorrect Options (replacement list, capitalization, exceptions, AutoFormat As You Type, Math AutoCorrect) dialogs; Restrict Editing pane (formatting and editing restrictions, exceptions for everyone, password); modal dialog rows merged into one (#412) |