mirror of
https://github.com/storytold/wordcraft.git
synced 2026-11-06 10:41:55 +00:00
Layout: text in a turned text box turns with it
A turned text box (free rotation) drew its frame turned and its text upright. Its text now turns with it about the box's centre, as Word draws it, on screen and in PNG and PDF export: upside down when the box is flipped across, never mirrored by a side-to-side flip. PDF export draws item by item, to tag each paragraph, so it turns each of the box's lines the same way, and tags the turned lines as their paragraph's text rather than as decoration. Clicks and the caret inside a turned box still go by its upright text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GBVQLyDnrG8Tn1yDQcUabC
This commit is contained in:
5 files changed
+90
-5
No files matched your search
@@ -48,13 +48,16 @@ pub struct ItemDisplay<'a> {
|
||||
page: &'a Page,
|
||||
authors: Authors<'a>,
|
||||
clips: Clips,
|
||||
/// The page's turned text boxes (see [`turned_boxes`]): their lines are drawn turned.
|
||||
turned: Vec<(u32, Rect, Spin)>,
|
||||
}
|
||||
|
||||
impl<'a> ItemDisplay<'a> {
|
||||
pub fn new(doc: &'a Document, page: &'a Page) -> Self {
|
||||
let cells: Vec<Placed> =
|
||||
page.header.iter().chain(&page.footer).chain(&page.items).filter(|c| matches!(c, Placed::Cell { clip: true, .. })).cloned().collect();
|
||||
ItemDisplay { doc, page, authors: Authors::new(doc), clips: clipped_cells(&cells) }
|
||||
let turned = [&page.items, &page.header, &page.footer].into_iter().flat_map(|items| turned_boxes(items)).collect();
|
||||
ItemDisplay { doc, page, authors: Authors::new(doc), clips: clipped_cells(&cells), turned }
|
||||
}
|
||||
|
||||
/// The draw items of `it` alone, as [`page_display`] gives them for a page holding just it
|
||||
@@ -64,6 +67,14 @@ impl<'a> ItemDisplay<'a> {
|
||||
let items = std::slice::from_ref(it);
|
||||
let (_, ba) = dimming(opts);
|
||||
stories_display(self.doc, &[items], Some(&self.clips), opts, &self.authors, ba, &mut out);
|
||||
// A line of a turned text box turns with it (as `stories_display` turns them on a page).
|
||||
if let Placed::Lines { story: StoryRef::Part(id), .. } = it
|
||||
&& let Some((_, rect, spin)) = self.turned.iter().find(|(t, ..)| t == id)
|
||||
{
|
||||
let mut turned = Vec::new();
|
||||
spun(*spin, *rect, std::mem::take(&mut out), &mut turned);
|
||||
out = turned;
|
||||
}
|
||||
body_change_bars(self.doc, self.page, items, opts, ba, &mut out);
|
||||
out
|
||||
}
|
||||
@@ -108,6 +119,7 @@ fn stories_display(
|
||||
let hidden = opts.hide_object.as_ref().map(|p| Hidden::new(doc, p));
|
||||
for items in stories {
|
||||
let warps = warped_boxes(doc, items);
|
||||
let turned = turned_boxes(items);
|
||||
let own;
|
||||
let clips = match shared {
|
||||
Some(c) => c,
|
||||
@@ -143,12 +155,41 @@ fn stories_display(
|
||||
warped_text(WarpedText { doc, lines: &lines, rect: *rect, warp, opts, authors, alpha: a }, out);
|
||||
}
|
||||
}
|
||||
Placed::Lines { story: StoryRef::Part(id), .. } if turned.iter().any(|(t, ..)| t == id) => {
|
||||
// A turned text box's lines are drawn together, turned with it about its
|
||||
// centre, where its first one is.
|
||||
if done.contains(id) {
|
||||
continue;
|
||||
}
|
||||
done.push(*id);
|
||||
if let Some((_, rect, spin)) = turned.iter().find(|(t, ..)| t == id) {
|
||||
let mut inner = Vec::new();
|
||||
for line in items.iter().filter(|i| matches!(i, Placed::Lines { story: StoryRef::Part(s), .. } if s == id)) {
|
||||
item(doc, line, opts, authors, a, &mut inner);
|
||||
}
|
||||
super::objects::spun(*spin, *rect, inner, out);
|
||||
}
|
||||
}
|
||||
_ => item(doc, it, opts, authors, a, out),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The text boxes among `items` that are turned: their story, frame (unturned) and how their
|
||||
/// text turns with them. As in Word, the text turns with the box but isn't mirrored: a box
|
||||
/// flipped across turns its text upside down, one flipped side to side leaves it readable.
|
||||
fn turned_boxes(items: &[Placed]) -> Vec<(u32, Rect, Spin)> {
|
||||
items
|
||||
.iter()
|
||||
.filter_map(|it| {
|
||||
let Placed::Object { rect, spin, text_box: Some(id), .. } = it else { return None };
|
||||
let text = Spin::new(spin.deg + if spin.flip_v { 180.0 } else { 0.0 }, false, false);
|
||||
(!text.is_identity()).then_some((*id, *rect, text))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// One object drawn alone, as its page draws it: the picture, shape or group `o` in `rect` (its
|
||||
/// unturned frame) and the text of the text box story `text_box` on `page`. A dragged object's
|
||||
/// preview, which mustn't show what was behind it (#1010).
|
||||
|
||||
@@ -4427,6 +4427,45 @@ fn text_box_at(d: &mut Document, pos: &Pos, text: &str, w: f32, h: f32, float: w
|
||||
id
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_turned_text_box_turns_its_text_with_it() {
|
||||
// Word turns a text box's text with the box (and turns it upside down when the box is
|
||||
// flipped across, but never mirrors it): its lines are drawn inside the box's rotation.
|
||||
let rotated_text = |rot: f32, flip_h: bool, flip_v: bool| {
|
||||
let mut d = Document::from_text("Body text.");
|
||||
let float =
|
||||
wordcraft_doc::para::Float { wrap: wordcraft_doc::para::Wrap::Square, x: 20.0, y: 20.0, rot, flip_h, flip_v, ..Default::default() };
|
||||
text_box(&mut d, 0, "Turned words", 140.0, 50.0, float);
|
||||
let l = lay(&d);
|
||||
let items = display::page_display(&d, &l.pages[0], &display::DisplayOptions::default());
|
||||
items.iter().find_map(|x| match x {
|
||||
display::Draw::Rotated { spin, items, .. }
|
||||
if items.iter().any(|g| matches!(g, display::Draw::Glyphs { text, .. } if text.contains("Turned"))) =>
|
||||
{
|
||||
Some(*spin)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
};
|
||||
assert_eq!(rotated_text(0.0, false, false), None, "an upright box's text is drawn as it is");
|
||||
let s = rotated_text(30.0, false, false).expect("turned with the box");
|
||||
assert_eq!((s.deg, s.flip_h, s.flip_v), (30.0, false, false));
|
||||
// Flipped side to side: readable, not mirrored.
|
||||
assert_eq!(rotated_text(0.0, true, false), None);
|
||||
// Flipped across: upside down.
|
||||
let s = rotated_text(0.0, false, true).expect("upside down");
|
||||
assert_eq!((s.deg, s.flip_h, s.flip_v), (180.0, false, false)); // Drawn item by item (as PDF export draws, to tag each paragraph): the same.
|
||||
let mut d = Document::from_text("Body text.");
|
||||
let float = wordcraft_doc::para::Float { wrap: wordcraft_doc::para::Wrap::Square, x: 20.0, y: 20.0, rot: 30.0, ..Default::default() };
|
||||
text_box(&mut d, 0, "Turned words", 140.0, 50.0, float);
|
||||
let l = lay(&d);
|
||||
let shown = display::ItemDisplay::new(&d, &l.pages[0]);
|
||||
let turned = l.pages[0].items.iter().flat_map(|it| shown.draws(it, &Default::default())).any(|x| {
|
||||
matches!(&x, display::Draw::Rotated { items, .. } if items.iter().any(|g| matches!(g, display::Draw::Glyphs { text, .. } if text.contains("Turned"))))
|
||||
});
|
||||
assert!(turned);
|
||||
}
|
||||
|
||||
fn box_rect(l: &DocLayout, id: u32) -> wordcraft_geom::Rect {
|
||||
l.pages[0]
|
||||
.items
|
||||
|
||||
@@ -223,6 +223,8 @@ impl Exporter<'_> {
|
||||
// A rotated inline picture, or a rotated group holding pictures: turned as a
|
||||
// whole, each picture tagged on its own (see `rotated_pictures`).
|
||||
Draw::Rotated { items, .. } if items.iter().any(holds_picture) => self.rotated_pictures(s, d),
|
||||
// A turned text box's line: still the paragraph's text.
|
||||
Draw::Rotated { .. } => self.tagged(s, Role::Para(idx), None, |me, s| me.draw(s, d)),
|
||||
// An inline chart, diagram or described shape: its text is part of the figure, not the paragraph.
|
||||
Draw::Figure { alt, kind, draws: inner } => self.tagged(s, Role::Figure, Some(figure_alt(alt, *kind)), |me, s| {
|
||||
for d in inner {
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
# Where WordCraft falls short of Word
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-01 · **Change:** trivial (gap 19 narrowed: ODT heading numbering read and written); previously trivial (gap 19 narrowed: ODT table borders and padding read and written); previously trivial (gap 19 narrowed: ODT heading style looks read and written); previously trivial (gap 19 narrowed: ODT tab stops read and written); previously trivial (gap 19 narrowed: ODT character formatting read and written); previously trivial (gap 19 narrowed: ODT list formats read and written); previously trivial (gap 19 narrowed: ODT comments read and written); previously trivial (gap 19 narrowed: ODT footnotes and endnotes read and written); previously trivial (gap 19 narrowed: ODT headers and footers written, text fields and paragraph spacing read and written); previously trivial (only the PDF sent to the printer leaves out link annotations; Save these pages as PDF keeps them, #4734; no ranked gap changed); previously trivial (documents with links print: the print job leaves out link annotations, #4734; no ranked gap changed; previously trivial: aligned-copy review fixes retain the document axis through scrolling and include secondary selections (#4764)); previously trivial (connector rerouting indexes laid-out object positions once per batch (#3631); previously trivial (custom-property bookmark-link preservation, #4767); previously trivial (Relative font-size commands preserve mixed sizes in the selection (#4903); existing button steps and one-point shortcuts are unchanged; readiness estimates unchanged); previously trivial (Arabic Backspace removes one combining mark and keeps its base letter (#4757); previously trivial (note conversion reference-run membership uses one ordered walk, #4750); previously trivial (DOCX comment-ID save lookups); previously trivial (gap 19: RTF reads and writes WMF and EMF pictures, #4259); previously trivial (#4658 bounds nested Markdown reference-label work); previously trivial (HTML import keeps preformatted lines with CR or CRLF input newlines, #4810); previously trivial (Recipient labels continue across product-sized sheets instead of stopping after the first sheet (#4880)); previously trivial (View windows: several open documents and Switch Windows in the engine; previously trivial: explicit documents keep the editor when recovery discovery finishes; readable recovery controls; previously trivial: text mini toolbar available with the right-click menu, #2959; previously trivial: personal templates in File › New and Save as Word Template, #2852; previously trivial: tracked table/row/cell/section property changes record in WordCraft, narrowing gap 5; previously trivial: a document with many content controls saves in the time the controls take, #13; previously trivial: Format Page Numbers dialog carries Word's number formats and Continue from previous section / Start at, #1113; previously trivial: centred list labels after leading page breaks, #2964; previously trivial: fixed font inputs in four layout controls; previously trivial: a right, centre or decimal tab whose text doesn't fit, or a left tab past the right indent, goes to the next line with its text, #1840; previously trivial: Bullets and Numbering toggle off style-inherited lists at their effective level, including basedOn chains and numbered ListParagraph styles; other paragraphs and list definitions stay unchanged (related to #3459); previously trivial: Russian sorting keeps ё with е and recognises decomposed ё/й; previously trivial: #8: English dialects — en-GB, en-CA, en-AU… flag each other's spellings, #1281; previously trivial: #1: shape gradients and transparency, #1500; previously trivial: text typed into a content control takes the control's formatting; localized placeholder styles recognised; previously trivial: forms protection keeps its content controls: no inserting, Properties or Remove Content Control; previously trivial: clearing a no-match font query keeps the recent heading visible, #2068; previously trivial: line breaking: a slash opening a word travels whole to the next line, #2963; previously trivial: recipient list search and merged-only view, #1576; previously trivial: #2 impact restated: only ink rendering and OLE editing remain; previously trivial: #2 narrowed: native ink nested in groups and drawing canvases kept on save; previously trivial: opening a DOCX no longer copies its pictures; previously trivial: RTF export writes sections (gap 19); previously trivial: ODT export writes sections (gap 19); previously trivial: automatic Asian/Latin and Asian/number spacing laid out, #2358; previously trivial: hint-reliant CJK outlines; previously trivial: numbered headings, #1793; previously trivial: legacy outline, shadow, emboss and imprint text effects drawn, #2534; previously trivial: #8 narrowed: Indonesian proofing from the installed Hunspell dictionary, #498; previously trivial: glossary/building blocks closed; previously trivial: Split Cells Line truncated
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-01 · **Change:** trivial (#10 narrowed: text in a turned text box is drawn turned with it); previously trivial (gap 19 narrowed: ODT heading numbering read and written); previously trivial (gap 19 narrowed: ODT table borders and padding read and written); previously trivial (gap 19 narrowed: ODT heading style looks read and written); previously trivial (gap 19 narrowed: ODT tab stops read and written); previously trivial (gap 19 narrowed: ODT character formatting read and written); previously trivial (gap 19 narrowed: ODT list formats read and written); previously trivial (gap 19 narrowed: ODT comments read and written); previously trivial (gap 19 narrowed: ODT footnotes and endnotes read and written); previously trivial (gap 19 narrowed: ODT headers and footers written, text fields and paragraph spacing read and written); previously trivial (only the PDF sent to the printer leaves out link annotations; Save these pages as PDF keeps them, #4734; no ranked gap changed); previously trivial (documents with links print: the print job leaves out link annotations, #4734; no ranked gap changed; previously trivial: aligned-copy review fixes retain the document axis through scrolling and include secondary selections (#4764)); previously trivial (connector rerouting indexes laid-out object positions once per batch (#3631); previously trivial (custom-property bookmark-link preservation, #4767); previously trivial (Relative font-size commands preserve mixed sizes in the selection (#4903); existing button steps and one-point shortcuts are unchanged; readiness estimates unchanged); previously trivial (Arabic Backspace removes one combining mark and keeps its base letter (#4757); previously trivial (note conversion reference-run membership uses one ordered walk, #4750); previously trivial (DOCX comment-ID save lookups); previously trivial (gap 19: RTF reads and writes WMF and EMF pictures, #4259); previously trivial (#4658 bounds nested Markdown reference-label work); previously trivial (HTML import keeps preformatted lines with CR or CRLF input newlines, #4810); previously trivial (Recipient labels continue across product-sized sheets instead of stopping after the first sheet (#4880)); previously trivial (View windows: several open documents and Switch Windows in the engine; previously trivial: explicit documents keep the editor when recovery discovery finishes; readable recovery controls; previously trivial: text mini toolbar available with the right-click menu, #2959; previously trivial: personal templates in File › New and Save as Word Template, #2852; previously trivial: tracked table/row/cell/section property changes record in WordCraft, narrowing gap 5; previously trivial: a document with many content controls saves in the time the controls take, #13; previously trivial: Format Page Numbers dialog carries Word's number formats and Continue from previous section / Start at, #1113; previously trivial: centred list labels after leading page breaks, #2964; previously trivial: fixed font inputs in four layout controls; previously trivial: a right, centre or decimal tab whose text doesn't fit, or a left tab past the right indent, goes to the next line with its text, #1840; previously trivial: Bullets and Numbering toggle off style-inherited lists at their effective level, including basedOn chains and numbered ListParagraph styles; other paragraphs and list definitions stay unchanged (related to #3459); previously trivial: Russian sorting keeps ё with е and recognises decomposed ё/й; previously trivial: #8: English dialects — en-GB, en-CA, en-AU… flag each other's spellings, #1281; previously trivial: #1: shape gradients and transparency, #1500; previously trivial: text typed into a content control takes the control's formatting; localized placeholder styles recognised; previously trivial: forms protection keeps its content controls: no inserting, Properties or Remove Content Control; previously trivial: clearing a no-match font query keeps the recent heading visible, #2068; previously trivial: line breaking: a slash opening a word travels whole to the next line, #2963; previously trivial: recipient list search and merged-only view, #1576; previously trivial: #2 impact restated: only ink rendering and OLE editing remain; previously trivial: #2 narrowed: native ink nested in groups and drawing canvases kept on save; previously trivial: opening a DOCX no longer copies its pictures; previously trivial: RTF export writes sections (gap 19); previously trivial: ODT export writes sections (gap 19); previously trivial: automatic Asian/Latin and Asian/number spacing laid out, #2358; previously trivial: hint-reliant CJK outlines; previously trivial: numbered headings, #1793; previously trivial: legacy outline, shadow, emboss and imprint text effects drawn, #2534; previously trivial: #8 narrowed: Indonesian proofing from the installed Hunspell dictionary, #498;Line truncated
|
||||
> **Highlight update:** 2026-10-25 · Highlight shortcut toggles the last chosen colour off when the whole selection has it; explicit colour commands remain setters (#4085). Readiness estimates unchanged.
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-29 · **Change:** trivial (aligned-copy review fixes retain the document axis through scrolling and include secondary selections (#4764)); previously trivial (#15: bibliography sources keep a corporate author, #4848); previously trivial (Table Properties applies to the selected rows, columns and cells, #4857); previously trivial (Relative font-size commands preserve mixed sizes in the selection (#4903); existing button steps and one-point shortcuts are unchanged; readiness estimates unchanged); previously trivial (Arabic Backspace removes one combining mark and keeps its base letter (#4757); previously trivial (Recipient labels continue across product-sized sheets instead of stopping after the first sheet (#4880)); previously trivial (#7: Custom Document Properties dialog, #2982; previously trivial: egui 0.37 migration: the interface catalogs stay shared and pinned by `craft-i18n.rev` at `9205108cfdced5177706264038848b68b2422d2b` (27 languages, English fallback); input-method handling has new regressions but managed CI passed, and native IME acceptance plus native-speaker catalog review remain open; previously trivial: Markdown code spans keep line-broken text literal, #3738; previously trivial: small follow-ups from the field, table and AutoFormat batch, #4232; previously trivial: highlight target includes selected paragraph marks, #4085; previously trivial: full-target highlight check beyond 10,000 runs, #4085; previously trivial: Exact-spacing table contact uses the specified line height rather than paragraph-mark metrics (#3977); previously trivial: cu20we: restrict pre-spacing float origins to paragraph anchors; document cap helpers and regressions; previously trivial: a command that changes nothing is not an edit: no undo step, no unsaved changes, no relayout, and the redo stack survives it; previously minor: #8 narrowed: Spanish proofing (Spain, Mexico, Latin America, Uruguay) from the installed Hunspell dictionaries, #3360, #3553; previously trivial: gap 10: group connector rerouting preserves transformed free and unresolved endpoints under nonuniform scaling; obstacle routing still missing; readiness estimates unchanged; previously trivial: a word with no break opportunity in it no longer makes the layout quadratic, #3412; previously trivial: the ranked gap list held two rows each for #19, #20 and #21; each gap appears once now; a document with many relationships opens in the time its part takes, #3411; previously trivial: the file commands that change the document are checked on a protected document, the protection of a protected document needs an explicit stop, the session author can't change through `file.setAuthor` under protection (the CLI's `--author` uses the same protection check), and the post-check compares the settings and the metadata; previously trivial: explicit documents keep the editor when recovery discovery finishes; readable recovery controls; previously trivial: text mini toolbar available with the right-click menu, #2959; previously trivial: personal templates in File › New and Save as Word Template, #2852; previously trivial: tracked table/row/cell/section property changes record in WordCraft, narrowing gap 5; previously trivial: a document with many content controls saves in the time the controls take, #13; previously trivial: Format Page Numbers dialog carries Word's number formats and Continue from previous section / Start at, #1113; previously trivial: centred list labels after leading page breaks, #2964; previously trivial: fixed font inputs in four layout controls; previously trivial: a right, centre or decimal tab whose text doesn't fit, or a left tab past the right indent, goes to the next line with its text, #1840; previously trivial: Bullets and Numbering toggle off style-inherited lists at their effective level, including basedOn chains and numbered ListParagraph styles; other paragraphs and list definitions stay unchanged (related to #3459); previously trivial: Russian sorting keeps ё with е and recognises decomposed ё/й; previously trivial: #8: English dialects — en-GB, en-CA, en-AU… flag each other's spellings, #1281; previously trivial: #1: shape gradients and transparency, #1500; previously trivial: text typed into a content control takes the control's formatting; localized placeholder styles recognised; previously trivial: forms protection keeps its content controls: no inserting, Properties or Remove Content Control; previously trivial: clearing a no-match font query keeps the recent heading visible, #2068; previously trivial: line breaking: a slash opening a word travels whole to the next line, #2963; previously trivial: recipient list search and merged-only view, #1576; previously trivial: #2 impact restated: only ink rendering and OLE editing remain; previously trivial: #2 narrowed: native ink nested in groups and drawing canvases kept on save; previously trivial: opening a DOCX no lonLine truncated
|
||||
|
||||
@@ -73,7 +73,7 @@ Text context menus now include the existing formatting mini toolbar (#2959), inc
|
||||
| 7 | **Dialog depth**: 61 modal dialogs vs Word's ~100 (Custom Document Properties from File › Info, #2982; Drop Cap Options matches Word's menu and dialog; Split Cells and Convert Text to Table match Word's observed limits and defaults; Format Page Numbers carries Word's number formats and Continue from previous section / Start at, #1113) | B | `Dialog` enum in `crates/ui-egui/src/dialogs.rs`; Cell Margins (#1960) now opens with current defaults and applies only confirmed edits | Power users reach for dialogs the ribbon alone doesn't cover | 11–19 | [ui-parity.md](ui-parity.md) |
|
||||
| 9 | **Password-protected `.doc` files with an AES header in the Word 97-2003 format** can't be opened: [MS-OFFCRYPTO] defines AES only for encrypted packages, so these are refused with a clear message. Encrypted packages open under any Word extension (`.docx`, `.doc`, `.dot`…) with either agile (Word 2010+) or Standard (Word 2007, AES-ECB) encryption; protected `.docx` saves with agile encryption (#55, #281, gap 9) | | `crates/docx/src/crypt.rs` (agile and Standard); `crates/docbin/src/crypt.rs` opens XOR/RC4/RC4 CryptoAPI `.doc` with a password (#1079). All synthetic fixtures; untested on Word-written files | Corporate and legal users with old files | 0 (refused by design; nothing left to build) | [file-format-parity.md](file-format-parity.md) |
|
||||
| 8 | **Proofing covers English, Polish, Portuguese, Italian, Croatian, Turkish, Russian, Czech, Indonesian and Spanish only**; no dictionaries or grammar for the other ~41 languages Word proofs (text in them is left unmarked), and no hyphenation for them except Bulgarian and German | | `crates/proof` `ProofLang::PROOFED`: English (American, British-spelling and Canadian, each flagging the other's spellings, #1281), Polish, Portuguese (pt-BR via the installed VERO dictionary, #797), Italian (it-IT via the installed Hunspell dictionary), Croatian (hr-HR, 2026-10-13), Turkish (tr_TR spelling, grammar, TDK hyphenation, 2026-10-13) Russian (the installed `ru_RU` dictionary, 2026-10-12) Indonesian (the installed `id_ID` dictionary, #498, 2026-10-16) and Spanish (es-ES, es-MX, es-419 and es-UY via the installed RLA-ES dictionaries, Uruguayan `vos` forms, #3360, #3553, 2026-10-22), a language per run; Bulgarian (#826) and German (TeX patterns, 2026-10-14) have hyphenation only; open PRs add Danish (#723), Russian (#894) and Czech (#1048); Word ships 120 proofing tools; issues #25, #40, #100 open | Every writer outside English, Polish, Portuguese, Italian, Croatian, Turkish, Russian, Czech, Indonesian and Spanish | 20–40 | [localization-parity.md](localization-parity.md) |
|
||||
| 10 | **Objects**: text in a freely rotated text box (free rotation and flips landed, #332; live rotation preview while dragging fixed for pictures and shapes without text stories, #2277; text direction, alignment and linked text boxes landed, #369; contour wrap and wrap side landed, #354), shape effects, WordArt 3-D effects and connector routing around shapes (WordArt effects and warps and glued connectors landed, #416), dragging members inside a Drawing Canvas (the canvas landed, #344) | | [parity-checklist.md](parity-checklist.md) (Shape Format 11/11, Layout 22/22); group and ungroup landed (#267) | Newsletters, flyers, reports with diagrams | 20–30 | [target-app-parity.md](target-app-parity.md) |
|
||||
| 10 | **Objects**: editing text in a freely rotated text box (its text is drawn turned; free rotation and flips landed, #332; live rotation preview while dragging fixed for pictures and shapes without text stories, #2277; text direction, alignment and linked text boxes landed, #369; contour wrap and wrap side landed, #354), shape effects, WordArt 3-D effects and connector routing around shapes (WordArt effects and warps and glued connectors landed, #416), dragging members inside a Drawing Canvas (the canvas landed, #344) | | [parity-checklist.md](parity-checklist.md) (Shape Format 11/11, Layout 22/22); group and ungroup landed (#267) | Newsletters, flyers, reports with diagrams | 20–30 | [target-app-parity.md](target-app-parity.md) |
|
||||
| 11 | **Interface languages**: 3 of the 12 key languages missing (hi, ar, id) and pt-PT (French landed, #729; Vietnamese, #1434; Korean, #1216; 21 interface languages in `crates/ui-egui/src/i18n`); open PRs for Arabic (#737, #1042, #1032), Indonesian (#572) and pt-PT (#1028); no mirrored RTL interface yet; ~5% of strings still English in the translated catalogs | | [localization-parity.md](localization-parity.md) | Most of the world's writers | 50–80 | [localization-parity.md](localization-parity.md) |
|
||||
| 12 | **Draw tab / ink**: pens, pencil, highlighter, eraser, Select and Hide Ink landed (#307); Lasso Select, Add Pen, Ink to Shape and Ink Replay landed (#395); Ink to Shape also recognises single-stroke arrows (a line plus a V head drawn in one stroke becomes a straight connector with an arrowhead); two-stroke arrows (a line and a separate V or closed-triangle head) and one-stroke arrows with a closed triangle head landed (#1778); missing: Ink to Math, pressure, rendering/editing Word's own ink (drawing-level and direct run-level InkML content parts now survive DOCX save) | | [parity-checklist.md](parity-checklist.md) (Draw 10/11) | Pen and tablet users | 4–8 | [hardware-parity.md](hardware-parity.md) |
|
||||
| 13 | **Charts and SmartArt editing** (editing charts and SmartArt from Word, more chart types and formatting) | | `insert.chart` and Chart Design basics landed (#409): WordCraft's own charts are editable; charts from Word stay read-only (no embedded-workbook editing), radar charts are drawn (#1890); no combo/3D/stock charts (charts of those types show a placeholder with their title and type, #998), axis or series formatting; Insert › SmartArt landed (#497) for graphics WordCraft makes | Reports and business documents | 35–55 | [target-app-parity.md](target-app-parity.md) |
|
||||
@@ -152,6 +152,7 @@ Recipient label generation (#4880) now includes all selected recipients across a
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-01 | trivial | #10 narrowed: text in a turned text box is drawn turned with it on screen and in PNG and PDF (upside down when flipped across, never mirrored); editing it still goes by its upright text; estimate unchanged |
|
||||
| 2026-10-30 | trivial | Only the PDF sent to the printer omits link annotations; File › Print › Save these pages as PDF (`file.printPdf`) keeps them, like Export (#4734); no ranked gap changed; readiness estimates unchanged |
|
||||
| 2026-10-29 | trivial | The print job's PDF omits link annotations, which the spooler refused to fit to the paper, so documents with links reach the printer (#4734); physical printer output remains to be validated; no ranked gap changed; readiness estimates unchanged |
|
||||
| 2026-10-28 | trivial | Aligned copies retain their locked document coordinate during middle-button scrolling and can start on secondary selected objects; existing Ctrl and Shift selection behavior stays (#4764) |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Layout and pagination parity with Microsoft Word
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-01 · **Change:** trivial (an in-line picture, shape or chart moves with its run's raise or lowering, Word's `w:position`; previously trivial: connector rerouting indexes laid-out object positions once per batch (#3631); previously trivial (field results take the `\* DollarText`, `\* Hex` and `\* ArabicDash` switches, #4226; previously trivial: a cell spanning columns shares its extra minimum width by the columns' preferred widths, equally in automatic tables, #1883; previously trivial: continuous sections one after another go onto new pages instead of running off the page; empty marks after another empty section mark take a line each until the page is full, then stay at its bottom drawing nothing, not even a list label; an empty mark that takes no room draws no list label either; a kept-together paragraph is measured against the page it would move to, so a tall different first-page header no longer splits it, #3907; previously trivial: a justified line ending in a manual line break is stretched to the margin unless `w:doNotExpandShiftReturn` is set, #3924; previously trivial: a no-break space is as wide as a space, #4192; previously trivial: a bookmark or other zero-width marker is no line-break opportunity, #4193; previously trivial: Arabic list labels: `arabicAlpha` and `arabicAbjad` draw the 28 Arabic letters (alphabetical and abjad order), doubling past the last letter; Hebrew, Hindi, Thai and Vietnamese still show decimal (`doc::section::tests::arabic_letters_count_in_both_orders`); previously trivial: Preserve grapheme edit/wrap boundaries across independently drawn font/style pieces; Exact-spacing table contact uses the specified line height rather than paragraph-mark metrics (#3977); previously trivial: cu20we: restrict pre-spacing float origins to paragraph anchors; document cap helpers and regressions; previously trivial: cu20we: outline caps; previously trivial: cu20we: box float paragraph origin; previously trivial: group connector rerouting preserves transformed free and unresolved endpoints under nonuniform scaling; readiness estimates unchanged; previously trivial: a word with no break opportunity in it no longer makes the layout quadratic, #3412; previously trivial: an empty paragraph whose mark is hidden shows nothing, not even its list number, which is still counted, #3982; previously trivial: a Thai run with Latin finds one installed font covering it all on Linux too, #1549; previously trivial: symbol fonts keep their line height without the font; previously trivial: an equation-array row without `&` is centred, #2617; previously trivial: a superscript or subscript sizes its line at the full font size, without its raise or drop, as Word does; previously trivial: an object dragged near the bottom of a page lands where it is dropped, #3169; previously trivial: centred list labels after leading page breaks, #2964; previously trivial: fixed font inputs in four layout controls; previously trivial: justified lines shrink their spaces by Word's measured limits; previously trivial: a right, centre or decimal tab whose text doesn't fit, or a left tab past the right indent, goes to the next line with its text, #1840; previously trivial: a click below a floating table goes to the text beside or after it, not to a cell of its last row, #2038; previously trivial: shapes drawn with their gradients and transparency, #1500; previously trivial: paragraphs above a float's anchor wrap around it, also when that pushes the anchor onto the next page, #2048; previously trivial: triple borders draw three lines, #1740; previously trivial: the caret, clicks and selection edges fall between the letters of a ligature, #1974; previously trivial: a word after a slash wraps whole, like Word, #2963; previously trivial: VML header-cell floats always on the page, user-checked in Word for Mac, #1775; previously trivial: an at-least row with no room for its minimum starts the next page whole, #1742; previously trivial: AutoFit donors lose equal widths down to their content minima; previously trivial: automatic space between Asian text and Latin text or digits, #2358; previously trivial: hint-reliant CJK outlines; previously trivial: positional tabs go to the margins or indents, #2367; previously trivial: field results with right-to-left text read right to left, #1670; previously trivial: a list bullet no installed font can draw shows the level's default bullet, #1683; previously trivial: a missing Thai font is drawn in one Thai face for the whole run, #1549; previously trivial: the caret after spaces hanging past the margin stays at the margin, #2146; previously trivial: covered black-circle fallback before UI sans; previously trivial: resolved split-cell continuation top insets; previously trivial: Symbol PCL6 alias uses Symbol encoding; previously trivial: exact full font names select their installed face; previously trivial: Arial Unicode MS uses proportionalLine truncated
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-01 · **Change:** trivial (text in a turned text box turns with it; previously trivial: an in-line picture, shape or chart moves with its run's raise or lowering, Word's `w:position`; previously trivial: connector rerouting indexes laid-out object positions once per batch (#3631); previously trivial (field results take the `\* DollarText`, `\* Hex` and `\* ArabicDash` switches, #4226; previously trivial: a cell spanning columns shares its extra minimum width by the columns' preferred widths, equally in automatic tables, #1883; previously trivial: continuous sections one after another go onto new pages instead of running off the page; empty marks after another empty section mark take a line each until the page is full, then stay at its bottom drawing nothing, not even a list label; an empty mark that takes no room draws no list label either; a kept-together paragraph is measured against the page it would move to, so a tall different first-page header no longer splits it, #3907; previously trivial: a justified line ending in a manual line break is stretched to the margin unless `w:doNotExpandShiftReturn` is set, #3924; previously trivial: a no-break space is as wide as a space, #4192; previously trivial: a bookmark or other zero-width marker is no line-break opportunity, #4193; previously trivial: Arabic list labels: `arabicAlpha` and `arabicAbjad` draw the 28 Arabic letters (alphabetical and abjad order), doubling past the last letter; Hebrew, Hindi, Thai and Vietnamese still show decimal (`doc::section::tests::arabic_letters_count_in_both_orders`); previously trivial: Preserve grapheme edit/wrap boundaries across independently drawn font/style pieces; Exact-spacing table contact uses the specified line height rather than paragraph-mark metrics (#3977); previously trivial: cu20we: restrict pre-spacing float origins to paragraph anchors; document cap helpers and regressions; previously trivial: cu20we: outline caps; previously trivial: cu20we: box float paragraph origin; previously trivial: group connector rerouting preserves transformed free and unresolved endpoints under nonuniform scaling; readiness estimates unchanged; previously trivial: a word with no break opportunity in it no longer makes the layout quadratic, #3412; previously trivial: an empty paragraph whose mark is hidden shows nothing, not even its list number, which is still counted, #3982; previously trivial: a Thai run with Latin finds one installed font covering it all on Linux too, #1549; previously trivial: symbol fonts keep their line height without the font; previously trivial: an equation-array row without `&` is centred, #2617; previously trivial: a superscript or subscript sizes its line at the full font size, without its raise or drop, as Word does; previously trivial: an object dragged near the bottom of a page lands where it is dropped, #3169; previously trivial: centred list labels after leading page breaks, #2964; previously trivial: fixed font inputs in four layout controls; previously trivial: justified lines shrink their spaces by Word's measured limits; previously trivial: a right, centre or decimal tab whose text doesn't fit, or a left tab past the right indent, goes to the next line with its text, #1840; previously trivial: a click below a floating table goes to the text beside or after it, not to a cell of its last row, #2038; previously trivial: shapes drawn with their gradients and transparency, #1500; previously trivial: paragraphs above a float's anchor wrap around it, also when that pushes the anchor onto the next page, #2048; previously trivial: triple borders draw three lines, #1740; previously trivial: the caret, clicks and selection edges fall between the letters of a ligature, #1974; previously trivial: a word after a slash wraps whole, like Word, #2963; previously trivial: VML header-cell floats always on the page, user-checked in Word for Mac, #1775; previously trivial: an at-least row with no room for its minimum starts the next page whole, #1742; previously trivial: AutoFit donors lose equal widths down to their content minima; previously trivial: automatic space between Asian text and Latin text or digits, #2358; previously trivial: hint-reliant CJK outlines; previously trivial: positional tabs go to the margins or indents, #2367; previously trivial: field results with right-to-left text read right to left, #1670; previously trivial: a list bullet no installed font can draw shows the level's default bullet, #1683; previously trivial: a missing Thai font is drawn in one Thai face for the whole run, #1549; previously trivial: the caret after spaces hanging past the margin stays at the margin, #2146; previously trivial: covered black-circle fallback before UI sans; previously trivial: resolved split-cell continuation top insets; previously trivial: Symbol PCL6 alias uses Symbol encoding; previously trivial: exact full font names select their installedLine truncated
|
||||
|
||||
A document only "opens correctly" if its pages break where Word breaks them: the same lines, the
|
||||
same page count, footnotes and pictures on the same pages. This checklist covers line breaking,
|
||||
@@ -69,7 +69,7 @@ Exact line spacing keeps the run font baseline around tall equations; automatic
|
||||
| Text wrap: square, top-and-bottom, behind, in front | ✅ | 2026-10-06; beside an object, a line's first word that doesn't fit the strip goes on whole to the row's next strip or the next row, instead of being split (#1376, `layout::tests::a_word_too_wide_for_the_strip_beside_a_float_moves_on_whole`; inferred from a reporter's comparison, not yet measured in Word) a paragraph running on into the next column or page is broken again there around that column's shapes, its own floating objects left where they are (#1374). Text in an earlier column doesn't yet wrap around a shape anchored in a later one (#1158) | #884: an object dragged on a multi-column page anchors to a paragraph in the column it lands in, so that column's text wraps around it. Paragraphs above an object's anchor wrap around it when it is placed higher up the page (the page is laid out again with it known from the start, #2048; when that wrap pushes the anchor onto the next page, the first page keeps the room and the object goes with its anchor, and a layout that still hasn't settled moves the object down to its paragraph rather than over text); text in an earlier column beside it doesn't yet, nor do lines a paragraph continues into the next column (broken once, in the column it starts in) |
|
||||
| Text wrap: tight, through (contour, wrap polygon) | 🟡 | #354: lines follow the wrap polygon, or the outline worked out from the object (shape geometry; a picture's opaque pixels as a convex hull; otherwise the rectangle), turned and flipped with it, kept the left/right distance from text; Through also uses gaps inside the outline. Wrap side (`wrapText`: both sides, left only, right only, largest side per line) applies to square, tight and through wraps, turned objects included; picture outlines are convex; a picture is decoded once for all its crops, pictures whose header says they are opaque are not decoded, and at most 2^28 pixels per layout are decoded (later pictures wrap as rectangles in that layout, #3692); not compared page-for-page with Word | 1–2 |
|
||||
| Text boxes, overflow, linked text boxes | ✅ | #46; linked text boxes (#369): the text runs on through each box in turn at that box's line length, overflow past the last box hidden; Align Text top / middle / bottom; the box's own internal margins, and a box set to Resize shape to fit text shows all its text (#743); a chain's story is laid out at no more than 16 box widths (told apart to a twip), later boxes needing another taking the nearest one in their text direction (#3692) | — |
|
||||
| Rotated objects and text | 🟡 | #332: pictures, shapes, charts and groups drawn turned and flipped about their centre (screen and PDF); square wrap and inline lines keep clear of the rotated bounds; hit testing on the turned shape. Text in a rotated text box stays upright | 2–3 |
|
||||
| Rotated objects and text | 🟡 | #332: pictures, shapes, charts and groups drawn turned and flipped about their centre (screen and PDF); square wrap and inline lines keep clear of the rotated bounds; hit testing on the turned shape. Text in a rotated text box turns with it (on screen, PNG and PDF; upside down when the box is flipped across, never mirrored, as Word draws it); clicks and the caret in it still go by its upright text | 1–2 |
|
||||
| Connector lookup cost | 🟡 | One temporary layout-object index per reroute replaces repeated page scans. Repeated positions retain page-hint preference, first-match order and first-page fallback; same-page glue and free/unresolved endpoints are unchanged. Synthetic lookup equivalence, geometry and 8,000-page scaling regressions; model object access and obstacle routing remain separate, no new native Word comparison | #3631 |
|
||||
| Group connector rerouting | 🟡 | Glued ends keep their existing connection-site path; free ends, missing targets and invalid sites retain their displayed rotated and flipped position when the connector frame is rebuilt, including under nonuniform group scaling. Model and engine regressions cover all connector kinds, both ends, rotations/flips, unit/uniform/nonuniform scale, idempotent rerouting, degenerate dimensions without nonfinite writes, and fallback spans beyond the 4000 pt display-size cap without endpoint movement or repeated shrinkage. Non-invertible display scales and unrepresentable fallback spans leave connectors needing fallback endpoints unchanged; fully resolved connectors still reroute. Invalid child extents use the renderer's unit-scale fallback. Routing around obstacles remains missing; no new native Word comparison | — |
|
||||
| Page borders (from page edge or text), page colour, watermark | ✅ | | — |
|
||||
@@ -260,6 +260,8 @@ layout suite are pending; this is not a new Word/native-IME acceptance claim.
|
||||
|
||||
## Revision history
|
||||
|
||||
> 2026-11-01 · Text in a turned text box is drawn turned with it, about its centre, on screen and in PNG and PDF export; flipped across, upside down; flipped side to side, still readable (as Word draws it). Clicks and the caret inside a turned box still go by its upright text (`layout::tests::a_turned_text_box_turns_its_text_with_it`).
|
||||
|
||||
> 2026-11-01 · An in-line picture, shape or chart is raised or lowered by its run's position (Word's `w:position`, Font › Advanced › Position): lowered, it reaches below the baseline and the line grows down to hold it (at most until its top is on the baseline); raised, it stands clear of the baseline. It used to stand on the baseline whatever the position (`layout::tests::an_inline_picture_moves_with_its_runs_position`).
|
||||
|
||||
> 2026-10-25 · Field results take the `\* DollarText`, `\* Hex` and `\* ArabicDash` switches in table formulas and in the page-number, SEQ, PAGEREF and count fields (#4226) (`layout::fields::tests::dollar_text_hex_and_arabic_dash_switches`).
|
||||
|
||||
Reference in new issue
Block a user