diff --git a/crates/engine/src/cmd/references.rs b/crates/engine/src/cmd/references.rs
--- a/crates/engine/src/cmd/references.rs
+++ b/crates/engine/src/cmd/references.rs
@@ -100,8 +100,9 @@ pub fn specs() -> Vec<CommandSpec> {
 
 /// A field instruction split into its name, positional arguments and switches (ECMA-376
 /// §17.16.1): `TOC \o "1-3" \h` → name `TOC`, switches `o` = `1-3` and `h`. Quoted text stays
-/// whole (without the quotes; `\"` and `\\` inside it are a quote and a backslash). A switch
-/// takes the word after it as its argument unless that word is another switch.
+/// whole (without the quotes; a backslash inside it makes the next character literal, as in Word:
+/// `\"` is a quote, `\\` a backslash, `\*` an asterisk). A switch takes the word after it as its
+/// argument unless that word is another switch.
 #[derive(Clone, Debug, Default, PartialEq)]
 pub struct FieldCode {
     pub name: String,
@@ -123,7 +124,7 @@ impl FieldCode {
                 while let Some(c) = chars.next() {
                     match c {
                         '"' => break,
-                        '\\' if matches!(chars.peek(), Some('"' | '\\')) => w.extend(chars.next()),
+                        '\\' if chars.peek().is_some() => w.extend(chars.next()),
                         c => w.push(c),
                     }
                 }
diff --git a/crates/engine/src/cmd/references/tests.rs b/crates/engine/src/cmd/references/tests.rs
--- a/crates/engine/src/cmd/references/tests.rs
+++ b/crates/engine/src/cmd/references/tests.rs
@@ -511,3 +511,16 @@ fn caption_for_an_object_in_a_cell_stays_in_the_cell() {
     let texts: Vec<String> = s.doc.body.iter().filter_map(|b| b.as_para()).map(|p| p.plain_text()).collect();
     assert_eq!(texts.get(1).map(String::as_str), Some("Figure 1"), "{texts:?}");
 }
+
+/// A backslash in a quoted field argument makes the next character literal, so `\*` there is an
+/// asterisk and not the `\* Upper` switch (Word shows `x * Upper` for `TITLE "x \* Upper"`).
+#[test]
+fn quoted_field_argument_backslash_escapes() {
+    for (instr, shown) in [(r#"TITLE "x \* Upper""#, "x * Upper"), (r#"TITLE "x \\* Upper""#, r"x \* Upper"), (r#"TITLE "a \"b\" c""#, r#"a "b" c"#)]
+    {
+        let value = new_property_value(instr).unwrap();
+        assert_eq!(value, shown, "{instr}");
+        assert_eq!(text_result(instr, value), shown, "{instr}");
+    }
+    assert_eq!(text_result(r#"TITLE "ab" \* Upper"#, new_property_value(r#"TITLE "ab" \* Upper"#).unwrap()), "AB");
+}
diff --git a/crates/layout/src/floats.rs b/crates/layout/src/floats.rs
--- a/crates/layout/src/floats.rs
+++ b/crates/layout/src/floats.rs
@@ -339,6 +339,7 @@ pub(crate) fn anchor_floats(
     place: bool,
 ) -> (HashMap<usize, Rect>, Arc<ParaLayout>, Vec<para::Exclusion>) {
     ctx.fields.page = pb.number;
+    ctx.fields.page_format = pb.sect.page_num_format;
     let (col_x, width) = (pb.col_x(), pb.col_w());
     let mut rects = HashMap::new();
     if !pb.web && place {
diff --git a/crates/layout/src/flow.rs b/crates/layout/src/flow.rs
--- a/crates/layout/src/flow.rs
+++ b/crates/layout/src/flow.rs
@@ -194,6 +194,7 @@ fn place_para_in_flow(ctx: &mut Ctx, pb: &mut PageBuilder, p: &Paragraph, block:
             if here != rel {
                 wrap_relays += 1;
                 ctx.fields.page = laid_page;
+                ctx.fields.page_format = pb.sect.page_num_format;
                 #[cfg(test)]
                 WRAP_RELAYS.with(|c| c.set(c.get() + 1));
                 let relaid =
@@ -224,6 +225,7 @@ fn place_para_in_flow(ctx: &mut Ctx, pb: &mut PageBuilder, p: &Paragraph, block:
             page_relays += 1;
             page_map.push((resume, pb.number));
             ctx.fields.page = laid_page;
+            ctx.fields.page_format = pb.sect.page_num_format;
             let relaid =
                 with_page_map(ctx, &mut page_map, |ctx| again(ctx, eq_start, |ctx| ctx.para_labelled(p, laid_width, None, &rel, label.clone())));
             match relaid.lines.iter().position(|l| l.start == resume) {
@@ -239,6 +241,7 @@ fn place_para_in_flow(ctx: &mut Ctx, pb: &mut PageBuilder, p: &Paragraph, block:
         }
         // What is laid out for these lines from here (their footnotes) is on this page.
         ctx.fields.page = pb.number;
+        ctx.fields.page_format = pb.sect.page_num_format;
         // How many lines fit?
         let Some(first) = pl.lines.get(l0) else { break };
         // A paragraph's first line may start below its top (pushed down by a floating object).
@@ -548,6 +551,7 @@ pub(crate) fn place_outline_para(ctx: &mut Ctx, pb: &mut PageBuilder, p: &Paragr
     let width = (pb.col_w() - indent - outline::SYMBOL_W).max(72.0);
     let q = outline::outline_para(ctx.doc, p, o.view.plain);
     ctx.fields.page = pb.number;
+    ctx.fields.page_format = pb.sect.page_num_format;
     let pl = ctx.para_labelled(&q, width, None, &[], label);
     pb.y += pl.rp.space_before.max(0.0);
     let n = pl.lines.len();
diff --git a/crates/layout/src/tests/pages.rs b/crates/layout/src/tests/pages.rs
--- a/crates/layout/src/tests/pages.rs
+++ b/crates/layout/src/tests/pages.rs
@@ -612,3 +612,24 @@ fn a_page_ends_when_a_multiple_spaced_lines_text_no_longer_fits() {
         assert!(past_margin > 0, "{multiple}: some shift should leave the last line's leading past the margin");
     }
 }
+
+/// A body PAGE field shows the page number in the section's number format, as Word does (a
+/// cardinal-text section shows "one"), and `\*` overrides it.
+#[test]
+fn body_page_field_uses_the_section_number_format() {
+    let mut d = Document::from_text("Page \nPage ");
+    d.last_section.page_num_format = wordcraft_doc::section::NumFormat::CardinalText;
+    for (i, instr) in ["PAGE", "PAGE \\* Upper"].into_iter().enumerate() {
+        let p = d.para_mut(StoryRef::Body, &Path::top(i)).unwrap();
+        let off = p.len();
+        let field = InlineObject::Field { instr: instr.into(), result: "1".into(), locked: false, dirty: false, code: false, form: String::new() };
+        p.insert_object(off, field, &Default::default()).unwrap();
+    }
+    let l = lay(&d);
+    let shown: Vec<String> = l.pages[0]
+        .items
+        .iter()
+        .filter_map(|it| if let Placed::Lines { para, .. } = it { Some(para.shown.iter().map(|x| x.1.clone()).collect::<String>()) } else { None })
+        .collect();
+    assert_eq!(shown, ["one", "ONE"], "{shown:?}");
+}
