From 76f5d4b817d879c9433e61cafc629749538f46e0 Mon Sep 17 00:00:00 2001 From: Isaac Levine Date: Sun, 11 Oct 2026 22:30:39 -0400 Subject: [PATCH 001/336] CI: run cargo xtask ci on pull requests and pushes to main (#754) --- .github/workflows/ci.yml | 62 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..cceda4c65b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,62 @@ +# The CLAUDE.md quality gate (`cargo xtask ci`: fmt, clippy -D warnings, tests, assets, layers, +# wasm) on every pull request and every push to main, so a merge burst can't land a red tree that +# only the next contributor's local run discovers. +name: CI + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +env: + # Same craft-fonts commit as release.yml and freebsd.yml, so layout tests see the fonts a + # release build embeds (the checkout is an optional build input, see crates/fonts/build.rs). + CRAFT_FONTS_REF: 8dcdacd5153e64560d109541a47d806f26f048c0 + +jobs: + gate: + name: cargo xtask ci + runs-on: ubuntu-24.04 + timeout-minutes: 60 + env: + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: "0" + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Check out craft-fonts + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: storytold/craft-fonts + ref: ${{ env.CRAFT_FONTS_REF }} + path: craft-fonts + + - name: Install Linux build dependencies (winit, wgpu, rfd) + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + pkg-config libxkbcommon-dev libwayland-dev libx11-dev libxcursor-dev libxrandr-dev \ + libxi-dev libgtk-3-dev libfontconfig1-dev libfreetype-dev mesa-vulkan-drivers + + - name: Install Rust (stable + clippy, rustfmt, wasm32) + run: | + rustup toolchain install stable --profile minimal --component clippy,rustfmt \ + --target wasm32-unknown-unknown + rustup default stable + + - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 + with: + save-if: ${{ github.ref == 'refs/heads/main' }} + + - name: Quality gate + env: + CRAFT_FONTS_DIR: ${{ github.workspace }}/craft-fonts + run: cargo xtask ci From ad485db499c25292cd0be0bbfa87c3c5974e29c6 Mon Sep 17 00:00:00 2001 From: Saad Abdullah Date: Tue, 13 Oct 2026 16:46:36 +0000 Subject: [PATCH 002/336] fix(geom): reject measurements that overflow to infinity in parse_measure (#1359) --- crates/geom/src/lib.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/crates/geom/src/lib.rs b/crates/geom/src/lib.rs index 8f24ff426f..16ce35776c 100644 --- a/crates/geom/src/lib.rs +++ b/crates/geom/src/lib.rs @@ -124,7 +124,11 @@ pub fn parse_measure(s: &str, default: Unit) -> Option { "px" => 0.75, _ => return None, }; - Some(v * k) + // `v` is finite, but scaling it can still overflow to infinity (a + // near-`f32::MAX` value in inches, centimetres, picas or lines); reject + // those instead of returning an infinite measurement (#1359). + let pt = v * k; + if pt.is_finite() { Some(pt) } else { None } } /// A point in page space (points, y down). @@ -326,6 +330,32 @@ mod tests { assert_eq!(parse_measure("1,2,3", Unit::Inches), None); } + #[test] + fn parse_measure_rejects_overflow_to_infinity() { + // Finite inputs whose scaled result overflows `f32` must be rejected, + // never returned as an infinite measurement (#1359). + let e38 = "100000000000000000000000000000000000000"; // 1e38, finite + for s in [ + format!("{e38}\""), + format!("{e38} in"), + format!("{e38} cm"), + format!("{e38} pi"), + format!("{e38} li"), + format!("-{e38} in"), + e38.to_string(), // bare number in the default unit (inches below) + ] { + assert_eq!(parse_measure(&s, Unit::Inches), None, "input: {s}"); + } + // Millimetres scale by ~2.83, so 2e38 is needed to overflow. + let two_e38 = "200000000000000000000000000000000000000"; + assert_eq!(parse_measure(&format!("{two_e38} mm"), Unit::Inches), None); + // A large result that stays finite is still accepted, and points + // scale by 1, so a finite input in points stays finite. + let big = parse_measure("1000000000000000000000000000000 in", Unit::Points).unwrap(); + assert!(big.is_finite() && big > 0.0); + assert!(parse_measure(&format!("{e38} pt"), Unit::Inches).unwrap().is_finite()); + } + #[test] fn formats_measurements() { assert_eq!(Unit::Inches.format(72.0), "1\""); From be93f3b82da4b9e902b2044ff68033ddd9545496 Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Wed, 14 Oct 2026 19:31:21 +0200 Subject: [PATCH 003/336] Tables: a table with its own width is not squeezed to the margins (#1721) --- crates/layout/src/table.rs | 3 ++- crates/layout/src/tests.rs | 23 +++++++++++++++++++++++ docs/layout-parity.md | 3 ++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/crates/layout/src/table.rs b/crates/layout/src/table.rs index 2a3df74c83..0997474694 100644 --- a/crates/layout/src/table.rs +++ b/crates/layout/src/table.rs @@ -350,7 +350,8 @@ pub fn layout_table(ctx: &mut Ctx, story: StoryRef, t: &Table, path: &[u32], ava grid.iter_mut().for_each(|w| *w *= k); total = tw; } - if total > avail * 1.5 && !t.props.fixed { + // A table with its own width keeps it, however far it runs past the margins (as in Word). + if target.is_none() && total > avail * 1.5 && !t.props.fixed { let k = avail / total.max(1.0); grid.iter_mut().for_each(|w| *w *= k); total = avail; diff --git a/crates/layout/src/tests.rs b/crates/layout/src/tests.rs index 7baaad3705..09c9c31378 100644 --- a/crates/layout/src/tests.rs +++ b/crates/layout/src/tests.rs @@ -5741,3 +5741,26 @@ fn space_after_stays_clear_before_a_numbered_paragraph_without_a_label() { wordcraft_doc::resolve::AUTO_SPACING, ); } + +/// A table with its own width keeps it when it is over 1.5 times the text width (#1721); one +/// without is still squeezed to the margins. +#[test] +fn table_with_its_own_width_is_not_squeezed_to_the_margins() { + let text = |s: &str| wordcraft_doc::para_block(wordcraft_doc::Paragraph::with_text(s, Default::default())); + let second_column_x = |own: Option| { + let mut t = Table::new(1, 3, 936.0); + t.props.width = own; + t.rows[0].cells[1].blocks = vec![text("Address")]; + let mut d = Document::from_text("After"); + d.insert_block(StoryRef::Body, &Path::top(0), wordcraft_doc::Block::Table(t)).unwrap(); + let l = lay(&d); + let [(x, _, _)] = para_at(&l, &[0, 0, 1, 0])[..] else { panic!("second cell missing") }; + x + }; + // 936pt (twice the 468pt of text) of its own: the columns stay 312pt wide, past the margin. + let x = second_column_x(Some(936.0)); + assert!(x > 72.0 + 300.0, "the second column starts at {x}, squeezed"); + // The same columns with an automatic table width are squeezed to 156pt each. + let x = second_column_x(None); + assert!(x < 72.0 + 200.0, "the second column starts at {x}, not squeezed"); +} diff --git a/docs/layout-parity.md b/docs/layout-parity.md index e02d4908d8..1b6f0c7e20 100644 --- a/docs/layout-parity.md +++ b/docs/layout-parity.md @@ -41,7 +41,7 @@ font can't match Word's metrics. | Body top below a tall header, footer pushes body up | ✅ | #138 | — | | Footnotes at the page bottom, endnotes at the end, note numbering | 🟡 | A long note continues onto the next page at a line boundary (its first line stays with the reference), first in that page's note area under a full-width continuation separator (#352); the document's own separator, continuation separator and continuation notice are drawn (the notice under a note that continues; endnotes use the document's separator but don't continue onto a page of their own); each section's note options: footnotes below the text, endnotes at the end of each section, start at, numbering restarted each section or each page (counted from where the notes land, at most three passes) (#385) | 1–2 | | Tables: row heights (at least/exact), rows split across pages, header rows repeat, Can't Split | ✅ | 2026-10-06, #138; every cell in a row gets the row's largest top and bottom margin; an exact height covers the top border, top margin and text, the bottom margin comes on top, and an exact row never splits; a row split across pages keeps its cells' bottom margin, border and last space after on the first page, widow/orphan control applies in its cells, each cell continues at the top of the next page and an at-least row keeps its minimum (`layout::tests::a_row_shares_its_largest_top_and_bottom_margins`, `…_a_split_row_keeps_its_bottom_margin_and_widow_control`, `…_split_row_cells_each_continue_at_the_top`) | — | -| Tables: autofit to contents/window, fixed widths, Word 2013 edge | 🟡 | #137; #44: AutoFit Contents measures each column's narrowest and widest text and shares the width like Word, once (not live as you type); cell preferred widths and spans still differ | 3–5 | +| Tables: autofit to contents/window, fixed widths, Word 2013 edge | 🟡 | #137; #44: AutoFit Contents measures each column's narrowest and widest text and shares the width like Word, once (not live as you type); a table with its own preferred width keeps it, however far past the margin, and only an automatic-width table over 1.5× the text width is squeezed to the margins (Word squeezes it as soon as it is wider) (#1721); cell preferred widths and spans still differ | 3–5 | | Floating tables (`w:tblpPr`) | 🟡 | #137; one placed on the page or margin that would cover the text before it and doesn't fit below it moves to the next page (#534); overlap rules untested | 2–3 | | Nested tables | 🟡 | render; polish missing | 2–4 | | Floating pictures/shapes placement (relative to page/margin/column/paragraph/line/character) | ✅ | #136 | — | @@ -84,6 +84,7 @@ font can't match Word's metrics. | 2026-10-12 | trivial | a list label moves with its first line when the paragraph is centred or end-aligned (#1159) | | 2026-10-13 | trivial | A paragraph ending with a page break keeps its space after on the old page with its mark: the next page starts with the next paragraph at the top (it started that much too low, and a page break before after it opened a blank page) | | 2026-10-13 | trivial | Table rows: every cell gets the row's largest top and bottom margin; exact rows add the bottom margin on top and never split; a split row keeps its cells' bottom margin, border and space after on the first page, applies widow control in its cells and continues each cell at the top of the next page | +| 2026-10-14 | trivial | A table with its own preferred width keeps it past the margin instead of being squeezed to the margins when over 1.5× the text width (#1721) | | 2026-10-13 | trivial | PAGE fields after a page break inside their paragraph, or on a page the paragraph runs onto, show that page's number (#1352) | | 2026-10-12 | trivial | Spaces, tabs and ideographic spaces don't count toward a line's height, as in Word (a line of only them takes the paragraph mark's) | | 2026-10-12 | trivial | A list label's font raises the first line's ascent but never its descent (Word keeps the text's descent below the baseline) | From 9e608e85aae6b5e2382ebfb39887c0d72bf87f6b Mon Sep 17 00:00:00 2001 From: rabis <340973947+rabis7575@users.noreply.github.com> Date: Wed, 14 Oct 2026 19:53:28 +0200 Subject: [PATCH 004/336] #1596: tables nest 24 deep at most; a paste that would go deeper keeps their text as paragraphs Pasting a table into its own cell doubled the nesting each time, and about a dozen pastes built a document that overflowed the stack when it was saved as JSON, copied, closed or replaced by a new one. Tables now nest at most 24 deep (wordcraft_doc::table::MAX_TABLE_DEPTH), the depth the .docx reader and writer already keep. Document::insert_fragment turns a table that would go deeper into its paragraphs, in document order; Document::insert_block refuses it, so Insert > Table in a cell 24 tables deep fails with a message and leaves the document as it was. The depth is measured without recursion. Word has no limit. Co-Authored-By: Claude Opus 5.5 --- ROADMAP.md | 4 +- crates/doc/src/edit.rs | 130 ++++++++++++++++++++++++++++++++++++- crates/doc/src/table.rs | 4 ++ crates/engine/src/tests.rs | 21 ++++++ docs/target-app-parity.md | 5 +- 5 files changed, 159 insertions(+), 5 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 68835cacf4..6b2f858b24 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **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-14 · **Change:** 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: 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-14 · **Change:** trivial (progress log: tables nest 24 deep at most, #1596; 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: 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. @@ -136,6 +136,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | Date | What landed | |---|---| +| 2026-10-14 | Tables nest 24 deep at most, the depth the .docx reader and writer keep (#1596): pasting a table into its own cell again and again no longer builds a document that crashes when it is saved as JSON, copied, closed or replaced by a new one. A table that would go deeper arrives as its paragraphs, text kept, and Insert › Table in a cell 24 tables deep says why it can't. Word has no limit | | 2026-10-13 | Rounded rectangles (#1476): a `roundRect` keeps the corner radius its file gives it (`adj`), on screen, in PDF and when saved; all were drawn with a 16% corner | | 2026-10-13 | PAGEREF in headers and footers (#1477): "Page X of Y" with Y a page reference to a bookmark at each letter's end shows each letter's own total instead of the result stored in the file | | 2026-10-13 | Replace All respects locked content controls with Track Changes on or off; it refuses the entire batch before editing when any match would change locked content or remove a control locked against deletion | @@ -317,6 +318,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | Date | Change | Summary | |---|---|---| +| 2026-10-14 | trivial | Progress log: tables nest 24 deep at most (#1596) | | 2026-10-13 | trivial | Progress log: rounded rectangle corners (#1476) | | 2026-10-13 | trivial | Progress log: PAGEREF in headers and footers (#1477) | | 2026-10-13 | trivial | Replace All checks content-control locks before editing; a refused batch leaves the document and undo history unchanged; existing deletion-lock rules reused, with tracked/untracked regression coverage | diff --git a/crates/doc/src/edit.rs b/crates/doc/src/edit.rs index 6c36f92951..a68f7712b4 100644 --- a/crates/doc/src/edit.rs +++ b/crates/doc/src/edit.rs @@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize}; use crate::para::InlineObject; use crate::props::{CharProps, ParaProps}; use crate::{Block, Blocks, DocError, Document, Paragraph, Part, PartKind, Path, Pos, Result, para_block}; +use crate::{Table, table::MAX_TABLE_DEPTH}; /// A copied piece of a document: blocks, where the first and last paragraphs may be partial. #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] @@ -58,6 +59,53 @@ fn order(a: &Pos, b: &Pos) -> (Pos, Pos) { if a <= b { (a.clone(), b.clone()) } else { (b.clone(), a.clone()) } } +/// The blocks in `t`'s cells, row by row. +fn cell_blocks(t: &Table) -> impl DoubleEndedIterator { + t.rows.iter().flat_map(|r| r.cells.iter()).flat_map(|c| c.blocks.iter()).map(|b| &**b) +} + +/// How many tables deep `t` goes, itself included: 1 when its cells hold no table (without +/// recursion: a pasted fragment may be deep). +fn table_height(t: &Table) -> usize { + let mut most = 1; + let mut stack: Vec<(&Block, usize)> = cell_blocks(t).map(|b| (b, 1)).collect(); + while let Some((b, d)) = stack.pop() { + if let Block::Table(inner) = b { + let d = d.saturating_add(1); + most = most.max(d); + stack.extend(cell_blocks(inner).map(|cb| (cb, d))); + } + } + most +} + +/// `b` as blocks nesting at most `room` tables deep: a table that would go deeper is replaced by +/// its paragraphs (tables in its cells too, document order), as the DOCX reader does. +fn fit_tables(b: Block, room: usize, out: &mut Vec) { + match b { + Block::Table(t) if room == 0 => { + let mut stack: Vec<&Block> = cell_blocks(&t).rev().collect(); + while let Some(cb) = stack.pop() { + match cb { + Block::Para(p) => out.push(Block::Para(p.clone())), + Block::Table(inner) => stack.extend(cell_blocks(inner).rev()), + } + } + } + Block::Table(mut t) if table_height(&t) > room => { + for cell in t.rows.iter_mut().flat_map(|r| r.cells.iter_mut()) { + let mut kept = Vec::new(); + for cb in std::mem::take(&mut cell.blocks) { + fit_tables(Arc::unwrap_or_clone(cb), room.saturating_sub(1), &mut kept); + } + cell.blocks = kept.into_iter().map(Arc::new).collect(); + } + out.push(Block::Table(t)); + } + b => out.push(b), + } +} + /// Nesting limit when following text boxes inside text boxes. const MAX_BOX_DEPTH: usize = 8; /// Most text box stories one paste may create (hostile documents can fan out). @@ -274,8 +322,14 @@ impl Document { Ok(Pos { story: pos.story, path: pos.path.with_last(i + 1), off: 0 }) } - /// Insert a block before the block at `path` (same container). + /// Insert a block before the block at `path` (same container). A table that would nest + /// deeper than [`MAX_TABLE_DEPTH`] is refused. pub fn insert_block(&mut self, story: crate::StoryRef, path: &Path, block: Block) -> Result<()> { + if let Block::Table(t) = &block + && path.depth().saturating_add(table_height(t)) > MAX_TABLE_DEPTH + { + return Err(DocError::Invalid(format!("tables can't nest more than {MAX_TABLE_DEPTH} deep"))); + } let i = path.last(); let bl = self.container_mut(story, path)?; if i > bl.len() { @@ -563,7 +617,12 @@ impl Document { /// Insert a fragment at `pos`; returns the position after the pasted content. pub fn insert_fragment(&mut self, pos: &Pos, frag: &Fragment) -> Result { - let mut blocks = frag.blocks.clone(); + // Tables that would nest deeper than the limit here become their paragraphs (#1596). + let room = MAX_TABLE_DEPTH.saturating_sub(pos.path.depth()); + let mut blocks = Vec::with_capacity(frag.blocks.len()); + for b in frag.blocks.iter().cloned() { + fit_tables(b, room, &mut blocks); + } if blocks.is_empty() { return Ok(pos.clone()); } @@ -700,6 +759,73 @@ mod tests { assert!(d.plain_text(StoryRef::Body).starts_with("XYalpha")); } + /// The first paragraph of the deepest cell, following the first table at each level. + fn deepest(d: &Document) -> Path { + let (mut path, mut blocks) = (Vec::new(), &d.body); + while let Some((i, t)) = blocks.iter().enumerate().find_map(|(i, b)| if let Block::Table(t) = &**b { Some((i, t)) } else { None }) { + path.extend([i as u32, 0, 0]); + blocks = &t.rows[0].cells[0].blocks; + } + path.push(0); + Path(path) + } + + fn body_height(d: &Document) -> usize { + d.body.iter().filter_map(|b| if let Block::Table(t) = &**b { Some(table_height(t)) } else { None }).max().unwrap_or(0) + } + + /// How many times `c` is in `blocks`, the deepest cells too. + fn count(blocks: &[Arc], c: char) -> usize { + blocks + .iter() + .map(|b| match &**b { + Block::Para(p) => p.text.matches(c).count(), + Block::Table(t) => t.rows.iter().flat_map(|r| &r.cells).map(|cell| count(&cell.blocks, c)).sum(), + }) + .sum() + } + + #[test] + fn tables_nest_at_most_max_table_depth() { + let mut d = doc(&["x", ""]); + for _ in 0..MAX_TABLE_DEPTH { + let at = deepest(&d); + d.insert_block(StoryRef::Body, &at, Block::Table(Table::new(1, 1, 100.0))).unwrap(); + } + assert_eq!(body_height(&d), MAX_TABLE_DEPTH); + // One more in the deepest cell is refused and changes nothing; a paragraph is fine. + let (at, before) = (deepest(&d), d.clone()); + assert_eq!(at.depth(), MAX_TABLE_DEPTH); + assert!(d.insert_block(StoryRef::Body, &at, Block::Table(Table::new(1, 1, 100.0))).is_err()); + assert_eq!(d, before); + d.insert_block(StoryRef::Body, &at, Block::Para(Paragraph::new())).unwrap(); + // One level up, a table fits but a table holding a table doesn't. + let up = Path(at.0[..at.0.len() - 3].to_vec()); + let mut two = Table::new(1, 1, 100.0); + two.rows[0].cells[0].blocks.insert(0, Arc::new(Block::Table(Table::new(1, 1, 100.0)))); + assert!(d.insert_block(StoryRef::Body, &up, Block::Table(two)).is_err()); + d.insert_block(StoryRef::Body, &up, Block::Table(Table::new(1, 1, 100.0))).unwrap(); + assert_eq!(body_height(&d), MAX_TABLE_DEPTH); + } + + #[test] + fn pasting_a_table_into_its_own_cell_stops_at_max_table_depth() { + // #1596: copy everything and paste it into the deepest cell, again and again: each round + // doubled the nesting, until saving, copying or closing the document overflowed the stack. + let mut d = doc(&["x", ""]); + d.insert_block(StoryRef::Body, &Path::top(1), Block::Table(Table::new(1, 1, 100.0))).unwrap(); + d.insert_text(&Pos { story: StoryRef::Body, path: Path(vec![1, 0, 0, 0]), off: 0 }, "a", &CharProps::default()).unwrap(); + for _ in 0..8 { + let all = d.copy_range(&Pos::body(0, 0), &Pos::body(d.body.len() - 1, 0)); + let at = deepest(&d); + let off = d.para(StoryRef::Body, &at).map_or(0, Paragraph::len); + d.insert_fragment(&Pos { story: StoryRef::Body, path: at, off }, &all).unwrap(); + } + assert_eq!(body_height(&d), MAX_TABLE_DEPTH); + // Nothing is lost: every copy's text is there, below the limit as paragraphs. + assert_eq!((count(&d.body, 'x'), count(&d.body, 'a')), (256, 256)); + } + fn text_box(d: &mut Document, pos: &Pos, text: &str) -> u32 { let id = d.add_part(PartKind::TextBox, vec![para_block(Paragraph::with_text(text, CharProps::default()))]); d.insert_object(pos, box_shape(Some(id)), &CharProps::default()).unwrap(); diff --git a/crates/doc/src/table.rs b/crates/doc/src/table.rs index 104bf96b82..b412b47e1c 100644 --- a/crates/doc/src/table.rs +++ b/crates/doc/src/table.rs @@ -58,6 +58,10 @@ pub struct Table { /// Hard limits that keep hostile input from exhausting memory. pub const MAX_ROWS: usize = 32_767; pub const MAX_COLS: usize = 63; +/// Most tables nested one inside another: the depth the DOCX reader and writer keep. Pasting +/// and inserting stop there too, so no edit builds a document deep enough to overflow the stack +/// when it is saved, copied or dropped (#1596). +pub const MAX_TABLE_DEPTH: usize = 24; impl Table { /// A `rows × cols` table spanning `width` points, "Table Grid" style. diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index eec8ffffa0..c47ca1249b 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -1261,6 +1261,27 @@ fn select_extend_from_a_table_paragraph_selects_the_document() { assert_eq!(b, s.doc.end_of(StoryRef::Body)); } +/// #1596: tables nest 24 deep at most, the depth the DOCX reader and writer keep. Insert › Table +/// in the deepest cell says so; a table pasted there brings its text as paragraphs. +#[test] +fn tables_nest_at_most_24_deep() { + use wordcraft_doc::table::MAX_TABLE_DEPTH; + let mut s = s(); + for _ in 0..MAX_TABLE_DEPTH { + run(&mut s, "insert.table", json!({"rows": 1, "cols": 1})); + } + assert_eq!(s.sel.focus.path.depth(), MAX_TABLE_DEPTH); + let before = s.doc.clone(); + let e = s.run("insert.table", &json!({"rows": 1, "cols": 1})).unwrap_err(); + assert!(e.to_string().contains("24 deep"), "{e}"); + assert_eq!(s.doc, before); + run(&mut s, "edit.paste", json!({"text": "b", "html": "
b
"})); + let cell = s.sel.focus.path.clone(); + let blocks: Vec<_> = (0..).map_while(|i| s.doc.block(StoryRef::Body, &cell.with_last(i))).collect(); + assert!(blocks.iter().all(|b| b.as_table().is_none()), "no table below the limit"); + assert!(blocks.iter().any(|b| b.as_para().is_some_and(|p| p.text.contains('b'))), "the pasted text is there"); +} + /// AutoFit Contents (#44) measures the text: a column of short words narrows, a column with a /// long sentence widens (wrapping within the page), and the widths are written to the grid. #[test] diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index baa2d3c974..80b40b91bf 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1,6 +1,6 @@ # WordCraft parity with Microsoft Word -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** 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-11 · **Change:** trivial (tables nest 24 deep at most, #1596; 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 @@ -60,7 +60,7 @@ full parity for the area. | Character and paragraph formatting | 11 | 82% | 85% | 8–12 | Nearly every property, Font/Paragraph dialogs, character border, Phonetic Guide (#288), Enclose Characters (#297). Missing: Asian Typography, kerning/ligature/stylistic-set options in the Font dialog's Advanced tab. | | Styles and themes | 8 | 74% | 75% | 8–12 | Gallery, pane, create/modify, style sets, themes. Style Inspector landed (#236). Missing: Manage Styles (#270 open), style separators, linked-style edge cases; Word ships 11 Quick Style sets plus dozens of themes, we ship our own smaller set. | | Lists | 6 | 78% | 75% | 5–8 | Bullets, numbering, multilevel, restart, `w:lvlOverride` levels (#134), Word's list AutoFormat and Enter/Backspace behaviour. Missing: Define New Multilevel List dialog depth, list styles, legal numbering edge cases. | -| Tables | 8 | 76% | 72% | 10–16 | Merge/split, styles (cell text takes style formatting, #104), custom table styles saved in the .docx (#256), Word 2013 edge, floating tables, rows split across pages, sort, formula, cell gridlines (#243), Draw Table and Eraser (#303). Missing: splitting or erasing around vertically merged cells, nested-table polish. Dragging column and row borders and the resize handle on the page (#217) and the column and row markers on the rulers (#700) landed, with Word's Ctrl, Alt, left-edge and selected-cells drags (#711), and so did selecting columns, rows and cells, the "+" insert buttons, the move handle and double-click AutoFit on the page, Select Column, and drags and Shift+arrows over cells selecting whole cells; right-clicking a cell offers the table's commands, following the selection as in Word (Delete Cells, Rows, Columns or Table, Merge and Split Cells, Distribute, AutoFit, Text Direction, Insert Caption…, Table Properties…); cell text direction landed (#245). | +| Tables | 8 | 76% | 72% | 10–16 | Merge/split, styles (cell text takes style formatting, #104), custom table styles saved in the .docx (#256), Word 2013 edge, floating tables, rows split across pages, sort, formula, cell gridlines (#243), Draw Table and Eraser (#303), tables nested 24 deep at most, as the .docx reader and writer keep them (#1596; Word has no limit). Missing: splitting or erasing around vertically merged cells, nested-table polish. Dragging column and row borders and the resize handle on the page (#217) and the column and row markers on the rulers (#700) landed, with Word's Ctrl, Alt, left-edge and selected-cells drags (#711), and so did selecting columns, rows and cells, the "+" insert buttons, the move handle and double-click AutoFit on the page, Select Column, and drags and Shift+arrows over cells selecting whole cells; right-clicking a cell offers the table's commands, following the selection as in Word (Delete Cells, Rows, Columns or Table, Merge and Split Cells, Distribute, AutoFit, Text Direction, Insert Caption…, Table Properties…); cell text direction landed (#245). | | 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). | @@ -251,6 +251,7 @@ can be used without committing Word output. | Date | Change | Summary | |---|---|---| +| 2026-10-14 | trivial | Tables: nesting stops at 24 levels, the depth .docx keeps (#1596); Tables row updated, no percentage change | | 2026-10-12 | minor | Italian proofing: it-IT spelling, accent-first suggestions and conservative grammar; Proofing row updated, 42% → 44% | | 2026-10-11 | trivial | Tables: Ctrl, Alt, left-edge and selected-cells border drags (#711); Tables row updated, no percentage change | | 2026-10-11 | trivial | Tables: column and row markers on the rulers (#700); Tables row updated, no percentage change | From 2d2e064ac69d4f9cc96b24a0b9aaf40b88476d41 Mon Sep 17 00:00:00 2001 From: midasmatterhorn <248507632+midasmatterhorn@users.noreply.github.com> Date: Wed, 14 Oct 2026 22:55:50 +0300 Subject: [PATCH 005/336] engine: read_file reads only a bounded regular file, so a pipe or device path can't hang or exhaust memory --- crates/engine/src/io.rs | 9 +++++++-- crates/engine/src/tests.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/crates/engine/src/io.rs b/crates/engine/src/io.rs index 2c4df20f16..959e63300a 100644 --- a/crates/engine/src/io.rs +++ b/crates/engine/src/io.rs @@ -421,12 +421,17 @@ fn read_regular_file(path: &std::path::Path, limit: u64) -> Option> { Some(out) } +/// Largest file [`read_file`] reads whole — the same 2 GB ceiling [`open_path_with`] gives a +/// document. +pub const MAX_READ_FILE: u64 = 2 << 30; + /// The bytes of the file at `path`, for commands that take a path (pictures, mail-merge data). /// Every such read goes through here so the automation read root applies ([`crate::file_access`]). +/// Only a regular file is read, and at most [`MAX_READ_FILE`] bytes of it: a named pipe blocks +/// when opened and a device like `/dev/zero` never reaches end of file. #[cfg(not(target_arch = "wasm32"))] pub fn read_file(path: &str) -> Result, String> { - crate::file_access::check_read(path)?; - std::fs::read(path).map_err(|e| format!("{path}: {e}")) + read_file_capped(path, MAX_READ_FILE, "file") } #[cfg(target_arch = "wasm32")] diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 19b269cf16..010a258edd 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -2809,6 +2809,37 @@ fn html_picture_that_is_a_named_pipe_is_skipped_without_blocking() { assert!(doc.plain_text(StoryRef::Body).contains("Pipe")); } +/// #98's rule for HTML pictures, for `insert.picture`'s own paths: opening a named pipe blocks +/// until a writer appears, and a device like `/dev/zero` never reaches end of file, so a picture +/// path that is not a regular file must be refused, not opened. +#[cfg(unix)] +#[test] +fn insert_picture_from_a_named_pipe_is_refused_without_blocking() { + let dir = scratch_dir("insert-img-fifo"); + let fifo = dir.join("pipe.png"); + let made = std::process::Command::new("mkfifo").arg(&fifo).status(); + if !made.is_ok_and(|s| s.success()) { + let _ = std::fs::remove_dir_all(&dir); + eprintln!("mkfifo unavailable; skipping"); + return; + } + let (tx, rx) = std::sync::mpsc::channel(); + let path = fifo.to_string_lossy().into_owned(); + std::thread::spawn(move || { + let _ = tx.send(crate::io::read_file(&path)); + }); + let got = rx.recv_timeout(std::time::Duration::from_secs(10)); + if got.is_err() { + // Unblock the stuck reader so the thread ends, then fail. + let _ = std::fs::OpenOptions::new().write(true).open(&fifo); + } + let _ = std::fs::remove_dir_all(&dir); + assert!(got.expect("reading a picture path blocked on a named pipe").is_err(), "a pipe is not a file to read"); + if std::path::Path::new("/dev/zero").exists() { + assert!(crate::io::read_file("/dev/zero").is_err(), "a device is not a file to read"); + } +} + #[cfg(not(target_arch = "wasm32"))] #[test] fn html_picture_size_limit_holds_whatever_length_the_file_reports() { From 5b84776c05de59ec201f9dd25bab56114fa851f0 Mon Sep 17 00:00:00 2001 From: midasmatterhorn <248507632+midasmatterhorn@users.noreply.github.com> Date: Wed, 14 Oct 2026 22:58:35 +0300 Subject: [PATCH 006/336] formats: the Markdown code fence outgrows any backtick run in its content, so a round trip keeps it --- crates/formats/src/markdown.rs | 4 +++- crates/formats/src/tests.rs | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/crates/formats/src/markdown.rs b/crates/formats/src/markdown.rs index 29afb1aff9..2d47821805 100644 --- a/crates/formats/src/markdown.rs +++ b/crates/formats/src/markdown.rs @@ -1294,7 +1294,9 @@ fn write_blocks(blocks: &[FBlock], out: &mut Vec) { i += 1; } let longest = lines.iter().map(|l| l.chars().filter(|c| *c == '`').count()).max().unwrap_or(0); - let fence = "`".repeat(3.max(longest + 1).min(64)); + // The fence must be longer than the longest run in the content, or a content + // line is read back as the closing fence (the `.min(64)` cap did exactly that). + let fence = "`".repeat(3.max(longest + 1)); out.push(fence.clone()); out.extend(lines); out.push(fence); diff --git a/crates/formats/src/tests.rs b/crates/formats/src/tests.rs index d22e5f6952..1f680eb97d 100644 --- a/crates/formats/src/tests.rs +++ b/crates/formats/src/tests.rs @@ -214,6 +214,20 @@ fn markdown_round_trip() { check_round_trip("md", true); } +/// A code line of backticks must not be mistaken for the closing fence when the file comes +/// back: the writer's fence has to be one backtick longer than any content line. +#[test] +fn markdown_code_fence_outgrows_its_content() { + let line = "`".repeat(100); + let doc = import("md", format!(" {line}\n").as_bytes()).unwrap().unwrap(); + assert_eq!(doc.plain_text(Default::default()).trim(), line); + let out = export("md", &doc).unwrap().unwrap(); + let text = String::from_utf8(out).unwrap(); + assert!(text.contains(&"`".repeat(101)), "the fence is longer than the content: {text}"); + let back = import("md", text.as_bytes()).unwrap().unwrap(); + assert_eq!(back.plain_text(Default::default()).trim(), line, "the code line survives a round trip"); +} + #[test] fn html_round_trip() { check_round_trip("html", true); From 322454a8cb218c54596ed725530b9aa8e239d32a Mon Sep 17 00:00:00 2001 From: asmodeus-oos Date: Tue, 13 Oct 2026 16:18:57 +0300 Subject: [PATCH 007/336] test: Farsi Yeh joins correctly (covers #824 shaping core) --- crates/layout/src/tests_bidi.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/layout/src/tests_bidi.rs b/crates/layout/src/tests_bidi.rs index 9fa922974c..780f433dfe 100644 --- a/crates/layout/src/tests_bidi.rs +++ b/crates/layout/src/tests_bidi.rs @@ -335,6 +335,13 @@ fn persian_letters_join_and_brackets_mirror() { // ZWNJ (نیم‌فاصله) breaks the join: "می‌خواهم" keeps می separate. let with_zwnj = cluster_glyphs(&wordcraft_fonts::shape_run(&face, "ب\u{200C}ب", &[], |c| c, true)); assert_eq!(with_zwnj.first(), isolated.first(), "a letter before ZWNJ takes its isolated form"); + // Farsi Yeh (ی, U+06CC) joins like any letter (#824). + let mi = wordcraft_fonts::shape_run(&face, "می", &[], |c| c, true); + let mi_cl = cluster_glyphs(&mi); + assert_eq!(mi_cl.len(), 2, "meem + Farsi Yeh: {mi_cl:?}"); + assert!(mi.iter().all(|g| g.gid != 0), "the font has Farsi Yeh"); + let meem_alone = cluster_glyphs(&wordcraft_fonts::shape_run(&face, "م", &[], |c| c, true)); + assert_ne!(mi_cl[0], meem_alone[0], "meem takes its initial form before Yeh: {mi_cl:?} vs {meem_alone:?}"); // In right-to-left text, "(" is drawn with the mirrored glyph. let open = wordcraft_fonts::shape_run(&face, "(", &[], |c| c, true); let close = wordcraft_fonts::shape_run(&face, ")", &[], |c| c, false); From 71d327163e52633cd4eb4e269fda88008d59a549 Mon Sep 17 00:00:00 2001 From: asmodeus-oos Date: Tue, 13 Oct 2026 15:28:25 +0300 Subject: [PATCH 008/336] test: end-of-document citation stays closed through typing and reload (probes #898, not reproducible) --- crates/zotero/src/tests.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/crates/zotero/src/tests.rs b/crates/zotero/src/tests.rs index 4a8d5810e4..394480719f 100644 --- a/crates/zotero/src/tests.rs +++ b/crates/zotero/src/tests.rs @@ -546,3 +546,25 @@ fn a_new_field_is_never_empty_so_zotero_keeps_a_new_bibliography() { z.ok("Document_complete", json!([1])); assert_eq!(z.text(), "Text. Doe, J. (2020)."); } + +#[test] +fn citation_at_document_end_stays_closed() { + // #898: citing at the very end, typing after it, saving and reloading must all keep + // the field's end marker in place. + let mut z = Z::new("Hello", 5); + z.begin(); + let id = z.cite(0, r"{\rtf (Doe, {\i{}2020})}"); + z.ok("Document_complete", json!([1])); + // Type right after the citation, at the very end of the document. + z.s.run("text.insert", &json!({"text": "!"}).into()).unwrap(); + assert_eq!(z.text(), "Hello (Doe, 2020)!"); + let ranges = z.s.doc.field_ranges(StoryRef::Body); + assert_eq!(ranges.len(), 1); + assert!(z.s.sel.focus.off > ranges[0].end.off, "typed text lands outside the field"); + let f = z.ok("Document_getFields", json!([1, "ReferenceMark"])); + assert_eq!(f, json!([[id], [CITE], [0]])); + // Save and reload: the end marker must survive the round trip. + let bytes = wordcraft_engine::io::save_bytes("probe.docx", &z.s.doc).unwrap(); + let back = wordcraft_engine::io::open_bytes("probe.docx", &bytes).unwrap(); + assert_eq!(back.field_ranges(StoryRef::Body).len(), 1); +} From 3fb47b910cf6e6480c4ad71f7d9a651259c8b56a Mon Sep 17 00:00:00 2001 From: asmodeus-oos Date: Wed, 14 Oct 2026 23:18:33 +0300 Subject: [PATCH 009/336] test: txt export keeps list labels (verifies #1080) --- crates/formats/src/tests.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/crates/formats/src/tests.rs b/crates/formats/src/tests.rs index d22e5f6952..663e2d88f3 100644 --- a/crates/formats/src/tests.rs +++ b/crates/formats/src/tests.rs @@ -303,6 +303,21 @@ fn txt_export_writes_symbol_font_text_as_unicode() { assert_eq!(String::from_utf8(out).unwrap(), "Done \u{263A}\u{2713}"); } +/// #1080: `txt` export (and so `wordcraft-cli text`) keeps list labels, so a list item reads +/// apart from an ordinary paragraph. +#[test] +fn txt_export_keeps_list_labels() { + let mut d = Document::new(); + let bul = d.numbering.add_list(wordcraft_doc::ListKind::Bullet); + let mut p = Paragraph::with_text("alpha", CharProps::default()); + p.props.numbering = Some(wordcraft_doc::props::NumRef { num: bul, level: 0 }); + d.body = vec![para_block(p), para_block(Paragraph::with_text("plain", CharProps::default()))]; + let out = String::from_utf8(export("txt", &d).unwrap().unwrap()).unwrap(); + let mut lines = out.split("\r\n"); + assert!(lines.next().is_some_and(|l| l.contains("alpha") && !l.starts_with("alpha")), "{out:?}"); + assert_eq!(lines.next(), Some("plain")); +} + /// #569: a file with an extension we don't know opens as text when it is text (a playlist), and /// a binary file is told apart. #[test] From a643d4bec5715caabc9b1dc14d33271d262daadb Mon Sep 17 00:00:00 2001 From: midasmatterhorn <248507632+midasmatterhorn@users.noreply.github.com> Date: Thu, 15 Oct 2026 00:03:04 +0300 Subject: [PATCH 010/336] engine: Check spelling as you type is saved with the proofing options, so it survives a restart --- crates/engine/src/cmd/options.rs | 1 + crates/engine/src/cmd/review.rs | 1 + crates/engine/src/session.rs | 4 ++++ crates/ui-egui/src/lib.rs | 2 ++ crates/ui-egui/src/options.rs | 14 ++++++++++++++ 5 files changed, 22 insertions(+) diff --git a/crates/engine/src/cmd/options.rs b/crates/engine/src/cmd/options.rs index a829f4c076..fea758be42 100644 --- a/crates/engine/src/cmd/options.rs +++ b/crates/engine/src/cmd/options.rs @@ -176,6 +176,7 @@ fn options(s: &mut Session, v: &Value) -> CmdResult { } if let Some(b) = check_spelling { s.view.proofing = b; + s.prefs.check_spelling = b; } for w in &add { wordcraft_proof::add_word(w); diff --git a/crates/engine/src/cmd/review.rs b/crates/engine/src/cmd/review.rs index 2ba4a79d65..9316b60147 100644 --- a/crates/engine/src/cmd/review.rs +++ b/crates/engine/src/cmd/review.rs @@ -104,6 +104,7 @@ pub fn specs() -> Vec { .params(r#"{"text": string}"#), CommandSpec::new("review.proofing", "Check Spelling as You Type", "File › Options › Proofing", |s, v| { s.view.proofing = p::bool(v, "value").unwrap_or(!s.view.proofing); + s.prefs.check_spelling = s.view.proofing; Ok(json!({"value": s.view.proofing})) }) .pure(), diff --git a/crates/engine/src/session.rs b/crates/engine/src/session.rs index 5d6aacaaec..1577b6ecd2 100644 --- a/crates/engine/src/session.rs +++ b/crates/engine/src/session.rs @@ -516,6 +516,9 @@ pub struct Prefs { pub marks: wordcraft_layout::display::FormattingMarks, /// Mark grammar errors as you type (with Check spelling as you type on). pub mark_grammar: bool, + /// Check spelling as you type (File › Options › Proofing). The live value is + /// `ViewState::proofing`; this is the copy that is saved and restored (#1792). + pub check_spelling: bool, pub ignore_uppercase: bool, pub ignore_numbers: bool, pub ignore_internet: bool, @@ -539,6 +542,7 @@ impl Default for Prefs { dictionaries_folder: None, marks: Default::default(), mark_grammar: true, + check_spelling: true, ignore_uppercase: true, ignore_numbers: true, ignore_internet: true, diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 6f030d3019..4ce89ff208 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -692,6 +692,8 @@ impl WordApp { } } self.session.prefs = std::mem::take(&mut self.ui.editing); + // Check spelling as you type follows the saved preference (#1792). + self.session.view.proofing = self.session.prefs.check_spelling; // The saved dictionaries folder (#1063) is searched from the start. if let Some(f) = self.session.prefs.dictionaries_folder.as_deref() { wordcraft_engine::cmd::options::use_dictionaries_folder(Some(f)); diff --git a/crates/ui-egui/src/options.rs b/crates/ui-egui/src/options.rs index 635757eca0..925d3350c2 100644 --- a/crates/ui-egui/src/options.rs +++ b/crates/ui-egui/src/options.rs @@ -772,6 +772,20 @@ mod tests { assert_eq!(wordcraft_engine::proof::hunspell::search_dirs(None).first(), Some(&dir), "searched first"); } + /// #1792: Check spelling as you type is saved with the other proofing choices, so after a + /// restart the box comes back the way the user left it. + #[test] + fn check_spelling_survives_a_restart() { + let mut a = app(); + a.run("file.options", json!({"checkSpelling": false})).unwrap(); + assert!(!a.session.view.proofing, "off right away"); + let saved = serde_json::to_value(a.prefs()).unwrap(); + assert_eq!(saved["editing"]["checkSpelling"], false, "saved in ui.json"); + let mut b = app(); + b.apply_prefs(UiState::from_json(&serde_json::to_vec(&saved).unwrap()).unwrap()); + assert!(!b.session.view.proofing, "spelling stays off after a restart"); + } + /// Settings saved before #838 kept the unit under `editing.units` (default inches): a unit /// the user chose carries over to `measurementUnits`; inches, junk or a newer value don't. #[test] From 16adb80b66f24045960999ac6c70677c9bd3f4c7 Mon Sep 17 00:00:00 2001 From: midasmatterhorn <248507632+midasmatterhorn@users.noreply.github.com> Date: Thu, 15 Oct 2026 00:11:03 +0300 Subject: [PATCH 011/336] .doc import: symbol runs read their font and character, and paragraph borders are read --- crates/docbin/src/fmt.rs | 87 +++++++++++++++++++++++++++++++++++++- crates/docbin/src/lib.rs | 29 +++++++++++++ crates/docbin/src/tests.rs | 48 ++++++++++++++++++++- 3 files changed, 162 insertions(+), 2 deletions(-) diff --git a/crates/docbin/src/fmt.rs b/crates/docbin/src/fmt.rs index 6c4232f2c1..95eecf43af 100644 --- a/crates/docbin/src/fmt.rs +++ b/crates/docbin/src/fmt.rs @@ -1,7 +1,7 @@ //! Sprms → model properties, and style-chain resolution. use wordcraft_doc::Rgb; -use wordcraft_doc::props::{Align, CharProps, Highlight, LineSpacing, ParaProps, TabAlign, TabLeader, TabStop, TextColor, Underline, VertAlign}; +use wordcraft_doc::props::{Align, Border, Borders, BorderStyle, CharProps, Highlight, LineSpacing, ParaProps, TabAlign, TabLeader, TabStop, TextColor, Underline, VertAlign}; use wordcraft_doc::styles::{Style, StyleKind, StyleSheet}; use crate::sprm::{self, Prl, Toggle}; @@ -42,6 +42,18 @@ const P_KEEP: u16 = 0x2405; // keep lines together const P_KEEP_FOLLOW: u16 = 0x2406; // keep with next const P_PAGE_BREAK_BEFORE: u16 = 0x2407; const P_OUTLVL: u16 = 0x2640; +// Paragraph borders ([MS-DOC] §2.6.2). The 80 forms carry a Brc80 (palette colour), the +// newer ones a BrcOperand: cb(1) + Brc(8, with a full COLORREF). +const P_BRC_TOP80: u16 = 0x6424; +const P_BRC_LEFT80: u16 = 0x6425; +const P_BRC_BOTTOM80: u16 = 0x6426; +const P_BRC_RIGHT80: u16 = 0x6427; +const P_BRC_BETWEEN80: u16 = 0x6428; +const P_BRC_TOP: u16 = 0xC64E; +const P_BRC_LEFT: u16 = 0xC64F; +const P_BRC_BOTTOM: u16 = 0xC650; +const P_BRC_RIGHT: u16 = 0xC651; +const P_BRC_BETWEEN: u16 = 0xC652; /// Word's 16-colour `Ico` palette ([MS-DOC] §2.9.135), 0 = automatic. const ICO: [Rgb; 17] = [ @@ -93,6 +105,69 @@ pub(crate) fn ico(i: u8) -> Option { ICO.get(i as usize).copied() } +/// The model border style a `brcType` stands for ([MS-DOC] §2.9.22 BrcType). Types the model +/// doesn't carry (the gap variants, 3-D, inset/outset) draw as single lines, as the DOCX +/// reader maps them. +fn brc_style(t: u8) -> BorderStyle { + match t { + 0x00 => BorderStyle::None, + 0x03 => BorderStyle::Double, + 0x06 => BorderStyle::Dotted, + 0x07 | 0x16 => BorderStyle::Dashed, + 0x08 | 0x09 => BorderStyle::DotDash, + 0x0A => BorderStyle::Triple, + 0x14 | 0x15 => BorderStyle::Wave, + _ => BorderStyle::Single, + } +} + +/// The border width in points: `dptLineWidth` in 1/8-point steps (values below 2 count as 2; +/// 1-point steps for page borders, which never reach a paragraph property). +fn brc_width(t: u8, dpt: u8) -> f32 { + if t >= 0x40 { f32::from(dpt) } else { f32::from(dpt.max(2)) / 8.0 } +} + +/// One border from a Brc80 operand ([MS-DOC] §2.9.18). All bits set means "no border"; a +/// truncated operand is ignored. +fn brc80(op: &[u8]) -> Option { + let [dpt, ty, ico_index, tail, ..] = op else { return None }; + if [*dpt, *ty, *ico_index, *tail] == [0xFF; 4] { + return Some(Border::default()); + } + Some(Border { style: brc_style(*ty), width: brc_width(*ty, *dpt), color: ico(*ico_index), space: f32::from(tail & 0x1F) }) +} + +/// One border from a `BrcOperand` ([MS-DOC] §2.9.21): cb(1, MUST be 8) + Brc(8). +fn brc(op: &[u8]) -> Option { + if op.first() != Some(&8) { + return None; + } + let b = op.get(1..9)?; + if b.iter().all(|x| *x == 0xFF) { + return Some(Border::default()); + } + let color = match b.get(..4) { + Some([0, 0, 0, 0]) => None, // automatic + Some(px) => Some(Rgb(px[0], px[1], px[2])), + None => None, + }; + Some(Border { style: brc_style(b[5]), width: brc_width(b[5], b[4]), color, space: f32::from(b[6] & 0x1F) }) +} + +/// Put a decoded border on the side its sprm names (`between` = the border between this +/// paragraph and the next). +fn set_border(p: &mut ParaProps, op: u16, b: Border) { + let borders = p.borders.get_or_insert_with(Borders::default); + let slot = match op { + P_BRC_TOP80 | P_BRC_TOP => &mut borders.top, + P_BRC_LEFT80 | P_BRC_LEFT => &mut borders.left, + P_BRC_BOTTOM80 | P_BRC_BOTTOM => &mut borders.bottom, + P_BRC_RIGHT80 | P_BRC_RIGHT => &mut borders.right, + _ => &mut borders.between, + }; + *slot = Some(b); +} + fn u16_of(p: &Prl) -> Option { match p.operand { [b0, b1, ..] => Some(u16::from_le_bytes([*b0, *b1])), @@ -324,6 +399,16 @@ pub(crate) fn apply_para(p: &mut ParaProps, prl: &Prl) { p.outline_level = Some(l.min(8)); } } + P_BRC_TOP80 | P_BRC_LEFT80 | P_BRC_BOTTOM80 | P_BRC_RIGHT80 | P_BRC_BETWEEN80 => { + if let Some(b) = brc80(prl.operand) { + set_border(p, prl.op, b); + } + } + P_BRC_TOP | P_BRC_LEFT | P_BRC_BOTTOM | P_BRC_RIGHT | P_BRC_BETWEEN => { + if let Some(b) = brc(prl.operand) { + set_border(p, prl.op, b); + } + } _ => {} } } diff --git a/crates/docbin/src/lib.rs b/crates/docbin/src/lib.rs index 9ff4386204..46dc57c482 100644 --- a/crates/docbin/src/lib.rs +++ b/crates/docbin/src/lib.rs @@ -45,6 +45,9 @@ const MAX_STREAM: u64 = 1 << 30; const MAX_PARTS: usize = 50_000; /// `sprmCPicLocation`: the fc of a picture in the Data stream. const C_PIC_LOCATION: u16 = 0x6A03; +/// `sprmCSymbol`: the record font and character of a symbol character ([MS-DOC] §2.6.1); the +/// text stream holds the placeholder U+0028 at its position. +const C_SYMBOL: u16 = 0x6A09; /// `sprmPIlvl` / `sprmPIlfo`: the paragraph's list level and list. const P_ILVL: u16 = 0x260A; const P_ILFO: u16 = 0x460B; @@ -566,6 +569,19 @@ fn pic_location(chpx: &[u8]) -> Option { None } +/// `sprmCSymbol` from a CHPX grpprl: the font table index and the character code of a symbol +/// character ([MS-DOC] §2.6.1, CSymbolOperand). +fn symbol_operand(chpx: &[u8]) -> Option<(u16, u16)> { + for prl in sprm::iter(chpx) { + if prl.op == C_SYMBOL + && let [f0, f1, c0, c1, ..] = prl.operand + { + return Some((u16::from_le_bytes([*f0, *f1]), u16::from_le_bytes([*c0, *c1]))); + } + } + None +} + /// `sprmPIlfo` operand (a signed 16-bit value, [MS-DOC] §2.6.2) → the num id, if the /// paragraph is in a list. 0x0001-0x07FE is a 1-based LFO index; 0xF802-0xFFFF (-2046..=-1) /// is the negation of one and keeps the paragraph's own indents, which we do by leaving the @@ -595,6 +611,19 @@ fn push_formatted(pb: &mut ParaBuild, cp: u32, c: char, ctx: &WalkCtx, field: Op }; let mut props = char_props(grpprl, ctx.fonts, base); apply_prm(&mut props, ctx.pieces.piece_prm(cp), ctx.fonts, base); + // A symbol character (Insert › Symbol) is stored as the placeholder U+0028 with + // sprmCSymbol carrying the font and the character code ([MS-DOC] §2.6.1). + let mut c = c; + if c == '(' + && let Some((ftc, xchar)) = symbol_operand(grpprl) + { + if let Some(font) = ctx.fonts.get(usize::from(ftc)) { + props.font = Some(font.clone()); + } + if let Some(sym) = char::from_u32(u32::from(xchar)) { + c = sym; + } + } match field { Some(f) => match f.sep.as_mut() { // Inside a field, characters are recorded for the field object instead of shown; diff --git a/crates/docbin/src/tests.rs b/crates/docbin/src/tests.rs index d914e5e0f7..2507274a3a 100644 --- a/crates/docbin/src/tests.rs +++ b/crates/docbin/src/tests.rs @@ -854,7 +854,7 @@ fn struct_doc(spec: &StructSpec) -> Vec { let stsh = stsh_bytes(&[std_bytes(1, 0x0FFF, "Normal", &Vec::new(), &Vec::new())]); let stsh_at = table.len(); table.extend_from_slice(&stsh); - let ffn = ffn_bytes(&["Times New Roman"]); + let ffn = ffn_bytes(&["Times New Roman", "Wingdings"]); let ffn_at = table.len(); table.extend_from_slice(&ffn); let lst_at = table.len(); @@ -2197,3 +2197,49 @@ fn second_table_gets_its_own_grid() { assert_eq!(tables[1].props.align, None, "alignment reset"); assert_eq!(tables[1].props.indent, None, "indent reset"); } + +// --------------------------------------------------------------------------------------------- +// #1767: symbol runs and paragraph borders. + +/// Word stores a symbol character (Insert › Symbol) as the placeholder U+0028 plus +/// `sprmCSymbol` (0x6A09), which carries the font table index and the character code +/// ([MS-DOC] §2.6.1 CSymbolOperand); the text stream alone would read "(". +#[test] +fn symbol_runs_read_the_character_and_font_from_sprm_c_symbol() { + let text = "(\r"; + // ftc = 1 (Wingdings), xchar = F071: the box Word draws for Wingdings 0x71. + let sym = [1u16.to_le_bytes(), 0xF071u16.to_le_bytes()].concat(); + let spec = StructSpec { text, chpx_runs: vec![(0, grpprl(&[(0x6A09, &sym)]))], ..structured_spec(text, text.chars().count() as u32) }; + let doc = read(&struct_doc(&spec)).expect("opens"); + let p = doc.body.first().and_then(|b| b.as_para()).expect("paragraph"); + assert_eq!(p.text, "\u{F071}", "the symbol, not the placeholder"); + assert_eq!(p.props_of_char(0).font.as_deref(), Some("Wingdings"), "its font from the font table"); +} + +/// Paragraph borders around a block are read from both storage forms: the Word 97 Brc80 +/// (palette colour) and the Word 2000+ BrcOperand (cb + Brc with a full colour). +#[test] +fn paragraph_borders_read_from_both_forms() { + let b80 = [8u8, 0x01, 0x06, 0x01]; // 1 pt single line, palette red (6), 1 pt from the text + let brc = [8u8, 0xFF, 0x00, 0x00, 0x00, 8, 0x01, 0x02, 0x00]; // cb 8, red COLORREF, 1 pt single, space 2 + let text = "boxed\r\r"; + let spec = StructSpec { + text, + papx_entries: vec![ + (0, 0, grpprl(&[(0x6424, &b80), (0x6425, &b80), (0x6426, &b80), (0x6427, &b80), (0x6428, &b80)])), + (6, 0, grpprl(&[(0xC64E, &brc)])), + ], + ..structured_spec(text, text.chars().count() as u32) + }; + let doc = read(&struct_doc(&spec)).expect("opens"); + let p0 = doc.body.first().and_then(|b| b.as_para()).expect("first paragraph"); + let b = p0.props.borders.expect("borders"); + let top = b.top.expect("top border"); + assert_eq!((top.style, top.width, top.space), (wordcraft_doc::props::BorderStyle::Single, 1.0, 1.0)); + assert_eq!(top.color, Some(wordcraft_doc::Rgb(0xFF, 0, 0))); + assert!(b.left.is_some() && b.bottom.is_some() && b.right.is_some() && b.between.is_some()); + let p1 = doc.body.get(1).and_then(|b| b.as_para()).expect("second paragraph"); + let top = p1.props.borders.expect("borders").top.expect("top border"); + assert_eq!((top.style, top.width, top.space), (wordcraft_doc::props::BorderStyle::Single, 1.0, 2.0)); + assert_eq!(top.color, Some(wordcraft_doc::Rgb(0xFF, 0, 0))); +} From 07479516ddb56c3c145d99a4184d83895a236bfe Mon Sep 17 00:00:00 2001 From: Gert Pellin Date: Thu, 15 Oct 2026 00:55:59 +0200 Subject: [PATCH 012/336] Length fields show their unit in the interface language (#1734) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Centimetres, millimetres and picas took their suffix from Unit::suffix() and never went through the catalog, so a Russian interface showed "2,54 cm". The suffix now comes from the catalog like "pt" already did, and a length field accepts the translated unit when typed ("3 см"). --- crates/ui-egui/src/dialogs_para.rs | 4 +- crates/ui-egui/src/i18n/bg.tsv | 3 ++ crates/ui-egui/src/i18n/cs.tsv | 3 ++ crates/ui-egui/src/i18n/de.tsv | 3 ++ crates/ui-egui/src/i18n/es.tsv | 3 ++ crates/ui-egui/src/i18n/et.tsv | 3 ++ crates/ui-egui/src/i18n/fr.tsv | 3 ++ crates/ui-egui/src/i18n/hu.tsv | 3 ++ crates/ui-egui/src/i18n/it.tsv | 3 ++ crates/ui-egui/src/i18n/ja.tsv | 3 ++ crates/ui-egui/src/i18n/pl.tsv | 3 ++ crates/ui-egui/src/i18n/pt-br.tsv | 3 ++ crates/ui-egui/src/i18n/ru.tsv | 3 ++ crates/ui-egui/src/i18n/sr-latn.tsv | 3 ++ crates/ui-egui/src/i18n/sr.tsv | 3 ++ crates/ui-egui/src/i18n/tr.tsv | 3 ++ crates/ui-egui/src/i18n/uk.tsv | 3 ++ crates/ui-egui/src/i18n/vi.tsv | 3 ++ crates/ui-egui/src/i18n/zh-hans.tsv | 3 ++ crates/ui-egui/src/i18n/zh-hant.tsv | 3 ++ crates/ui-egui/src/options.rs | 84 ++++++++++++++++++++++++++--- 21 files changed, 135 insertions(+), 10 deletions(-) diff --git a/crates/ui-egui/src/dialogs_para.rs b/crates/ui-egui/src/dialogs_para.rs index 9a4a81979a..aa9a3edc2c 100644 --- a/crates/ui-egui/src/dialogs_para.rs +++ b/crates/ui-egui/src/dialogs_para.rs @@ -130,7 +130,7 @@ pub fn tabs(app: &mut WordApp, ui: &mut Ui, f: &mut TabsForm) -> bool { ui.set_min_size(vec2(150.0, 96.0)); egui::ScrollArea::vertical().max_height(96.0).show(ui, |ui| { for (i, t) in f.stops.iter().enumerate() { - if ui.selectable_label(f.selected == Some(i), u.format_with(t.pos, crate::options::decimal())).clicked() { + if ui.selectable_label(f.selected == Some(i), crate::options::length_in(u, t.pos)).clicked() { pick = Some(i); } } @@ -157,7 +157,7 @@ pub fn tabs(app: &mut WordApp, ui: &mut Ui, f: &mut TabsForm) -> bool { { // `1,5 cm; 3 cm` where the comma is the decimal separator. let sep = if crate::options::decimal() == ',' { "; " } else { ", " }; - f.cleared.iter().map(|p| u.format_with(*p, crate::options::decimal())).collect::>().join(sep) + f.cleared.iter().map(|p| crate::options::length_in(u, *p)).collect::>().join(sep) } }; ui.label(egui::RichText::new(list).weak()); diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index ce4a7100d1..b545855fce 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -1305,6 +1305,9 @@ {cols}×{rows} Table Таблица {cols}×{rows} AaBbYyZz AaBbYyZz pt пт + cm см + mm мм + pi pi Document1 Документ1 Username Потребителско име Display name Показвано име diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 9aaed9095e..42a0c9f908 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -1381,6 +1381,9 @@ {cols}×{rows} Table Tabulka {cols}×{rows} AaBbYyZz AaBbYyZz ČčŘřŽž pt b. + cm cm + mm mm + pi pi Document1 Dokument1 Username Uživatelské jméno Display name Zobrazované jméno diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 93e6b9e8e3..1087f82b2f 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -1286,6 +1286,9 @@ {cols}×{rows} Table {cols}×{rows}-Tabelle AaBbYyZz AaBbYyZz pt pt + cm cm + mm mm + pi pi Document1 Document1 Username Benutzername Display name Anzeigename diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 910871ec34..f8470157d3 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -1404,6 +1404,9 @@ {cols}×{rows} Table Tabla de {cols}×{rows} AaBbYyZz AaBbYyZz ÑñÁá pt pt + cm cm + mm mm + pi pi Document1 Documento1 Username Nombre de usuario Display name Nombre visible diff --git a/crates/ui-egui/src/i18n/et.tsv b/crates/ui-egui/src/i18n/et.tsv index 4624c3f4d7..09281dc5b7 100644 --- a/crates/ui-egui/src/i18n/et.tsv +++ b/crates/ui-egui/src/i18n/et.tsv @@ -1230,6 +1230,9 @@ {cols}×{rows} Table Tabel {cols}×{rows} AaBbYyZz AaBbYyZz ÕõÄäÖöÜü pt pt + cm cm + mm mm + pi pi Document1 Dokument1 Username Kasutajanimi Display name Kuvatav nimi diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 2fd447dcb4..7ddc22c140 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -1300,6 +1300,9 @@ {cols}×{rows} Table Tableau {cols}×{rows} AaBbYyZz AaBbYyZz pt pt + cm cm + mm mm + pi pi Document1 Document1 Username Nom d’utilisateur Display name Nom d’affichage diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 8bc2aec22f..0894abd8a0 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -1114,6 +1114,9 @@ Version Verzió % of all commits Az összes commit %-a pt pt + cm cm + mm mm + pi pi Save the document to turn on AutoSave Az automatikus mentéshez mentsd a dokumentumot Contributors Közreműködők Models Modellek diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 653b537c7b..cc2662afbf 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -1292,6 +1292,9 @@ {cols}×{rows} Table Tabella {cols}×{rows} AaBbYyZz AaBbYyZz ÀàÈé pt pt + cm cm + mm mm + pi pi Document1 Documento1 Username Nome utente Display name Nome visualizzato diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 287f9e6fd4..41c93b5c09 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -1365,6 +1365,9 @@ {cols}×{rows} Table {cols}×{rows} の表 AaBbYyZz AaBbYyZz あア亜 pt pt + cm cm + mm mm + pi pi Document1 文書 1 Username ユーザー名 Display name 表示名 diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index ceb54c4a64..f7bc825b6b 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -1347,6 +1347,9 @@ Width and spacing Szerokość i odstępy from Unicode (hex) z Unicode (szesnastkowo) pt pt + cm cm + mm mm + pi pi {cols}×{rows} Table Tabela {cols}×{rows} {contributors} contributors · {commits} commits Współtwórcy: {contributors} · commity: {commits} Copyright Sign Znak praw autorskich diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 372ca915b4..308e02e159 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -1377,6 +1377,9 @@ {cols}×{rows} Table Tabela {cols}×{rows} AaBbYyZz AaBbYyZz ÇçÃã pt pt + cm cm + mm mm + pi pi Document1 Documento1 Username Nome de usuário Display name Nome de exibição diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 7333adf54e..70be0910d1 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -1127,6 +1127,9 @@ preview Title Пример Version Версия % of all commits % всех коммитов pt пт + cm см + mm мм + pi пик Save the document to turn on AutoSave Сохраните документ, чтобы включить автосохранение Contributors Участники Models Модели diff --git a/crates/ui-egui/src/i18n/sr-latn.tsv b/crates/ui-egui/src/i18n/sr-latn.tsv index 9944d8cc8f..5da15848fe 100644 --- a/crates/ui-egui/src/i18n/sr-latn.tsv +++ b/crates/ui-egui/src/i18n/sr-latn.tsv @@ -1298,6 +1298,9 @@ {cols}×{rows} Table Tabela {cols}×{rows} AaBbYyZz AaBbYyZz ČčĐđ pt pt + cm cm + mm mm + pi pi Document1 Dokument1 Username Korisničko ime Display name Ime za prikaz diff --git a/crates/ui-egui/src/i18n/sr.tsv b/crates/ui-egui/src/i18n/sr.tsv index db56181481..8a58a41e34 100644 --- a/crates/ui-egui/src/i18n/sr.tsv +++ b/crates/ui-egui/src/i18n/sr.tsv @@ -1302,6 +1302,9 @@ {cols}×{rows} Table Табела {cols}×{rows} AaBbYyZz AaBbYyZz ЂђЋћ pt пт + cm цм + mm мм + pi pi Document1 Документ1 Username Корисничко име Display name Име за приказ diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 18fb2c0109..5448615078 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -1127,6 +1127,9 @@ preview Title Örnek Version Sürüm % of all commits Commit payı (%) pt pt + cm cm + mm mm + pi pi Save the document to turn on AutoSave Otomatik Kaydet'i açmak için belgeyi kaydedin Models Modeller Good evening İyi akşamlar diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 4cc6dc3c65..5f6b7a9793 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -1377,6 +1377,9 @@ {cols}×{rows} Table Таблиця {cols}×{rows} AaBbYyZz AaBbYyZz ЄєЇї pt пт + cm см + mm мм + pi pi Document1 Документ1 Username Ім'я користувача Display name Відображуване ім'я diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 68e05e217f..35e9749b51 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -1299,6 +1299,9 @@ {cols}×{rows} Table Bảng {cols}×{rows} AaBbYyZz AaBbYyZz pt pt + cm cm + mm mm + pi pi Document1 Document1 Username Tên đăng nhập Display name Tên hiển thị diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index d41d578368..bfb6aa0cb8 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -1377,6 +1377,9 @@ {cols}×{rows} Table {cols}×{rows} 表格 AaBbYyZz AaBbYyZz 中文 pt 磅 + cm 厘米 + mm 毫米 + pi 派卡 Document1 文档1 Username 用户名 Display name 显示名称 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 0141e7fad2..71e373095b 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -1366,6 +1366,9 @@ {cols}×{rows} Table {cols}×{rows} 表格 AaBbYyZz AaBbYyZz 中文 pt 點 + cm 公分 + mm 公釐 + pi 派卡 Document1 文件1 Username 使用者名稱 Display name 顯示名稱 diff --git a/crates/ui-egui/src/options.rs b/crates/ui-egui/src/options.rs index 7bdbd87c52..d6f3029577 100644 --- a/crates/ui-egui/src/options.rs +++ b/crates/ui-egui/src/options.rs @@ -35,9 +35,44 @@ pub fn decimal() -> char { DECIMAL.get() } -/// `pt` in [`unit`], written the region's way (`2,54 cm`). +/// `pt` in [`unit`], written the region's way (`2,54 cm`), with the unit in the interface +/// language (`2,54 см` in Russian). pub fn length(pt: f32) -> String { - unit().format_with(pt, decimal()) + length_in(unit(), pt) +} + +/// [`length`] in a given unit. +pub fn length_in(unit: Unit, pt: f32) -> String { + format!("{}{}", unit.number(pt, decimal()), unit_suffix(unit)) +} + +/// The unit's suffix as the interface shows it: ` cm`, ` mm`, ` pt` and ` pi` go through the +/// catalog (` см` in Russian, #1734); inches keep `"`. +pub fn unit_suffix(unit: Unit) -> String { + match unit { + Unit::Inches => unit.suffix().to_string(), + Unit::Centimeters => format!(" {}", tl!("cm")), + Unit::Millimeters => format!(" {}", tl!("mm")), + Unit::Points => format!(" {}", tl!("pt")), + Unit::Picas => format!(" {}", tl!("pi")), + } +} + +/// A length typed into a field in `unit`, as a number of that unit: either decimal separator, +/// another unit (`1 in` in a centimetre field), and the units' names in the interface language +/// as well as in English (`3 см`). +pub fn parse_length(text: &str, unit: Unit) -> Option { + let mut text: String = text.chars().filter(|c| !c.is_whitespace()).collect::().to_lowercase(); + for (english, translated) in [("cm", tl!("cm")), ("mm", tl!("mm")), ("pt", tl!("pt")), ("pi", tl!("pi"))] { + let translated = translated.to_lowercase(); + if translated != english + && let Some(number) = text.strip_suffix(translated.as_str()) + { + text = format!("{number}{english}"); + break; + } + } + wordcraft_geom::parse_measure(&text, unit).map(|pt| f64::from(pt / unit.pt_per_unit())) } /// Copy the session's settings the UI reads without the app. @@ -56,15 +91,12 @@ pub trait MeasureExt { impl MeasureExt for egui::DragValue<'_> { fn measure(self, unit: Unit) -> Self { let sep = decimal(); - self.suffix(unit.suffix()) + self.suffix(unit_suffix(unit)) .custom_formatter(move |n, decimals| { let s = egui::emath::format_with_decimals_in_range(n, decimals); if sep == '.' { s } else { s.replace('.', &sep.to_string()) } }) - .custom_parser(move |text| { - let text: String = text.chars().filter(|c| !c.is_whitespace()).collect(); - wordcraft_geom::parse_measure(&text, unit).map(|pt| f64::from(pt / unit.pt_per_unit())) - }) + .custom_parser(move |text| parse_length(text, unit)) } } @@ -716,7 +748,7 @@ mod tests { use serde_json::json; use wordcraft_geom::Unit; - use super::{decimal, length, unit}; + use super::{decimal, length, parse_length, unit}; use crate::theme::Appearance; use crate::{Services, UiState, WordApp}; @@ -1105,4 +1137,40 @@ mod tests { h.run_steps(4); assert_eq!(length(72.0), "1\""); } + + /// #1734: a Russian interface shows centimetres as `см`, as Word does, and a length field + /// takes the unit typed either way. + #[test] + fn length_units_follow_the_interface_language() { + let mut a = app(); + a.run("ui.language", json!({"value": "ru"})).unwrap(); + a.run("file.options", json!({"region": "ru-RU"})).unwrap(); + a.run("file.new", json!({})).unwrap(); + 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, + ); + h.run_steps(2); + h.state_mut().run("ui.dialog", json!({"name": "pageSetup"})).unwrap(); + h.run_steps(4); + assert_eq!((unit(), decimal()), (Unit::Centimeters, ',')); + assert_eq!(length(72.0), "2,54 см"); + assert_eq!(h.query_all_by_value("2,54 см").count(), 4, "four margins in centimetres, named in Russian"); + assert_eq!(h.query_all_by_value("2,54 cm").count(), 0); + assert_eq!(parse_length("3 см", Unit::Centimeters), Some(3.0)); + assert_eq!(parse_length("3cm", Unit::Centimeters), Some(3.0)); + assert_eq!(parse_length("10 мм", Unit::Centimeters).map(|v| (v * 100.0).round() / 100.0), Some(1.0)); + assert_eq!(parse_length("72 пт", Unit::Centimeters).map(|v| (v * 100.0).round() / 100.0), Some(2.54)); + + h.state_mut().run("file.options", json!({"units": "millimeters"})).unwrap(); + h.run_steps(4); + assert_eq!(length(72.0), "25,4 мм"); + h.state_mut().run("ui.language", json!({"value": "en"})).unwrap(); + h.run_steps(4); + assert_eq!(length(72.0), "25,4 mm", "English keeps the English names"); + } } From 622df29a1e0c082bfcb0c3ada00d831d3b6b6682 Mon Sep 17 00:00:00 2001 From: vangel76 Date: Thu, 15 Oct 2026 01:25:34 +0200 Subject: [PATCH 013/336] Layout: floats in table cells are not aligned in a zero-height area (#1887) Co-Authored-By: Claude Sonnet 5.5 --- ROADMAP.md | 1 + crates/layout/src/lib.rs | 5 ++++- crates/layout/src/tests.rs | 19 +++++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/ROADMAP.md b/ROADMAP.md index 789842411a..a2ca9ad6a1 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -136,6 +136,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | Date | What landed | |---|---| +| 2026-10-15 | A floating picture or shape in a table cell set to align centre or bottom no longer lands half its height above its line (#1887); it starts at the anchor line | | 2026-10-13 | A picture (or shape) set to Behind Text can be dragged over the text it overlaps, whether or not it was selected first, in the body and in a header/footer. The press becomes an object drag only after the pointer moves, so an ordinary click still places the text caret in the overlapping text. The UI regression tests cover In Front of Text too | | 2026-10-13 | Saving to DOCX keeps the watermark: it is written as Word's own text-watermark shape in every header, and a document without headers gains one to hold it (#1290) | | 2026-10-13 | DOCX files with Word's text watermark (Design › Watermark, a VML text shape in the headers) show it on every page on screen and in the PDF (#1290) | diff --git a/crates/layout/src/lib.rs b/crates/layout/src/lib.rs index 1a6f8efedc..d387fabeb4 100644 --- a/crates/layout/src/lib.rs +++ b/crates/layout/src/lib.rs @@ -2581,7 +2581,10 @@ fn float_rect(frame: Option, col: (f32, f32), y0: f32, w: f32, h: f32 let hx = frame.and_then(|f| page_x(&f).map(|(a, e)| (a - f.origin.0, e))).unwrap_or(col); let vy = frame.and_then(|f| page_y(f.sect).map(|(a, e)| (a - f.origin.1, e))).unwrap_or((y0, 0.0)); let h_align = PageFrame::h_align(frame.as_ref(), float.h_align); - Rect::new(align_in(hx, float.x, w, h_align), align_in(vy, float.y, h, float.v_align), w, h) + // Without a frame (a cell) `vy` has no height to align in: the object starts at the anchor + // line instead of being pushed up by half its height or more. + let v_align = if frame.is_none() { None } else { float.v_align }; + Rect::new(align_in(hx, float.x, w, h_align), align_in(vy, float.y, h, v_align), w, h) } /// Where an object of `size` starts on one axis of `area` (start, extent): at `offset` from the diff --git a/crates/layout/src/tests.rs b/crates/layout/src/tests.rs index f6a4ac2c05..b241fdbb3a 100644 --- a/crates/layout/src/tests.rs +++ b/crates/layout/src/tests.rs @@ -3261,6 +3261,25 @@ fn floats_in_headers_and_table_cells_are_drawn() { assert!(c[0].x > 72.0 && c[0].x < 72.0 + 234.0, "inside the first cell: {:?}", c[0]); } +#[test] +fn a_centred_float_in_a_cell_is_not_pushed_above_its_anchor_line() { + use wordcraft_doc::para::{Anchor, Float, FloatAlign, Wrap}; + for align in [FloatAlign::Center, FloatAlign::End] { + let mut d = Document::from_text("Before."); + let mut t = Table::new(1, 1, 468.0); + let mut cp = wordcraft_doc::Paragraph::with_text("cell", Default::default()); + let f = Float { wrap: Wrap::Square, h_rel: Anchor::Column, v_rel: Anchor::Margin, v_align: Some(align), ..Default::default() }; + cp.insert_object(0, rect_shape(100.0, 200.0, f), &Default::default()).unwrap(); + t.rows[0].cells[0].blocks = vec![wordcraft_doc::para_block(cp)]; + d.insert_block(StoryRef::Body, &Path::top(1), wordcraft_doc::Block::Table(t)).unwrap(); + let l = lay(&d); + let c = shapes(&l.pages[0].items); + assert_eq!(c.len(), 1, "cell float drawn"); + // The first line sits at the top margin (72 pt); the table is below the "Before." line. + assert!(c[0].y >= 72.0 + 10.0, "{align:?}: float not above the anchor line: {:?}", c[0]); + } +} + #[test] fn wrap_area_uses_each_distance() { use wordcraft_doc::para::{Float, Wrap}; From 9acb1241b540f41726c21df0cdeee93d2d2de01c Mon Sep 17 00:00:00 2001 From: vangel76 Date: Thu, 15 Oct 2026 01:31:37 +0200 Subject: [PATCH 014/336] Charts: draw radar charts (standard, marker, filled) instead of a placeholder (#1890) Co-Authored-By: Claude Sonnet 5.5 --- ROADMAP.md | 1 + crates/docx/src/read/chart.rs | 203 ++++++++++++++++++++++++++++++++-- docs/gaps.md | 2 +- 3 files changed, 194 insertions(+), 12 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 789842411a..c9a5d1ee35 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -525,3 +525,4 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | 2026-10-09 | minor | Recently landed: RTL, Ukrainian and Brazilian Portuguese | | 2026-10-06 | major | First honest estimate: 88% catalog, ~62% real parity, alpha ~85% of the way | | 2026-10-12 | trivial | Progress log: recipient filter and sort, .xls and legacy-encoding recipient lists (#778) | +| 2026-10-15 | trivial | Charts: radar charts (standard, marker, filled) are drawn instead of a placeholder (#1890) | diff --git a/crates/docx/src/read/chart.rs b/crates/docx/src/read/chart.rs index 82ed31fbdb..47dc6f6c58 100644 --- a/crates/docx/src/read/chart.rs +++ b/crates/docx/src/read/chart.rs @@ -2,7 +2,8 @@ //! //! Bar and column (clustered, stacked or 100% stacked), line, area, pie, doughnut and scatter //! charts are drawn from the first supported chart type of the plot area, with their values as -//! data labels where `c:dLbls` shows them (`c:showVal`). A chart of only other types (radar, stock…) +//! data labels where `c:dLbls` shows them (`c:showVal`). Radar charts (standard, with markers or +//! filled) draw as spokes, concentric grid polygons and one polygon per series. A chart of only other types (stock, bubble…) //! draws a placeholder: its frame, its title and the type it is. Text widths are estimated ([`CHAR_W`] em a character), so label places are approximate. use std::f64::consts::{FRAC_PI_2, TAU}; @@ -23,6 +24,10 @@ const MAX_POINTS: usize = 10_000; const MAX_CELLS: usize = 100_000; /// Longest series name or category label kept. const MAX_LABEL: usize = 200; +/// Most categories (spokes) of a radar chart. +const MAX_SPOKES: usize = 100; +/// Most series of a radar chart. +const MAX_RADAR_SERIES: usize = 50; /// Most axis ticks drawn. const MAX_TICKS: usize = 20; /// Default text size, points. @@ -75,6 +80,7 @@ pub(crate) fn chart_items(space: &El, theme: &[Rgb], w: f32, h: f32) -> Vec pie(&mut out, &chart, area), + Kind::Radar => radar(&mut out, &chart, area), _ => cartesian(&mut out, &chart, area), } out @@ -87,7 +93,7 @@ fn title_block(out: &mut Vec, t: &str, area: Rect) -> Rect { Rect { y: area.y + lh + PAD, h: (area.h - lh - PAD).max(1.0), ..area } } -/// A chart whose plot area holds only chart types WordCraft doesn't draw (radar, stock…): its +/// A chart whose plot area holds only chart types WordCraft doesn't draw (stock, bubble…): its /// frame with a light border, its title and a line saying which type it is, so it isn't blank /// space. Nothing when the plot area has no chart type at all, or a supported one with nothing /// to draw. @@ -114,7 +120,6 @@ fn placeholder(out: &mut Vec, space: &El, theme: &[Rgb], w: f32, h: /// What a placeholder calls a chart type element WordCraft doesn't draw. fn unsupported_name(name: &str) -> &'static str { match name { - "c:radarChart" => "Radar chart", "c:stockChart" => "Stock chart", "c:bubbleChart" => "Bubble chart", "c:surfaceChart" | "c:surface3DChart" => "Surface chart", @@ -144,6 +149,7 @@ enum Kind { Pie, Doughnut, Scatter, + Radar, } impl Kind { @@ -155,6 +161,7 @@ impl Kind { "c:pieChart" => Kind::Pie, "c:doughnutChart" => Kind::Doughnut, "c:scatterChart" => Kind::Scatter, + "c:radarChart" => Kind::Radar, _ => return None, }) } @@ -221,6 +228,10 @@ struct Chart { /// Doughnut hole, fraction of the outer radius. hole: f32, first_angle: f64, + /// Radar: series polygons are filled (`c:radarStyle` = `filled`). + radar_fill: bool, + /// Radar: points carry markers (`c:radarStyle` = `marker`). + radar_marks: bool, vary: bool, /// Number of categories (scatter: points per series at most). n: usize, @@ -253,7 +264,9 @@ impl Chart { return None; } let cats = ty.children("c:ser").find_map(|s| s.child("c:cat")).map(|c| labels(Some(c), MAX_POINTS, date1904)).unwrap_or_default(); - let n = if matches!(kind, Kind::Pie | Kind::Doughnut) { + let n = if kind == Kind::Radar { + series.iter().map(|s| s.values.len()).max().unwrap_or(0).max(cats.len()).min(MAX_SPOKES) + } else if matches!(kind, Kind::Pie | Kind::Doughnut) { series.first().map_or(0, |s| s.values.len()).max(cats.len()) } else { series.iter().map(|s| s.values.len()).max().unwrap_or(0).max(cats.len()) @@ -262,6 +275,9 @@ impl Chart { if let Some(keep) = MAX_CELLS.checked_div(n) { series.truncate(keep.max(1)); } + if kind == Kind::Radar { + series.truncate(MAX_RADAR_SERIES); + } let grouping = match ty.child("c:grouping").and_then(|g| g.attr("val")) { Some("stacked") if matches!(kind, Kind::Bar | Kind::Area | Kind::Line) => Grouping::Stacked, Some("percentStacked") if matches!(kind, Kind::Bar | Kind::Area | Kind::Line) => Grouping::Percent, @@ -269,6 +285,7 @@ impl Chart { }; let mut cats = cats; cats.resize(n, String::new()); + let radar_style = ty.child("c:radarStyle").and_then(|r| r.attr("val")); let type_labels = show_values(ty.child("c:dLbls")); for s in &mut series { s.values.resize(n, None); @@ -296,6 +313,8 @@ impl Chart { overlap: num("c:overlap").unwrap_or(0).clamp(-100, 100) as f32 / 100.0, hole: num("c:holeSize").unwrap_or(50).clamp(10, 90) as f32 / 100.0, first_angle: num("c:firstSliceAng").unwrap_or(0).clamp(0, 360) as f64, + radar_fill: radar_style == Some("filled"), + radar_marks: radar_style == Some("marker"), vary: matches!(kind, Kind::Pie | Kind::Doughnut) && ty.child("c:varyColors").and_then(|v| v.attr("val")).is_none_or(truthy), n, cats, @@ -1324,7 +1343,7 @@ fn cartesian(out: &mut Vec, c: &Chart, area: Rect) { Kind::Line => lines(out, c, &g, &mut labels), Kind::Area => areas(out, c, &g, &mut labels), Kind::Scatter => draw_scatter(out, c, &g, &mut labels), - Kind::Pie | Kind::Doughnut => {} + Kind::Pie | Kind::Doughnut | Kind::Radar => {} } draw_axes(out, c, &g); draw_data_labels(out, &labels, c.text_size, g.horiz && c.kind == Kind::Bar); @@ -1621,6 +1640,94 @@ fn pie(out: &mut Vec, c: &Chart, area: Rect) { draw_data_labels(out, &labels, c.text_size, false); } +/// A radar chart in `area`: category spokes from the centre, concentric grid polygons at the value +/// ticks, one polygon per series (filled, or outlined with markers) and the category labels at the +/// spoke ends. The lowest tick is the centre. +fn radar(out: &mut Vec, c: &Chart, area: Rect) { + const MARKER: f32 = 5.0; + if c.n == 0 { + return; + } + let fs = c.text_size; + let lh = fs * LINE; + let labelled = c.cats.iter().any(|s| !s.is_empty()); + let side = if labelled { widest(&c.cats, fs).min(area.w * 0.25) + GAP + 2.0 } else { 0.0 }; + let vert = if labelled { lh + GAP } else { 0.0 }; + let r = ((area.w / 2.0 - side).min(area.h / 2.0 - vert) - 2.0) as f64; + if r.is_nan() || r < 8.0 { + return; + } + let (cx, cy) = ((area.x + area.w / 2.0) as f64, (area.y + area.h / 2.0) as f64); + let vs = Axes::of(c).vs; + let span = vs.hi - vs.lo; + let angle = |i: usize| i as f64 / c.n as f64 * TAU; + let at = |v: f64, i: usize| { + let t = if span > 0.0 { ((v - vs.lo) / span).clamp(0.0, 1.0) } else { 0.0 }; + polar(cx, cy, r * t, angle(i)) + }; + let ticks = vs.ticks(); + if c.val.grid { + for t in ticks.iter().skip(1) { + let mut segs: Vec = (0..c.n).map(|i| if i == 0 { pt(at(*t, i)) } else { line_to(at(*t, i)) }).collect(); + segs.push(PathSeg::Close); + stroke(out, segs, GRID, 0.75); + } + } + for i in 0..c.n { + let end = polar(cx, cy, r, angle(i)); + stroke(out, vec![pt((cx, cy)), line_to(end)], GRID, 0.75); + } + let mut labels = Vec::new(); + for (j, s) in c.series.iter().enumerate().filter(|(_, s)| has_values(s)) { + let col = c.series_colour(j); + // A missing value counts as the centre, as Word draws it. + let ring: Vec<(f64, f64)> = (0..c.n).map(|i| at(s.values.get(i).copied().flatten().unwrap_or(vs.lo), i)).collect(); + let mut segs: Vec = ring.iter().enumerate().map(|(i, p)| if i == 0 { pt(*p) } else { line_to(*p) }).collect(); + segs.push(PathSeg::Close); + if c.radar_fill { + fill(out, segs.clone(), col); + } + stroke(out, segs, col, s.line_w); + for (i, p) in ring.iter().enumerate() { + if c.radar_marks && s.values.get(i).copied().flatten().is_some() { + let rect = [p.0 as f32 - MARKER / 2.0, p.1 as f32 - MARKER / 2.0, MARKER, MARKER]; + out.push(GraphicItem::Shape { rect, kind: ShapeKind::Ellipse, fill: Some(col), stroke: None, stroke_width: 0.0 }); + } + if let Some(text) = label_of(s, i) { + labels.push(DataLabel { x: p.0 as f32, y: p.1 as f32, text, inside: false }); + } + } + } + draw_data_labels(out, &labels, fs, false); + if !c.val.deleted { + for t in &ticks { + let y = at(*t, 0).1 as f32; + let s = format_value(*t, &c.val_fmt, vs.step); + let w = text_w(&s, fs) + 4.0; + text(out, Rect { x: cx as f32 - GAP / 2.0 - w, y: y - lh / 2.0, w, h: lh }, &s, fs, TextAlign::Right, false); + } + } + if labelled { + for (i, s) in c.cats.iter().enumerate().filter(|(_, s)| !s.is_empty()) { + let a = angle(i); + let (x, y) = polar(cx, cy, r + GAP as f64, a); + let (x, y) = (x as f32, y as f32); + let w = side - GAP; + let txt = clip_text(s, w, fs); + let (sx, sy) = (a.sin(), a.cos()); + let (rect, align) = if sx > 0.2 { + (Rect { x, y: y - lh / 2.0, w, h: lh }, TextAlign::Left) + } else if sx < -0.2 { + (Rect { x: x - w, y: y - lh / 2.0, w, h: lh }, TextAlign::Right) + } else { + let cw = (text_w(&txt, fs) + 4.0).max(1.0); + (Rect { x: x - cw / 2.0, y: if sy > 0.0 { y - lh } else { y }, w: cw, h: lh }, TextAlign::Center) + }; + text(out, rect, &txt, fs, align, false); + } + } +} + /// The data label of point `i` of series `s`, when it shows one: the value as the file has it, /// in the series' number format. fn label_of(s: &Series, i: usize) -> Option { @@ -1849,21 +1956,95 @@ mod tests { fn unsupported_chart_types_draw_a_placeholder() { let ser = r#"1"#; let title = r#"Skills"#; - let x = format!(r#"{title}{ser}"#); - let radar = chart_items(&crate::xml::parse(x.as_bytes()).unwrap(), &theme(), 300.0, 200.0); - assert_eq!(texts(&radar), ["Skills", "Radar chart (not supported yet)"]); - let framed = radar.iter().any(|it| matches!(it, GraphicItem::Shape { rect, stroke: Some(_), .. } if *rect == [0.0, 0.0, 300.0, 200.0])); - assert!(framed, "a light border marks the chart's frame: {radar:?}"); + let x = format!(r#"{title}{ser}"#); + let bubble = chart_items(&crate::xml::parse(x.as_bytes()).unwrap(), &theme(), 300.0, 200.0); + assert_eq!(texts(&bubble), ["Skills", "Bubble chart (not supported yet)"]); + let framed = bubble.iter().any(|it| matches!(it, GraphicItem::Shape { rect, stroke: Some(_), .. } if *rect == [0.0, 0.0, 300.0, 200.0])); + assert!(framed, "a light border marks the chart's frame: {bubble:?}"); let stock = chart_items(&space(&format!("{ser}{ser}{ser}")), &theme(), 300.0, 200.0); assert_eq!(texts(&stock), ["Stock chart (not supported yet)"]); let other = chart_items(&space(&format!("{ser}")), &theme(), 300.0, 200.0); assert_eq!(texts(&other), ["Chart (not supported yet)"]); // A supported type in the plot area is drawn, an unsupported one beside it is skipped. - let combo = chart_items(&space(&format!("{ser}{ser}")), &theme(), 300.0, 200.0); + let combo = chart_items(&space(&format!("{ser}{ser}")), &theme(), 300.0, 200.0); assert!(!texts(&combo).iter().any(|t| t.contains("not supported")), "{combo:?}"); } + fn radar_chart(style: &str, sers: &[String]) -> Vec { + let plot = format!(r#"{}"#, sers.concat()); + chart_items(&space(&plot), &theme(), 300.0, 220.0) + } + + #[test] + fn radar_charts_draw_spokes_grid_and_series_polygons() { + let cats = ["Speed", "Power", "Range", "Armor", "Luck"]; + let sers = [ser(0, "A", Some("FF0000"), &cats, &["3", "4", "5", "2", "1"]), ser(1, "B", Some("0000FF"), &cats, &["2", "2", "3", "5", "4"])]; + let std = radar_chart("standard", &sers); + assert!(!texts(&std).iter().any(|t| t.contains("not supported")), "{std:?}"); + for c in cats { + assert!(texts(&std).contains(&c), "category label {c}: {:?}", texts(&std)); + } + let stroked = |items: &[GraphicItem], col: Rgb| items.iter().filter(|it| matches!(it, GraphicItem::Path { stroke: Some(c), segs, .. } if *c == col && segs.len() == 6)).count(); + assert_eq!(stroked(&std, Rgb(0xFF, 0, 0)), 1, "one closed pentagon per series: {std:?}"); + assert_eq!(stroked(&std, Rgb(0, 0, 0xFF)), 1); + assert!(filled(&std).is_empty(), "standard radar is not filled"); + assert!(!std.iter().any(|it| matches!(it, GraphicItem::Shape { kind: ShapeKind::Ellipse, .. }))); + // 5 spokes (2 segs each) plus grid pentagons. + let spokes = std.iter().filter(|it| matches!(it, GraphicItem::Path { stroke: Some(c), segs, .. } if *c == GRID && segs.len() == 2)).count(); + assert_eq!(spokes, 5); + assert!(std.iter().any(|it| matches!(it, GraphicItem::Path { stroke: Some(c), segs, .. } if *c == GRID && segs.len() == 6))); + + let marked = radar_chart("marker", &sers); + let dots = marked.iter().filter(|it| matches!(it, GraphicItem::Shape { kind: ShapeKind::Ellipse, .. })).count(); + assert_eq!(dots, 10, "a marker per point"); + + let solid = radar_chart("filled", &sers); + let f = filled(&solid); + assert_eq!(f.len(), 2); + assert!(f.iter().any(|(c, ..)| *c == Rgb(0xFF, 0, 0)) && f.iter().any(|(c, ..)| *c == Rgb(0, 0, 0xFF))); + } + + #[test] + fn radar_charts_show_a_legend() { + let cats = ["a", "b", "c"]; + let plot = format!("{}", ser(0, "Team", None, &cats, &["1", "2", "3"])); + let items = with_legend(&plot, "r", 300.0, 200.0); + assert!(texts(&items).contains(&"Team"), "{:?}", texts(&items)); + } + + #[test] + fn hostile_radar_charts_never_panic() { + // No categories and no values. + let empty = r#""#; + let _ = chart_items(&space(&format!("{empty}")), &theme(), 300.0, 200.0); + let none = chart_items(&space(""), &theme(), 300.0, 200.0); + assert!(none.is_empty(), "{none:?}"); + // One category: a degenerate polygon, nothing to crash on; two as well. + for n in 1..=2 { + let cats: Vec = (0..n).map(|i| format!("c{i}")).collect(); + let refs: Vec<&str> = cats.iter().map(String::as_str).collect(); + let vals = vec!["5"; n]; + let items = radar_chart("filled", &[ser(0, "S", None, &refs, &vals)]); + assert!(texts(&items).contains(&"c0"), "{items:?}"); + } + // NaN, infinities, junk and a tiny frame. + let junk = ser(0, "S", None, &["a", "b", "c"], &["NaN", "inf", "1e999"]); + let _ = radar_chart("filled", &[junk]); + let small = chart_items(&space(&format!("{}", ser(0, "S", None, &["a", "b", "c"], &["1", "2", "3"]))), &theme(), 20.0, 20.0); + let _ = small; + // Caps: thousands of categories and series stay bounded. + let cats: Vec = (0..5000).map(|i| format!("c{i}")).collect(); + let refs: Vec<&str> = cats.iter().map(String::as_str).collect(); + let vals = vec!["1"; 5000]; + let many: Vec = (0..300).map(|i| ser(i, "S", None, &refs, &vals)).collect(); + let items = radar_chart("marker", &many); + let polygons = items.iter().filter(|it| matches!(it, GraphicItem::Path { stroke: Some(c), .. } if *c != GRID)).count(); + assert!(polygons <= MAX_RADAR_SERIES, "{polygons}"); + let spokes = items.iter().filter(|it| matches!(it, GraphicItem::Path { stroke: Some(c), segs, .. } if *c == GRID && segs.len() == 2)).count(); + assert_eq!(spokes, MAX_SPOKES); + } + /// Chart items for a chart with a legend at `pos` (`r`, `b`…). fn with_legend(plot: &str, pos: &str, w: f32, h: f32) -> Vec { let x = format!( diff --git a/docs/gaps.md b/docs/gaps.md index c698251dbc..ffb794be95 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -33,7 +33,7 @@ Document renaming: desktop renaming requires a filesystem that supports hard lin | 10 | **Objects**: text in a freely rotated text box (free rotation and flips landed, #332; 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**: 5 of the 12 key languages missing (hi, ar, id, ko, vi) and pt-PT (French landed, #729; 19 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); missing: Ink to Math, two-stroke arrows and arrows with a closed triangle head, 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), no combo/3D/radar/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) | +| 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) | | 14 | **`.doc` can't be written; Word 6/95 files refused** | | `crates/docbin` is read-only | Users exchanging files with old Word or legacy systems | 25–40 | [file-format-parity.md](file-format-parity.md) | | 15 | **References depth**: no EndNote/Mendeley desktop integration (bibliography sources are saved in DOCX, #790; Zotero landed, #189) | | #790 stores sources in `customXml` and reads them back; `crates/zotero` | Students and researchers | 3–6 | [target-app-parity.md](target-app-parity.md) | | 16 | **Right-to-left completeness**: kashida, RTL in HTML/ODT/RTF; six still-open older RTL bug reports to re-verify after #207 (#211, #199, #66, #63, #48, #19; #215 is closed). RTL tables and sections landed (#362); PDF export's Hebrew and Arabic text copies and searches as whole words (#1383) | | [layout-parity.md](layout-parity.md) | Arabic, Persian, Hebrew writers | 7–11 | [layout-parity.md](layout-parity.md) | From 6931b8db2a501e713182085cfe10548951c0f647 Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Thu, 15 Oct 2026 02:50:12 +0200 Subject: [PATCH 015/336] Draw EMF text records (EXTTEXTOUTW) in metafile pictures Fixes #1764 Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Sonnet 5.5 Co-Authored-By: Claude Opus 5.5 --- crates/metafile/src/canvas.rs | 117 +++++++++++++++++++++++++++++++++- crates/metafile/src/emf.rs | 58 ++++++++++++++++- crates/metafile/src/lib.rs | 81 ++++++++++++++++++++++- crates/metafile/src/place.rs | 76 +++++++++++++++++++++- crates/metafile/src/tests.rs | 2 +- crates/pdf/src/lib.rs | 64 ++++++++++++++++++- crates/pdf/src/tests.rs | 38 +++++++++++ crates/render/src/lib.rs | 69 ++++++++++++++++++++ 8 files changed, 494 insertions(+), 11 deletions(-) diff --git a/crates/metafile/src/canvas.rs b/crates/metafile/src/canvas.rs index 0ac4790f61..03a8f34532 100644 --- a/crates/metafile/src/canvas.rs +++ b/crates/metafile/src/canvas.rs @@ -21,6 +21,12 @@ const R2_NOP: u32 = 11; const PATCOPY: u32 = 0x00F0_0021; const BLACKNESS: u32 = 0x0000_0042; const WHITENESS: u32 = 0x00FF_0062; +/// SETTEXTALIGN: draw at and update the current point. +const TA_UPDATECP: u32 = 1; +/// Font height (cell height, logical units) of the default font, used for a height of 0. +const DEFAULT_FONT_HEIGHT: f64 = 16.0; +/// Face used when a font names none. +const DEFAULT_FONT_FACE: &str = "Arial"; /// Device pixels per millimetre assumed for WMF (96 dpi), and for EMF files without a device size. const DEFAULT_PX_PER_MM: f64 = 96.0 / 25.4; @@ -54,11 +60,23 @@ pub(crate) struct Brush { pub color: [u8; 4], } +/// A logical font (EMF LogFont): height in logical units (negative = character height, positive = +/// cell height, 0 = default), escapement in tenths of a degree, face name as UTF-16 (NUL-padded). +#[derive(Clone, Copy, PartialEq, Debug)] +pub(crate) struct Font { + pub height: i32, + pub escapement: i32, + pub weight: i32, + pub italic: bool, + pub face: [u16; 32], +} + #[derive(Clone, Copy, Debug)] pub(crate) enum Obj { Pen(Pen), Brush(Brush), - /// Fonts, palettes, pattern brushes and regions: they take a table slot but draw nothing. + Font(Font), + /// Palettes, pattern brushes and regions: they take a table slot but draw nothing. Other, } @@ -168,6 +186,23 @@ fn ratio(num: f64, den: f64) -> f64 { pub(crate) enum Raw { Path { ops: Vec, fill: Option<[u8; 4]>, stroke: Option<([u8; 4], f64)>, even_odd: bool }, Bitmap { rect: [f64; 4], img: Rgba }, + Text(RawText), +} + +/// A text run in device units: the reference point, the font height along the text's up direction, +/// the baseline angle (radians, clockwise in y-down space) and per-character advances. +pub(crate) struct RawText { + pub text: String, + pub origin: P, + pub size: f64, + pub cell: bool, + pub angle: f64, + pub dx: Vec, + pub font: String, + pub bold: bool, + pub italic: bool, + pub color: [u8; 4], + pub align: u32, } #[derive(Clone)] @@ -177,6 +212,9 @@ struct Saved { brush: Brush, even_odd: bool, nop: bool, + font: Option, + text_color: [u8; 4], + text_align: u32, } pub(crate) struct Canvas { @@ -201,6 +239,11 @@ pub(crate) struct Canvas { even_odd: bool, /// The mix mode is R2_NOP: shapes draw nothing. nop: bool, + /// The selected font; None is the default font. + font: Option, + text_color: [u8; 4], + /// SETTEXTALIGN flags (TA_*). + text_align: u32, pos: P, in_path: bool, path: Vec, @@ -224,6 +267,9 @@ impl Canvas { brush: Brush { style: BS_SOLID, color: [255, 255, 255, 255] }, even_odd: true, nop: false, + font: None, + text_color: [0, 0, 0, 255], + text_align: 0, pos: (0.0, 0.0), in_path: false, path: Vec::new(), @@ -240,6 +286,7 @@ impl Canvas { let cost = match &raw { Raw::Path { ops, .. } => ops.len() * std::mem::size_of::(), Raw::Bitmap { img, .. } => img.px.len(), + Raw::Text(t) => t.text.len() + t.font.len() + t.dx.len() * std::mem::size_of::(), }; if self.items.len() >= MAX_ITEMS || self.used.saturating_add(cost) > MAX_BYTES { self.full = true; @@ -318,7 +365,16 @@ impl Canvas { /// could no longer match the file. pub fn save_dc(&mut self) { if self.saved.len() < MAX_STACK { - self.saved.push(Saved { map: self.map, pen: self.pen, brush: self.brush, even_odd: self.even_odd, nop: self.nop }); + self.saved.push(Saved { + map: self.map, + pen: self.pen, + brush: self.brush, + even_odd: self.even_odd, + nop: self.nop, + font: self.font, + text_color: self.text_color, + text_align: self.text_align, + }); } else { self.full = true; } @@ -335,6 +391,9 @@ impl Canvas { self.brush = s.brush; self.even_odd = s.even_odd; self.nop = s.nop; + self.font = s.font; + self.text_color = s.text_color; + self.text_align = s.text_align; } /// Stores an object in `slot` (EMF index), or in the lowest free slot (WMF handle table). @@ -384,6 +443,7 @@ impl Canvas { match self.objects.get(i as usize).copied().flatten() { Some(Obj::Pen(p)) => self.pen = p, Some(Obj::Brush(b)) => self.brush = b, + Some(Obj::Font(f)) => self.font = Some(f), Some(Obj::Other) | None => {} } } @@ -402,6 +462,8 @@ impl Canvas { 6 => self.pen = Pen { style: 0, width: 0.0, color: white }, 7 | 19 => self.pen = Pen { style: 0, width: 0.0, color: black }, 8 => self.pen = Pen { style: PS_NULL, width: 0.0, color: black }, + // The stock fonts (OEM, ANSI, system, device default...): the default font. + 10..=17 => self.font = None, _ => {} } } @@ -571,6 +633,57 @@ impl Canvas { } } + pub fn set_text_color(&mut self, c: [u8; 4]) { + self.text_color = c; + } + + pub fn set_text_align(&mut self, a: u32) { + self.text_align = a; + } + + /// Draws `text` at the logical point `at` (the current point with TA_UPDATECP) with the selected + /// font, text colour and alignment. `dx` holds per-character logical advances (empty for the + /// font's own); with TA_UPDATECP the current point moves past them. + pub fn text(&mut self, at: P, text: String, dx: &[f64]) { + if text.is_empty() || self.in_path { + return; + } + let font = self.font.unwrap_or(Font { height: 0, escapement: 0, weight: 400, italic: false, face: [0; 32] }); + let at = if self.text_align & TA_UPDATECP != 0 { self.pos } else { at }; + if self.text_align & TA_UPDATECP != 0 { + self.pos = (at.0 + dx.iter().sum::(), at.1); + } + let height = if font.height == 0 { DEFAULT_FONT_HEIGHT } else { f64::from(font.height) }; + let origin = self.map.device(at); + let len = |v: P| { + let d = self.map.device((at.0 + v.0, at.1 + v.1)); + (d.0 - origin.0).hypot(d.1 - origin.1) + }; + let base = self.map.device((at.0 + 1.0, at.1)); + let escapement = f64::from(font.escapement) / 10.0; + let angle = (base.1 - origin.1).atan2(base.0 - origin.0) - escapement.to_radians(); + let size = len((0.0, height.abs())); + let dx = dx.iter().map(|d| len((*d, 0.0)).copysign(*d)).collect(); + let name = String::from_utf16_lossy(&font.face); + let name = name.trim_end_matches('\0').trim(); + let raw = RawText { + text, + origin, + size, + cell: height > 0.0, + angle: if angle.is_finite() { angle } else { 0.0 }, + dx, + font: if name.is_empty() { DEFAULT_FONT_FACE.to_string() } else { name.to_string() }, + bold: font.weight >= 600, + italic: font.italic, + color: self.text_color, + align: self.text_align, + }; + if raw.origin.0.is_finite() && raw.origin.1.is_finite() && raw.size.is_finite() && raw.size > 0.0 { + self.push(Raw::Text(raw)); + } + } + /// A bitmap stretched into a logical rectangle `[x, y, w, h]`. A negative extent mirrors the /// bitmap, as the mapping does. pub fn bitmap(&mut self, rect: [f64; 4], mut img: Rgba) { diff --git a/crates/metafile/src/emf.rs b/crates/metafile/src/emf.rs index 904d126b72..4ffe9cd1b0 100644 --- a/crates/metafile/src/emf.rs +++ b/crates/metafile/src/emf.rs @@ -2,7 +2,7 @@ //! record (bitmap info and bits) are relative to the record start, so each handler reads the record slice. use crate::bytes::Bytes; -use crate::canvas::{BS_NULL, Brush, Canvas, Obj, PS_NULL, PS_STYLE_MASK, Pen, Xform, colorref}; +use crate::canvas::{BS_NULL, Brush, Canvas, Font, Obj, PS_NULL, PS_STYLE_MASK, Pen, Xform, colorref}; use crate::dib::{self, Blend}; use crate::geom::{self, ArcKind, P}; use crate::{Error, Frame, MAX_PATH_OPS}; @@ -15,6 +15,9 @@ const SRCCOPY: u32 = 0x00CC_0020; /// LogPenEx pen type bits; PS_COSMETIC pens are one device pixel wide. const PS_TYPE_MASK: u32 = 0x000F_0000; const PS_COSMETIC: u32 = 0; +/// EXTTEXTOUT options: the string holds glyph indices, and the dx array holds (x, y) pairs. +const ETO_GLYPH_INDEX: u32 = 0x10; +const ETO_PDY: u32 = 0x2000; pub(crate) fn is_emf(b: Bytes) -> bool { b.u32(0) == Some(1) && b.u32(40) == Some(EMF_SIGNATURE) @@ -100,6 +103,10 @@ fn record(cv: &mut Canvas, typ: u32, r: Bytes) { 38 => create_pen(cv, r), 95 => create_ext_pen(cv, r), 39 => create_brush(cv, r), + 82 => create_font(cv, r), + 22 => r.u32(8).map(|a| cv.set_text_align(a)), + 24 => r.u32(8).map(|c| cv.set_text_color(colorref(c))), + 84 => ext_text_out_w(cv, r), 40 => r.u32(8).map(|i| cv.delete_object(i)), 27 => xy32(r, 8).map(|p| cv.move_to(p)), 54 => xy32(r, 8).map(|p| cv.line_to(p)), @@ -250,6 +257,55 @@ fn create_brush(cv: &mut Canvas, r: Bytes) -> Option<()> { Some(()) } +/// EMR_EXTCREATEFONTINDIRECTW: handle, then a LogFont (height, width, escapement, orientation, weight, +/// italic, ..., face name of 32 UTF-16 units at 40). +fn create_font(cv: &mut Canvas, r: Bytes) -> Option<()> { + let slot = r.u32(8)? as usize; + let mut face = [0u16; 32]; + for (i, u) in face.iter_mut().enumerate() { + *u = r.u16(40 + i * 2).unwrap_or(0); + } + let font = Font { height: r.i32(12)?, escapement: r.i32(20)?, weight: r.i32(28)?, italic: *r.0.get(32)? != 0, face }; + cv.add_object(Some(slot), Obj::Font(font)); + Some(()) +} + +/// EMR_EXTTEXTOUTW: an EmrText at 36 with the reference point, the character count at 44, the string +/// offset at 48, the options at 52 and the dx array offset at 72 (offsets from the record start). +/// Glyph-index strings are skipped: they cannot be mapped back to characters. +fn ext_text_out_w(cv: &mut Canvas, r: Bytes) -> Option<()> { + let at = xy32(r, 36)?; + let n = r.u32(44)? as usize; + let (off, options, off_dx) = (r.u32(48)? as usize, r.u32(52)?, r.u32(72)? as usize); + if options & ETO_GLYPH_INDEX != 0 || n == 0 { + return None; + } + let units: Vec = r.slice(off, n.checked_mul(2)?)?.as_chunks::<2>().0.iter().map(|c| u16::from_le_bytes(*c)).collect(); + // dx has one entry per UTF-16 unit (two with ETO_PDY); a character takes the sum of its units. + let step = if options & ETO_PDY != 0 { 8 } else { 4 }; + let raw_dx: Vec = match r.slice(off_dx, n.checked_mul(step)?) { + Some(b) if off_dx != 0 => b.chunks_exact(step).filter_map(|c| Some(f64::from(i32::from_le_bytes(c.get(..4)?.try_into().ok()?)))).collect(), + _ => Vec::new(), + }; + let mut text = String::new(); + let mut dx = Vec::new(); + let mut i = 0; + for ch in char::decode_utf16(units.iter().copied()) { + let c = ch.unwrap_or(char::REPLACEMENT_CHARACTER); + // A replacement stands for one unpaired unit. + let w = if c == char::REPLACEMENT_CHARACTER { 1 } else { c.len_utf16() }; + if !raw_dx.is_empty() { + dx.push(raw_dx.get(i..i + w).map_or(0.0, |s| s.iter().sum())); + } + i += w; + text.push(c); + } + let text = text.trim_end_matches('\0').to_string(); + dx.truncate(text.chars().count()); + cv.text(at, text, &dx); + Some(()) +} + /// BITBLT (76) and STRETCHBLT (77): dest at 24..40, rop at 40, source x/y at 44, bmi at 84, bits at /// 92; STRETCHBLT adds the source w/h at 100. A source-less rop fills the destination instead. fn bitblt(cv: &mut Canvas, r: Bytes, stretch: bool) -> Option<()> { diff --git a/crates/metafile/src/lib.rs b/crates/metafile/src/lib.rs index cc2f64e5cd..473d69baf6 100644 --- a/crates/metafile/src/lib.rs +++ b/crates/metafile/src/lib.rs @@ -1,5 +1,5 @@ //! WordCraft metafiles: reads Windows Metafiles (WMF) and Enhanced Metafiles (EMF) into a neutral -//! vector model of filled and stroked paths and RGBA bitmaps, in a picture frame with y down. +//! vector model of filled and stroked paths, RGBA bitmaps and text runs, in a picture frame with y down. //! //! The reader is pure Rust over byte slices (no file system), so it builds for wasm. Every read is //! bounds-checked and every count is capped; hostile input yields a shorter picture or an [`Error`], @@ -21,9 +21,9 @@ mod wmf; mod tests; use bytes::Bytes; -use canvas::Raw; +use canvas::{Raw, RawText}; use geom::{Op, P}; -pub use place::{MAX_BITMAP_SIDE, PlacedItem}; +pub use place::{MAX_BITMAP_SIDE, PlacedItem, TextLayout}; /// Items kept from one file. Past the cap the parse stops and keeps what it has. pub(crate) const MAX_ITEMS: usize = 200_000; @@ -60,6 +60,46 @@ pub enum Item { }, /// A bitmap stretched into `rect` = `[x, y, w, h]` (picture units), RGBA8 rows top-down. Bitmap { rect: [f32; 4], width: u32, height: u32, rgba: Vec }, + /// A run of text; the renderers shape it with the named font (see [`Text::layout`]). + Text(Text), +} + +/// A text run, in picture units. +#[derive(Debug, Clone, PartialEq)] +pub struct Text { + pub text: String, + /// The reference point that `halign` and `valign` refer to. + pub x: f32, + pub y: f32, + /// Font height: the em size, or the cell height (ascent + descent) when `cell` is set. + pub size: f32, + pub cell: bool, + pub font: String, + pub bold: bool, + pub italic: bool, + pub color: [u8; 4], + /// Baseline direction in radians, clockwise from +x (y down). + pub angle: f32, + pub halign: HAlign, + pub valign: VAlign, + /// Advance of each character along the baseline; empty when the font's own advances apply. + pub dx: Vec, +} + +/// Where the reference point sits along the run. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum HAlign { + Left, + Center, + Right, +} + +/// Where the reference point sits across the run. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum VAlign { + Top, + Baseline, + Bottom, } #[derive(Debug, Clone, Copy, PartialEq)] @@ -137,11 +177,45 @@ fn build(raw: Vec, frame: Option) -> Picture { let rect = [f(x)?, f(y)?, f(r[2] * sx)?, f(r[3] * sy)?]; Some(Item::Bitmap { rect, width: img.w as u32, height: img.h as u32, rgba: img.px }) } + Raw::Text(t) => text_item(t, tf, sx, sy), }); let items = items.collect(); Picture { width: f(size.0).unwrap_or(0.0), height: f(size.1).unwrap_or(0.0), items } } +/// A device-space text run mapped into the picture: the reference point through `tf`, lengths by the +/// frame scale. TA_* flags become the alignment. +fn text_item(t: RawText, tf: impl Fn(P) -> P, sx: f64, sy: f64) -> Option { + let (x, y) = tf(t.origin); + let halign = match t.align & 6 { + 2 => HAlign::Right, + 6 => HAlign::Center, + _ => HAlign::Left, + }; + let valign = match t.align & 24 { + 8 => VAlign::Bottom, + 24 => VAlign::Baseline, + _ => VAlign::Top, + }; + let dx = t.dx.iter().map(|d| f(d * sx.abs())).collect::>>()?; + let size = f(t.size * sy.abs()).filter(|s| *s > 0.0)?; + Some(Item::Text(Text { + text: t.text, + x: f(x)?, + y: f(y)?, + size, + cell: t.cell, + font: t.font, + bold: t.bold, + italic: t.italic, + color: t.color, + angle: f(t.angle).unwrap_or(0.0), + halign, + valign, + dx, + })) +} + fn f(v: f64) -> Option { let v = v as f32; v.is_finite().then_some(v) @@ -201,6 +275,7 @@ fn bbox(raw: &[Raw]) -> Option<[f64; 4]> { grow((rect[0], rect[1])); grow((rect[0] + rect[2], rect[1] + rect[3])); } + Raw::Text(t) => grow(t.origin), } } b diff --git a/crates/metafile/src/place.rs b/crates/metafile/src/place.rs index 68f0795531..73ac10cc3b 100644 --- a/crates/metafile/src/place.rs +++ b/crates/metafile/src/place.rs @@ -3,7 +3,7 @@ use kurbo::{Affine, BezPath, Rect, Shape}; -use crate::{Item, Picture, Seg}; +use crate::{HAlign, Item, Picture, Seg, Text, VAlign}; /// Largest bitmap side the renderers draw: within the PDF image limit and the rasteriser's 16-bit sides. pub const MAX_BITMAP_SIDE: u32 = 30_000; @@ -19,6 +19,72 @@ pub enum PlacedItem { Path { path: BezPath, fill: Option<[u8; 4]>, stroke: Option<([u8; 4], f64)>, even_odd: bool }, /// `Picture::items[index]`, a bitmap, stretched into `rect`. Bitmap { index: usize, rect: Rect }, + /// `Picture::items[index]`, a text run. `tf` maps the run's own space (picture units, the + /// reference point at the origin, the baseline along +x, y down) to the target. + Text { index: usize, tf: Affine }, +} + +/// A text run laid out with a font's metrics: the em size (in the run's own units, see +/// [`PlacedItem::Text`]), the baseline's y and each glyph's (x, y), alignment included. +pub struct TextLayout { + pub em: f64, + pub baseline: f64, + pub glyphs: Vec<(f64, f64)>, +} + +impl Text { + /// Lays out shaped glyphs, given as (cluster byte offset, x advance, x offset, y offset) in font + /// units of a font with `upem` units per em, `ascent` above and `descent` below the baseline. + /// The per-character advances `dx` place each cluster when there is one per character. + pub fn layout(&self, upem: f64, ascent: f64, descent: f64, glyphs: &[(usize, f64, f64, f64)]) -> Option { + let upem = if upem.is_finite() && upem > 0.0 { upem } else { 1000.0 }; + let cell = ascent + descent; + let size = f64::from(self.size); + let em = if self.cell && cell.is_finite() && cell > 0.0 { size * upem / cell } else { size }; + if !em.is_finite() || em <= 0.0 { + return None; + } + let k = em / upem; + let starts: Vec = self.text.char_indices().map(|(i, _)| i).collect(); + let dx = (self.dx.len() == starts.len() && !starts.is_empty()).then(|| { + let mut cum = Vec::with_capacity(self.dx.len() + 1); + let mut x = 0.0; + cum.push(x); + for d in &self.dx { + x += f64::from(*d); + cum.push(x); + } + cum + }); + let mut out = Vec::with_capacity(glyphs.len()); + let (mut pen, mut cluster_pen, mut last) = (0.0, 0.0, None); + for &(cluster, adv, xo, yo) in glyphs { + let ci = starts.partition_point(|&b| b <= cluster).saturating_sub(1); + if last != Some(ci) { + cluster_pen = pen; + last = Some(ci); + } + let x = match &dx { + Some(cum) => cum.get(ci).copied().unwrap_or(0.0) + pen - cluster_pen, + None => pen, + }; + out.push((x + xo * k, -yo * k)); + pen += adv * k; + } + let width = dx.as_ref().and_then(|c| c.last().copied()).unwrap_or(pen); + let hoff = match self.halign { + HAlign::Left => 0.0, + HAlign::Center => -width / 2.0, + HAlign::Right => -width, + }; + let baseline = match self.valign { + VAlign::Top => ascent * k, + VAlign::Baseline => 0.0, + VAlign::Bottom => -descent * k, + }; + let glyphs = out.into_iter().map(|(x, y)| (x + hoff, y + baseline)).collect(); + Some(TextLayout { em, baseline, glyphs }) + } } impl Picture { @@ -63,6 +129,14 @@ impl Picture { out.push(PlacedItem::Bitmap { index, rect }); } } + Item::Text(t) => { + let (x, y) = (f64::from(t.x), f64::from(t.y)); + let at = tf * kurbo::Point::new(x, y); + if in_range(Rect::new(at.x, at.y, at.x, at.y)) { + let tf = tf * Affine::translate((x, y)) * Affine::rotate(f64::from(t.angle)); + out.push(PlacedItem::Text { index, tf }); + } + } } } out diff --git a/crates/metafile/src/tests.rs b/crates/metafile/src/tests.rs index 8a93e14111..e6ffc8c340 100644 --- a/crates/metafile/src/tests.rs +++ b/crates/metafile/src/tests.rs @@ -120,7 +120,7 @@ const DIB24_RGBA: [u8; 16] = [255, 0, 0, 255, 255, 255, 255, 255, 0, 0, 255, 255 fn path_segs(item: &Item) -> (&Vec, Option<[u8; 4]>, Option<([u8; 4], f32)>, bool) { match item { Item::Path { segs, fill, stroke, even_odd } => (segs, *fill, *stroke, *even_odd), - Item::Bitmap { .. } => panic!("expected a path"), + Item::Bitmap { .. } | Item::Text(_) => panic!("expected a path"), } } diff --git a/crates/pdf/src/lib.rs b/crates/pdf/src/lib.rs index 5b302b13af..b3fb9d48bb 100644 --- a/crates/pdf/src/lib.rs +++ b/crates/pdf/src/lib.rs @@ -1175,7 +1175,7 @@ impl Exporter<'_> { fn picture(&mut self, s: &mut Surface, rect: &Rect, media: &str, crop: &[f32; 4], alpha: f32) { let Some(clip) = rect_path(rect) else { return }; if let Some(v) = self.vector(media) { - vector_picture(s, &v, rect, crop, &clip, alpha); + vector_picture(self, s, &v, rect, crop, &clip, alpha); return; } let Some(img) = self.image(media) else { @@ -1496,6 +1496,58 @@ impl Exporter<'_> { Some(o) } + /// Draws a metafile text run as PDF text (outlines when the font cannot be embedded). `tf` maps + /// the run's own space (see [`PlacedItem::Text`]) to the page. + fn vector_text(&mut self, s: &mut Surface, t: &wordcraft_metafile::Text, tf: kurbo::Affine) { + let face = wordcraft_fonts::word::resolve(&t.font, t.bold, t.italic).face; + let shaped = wordcraft_fonts::shape(&face, &t.text, &[], |c| c); + let glyphs: Vec<_> = shaped.iter().map(|g| (g.cluster, f64::from(g.x_advance), f64::from(g.x_offset), f64::from(g.y_offset))).collect(); + let Some(lay) = t.layout(face.upem, face.ascent, face.descent, &glyphs) else { return }; + let font = self.font(&face); + if font.is_none() && !self.outlined.contains(&face.id()) { + return; + } + let em = lay.em as f32; + let c = tf.as_coeffs().map(|v| v as f32); + if !c.iter().all(|v| v.is_finite()) || !em.is_finite() { + return; + } + let (paint, opacity) = paint_of(t.color); + s.push_transform(&Transform::from_row(c[0], c[1], c[2], c[3], c[4], c[5])); + s.set_stroke(None); + s.set_fill(Some(Fill { paint: paint.into(), opacity, rule: FillRule::NonZero })); + match font { + Some(font) => { + let upem = face.upem.max(1.0) as f32; + let kg: Vec = shaped + .iter() + .enumerate() + .map(|(i, g)| { + let end = shaped.iter().skip(i + 1).map(|n| n.cluster).find(|cl| *cl > g.cluster).unwrap_or(t.text.len()); + let start = g.cluster.min(end); + let x = lay.glyphs.get(i).map_or(0.0, |p| p.0); + let adv = match lay.glyphs.get(i + 1) { + Some(n) => ((n.0 - x) / lay.em) as f32, + None => g.x_advance as f32 / upem, + }; + let dy = lay.glyphs.get(i).map_or(0.0, |p| ((lay.baseline - p.1) / lay.em) as f32); + KrillaGlyph::new(GlyphId::new(g.gid), adv, 0.0, dy, 0.0, start..end, None) + }) + .collect(); + let x0 = lay.glyphs.first().map_or(0.0, |p| p.0) as f32; + s.draw_glyphs(Point::from_xy(x0, lay.baseline as f32), &kg, font, &t.text, em, false); + } + None => { + let pen: Vec<(u32, f32, f32)> = shaped.iter().zip(&lay.glyphs).map(|(g, (x, y))| (g.gid, *x as f32, *y as f32)).collect(); + if let Some(path) = glyph_outlines(&face, em, &pen) { + s.draw_path(&path); + } + } + } + s.set_fill(None); + s.pop(); + } + fn watermark(&mut self, s: &mut Surface, wm: &wordcraft_doc::Watermark, w: f32, h: f32) { let r = wordcraft_fonts::word::resolve(&wm.font, false, false); let face = r.face; @@ -1588,8 +1640,9 @@ const HAIRLINE: f64 = 0.25; /// Draws a metafile picture into the clip (the picture frame): paths are filled and stroked in page /// space (strokes keep their width under non-uniform scaling), bitmaps stretched, all faded by `alpha`. -/// Every path sets its fill and stroke, so none carries over; strokes have round caps and joins. -fn vector_picture(s: &mut Surface, v: &Vector, r: &Rect, crop: &[f32; 4], clip: &Path, alpha: f32) { +/// Every path sets its fill and stroke, so none carries over; strokes have round caps and joins. Text +/// runs are drawn as text. +fn vector_picture(ex: &mut Exporter, s: &mut Surface, v: &Vector, r: &Rect, crop: &[f32; 4], clip: &Path, alpha: f32) { let target = kurbo::Rect::new(f64::from(r.x), f64::from(r.y), f64::from(r.x + r.w), f64::from(r.y + r.h)); let fade = alpha < 0.999; if fade { @@ -1627,6 +1680,11 @@ fn vector_picture(s: &mut Surface, v: &Vector, r: &Rect, crop: &[f32; 4], clip: s.draw_image(img.clone(), size); s.pop(); } + PlacedItem::Text { index, tf } => { + if let Some(wordcraft_metafile::Item::Text(t)) = v.pic.items.get(index) { + ex.vector_text(s, t, tf); + } + } } } s.set_fill(None); diff --git a/crates/pdf/src/tests.rs b/crates/pdf/src/tests.rs index 8e01a0f15b..2144f2cf39 100644 --- a/crates/pdf/src/tests.rs +++ b/crates/pdf/src/tests.rs @@ -965,6 +965,44 @@ fn cosmetic_metafile_pens_are_thin_but_visible() { assert!(raw.contains("0.25 w"), "the hairline is a quarter point wide"); } +/// EMF text records: EXTCREATEFONTINDIRECTW (Arial, 20 units high) in slot 1, SELECTOBJECT, +/// SETTEXTCOLOR (black), then EXTTEXTOUTW `text` at (5, 40) with no dx array. +fn emf_text_records(text: &str) -> Vec> { + let mut font = [82i32.to_le_bytes(), 104u32.to_le_bytes(), 1i32.to_le_bytes(), (-20i32).to_le_bytes()].concat(); + font.extend([0i32, 0, 0, 400, 0, 0].iter().flat_map(|v| v.to_le_bytes())); + let mut name: Vec = "Arial".encode_utf16().flat_map(|u| u.to_le_bytes()).collect(); + name.resize(64, 0); + font.extend(name); + let units: Vec = text.encode_utf16().flat_map(|u| u.to_le_bytes()).collect(); + let padded = units.len().div_ceil(4) * 4; + let mut out = [84u32.to_le_bytes(), ((76 + padded) as u32).to_le_bytes()].concat(); + out.extend([0i32, 0, 100, 100, 1, 0, 0, 5, 40, text.encode_utf16().count() as i32, 76, 0, 0, 0, 0, 0, 0].iter().flat_map(|v| v.to_le_bytes())); + out.extend(units); + out.resize(76 + padded, 0); + vec![font, emf_rec(37, &[1]), emf_rec(24, &[0]), out] +} + +/// #1764: the text of an EMF picture (EXTTEXTOUTW) is drawn, so it is in the PDF's text layer. +#[test] +fn metafile_text_records_are_drawn_as_text() { + let mut recs = emf_text_records("Cell42"); + recs.push(emf_rec(14, &[0; 3])); + let mut d = Document::new(); + let media = d.add_media(emf_file(&recs), "emf"); + let mut p = Paragraph::with_text("Figure: ", CharProps::default()); + let n = p.len(); + p.insert_object( + n, + InlineObject::Image { media, w: 70.0, h: 70.0, alt: String::new(), float: Default::default(), crop: [0.0; 4], ole: None }, + &CharProps::default(), + ) + .unwrap(); + d.body = vec![para_block(p)]; + let bytes = export(&d, &PdfOptions { compress: false, tagged: false, ..Default::default() }).unwrap(); + let text = extract_text(&bytes).concat(); + assert!(text.contains("Cell42"), "the metafile's text is drawn: {text:?}"); +} + /// #455: a link to a bookmark after a page break inside its paragraph goes to the page the /// bookmark is on, not the paragraph's first page. #[test] diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index ac79f73aee..02a9262a93 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -248,6 +248,7 @@ impl Vector { .map(|it| match it { Item::Path { segs, .. } => segs.len() * std::mem::size_of::(), Item::Bitmap { .. } => 0, + Item::Text(t) => t.text.len() + t.dx.len() * 4, }) .sum(); px + segs @@ -326,6 +327,12 @@ fn draw_vector(ctx: &mut RenderContext, v: &Vector, r: &kurbo::Rect, crop: &[f32 ctx.fill_rect(&rect); ctx.reset_paint_transform(); } + PlacedItem::Text { index, tf } => { + if let Some(Item::Text(t)) = v.pic.items.get(index) { + draw_vector_text(ctx, t, view * tf); + } + ctx.set_transform(view); + } } } ctx.set_fill_rule(peniko::Fill::NonZero); @@ -335,6 +342,22 @@ fn draw_vector(ctx: &mut RenderContext, v: &Vector, r: &kurbo::Rect, crop: &[f32 } } +/// Draws a metafile text run: `tf` maps the run's own space (see [`PlacedItem::Text`]) to the device. +fn draw_vector_text(ctx: &mut RenderContext, t: &wordcraft_metafile::Text, tf: Affine) { + let face = wordcraft_fonts::word::resolve(&t.font, t.bold, t.italic).face; + let shaped = wordcraft_fonts::shape(&face, &t.text, &[], |c| c); + let glyphs: Vec<_> = shaped.iter().map(|g| (g.cluster, f64::from(g.x_advance), f64::from(g.x_offset), f64::from(g.y_offset))).collect(); + let Some(lay) = t.layout(face.upem, face.ascent, face.descent, &glyphs) else { return }; + let k = lay.em / face.upem.max(1.0); + ctx.set_paint(rgba_color(t.color)); + let db = FontDb::global(); + for (g, (x, y)) in shaped.iter().zip(&lay.glyphs) { + let o = db.outline(&face, g.gid); + ctx.set_transform(tf * Affine::translate((*x, *y)) * Affine::scale(k)); + ctx.fill_path(&o); + } +} + /// One device pixel in page points under `view` (its mean axis scale); 1 for a degenerate transform. fn device_pixel(view: Affine) -> f64 { let c = view.as_coeffs(); @@ -1582,6 +1605,52 @@ mod tests { assert!(darkest < 140, "the hairline should be clearly visible: {darkest}"); } + /// #1764: an EMF picture's text (EXTCREATEFONTINDIRECTW, SELECTOBJECT, SETTEXTCOLOR, EXTTEXTOUTW) + /// is painted, not dropped. + #[test] + fn metafile_text_records_are_painted() { + let mut font = emf_rec(82, &le32(&[1, -20, 0, 0, 0, 700, 0, 0])); + let mut name: Vec = "Arial".encode_utf16().flat_map(|u| u.to_le_bytes()).collect(); + name.resize(64, 0); + font.extend(name); + font[4..8].copy_from_slice(&104u32.to_le_bytes()); + // "WWWW" at (5, 40), top-aligned, no dx array. + let mut text = emf_rec(84, &le32(&[0, 0, 100, 100, 1, 0, 0, 5, 40, 4, 76, 0, 0, 0, 0, 0, 0])); + text.extend("WWWW".encode_utf16().flat_map(|u| u.to_le_bytes())); + text[4..8].copy_from_slice(&84u32.to_le_bytes()); + let mut emf = red_right_half_emf()[..88].to_vec(); + emf.extend([font, emf_rec(37, &le32(&[1])), emf_rec(24, &le32(&[0])), text, emf_rec(14, &[0; 12])].concat()); + let mut d = Document::new(); + let media = d.add_media(emf, "emf"); + let mut p = wordcraft_doc::Paragraph::new(); + let obj = wordcraft_doc::para::InlineObject::Image { + media, + w: 70.0, + h: 70.0, + alt: String::new(), + float: Default::default(), + crop: [0.0; 4], + ole: None, + }; + p.insert_object(0, obj, &Default::default()).unwrap(); + d.body = vec![wordcraft_doc::para_block(p)]; + let l = layout(&d, &mut LayoutCache::new(), &LayoutOptions::default()); + let opts = RenderOptions::default(); + let rect = page_display(&d, &l.pages[0], &opts.display) + .iter() + .find_map(|i| match i { + Draw::Image { rect, .. } => Some(*rect), + _ => None, + }) + .expect("a picture on the page"); + let scale = 2.0; + let img = render_page(&d, &l.pages[0], scale, &opts); + let (x0, y0) = ((rect.x * scale) as u32, (rect.y * scale) as u32); + let (x1, y1) = (((rect.x + rect.w) * scale) as u32, ((rect.y + rect.h) * scale) as u32); + let dark = (y0..y1).flat_map(|y| (x0..x1).map(move |x| (x, y))).filter(|&(x, y)| img.pixel(x, y)[0] < 100).count(); + assert!(dark > 50, "the text is painted inside the picture: {dark} dark pixels"); + } + #[test] fn unreadable_metafile_keeps_the_grey_box() { let mut d = Document::new(); From a4b3a74655fd4dafb60685a4ed285fb9c2573020 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20=C4=90=E1=BB=A9c?= Date: Thu, 15 Oct 2026 08:53:00 +0700 Subject: [PATCH 016/336] ODT: read headers and footers from each master page (#863) Headers and footers of an .odt live on its master pages in styles.xml (style:header, -left, -first and the footer equivalents); import skipped them, so a letterhead or logo at the top of every page was lost. They are now read with the body reader into header/footer parts and named on their master page's section, so each section shows its own: every page, left pages (turns on different odd and even pages) and the first page (turns on a different first page). Hidden ones (style:display="false") are left out. A master page without a header or footer gets an empty one rather than continuing the previous section's, as each ODF master page stands alone. --- crates/formats/src/odt.rs | 242 +++++++++++++++++++-------- crates/formats/src/odt/page_parts.rs | 156 +++++++++++++++++ docs/file-format-parity.md | 5 +- 3 files changed, 332 insertions(+), 71 deletions(-) create mode 100644 crates/formats/src/odt/page_parts.rs diff --git a/crates/formats/src/odt.rs b/crates/formats/src/odt.rs index da438604b7..ef8d8c9a0e 100644 --- a/crates/formats/src/odt.rs +++ b/crates/formats/src/odt.rs @@ -40,6 +40,8 @@ const MAX_OPEN_INSERTIONS: usize = 64; /// Longest change author or date kept, in characters. const MAX_INFO_CHARS: usize = 256; +mod page_parts; + // --------------------------------------------------------------------------------------------- // Export @@ -1351,6 +1353,81 @@ struct Body<'a> { active_ins: Vec<(String, RevMark)>, } +impl<'a> Body<'a> { + fn new(styles: &'a Styles, pages: &'a Pages, zip: &'a mut dyn FnMut(&str) -> Option>) -> Self { + let page = pages.first.clone().unwrap_or_default(); + Body { + styles, + zip, + containers: vec![Vec::new()], + fmt: Vec::new(), + para: None, + para_cont: false, + lists: Vec::new(), + lists_saved: Vec::new(), + item_used: Vec::new(), + tables: Vec::new(), + cells: Vec::new(), + in_text: false, + skip: 0, + depth: 0, + frame: None, + in_desc: false, + pages, + sec: page.clone(), + page, + sec_from: 0, + sections: 0, + cols_at: None, + in_tc: false, + track: None, + regions: HashMap::new(), + region_count: 0, + region: None, + info_at: None, + info_field: None, + del: None, + active_ins: Vec::new(), + } + } + + fn feed(&mut self, ev: Ev) { + match ev { + Ev::Start(q, a) => { + if self.depth < MAX_XML_DEPTH { + self.start(&q, &a); + } else { + self.depth += 1; + } + } + Ev::End(q) => { + if self.depth <= MAX_XML_DEPTH { + self.end(&q); + } else { + self.depth -= 1; + } + } + Ev::Text(t) => self.text(&t), + } + } + + /// The blocks read, with anything still open closed. + fn take_blocks(&mut self) -> Vec { + self.end_para(); + while self.containers.len() > 1 { + let c = self.containers.pop().unwrap_or_default(); + self.container().extend(c); + } + let mut blocks = self.containers.pop().unwrap_or_default(); + for t in self.tables.drain(..) { + if !t.rows.is_empty() { + blocks.push(FBlock::Table(FTable { rows: t.rows, widths: Vec::new(), borderless: false })); + } + } + blocks + } +} + impl Body<'_> { fn container(&mut self) -> &mut Vec { if self.containers.is_empty() { @@ -2139,11 +2216,11 @@ pub fn parse(bytes: &[u8]) -> Result { /// [`parse`], with the last section's properties (the others are on the paragraphs that end them). fn parse_full(bytes: &[u8]) -> Result<(Flow, SectionProps), String> { - parse_tracked(bytes).map(|(f, last, _)| (f, last)) + parse_tracked(bytes).map(|(f, last, ..)| (f, last)) } /// [`parse_full`], plus whether change tracking is on (`None`: the document has no tracked changes). -fn parse_tracked(bytes: &[u8]) -> Result<(Flow, SectionProps, Option), String> { +fn parse_tracked(bytes: &[u8]) -> Result<(Flow, SectionProps, Option, page_parts::Parts), String> { let mut z = zip::ZipArchive::new(Cursor::new(bytes)).map_err(|e| format!("ODT: not a valid package ({e})"))?; if let Some(mt) = read_entry(&mut z, "mimetype") { let mt = String::from_utf8_lossy(&mt); @@ -2154,11 +2231,11 @@ fn parse_tracked(bytes: &[u8]) -> Result<(Flow, SectionProps, Option), Str let content = read_entry(&mut z, "content.xml").ok_or("ODT: content.xml is missing")?; let mut styles = Styles::default(); let mut pages = Pages::default(); - if let Some(s) = read_entry(&mut z, "styles.xml") { - styles.read(&s, false); - pages = read_pages(&s); + let styles_xml = read_entry(&mut z, "styles.xml"); + if let Some(s) = &styles_xml { + styles.read(s, false); + pages = read_pages(s); } - let page = pages.first.clone().unwrap_or_default(); styles.read(&content, true); let meta = read_entry(&mut z, "meta.xml").map(|m| read_meta(&m)).unwrap_or_default(); let mut pictures = Pictures::new(MAX_ENTRY); @@ -2166,67 +2243,10 @@ fn parse_tracked(bytes: &[u8]) -> Result<(Flow, SectionProps, Option), Str // A frame may point at any entry, as often as it likes: `Pictures` keeps that bounded. pictures.get(&mut z, path) }; - let mut body = Body { - styles: &styles, - zip: &mut fetch, - containers: vec![Vec::new()], - fmt: Vec::new(), - para: None, - para_cont: false, - lists: Vec::new(), - lists_saved: Vec::new(), - item_used: Vec::new(), - tables: Vec::new(), - cells: Vec::new(), - in_text: false, - skip: 0, - depth: 0, - frame: None, - in_desc: false, - pages: &pages, - sec: page.clone(), - page, - sec_from: 0, - sections: 0, - cols_at: None, - in_tc: false, - track: None, - regions: HashMap::new(), - region_count: 0, - region: None, - info_at: None, - info_field: None, - del: None, - active_ins: Vec::new(), - }; - events(&content, |ev| match ev { - Ev::Start(q, a) => { - if body.depth < MAX_XML_DEPTH { - body.start(&q, &a); - } else { - body.depth += 1; - } - } - Ev::End(q) => { - if body.depth <= MAX_XML_DEPTH { - body.end(&q); - } else { - body.depth -= 1; - } - } - Ev::Text(t) => body.text(&t), - }); - body.end_para(); - while body.containers.len() > 1 { - let c = body.containers.pop().unwrap_or_default(); - body.container().extend(c); - } - let mut blocks = body.containers.pop().unwrap_or_default(); - for t in body.tables.drain(..) { - if !t.rows.is_empty() { - blocks.push(FBlock::Table(FTable { rows: t.rows, widths: Vec::new(), borderless: false })); - } - } + let parts = styles_xml.as_deref().map(|x| page_parts::read(x, &styles, &mut pages, &mut fetch)).unwrap_or_default(); + let mut body = Body::new(&styles, &pages, &mut fetch); + events(&content, |ev| body.feed(ev)); + let mut blocks = body.take_blocks(); // An open section with nothing in it (after a column section at the end) isn't one: the // section before it ends the document. let mut last = body.sec; @@ -2240,14 +2260,15 @@ fn parse_tracked(bytes: &[u8]) -> Result<(Flow, SectionProps, Option), Str blocks.push(FBlock::Para(Para::default())); } let track = body.track; - Ok((Flow { blocks, meta }, last, track)) + Ok((Flow { blocks, meta }, last, track, parts)) } /// Parse an `.odt` package into a document. pub fn import(bytes: &[u8]) -> Result { - let (flow, last, track) = parse_tracked(bytes)?; + let (flow, last, track, parts) = parse_tracked(bytes)?; let mut doc = model::to_doc(&flow); doc.last_section = last; + page_parts::add(&mut doc, parts); if let Some(t) = track { doc.settings.track_changes = t; } @@ -2260,6 +2281,7 @@ pub fn import(bytes: &[u8]) -> Result { struct Pages { masters: HashMap, first: Option, + first_name: Option, } fn read_pages(xml: &[u8]) -> Pages { @@ -2311,6 +2333,7 @@ fn read_pages(xml: &[u8]) -> Pages { } if pages.first.is_none() { pages.first = Some(sec.clone()); + pages.first_name = Some(name.clone()); } pages.masters.entry(name).or_insert(sec); } @@ -2610,6 +2633,87 @@ mod tests { package(&content, Some(&styles)) } + fn part_text(d: &Document, id: Option) -> String { + d.plain_text(wordcraft_doc::StoryRef::Part(id.expect("part"))) + } + + #[test] + fn headers_and_footers_follow_their_master_page() { + // #863: headers and footers live on the master pages in styles.xml. + let styles = format!( + r#" + + + + + + ACME Letterhead + Left page + First page + Page 1 + Hidden + + Wide header + "# + ); + let content = format!( + r#" + + + Upright bodyWide body + "# + ); + let d = import(&package(&content, Some(&styles))).unwrap(); + let secs = d.sections(); + assert_eq!(secs.len(), 2); + let (up, wide) = (&secs[0].1, &secs[1].1); + assert_eq!(part_text(&d, up.headers.default), "ACME Letterhead"); + assert_eq!(part_text(&d, up.headers.even), "Left page"); + assert_eq!(part_text(&d, up.headers.first), "First page"); + assert_eq!(part_text(&d, up.footers.default), "Page 1"); + assert!(up.title_page && d.settings.even_odd_headers); + // The hidden left footer: left pages show the right pages' footer. + assert_eq!((up.footers.even, up.footers.first), (up.footers.default, None)); + assert_eq!(part_text(&d, wide.headers.default), "Wide header"); + assert_eq!((wide.headers.even, wide.title_page), (wide.headers.default, false)); + // Its master page has no footer: an empty one, not the previous section's. + assert_eq!(part_text(&d, wide.footers.default), ""); + assert_eq!(d.parts.len(), 6); + let id = up.headers.default.unwrap(); + let Some(wordcraft_doc::Block::Para(p)) = d.parts[&id].blocks.first().map(|b| &**b) else { panic!("no header paragraph") }; + assert!(p.runs.iter().all(|r| r.props.bold == Some(true)), "{:?}", p.runs); + } + + #[test] + fn documents_without_headers_get_none() { + let content = format!( + r#"Body"# + ); + let styles = format!( + r#""# + ); + for d in [import(&package(&content, Some(&styles))).unwrap(), import(&package(&content, None)).unwrap()] { + assert!(d.parts.is_empty()); + assert_eq!(d.last_section.headers, Default::default()); + assert!(!d.last_section.title_page && !d.settings.even_odd_headers); + } + } + + #[test] + fn deeply_nested_header_content_does_not_panic() { + let n = 4 * MAX_XML_DEPTH; + let (open, close) = ("".repeat(n), "".repeat(n)); + let styles = format!( + r#"{open}Deep{close}After"# + ); + let content = format!( + r#"Body"# + ); + let d = import(&package(&content, Some(&styles))).unwrap(); + assert!(d.last_section.headers.default.is_some()); + assert_eq!(part_text(&d, d.last_section.footers.default), "After"); + } + #[test] fn master_pages_and_column_sections_become_sections() { use wordcraft_doc::section::SectionStart; diff --git a/crates/formats/src/odt/page_parts.rs b/crates/formats/src/odt/page_parts.rs new file mode 100644 index 0000000000..0a142590e4 --- /dev/null +++ b/crates/formats/src/odt/page_parts.rs @@ -0,0 +1,156 @@ +//! Headers and footers of the master pages in `styles.xml` (ODF 1.3 §16.10), read with the body +//! reader (#863). +use std::collections::HashSet; + +use wordcraft_doc::{Part, PartKind}; + +use super::*; + +/// Most XML events kept for all headers and footers together; a slot past it is left out. +const MAX_EVENTS: usize = 1 << 20; + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Slot { + Header, + HeaderLeft, + HeaderFirst, + Footer, + FooterLeft, + FooterFirst, +} + +impl Slot { + fn of(q: &str) -> Option { + Some(match q { + "style:header" => Self::Header, + "style:header-left" => Self::HeaderLeft, + "style:header-first" | "loext:header-first" => Self::HeaderFirst, + "style:footer" => Self::Footer, + "style:footer-left" => Self::FooterLeft, + "style:footer-first" | "loext:footer-first" => Self::FooterFirst, + _ => return None, + }) + } + + fn footer(self) -> bool { + matches!(self, Self::Footer | Self::FooterLeft | Self::FooterFirst) + } + + fn set(self, sec: &mut SectionProps, id: u32) { + let set = if self.footer() { &mut sec.footers } else { &mut sec.headers }; + match self { + Self::Header | Self::Footer => set.default = Some(id), + Self::HeaderLeft | Self::FooterLeft => set.even = Some(id), + Self::HeaderFirst | Self::FooterFirst => { + set.first = Some(id); + sec.title_page = true; + } + } + } +} + +/// The headers and footers read, in part-id order from 1, and whether one is for left pages. +#[derive(Default)] +pub(super) struct Parts { + parts: Vec<(PartKind, Vec)>, + even_odd: bool, +} + +/// Read each visible header and footer of the master pages in `pages`, and name it on its master +/// page's section as part id 1, 2, … in order, so the sections the body starts carry them. +/// [`add`] puts those parts into the document. +pub(super) fn read(xml: &[u8], styles: &Styles, pages: &mut Pages, fetch: &mut dyn FnMut(&str) -> Option>) -> Parts { + let mut found: Vec<(String, Slot, Vec)> = Vec::new(); + let mut master: Option = None; + // The slot being read, its nesting depth and whether it is kept. + let mut slot: Option<(usize, bool)> = None; + let mut seen = HashSet::new(); + let mut room = MAX_EVENTS; + events(xml, |ev| { + if let Some((depth, keep)) = &mut slot { + match &ev { + Ev::End(_) if *depth == 0 => { + slot = None; + return; + } + Ev::Start(..) => *depth += 1, + Ev::End(_) => *depth -= 1, + Ev::Text(_) => {} + } + if *keep && room == 0 { + *keep = false; + found.pop(); + } + if *keep && let Some((.., evs)) = found.last_mut() { + room -= 1; + evs.push(ev); + } + return; + } + match ev { + Ev::Start(q, a) if q == "style:master-page" => { + master = get(&a, "style:name").filter(|n| pages.masters.contains_key(*n)).map(str::to_string); + } + Ev::End(q) if q == "style:master-page" => master = None, + Ev::Start(q, a) => { + if let (Some(m), Some(kind)) = (&master, Slot::of(&q)) { + let keep = get(&a, "style:display") != Some("false") && seen.insert((m.clone(), q.clone())); + if keep { + found.push((m.clone(), kind, Vec::new())); + } + slot = Some((0, keep)); + } + } + _ => {} + } + }); + let none = Pages::default(); + let mut out = Parts::default(); + for (name, kind, evs) in found { + let mut reader = Body::new(styles, &none, fetch); + reader.in_text = true; + for ev in evs { + reader.feed(ev); + } + out.parts.push((if kind.footer() { PartKind::Footer } else { PartKind::Header }, reader.take_blocks())); + if let Some(sec) = pages.masters.get_mut(&name) { + kind.set(sec, out.parts.len() as u32); + } + out.even_odd |= matches!(kind, Slot::HeaderLeft | Slot::FooterLeft); + } + // Each master page stands alone, but a section without its own header or footer continues + // the previous one's: give those an empty one, and left pages the right pages' one. + for footer in [false, true] { + let pick = |s: &SectionProps| if footer { s.footers } else { s.headers }; + if !pages.masters.values().any(|s| pick(s).default.is_some() || pick(s).even.is_some()) { + continue; + } + let mut empty = None; + for sec in pages.masters.values_mut() { + let set = if footer { &mut sec.footers } else { &mut sec.headers }; + if set.default.is_none() { + set.default = Some(*empty.get_or_insert_with(|| { + out.parts.push((if footer { PartKind::Footer } else { PartKind::Header }, Vec::new())); + out.parts.len() as u32 + })); + } + if out.even_odd && set.even.is_none() { + set.even = set.default; + } + } + } + if let Some(first) = pages.first_name.as_ref().and_then(|n| pages.masters.get(n)) { + pages.first = Some(first.clone()); + } + out +} + +/// Put the parts [`read`] found into `doc` under the ids its sections name. `doc` comes from +/// [`model::to_doc`], which adds no parts of its own. +pub(super) fn add(doc: &mut Document, parts: Parts) { + for (i, (kind, blocks)) in parts.parts.into_iter().enumerate() { + let blocks = model::part_blocks(doc, &blocks); + doc.parts.insert(i as u32 + 1, Part { kind, blocks, body: Default::default() }); + } + doc.settings.even_odd_headers |= parts.even_odd; +} diff --git a/docs/file-format-parity.md b/docs/file-format-parity.md index 36a3727396..003f301e59 100644 --- a/docs/file-format-parity.md +++ b/docs/file-format-parity.md @@ -1,6 +1,6 @@ # File-format parity with Microsoft Word -> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-14 · **Change:** trivial (VML freeforms read and kept on save; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: `flowChartOr` draws its circle and cross spokes, as in Word; previously trivial: RTF: Mac OS Roman and DOS text, #1653; previously trivial: VML text boxes keep their internal margins, fit to their text and vertical alignment; hidden drawings not shown; previously trivial: PDF export draws horizontal and vertical lines inside groups, #1474; previously trivial: symbol-font text exported as Unicode, #776; previously trivial: text-box margins, Resize shape to fit text and freeforms in groups, #743; previously trivial: formatted paste from the system clipboard's HTML and RTF, #687; previously trivial: independent complex-script formatting inheritance and round-trip regression tests; previously trivial: import/export fixes for TXT, RTF, ODT, HTML and LaTeX, #433–#440; previously major: first version; every format Word reads or writes, measured from the readers and writers on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4, plus Word for Windows' extra formats +> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-15 · **Change:** trivial (ODT headers and footers read from each master page, #863; previously trivial: VML freeforms read and kept on save; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: `flowChartOr` draws its circle and cross spokes, as in Word; previously trivial: RTF: Mac OS Roman and DOS text, #1653; previously trivial: VML text boxes keep their internal margins, fit to their text and vertical alignment; hidden drawings not shown; previously trivial: PDF export draws horizontal and vertical lines inside groups, #1474; previously trivial: symbol-font text exported as Unicode, #776; previously trivial: text-box margins, Resize shape to fit text and freeforms in groups, #743; previously trivial: formatted paste from the system clipboard's HTML and RTF, #687; previously trivial: independent complex-script formatting inheritance and round-trip regression tests; previously trivial: import/export fixes for TXT, RTF, ODT, HTML and LaTeX, #433–#440; previously major: first version; every format Word reads or writes, measured from the readers and writers on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4, plus Word for Windows' extra formats Every format Word opens or saves, what WordCraft does with it, and how it's tested. Fidelity is the share of a typical real-world file's content and formatting that survives (read: shows correctly; @@ -42,7 +42,7 @@ Weights are the share of a Word user's file traffic (estimate). Read/write: ✅ | PDF export | 8% | W | — | ✅ | ~80% | 6–10 | `crates/pdf/src/tests.rs` | krilla: embedded subsets, selectable text incl. field results (#133); Hebrew and Arabic copy and search as whole words (right-to-left glyphs written in visual order, #1383); All Caps and Small Caps text extracts as the capitals shown, as from Word's PDFs, and never changes the case of other text (#1473), links, outline, tagging. Fonts it can't subset become outlines (#132). Print Markup (#589): comments in balloons beside each page (scaled to its paper) and tracked changes; HTML/Markdown/text copies end with a comment list. Horizontal and vertical lines inside groups are drawn (#1474). No PDF/A option, no "best for printing/online" choice | | PDF open (PDF Reflow, Word for Windows) | 1% | R | ❌ | — | 0% | 30–50 | — | Converting a PDF into an editable document. A sibling app (PdfCraft) may supply the parser | | `.rtf` Rich Text Format | 3% | R/W | 🟡 | 🟡 | ~50% | 10–15 | `crates/formats/src/tests.rs` | ~1,500 lines: text, character/paragraph formatting, styles, lists, tables, pictures (`\shppict`, #154), fields, bookmarks. 8-bit text in the document's code page (`\ansicpg`) or the current font's (`\fcharset`, `\cpg`): Cyrillic, Central European, Greek, Turkish, Hebrew, Arabic, Baltic, Vietnamese, Thai (was Windows-1252 only), Mac OS Roman (`\mac`, `\fcharset77`) and DOS code pages (`\pc` 437, `\pca` 850, `\cpg` 437/737/850/852/857/866, `\fcharset254`) (#1653). Double-byte East Asian code pages (Japanese `\fcharset128`, Chinese, Korean) not decoded yet. Pasted from the system clipboard where the platform offers it (Windows, Wayland) when there is no HTML (#687). Tracked insertions and deletions read and written (`\revtbl`, `\revised`/`\deleted`, authors, `\revdttm` dates), so Accept/Reject All work after opening or a round trip; paragraph-mark and formatting revisions not yet. Sections on import (#1378): each `\sect` ends a section with its own page size and orientation, margins, header/footer distance, columns, break type, page-number restart, start and format, title page and headers/footers (those it leaves out link to the previous); export still writes one section. Thin: comments, notes, section export, RTL | -| `.odt` OpenDocument Text | 2% | R/W | 🟡 | 🟡 | ~50% | 8–12 | `crates/formats/src/tests.rs`, `crates/formats/src/odt.rs` unit tests, `crates/engine/tests/odt_sections.rs` | Styles, lists (#87), tables, pictures, headers/footers, page breaks (#86), page size, orientation and margins (#810), sections: one per master-page change on a paragraph or table (next page, with its own page size, orientation, margins, columns and page-numbering restart) and per columned `text:section` (continuous, with its columns and separator line) (#1379); tracked insertions and deletions with author and date, inserted and deleted paragraph marks and text inserted then deleted included (ODF change tracking; format changes read as none; pictures and tables inside a tracked deletion are dropped). No footnotes, comments, fields or RTL | +| `.odt` OpenDocument Text | 2% | R/W | 🟡 | 🟡 | ~50% | 8–12 | `crates/formats/src/tests.rs`, `crates/formats/src/odt.rs` unit tests, `crates/engine/tests/odt_sections.rs` | Styles, lists (#87), tables, pictures, page breaks (#86), headers and footers of each master page (every, left and first page) read on open, not yet written (#863), page size, orientation and margins (#810), sections: one per master-page change on a paragraph or table (next page, with its own page size, orientation, margins, columns and page-numbering restart) and per columned `text:section` (continuous, with its columns and separator line) (#1379); tracked insertions and deletions with author and date, inserted and deleted paragraph marks and text inserted then deleted included (ODF change tracking; format changes read as none; pictures and tables inside a tracked deletion are dropped). No footnotes, comments, fields or RTL | | `.txt` Plain text | 2% | R/W | ✅ | ✅ | ~95% | 1–2 | unit tests | Encoding detection. Files with other extensions (`.m3u`/`.m3u8` playlists, logs, config files, no extension) open as text when they are text, Word and RTF files by their signature; binary files are refused (#569). A Word or RTF extension that contradicts the content also opens by signature, as in Word: RTF saved as `.doc` (hh.ru résumés, accounting exports), a `.docx` renamed `.doc`, a Word 97-2003 file renamed `.docx`. Word's encoding choice dialog and line-ending options missing; such files save only through Save As | | `.htm/.html` Web Page and Web Page, Filtered | 1% | R/W | 🟡 | 🟡 | ~60% | 6–10 | `crates/formats/src/tests.rs` | Tables (#102), relative pictures embedded (#113), missing `` tolerated (#154). Header cells (``) bold unless their CSS says otherwise; `text-align` on a cell, or inherited from its row, row group or table, aligns the cell's paragraphs (#1381). HTML gives no page size, so it opens on the region's paper (A4 or Letter), as Markdown and plain text do; `@page` isn't read (#1381). No Word "Web Page" (with `_files` folder) round-trip. The same reader takes HTML from the system clipboard: Paste keeps another program's formatting (a Word document, a web page; Windows' "HTML Format" header understood), Merge Formatting keeps its emphasis (#687). Equations are written as MathML (with the linear format as a UnicodeMath annotation) and read back from MathML: our annotation, else the presentation elements, else a TeX annotation (#816) | | `.mht/.mhtml` Single File Web Page | <1% | R/W | ❌ | ❌ | 0% | 3–5 | — | | @@ -208,6 +208,7 @@ attributes inside stories and style or list definitions; a regenerated theme. Te | Date | Change | Summary | |---|---|---| +| 2026-10-15 | trivial | ODT headers and footers (`style:header`/`-left`/`-first`, `style:footer`/…) read from each master page, so each section shows its own (#863) | | 2026-10-14 | trivial | VML freeforms (`v:shape` with a `path`, such as rule lines in headers) read as freeforms and kept on save instead of being dropped | | 2026-10-14 | trivial | DOCX paragraphs inside complex-field instructions carry a hidden paragraph mark; cached result paragraphs remain visible. | | 2026-10-14 | trivial | DrawingML `flowChartOr` preserves its circle and crossing spokes, including when grouped or rotated | From 8922ec6ff7da3838392dfd61b456b9ed0b48243e Mon Sep 17 00:00:00 2001 From: Patrik Lermon Date: Thu, 15 Oct 2026 06:42:25 +0200 Subject: [PATCH 017/336] i18n(sv): translate 32 new strings Brings the Swedish catalog up to date with upstream main (0aab569). Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/i18n/sv.tsv | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index fe6f14673b..c7e067c082 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -2636,3 +2636,16 @@ preview Title Titel # Reading Mode column widths (#1040) Narrow Smal Medium Medel +template Letter Brev + Style: APA Stil: APA + ➕ New comment ➕ Ny kommentar + 📂 Browse… 📂 Bläddra… + B4 (JIS) 10.12" × 14.33" B4 (JIS) 10,12" × 14,33" + B5 (JIS) 7.17" × 10.12" B5 (JIS) 7,17" × 10,12" + Envelope #10 4.12" × 9.50" Kuvert #10 4,12" × 9,50" + Envelope DL 4.33" × 8.66" Kuvert DL 4,33" × 8,66" + Executive 7.25" × 10.50" Executive 7,25" × 10,50" + Legal 8.50" × 14.00" Legal 8,50" × 14,00" + Letter 8.50" × 11.00" Letter 8,50" × 11,00" + Statement 5.50" × 8.50" Statement 5,50" × 8,50" + Tabloid 11.00" × 17.00" Tabloid 11,00" × 17,00" From 3ab6d287d13a1c3e6f2f95bd2aaf43d3cd7872f2 Mon Sep 17 00:00:00 2001 From: Dhruv Date: Wed, 14 Oct 2026 22:42:37 -0600 Subject: [PATCH 018/336] Typing: Option+Shift+- types an em dash outside Outline view (#1933) Alt+Shift+- is Word's Collapse shortcut, which only runs in Outline view, but dispatch claimed the key and swallowed the typed character everywhere. An Alt shortcut whose command is disabled now leaves the key to typing. --- crates/ui-egui/src/keys.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/ui-egui/src/keys.rs b/crates/ui-egui/src/keys.rs index 8723b5a9e0..86ac28ef16 100644 --- a/crates/ui-egui/src/keys.rs +++ b/crates/ui-egui/src/keys.rs @@ -126,6 +126,11 @@ fn dispatch(app: &mut WordApp, key: Key, m: Modifiers) -> bool { let Some(name) = key_name(key) else { return false }; let reg = app.session.registry.clone(); if let Some(spec) = app.session.keymap.resolve(®, &combo(m, name, true)) { + // Option+Shift+- types an em dash on macOS; Word's Alt+Shift+- (Collapse) only works in + // Outline view, so elsewhere the key stays typing (#1933). + if m.alt && !crate::widgets::enabled(app, spec.id) { + return false; + } let _ = app.run(spec.id, json!({})); return true; } @@ -726,6 +731,17 @@ mod tests { egui::Event::Key { key, physical_key: Some(key), pressed, repeat, modifiers: Modifiers { alt: true, ..Default::default() } } } + /// Option+Shift+- types an em dash outside Outline view, where Word's Alt+Shift+- (Collapse) + /// can't run, so its shortcut doesn't swallow the character (#1933). + #[test] + fn disabled_alt_shortcut_leaves_the_typed_character() { + let (ctx, mut a) = alt_frame_app(); + let alt_shift = Modifiers { alt: true, shift: true, ..Default::default() }; + let key = |pressed| egui::Event::Key { key: Key::Minus, physical_key: Some(Key::Minus), pressed, repeat: false, modifiers: alt_shift }; + alt_frame(&ctx, &mut a, vec![key(true), egui::Event::Text("\u{2014}".into()), key(false)]); + assert_eq!(a.session.doc.plain_text(wordcraft_doc::StoryRef::Body).trim_end(), "\u{2014}"); + } + /// Holding a digit with Alt adds it once: its auto-repeat presses don't (#961). #[test] fn alt_code_ignores_auto_repeat() { From 8a0dcefb3fda687fdedf4610acbbcd59a7fca9ad Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Thu, 15 Oct 2026 07:53:50 +0200 Subject: [PATCH 019/336] Read ^0nnn character codes 128-159 as Windows-1252 characters Found while testing crates/engine/src/cmd/find_pattern.rs:97 Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Opus 5.5 --- crates/engine/src/cmd/find_pattern.rs | 46 +++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/crates/engine/src/cmd/find_pattern.rs b/crates/engine/src/cmd/find_pattern.rs index 8702fcbec1..677c5bcc71 100644 --- a/crates/engine/src/cmd/find_pattern.rs +++ b/crates/engine/src/cmd/find_pattern.rs @@ -106,6 +106,16 @@ fn code_char(n: u32) -> Option { }) } +/// A Windows-1252 code as its Unicode scalar: 128–159 hold typographic characters there (151 is +/// an em dash); the five undefined slots and every other code stay as they are. +fn ansi(n: u32) -> u32 { + const HIGH: [u32; 32] = [ + 0x20AC, 0x81, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x8D, 0x017D, 0x8F, 0x90, 0x2018, + 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x9D, 0x017E, 0x0178, + ]; + n.checked_sub(128).and_then(|k| HIGH.get(k as usize)).copied().unwrap_or(n) +} + /// Read a numeric code after `^` (`13`, `0151`, `u8212`) starting at `chars[i]`; returns the /// character and how many characters it took. fn numeric_code(chars: &[char], i: usize) -> Option<(char, usize)> { @@ -115,6 +125,8 @@ fn numeric_code(chars: &[char], i: usize) -> Option<(char, usize)> { return None; } let n: u32 = digits.parse().ok()?; + // `^nnn` and `^0nnn` are Windows-1252 (ANSI) codes; `^unnnn` is Unicode. + let n = if start == i { ansi(n) } else { n }; Some((code_char(n)?, start - i + digits.chars().count())) } @@ -415,3 +427,37 @@ pub fn expand(pieces: &[Piece], found: &str, group: &dyn Fn(usize) -> Option bool { + let c = compile(query, &Opts::default()).unwrap(); + regex::Regex::new(&c.pattern).unwrap().is_match(hay) + } + + fn replaced(with: &str) -> String { + expand(&parse_replace(with, &Opts::default()), "", &|_| None, "") + } + + #[test] + fn ansi_codes_128_to_159_are_windows_1252() { + for (code, ch) in [("^0151", '\u{2014}'), ("^0150", '\u{2013}'), ("^0128", '\u{20AC}'), ("^0145", '\u{2018}')] { + let hay = format!("a{ch}b"); + assert!(finds(code, &hay), "{code} should find {ch:?}"); + assert!(!finds(code, code), "{code} should not match its literal text"); + let w = Opts { wildcards: true, ..Opts::default() }; + let c = compile(code, &w).unwrap(); + assert!(regex::Regex::new(&c.pattern).unwrap().is_match(&hay), "wildcard {code} should find {ch:?}"); + } + assert_eq!(replaced("x^0151y"), "x\u{2014}y"); + assert_eq!(replaced("^0128"), "\u{20AC}"); + // Unchanged: other codes and the undefined Windows-1252 slots. + assert!(finds("^13", "a\rb")); + assert!(finds("^0065", "A")); + for undefined in ["^0129", "^0141", "^0143", "^0144", "^0157"] { + assert_eq!(replaced(undefined), undefined); + } + } +} From 9066db833490bfd2e225d2b4f335ecdcf3eec6ae Mon Sep 17 00:00:00 2001 From: rabis <340973947+rabis7575@users.noreply.github.com> Date: Thu, 15 Oct 2026 09:26:44 +0200 Subject: [PATCH 020/336] #1976: breaks_menu tests use the English interface The Breaks menu tests look for English labels, but an integration test links the library without cfg(test), so WordApp followed the machine's interface language and both tests failed on a Polish Windows. Set the app's language to English in layout_tab(), as native_print already does. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/tests/breaks_menu.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/ui-egui/tests/breaks_menu.rs b/crates/ui-egui/tests/breaks_menu.rs index db8550279e..3640a6a01e 100644 --- a/crates/ui-egui/tests/breaks_menu.rs +++ b/crates/ui-egui/tests/breaks_menu.rs @@ -6,7 +6,9 @@ use wordcraft_ui_egui::{Services, WordApp}; /// A WordCraft window on the Layout tab, ready for ribbon clicks. fn layout_tab() -> egui_kittest::Harness<'static, WordApp> { - let app = WordApp::new(Session::new(wordcraft_doc::Document::from_text("one\ntwo")), Services::default()); + let mut app = WordApp::new(Session::new(wordcraft_doc::Document::from_text("one\ntwo")), Services::default()); + // The labels below are English; an integration test otherwise follows the machine's language. + app.ui.language = "en".into(); 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(); From 7e33e5ae31a3193e33f17087cd3697152ab35a56 Mon Sep 17 00:00:00 2001 From: przemek-arch <263159043+przemek-arch@users.noreply.github.com> Date: Thu, 15 Oct 2026 09:53:17 +0200 Subject: [PATCH 021/336] Ribbon: names for screen readers on the tab strip's drawn buttons (#1110) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Share and the » that holds the tabs that don't fit are painted, so a screen reader had no name for them (» read as a glyph). They now say "Share" and "More tabs", with "More tabs" translated in every catalog. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/i18n/bg.tsv | 1 + crates/ui-egui/src/i18n/cs.tsv | 1 + crates/ui-egui/src/i18n/de.tsv | 1 + crates/ui-egui/src/i18n/es.tsv | 1 + crates/ui-egui/src/i18n/et.tsv | 1 + crates/ui-egui/src/i18n/fr.tsv | 1 + crates/ui-egui/src/i18n/hr.tsv | 1 + crates/ui-egui/src/i18n/hu.tsv | 1 + crates/ui-egui/src/i18n/it.tsv | 1 + crates/ui-egui/src/i18n/ja.tsv | 1 + crates/ui-egui/src/i18n/ko.tsv | 1 + crates/ui-egui/src/i18n/pl.tsv | 1 + crates/ui-egui/src/i18n/pt-br.tsv | 1 + crates/ui-egui/src/i18n/ru.tsv | 1 + crates/ui-egui/src/i18n/sr-latn.tsv | 1 + crates/ui-egui/src/i18n/sr.tsv | 1 + crates/ui-egui/src/i18n/sv.tsv | 1 + crates/ui-egui/src/i18n/tr.tsv | 1 + crates/ui-egui/src/i18n/uk.tsv | 1 + crates/ui-egui/src/i18n/vi.tsv | 1 + crates/ui-egui/src/i18n/zh-hans.tsv | 1 + crates/ui-egui/src/i18n/zh-hant.tsv | 1 + crates/ui-egui/src/ribbon.rs | 29 +++++++++++++++++++++++++++++ 23 files changed, 51 insertions(+) diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 66d02b6cf9..c76e983ea9 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -2621,3 +2621,4 @@ # Reading Mode column widths (#1040) Narrow Тясна Medium Средна + More tabs Още раздели diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 02e380d826..229b5a382b 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -2624,3 +2624,4 @@ preview Title Ukázka # Reading Mode column widths (#1040) Narrow Úzká Medium Střední + More tabs Další karty diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 6a9f286f88..cc8a0ba1c2 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -2232,3 +2232,4 @@ # Reading Mode column widths (#1040) Narrow Schmal Medium Mittel + More tabs Weitere Registerkarten diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 794cef7de7..24313f9826 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -2599,3 +2599,4 @@ preview Title Título # Reading Mode column widths (#1040) Narrow Estrecha Medium Media + More tabs Más pestañas diff --git a/crates/ui-egui/src/i18n/et.tsv b/crates/ui-egui/src/i18n/et.tsv index b37c9ac9d0..add84e4b69 100644 --- a/crates/ui-egui/src/i18n/et.tsv +++ b/crates/ui-egui/src/i18n/et.tsv @@ -2267,3 +2267,4 @@ # Reading Mode column widths (#1040) Narrow Kitsas Medium Keskmine + More tabs Veel vahekaarte diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 78f281359a..514c3d1a61 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -2240,3 +2240,4 @@ # Reading Mode column widths (#1040) Narrow Étroite Medium Moyenne + More tabs Plus d'onglets diff --git a/crates/ui-egui/src/i18n/hr.tsv b/crates/ui-egui/src/i18n/hr.tsv index 2fda6e1926..a617af2895 100644 --- a/crates/ui-egui/src/i18n/hr.tsv +++ b/crates/ui-egui/src/i18n/hr.tsv @@ -2632,3 +2632,4 @@ preview Title Naslov Bottom Right Cell Donja desna ćelija Inside Horizontal Border Unutarnji vodoravni obrub Inside Vertical Border Unutarnji okomiti obrub + More tabs Više kartica diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 73d3f752ee..784a9108c4 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -2628,3 +2628,4 @@ # Reading Mode column widths (#1040) Narrow Keskeny Medium Közepes + More tabs További lapok diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 0edbd298a2..53902906ef 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -2641,3 +2641,4 @@ preview Title Esempio # Reading Mode column widths (#1040) Narrow Stretta Medium Media + More tabs Altre schede diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 9c92f9fcfd..ac3a8e4965 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -2233,3 +2233,4 @@ # Reading Mode column widths (#1040) Narrow 狭い Medium 中 + More tabs その他のタブ diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 96002d803a..c7b71afd83 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -2488,3 +2488,4 @@ preview Title 제목 Bottom Right Cell 오른쪽 아래 셀 Inside Horizontal Border 안쪽 가로 테두리 Inside Vertical Border 안쪽 세로 테두리 + More tabs 탭 더 보기 diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index dd9969a3b6..0319574228 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -2622,3 +2622,4 @@ preview Title Tytuł # Reading Mode column widths (#1040) Narrow Wąska Medium Średnia + More tabs Więcej kart diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 5e44995910..ca93859013 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -2245,3 +2245,4 @@ # Reading Mode column widths (#1040) Narrow Estreita Medium Média + More tabs Mais guias diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index a9c41168b7..2d8f87ee65 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -2630,3 +2630,4 @@ preview Title Пример # Reading Mode column widths (#1040) Narrow Узкая Medium Средняя + More tabs Другие вкладки diff --git a/crates/ui-egui/src/i18n/sr-latn.tsv b/crates/ui-egui/src/i18n/sr-latn.tsv index 81d9f4f4af..bdd2195b47 100644 --- a/crates/ui-egui/src/i18n/sr-latn.tsv +++ b/crates/ui-egui/src/i18n/sr-latn.tsv @@ -2243,3 +2243,4 @@ # Reading Mode column widths (#1040) Narrow Uska Medium Srednja + More tabs Još kartica diff --git a/crates/ui-egui/src/i18n/sr.tsv b/crates/ui-egui/src/i18n/sr.tsv index 42bbeaf7b6..c10cc293d4 100644 --- a/crates/ui-egui/src/i18n/sr.tsv +++ b/crates/ui-egui/src/i18n/sr.tsv @@ -2247,3 +2247,4 @@ # Reading Mode column widths (#1040) Narrow Уска Medium Средња + More tabs Још картица diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 6052c9a231..c96a54da82 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -2647,3 +2647,4 @@ preview Title Titel Bottom Right Cell Nedre högra cellen Inside Horizontal Border Inre vågrät kantlinje Inside Vertical Border Inre lodrät kantlinje + More tabs Fler flikar diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 8ace2dd7b6..64f0dabb27 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -2629,3 +2629,4 @@ preview Title Örnek # Reading Mode column widths (#1040) Narrow Dar Medium Orta + More tabs Diğer sekmeler diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index ca2070d95d..4901e245bc 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -2245,3 +2245,4 @@ # Reading Mode column widths (#1040) Narrow Вузька Medium Середня + More tabs Інші вкладки diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index c1d28add3d..ae7035ff07 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -2672,3 +2672,4 @@ template Letter Thư # Reading Mode column widths (#1040) Narrow Hẹp Medium Trung bình + More tabs Các tab khác diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 07b0861daa..102f615c78 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -2245,3 +2245,4 @@ # Reading Mode column widths (#1040) Narrow 窄 Medium 中等 + More tabs 更多选项卡 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 7b75dc9783..7ba9f49b67 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -2234,3 +2234,4 @@ # Reading Mode column widths (#1040) Narrow 窄 Medium 中等 + More tabs 更多索引標籤 diff --git a/crates/ui-egui/src/ribbon.rs b/crates/ui-egui/src/ribbon.rs index 0dd38891ed..31ddbde0a9 100644 --- a/crates/ui-egui/src/ribbon.rs +++ b/crates/ui-egui/src/ribbon.rs @@ -239,6 +239,8 @@ pub fn show(app: &mut WordApp, ui: &mut Ui) { t.on_accent, ); ui.painter().text(pos2(r.min.x + 26.0, r.center().y), Align2::LEFT_CENTER, share, medium(12.0), t.on_accent); + // Drawn by hand: screen readers get its name from here. + resp.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, true, share)); if resp.on_hover_text(tl!("Export a copy to share (PDF, Word document)")).clicked() { let _ = app.run("ui.backstage", json!({"value": true, "page": "export"})); } @@ -317,6 +319,10 @@ pub fn show(app: &mut WordApp, ui: &mut Ui) { ui.painter().rect_filled(r.shrink2(vec2(0.0, 4.0)), 4.0, t.hover); } ui.painter().text(r.center(), Align2::CENTER_CENTER, "»", medium(12.5), t.text); + // "»" means nothing read aloud: the button says what it holds. + let more = tl!("More tabs"); + resp.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, true, more)); + let resp = resp.on_hover_text(more); egui::Popup::menu(&resp).show(|ui| { for (tab, shown) in left_out { if ui.selectable_label(false, shown).clicked() { @@ -2934,6 +2940,29 @@ mod tests { } } + /// #1110: the strip's drawn buttons have names for screen readers: Share, and the "»" that + /// holds the tabs that don't fit ("More tabs", not a glyph). + #[test] + fn the_tab_strip_buttons_have_names() { + use egui_kittest::kittest::Queryable; + let app = WordApp::new(Session::new(wordcraft_doc::Document::from_text("Text")), crate::Services::default()); + let mut h = egui_kittest::Harness::builder().with_size(vec2(760.0, 700.0)).build_ui_state( + |ui, app: &mut WordApp| { + let ctx = ui.ctx().clone(); + app.logic(&ctx); + app.ui(ui); + }, + app, + ); + h.run_steps(3); + h.get_by_role_and_label(egui::accesskit::Role::Button, tl!("Share")); + let more = h.get_by_role_and_label(egui::accesskit::Role::Button, tl!("More tabs")); + more.click(); + h.run_steps(2); + // The menu lists a tab that didn't fit (View is among the last on a narrow strip). + assert!(h.query_by_label(tl!("View")).is_some(), "the » menu lists the tabs left out"); + } + #[test] fn fit_tabs_keeps_the_kept_tabs_and_drops_from_the_end() { let tabs = [(40.0, true), (50.0, false), (50.0, false), (50.0, false), (60.0, true)]; From bdc7226fc7e001d834b8a83e839d8e0a0e9d1695 Mon Sep 17 00:00:00 2001 From: fabiodalez-dev Date: Thu, 15 Oct 2026 11:20:32 +0200 Subject: [PATCH 022/336] i18n(it): translate the 15 strings other catalogs added since #1244 Paper and envelope sizes in the page-size list (decimal comma), the Go To and bookmark prompts, the print-job notice and the Letter template name the Croatian, Swedish and Vietnamese catalogs added. Italian again has every key the other catalogs have. --- crates/ui-egui/src/i18n/it.tsv | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 0edbd298a2..298f317f80 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -2641,3 +2641,19 @@ preview Title Esempio # Reading Mode column widths (#1040) Narrow Stretta Medium Media +# Paper sizes, Go To, bookmarks and print labels the Croatian, Swedish and Vietnamese catalogs added + B4 (JIS) 10.12" × 14.33" B4 (JIS) 10,12" × 14,33" + B5 (JIS) 7.17" × 10.12" B5 (JIS) 7,17" × 10,12" + Executive 7.25" × 10.50" Executive 7,25" × 10,50" + Legal 8.50" × 14.00" Legal 8,50" × 14,00" + Letter 8.50" × 11.00" Letter 8,50" × 11,00" + Statement 5.50" × 8.50" Statement 5,50" × 8,50" + Tabloid 11.00" × 17.00" Tabloid 11,00" × 17,00" + Envelope #10 4.12" × 9.50" Busta n. 10 4,12" × 9,50" + Envelope DL 4.33" × 8.66" Busta DL 4,33" × 8,66" + Enter bookmark name: Inserisci il nome del segnalibro: + This document has no bookmarks. Questo documento non ha segnalibri. + Find “{text}” in the document Trova “{text}” nel documento + Type a page number, or +2 / -1. Digita un numero di pagina, oppure +2 / -1. + Wait for the print job to finish submitting before closing. Attendi che l'invio del lavoro di stampa sia terminato prima di chiudere. +template Letter Lettera From 70cfe068460bc6ec22be87e6fe3faa45f21a4df8 Mon Sep 17 00:00:00 2001 From: Guru Swarupa Date: Thu, 15 Oct 2026 14:54:20 +0530 Subject: [PATCH 023/336] feat(navigation): implement collapsible headings in the navigation pane --- crates/ui-egui/src/lib.rs | 4 ++ crates/ui-egui/src/panes.rs | 89 ++++++++++++++++++++++++++++++++----- 2 files changed, 81 insertions(+), 12 deletions(-) diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 662516fbd6..867fdb31a6 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -256,6 +256,9 @@ pub struct UiState { #[serde(rename = "dark", skip_serializing)] pub(crate) legacy_dark: Option, pub nav_tab: String, + /// Headings whose sub-headings the Navigation pane hides (#1997); per session, not saved. + #[serde(skip)] + pub nav_collapsed: std::collections::HashSet, pub show_discord: bool, /// User name (File › Options) for comments and tracked changes; empty keeps the default. pub author: String, @@ -351,6 +354,7 @@ impl Default for UiState { animations: theme::Animations::default(), legacy_dark: None, nav_tab: "headings".into(), + nav_collapsed: Default::default(), show_discord: true, author: String::new(), window: None, diff --git a/crates/ui-egui/src/panes.rs b/crates/ui-egui/src/panes.rs index 76d5892576..f96cf0f754 100644 --- a/crates/ui-egui/src/panes.rs +++ b/crates/ui-egui/src/panes.rs @@ -198,8 +198,22 @@ fn nav(app: &mut WordApp, ui: &mut Ui) { if items.is_empty() { ui.label(egui::RichText::new(tl!("Create an interactive outline of your document.\n\nIt's a great way to keep track of where you are or quickly move your content around.\n\nTo get started, go to the Home tab and apply Heading styles to the headings in your document.")).weak()); } + // A heading has children when the next heading is a deeper level (#1997). + let has_children: Vec = items.iter().enumerate().map(|(i, (_, lv, _))| items.get(i + 1).is_some_and(|n| n.1 > *lv)).collect(); + let collapsed_flags: Vec = items.iter().enumerate().map(|(i, (p, _, _))| has_children[i] && app.ui.nav_collapsed.contains(p)).collect(); + let levels: Vec = items.iter().map(|(_, lv, _)| *lv).collect(); + let visible = visible_headings(&levels, &collapsed_flags); for (i, (path, lv, txt)) in items.into_iter().enumerate() { - if nav_heading(ui, lv, &txt, current == Some(i)).clicked() { + if !visible[i] { + continue; + } + let collapsed = collapsed_flags[i]; + let (toggle, r) = nav_heading(ui, lv, &txt, current == Some(i), has_children[i].then_some(collapsed)); + if toggle { + if !app.ui.nav_collapsed.remove(&path) { + app.ui.nav_collapsed.insert(path); + } + } else if r.clicked() { let _ = app.run("caret.set", json!({"pos": Pos { story: StoryRef::Body, path, off: 0 }})); // Align the chosen heading's page immediately against this pane, rather // than the indeterminate minimum movement from scroll_to_rect. @@ -212,19 +226,52 @@ fn nav(app: &mut WordApp, ui: &mut Ui) { }); } +/// Which headings the pane shows: those under a collapsed heading (every following heading +/// of a deeper level) are hidden (#1997). +fn visible_headings(levels: &[u8], collapsed: &[bool]) -> Vec { + let mut hide_below: Option = None; + levels + .iter() + .zip(collapsed) + .map(|(&lv, &c)| { + if hide_below.is_some_and(|h| lv > h) { + return false; + } + hide_below = c.then_some(lv); + true + }) + .collect() +} + /// Constrain each heading to the available pane width so long document text cannot /// enlarge the panel. The full heading stays accessible on hover. -fn nav_heading(ui: &mut Ui, level: u8, text: &str, selected: bool) -> egui::Response { +fn nav_heading(ui: &mut Ui, level: u8, text: &str, selected: bool, collapsed: Option) -> (bool, egui::Response) { ui.horizontal(|ui| { ui.add_space(level as f32 * 14.0); + // The triangle is drawn in code; headings without children keep an empty slot so text aligns. + let mut toggled = false; + let (rect, tri) = ui.allocate_exact_size(vec2(14.0, ui.spacing().interact_size.y), egui::Sense::click()); + if let Some(c) = collapsed { + let ctr = rect.center(); + let col = ui.visuals().text_color(); + let pts = if c { + vec![ctr + vec2(-2.5, -4.0), ctr + vec2(-2.5, 4.0), ctr + vec2(3.0, 0.0)] + } else { + vec![ctr + vec2(-4.0, -2.5), ctr + vec2(4.0, -2.5), ctr + vec2(0.0, 3.0)] + }; + ui.painter().add(egui::Shape::convex_polygon(pts, col, Stroke::NONE)); + toggled = tri.clicked(); + } let label = egui::RichText::new(text).font(if level == 0 { semibold(12.5) } else { regular(12.5) }); - ui.add_sized( - vec2(ui.available_width(), ui.spacing().interact_size.y), - // The trailing grow atom takes the spare width, so the text starts flush left - // under its parent instead of centred, as in Word (#1199). - egui::Button::selectable(selected, (label, egui::Atom::grow())).truncate(), - ) - .on_hover_text(text) + let r = ui + .add_sized( + vec2(ui.available_width(), ui.spacing().interact_size.y), + // The trailing grow atom takes the spare width, so the text starts flush left + // under its parent instead of centred, as in Word (#1199). + egui::Button::selectable(selected, (label, egui::Atom::grow())).truncate(), + ) + .on_hover_text(text); + (toggled, r) }) .inner } @@ -757,7 +804,7 @@ mod navigation_tests { let result = egui::Panel::left("navigation_test") .default_size(width) .resizable(true) - .show(ui, |ui| egui::ScrollArea::vertical().show(ui, |ui| nav_heading(ui, level, &text, true)).inner); + .show(ui, |ui| egui::ScrollArea::vertical().show(ui, |ui| nav_heading(ui, level, &text, true, None).1).inner); panel_rect = result.response.rect; heading_rect = result.inner.rect; }); @@ -786,7 +833,7 @@ mod navigation_tests { let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, vec2(900.0, 600.0)); let mut panel_left = 0.0; let output = ctx.run_ui(egui::RawInput { screen_rect: Some(screen), ..Default::default() }, |ui| { - let result = egui::Panel::left("navigation_test").default_size(260.0).show(ui, |ui| nav_heading(ui, level, "Kilns", false)); + let result = egui::Panel::left("navigation_test").default_size(260.0).show(ui, |ui| nav_heading(ui, level, "Kilns", false, None)); panel_left = result.response.rect.min.x; }); let x = output @@ -801,7 +848,25 @@ mod navigation_tests { x - panel_left }; let (top, second) = (text_left(0), text_left(1)); - assert!(top < 30.0, "a top-level heading starts at the pane's left edge, not centred: {top}px in"); + assert!(top < 40.0, "a top-level heading starts at the pane's left edge, not centred: {top}px in"); assert!((second - top - 14.0).abs() < 0.5, "a level-2 heading is indented 14px under its parent: {top} / {second}"); } + + /// Collapsing a heading hides everything nested under it, and only that (#1997). + #[test] + fn collapsed_heading_hides_its_descendants_only() { + // 1 / 1.1 / 1.1.1 / 1.2 / 1.2.1 / 2 / 2.1 + let levels = [0, 1, 2, 1, 2, 0, 1]; + let none = [false; 7]; + assert_eq!(visible_headings(&levels, &none), vec![true; 7]); + let mut c = none; + c[3] = true; // collapse 1.2 + assert_eq!(visible_headings(&levels, &c), vec![true, true, true, true, false, true, true]); + c[3] = false; + c[0] = true; // collapse 1 + assert_eq!(visible_headings(&levels, &c), vec![true, false, false, false, false, true, true]); + // A collapsed child inside a collapsed parent stays hidden and does not reopen siblings. + c[1] = true; + assert_eq!(visible_headings(&levels, &c), vec![true, false, false, false, false, true, true]); + } } From e4d9eb61b99c4185d8048f635487c3d58d307b91 Mon Sep 17 00:00:00 2001 From: dfgodvan <180286157+dfgodvan@users.noreply.github.com> Date: Wed, 14 Oct 2026 22:21:24 +0300 Subject: [PATCH 024/336] Tables: rows with gridBefore/gridAfter keep their grid columns (part of #1174) A row that skips grid columns before its first cell (w:gridBefore) was laid out from column 0, so its cells sat one or more columns to the left and wrapped at the wrong width (a text cell landing on a 5 pt column sets one letter per line). Row::first_grid() gives the column the row's first cell starts at; Table::grid_col, cell_at_grid and cols(), the layout passes, vertical-merge matching, cell selection and the insert-column spot count from it, and cols() also counts gridAfter. The skipped columns stay empty and borderless, as in Word: a row's own first and last cell carry the table's outer side borders, and AutoFit gives the skipped columns the row's wBefore/wAfter width. Co-Authored-By: Claude Opus 5.5 --- crates/doc/src/table.rs | 29 +++++- crates/engine/src/cmd/table_mouse.rs | 2 +- crates/engine/src/session.rs | 2 +- crates/layout/src/table.rs | 27 +++-- crates/layout/src/tests.rs | 141 +++++++++++++++++++++++++++ 5 files changed, 190 insertions(+), 11 deletions(-) diff --git a/crates/doc/src/table.rs b/crates/doc/src/table.rs index 104bf96b82..a6097f6cbf 100644 --- a/crates/doc/src/table.rs +++ b/crates/doc/src/table.rs @@ -43,6 +43,13 @@ pub struct Row { pub controls: ControlWrap, } +impl Row { + /// Grid column where the row's first cell starts: the columns `w:gridBefore` skips. + pub fn first_grid(&self) -> usize { + (self.props.grid_before as usize).min(MAX_COLS) + } +} + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)] pub struct Table { #[serde(default)] @@ -86,18 +93,22 @@ impl Table { } pub fn cols(&self) -> usize { - self.grid.len().max(self.rows.iter().map(|r| r.cells.iter().map(Cell::span).sum::()).max().unwrap_or(0)) + let row_cols = |r: &Row| r.first_grid() + r.cells.iter().map(Cell::span).sum::() + r.props.grid_after as usize; + self.grid.len().max(self.rows.iter().map(row_cols).max().unwrap_or(0)) } /// Grid column where cell `c` of row `r` starts. pub fn grid_col(&self, r: usize, c: usize) -> usize { - self.rows.get(r).map(|row| row.cells.iter().take(c).map(Cell::span).sum()).unwrap_or(0) + self.rows.get(r).map(|row| row.first_grid() + row.cells.iter().take(c).map(Cell::span).sum::()).unwrap_or(0) } /// Cell index in row `r` that covers grid column `g`. pub fn cell_at_grid(&self, r: usize, g: usize) -> Option { let row = self.rows.get(r)?; - let mut x = 0; + let mut x = row.first_grid(); + if g < x { + return None; + } for (i, c) in row.cells.iter().enumerate() { if g < x + c.span() { return Some(i); @@ -374,6 +385,18 @@ impl Table { mod tests { use super::*; + #[test] + fn grid_before_shifts_the_row() { + let mut t = Table::new(2, 3, 300.0); + t.rows[1].cells.remove(0); + t.rows[1].props.grid_before = 1; + assert_eq!(t.grid_col(1, 0), 1); + assert_eq!(t.grid_col(1, 1), 2); + assert_eq!(t.cell_at_grid(1, 0), None); + assert_eq!(t.cell_at_grid(1, 2), Some(1)); + assert_eq!(t.cols(), 3); + } + #[test] fn new_table_shape() { let t = Table::new(3, 4, 468.0); diff --git a/crates/engine/src/cmd/table_mouse.rs b/crates/engine/src/cmd/table_mouse.rs index 38ac1409d7..f4bd680543 100644 --- a/crates/engine/src/cmd/table_mouse.rs +++ b/crates/engine/src/cmd/table_mouse.rs @@ -374,7 +374,7 @@ fn select_at(s: &mut Session, v: &Value) -> CmdResult { /// before it) or, at the table's right edge, a row's last cell (after it). (row, cell, after) fn beside_line(t: &Table, line: usize) -> Option<(usize, usize, bool)> { t.rows.iter().enumerate().find_map(|(r, row)| { - let mut g = 0usize; + let mut g = row.first_grid(); for (c, cell) in row.cells.iter().enumerate() { if g == line { return Some((r, c, false)); diff --git a/crates/engine/src/session.rs b/crates/engine/src/session.rs index 4f16aea9af..26302d11ca 100644 --- a/crates/engine/src/session.rs +++ b/crates/engine/src/session.rs @@ -81,7 +81,7 @@ impl CellRange { let ((r0, r1), (g0, g1)) = (self.rows, self.cols); let mut out = Vec::new(); for (r, row) in t.rows.iter().enumerate().take(r1.saturating_add(1)).skip(r0) { - let mut g = 0usize; + let mut g = row.first_grid(); for (c, cell) in row.cells.iter().enumerate() { let end = g.saturating_add(cell.span()); if g <= g1 && end > g0 { diff --git a/crates/layout/src/table.rs b/crates/layout/src/table.rs index 577bd0000e..bb5d8ef77f 100644 --- a/crates/layout/src/table.rs +++ b/crates/layout/src/table.rs @@ -5,7 +5,7 @@ use wordcraft_doc::numbering::Counters; use wordcraft_doc::props::{Align, Border, Borders, HeightRule, Rgb, TextDirection, VAlign, VMerge}; use wordcraft_doc::styles::{PartSet, TablePart, TableStyleProps}; -use wordcraft_doc::{Block, Blocks, Paragraph, StoryRef, Table}; +use wordcraft_doc::{Block, Blocks, Paragraph, Row, StoryRef, Table}; use wordcraft_geom::Rect; use crate::para::{CellText, ClKind, ParaLayout}; @@ -178,7 +178,7 @@ pub fn measure_table_columns(doc: &wordcraft_doc::Document, t: &Table) -> Vec<(f let mut spanned: Vec<(usize, usize, f32, f32)> = Vec::new(); let mut budget = MEASURE_BUDGET; 'rows: for (ri, row) in t.rows.iter().enumerate() { - let mut g = 0usize; + let mut g = row.first_grid(); for cell in &row.cells { let span = cell.span(); let margins = cell.props.margins.unwrap_or(margins_def); @@ -235,6 +235,7 @@ pub fn measure_table_columns(doc: &wordcraft_doc::Document, t: &Table) -> Vec<(f } g += span; } + spanned.extend(row_gaps(row, g).map(|(g, span, w)| (g, span, w, w))); } for (g, span, min, max) in spanned { let Some(cs) = cols.get_mut(g..(g + span).min(ncols)) else { continue }; @@ -455,7 +456,7 @@ pub fn layout_table(ctx: &mut Ctx, story: StoryRef, t: &Table, path: &[u32], ava let mut heights: Vec = Vec::with_capacity(nrows); let mut bottoms: Vec = Vec::with_capacity(nrows); for (ri, row) in t.rows.iter().enumerate() { - let mut g = 0usize; + let mut g = row.first_grid(); let mut cells = Vec::with_capacity(row.cells.len()); let mut rh = 0.0f32; // The tallest cell margins plus border bands in the row. @@ -502,7 +503,9 @@ pub fn layout_table(ctx: &mut Ctx, story: StoryRef, t: &Table, path: &[u32], ava let cb = cell.props.borders.unwrap_or_default(); // A row's table property exceptions (`w:tblPrEx`) override the table's borders for its cells. let row_borders = row.props.exceptions.as_ref().and_then(|e| e.borders).unwrap_or_default(); - let (first_g, last_g) = (g == 0, g + span >= ncols); + // A row that skips grid columns (`w:gridBefore`/`w:gridAfter`) has its edge at its own + // first and last cell. + let (first_g, last_g) = (g == row.first_grid(), g + span >= ncols.saturating_sub(row.props.grid_after as usize)); let rb = look.map(|l| region_borders(&l.borders, (ri, nrows), (first_g, last_g))).unwrap_or_default(); let mut borders = Borders { top: edge(cb.top.or(if ri == 0 { row_borders.top } else { row_borders.between }).or(rb.top), ri == 0, tb.top, tb.between), @@ -636,7 +639,7 @@ pub fn layout_table(ctx: &mut Ctx, story: StoryRef, t: &Table, path: &[u32], ava .rows .iter() .map(|r| { - let mut gx = 0; + let mut gx = r.first_grid(); r.cells .iter() .filter_map(|x| { @@ -1045,7 +1048,7 @@ fn column_content(ctx: &mut Ctx, t: &Table, style: Option<&TableStyleProps>, dep let mut spanning = Vec::new(); let mut any = false; for (ri, row) in t.rows.iter().enumerate() { - let mut g = 0usize; + let mut g = row.first_grid(); for cell in &row.cells { let span = cell.span(); if cell.props.vmerge != VMerge::Continue { @@ -1061,6 +1064,7 @@ fn column_content(ctx: &mut Ctx, t: &Table, style: Option<&TableStyleProps>, dep } g += span; } + spanning.extend(row_gaps(row, g).map(|(g, span, w)| (g, span, (w, w)))); } for (g, span, need) in spanning { let Some(cs) = cols.get_mut(g..(g + span).min(t.cols())) else { continue }; @@ -1074,6 +1078,17 @@ fn column_content(ctx: &mut Ctx, t: &Table, style: Option<&TableStyleProps>, dep any.then_some(cols) } +/// The grid columns row `row` leaves empty before its first cell and after its last one (which +/// ends at grid column `end`) when it gives their width (`w:wBefore`, `w:wAfter`): (first column, +/// columns, width in points). Column widths make room for them like for an empty cell. +fn row_gaps(row: &Row, end: usize) -> impl Iterator { + let p = &row.props; + [(0, row.first_grid(), p.w_before), (end, p.grid_after as usize, p.w_after)] + .into_iter() + .filter(|&(_, n, _)| n > 0) + .filter_map(|(g, n, w)| w.filter(|w| w.is_finite() && *w > 0.0).map(|w| (g, n, w.min(1584.0)))) +} + /// The narrowest and widest a block list can be laid out. fn blocks_content(ctx: &mut Ctx, blocks: &Blocks, text: Option<&CellText>, depth: usize) -> (f32, f32) { let (mut lo, mut hi) = (0.0f32, 0.0f32); diff --git a/crates/layout/src/tests.rs b/crates/layout/src/tests.rs index d6865b0d92..c8dda44a9d 100644 --- a/crates/layout/src/tests.rs +++ b/crates/layout/src/tests.rs @@ -1270,6 +1270,147 @@ fn table_border_corners_close() { assert!((h_left - (v_left - 1.5)).abs() < 0.01, "horizontal starts at {h_left}, vertical centre line at {v_left}"); } +/// A row with `w:gridBefore` starts at the grid column after the skipped ones, not at the first. +#[test] +fn grid_before_row_starts_after_skipped_columns() { + let mut d = Document::from_text("before\nafter"); + let mut t = Table::new(2, 3, 468.0); + t.rows[1].cells.remove(0); + t.rows[1].props.grid_before = 1; + d.insert_block(StoryRef::Body, &Path::top(1), wordcraft_doc::Block::Table(t)).unwrap(); + let l = lay(&d); + let x_of = |r: usize, c: usize| { + l.pages[0].items.iter().find_map(|i| match i { + Placed::Cell { rect, row, cell, .. } if *row == r && *cell == c => Some(rect.x), + _ => None, + }) + }; + let (shifted, second) = (x_of(1, 0).unwrap(), x_of(0, 1).unwrap()); + assert!((shifted - second).abs() < 0.01, "row 1 starts at {shifted}, grid column 1 at {second}"); +} + +/// The area of cell `c` of row `r` of the table on page `page`. +fn cell_rect(l: &DocLayout, page: usize, r: usize, c: usize) -> Option { + l.pages.get(page)?.items.iter().find_map(|i| match i { + Placed::Cell { rect, row, cell, .. } if *row == r && *cell == c => Some(*rect), + _ => None, + }) +} + +/// Rows that skip grid columns at the start and at the end (`w:gridBefore`, `w:gridAfter`, as in +/// #1174): the cells sit on their own grid columns, the middle one keeps its width, and a merge +/// down the last column matches by the shifted column. +#[test] +fn grid_before_and_after_rows_keep_their_columns_and_merges() { + let mut d = Document::from_text("before\nafter"); + let grid = [239.0f32, 679.0, 101.0, 3790.0, 2312.0, 1189.0].map(|w| w / 20.0); + let mut t = Table::new(3, 4, 468.0); + t.props.fixed = true; + t.grid = grid.to_vec(); + for (ri, row) in t.rows.iter_mut().enumerate() { + row.cells.truncate(3); + row.props.grid_before = 1; + row.props.w_before = Some(grid[0]); + for (c, span) in row.cells.iter_mut().zip([2, 1, 2]) { + c.props.span = span; + c.props.width = None; + } + row.cells[1] = wordcraft_doc::Cell::with_text("Middle cell, wide enough for a few words on a line."); + row.cells[2].props.span = 2; + row.cells[2].props.vmerge = if ri == 0 { wordcraft_doc::props::VMerge::Restart } else { wordcraft_doc::props::VMerge::Continue }; + } + // A row without the gap, and one skipping the last column instead. + t.rows[1].props = Default::default(); + t.rows[1].cells.insert(0, wordcraft_doc::Cell::empty()); + t.rows[2].props.grid_before = 0; + t.rows[2].props.grid_after = 1; + t.rows[2].cells[0].props.span = 3; + t.rows[2].cells[2].props.span = 1; + let colx = |g: usize| grid[..g].iter().sum::(); + d.insert_block(StoryRef::Body, &Path::top(1), wordcraft_doc::Block::Table(t)).unwrap(); + let l = lay(&d); + let rect = |r, c| cell_rect(&l, 0, r, c).unwrap(); + let x0 = rect(1, 0).x; + let middle = rect(0, 1); + assert!((rect(0, 0).x - x0 - colx(1)).abs() < 0.01, "row 0 starts at {}", rect(0, 0).x - x0); + assert!((middle.x - x0 - colx(3)).abs() < 0.01 && (middle.w - grid[3]).abs() < 0.01, "middle cell {middle:?}"); + assert!((rect(2, 2).x - x0 - colx(4)).abs() < 0.01 && (rect(2, 2).w - grid[4]).abs() < 0.01, "row 2's last cell {:?}", rect(2, 2)); + // The merge runs down all three rows. + let merged = rect(0, 2); + let rows_h = rect(0, 0).h + rect(1, 0).h + rect(2, 0).h; + assert!((merged.h - rows_h).abs() < 0.01, "merged cell {} tall, rows {rows_h}", merged.h); +} + +/// A row's own first and last cell carry the table's outer side borders when it skips the grid +/// columns beyond them. +#[test] +fn skipped_columns_put_the_side_borders_on_the_rows_cells() { + let mut d = Document::from_text("before\nafter"); + let mut t = Table::new(3, 3, 468.0); + t.props.style = None; + let thick = Some(Border::single(3.0)); + t.props.borders = Some(Borders { left: thick, right: thick, ..Borders::default() }); + t.rows[1].cells.remove(0); + t.rows[1].props.grid_before = 1; + t.rows[2].cells.pop(); + t.rows[2].props.grid_after = 1; + d.insert_block(StoryRef::Body, &Path::top(1), wordcraft_doc::Block::Table(t)).unwrap(); + let l = lay(&d); + let rect = |r, c| cell_rect(&l, 0, r, c).unwrap(); + let vertical_at = |x: f32, y: f32| { + l.pages[0].items.iter().any(|i| matches!(i, Placed::Rule { x0, y0, x1, y1, .. } if x0 == x1 && (x0 - x).abs() < 0.01 && *y0 <= y && *y1 >= y)) + }; + let (r1, r2) = (rect(1, 0), rect(2, 1)); + assert!(vertical_at(r1.x, r1.y + r1.h / 2.0), "no left border at row 1's first cell {r1:?}"); + assert!(vertical_at(r2.x + r2.w, r2.y + r2.h / 2.0), "no right border at row 2's last cell {r2:?}"); +} + +/// AutoFit makes the skipped columns as wide as the row says (`w:wBefore`). +#[test] +fn autofit_gives_skipped_columns_their_width() { + let mut d = Document::from_text("before\nafter"); + let mut t = Table::new(2, 2, 468.0); + t.props.style = None; + for row in &mut t.rows { + for c in &mut row.cells { + *c = wordcraft_doc::Cell::with_text("A"); + } + } + t.rows[1].cells.remove(0); + t.rows[1].props.grid_before = 1; + t.rows[1].props.w_before = Some(150.0); + d.insert_block(StoryRef::Body, &Path::top(1), wordcraft_doc::Block::Table(t)).unwrap(); + let l = lay(&d); + let gap = cell_rect(&l, 0, 1, 0).unwrap().x - cell_rect(&l, 0, 0, 0).unwrap().x; + assert!((gap - 150.0).abs() < 0.01, "skipped column {gap} wide"); +} + +/// Rows skipping a grid column keep it skipped on every page, under the repeated header row. +#[test] +fn grid_before_rows_stay_shifted_across_pages() { + let mut d = Document::from_text("before\nafter"); + let mut t = Table::new(80, 3, 468.0); + t.rows[0].props.header = true; + for row in t.rows.iter_mut().skip(1) { + row.cells.remove(0); + row.props.grid_before = 1; + } + d.insert_block(StoryRef::Body, &Path::top(1), wordcraft_doc::Block::Table(t)).unwrap(); + let l = lay(&d); + assert!(l.pages.len() >= 2, "pages {}", l.pages.len()); + let column1 = cell_rect(&l, 0, 0, 1).unwrap().x; + for (pi, p) in l.pages.iter().enumerate() { + assert!(cell_rect(&l, pi, 0, 1).is_some_and(|r| (r.x - column1).abs() < 0.01), "no header on page {pi}"); + for i in &p.items { + if let Placed::Cell { rect, row, cell: 0, .. } = i + && *row > 0 + { + assert!((rect.x - column1).abs() < 0.01, "row {row} on page {pi} starts at {}", rect.x); + } + } + } +} + #[test] fn table_own_border_overrides_only_that_side() { let mut d = Document::from_text("before\nafter"); From 624ce0bdc2c0b50bbbb088621ada9aee5e46c469 Mon Sep 17 00:00:00 2001 From: Kaan Ilter <339852324+kilter-ecnvs@users.noreply.github.com> Date: Thu, 15 Oct 2026 12:44:48 +0100 Subject: [PATCH 025/336] fix(fonts): emoji prefer a colour emoji font on Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fallback put Segoe UI Symbol before Segoe UI Emoji, and Symbol has monochrome glyphs for some emoji (✅), so they drew in black and white and tests_color_glyphs failed on Windows. Characters with default emoji presentation now rank the colour emoji fonts first, both when loading a system fallback and when choosing among loaded faces. Co-Authored-By: Claude Opus 5.5 --- crates/fonts/src/fontdb.rs | 4 ++- crates/fonts/src/lib.rs | 2 +- crates/fonts/src/script.rs | 63 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/crates/fonts/src/fontdb.rs b/crates/fonts/src/fontdb.rs index 5f07295138..c573debc16 100644 --- a/crates/fonts/src/fontdb.rs +++ b/crates/fonts/src/fontdb.rs @@ -1474,10 +1474,12 @@ impl FontDb { let faces = self.read_faces(); let mut order: Vec<&Arc> = faces.iter().filter(|f| f.id != exclude).collect(); // Right-to-left letters prefer a font made for the script, so a word isn't split across - // fonts (that breaks Arabic joining). + // fonts (that breaks Arabic joining). Emoji prefer a colour emoji font. let rank = |f: &FontFace| { if crate::is_rtl(c) || arabic_block(c) { RTL_FALLBACKS.iter().position(|n| f.family.eq_ignore_ascii_case(n)).unwrap_or(RTL_FALLBACKS.len()) + } else if crate::is_emoji(c) { + crate::fallback_rank(c, &f.family) } else { 0 } diff --git a/crates/fonts/src/lib.rs b/crates/fonts/src/lib.rs index 994a98f298..b82cbc4caa 100644 --- a/crates/fonts/src/lib.rs +++ b/crates/fonts/src/lib.rs @@ -21,7 +21,7 @@ pub use fontdb::{ pub use harfrust::Feature; use harfrust::{Direction, ShapeOptions, Tag, UnicodeBuffer}; pub use kurbo::BezPath; -pub use script::{fallback_rank, needs_layout_tables, script_fallbacks}; +pub use script::{fallback_rank, is_emoji, needs_layout_tables, script_fallbacks}; use skrifa::MetadataProvider; use skrifa::instance::Size; pub use ui_fallback::{SystemUiFont, is_cjk, system_cjk_ui_font, system_cjk_ui_font_for, ui_needs_system_cjk, ui_uncovered_cjk}; diff --git a/crates/fonts/src/script.rs b/crates/fonts/src/script.rs index 1c7c9609af..1727b71af4 100644 --- a/crates/fonts/src/script.rs +++ b/crates/fonts/src/script.rs @@ -77,6 +77,9 @@ pub fn needs_layout_tables(c: char) -> bool { /// it: the platforms' own OpenType fonts and the open Noto/SIL families. Apple's older fonts for /// these scripts carry only AAT tables (which the shaper doesn't read), so they come last. pub fn script_fallbacks(c: char) -> &'static [&'static str] { + if is_emoji(c) { + return COLOR_EMOJI; + } match c.script() { Script::Myanmar => &["Myanmar Text", "Noto Sans Myanmar", "Padauk", "Pyidaungsu", "Noto Serif Myanmar", "Myanmar Sangam MN", "Myanmar MN"], Script::Khmer => &["Leelawadee UI", "Khmer UI", "Noto Sans Khmer", "Noto Serif Khmer", "Khmer OS", "Khmer Sangam MN", "Khmer MN"], @@ -89,6 +92,53 @@ pub fn script_fallbacks(c: char) -> &'static [&'static str] { } } +/// The platforms' colour emoji fonts. Emoji prefer them over the monochrome symbol fonts that +/// also cover many emoji (Windows' Segoe UI Symbol has ✅, so it drew in black and white). +const COLOR_EMOJI: &[&str] = &["Segoe UI Emoji", "Apple Color Emoji", "Noto Color Emoji"]; + +/// Characters drawn as emoji by default (Unicode `Emoji_Presentation`): the BMP ones listed in +/// emoji-data.txt, and the pictograph blocks from U+1F000. +// ponytail: all of U+1F000..U+1FAFF counts as emoji (a few text-default pictographs too); switch to the exact emoji-data ranges if one must draw monochrome +pub fn is_emoji(c: char) -> bool { + matches!( + u32::from(c), + 0x1F000..=0x1FAFF + | 0x231A..=0x231B + | 0x23E9..=0x23EC + | 0x23F0 + | 0x23F3 + | 0x25FD..=0x25FE + | 0x2614..=0x2615 + | 0x2648..=0x2653 + | 0x267F + | 0x2693 + | 0x26A1 + | 0x26AA..=0x26AB + | 0x26BD..=0x26BE + | 0x26C4..=0x26C5 + | 0x26CE + | 0x26D4 + | 0x26EA + | 0x26F2..=0x26F3 + | 0x26F5 + | 0x26FA + | 0x26FD + | 0x2705 + | 0x270A..=0x270B + | 0x2728 + | 0x274C + | 0x274E + | 0x2753..=0x2755 + | 0x2757 + | 0x2795..=0x2797 + | 0x27B0 + | 0x27BF + | 0x2B1B..=0x2B1C + | 0x2B50 + | 0x2B55 + ) +} + /// How well `family` suits `c` as a fallback: 0 for the first preferred family, then the others /// in order; then families named after the script ("Noto Sans Tamil"); then everything else. pub fn fallback_rank(c: char, family: &str) -> usize { @@ -132,4 +182,17 @@ mod tests { assert!(fallback_rank('த', "Noto Sans Tamil") < fallback_rank('த', "Arial")); assert_eq!(fallback_rank('a', "Arial"), fallback_rank('a', "Noto Sans")); } + + #[test] + fn emoji_prefer_colour_emoji_fonts() { + for c in ['✅', '🧠', '⭐', '⌚', '🫶'] { + assert!(is_emoji(c), "{c}"); + assert!(fallback_rank(c, "Segoe UI Emoji") < fallback_rank(c, "Segoe UI Symbol"), "{c}"); + assert!(fallback_rank(c, "Noto Color Emoji") < fallback_rank(c, "Noto Emoji"), "{c}"); + } + // Text-style symbols keep the symbol fonts. + for c in ['✔', '☺', '→', 'a', '1'] { + assert!(!is_emoji(c), "{c}"); + } + } } From e500df7fb6f3f8ae004095d3dfc1d6d0899f3814 Mon Sep 17 00:00:00 2001 From: asystemoffields <193909980+asystemoffields@users.noreply.github.com> Date: Thu, 15 Oct 2026 09:24:08 -0400 Subject: [PATCH 026/336] Opening a named pipe or a device no longer hangs or eats memory, whatever its name (#1444 follow-up) The app read a file whose name isn't valid Unicode (#1440) with a plain std::fs::read, and file.open on a path (also wordcraft-cli) checked only the length before std::fs::read. Neither checked for a regular file: a named pipe blocked the open (app start-up hung), and /dev/zero read until memory ran out. The new io::read_document reads a document path inside the automation read root, only as a regular file of at most 2 GB, through the existing read_regular_file. open_path_in and the app's open_non_unicode both use it, so the two read a path the same way. Regression tests: file.open on a FIFO and read_document on /dev/zero are refused without blocking (engine), and the app's open of a FIFO or a /dev/zero symlink under a non-Unicode name is a quick warning that leaves the open document alone. Co-Authored-By: Claude Opus 5.5 --- apps/wordcraft/src/main.rs | 38 ++++++++++++++++++++++++++++++++++++-- crates/engine/src/io.rs | 33 +++++++++++++++++++++++++++------ crates/engine/src/tests.rs | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 8 deletions(-) diff --git a/apps/wordcraft/src/main.rs b/apps/wordcraft/src/main.rs index e7b533015f..87df52e422 100644 --- a/apps/wordcraft/src/main.rs +++ b/apps/wordcraft/src/main.rs @@ -303,10 +303,10 @@ fn text_args() -> (Vec, Vec) { /// Open a file whose name isn't valid Unicode (#1440). `file.open` takes its path as text, so it /// gets the file's bytes under the lossy name, and the document then keeps the real path to save to. +/// The bytes are read as `file.open` reads a path: only a regular file, and at most 2 GB. fn open_non_unicode(app: &mut WordApp, path: &std::path::Path) { let name = path.to_string_lossy(); - let opened = std::fs::read(path) - .map_err(|e| e.to_string()) + let opened = wordcraft_engine::io::read_document(path) .and_then(|bytes| app.run("file.open", serde_json::json!({"path": name, "data": wordcraft_engine::cmd::insert::base64_encode(&bytes)}))); match opened { // Not when it waits for a password: that open names the document itself. @@ -510,6 +510,40 @@ mod tests { assert_eq!(app.session.path.as_deref(), Some(path.as_path())); let _ = std::fs::remove_dir_all(dir); } + + /// A named pipe or a device under such a name is a warning, not a read: the pipe blocked + /// start-up until something wrote to it, and `/dev/zero` read until memory ran out. + #[test] + fn a_pipe_or_device_whose_name_isnt_unicode_is_not_read() { + let dir = std::env::temp_dir().join(format!("wc-app-non-utf8-dev-{}", std::process::id())); + let _ = std::fs::remove_dir_all(&dir); + std::fs::create_dir_all(&dir).unwrap(); + let fifo = dir.join(std::ffi::OsString::from_vec(b"fif\xe9.txt".to_vec())); + if !std::process::Command::new("mkfifo").arg(&fifo).status().is_ok_and(|s| s.success()) { + let _ = std::fs::remove_dir_all(&dir); + eprintln!("mkfifo unavailable; skipping"); + return; + } + let zero = dir.join(std::ffi::OsString::from_vec(b"z\xe9ro.txt".to_vec())); + std::os::unix::fs::symlink("/dev/zero", &zero).unwrap(); + // If the open blocks on the pipe, a writer comes after 10 s, so the test fails rather than hangs. + let unblock = fifo.clone(); + std::thread::spawn(move || { + std::thread::sleep(std::time::Duration::from_secs(10)); + let _ = std::fs::OpenOptions::new().write(true).open(unblock); + }); + + let mut app = WordApp::new(Session::new(wordcraft_doc::Document::new()), Services::default()); + app.run("text.insert", serde_json::json!({"text": "kept"})).unwrap(); + for p in [&fifo, &zero] { + let started = std::time::Instant::now(); + open_non_unicode(&mut app, p); + assert!(started.elapsed() < std::time::Duration::from_secs(5), "{p:?} took {:?}", started.elapsed()); + assert_eq!(app.session.path, None, "{p:?}"); + assert!(app.session.doc.plain_text(wordcraft_doc::StoryRef::Body).contains("kept"), "{p:?}"); + } + let _ = std::fs::remove_dir_all(dir); + } } #[cfg(test)] diff --git a/crates/engine/src/io.rs b/crates/engine/src/io.rs index 725f633c5c..8b87acae97 100644 --- a/crates/engine/src/io.rs +++ b/crates/engine/src/io.rs @@ -346,12 +346,7 @@ pub fn open_path_with(path: &std::path::Path, password: Option<&str>) -> Result< /// [`open_path_with`] in `region` (see [`open_bytes_in`]). #[cfg(not(target_arch = "wasm32"))] pub fn open_path_in(path: &std::path::Path, password: Option<&str>, region: &wordcraft_geom::Region) -> Result<(Document, bool), String> { - crate::file_access::check_read(&path.to_string_lossy())?; - let meta = std::fs::metadata(path).map_err(|e| format!("{}: {e}", path.display()))?; - if meta.len() > 2 << 30 { - return Err(format!("{}: file is larger than 2 GB", path.display())); - } - let bytes = std::fs::read(path).map_err(|e| format!("{}: {e}", path.display()))?; + let bytes = read_document(path)?; let name = path.to_string_lossy(); if matches!(ext_of(&name).as_str(), "html" | "htm" | "xhtml") { // Pictures referenced by a relative path load from beside the HTML file. @@ -365,6 +360,32 @@ pub fn open_path_in(path: &std::path::Path, password: Option<&str>, region: &wor open_bytes_in(&name, &bytes, password, region) } +/// Largest document file opened by path. +#[cfg(not(target_arch = "wasm32"))] +const MAX_DOCUMENT: u64 = 2 << 30; + +/// The bytes of the document at `path`, read as `file.open` reads a path: inside the automation +/// read root, and only a regular file of at most 2 GB. A named pipe would block the open and a +/// device such as `/dev/zero` would never end ([`read_regular_file`]). Public for the app, which +/// opens a file whose name isn't valid Unicode through `file.open`'s `data` (#1440). +#[cfg(not(target_arch = "wasm32"))] +pub fn read_document(path: &std::path::Path) -> Result, String> { + crate::file_access::check_read(&path.to_string_lossy())?; + let meta = std::fs::metadata(path).map_err(|e| format!("{}: {e}", path.display()))?; + if !meta.is_file() { + return Err(format!("{}: not a file", path.display())); + } + if meta.len() > MAX_DOCUMENT { + return Err(format!("{}: file is larger than 2 GB", path.display())); + } + // Checked again on the opened file, which may not be the one just looked at. + match read_regular_file(path, MAX_DOCUMENT) { + Some(bytes) if bytes.len() as u64 <= MAX_DOCUMENT => Ok(bytes), + Some(_) => Err(format!("{}: file is larger than 2 GB", path.display())), + None => Err(format!("{}: could not be read as a file", path.display())), + } +} + /// The pictures an HTML file names by a path relative to its folder. /// /// Only regular files inside the folder (after resolving `..` and symlinks) are read. The folder is diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 038786e02a..af074855c6 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -3266,6 +3266,39 @@ fn html_pictures_through_a_symlink_out_of_the_folder_are_not_loaded() { assert!(doc.plain_text(StoryRef::Body).contains("Link")); } +/// `file.open` on a path reads only a regular file: a named pipe blocked until a writer came, and +/// `/dev/zero` read until memory ran out. +#[cfg(unix)] +#[test] +fn opening_a_named_pipe_or_device_is_refused_without_blocking() { + let dir = scratch_dir("open-fifo"); + let fifo = dir.join("pipe.docx"); + let made = std::process::Command::new("mkfifo").arg(&fifo).status(); + if !made.is_ok_and(|s| s.success()) { + let _ = std::fs::remove_dir_all(&dir); + eprintln!("mkfifo unavailable; skipping"); + return; + } + let zero = dir.join("zero.txt"); + std::os::unix::fs::symlink("/dev/zero", &zero).unwrap(); + let (tx, rx) = std::sync::mpsc::channel(); + let (pipe, device) = (fifo.clone(), zero.clone()); + std::thread::spawn(move || { + let mut s = Session::new(wordcraft_doc::Document::new()); + let by_command = s.run("file.open", &json!({"path": pipe.to_string_lossy()})).map(|_| ()); + let _ = tx.send([by_command.map_err(|e| e.to_string()), crate::io::read_document(&device).map(|_| ())]); + }); + let got = rx.recv_timeout(std::time::Duration::from_secs(10)); + if got.is_err() { + // Unblock the stuck reader so the thread ends, then fail. + let _ = std::fs::OpenOptions::new().write(true).open(&fifo); + } + let _ = std::fs::remove_dir_all(&dir); + let [pipe, device] = got.expect("opening a named pipe blocked"); + assert!(pipe.unwrap_err().contains("not a file")); + assert!(device.unwrap_err().contains("not a file")); +} + #[cfg(unix)] #[test] fn html_picture_that_is_a_named_pipe_is_skipped_without_blocking() { From 9d22d69ee57b2acebb60ede384b6d27a85824753 Mon Sep 17 00:00:00 2001 From: asystemoffields <193909980+asystemoffields@users.noreply.github.com> Date: Thu, 15 Oct 2026 09:24:09 -0400 Subject: [PATCH 027/336] Contributors: add asystemoffields to people.toml --- contributors/people.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contributors/people.toml b/contributors/people.toml index 6a405d053c..043488c372 100644 --- a/contributors/people.toml +++ b/contributors/people.toml @@ -135,3 +135,6 @@ sync_github_name = false real_name = "Andrej Škvorc" display_name = "Andrej Škvorc" sync_github_name = false + +[people.asystemoffields] +display_name = "asystemoffields" From 5795259c45780061c13f5573217bdc0bc764c7b0 Mon Sep 17 00:00:00 2001 From: asystemoffields <193909980+asystemoffields@users.noreply.github.com> Date: Thu, 15 Oct 2026 10:10:23 -0400 Subject: [PATCH 028/336] File: recovery copies and .json files store pictures as base64 (#586 follow-up) Native JSON wrote Document::media and Document::passthrough with serde's default for bytes, an array of numbers. With a synthetic 27 MiB picture a recovery copy (compact) was 96.4 MiB and a .json file (pretty-printed, one number per line) 285.5 MiB. Each part is now one standard base64 string (doc::byte_store, using the workspace base64 crate), encoded straight into the writer. Reading accepts base64 or the old array of numbers, so recovery copies and .json files written before still open; anything else is an error, never a panic. Measured (release, same synthetic picture): compact 96.4 -> 36.0 MiB, pretty 285.5 -> 36.1 MiB, write 252/614 -> 52/57 ms, read 404/533 -> 46/37 ms. A recovery snapshot of a 6,000-paragraph document with the picture: 99 -> 39 MB, worker time 2.3 -> 0.26 s, start-up scan 1.03 -> 0.14 s. Tests: byte_store unit tests (round trip, old arrays, hostile values); recovery_format.rs native_json_stores_bytes_as_base64, native_json_with_byte_arrays_still_opens, native_json_with_bad_bytes_is_an_error, ignored measurement native_json_size_with_a_large_picture; desktop.rs copies_with_byte_arrays_written_before_still_restore. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 1 + ROADMAP.md | 4 +- crates/doc/Cargo.toml | 1 + crates/doc/src/byte_store.rs | 130 +++++++++++++++++++++++++ crates/doc/src/lib.rs | 7 +- crates/engine/src/recovery/desktop.rs | 20 ++++ crates/engine/tests/recovery_format.rs | 85 ++++++++++++++++ docs/file-format-parity.md | 3 +- docs/recovery.md | 9 +- 9 files changed, 252 insertions(+), 8 deletions(-) create mode 100644 crates/doc/src/byte_store.rs diff --git a/Cargo.lock b/Cargo.lock index 6f7c8bf02f..896674a259 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5186,6 +5186,7 @@ version = "0.7.0" name = "wordcraft-doc" version = "0.7.0" dependencies = [ + "base64", "proptest", "serde", "serde_json", diff --git a/ROADMAP.md b/ROADMAP.md index 6ef6b33708..7f25d57ebe 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **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-14 · **Change:** 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-15 · **Change:** trivial (progress log: recovery copies and .json files store pictures as base64, #586 follow-up; 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-14 · **Change:** trivial (progress log: print-area strings translated in every catalog; 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. @@ -139,6 +139,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | 2026-10-14 | Tab stops edited on the ruler: the corner selector picks Left/Center/Right/Decimal/Bar, a click sets a stop (Alt skips the snap), dragging moves it with a guide line and position tooltip, dragging off the ruler clears it; markers draw per kind and mirror for RTL; one `para.tabs` call and one undo step per gesture | | 2026-10-12 | Fields keep `w:dirty` through DOCX saves (#877): a begin marker flagged by a generator that couldn't compute a result (pandoc-style TOC placeholders) keeps the flag on open/save — as a buffered field or a range field — so Word still refreshes it on open; computing a result (Update Field, Update Table) clears it, like Word's F9 | |---|---| +| 2026-10-15 | Crash recovery copies and native .json files store pictures and kept parts as base64 text instead of arrays of numbers (#586 follow-up, #518): with a 27 MiB picture a recovery copy is 36 MiB instead of 96 MiB and a .json file 36 MiB instead of 286 MiB, written and read about five times faster. Copies and files written before still open | | 2026-10-14 | Consistent preferred cell widths override a stale table grid when both totals agree, for fixed and AutoFit tables. Conflicting widths, spans and percentages remain unchanged. | | 2026-10-14 | Legacy VML freeforms (`v:shape` with its own `path`), such as the rule lines in many older headers, are read as freeform shapes: they show on screen and in PDFs and are saved back as DrawingML custom geometry instead of vanishing | | 2026-10-14 | Field instructions spanning paragraphs no longer add blank lines during pagination; empty result paragraphs still take a line. | @@ -402,6 +403,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | Date | Change | Summary | |---|---|---| +| 2026-10-15 | trivial | Progress log: recovery copies and .json files store pictures as base64 (#586 follow-up) | | 2026-10-14 | trivial | Progress log: row table border exceptions | | 2026-10-12 | minor | Czech proofing review: short-word splits rank after deletion, NOSUGGEST excludes suggestions while accepting typed forms, affix comments apply only to headers, ratios keep spaces and repeated-word messages use Czech quotes; synthetic engine end-to-end and latency checks | | 2026-10-12 | minor | Czech spelling from the installed Hunspell dictionary, runtime-table suggestions and generic checks; Czech interface writes cs-CZ documents; no Czech hyphenation patterns bundled; estimates unchanged | diff --git a/crates/doc/Cargo.toml b/crates/doc/Cargo.toml index f0111eb18b..9ab62d34d8 100644 --- a/crates/doc/Cargo.toml +++ b/crates/doc/Cargo.toml @@ -10,6 +10,7 @@ workspace = true [dependencies] wordcraft-geom = { workspace = true } +base64 = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } diff --git a/crates/doc/src/byte_store.rs b/crates/doc/src/byte_store.rs new file mode 100644 index 0000000000..75fae6453c --- /dev/null +++ b/crates/doc/src/byte_store.rs @@ -0,0 +1,130 @@ +//! How [`crate::Document::media`] and [`crate::Document::passthrough`] are written in the native +//! JSON form (`.json` files and crash-recovery copies): each part's bytes as one base64 string. +//! serde's default for bytes, an array of numbers, took about 3.6 times their size compact and +//! over six times pretty-printed (a 27 MB picture made a 99 MB recovery copy, #586). +//! +//! Reading takes either form, so `.json` files and recovery copies written before still open. +use std::collections::BTreeMap; +use std::fmt; +use std::sync::Arc; + +use base64::Engine as _; +use base64::display::Base64Display; +use base64::engine::general_purpose::STANDARD; +use serde::de::{self, Deserializer, MapAccess, SeqAccess, Visitor}; +use serde::ser::{Serialize, Serializer}; + +type Store = BTreeMap>>; + +/// One part's bytes, encoded as it is written out: no second copy of a large picture is made. +struct Encoded<'a>(&'a [u8]); + +impl Serialize for Encoded<'_> { + fn serialize(&self, s: S) -> Result { + s.collect_str(&Base64Display::new(self.0, &STANDARD)) + } +} + +pub(crate) fn serialize(store: &Store, s: S) -> Result { + s.collect_map(store.iter().map(|(name, bytes)| (name, Encoded(bytes)))) +} + +pub(crate) fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result { + d.deserialize_map(StoreVisitor) +} + +struct StoreVisitor; + +impl<'de> Visitor<'de> for StoreVisitor { + type Value = Store; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("a map of part names to base64 text or byte arrays") + } + + fn visit_map>(self, mut map: A) -> Result { + let mut store = Store::new(); + while let Some((name, Bytes(bytes))) = map.next_entry::()? { + store.insert(name, Arc::new(bytes)); + } + Ok(store) + } +} + +/// A part's bytes as either form. +struct Bytes(Vec); + +impl<'de> de::Deserialize<'de> for Bytes { + fn deserialize>(d: D) -> Result { + d.deserialize_any(BytesVisitor).map(Bytes) + } +} + +struct BytesVisitor; + +impl<'de> Visitor<'de> for BytesVisitor { + type Value = Vec; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("base64 text or an array of bytes") + } + + fn visit_str(self, text: &str) -> Result, E> { + STANDARD.decode(text).map_err(|e| E::custom(format!("bytes are not valid base64: {e}"))) + } + + fn visit_bytes(self, bytes: &[u8]) -> Result, E> { + Ok(bytes.to_vec()) + } + + /// The form written before: one number per byte. + fn visit_seq>(self, mut seq: A) -> Result, A::Error> { + // The length hint comes from the file: trust it only so far. + let mut out = Vec::with_capacity(seq.size_hint().unwrap_or(0).min(1 << 20)); + while let Some(b) = seq.next_element::()? { + out.push(b); + } + Ok(out) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug, Default)] + struct Parts { + #[serde(default, with = "super")] + media: Store, + } + + fn parts(entries: &[(&str, &[u8])]) -> Parts { + Parts { media: entries.iter().map(|(n, b)| ((*n).to_string(), Arc::new(b.to_vec()))).collect() } + } + + #[test] + fn bytes_are_written_as_base64_and_read_back() { + let all: Vec = (0..=255).collect(); + for n in [0, 1, 2, 3, 4, 255, 256] { + let p = parts(&[("a.png", &all[..n.min(all.len())])]); + let text = serde_json::to_string(&p).unwrap(); + assert_eq!(serde_json::from_str::(&text).unwrap(), p, "{n}"); + } + assert_eq!(serde_json::to_string(&parts(&[("m", b"Man"), ("ma", b"Ma")])).unwrap(), r#"{"media":{"m":"TWFu","ma":"TWE="}}"#); + } + + #[test] + fn number_arrays_written_before_still_read() { + let old = r#"{"media":{"a.png":[0,1,127,128,254,255],"empty":[]}}"#; + assert_eq!(serde_json::from_str::(old).unwrap(), parts(&[("a.png", &[0, 1, 127, 128, 254, 255]), ("empty", &[])])); + } + + #[test] + fn hostile_values_are_errors_not_panics() { + for bad in [r#""T""#, r#""TWFu!""#, r#""-_""#, r#""TQ=x""#, "[256]", "[-1]", "[1.5]", "true", "{}", r#"["a"]"#] { + let text = format!(r#"{{"media":{{"a.png":{bad}}}}}"#); + assert!(serde_json::from_str::(&text).is_err(), "{bad}"); + } + assert!(serde_json::from_str::(r#"{"media":"x"}"#).is_err()); + } +} diff --git a/crates/doc/src/lib.rs b/crates/doc/src/lib.rs index 797d6c3e4e..18449c9498 100644 --- a/crates/doc/src/lib.rs +++ b/crates/doc/src/lib.rs @@ -11,6 +11,7 @@ #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] pub mod bidi; +mod byte_store; pub mod chart; mod cjk_numbers; pub mod cluster; @@ -676,11 +677,11 @@ pub struct Document { /// The document's own endnote separators and continuation notice. #[serde(skip_serializing_if = "NoteSeparators::is_empty")] pub endnote_separators: NoteSeparators, - /// Embedded media (images) by key. - #[serde(skip_serializing_if = "BTreeMap::is_empty")] + /// Embedded media (images) by key. Native JSON writes each as base64 (`byte_store.rs`). + #[serde(skip_serializing_if = "BTreeMap::is_empty", with = "byte_store")] pub media: BTreeMap>>, /// Source-format parts we keep verbatim for round-trip (e.g. custom XML), by part name. - #[serde(skip_serializing_if = "BTreeMap::is_empty")] + #[serde(skip_serializing_if = "BTreeMap::is_empty", with = "byte_store")] pub passthrough: BTreeMap>>, } diff --git a/crates/engine/src/recovery/desktop.rs b/crates/engine/src/recovery/desktop.rs index 676a6ed0cd..ad38ed0da8 100644 --- a/crates/engine/src/recovery/desktop.rs +++ b/crates/engine/src/recovery/desktop.rs @@ -706,6 +706,26 @@ mod tests { assert_eq!(Store::files(&t.0.join(id)).unwrap().len(), 1); } + /// Copies are written with picture bytes in base64 (#586 follow-up); those written before kept + /// them as arrays of numbers and are still listed and restored, bytes intact. + #[test] + fn copies_with_byte_arrays_written_before_still_restore() { + let t = Temp::new(); + let id = new_id().unwrap(); + let mut s = snapshot(&id, "Synthetic draft with a picture"); + s.generation = 1; + s.document.media.insert("media/image1.png".into(), Arc::new(vec![0, 1, 127, 128, 254, 255])); + let mut value = serde_json::to_value(&s).unwrap(); + assert_eq!(value["document"]["media"]["media/image1.png"], "AAF/gP7/"); + value["document"]["media"]["media/image1.png"] = json!([0, 1, 127, 128, 254, 255]); + private_dir(&t.0.join(&id)).unwrap(); + fs::write(t.0.join(&id).join("snapshot-00000000000000000001.json"), serde_json::to_vec(&value).unwrap()).unwrap(); + let mut store = Store::new(t.0.clone()).unwrap(); + store.scan().unwrap(); + assert_eq!(store.inventory().len(), 1); + assert_eq!(store.load(&id).unwrap().document, s.document); + } + #[test] fn failed_pruning_cannot_push_a_record_beyond_the_discovery_limit() { let t = Temp::new(); diff --git a/crates/engine/tests/recovery_format.rs b/crates/engine/tests/recovery_format.rs index 38f372ac76..f889d6a832 100644 --- a/crates/engine/tests/recovery_format.rs +++ b/crates/engine/tests/recovery_format.rs @@ -67,3 +67,88 @@ fn existing_native_json_without_byte_stores_still_opens() { let doc = wordcraft_engine::io::open_bytes("old.json", &serde_json::to_vec(&value).unwrap()).unwrap(); assert_eq!(doc.plain_text(wordcraft_doc::StoryRef::Body), "Old native document"); } + +/// Picture and part bytes are one base64 string each in native JSON (`.json` files and recovery +/// copies, #586). An array of numbers took about 3.6 times their size, and over ten times in a +/// pretty-printed `.json` file. +#[test] +fn native_json_stores_bytes_as_base64() { + let mut doc = Document::from_text("Synthetic"); + let compact = |d: &Document| serde_json::to_vec(d).unwrap().len(); + let pretty = |d: &Document| wordcraft_engine::io::save_bytes("doc.json", d).unwrap(); + let (without, without_pretty) = (compact(&doc), pretty(&doc).len()); + let bytes: Vec = (0..300_000u32).map(|i| (i.wrapping_mul(2_654_435_761) >> 13) as u8).collect(); + doc.media.insert("big.png".into(), Arc::new(bytes.clone())); + doc.passthrough.insert("customXml/item1.xml".into(), Arc::new(b"".to_vec())); + let saved = pretty(&doc); + // Base64 is 4/3 of the bytes; allow a little for names and punctuation. + for (with, before) in [(compact(&doc), without), (saved.len(), without_pretty)] { + assert!(with - before < bytes.len() * 14 / 10, "{} bytes for {}", with - before, bytes.len()); + } + let value: serde_json::Value = serde_json::from_slice(&saved).unwrap(); + assert_eq!(value["passthrough"]["customXml/item1.xml"], "PHN5bnRoZXRpYy8+"); + let reopened = wordcraft_engine::io::open_bytes("doc.json", &saved).unwrap(); + assert_eq!((reopened.media, reopened.passthrough), (doc.media, doc.passthrough)); +} + +/// `.json` files and recovery copies written before kept bytes as arrays of numbers: they still +/// open, with the same bytes. +#[test] +fn native_json_with_byte_arrays_still_opens() { + let doc = complete_document(); + let mut value = serde_json::to_value(&doc).unwrap(); + for (store, parts) in [("media", &doc.media), ("passthrough", &doc.passthrough)] { + for (name, bytes) in parts { + value[store][name] = serde_json::json!(bytes.as_slice()); + } + } + assert!(value["media"]["synthetic.png"].is_array()); + let reopened = wordcraft_engine::io::open_bytes("old.json", &serde_json::to_vec_pretty(&value).unwrap()).unwrap(); + assert_eq!(reopened, doc); +} + +/// Bytes that are neither base64 nor an array of bytes make the file fail to open, never panic. +#[test] +fn native_json_with_bad_bytes_is_an_error() { + let saved = wordcraft_engine::io::save_bytes("doc.json", &complete_document()).unwrap(); + for bad in [serde_json::json!("not base64!"), serde_json::json!([1, 2, 300]), serde_json::json!({"a": 1}), serde_json::json!(7)] { + let mut value: serde_json::Value = serde_json::from_slice(&saved).unwrap(); + value["media"]["synthetic.png"] = bad.clone(); + assert!(wordcraft_engine::io::open_bytes("doc.json", &serde_json::to_vec(&value).unwrap()).is_err(), "{bad}"); + } +} + +/// Measures native JSON with a large picture: the compact form a recovery copy wraps, and a +/// pretty-printed `.json` file. Run on demand: +/// `cargo test --release -p wordcraft-engine --test recovery_format -- --ignored --nocapture`. +#[test] +#[ignore = "measurement"] +fn native_json_size_with_a_large_picture() { + let mut doc = Document::from_text("Synthetic document with one large picture."); + // Incompressible bytes, as a photograph's are. + let mut x = 0x2545_f491_4f6c_dd1du64; + let noise: Vec = (0..27u32 << 20) + .map(|_| { + x ^= x << 13; + x ^= x >> 7; + x ^= x << 17; + x as u8 + }) + .collect(); + doc.media.insert("media/image1.png".into(), Arc::new(noise)); + let mib = |n: usize| n as f64 / f64::from(1u32 << 20); + let t0 = std::time::Instant::now(); + let compact = serde_json::to_vec(&doc).unwrap(); + let written = t0.elapsed(); + let t0 = std::time::Instant::now(); + let back: Document = serde_json::from_slice(&compact).unwrap(); + println!("27 MiB picture, compact (recovery copy): {:.1} MiB, written in {written:?}, read in {:?}", mib(compact.len()), t0.elapsed()); + assert_eq!(back.media, doc.media); + let t0 = std::time::Instant::now(); + let pretty = wordcraft_engine::io::save_bytes("doc.json", &doc).unwrap(); + let written = t0.elapsed(); + let t0 = std::time::Instant::now(); + let back = wordcraft_engine::io::open_bytes("doc.json", &pretty).unwrap(); + println!("27 MiB picture, .json file (pretty): {:.1} MiB, written in {written:?}, read in {:?}", mib(pretty.len()), t0.elapsed()); + assert_eq!(back.media, doc.media); +} diff --git a/docs/file-format-parity.md b/docs/file-format-parity.md index 38a1638f16..a309a9d1fd 100644 --- a/docs/file-format-parity.md +++ b/docs/file-format-parity.md @@ -1,6 +1,6 @@ # File-format parity with Microsoft Word -> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-14 · **Change:** trivial (every table style part round-trips, #714); previously trivial: VML freeforms read and kept on save; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: `flowChartOr` draws its circle and cross spokes, as in Word; previously trivial: RTF: Mac OS Roman and DOS text, #1653; previously trivial: VML text boxes keep their internal margins, fit to their text and vertical alignment; hidden drawings not shown; previously trivial: PDF export draws horizontal and vertical lines inside groups, #1474; previously trivial: symbol-font text exported as Unicode, #776; previously trivial: text-box margins, Resize shape to fit text and freeforms in groups, #743; previously trivial: formatted paste from the system clipboard's HTML and RTF, #687; previously trivial: independent complex-script formatting inheritance and round-trip regression tests; previously trivial: import/export fixes for TXT, RTF, ODT, HTML and LaTeX, #433–#440; previously major: first version; every format Word reads or writes, measured from the readers and writers on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4, plus Word for Windows' extra formats +> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-15 · **Change:** trivial (native .json stores pictures and kept parts as base64, #586 follow-up; previously trivial: every table style part round-trips, #714); previously trivial: VML freeforms read and kept on save; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: `flowChartOr` draws its circle and cross spokes, as in Word; previously trivial: RTF: Mac OS Roman and DOS text, #1653; previously trivial: VML text boxes keep their internal margins, fit to their text and vertical alignment; hidden drawings not shown; previously trivial: PDF export draws horizontal and vertical lines inside groups, #1474; previously trivial: symbol-font text exported as Unicode, #776; previously trivial: text-box margins, Resize shape to fit text and freeforms in groups, #743; previously trivial: formatted paste from the system clipboard's HTML and RTF, #687; previously trivial: independent complex-script formatting inheritance and round-trip regression tests; previously trivial: import/export fixes for TXT, RTF, ODT, HTML and LaTeX, #433–#440; previously major: first version; every format Word reads or writes, measured from the readers and writers on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4, plus Word for Windows' extra formats Every format Word opens or saves, what WordCraft does with it, and how it's tested. Fidelity is the share of a typical real-world file's content and formatting that survives (read: shows correctly; @@ -210,6 +210,7 @@ attributes inside stories and style or list definitions; a regenerated theme. Te | Date | Change | Summary | |---|---|---| +| 2026-10-15 | trivial | Native .json writes pictures and kept parts as base64 text instead of arrays of numbers (a 27 MiB picture: 36 MiB instead of 286 MiB); files with the old arrays still open (#586 follow-up) | | 2026-10-13 | trivial | A cell's explicit top vertical alignment (`w:vAlign w:val="top"`) is kept on read and write and beats a table style that centres or bottom-aligns (#1288) | | 2026-10-14 | trivial | Row table border exceptions survive DOCX saves | | 2026-10-12 | trivial | Fields keep `w:dirty` through DOCX round trips — on buffered fields and range-field begin markers alike — and computing a result (Update Field, Update Table) clears it like Word's F9, so a generator's placeholder TOC still refreshes when Word opens the file until WordCraft replaces the placeholder (#877) | diff --git a/docs/recovery.md b/docs/recovery.md index 00ce845061..45a05479fc 100644 --- a/docs/recovery.md +++ b/docs/recovery.md @@ -32,7 +32,9 @@ be lost if a crash happens before the next snapshot or while its write is still Version 1 snapshots wrap the native JSON document with an id, document name, timestamp and monotonic generation. Native JSON now includes media bytes, opaque preserved parts, and -chart/SmartArt/OLE source markup and relationships. Older JSON without these fields still opens. +chart/SmartArt/OLE source markup and relationships. Media and preserved parts are written as one +base64 string each. Older JSON without these fields, or with their bytes as arrays of numbers (as +written before base64), still opens. Paragraph cache revisions are transient and regenerated on load; undo history and selection are not part of the document snapshot. Recovery preserves the current in-memory model. It cannot recover content already lost during import or make later export more faithful than the exporter. @@ -50,8 +52,9 @@ Windows is therefore unverified. Limits are 256 MiB of encoded JSON per snapshot, 2 GiB of snapshot files per profile, 128 records and 16 generations per record during discovery. Ordinarily only two valid generations remain. Copies are retained until saved or explicitly discarded; there is no automatic age-based deletion. -Over-limit writes report an error and retain the previous valid copy. JSON stores binary parts -as byte arrays, so it can be substantially larger than DOCX. This first version has a fixed +Over-limit writes report an error and retain the previous valid copy. Base64 takes 4/3 of a +part's size (a 27 MiB picture makes a 36 MiB copy), and DOCX compresses, so a copy can still be +larger than the DOCX it came from. This first version has a fixed interval, English fallback for new labels, and no cloud, version-history or browser recovery. ## Commands and verification From 9371cb05362bcdc62623683f7ff032e70fb632ab Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Thu, 15 Oct 2026 14:44:40 +0000 Subject: [PATCH 029/336] Return 0 for SIGN(0) in table formulas Found while testing crates/engine/src/cmd/table_formula.rs:721 Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Sonnet 5.5 --- crates/engine/src/cmd/table_formula.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/cmd/table_formula.rs b/crates/engine/src/cmd/table_formula.rs index 165716d689..a68df8198c 100644 --- a/crates/engine/src/cmd/table_formula.rs +++ b/crates/engine/src/cmd/table_formula.rs @@ -718,7 +718,10 @@ fn call(name: &str, args: &[Expr], here: &Here) -> Result { } "ABS" => Ok(one(name, args, here)?.abs()), "INT" => Ok(one(name, args, here)?.floor()), - "SIGN" => Ok(one(name, args, here)?.signum()), + "SIGN" => { + let x = one(name, args, here)?; + Ok(if x == 0.0 { 0.0 } else { x.signum() }) + } "ROUND" => match args { [a, b] => { let x = scalar(eval(a, here)?)?; @@ -763,6 +766,16 @@ mod tests { evaluate(t, r, c, f) } + #[test] + fn sign_of_zero_is_zero() { + let t = table(&[&["0", "5", "-5"]]); + assert_eq!(ev(&t, 0, 0, "=SIGN(0)"), Ok(0.0)); + assert_eq!(ev(&t, 0, 0, "=SIGN(-0)"), Ok(0.0)); + assert_eq!(ev(&t, 0, 1, "=SIGN(A1)"), Ok(0.0), "a cell holding 0"); + assert_eq!(ev(&t, 0, 0, "=SIGN(5)"), Ok(1.0)); + assert_eq!(ev(&t, 0, 0, "=SIGN(-5)"), Ok(-1.0)); + } + #[test] fn cell_references_count_from_one_and_from_a() { assert_eq!(cell_ref("A1"), Some((0, 0))); From 57a000e97853cd8094ec0b9a522f96f274f78201 Mon Sep 17 00:00:00 2001 From: tydude001 Date: Thu, 15 Oct 2026 11:04:44 -0500 Subject: [PATCH 030/336] Linux: scan the Flatpak host font folders (#1937) Inside a Flatpak, /usr/share/fonts and /usr/local/share/fonts are the runtime's and the home font folders aren't exposed, so WordCraft listed only the runtime's fonts. Flatpak mounts the host's system, local and user fonts at /run/host/fonts, /run/host/local-fonts and /run/host/user-fonts for every app, and its main branch (after 1.19.2) also mounts ~/.fonts at /run/host/user-fonts-legacy when the XDG user font folder exists too; scan those as well. Folders that don't exist are skipped, as before. Co-Authored-By: Claude Opus 5.5 --- crates/fonts/src/fontdb.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crates/fonts/src/fontdb.rs b/crates/fonts/src/fontdb.rs index 5f07295138..98e3c4630f 100644 --- a/crates/fonts/src/fontdb.rs +++ b/crates/fonts/src/fontdb.rs @@ -736,6 +736,10 @@ pub fn system_font_dirs() -> Vec { dirs.push(h.join(".fonts")); dirs.push(h.join(".local/share/fonts")); } + // Flatpak (#1937): the folders above are the runtime's, and the host's system, local and + // user fonts are mounted here instead (no filesystem permission needed). Flatpak after + // 1.19.2 also mounts ~/.fonts, at user-fonts-legacy when the XDG user font folder exists too. + dirs.extend(["/run/host/fonts", "/run/host/local-fonts", "/run/host/user-fonts", "/run/host/user-fonts-legacy"].map(Into::into)); } dirs } @@ -1710,6 +1714,16 @@ mod tests_sysfonts; mod tests { use super::*; + #[test] + #[cfg(all(unix, not(target_os = "macos"), not(target_arch = "wasm32")))] + fn system_font_dirs_include_the_flatpak_host_folders() { + // Inside a Flatpak, /usr/share/fonts is the runtime's: the host's fonts are under /run/host (#1937). + let dirs = system_font_dirs(); + for d in ["/usr/share/fonts", "/run/host/fonts", "/run/host/local-fonts", "/run/host/user-fonts", "/run/host/user-fonts-legacy"] { + assert!(dirs.contains(&std::path::PathBuf::from(d)), "{d} missing from {dirs:?}"); + } + } + #[test] fn east_asian_run_fallback_is_cached_per_char_set() { let db = FontDb::with_font_dirs(Vec::new()); From bdbfa620569a254d4705144bfb68fc9b310b224b Mon Sep 17 00:00:00 2001 From: Divyanshu Gupta Date: Thu, 15 Oct 2026 21:49:08 +0530 Subject: [PATCH 031/336] fix(formats): support left alignment in markdown tables --- crates/formats/src/markdown.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/crates/formats/src/markdown.rs b/crates/formats/src/markdown.rs index 45c8052022..0410c56bae 100644 --- a/crates/formats/src/markdown.rs +++ b/crates/formats/src/markdown.rs @@ -215,6 +215,7 @@ fn delimiter_row(l: &str) -> Option>> { out.push(match (left, right) { (true, true) => Some(Align::Center), (false, true) => Some(Align::Right), + (true, false) => Some(Align::Left), _ => None, }); } @@ -1280,6 +1281,7 @@ fn table_md(t: &FTable, out: &mut Vec) { .map(|a| match a { Some(Align::Center) => ":---:", Some(Align::Right) => "---:", + Some(Align::Left) => ":---", _ => "---", }) .collect(); @@ -1680,4 +1682,22 @@ mod tests { assert_eq!(back_table2.rows[1][0].text(), "a`b|c"); assert_eq!(back_table2.rows[1][1].text(), "CRITICAL_DISTINCTIVE_INFO"); } + + #[test] + fn table_column_alignments_import_and_export() { + let md = "| Left | Center | Right | Default |\n| :--- | :---: | ---: | --- |\n| 1 | 2 | 3 | 4 |\n"; + let flow = parse(md); + let FBlock::Table(t) = &flow.blocks[0] else { panic!("expected table") }; + assert_eq!(t.rows.len(), 2); + let aligns: Vec> = t.rows[0] + .iter() + .map(|c| c.blocks.iter().find_map(|b| if let FBlock::Para(p) = b { Some(p.align) } else { None }).flatten()) + .collect(); + assert_eq!(aligns, vec![Some(Align::Left), Some(Align::Center), Some(Align::Right), None]); + + let mut out = Vec::new(); + table_md(t, &mut out); + let exported = out.join("\n"); + assert!(exported.contains("| :--- | :---: | ---: | --- |"), "Exported: {exported}"); + } } From 19109657d01a38eca4467de19199de138b1e2713 Mon Sep 17 00:00:00 2001 From: Divyanshu Gupta Date: Thu, 15 Oct 2026 22:01:39 +0530 Subject: [PATCH 032/336] fix(formats): support distributed paragraph alignment in RTF --- crates/formats/src/rtf.rs | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/crates/formats/src/rtf.rs b/crates/formats/src/rtf.rs index c8b002a205..8cb87677bb 100644 --- a/crates/formats/src/rtf.rs +++ b/crates/formats/src/rtf.rs @@ -243,7 +243,8 @@ impl Writer { match p.align { Some(Align::Center) => out.push_str("\\qc"), Some(Align::Right) => out.push_str("\\qr"), - Some(Align::Justify) | Some(Align::Distribute) => out.push_str("\\qj"), + Some(Align::Justify) => out.push_str("\\qj"), + Some(Align::Distribute) => out.push_str("\\qd"), _ => out.push_str("\\ql"), } if p.page_break { @@ -1692,7 +1693,8 @@ impl Reader { "ql" => self.st.align = Some(Align::Left), "qc" => self.st.align = Some(Align::Center), "qr" => self.st.align = Some(Align::Right), - "qj" | "qd" => self.st.align = Some(Align::Justify), + "qj" => self.st.align = Some(Align::Justify), + "qd" => self.st.align = Some(Align::Distribute), "s" => self.st.style = pv, "outlinelevel" => self.st.outline = Some(pv.clamp(0, 9) as u8), "pagebb" => self.st.pagebb = on, @@ -2685,4 +2687,25 @@ mod tests { fn escapes() { assert_eq!(esc("a{b}\\c\té😀"), "a\\{b\\}\\\\c\\tab \\u233?\\u-10179?\\u-8704?"); } + + #[test] + fn distributed_alignment_round_trips() { + let rtf = br"{\rtf1\ansi\pard\qd Distributed\par\pard\qj Justified\par}"; + let flow = parse(rtf).unwrap(); + let p0 = match &flow.blocks[0] { + FBlock::Para(p) => p.align, + _ => panic!("expected para"), + }; + let p1 = match &flow.blocks[1] { + FBlock::Para(p) => p.align, + _ => panic!("expected para"), + }; + assert_eq!(p0, Some(Align::Distribute)); + assert_eq!(p1, Some(Align::Justify)); + + let doc = import(rtf).unwrap(); + let exported = export(&doc); + assert!(exported.contains("\\qd"), "expected \\qd in: {exported}"); + assert!(exported.contains("\\qj"), "expected \\qj in: {exported}"); + } } From 147866bce54269bdd6ef61fcc6b15b20262558ad Mon Sep 17 00:00:00 2001 From: Divyanshu Gupta Date: Thu, 15 Oct 2026 22:25:36 +0530 Subject: [PATCH 033/336] fix(formats): support tab-separated list markers in markdown --- crates/formats/src/markdown.rs | 70 +++++++++++++++++++++++++++++----- 1 file changed, 61 insertions(+), 9 deletions(-) diff --git a/crates/formats/src/markdown.rs b/crates/formats/src/markdown.rs index 45c8052022..a1cee369eb 100644 --- a/crates/formats/src/markdown.rs +++ b/crates/formats/src/markdown.rs @@ -161,7 +161,7 @@ fn list_marker(t: &str) -> Option { (Marker { ordered: true, ch: d, len: digits + 1, start }, digits + 1) }; let rest = t.get(len..)?; - if rest.is_empty() || rest.starts_with(' ') { Some(m) } else { None } + if rest.is_empty() || rest.starts_with([' ', '\t']) { Some(m) } else { None } } fn split_row(l: &str) -> Vec { @@ -421,6 +421,33 @@ fn parse_blocks(lines: &[String], depth: usize, level: u8) -> Vec { out } +fn marker_space(ind: usize, marker_len: usize, after: &str) -> (usize, usize) { + let mut cols = 0; + let mut bytes = 0; + let mut rest = after; + while let Some(c) = rest.chars().next() { + match c { + ' ' => { + cols += 1; + bytes += 1; + rest = &rest[1..]; + if cols >= 4 { + break; + } + } + '\t' => { + let col = ind + marker_len + cols; + let n = 4 - col % 4; + cols += n; + bytes += 1; + break; + } + _ => break, + } + } + (cols, bytes) +} + /// Parse a list starting at `lines[i]`; returns the index after it. fn parse_list(lines: &[String], mut i: usize, depth: usize, level: u8, out: &mut Vec) -> usize { let Some(first) = lines.get(i).and_then(|l| list_marker(l.trim_start())) else { return i + 1 }; @@ -435,13 +462,16 @@ fn parse_list(lines: &[String], mut i: usize, depth: usize, level: u8, out: &mut break; } let after = t.get(m.len..).unwrap_or(""); - let spaces = indent(after); - let content_indent = if is_blank(after) || spaces > 4 { ind + m.len + 1 } else { ind + m.len + spaces }; - let mut item: Vec = - vec![if is_blank(after) { String::new() } else { after.get(spaces.min(4).min(after.len())..).unwrap_or("").to_string() }]; - if spaces > 4 { - item[0] = after.get(1..).unwrap_or("").to_string(); - } + let (spaces, skip) = if indent(after) > 4 { + (1, 1) + } else if after.starts_with([' ', '\t']) { + marker_space(ind, m.len, after) + } else { + (0, 0) + }; + let content_indent = if is_blank(after) { ind + m.len + 1 } else { ind + m.len + spaces }; + let item0 = if is_blank(after) { String::new() } else { after.get(skip.min(after.len())..).unwrap_or("").to_string() }; + let mut item: Vec = vec![item0]; i += 1; while let Some(l) = lines.get(i) { if is_blank(l) { @@ -1082,13 +1112,17 @@ fn escape_line_start(s: String) -> String { || t.starts_with('>') || t.starts_with("- ") || t.starts_with("+ ") + || t.starts_with("-\t") + || t.starts_with("+\t") || t == "-" || t == "+" || t.starts_with('=') || thematic_break(t) || { let d = t.chars().take_while(|c| c.is_ascii_digit()).count(); - d > 0 && t.get(d..).is_some_and(|r| r.starts_with(". ") || r.starts_with(") ") || r == "." || r == ")") + d > 0 + && t.get(d..) + .is_some_and(|r| r.starts_with(". ") || r.starts_with(") ") || r.starts_with(".\t") || r.starts_with(")\t") || r == "." || r == ")") }; if !needs { return if cols > 3 || lead.contains('\t') { format!("{indent}{t}") } else { s }; @@ -1680,4 +1714,22 @@ mod tests { assert_eq!(back_table2.rows[1][0].text(), "a`b|c"); assert_eq!(back_table2.rows[1][1].text(), "CRITICAL_DISTINCTIVE_INFO"); } + + #[test] + fn tab_separated_list_markers() { + let f = parse("-\tbullet item\n*\tstar item\n+\tplus item\n1.\tone item\n2)\ttwo item\n"); + assert_eq!(f.blocks.len(), 5); + for (i, b) in f.blocks.iter().enumerate() { + let FBlock::Para(p) = b else { panic!("block {i} not a para") }; + assert!(p.list.is_some(), "block {i} should be a list item: {:?}", p); + } + let FBlock::Para(p0) = &f.blocks[0] else { panic!() }; + assert_eq!(p0.text(), "bullet item"); + assert_eq!(p0.list.unwrap().ordered, false); + + let FBlock::Para(p3) = &f.blocks[3] else { panic!() }; + assert_eq!(p3.text(), "one item"); + assert_eq!(p3.list.unwrap().ordered, true); + } } + From 262310756d0bef8d4048b9664f3f7be80b999a6d Mon Sep 17 00:00:00 2001 From: Filip Strajnar Date: Mon, 12 Oct 2026 21:08:26 +0200 Subject: [PATCH 034/336] i18n: add Slovenian (sl) interface translation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A 2,463-entry Slovenian catalog: every key of every other catalog in the repository (built-in style names, equation gallery, paper sizes, citation styles included). Written from the meaning of the English labels in ordinary Slovenian computing vocabulary; no other product's localisation was used. Counts use count-neutral wording ("Besede: 21"), since the catalog has no dual/plural forms. - sl, sl-SI, sl_SI.UTF-8 select it automatically; "Slovenščina" in Options and ui.language with {"value":"sl"}. - The interface audit checks Slovenian coverage the way it checks Russian. - Tests: locales and saved preference, catalog coverage and key terms, count wording, and bundled fonts covering č, š, ž. - docs/localization-sl.md with conventions and terminology; parity table, README, ROADMAP and ATTRIBUTION updated. --- ATTRIBUTION.md | 1 + README.md | 2 +- ROADMAP.md | 5 +- crates/ui-egui/src/i18n/audit.rs | 4 +- crates/ui-egui/src/i18n/mod.rs | 4 +- crates/ui-egui/src/i18n/sl.tsv | 2481 ++++++++++++++++++++++++++++++ crates/ui-egui/src/i18n/tests.rs | 96 +- docs/localization-parity.md | 14 +- docs/localization-sl.md | 58 + 9 files changed, 2652 insertions(+), 13 deletions(-) create mode 100644 crates/ui-egui/src/i18n/sl.tsv create mode 100644 docs/localization-sl.md diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index 14b58afcdc..3f8480e573 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -74,6 +74,7 @@ Generated-in-code art is original and has no file to list: the UI icon set (`cra | `crates/ui-egui/src/i18n/ru.tsv` | Dmitrii Khlapov with Claude | Original translation from the English interface labels in this repository; no other product localisation copied | MIT OR Apache-2.0 | Russian interface catalog; reviewed by a native speaker | | `crates/ui-egui/src/i18n/tr.tsv` | @PbyLoyth with Codex and Claude Code | Original translation from the English interface labels in this repository; no other product localisation copied | MIT OR Apache-2.0 | Turkish interface catalog | | `crates/ui-egui/src/i18n/bg.tsv` | Dimitar Klaturov (@bulgariamitko) with Claude Code | Original translation from the English interface labels in this repository; no other product localisation or other catalog used | MIT OR Apache-2.0 | Bulgarian interface catalog | +| `crates/ui-egui/src/i18n/sl.tsv` | Filip Strajnar with Grok Bot | Original translation from the English interface labels in this repository; no other product localisation copied | MIT OR Apache-2.0 | Slovenian interface catalog | | `crates/ui-egui/src/i18n/hu.tsv` | @Skaywalker | Original translation from the English interface labels in this repository; no other product localisation copied | MIT OR Apache-2.0 | Hungarian interface catalog | | `crates/ui-egui/src/i18n/fr.tsv` | @pechnikov2026-star | Original translation from the English interface labels in this repository; no other product localisation copied | MIT OR Apache-2.0 | French interface catalog | | `crates/ui-egui/src/i18n/sv.tsv` | Erik Holmbom (@holmbom) with Claude Code | Original translation from the English interface labels in this repository; no other product localisation or other catalog used | MIT OR Apache-2.0 | Swedish interface catalog; AI-assisted, terminology and the running app reviewed by a native speaker | diff --git a/README.md b/README.md index 15ebbb05f2..6c8221b6b6 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ Every screenshot below is WordCraft itself, rendered offscreen by its own UI tes | **References** | Table of contents, footnotes and endnotes, citations and bibliography (APA, MLA, Chicago, IEEE), captions, table of figures, cross-references, index, table of authorities | | **Review** | Spelling and grammar with suggestions (English and Polish, by each run's language), thesaurus, word count, comments in margin balloons or a pane, track changes, accept/reject, compare documents, restrict editing, accessibility checker, document inspector | | **Mailings** | Mail merge from CSV, merge fields, address block, greeting line, rules, preview, finish to a document; envelopes and labels | -| **View** | Print layout, web layout, draft, read mode, focus, zoom, one/multiple pages, page width, Navigation pane, rulers, gridlines, dark mode; interface in English, 简体中文, 繁體中文, 日本語, Português (Brasil), Español, Українська, Српски / Srpski, Eesti, Türkçe, Русский, Italiano, Deutsch, Polski, Čeština, Magyar, Français, Български, Tiếng Việt, 한국어 or Svenska (follows the system language by default) | +| **View** | Print layout, web layout, draft, read mode, focus, zoom, one/multiple pages, page width, Navigation pane, rulers, gridlines, dark mode; interface in English, 简体中文, 繁體中文, 日本語, Português (Brasil), Español, Українська, Српски / Srpski, Eesti, Türkçe, Русский, Italiano, Deutsch, Polski, Čeština, Magyar, Français, Български, Tiếng Việt, 한국어, Svenska, Hrvatski or Slovenščina (follows the system language by default) | | **Files** | .docx read/write (opens in Word), Word 97-2003 .doc import, PDF export, .odt, .rtf, .html, .md, .tex, .txt import/export, page images | The honest picture, area by area, is in [ROADMAP.md](ROADMAP.md), the diff --git a/ROADMAP.md b/ROADMAP.md index 6ef6b33708..748ca93b54 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -112,10 +112,10 @@ Weights and evidence per area: [docs/target-app-parity.md](docs/target-app-parit | Korean | ko | partial (interface catalog; proofing unavailable) | 100% (2,463 entries) | | Vietnamese | vi | partial (#1434) | ~95% | -Also shipped: Traditional Chinese (`zh-hant`), Ukrainian (`uk`), Serbian (`sr`, `sr-latn`, #250), Russian (`ru`) and Polish (`pl`), all partial (~95%). Bulgarian (`bg`) covers everything the interface audit finds (2,419 entries); Swedish (`sv`) covers that too, plus 44 newer strings no other catalog has yet (2,504 entries). Estonian (`et`), Italian (`it`, #594) and Czech (`cs`) now have complete interface catalogs, and Turkish (`tr`) a complete 2,325-entry one (every key of the Russian catalog), all with +Also shipped: Traditional Chinese (`zh-hant`), Ukrainian (`uk`), Serbian (`sr`, `sr-latn`, #250), Russian (`ru`) and Polish (`pl`), all partial (~95%). Bulgarian (`bg`) covers everything the interface audit finds (2,419 entries); Swedish (`sv`) covers that too, plus 44 newer strings no other catalog has yet (2,504 entries). Slovenian (`sl`) covers every key of every catalog. Estonian (`et`), Italian (`it`, #594) and Czech (`cs`) now have complete interface catalogs, and Turkish (`tr`) a complete 2,325-entry one (every key of the Russian catalog), all with system-locale selection and a saved language choice (Italian also has 51 strings none of the others had yet); Estonian, Turkish and Italian proofing are not included. Hungarian (`hu`) has a complete 2,419-entry catalog, also without proofing. Vietnamese (`vi`, #1434) has a complete -2,448-entry catalog, also without proofing. +2,448-entry catalog, also without proofing. Croatian (`hr`, #1271) has a complete catalog. Proofing: English, Polish, Portuguese and Russian (spelling, grammar, hyphenation), plus Czech (system Hunspell spelling, generic checks), chosen per run from its language. Word ships 30 interface languages and proofing for ~50. Detail: [docs/localization-parity.md](docs/localization-parity.md). @@ -369,6 +369,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | 2026-10-12 | Native saves use exclusively created sibling temporary files, synchronize before replacement and clean up on failure; existing temporary-name files/links and original files on failed saves are protected. Synthetic CLI and focused tests on macOS; no new Word parity or readiness claim. | | 2026-10-11 | Field codes like Word's field keys (#339): show codes (Alt+F9, Shift+F9), edit, unlink, lock and step through fields | | 2026-10-11 | Word's common keyboard shortcuts (#330): 101 → 125 of ~250 | +| 2026-10-15 | Slovenian (`sl`) interface: every key of every other catalog, `sl-SI` system locales and a saved language choice; locale, catalog and bundled-font tests; no Slovenian proofing resources | | 2026-10-13 | Swedish (`sv`) interface: 2,504 catalog entries (every key of the reference catalog, the equation gallery, everything the interface audit finds, and 44 newer strings no catalog had yet, among them the native print settings), `sv-SE`/`sv-FI` system locales and a saved language choice; locale, catalog and bundled-font tests; AI-assisted, terminology and the running app reviewed by a native speaker; no Swedish proofing resources | | 2026-10-12 | Bulgarian (`bg`) interface: 2,419 catalog entries (every key of the reference catalog, the equation gallery and everything the interface audit finds), `bg-BG` system locales and a saved language choice; locale, catalog and bundled-font tests; no Bulgarian proofing resources | | 2026-10-13 | Croatian (`hr`) interface: 2,507 catalog entries (every key of the reference catalog, built-in style names and the equation gallery), `hr-*` system locales and a saved language choice; locale, catalog and bundled-font tests; no Croatian proofing yet | diff --git a/crates/ui-egui/src/i18n/audit.rs b/crates/ui-egui/src/i18n/audit.rs index 9c326d5640..b98c9c53c5 100644 --- a/crates/ui-egui/src/i18n/audit.rs +++ b/crates/ui-egui/src/i18n/audit.rs @@ -333,7 +333,7 @@ fn untranslated_interface_text() { complete_catalogs_have_every_string_the_interface_looked_up(pseudo::looked_up()); } -/// Russian, Hungarian, Turkish, Bulgarian, Italian, Swedish and Croatian cover everything the audit saw +/// Russian, Hungarian, Turkish, Bulgarian, Italian, Swedish, Croatian and Slovenian cover everything the audit saw /// translated, plus the equation gallery's labels (shown in menus the audit doesn't open one by /// one). Numbered style names go through a template. fn complete_catalogs_have_every_string_the_interface_looked_up(mut keys: std::collections::BTreeSet) { @@ -379,7 +379,7 @@ fn complete_catalogs_have_every_string_the_interface_looked_up(mut keys: std::co // translate: its placeholder names aren't words the interface shows. let words = |s: &str| english_words(&s.split('{').map(|p| p.split_once('}').map_or(p, |(_, rest)| rest)).collect::()); let mut report = String::new(); - for code in ["ru", "hu", "tr", "bg", "it", "sv", "hr"] { + for code in ["ru", "hu", "tr", "bg", "it", "sv", "hr", "sl"] { let lang = super::Lang::from_code(code).unwrap_or(super::Lang::EN); let missing: Vec<&String> = keys.iter().filter(|s| words(s) && !super::has(lang, s) && !numbered(s) && !same(s) && !script_error(s)).collect(); diff --git a/crates/ui-egui/src/i18n/mod.rs b/crates/ui-egui/src/i18n/mod.rs index 092e376844..e506e6ae29 100644 --- a/crates/ui-egui/src/i18n/mod.rs +++ b/crates/ui-egui/src/i18n/mod.rs @@ -52,7 +52,7 @@ pub struct LangInfo { } /// The registry. English first: it is the fallback and the source language. -pub static LANGUAGES: [LangInfo; 23] = [ +pub static LANGUAGES: [LangInfo; 24] = [ LangInfo { code: "en", name: "English", source: "", prefer_hans: false, editing_lang: None, catalog: OnceLock::new() }, // Simplified Chinese; `zh`, `zh-CN`, `zh-SG` and `zh-Hans-*` resolve here (see `candidates`). LangInfo { @@ -165,6 +165,8 @@ pub static LANGUAGES: [LangInfo; 23] = [ LangInfo { code: "sv", name: "Svenska", source: include_str!("sv.tsv"), prefer_hans: false, editing_lang: None, catalog: OnceLock::new() }, // Croatian; `hr`, `hr-HR` and `hr_HR.UTF-8` resolve here. LangInfo { code: "hr", name: "Hrvatski", source: include_str!("hr.tsv"), prefer_hans: false, editing_lang: None, catalog: OnceLock::new() }, + // Slovenian; `sl`, `sl-SI` and `sl_SI.UTF-8` resolve here. + LangInfo { code: "sl", name: "Slovenščina", source: include_str!("sl.tsv"), prefer_hans: false, editing_lang: None, catalog: OnceLock::new() }, ]; impl LangInfo { diff --git a/crates/ui-egui/src/i18n/sl.tsv b/crates/ui-egui/src/i18n/sl.tsv new file mode 100644 index 0000000000..8e95bf21f6 --- /dev/null +++ b/crates/ui-egui/src/i18n/sl.tsv @@ -0,0 +1,2481 @@ +# Slovenian (sl) interface catalog. Columns, separated by a TAB: +# context English source translation +# context usually empty. `preview` words sample text inside rendered previews (the Design +# tab's style-set tiles), which must fit a small tile; it falls back to the plain entry. +# source the English text exactly as in the code (tl!(...), ribbon labels, command labels) +# escapes \n \t \\ inside a column. `{name}` placeholders must appear in both columns, and a +# trailing "…" in one needs one in the other. +# Lines starting with # and blank lines are ignored. A missing entry shows in English. +# +# Clean room: every entry was written from the plain meaning of the English label in ordinary +# Slovenian computing vocabulary. No Microsoft Word (or other product) localisation, string table +# or translated UI was consulted or copied; add entries the same way. +# Ribbon tabs, groups and panes are nouns (Vstavljanje, Pisava, Podokno slogov); commands and +# buttons use the imperative (Shrani, Vstavi tabelo); messages address the user with the polite +# plural (Shranite dokument). Only the first word of a label is capitalised. Product, format and +# technology names stay as they are (WordCraft, PDF, Markdown, MCP …). Counts use count-neutral +# wording (`Besede: 21`), so no plural forms (dvojina, množina) are needed. Quotes are »…«. + + % of all commits % vseh potrditev + (All) (Vse) + (Custom) (Po meri) + (no style) (brez sloga) + (no suggestions) (ni predlogov) + (none) (brez) + (normal text) (navadno besedilo) + (not matched) (ni ujemanja) + (picture or object) (slika ali predmet) + 0 or More Characters 0 ali več znakov + 1. Formatting restrictions 1. Omejitve oblikovanja + 1.5 Line Spacing Razmik vrstic 1,5 + 1/4 Em Space Presledek 1/4 em + 1×2 Empty Matrix Prazna matrika 1×2 + 1×3 Empty Matrix Prazna matrika 1×3 + 2. Editing restrictions 2. Omejitve urejanja + 2×1 Empty Matrix Prazna matrika 2×1 + 2×2 Determinant Determinanta 2×2 + 2×2 Empty Matrix Prazna matrika 2×2 + 2×2 Identity Matrix Enotska matrika 2×2 + 2×2 Identity Matrix (sparse) Enotska matrika 2×2 (redka) + 2×2 Matrix in Brackets Matrika 2×2 v oglatih oklepajih + 2×2 Matrix in Double Bars Matrika 2×2 v dvojnih navpičnicah + 2×2 Matrix in Parentheses Matrika 2×2 v okroglih oklepajih + 2×3 Empty Matrix Prazna matrika 2×3 + 3-D 3D + 3. Start enforcement 3. Začetek uveljavljanja + 3×1 Empty Matrix Prazna matrika 3×1 + 3×2 Empty Matrix Prazna matrika 3×2 + 3×3 Empty Matrix Prazna matrika 3×3 + 3×3 Identity Matrix Enotska matrika 3×3 + 3×3 Identity Matrix (sparse) Enotska matrika 3×3 (redka) + << Less << Manj + << Remove << Odstrani + A file dialog is already open. Pogovorno okno za datoteke je že odprto. + A free, open-source word processor written from scratch in Rust.\nPart of the Crafting Apps from the ArtCraft team. Brezplačen odprtokodni urejevalnik besedil, iz nič napisan v jeziku Rust.\nDel zbirke Crafting Apps ekipe ArtCraft. + A mail merge field Polje spajanja dokumentov + A numbered sequence (figures, tables…) Oštevilčeno zaporedje (slike, tabele …) + A password is required to open this document. Za odpiranje tega dokumenta je potrebno geslo. + A table of contents Kazalo vsebine + A xor B A xor B + A, B, C, … A, B, C, … + APA APA + ASAP ČIM PREJ + AaBbCcDd AaBbČ莞 + AaBbYyZz AaBbČčŠšŽž + About O programu + About WordCraft O programu WordCraft + Above Chart Nad grafikonom + Above the item Nad elementom + Absolute value cases Primeri absolutne vrednosti + Accent Poudarek + Accent Gradient Poudarek s prelivom + Accent with Reflection Poudarek z odsevom + Accent {n} Poudarek {n} + Accents Naglasi + Accept Sprejmi + Accept All Changes Sprejmi vse spremembe + Accept This Change Sprejmi to spremembo + Accessed on Datum dostopa + Accessibility Dostopnost + Accessibility Pane Podokno za dostopnost + Acute Ostrivec + Add Dodaj + Add >> Dodaj >> + Add Chart Element Dodaj element grafikona + Add Field Dodaj polje + Add Font… Dodaj pisavo … + Add New Source… Dodaj nov vir … + Add Object to Selection Dodaj predmet v izbor + Add Pen Dodaj pero + Add Row Dodaj vrstico + Add Series Dodaj niz + Add Shape Dodaj obliko + Add Space Before Paragraph Dodaj razmik pred odstavkom + Add Text Dodaj besedilo + Add a Table Dodaj tabelo + Add a comment Dodaj komentar + Add at least one field. Dodajte vsaj eno polje. + Add line numbering Dodaj oštevilčenje vrstic + Add space between Asian and Latin text Dodaj presledek med azijskim in latiničnim besedilom + Add space between Asian text and numbers Dodaj presledek med azijskim besedilom in številkami + Add tab stop at: Dodaj tabulator pri: + Add the chapter number Dodaj številko poglavja + Add to Dictionary Dodaj v slovar + Add to Document Dodaj v dokument + Add to Quick Access Toolbar Dodaj v orodno vrstico za hitri dostop + Add to Style Gallery Dodaj v galerijo slogov + Add/Edit\nBibliography Dodaj/uredi\nbibliografijo + Add/Edit\nCitation Dodaj/uredi\ncitat + Add\nNote Dodaj\nopombo + Add\nPen Dodaj\npero + Add\nShape Dodaj\nobliko + Added Fonts Dodane pisave + Address Naslov + Address Block Blok z naslovom + Address labels Naslovne nalepke + Address: Naslov: + Address\nBlock Blok z\nnaslovom + Adjust Prilagodi + Advanced Napredno + Advanced Find Napredno iskanje + Advanced Find… Napredno iskanje … + After: Za: + Agents Agenti + Align Poravnaj + Align Bottom Center Poravnaj spodaj na sredino + Align Bottom Left Poravnaj spodaj levo + Align Bottom Right Poravnaj spodaj desno + Align Center Poravnaj na sredino + Align Center Left Poravnaj na sredino levo + Align Center Right Poravnaj na sredino desno + Align Left Poravnaj levo + Align Right Poravnaj desno + Align Text Poravnaj besedilo + Align Top Center Poravnaj zgoraj na sredino + Align Top Left Poravnaj zgoraj levo + Align Top Right Poravnaj zgoraj desno + Aligned at: Poravnano pri: + Alignment Poravnava + Alignment: Poravnava: + All Vse + All - Linear Vse – linearno + All - Professional Vse – profesionalno + All Borders Vse obrobe + All Caps Vse velike črke + All Commands Vsi ukazi + All Levels Vse ravni + All Markup Vse oznake + All caps Vse velike črke + All files Vse datoteke + All pages Vse strani + All pages in the range Vse strani v obsegu + All symbol sets Vsi nabori simbolov + Allow Latin text to wrap in the middle of a word Dovoli prelom latiničnega besedila sredi besede + Allow hanging punctuation Dovoli viseča ločila + Allow line breaks Dovoli prelome vrstic + Allow row to break across pages Dovoli prelom vrstice čez strani + Alphabetical Po abecedi + Alt Text Nadomestno besedilo + Alt Text Pane Podokno za nadomestno besedilo + Alt text Nadomestno besedilo + Alt: Nadomestno: + Alt\nText Nadomestno\nbesedilo + Angle Brackets Lomljeni oklepaji + Angle Brackets with Separator Lomljeni oklepaji z ločilom + Angle Brackets with Two Separators Lomljeni oklepaji z dvema ločiloma + Animate the caret (it glides and fades) Animiraj besedilni kazalec (drsi in pojema) + Animation Animacija + Animation effects: Animacijski učinki: + Annotation Reference Sklic na pripombo + Annotation Subject Zadeva pripombe + Annotation Text Besedilo pripombe + Another file Druga datoteka + Any Character Poljuben znak + Any Digit Poljubna števka + Any Letter Poljubna črka + Anyone can open this document. Encrypt it with a password to keep it private. Ta dokument lahko odpre kdor koli. Če želite, da ostane zaseben, ga šifrirajte z geslom. + Anyone who opens the document will need this password. Vsak, ki odpre dokument, bo potreboval to geslo. + Apply Uporabi + Apply Asian line-breaking rules (kinsoku) Uporabi azijska pravila za prelom vrstic (kinsoku) + Apply Character Style Uporabi znakovni slog + Apply Style Uporabi slog + Apply Styles Uporabi sloge + Apply as you type Uporabi med tipkanjem + Apply formatting to: Uporabi oblikovanje za: + Apply to: Uporabi za: + Arabic Arabske številke + Arch Down Lok navzdol + Arch Up Lok navzgor + Area Ploščinski + Arrange Razporedi + Arrange\nAll Razporedi\nvse + Arrow Puščica + Arrows Puščice + Artistic Effects Umetniški učinki + As Recommended Kot je priporočeno + As you type Med tipkanjem + Asian Typography Azijska tipografija + Asian text font: Pisava za azijsko besedilo: + Assign Dodeli + At least Najmanj + At least {n} Najmanj {n} + Atelier Atelje + Atmosphere Vzdušje + Attach Template Pripni predlogo + Attach… Pripni … + Author Avtor + Auto Samodejno + AutoCorrect Samopopravki + AutoCorrect Options Možnosti samopopravkov + AutoFit Samodejno prilagodi + AutoFit Column by Double-Clicking Samodejno prilagodi stolpec z dvoklikom + AutoFit Contents Samodejno prilagodi vsebini + AutoFit Window Samodejno prilagodi oknu + AutoFormat As You Type Samooblikovanje med tipkanjem + AutoSave Samodejno shranjevanje + AutoSave documents you have saved in WordCraft Samodejno shranjuj dokumente, ki ste jih shranili v programu WordCraft + AutoSave failed: {error}. Save the document to turn AutoSave back on. Samodejno shranjevanje ni uspelo: {error}. Shranite dokument, da znova vklopite samodejno shranjevanje. + AutoSave isn't available in the browser: it can't write to your files. Save downloads a copy. Samodejno shranjevanje v brskalniku ni na voljo, ker ne more pisati v vaše datoteke. Ukaz Shrani prenese kopijo. + AutoSave only keeps .docx, .odt and .rtf files; {name} can't keep all formatting. Save as .docx to turn on AutoSave. Samodejno shranjevanje deluje samo za datoteke .docx, .odt in .rtf; {name} ne more ohraniti vsega oblikovanja. Shranite kot .docx, da vklopite samodejno shranjevanje. + AutoSave saves every change to the file (needs a saved document) Samodejno shranjevanje shrani vsako spremembo v datoteko (dokument mora biti shranjen) + AutoSave writes your changes to the file a moment after you stop typing. Samodejno shranjevanje zapiše spremembe v datoteko trenutek po tem, ko nehate tipkati. + AutoText Samobesedilo + Automatic Samodejno + Automatic ({language}) Samodejno ({language}) + Automatic ({region}) Samodejno ({region}) + Automatic Table 1 (3 levels) Samodejna tabela 1 (3 ravni) + Automatic Table 2 (2 levels) Samodejna tabela 2 (2 ravni) + Automatic bulleted lists Samodejni označeni seznami + Automatic follows your system's language. Menus and commands change; your documents don't. Samodejno sledi jeziku sistema. Spremenijo se meniji in ukazi, vaši dokumenti ne. + Automatic numbered lists Samodejni oštevilčeni seznami + Automatically Update Document Styles Samodejno posodobi sloge dokumenta + Automatically hyphenate document Samodejno deli besede v dokumentu + Automatically update document styles Samodejno posodobi sloge dokumenta + B4 (JIS) 10.12" × 14.33" B4 (JIS) 10.12" × 14.33" + B5 (JIS) 7.17" × 10.12" B5 (JIS) 7.17" × 10.12" + Backspace Vračalka + Balloons Oblački + Banded Columns Pasovni stolpci + Banded Rows Pasovne vrstice + Bar Palični + Base text: Osnovno besedilo: + Based on: {style} Temelji na: {style} + Baseline Dots Pike na osnovnici + Basic Cycle Osnovni cikel + Basic List Osnovni seznam + Basic Math Osnovna matematika + Basic Matrix Osnovna matrika + Basic Process Osnovni proces + Basic Pyramid Osnovna piramida + Basic Radial Osnovni radialni + Basic Venn Osnovni Vennov diagram + Bayes' rule Bayesovo pravilo + Before: Pred: + Beginning of Word Začetek besede + Behind Text Za besedilom + Below text Pod besedilom + Below the item Pod elementom + Between Med + Bibliography Bibliografija + Binary assets added Dodana binarna sredstva + Binary assets removed Odstranjena binarna sredstva + Binomial Coefficient Binomski koeficient + Binomial Coefficient (angle) Binomski koeficient (lomljeni oklepaji) + Binomial Coefficient with Angle Brackets Binomski koeficient z lomljenimi oklepaji + Binomial expansion of (x+a)ⁿ Binomski razvoj (x+a)ⁿ + Blank Prazno + Blank (Three Columns) Prazno (trije stolpci) + Blank Page Prazna stran + Blank document Prazen dokument + Block\nAuthors Blokiraj\navtorje + Blue: Modra: + Body Text Telo besedila + Body text in a short paragraph to show spacing. Telo besedila v kratkem odstavku za prikaz razmikov. + Bold Krepko + Book Knjiga + Book Title Naslov knjige + Book title Naslov knjige + Bookmark Zaznamek + Bookmark name: Ime zaznamka: + Border Painter Čopič za obrobe + Border lines Črte obrobe + Border\nPainter Čopič za\nobrobe + Borders Obrobe + Borders and Shading Obrobe in senčenje + Borders and Shading… Obrobe in senčenje … + Both Sides Obe strani + Bottom Spodaj + Bottom Border Spodnja obroba + Bottom Margin Spodnji rob + Bottom of Page Dno strani + Bottom: Spodaj: + Box Okvir + Boxed Formula Uokvirjena formula + Boxed Formula (example) Uokvirjena formula (primer) + Boxed Formulas Uokvirjene formule + Braces Zaviti oklepaji + Braces with Separator Zaviti oklepaji z ločilom + Bracket Oklepaj + Brackets Oklepaji + Brackets with Separators Oklepaji z ločili + Break Link Prekini povezavo + Breaks Prelomi + Breve Breve + Bring Forward Premakni naprej + Browse in WordCraft… Prebrskaj v programu WordCraft … + Browse… Prebrskaj … + Building Block Gallery Content Control Kontrolnik vsebine galerije gradnikov + Built-In Vgrajeno + Built-in style Vgrajeni slog + Built-in styles can't be deleted. Vgrajenih slogov ni mogoče izbrisati. + Bullet Oznaka + Bullet Library Knjižnica oznak + Bullets Oznake + By author Po avtorju + CONFIDENTIAL ZAUPNO + Calendar Koledar + Can't be deleted Ni mogoče izbrisati + Cancel Prekliči + Canvas Member Element platna + Capitalize Each Word Vsaka Beseda Z Veliko Začetnico + Capitalize first letter of sentences Prva črka stavka velika + Capitalize first letter of table cells Prva črka v celicah tabele velika + Capitalize names of days Imena dni z veliko začetnico + Capitalize the first word of a sentence Prva beseda stavka z veliko začetnico + Caption Napis + Caption Numbering Oštevilčenje napisov + Caption text Besedilo napisa + Caption: Napis: + Captions Napisi + Caret Character Znak strešice + Case changes Spremembe velikosti črk + Cases (Three Conditions) Primeri (trije pogoji) + Cases (Two Conditions) Primeri (dva pogoja) + Cases and Stacks Primeri in skladi + Categories Kategorije + Categories: Kategorije: + Category Kategorija + Category {n} Kategorija {n} + Ceiling Zgornji celi del + Cell Celica + Cell Margins Robovi celic + Cell Size Velikost celice + Cell\nMargins Robovi\ncelic + Center Sredina + Centered Na sredini + Centered as Group Na sredini kot skupina + Centimeters Centimetri + Change Spremeni + Change Case Spremeni velikost črk + Change Chart Type Spremeni vrsto grafikona + Change Colors Spremeni barve + Change Layout Spremeni postavitev + Change List Level Spremeni raven seznama + Change Password… Spremeni geslo … + Change Picture Spremeni sliko + Change Picture… Spremeni sliko … + Change Shape Spremeni obliko + Change Structure Spremeni strukturo + Change in y over change in x Sprememba y glede na spremembo x + Change to Display Spremeni v prikazno obliko + Change to Inline Spremeni v vrstično obliko + Change to Level 1 Spremeni v raven 1 + Change to Level 2 Spremeni v raven 2 + Change to Level 3 Spremeni v raven 3 + Change to Level 4 Spremeni v raven 4 + Change to Level 5 Spremeni v raven 5 + Change to Level 6 Spremeni v raven 6 + Change to Level 7 Spremeni v raven 7 + Change to Level 8 Spremeni v raven 8 + Change to Level 9 Spremeni v raven 9 + Change\nChart Type Spremeni vrsto\ngrafikona + Change\nColors Spremeni\nbarve + Changed lines: Spremenjene vrstice: + Changes Spremembe + Chapter in a book Poglavje v knjigi + Chapters begin at: Poglavja se začnejo pri: + Character Znak + Character Border Obroba znakov + Character Position Položaj znakov + Character Scale Merilo znakov + Character Shading Senčenje znakov + Character Spacing Razmik med znaki + Character code: Koda znaka: + Character in Range Znak v obsegu + Character level Znakovna raven + Character spacing Razmik med znaki + Character style Znakovni slog + Characters Znaki + Characters (no spaces) Znaki (brez presledkov) + Characters (with spaces) Znaki (s presledki) + Characters per line: Število znakov v vrstici: + Chart Grafikon + Chart Design Načrt grafikona + Chart Layouts Postavitve grafikona + Chart Title Naslov grafikona + Check Preveri + Check Accessibility Preveri dostopnost + Check Box Content Control Kontrolnik vsebine potrditvenega polja + Check Compatibility Preveri združljivost + Check Spelling as You Type Preverjaj črkovanje med tipkanjem + Check for Errors Preveri napake + Check spelling as you type Preverjaj črkovanje med tipkanjem + Check\nAccessibility Preveri\ndostopnost + Checked styles are allowed: Dovoljeni so označeni slogi: + Checked symbol Simbol za označeno polje + Chevron Down Ševron navzdol + Chevron Up Ševron navzgor + Chicago Chicago + Chinese (Simplified) Kitajščina (poenostavljena) + Chinese (Traditional) Kitajščina (tradicionalna) + Choose List Item Izberi element seznama + Choose a command Izberite ukaz + Choose a shortcut Izberite bližnjico + Choose the sheet that holds the recipients. Izberite list, na katerem so prejemniki. + Circle Krog + Circle area from its radius Ploščina kroga iz polmera + Circled Dot Pika v krogu + Circled Plus Plus v krogu + Circled Times Krat v krogu + Citation Style Slog citiranja + Citations Citati + Citations & Bibliography Citati in bibliografija + City Mesto + Clear Počisti + Clear All Počisti vse + Clear All Formatting Počisti vse oblikovanje + Clear Character Formatting Počisti oblikovanje znakov + Clear Character Style Počisti znakovni slog + Clear Formatting Level Počisti raven oblikovanja + Clear Paragraph Formatting Počisti oblikovanje odstavka + Click a border between two cells to merge them. Esc stops. Kliknite mejo med celicama, da ju spojite. Esc ustavi. + Click an item to paste it. Kliknite element, da ga prilepite. + Click level to modify: Kliknite raven, ki jo želite spremeniti: + Click the empty text box the text should continue in (Esc cancels) Kliknite prazno polje z besedilom, v katerem naj se besedilo nadaljuje (Esc prekliče) + Click to switch back to inserting Kliknite, da se vrnete na vstavljanje + Clipboard Odložišče + Clipboard Contents Vsebina odložišča + Clipboard Items Elementi odložišča + Clipboard Pane Podokno odložišča + Close Zapri + Close Equation Zapri enačbo + Close Header and Footer Zapri glavo in nogo + Close Outline View Zapri pogled orisa + Close Outline\nView Zapri pogled\norisa + Clustered Bar Gručasti palični + Clustered Column Gručasti stolpčni + Col #: Stolpec št.: + Collapse Strni + Collapse Selection Skrči izbor + Collate copies Zbiraj kopije + Collect entries from: Zberi vnose iz: + Colon Equal Dvopičje in enačaj + Color Barva + Color model: Barvni model: + Color only Samo barva + Color: Barva: + Color: {color} Barva: {color} + Colorful Barvito + Colors Barve + Colors: Barve: + Column Stolpec + Column Break Prelom stolpca + Column Selection Izbor stolpca + Column: Stolpec: + Columns Stolpci + Columns: Stolpci: + Combine Združi + Combine Documents Združi dokumente + Combo Box Content Control Kontrolnik vsebine kombiniranega polja + Commands Ukazi + Commands: Ukazi: + Comment Komentar + Comments Komentarji + Comments Pane Podokno s komentarji + Comments beside each page and tracked changes in PDFs, prints and images; a comment list at the end of web pages, Markdown and text. Komentarji ob vsaki strani in sledene spremembe v datotekah PDF, natisih in slikah; seznam komentarjev na koncu spletnih strani, Markdowna in besedila. + Commits Potrditve + Common Accent Objects Pogosti naglasni predmeti + Common Brackets Pogosti oklepaji + Common Fractions Pogosti ulomki + Common Functions Pogoste funkcije + Common Large Operators Pogosti veliki operatorji + Common Operator Structures Pogoste strukture operatorjev + Common Operators Pogosti operatorji + Common Radicals Pogosti koreni + Common Subscripts and Superscripts Pogosti podpisani in nadpisani indeksi + Communication Komunikacija + Community\nDiscord Skupnost\nDiscord + Compact Strnjeno + Company Podjetje + Compare Primerjaj + Compare Documents Primerjaj dokumente + Compared: {changes} changes Primerjano – število sprememb: {changes} + Comparing with another file needs the desktop app. Za primerjavo z drugo datoteko potrebujete namizno aplikacijo. + Comparison settings Nastavitve primerjave + Compress Pictures Stisni slike + Compress punctuation at the start of a line Stisni ločila na začetku vrstice + Conference Konferenca + Conference paper Konferenčni prispevek + Confirm password: Potrdite geslo: + Connect Shapes Poveži oblike + Container Title Naslov vsebnika + Content Control Properties Lastnosti kontrolnika vsebine + Content Control at Selection Kontrolnik vsebine pri izboru + Contents can't be edited Vsebine ni mogoče urejati + Context Kontekst + Continuous Neprekinjeno + Continuous numbering Neprekinjeno oštevilčenje + Contour Integral Krivuljni integral + Contour Integral with Limits Krivuljni integral z mejama + Contour Integral with Stacked Limits Krivuljni integral z zloženima mejama + Contour Integrals Krivuljni integrali + Contributors Sodelavci + Controls Kontrolniki + Conversions Pretvorbe + Convert Pretvori + Convert Notes Pretvori opombe + Convert Text to Table Pretvori besedilo v tabelo + Convert Text to Table… Pretvori besedilo v tabelo … + Convert between built-up and linear form Pretvori med sestavljeno in linearno obliko + Convert to Shapes Pretvori v oblike + Convert to Text Pretvori v besedilo + Copies Kopije + Coproduct Koprodukt + Coproduct with Limits Koprodukt z mejama + Coproduct with Lower Limit Koprodukt s spodnjo mejo + Coproduct with Subscript-Superscript Limits Koprodukt z mejama v indeksu + Copy Kopiraj + Copy Formatting Kopiraj oblikovanje + Copy Styles Kopiraj sloge + Copy of the text a bookmark marks Kopija besedila, ki ga označuje zaznamek + Copyright Avtorske pravice + Copyright Sign Znak za avtorske pravice + Correct TWo INitial CApitals Popravi DVe ZAčetni VEliki črki + Correct accidental usage of cAPS LOCK key Popravi nenamerno uporabo tipke cAPS LOCK + Corrections Popravki + Cosecant Function Funkcija kosekansa + Cosine 2x Kosinus 2x + Cosine Function Funkcija kosinusa + Cotangent Function Funkcija kotangensa + Couldn't download {name}: {error} Prenos {name} ni uspel: {error} + Couldn't make WordCraft the default: {error} WordCraft ni bilo mogoče nastaviti kot privzeti program: {error} + Couldn't open {path}: {error} Ni mogoče odpreti {path}: {error} + Count by: Štej po: + Count words in text boxes and notes Štej besede v poljih z besedilom in opombah + Country Država + Courtesy Title Naziv + Cover Page Naslovnica + Craft Craft + Create Ustvari + Create Graphic Ustvari grafiko + Create Link Ustvari povezavo + Create New Style Ustvari nov slog + Create Source Ustvari vir + Create a Style Ustvari slog + Create an interactive outline of your document.\n\nIt's a great way to keep track of where you are or quickly move your content around.\n\nTo get started, go to the Home tab and apply Heading styles to the headings in your document. Ustvarite interaktivni oris dokumenta.\n\nTako boste vedno vedeli, kje ste, in lahko hitro premikali vsebino.\n\nZa začetek na zavihku Osnovno naslovom v dokumentu dodelite sloge naslovov. + Crop Obreži + Cross-reference Navzkrižni sklic + Cross-reference: Navzkrižni sklic: + Cube Root Kubični koren + Current Trenutno + Current - Linear Trenutno – linearno + Current - Professional Trenutno – profesionalno + Current Position Trenutni položaj + Current page Trenutna stran + Current style: {style} Trenutni slog: {style} + Curved Arrow Connector Ukrivljen puščični povezovalnik + Curved Connector Ukrivljen povezovalnik + Custom Po meri + Custom Margins… Robovi po meri … + Custom Table of Contents… Kazalo vsebine po meri … + Custom Watermark Vodni žig po meri + Custom Watermark… Vodni žig po meri … + Custom dictionary Slovar po meri + Custom mark: Oznaka po meri: + Custom range Obseg po meri + Customize Prilagodi + Customize Keyboard Prilagodi tipkovnico + Customize Quick Access Toolbar Prilagodi orodno vrstico za hitri dostop + Customize Quick Access Toolbar… Prilagodi orodno vrstico za hitri dostop … + Customize Ribbon Prilagodi trak + Customize the Ribbon… Prilagodi trak … + Customize… Prilagodi … + Cut Izreži + Cycle Cikel + Czech Češčina + DO NOT COPY NE KOPIRAJ + DOI DOI + DRAFT OSNUTEK + Danish Danščina + Dark Temno + Dark Mode Temni način + Dark page (white text on black) Temna stran (belo besedilo na črnem) + Dark with Shadow Temno s senco + Darkroom Temnica + Dashed Črtkano + Data Podatki + Data Labels Oznake podatkov + Date & Time Datum in ura + Date Picker Content Control Kontrolnik vsebine za izbiro datuma + Date and Time Datum in ura + Date format Oblika datuma + Date formats: Oblike datuma: + Day Dan + Decimal Decimalno + Decorative objects only add visual interest: screen readers skip them. Okrasni predmeti so zgolj vizualni dodatek: bralniki zaslona jih preskočijo. + Decrease Font Size Zmanjšaj velikost pisave + Decrease Indent Zmanjšaj zamik + Default Privzeto + Default Paragraph Font Privzeta pisava odstavka + Default tab stops: Privzeti tabulatorji: + Define New Bullet Določi novo oznako + Define New Multilevel List Določi nov večravenski seznam + Define New Multilevel List… Določi nov večravenski seznam … + Define New Number Format Določi novo obliko številk + Deflate Izprazni + Delete Izbriši + Delete Back in Equation Izbriši nazaj v enačbi + Delete Cells Izbriši celice + Delete Clipboard Item Izbriši element odložišča + Delete Columns Izbriši stolpce + Delete Comment Izbriši komentar + Delete Label Izbriši nalepko + Delete Next Word Izbriši naslednjo besedo + Delete Previous Word Izbriši prejšnjo besedo + Delete Rows Izbriši vrstice + Delete Selected Ink Izbriši izbrano črnilo + Delete Style Izbriši slog + Delete Table Izbriši tabelo + Delete Table Style Izbriši slog tabele + Delete in Equation Izbriši v enačbi + Deleted Izbrisano + Deletions: Izbrisi: + Delivery address: Naslov za dostavo: + Delta Equal To Delta enako + Delta over arrow Delta nad puščico + Demote Znižaj raven + Demote to Body Text Znižaj na telo besedila + Derivative as a limit Odvod kot limita + Describe what this object shows, for people who can't see it. Opišite, kaj prikazuje ta predmet, za ljudi, ki ga ne vidijo. + Describe what this table shows, for people who can't see it. Opišite, kaj prikazuje ta tabela, za ljudi, ki je ne vidijo. + Description: Opis: + Design Načrt + Design Mode Način načrtovanja + Detach Template Odklopi predlogo + Detect language automatically Samodejno zaznaj jezik + Developer Razvijalec + Diagonal Diagonalno + Diagonal Dots Diagonalne pike + Diamond Romb + Dictate Narekuj + Different First Page Drugačna prva stran + Different Odd & Even Pages Drugačne lihe in sode strani + Differential Diferencial + Differential theta Diferencial theta + Differential x Diferencial x + Differential y Diferencial y + Differentials Diferenciali + Direct formatting Neposredno oblikovanje + Direction: Smer: + Directory Imenik + Discord Discord + Discord community Skupnost na Discordu + Display Prikaz + Display for Review Prikaz za pregled + Display name Prikazno ime + Display text Prikazano besedilo + Distribute Columns Enakomerno razporedi stolpce + Distribute Rows Enakomerno razporedi vrstice + Distribute letters Razporedi črke + Distribute spacing Razporedi razmike + Distributed Porazdeljeno + Do Not Check Spelling or Grammar Ne preverjaj črkovanja ali slovnice + Do Not Show in TOC Ne prikaži v kazalu vsebine + Do not check spelling or grammar Ne preverjaj črkovanja ali slovnice + Do you want to save changes to {name}? Ali želite shraniti spremembe v {name}? + Document Dokument + Document End Konec dokumenta + Document Formatting Oblikovanje dokumenta + Document Grid Mreža dokumenta + Document Information Podatki o dokumentu + Document Property Lastnost dokumenta + Document Start Začetek dokumenta + Document Text Besedilo dokumenta + Document Title Naslov dokumenta + Document template: Predloga dokumenta: + Document1 Dokument1 + Document\nPreferences Nastavitve\ndokumenta + Documents Dokumenti + Documents you open will show up here. Tukaj bodo prikazani dokumenti, ki jih odprete. + Don't Save Ne shrani + Don't add space between paragraphs of the same style Ne dodajaj razmika med odstavki istega sloga + Don't capitalize after: Brez velike začetnice za: + Don't hyphenate Ne deli besed + Don't read Zotero, Mendeley or EndNote citations and bibliographies Ne beri citatov in bibliografij iz Zotera, Mendeleya ali EndNota + Dot Pika + Dot dash Pika črta + Dot-dash Pika-črta + Dots Pike + Dotted Pikčasto + Double Dvojno + Double Bar Dvojna navpičnica + Double Closing Quote Dvojni zaključni narekovaj + Double Dot Dvojna pika + Double Integral Dvojni integral + Double Integral with Limits Dvojni integral z mejami + Double Integral with Stacked Limits Dvojni integral z zloženimi mejami + Double Left Arrow with Text Above Dvojna puščica levo z besedilom zgoraj + Double Left Arrow with Text Below Dvojna puščica levo z besedilom spodaj + Double Left Right Arrow with Text Above Dvojna puščica levo-desno z besedilom zgoraj + Double Left Right Arrow with Text Below Dvojna puščica levo-desno z besedilom spodaj + Double Opening Quote Dvojni začetni narekovaj + Double Right Arrow with Text Above Dvojna puščica desno z besedilom zgoraj + Double Right Arrow with Text Below Dvojna puščica desno z besedilom spodaj + Double Spacing Dvojni razmik + Double Square Brackets Dvojni oglati oklepaji + Double Strikethrough Dvojno prečrtano + Double Underline Dvojno podčrtano + Double Vertical Bars Dvojne navpičnice + Double strikethrough Dvojno prečrtano + Double summation Dvojna vsota + Doughnut Kolobarni + Down Navzdol + Draft Osnutek + Drag a rectangle to draw a table, or a line across cells to split them. Esc stops. Povlecite pravokotnik, da narišete tabelo, ali črto čez celice, da jih razdelite. Esc ustavi. + Draw Risanje + Draw Ink Riši s črnilom + Draw Table Nariši tabelo + Draw\nTable Nariši\ntabelo + Drawing Canvas Risalno platno + Drawing Tools Orodja za risanje + Drawing\nCanvas Risalno\nplatno + Drop Cap Spuščena začetnica + Drop cap Spuščena začetnica + Drop-Down List Content Control Kontrolnik vsebine spustnega seznama + Drops all formatting and pastes plain text. Odstrani vse oblikovanje in prilepi navadno besedilo. + Dutch Nizozemščina + E-mail Messages E-poštna sporočila + Edit Comment Uredi komentar + Edit Data Uredi podatke + Edit Equation Uredi enačbo + Edit Field Code Uredi kodo polja + Edit Field… Uredi polje … + Edit Footer Uredi nogo + Edit Header Uredi glavo + Edit Items Uredi elemente + Edit Recipient List Uredi seznam prejemnikov + Edit Source Uredi vir + Edit\nData Uredi\npodatke + Edit\nRecipient List Uredi seznam\nprejemnikov + Edited Urejeno + Editing Urejanje + Editing header/footer Urejanje glave/noge + Editing in this document is restricted. Urejanje tega dokumenta je omejeno. + Editing — edit the document directly Urejanje – neposredno urejajte dokument + Edition Izdaja + Editor Urednik + Edit… Uredi … + Effects Učinki + Elbow Arrow Connector Kolenast puščični povezovalnik + Elbow Connector Kolenast povezovalnik + Ellipsis Tropičje + Em Dash Dolgi pomišljaj + Em Space Dolgi presledek + Emboss Reliefno + Emphasis Poudarjeno + Empty Matrices Prazne matrike + En Dash Kratki pomišljaj + En Space Kratki presledek + Enclose Characters Obkroži znake + Enclosure Obroba + Encrypt with Password Šifriraj z geslom + Encrypt with Password… Šifriraj z geslom … + End of Word Konec besede + End of document Konec dokumenta + End of section Konec odseka + Endnote Reference Sklic na končno opombo + Endnote Text Besedilo končne opombe + Endnotes Končne opombe + Endnotes to footnotes Končne opombe v sprotne opombe + English (United Kingdom) Angleščina (Združeno kraljestvo) + English (United States) Angleščina (Združene države) + Engrave Vgravirano + Enlarge symbol Povečaj simbol + Enter formatting for number: Vnesite oblikovanje za številko: + Enter in Equation Enter v enačbi + Enter page number: Vnesite številko strani: + Entry marked. Vnos je označen. + Envelope #10 4.12" × 9.50" Ovojnica #10 4.12" × 9.50" + Envelope Address Naslov na ovojnici + Envelope DL 4.33" × 8.66" Ovojnica DL 4.33" × 8.66" + Envelope Return Povratni naslov na ovojnici + Envelope size: Velikost ovojnice: + Envelopes Ovojnice + Equal Equal Dvojni enačaj + Equal by Definition Enako po definiciji + Equal column width Enaka širina stolpcev + Equals: Enako: + Equation Enačba + Equation Gallery Galerija enačb + Equation Info Podatki o enačbi + Equation Number Številka enačbe + Equation Options Možnosti enačbe + Erase Ink Izbriši črnilo + Eraser Radirka + Errors ({n}) Napake ({n}) + Estonian Estonščina + Euler's identity Eulerjeva identiteta + Even Page Soda stran + Even pages only Samo sode strani + Every change is recorded as a tracked change. Vsaka sprememba se zabeleži kot sledena sprememba. + Every command is available to scripts and AI agents: run `wordcraft-cli mcp` for an MCP server, or start the app with `--control ` for the JSON control channel. Vsi ukazi so na voljo skriptam in agentom umetne inteligence: za strežnik MCP zaženite `wordcraft-cli mcp`, za nadzorni kanal JSON pa zaženite aplikacijo z `--control `. + Every field needs a name. Vsako polje potrebuje ime. + Everyone Vsi + Exactly Natančno + Exactly {n} Natančno {n} + Example: {sample} Primer: {sample} + Exceptions (optional) Izjeme (neobvezno) + Exceptions… Izjeme … + Executive 7.25" × 10.50" Executive 7.25" × 10.50" + Expand Razširi + Expand or Collapse Heading Razširi ali strni naslov + Exponential Eksponentno + Exponential series Eksponentna vrsta + Export Izvozi + Export PDF Izvozi PDF + Export Page as PNG Izvozi stran kot PNG + Export a copy to share (PDF, Word document) Izvozi kopijo za deljenje (PDF, dokument Word) + Exported {path} Izvoženo: {path} + Expression Izraz + Extend Selection Razširi izbor + Extend Selection by Units Razširi izbor po enotah + Extra space Dodaten prostor + Field Polje + Field Codes Kode polj + Field code: Koda polja: + Field codes: Kode polj: + Field name Ime polja + Field name: Ime polja: + Field names must be different. Imena polj morajo biti različna. + Field names: Imena polj: + Field properties Lastnosti polja + Field: Polje: + Fields Polja + Fields: Polja: + Field… Polje … + Figure Slika + File Datoteka + File name: Ime datoteke: + Fill Polnilo + Fill color: Barva polnila: + Filling in forms Izpolnjevanje obrazcev + Film Film + Find Najdi + Find Next Najdi naslednje + Find Next Editable Region Najdi naslednje področje za urejanje + Find Previous Najdi prejšnje + Find Recipient Najdi prejemnika + Find What Expression Izraz za iskanje + Find What Text Besedilo za iskanje + Find and Replace Iskanje in zamenjava + Find what: Najdi: + Find: Najdi: + Finish Dokončaj + Finish & Merge Dokončaj in spoji + Finish &\nMerge Dokončaj\nin spoji + Finished Dokončano + Finnish Finščina + First Prvi + First Column Prvi stolpec + First Name Ime + First commit Prva potrditev + First line Prva vrstica + First line: Prva vrstica: + First row of data contains column headers Prva vrstica podatkov vsebuje glave stolpcev + Fit Prilagodi + Fix Position on Page Pritrdi položaj na strani + Fixed Column Width Fiksna širina stolpca + Flip Horizontal Prezrcali vodoravno + Flip Vertical Prezrcali navpično + Floor Spodnji celi del + Focus Osredotočenje + Follow number with: Za številko vstavi: + Font Pisava + Font Color Barva pisave + Font Dialog Pogovorno okno Pisava + Font Size Velikost pisave + Font color Barva pisave + Font color: Barva pisave: + Font: Pisava: + Font: {font} Pisava: {font} + Fonts Pisave + Font… Pisava … + Footer Noga + Footnote Reference Sklic na sprotno opombo + Footnote Text Besedilo sprotne opombe + Footnote and Endnote Sprotna in končna opomba + Footnotes Sprotne opombe + Footnotes and endnotes Sprotne in končne opombe + Footnotes to endnotes Sprotne opombe v končne opombe + Format Oblika + Format Page Numbers Oblikuj številke strani + Format Page Numbers… Oblikuj številke strani … + Format Painter Preslikovalnik oblike + Format: Oblika: + Formatted Oblikovano + Formatted Text Oblikovano besedilo + Formatting Oblikovanje + Formatting at Selection Oblikovanje pri izboru + Formatting exceptions Izjeme oblikovanja + Formatting marks Oznake oblikovanja + Formatting, case changes, white space and tables are compared; the other settings are kept for later. Primerjajo se oblikovanje, spremembe velikosti črk, presledki in tabele; druge nastavitve so shranjene za pozneje. + Formula Formula + Forward Naprej + Fraction Ulomek + Fractions (1/2) as fraction characters (½) Ulomke (1/2) v znake ulomkov (½) + French (France) Francoščina (Francija) + From text: Iz besedila: + Full page of the same label Cela stran iste nalepke + Function Funkcija + Functions Funkcije + GB/T 7714 GB/T 7714 + GOST (name sort) GOST (razvrščanje po imenu) + GOST (title sort) GOST (razvrščanje po naslovu) + Gallery Galerija + General Splošno + Geometry Geometrija + German (Germany) Nemščina (Nemčija) + Glow Sij + Glow Colors Barve sija + Go Back Nazaj + Go To Pojdi na + Go To (⌘⌥G) Pojdi na (⌘⌥G) + Go To… Pojdi na … + Go to Pojdi na + Good afternoon Dober dan + Good evening Dober večer + Good morning Dobro jutro + Grab bag Mešanica + Graphic Grafika + Grave Krativec + Greek Grščina + Greek Letters Grške črke + Green: Zelena: + Greeting Line Pozdravna vrstica + Greeting\nLine Pozdravna\nvrstica + Grid Table 1 Light Mrežna tabela 1 – svetla + Grid Table 4 – Blue Mrežna tabela 4 – modra + Grid Table 4 – Green Mrežna tabela 4 – zelena + Grid Table 4 – Orange Mrežna tabela 4 – oranžna + Grid Table 4 – Purple Mrežna tabela 4 – vijolična + Gridlines Mrežne črte + Group Združi v skupino + Grouping Character Above Združevalni znak zgoraj + Grouping Character Below Združevalni znak spodaj + Grow Font 1 Point Povečaj pisavo za 1 točko + H: V: + HTML Format Oblika HTML + Hanging Indent Viseči zamik + Harvard (Anglia) Harvard (Anglia) + Hat Strešica + Header Glava + Header & Footer Glava in noga + Header Row Vrstica z glavo + Header/Footer Position Položaj glave/noge + Headers and footers Glave in noge + Heading 1 Naslov 1 + Heading 2 Naslov 2 + Heading 3 Naslov 3 + Heading styles (levels 1–{n}) Slogi naslovov (ravni 1–{n}) + Heading {n} Naslov {n} + Headings Naslovi + Heart Srce + Hebrew Hebrejščina + Height: Višina: + Help Pomoč + Hex: Hex: + Hidden Skrito + Hidden text Skrito besedilo + Hide Codes Skrij kode + Hide Ink Skrij črnilo + Hide White Space Skrij prazen prostor + Hide from style lists Skrij s seznamov slogov + Hide\nInk Skrij\nčrnilo + Hierarchy Hierarhija + Highlight Označi + Highlight Merge Fields Označi polja spajanja + Highlight\nMerge Fields Označi polja\nspajanja + Highlighter Označevalnik + Hindi Hindijščina + Hollow Votlo + Home Osnovno + Home folder Domača mapa + Horizontal Vodoravno + Horizontal Line Vodoravna črta + How many pages the whole document has Koliko strani ima celoten dokument + How many pages this section has Koliko strani ima ta odsek + How many words the document holds Koliko besed vsebuje dokument + Hue: Odtenek: + Hyperbolic Cosecant Function Funkcija hiperboličnega kosekansa + Hyperbolic Cosine Function Funkcija hiperboličnega kosinusa + Hyperbolic Cotangent Function Funkcija hiperboličnega kotangensa + Hyperbolic Functions Hiperbolične funkcije + Hyperbolic Secant Function Funkcija hiperboličnega sekansa + Hyperbolic Sine Function Funkcija hiperboličnega sinusa + Hyperbolic Tangent Function Funkcija hiperboličnega tangensa + Hyperlink Hiperpovezava + Hyphenate at: Deli pri: + Hyphenate words in CAPS Deli besede, pisane z VELIKIMI črkami + Hyphenation Deljenje besed + Hyphenation Options… Možnosti deljenja besed … + Hyphenation zone: Območje deljenja besed: + Hyphens (--) as dashes (—) Vezaje (--) v pomišljaje (—) + Hypotenuse Hipotenuza + I, II, III, … I, II, III, … + IEEE IEEE + ISO 690 (author-date) ISO 690 (avtor-datum) + ISO 690 (numerical) ISO 690 (številčno) + Icon Color Barva ikone + Icons Ikone + Identity Matrices Enotske matrike + If…Then…Else Če…Potem…Sicer + If…Then…Else… Če…Potem…Sicer… + Ignore All Prezri vse + Ignore punctuation characters Prezri ločila + Ignore web and file addresses Prezri spletne naslove in poti do datotek + Ignore white-space characters Prezri presledke + Ignore words in capitals Prezri besede z velikimi črkami + Ignore words with numbers Prezri besede s številkami + Illustrations Ilustracije + Immersive Poglobljeno + Immersive Reader Poglobljeni bralnik + Immersive\nReader Poglobljeni\nbralnik + In Default apps, choose WordCraft for the document types you want it to open. V privzetih aplikacijah izberite WordCraft za vrste dokumentov, ki naj jih odpira. + In Front of Text Pred besedilom + In Line with Text V vrstici z besedilom + In equations, typing a name after a backslash replaces it with its symbol. Ko v enačbi za poševnico nazaj vtipkate ime, ga zamenja ustrezni simbol. + Inches Palci + Include level number from: Vključi številko ravni od: + Include page numbers Vključi številke strani + Increase Font Size Povečaj velikost pisave + Increase Indent Povečaj zamik + Indent Zamik + Indent from left: Zamik od leve: + Indentation Zamiki + Indented Zamaknjeno + Indents Zamiki + Indents and Spacing Zamiki in razmiki + Index Stvarno kazalo + Index Heading Naslov stvarnega kazala + Index and Tables Kazala in tabele + Index {n} Stvarno kazalo {n} + Inflate Napihni + Info Informacije + Initials: Začetnice: + Ink Črnilo + Ink Replay Predvajanje črnila + Ink to Shape Črnilo v obliko + Ink to\nMath Črnilo v\nmatematiko + Ink to\nShape Črnilo\nv obliko + Ink\nReplay Predvajanje\nčrnila + Inline V vrstici + Input Format Vnosna oblika + Insert Vstavljanje + Insert Above Vstavi zgoraj + Insert Below Vstavi spodaj + Insert Caption Vstavi napis + Insert Caption… Vstavi napis … + Insert Chart Vstavi grafikon + Insert Citation Vstavi citat + Insert Columns to the Left Vstavi stolpce levo + Insert Columns to the Right Vstavi stolpce desno + Insert Date Field Vstavi polje z datumom + Insert Endnote Vstavi končno opombo + Insert Footnote Vstavi sprotno opombo + Insert Hyperlink Vstavi hiperpovezavo + Insert Index Vstavi stvarno kazalo + Insert Left Vstavi levo + Insert Merge Field Vstavi polje spajanja + Insert Merge\nField Vstavi polje\nspajanja + Insert New Equation Vstavi novo enačbo + Insert Page Field Vstavi polje s številko strani + Insert Right Vstavi desno + Insert Row or Column by Clicking Vstavi vrstico ali stolpec s klikom + Insert Rows Above Vstavi vrstice zgoraj + Insert Rows Below Vstavi vrstice spodaj + Insert Shapes Vstavi oblike + Insert SmartArt Vstavi SmartArt + Insert Structure Vstavi strukturo + Insert Symbol Vstavi simbol + Insert Table Vstavi tabelo + Insert Table of Authorities Vstavi kazalo virov + Insert Table of Figures Vstavi kazalo slik + Insert Table… Vstavi tabelo … + Insert Time Field Vstavi polje s časom + Insert a new equation (Alt+=) Vstavi novo enačbo (Alt+=) + Insert one text or another, depending on a field's value. Vstavi eno ali drugo besedilo, odvisno od vrednosti polja. + Insert\nAbove Vstavi\nzgoraj + Insert\nCaption Vstavi\nnapis + Insert\nCitation Vstavi\ncitat + Insert\nFootnote Vstavi sprotno\nopombo + Insertions and Deletions Vstavitve in izbrisi + Insertions: Vstavitve: + Inside Borders Notranje obrobe + Inside: Znotraj: + Inspect Document Preglej dokument + Inspect Styles at Selection Preglej sloge pri izboru + Institution Ustanova + Integral Integral + Integral of e^(−x²) Integral e^(−x²) + Integral with Limits Integral z mejama + Integral with Stacked Limits Integral z zloženima mejama + Integrals Integrali + Intense Emphasis Močan poudarek + Intense Quote Izrazit citat + Intense Reference Izrazit sklic + Interface Vmesnik + Interface Theme Tema vmesnika + Interface language Jezik vmesnika + Interface language: Jezik vmesnika: + Interface size: Velikost vmesnika: + Interface theme: Tema vmesnika: + Interface\nTheme Tema\nvmesnika + Internet addresses as links Internetne naslove v povezave + Intersection Presek + Intersection with Limits Presek z mejama + Intersection with Lower Limit Presek s spodnjo mejo + Intersection with Subscript-Superscript Limits Presek z mejama v indeksu + Inverse Cosecant Function Funkcija inverznega kosekansa + Inverse Cosine Function Funkcija inverznega kosinusa + Inverse Cotangent Function Funkcija inverznega kotangensa + Inverse Functions Inverzne funkcije + Inverse Hyperbolic Cosecant Function Funkcija inverznega hiperboličnega kosekansa + Inverse Hyperbolic Cosine Function Funkcija inverznega hiperboličnega kosinusa + Inverse Hyperbolic Cotangent Function Funkcija inverznega hiperboličnega kotangensa + Inverse Hyperbolic Functions Inverzne hiperbolične funkcije + Inverse Hyperbolic Secant Function Funkcija inverznega hiperboličnega sekansa + Inverse Hyperbolic Sine Function Funkcija inverznega hiperboličnega sinusa + Inverse Hyperbolic Tangent Function Funkcija inverznega hiperboličnega tangensa + Inverse Secant Function Funkcija inverznega sekansa + Inverse Sine Function Funkcija inverznega sinusa + Inverse Tangent Function Funkcija inverznega tangensa + Ionosphere Ionosfera + Issue Številka + Italian Italijanščina + Italic Ležeče + Japanese Japonščina + Join the ArtCraft community on Discord Pridružite se skupnosti ArtCraft na Discordu + Join us on Discord: discord.gg/artcraft Pridružite se nam na Discordu: discord.gg/artcraft + Journal Revija + Justification Poravnava + Justification of a display equation Poravnava prikazne enačbe + Justified Obojestransko + Justify Poravnaj obojestransko + Keep Lines Together Obdrži vrstice skupaj + Keep Text Only Ohrani samo besedilo + Keep lines together Obdrži vrstice skupaj + Keep the password somewhere safe: without it nobody, WordCraft included, can open the document. Leave both boxes empty to remove the password. Geslo shranite na varno: brez njega dokumenta ne more odpreti nihče, niti WordCraft. Če želite geslo odstraniti, pustite obe polji prazni. + Keep with Next Obdrži z naslednjim + Keep with next Obdrži z naslednjim + Keeps the fonts and formatting of the copied text. Ohrani pisave in oblikovanje kopiranega besedila. + Keyboard shortcuts: Bližnjice na tipkovnici: + Keys for this command: Tipke za ta ukaz: + Keywords Ključne besede + Korean Korejščina + Label changes with: Oznaka se spreminja z: + Label: Oznaka: + Labels Nalepke + Landscape Ležeče + Language Jezik + Large Veliko + Large\nOperator Veliki\noperator + Largest Only Samo največji + Lasso Laso + Lasso Select Izbira z lasom + Last Zadnji + Last Column Zadnji stolpec + Last Name Priimek + Last commit Zadnja potrditev + Last, First; Last, First Priimek, Ime; Priimek, Ime + Latin text font: Pisava za latinično besedilo: + Layout Postavitev + Layout Summary Povzetek postavitve + Layouts Postavitve + Leader Vodilni znak + Leave Equation Zapusti enačbo + Leave a recipient out of the merge when a field has this value. Izpusti prejemnika iz spajanja, kadar ima polje to vrednost. + Leave the equation (Esc) Zapusti enačbo (Esc) + Leave the label out of the caption Izpusti oznako iz napisa + Left Levo + Left Arrow with Text Above Puščica levo z besedilom zgoraj + Left Arrow with Text Below Puščica levo z besedilom spodaj + Left Border Leva obroba + Left Bracket Twice Dvojni levi oklepaj + Left Margin Levi rob + Left Only Samo levo + Left Right Arrow Above Puščica levo-desno zgoraj + Left Right Arrow with Text Above Puščica levo-desno z besedilom zgoraj + Left Right Arrow with Text Below Puščica levo-desno z besedilom spodaj + Left Subscript-Superscript Levi podpisani in nadpisani indeks + Left border Leva obroba + Left indent Levi zamik + Left-to-Right Text Direction Smer besedila od leve proti desni + Left-to-right Od leve proti desni + Left: Levo: + Leftwards Arrow Above Puščica levo zgoraj + Leftwards Harpoon Above Harpuna levo zgoraj + Legal 8.50" × 14.00" Legal 8.50" × 14.00" + Legal style numbering Pravni slog oštevilčenja + Legend Legenda + Letter Pismo + Letter 8.50" × 11.00" Letter 8.50" × 11.00" + Letter headings Naslovi s črkami + Letter-Like Symbols Črkam podobni simboli + Letters Pisma + Level 1 Raven 1 + Level 2 Raven 2 + Level 3 Raven 3 + Level 4 Raven 4 + Level 5 Raven 5 + Level 6 Raven 6 + Level 7 Raven 7 + Level 8 Raven 8 + Level 9 Raven 9 + Level {n} Raven {n} + Levels: Ravni: + Light Svetlo + Light with Outline Svetlo z obrisom + Limit Limita + Limit Example Primer limite + Limit and Limita in + Limit and\nLog Limita in\nlogaritem + Limit consecutive hyphens to: Omeji zaporedne vezaje na: + Limit formatting to a selection of styles Omeji oblikovanje na izbrane sloge + Line Črta + Line Break Prelom vrstice + Line End Konec vrstice + Line Number Številka vrstice + Line Numbering Options… Možnosti oštevilčenja vrstic … + Line Numbers Številke vrstic + Line Spacing Razmik vrstic + Line Spacing Options… Možnosti razmika vrstic … + Line Start Začetek vrstice + Line and Page Breaks Prelomi vrstic in strani + Line and Paragraph Spacing Razmik vrstic in odstavkov + Line between Črta med + Line breaking Prelamljanje vrstic + Line delta Razlika vrstic + Line spacing Razmik vrstic + Line spacing: Razmik vrstic: + Linear Linearno + Linear Fraction Linearni ulomek + Lines Vrstice + Lines +/− Vrstice +/− + Lines added Dodane vrstice + Lines and characters Vrstice in znaki + Lines deleted Izbrisane vrstice + Lines only Samo vrstice + Lines per page: Število vrstic na stran: + Link Povezava + Link entries to their headings Poveži vnose z njihovimi naslovi + Link level to style: Poveži raven s slogom: + Link to Previous Poveži s prejšnjim + Linked style (paragraph and character) Povezan slog (odstavek in znak) + Links Povezave + Links and References Povezave in sklici + Link… Povezava … + List Seznam + List Definition Definicija seznama + List Paragraph Odstavek seznama + List Table 3 – Blue Seznamska tabela 3 – modra + List Table 3 – Green Seznamska tabela 3 – zelena + List Table 3 – Orange Seznamska tabela 3 – oranžna + List Table 3 – Purple Seznamska tabela 3 – vijolična + List entries Vnosi seznama + Location Mesto + Location: {path} Mesto: {path} + Lock Fields Zakleni polja + Log Logaritem + Logarithm Logaritem + Logarithm with Base Logaritem z osnovo + Logical AND Logični IN + Logical AND with Limits Logični IN z mejama + Logical OR Logični ALI + Logical OR with Limits Logični ALI z mejama + Lum: Svetl.: + MIT OR Apache-2.0. Copyright (c) 2026 ArtCraft Team and the WordCraft contributors. MIT OR Apache-2.0. Copyright (c) 2026 ArtCraft Team in sodelavci projekta WordCraft. + MLA MLA + Macros Makri + Made from the author and year when empty Če je prazno, se ustvari iz avtorja in leta + Magazine or newspaper article Članek v reviji ali časopisu + Mail Merge Spajanje dokumentov + Mailings Pošiljanje + Main entry: Glavni vnos: + Make WordCraft the default for Word documents… Nastavi WordCraft kot privzeti program za dokumente Word … + Makes the ribbon, menus and dialogs larger or smaller, on top of your system's display scaling. Document zoom is separate. Poveča ali pomanjša trak, menije in pogovorna okna, poleg sistemskega merila zaslona. Povečava dokumenta je ločena. + Manage Sources Upravljaj vire + Manage Styles Upravljaj sloge + Manage Styles… Upravljaj sloge … + Manual Hyphenation Ročno deljenje besed + Manual Line Break Ročni prelom vrstice + Manual Page Break Ročni prelom strani + Manual hyphenation is complete. Ročno deljenje besed je končano. + Manual… Ročno … + Many pages Več strani + Margins Robovi + Mark Označi + Mark All Označi vse + Mark Citation Označi citat + Mark Entry Označi vnos + Mark Entry… Označi vnos … + Mark Index Entry Označi vnos stvarnega kazala + Mark as decorative Označi kot okrasno + Mark grammar errors as you type Označuj slovnične napake med tipkanjem + Mark selected text as: Označi izbrano besedilo kot: + Mark\nCitation Označi\ncitat + Mark\nEntry Označi\nvnos + Markdown (*.md) Markdown (*.md) + Match Fields Uskladi polja + Match case Razlikuj velike in male črke + Match prefix Ujemanje predpone + Match suffix Ujemanje pripone + Math AutoCorrect Matematični samopopravki + Matrices with Brackets Matrike z oklepaji + Matrix Matrika + Maximum Maksimum + Maximum Example Primer maksimuma + Measured By Merjeno po + Measurement units: Merske enote: + Merge Spoji + Merge Cells Spoji celice + Merge Formatting Združi oblikovanje + Merge Record # Zapis spajanja št. + Merged PRs Združeni PR-ji + Mesosphere Mezosfera + Middle Sredina + Midline Dots Pike na sredini vrstice + Millimeters Milimetri + Minimum Minimum + Minus Equal Minus enako + Mirror indents Zrcalni zamiki + Mirrored Zrcalno + Model Model + Models Modeli + Moderate Zmerno + Modify Style Spremeni slog + Modify Table Style Spremeni slog tabele + Modify Table Style… Spremeni slog tabele … + Modify… Spremeni … + Mon Pon + Month Mesec + More >> Več >> + More Colors… Več barv … + More Columns… Več stolpcev … + More Commands… Več ukazov … + More Symbols… Več simbolov … + More templates → Več predlog → + Move Down Premakni navzdol + Move Paragraph Down Premakni odstavek navzdol + Move Paragraph Up Premakni odstavek navzgor + Move Past Citation Premakni se za citat + Move Past\nCitation Premakni se\nza citat + Move Selected Ink Premakni izbrano črnilo + Move Table by Dragging Premikaj tabelo z vlečenjem + Move Up Premakni navzgor + Move in Equation Premik v enačbi + Move or Resize Premakni ali spremeni velikost + Move with Text Premikaj z besedilom + Moved from Premaknjeno iz + Moved to Premaknjeno v + Moves Premiki + Multilevel List Večravenski seznam + Multiple Pages Več strani + Name Ime + Name (A–Z) Ime (A–Ž) + Name of the file this document is saved as Ime datoteke, v katero je shranjen ta dokument + Name: Ime: + Narrow 0.5" all Ozki 0.5" povsod + Natural Logarithm Naravni logaritem + Nature Narava + Navigation Krmarjenje + Navigation Pane Podokno za krmarjenje + Negated Relations Zanikane relacije + New Nov + New Comment Nov komentar + New Document Nov dokument + New Drawing Canvas Novo risalno platno + New Entry Nov vnos + New Group Nova skupina + New Label Nova nalepka + New Label… Nova nalepka … + New Paragraph Nov odstavek + New Style… Nov slog … + New Tab Nov zavihek + New Table Style Nov slog tabele + New Table Style… Nov slog tabele … + New Window Novo okno + New comments and tracked changes are recorded under this name and these initials. Novi komentarji in sledene spremembe se beležijo pod tem imenom in temi začetnicami. + New document Nov dokument + New from Template Nov dokument iz predloge + New shortcut: Nova bližnjica: + New word Nova beseda + New\nComment Nov\nkomentar + New\nWindow Novo\nokno + New… Nov … + Next Naprej + Next Change Naslednja sprememba + Next Comment Naslednji komentar + Next Field Naslednje polje + Next Footnote Naslednja sprotna opomba + Next Page Naslednja stran + Next Record Naslednji zapis + Next Section Naslednji odsek + Next Sentence Naslednji stavek + Next sentence Naslednji stavek + No Ne + No Border Brez obrobe + No Color Brez barve + No Fill Brez polnila + No Glow Brez sija + No Markup Brez oznak + No Outline Brez obrisa + No Paragraph Space Brez razmika med odstavki + No Reflection Brez odseva + No Shadow Brez sence + No Soft Edges Brez mehkih robov + No Spacing Brez razmika + No Transform Brez preoblikovanja + No accessibility issues found. Ni najdenih težav z dostopnostjo. + No changes (read only) Brez sprememb (samo za branje) + No contributor data was built into this copy. V to kopijo niso vgrajeni podatki o sodelavcih. + No errors found: every merge field is in the recipient list. Ni napak: vsa polja spajanja so na seznamu prejemnikov. + No grid Brez mreže + No icons match your search. Nobena ikona se ne ujema z iskanjem. + No limit Brez omejitve + No model credits were built into this copy. V to kopijo ni vgrajenih podatkov o modelih. + No sources yet. Virov še ni. + No templates match your search. Nobena predloga se ne ujema z iskanjem. + No words yet. Add to Dictionary on a misspelled word puts it here. Besed še ni. Ukaz Dodaj v slovar na napačno črkovani besedi jo doda sem. + No-Width Non Break Nedeljivi presledek brez širine + No-Width Optional Break Neobvezni prelom brez širine + Nonbreaking Hyphen Nedeljivi vezaj + Nonbreaking Space Nedeljivi presledek + None Brez + Normal Navadno + Normal 1" all Navadni 1" povsod + Normal Indent Navadni zamik + Normal Style Navadni slog + Normal Table Navadna tabela + Normal Text Navadno besedilo + Normal Word Document Navadni dokument Word + Normal probability density Gostota normalne porazdelitve + Normal speed Navadna hitrost + North Sever + Norwegian (Bokmål) Norveščina (bokmål) + Not Ne + Not available right now Trenutno ni na voljo + Not linked: click a text box Ni povezano: kliknite polje z besedilom + Not saved Ni shranjeno + Not saved yet Še ni shranjeno + Not used by any command Ni uporabljeno v nobenem ukazu + Not {name} Ni {name} + Nothing Nič + Nothing collected yet. Items you copy or cut appear here. Zbranega še ni nič. Tukaj se prikažejo elementi, ki jih kopirate ali izrežete. + Now used by {command} Zdaj jo uporablja ukaz {command} + Nudge Rahlo premakni + Num Occurrences Število pojavitev + Number Številka + Number alignment: Poravnava številk: + Number format Oblika številk + Number of columns: Število stolpcev: + Number of rows: Število vrstic: + Number of the page the field sits on Številka strani, na kateri je polje + Number of the section the field sits in Številka odseka, v katerem je polje + Number style for this level: Slog številk za to raven: + Number the equation; the number sits at the right margin (or type # then the number) Oštevilčite enačbo; številka je ob desnem robu (ali vtipkajte # in številko) + Numbering Oštevilčevanje + Numbering: Oštevilčevanje: + Numbering… Oštevilčevanje … + Numerals Števke + Numerals: Števke: + OK V redu + Object Predmet + Odd Page Liha stran + Odd pages only Samo lihe strani + Off Izklopljeno + Office Office + Office 2003 Default Privzeto Office 2003 + Offset (pt): Odmik (pt): + Offset: Bottom Odmik: spodaj + Offset: Bottom Left Odmik: spodaj levo + Offset: Bottom Right Odmik: spodaj desno + Offset: Center Odmik: sredina + Offset: Left Odmik: levo + Offset: Right Odmik: desno + Offset: Top Odmik: zgoraj + Offset: Top Left Odmik: zgoraj levo + Offset: Top Right Odmik: zgoraj desno + Omit Izpusti + On Vklopljeno + On: WordCraft opens on File › Home, with a blank document, the templates and your recent documents. Off: it opens a blank document. Vklopljeno: WordCraft se odpre na Datoteka › Osnovno s praznim dokumentom, predlogami in nedavnimi dokumenti. Izklopljeno: odpre prazen dokument. + On: the caret glides to its new place when you type or use the arrow keys, and its blink fades. Off: it jumps and blinks on and off. Vklopljeno: kazalec med tipkanjem ali uporabo puščičnih tipk zdrsne na novo mesto, utripanje pa pojema. Izklopljeno: kazalec skoči in utripa. + One Ena + One Page Ena stran + One half Polovica + One label per recipient Ena nalepka na prejemnika + One line per item. Enter adds an item. En element na vrstico. Enter doda element. + Only allow this kind of editing: Dovoli samo to vrsto urejanja: + Only comments can be added, except in regions marked editable. Dodajati je mogoče samo komentarje, razen v področjih, označenih za urejanje. + Only form fields can be filled in. Izpolnjevati je mogoče samo polja obrazca. + Only the allowed styles can be applied. Uporabiti je mogoče samo dovoljene sloge. + Only web (http, https) and email (mailto) links can be followed Slediti je mogoče samo spletnim (http, https) in e-poštnim (mailto) povezavam + Open Odpri + Open a file with WordCraft's own browser when the system file dialog doesn't appear. Odpri datoteko z WordCraftovim brskalnikom, kadar se sistemsko pogovorno okno ne prikaže. + OpenDocument Text (*.odt) Besedilo OpenDocument (*.odt) + Opening print dialog… Odpiranje pogovornega okna za tiskanje … + Opens .docx, .doc, .rtf and .odt files and Word templates with WordCraft from now on. Odslej odpira datoteke .docx, .doc, .rtf in .odt ter predloge Word s programom WordCraft. + Opens Settings › Default apps, where you choose WordCraft for .docx and the other document types. Odpre Nastavitve › Privzete aplikacije, kjer izberete WordCraft za .docx in druge vrste dokumentov. + Operator Operator + Operator Structures Strukture operatorjev + Operators Operatorji + Optional Hyphen Neobvezni vezaj + Options Možnosti + Options… Možnosti … + Or save a copy in another format: Ali shranite kopijo v drugi obliki: + Ordinals (1st) as superscript Vrstilnike (1.) kot nadpisano + Organizer Organizator + Organizer… Organizator … + Orientation Usmerjenost + Original document Izvirni dokument + Other Drugo + Other Large Operators Drugi veliki operatorji + Other styles and their TOC level: Drugi slogi in njihova raven v kazalu vsebine: + Otherwise insert: Sicer vstavi: + Outline Oris + Outline Level Raven orisa + Outline State Stanje orisa + Outline Tools Orodja za oris + Outline level Raven orisa + Outline levels Ravni orisa + Outlining Orisovanje + Outside Borders Zunanje obrobe + Outside border Zunanja obroba + Outside: Zunaj: + Outward Brackets Navzven obrnjeni oklepaji + Oval Elipsa + Overbar Zgornja črta + Overbars and Underbars Črte zgoraj in spodaj + Overbrace Zgornji zaviti oklepaj + Overtype Prepisovanje + Overtype (typing replaces the characters after the caret) Prepisovanje (tipkanje zamenja znake za kazalcem) + PDF document (*.pdf) Dokument PDF (*.pdf) + PRs PR-ji + Page Stran + Page Background Ozadje strani + Page Border Obroba strani + Page Borders Obrobe strani + Page Break Prelom strani + Page Break Before Prelom strani pred + Page Breaks Prelomi strani + Page Color Barva strani + Page Down Stran navzdol + Page Movement Premikanje po straneh + Page Number Številka strani + Page Setup Nastavitev strani + Page Up Stran navzgor + Page Width Širina strani + Page X of Y Stran X od Y + Page break before Prelom strani pred + Page display Prikaz strani + Page image (*.png) Slika strani (*.png) + Page number style Slog številk strani + Page numbers at the right margin Številke strani ob desnem robu + Page on which a bookmarked passage falls Stran, na kateri je odlomek z zaznamkom + Page title Naslov strani + Page width Širina strani + Page {page} Stran {page} + Page {page} of {pages} Stran {page} od {pages} + Page\nBorders Obrobe\nstrani + Page\nColor Barva\nstrani + Page\nNumber Številka\nstrani + Pages Strani + Pages like 1-3, 5 Strani, npr. 1-3, 5 + Pages of bookmark: Strani zaznamka: + Paragraph Odstavek + Paragraph Character Znak za odstavek + Paragraph Details Podrobnosti odstavka + Paragraph Down Odstavek navzdol + Paragraph Mark Oznaka odstavka + Paragraph Settings Nastavitve odstavka + Paragraph Spacing Razmik med odstavki + Paragraph Up Odstavek navzgor + Paragraph marks Oznake odstavkov + Paragraph style Slog odstavka + Paragraphs Odstavki + Paragraph… Odstavek … + Parentheses Okrogli oklepaji + Parentheses with Separator Okrogli oklepaji z ločilom + Partial Differential Parcialni diferencial + Password Geslo + Password: Geslo: + Paste Prilepi + Paste (⌘V) Prilepi (⌘V) + Paste All Prilepi vse + Paste Clipboard Item Prilepi element odložišča + Paste Formatting Prilepi oblikovanje + Paste Special Posebno lepljenje + Paste Special… Posebno lepljenje … + Paste as: Prilepi kot: + Paste: Keep Text Only Prilepi: ohrani samo besedilo + Paste: Merge Formatting Prilepi: združi oblikovanje + Pastes the clipboard's picture. Prilepi sliko iz odložišča. + Pause Začasno ustavi + Paused Začasno ustavljeno + Pen Pero + Pencil Svinčnik + Pens Peresa + People Ljudje + Periodical Periodična publikacija + Personalize Prilagoditev + Phonetic Guide Fonetični vodnik + Pi Over 2 Pi polovic + Picas Pice + Pick a custom group (New Group makes one) Izberite skupino po meri (ustvarite jo z ukazom Nova skupina) + Pick a field from the list Izberite polje s seznama + Picture Slika + Picture Border Obroba slike + Picture Content Control Kontrolnik vsebine za sliko + Picture Format Oblika slike + Picture Styles Slogi slik + Pictures Slike + Pie Tortni + Placeholder text Nadomestno besedilo + Plain Table 1 Navadna tabela 1 + Plain Text Content Control Kontrolnik vsebine z navadnim besedilom + Plain text (*.txt) Navadno besedilo (*.txt) + Play Predvajaj + Play/Pause Predvajaj/začasno ustavi + Plus Equal Plus enako + Points Točke + Polish Poljščina + Portrait Pokončno + Portuguese (Brazil) Portugalščina (Brazilija) + Portuguese (Portugal) Portugalščina (Portugalska) + Position Položaj + Position: Položaj: + Possible spelling mistake Morebitna črkovalna napaka + Postal Code Poštna številka + Power series of (1+x)ⁿ Potenčna vrsta (1+x)ⁿ + Preferred width: Želena širina: + Preserve formatting during updates Ohrani oblikovanje ob posodobitvah + Presets Prednastavitve + Press a key combination Pritisnite kombinacijo tipk + Preview Predogled + Preview Results Predogled rezultatov + Preview\nResults Predogled\nrezultatov + Previous Nazaj + Previous 1 or More Prejšnji 1 ali več + Previous Change Prejšnja sprememba + Previous Comment Prejšnji komentar + Previous Field Prejšnje polje + Previous Page Prejšnja stran + Previous Record Prejšnji zapis + Previous Section Prejšnji odsek + Previous Sentence Prejšnji stavek + Previous sentence Prejšnji stavek + Print Natisni + Print Layout Postavitev tiskanja + Print Markup Natisni oznake + Print failed: {error} Tiskanje ni uspelo: {error} + Print the page color Natisni barvo strani + Print to PDF Natisni v PDF + Print\nLayout Postavitev\ntiskanja + Printing Tiskanje + Process Proces + Product Produkt + Product of A Produkt A + Product with Limits Produkt z mejama + Product with Lower Limit Produkt s spodnjo mejo + Product with Subscript-Superscript Limits Produkt z mejama v indeksu + Products and Coproducts Produkti in koprodukti + Professional Profesionalno + Promote Zvišaj raven + Promote to Heading 1 Zvišaj na Naslov 1 + Proofing Preverjanje + Proofing Issues Težave pri preverjanju + Proofing language Jezik preverjanja + Properties Lastnosti + Protect Zaščiti + Protect Document Zaščiti dokument + Publisher Založnik + Put the caret in a content control first. Najprej postavite kazalec v kontrolnik vsebine. + Put the default commands back on the Quick Access Toolbar? Želite v orodno vrstico za hitri dostop vrniti privzete ukaze? + Pyramid Piramida + Quadratic Root Kvadratni koren + Quick Access Toolbar Orodna vrstica za hitri dostop + Quick Parts Hitri deli + Quick Tables Hitre tabele + Quote Citat + Radical Koren + Radical with Degree Koren s stopnjo + Radicals Koreni + Raised/lowered Dvignjeno/spuščeno + Read Aloud Beri na glas + Read Aloud Status Stanje branja na glas + Read Mode Način branja + Read\nAloud Beri\nna glas + Read\nMode Način\nbranja + Reading Branje + Reading Column Width Širina stolpca za branje + Reading Highlight Poudarek pri branju + Reading Line Spacing Razmik vrstic za branje + Reading Speed Hitrost branja + Reading Text Size Velikost besedila za branje + Reads the clipboard's rich text and keeps its formatting. Prebere obogateno besedilo iz odložišča in ohrani njegovo oblikovanje. + Reads the clipboard's web content and keeps its formatting. Prebere spletno vsebino iz odložišča in ohrani njeno oblikovanje. + Real name Pravo ime + Recent Nedavno + Recent Colors Nedavne barve + Recent documents to show: Število prikazanih nedavnih dokumentov: + Recently used symbols: Nedavno uporabljeni simboli: + Recipient List Seznam prejemnikov + Recipient lists Seznami prejemnikov + Recipients: {count} Prejemniki: {count} + Recolor Selected Ink Prebarvaj izbrano črnilo + Recolor a one-color picture, such as an icon Prebarvaj enobarvno sliko, na primer ikono + Record Macro Posnemi makro + Recover Unsaved Documents Obnovi neshranjene dokumente + Rectangle Pravokotnik + Red: Rdeča: + Redo Uveljavi znova + Reduce Hanging Indent Zmanjšaj viseči zamik + References Sklici + Reflection Odsev + Refresh Osveži + Region: Regija: + Registered Registrirano + Registered Sign Znak za registrirano znamko + Regular expressions Regularni izrazi + Reject Zavrni + Reject All Changes Zavrni vse spremembe + Reject Change Zavrni spremembo + Relationship Razmerje + Relaxed Sproščeno + Remove Odstrani + Remove Added Font Odstrani dodano pisavo + Remove Background Odstrani ozadje + Remove Content Control Odstrani kontrolnik vsebine + Remove Footer Odstrani nogo + Remove Header Odstrani glavo + Remove Hyperlink Odstrani hiperpovezavo + Remove Password Odstrani geslo + Remove Pen Odstrani pero + Remove Space After Paragraph Odstrani razmik za odstavkom + Remove Table of Contents Odstrani kazalo vsebine + Remove Watermark Odstrani vodni žig + Remove every custom shortcut and restore the built-in keys? Želite odstraniti vse bližnjice po meri in obnoviti vgrajene tipke? + Remove every custom tab and group and show all tabs again? Želite odstraniti vse zavihke in skupine po meri ter znova prikazati vse zavihke? + Remove field Odstrani polje + Remove from Quick Access Toolbar Odstrani iz orodne vrstice za hitri dostop + Remove recipient Odstrani prejemnika + Remove row Odstrani vrstico + Remove series Odstrani niz + Remove the control once edited Po urejanju odstrani kontrolnik + Remove the space before the punctuation Odstrani presledek pred ločilom + Rename Style Preimenuj slog + Rename… Preimenuj … + Reopen Znova odpri + Repeat Ponovi + Repeat Header Rows Ponovi vrstice z glavo + Repeat as header row on each page Ponovi kot vrstico z glavo na vsaki strani + Repeating Section Content Control Kontrolnik vsebine s ponavljajočim se odsekom + Replace Zamenjaj + Replace All Zamenjaj vse + Replace Document Text Zamenjaj besedilo dokumenta + Replace as you type Zamenjuj med tipkanjem + Replace text as you type Zamenjuj besedilo med tipkanjem + Replace with: Zamenjaj z: + Replace: Zamenjaj: + Replay Znova predvajaj + Reply Odgovori + Report Poročilo + Research Raziskovanje + Researcher Raziskovalec + Reset Ponastavi + Reset All… Ponastavi vse … + Reset Character Formatting Ponastavi oblikovanje znakov + Reset Crop Ponastavi obrezovanje + Reset Graphic Ponastavi grafiko + Reset Paragraph Formatting Ponastavi oblikovanje odstavka + Reset Picture Ponastavi sliko + Reset to Normal Ponastavi na Navadno + Reset\nGraphic Ponastavi\ngrafiko + Reset… Ponastavi … + Resize Table by Dragging Spreminjaj velikost tabele z vlečenjem + Resolve Razreši + Resolved Razrešeno + Restart Each Page Začni znova na vsaki strani + Restart Each Section Začni znova v vsakem odseku + Restart at 1 Začni znova pri 1 + Restart list after: Začni seznam znova po: + Restrict Editing Omeji urejanje + Restrict\nEditing Omeji\nurejanje + Result Rezultat + Results Rezultati + Return address labels Nalepke s povratnim naslovom + Return address: Povratni naslov: + Reverse the order Obrni vrstni red + Review Pregled + Reviewing Pregledovanje + Reviewing Pane Podokno za pregledovanje + Reviewing — edits become suggestions Pregledovanje – spremembe postanejo predlogi + Revised document Popravljeni dokument + Rewind Previj nazaj + Ribbon Trak + Ribbon and Quick Access Toolbar: Trak in orodna vrstica za hitri dostop: + Ribbon tabs Zavihki traku + Rich Text Content Control Kontrolnik vsebine z obogatenim besedilom + Rich Text Format (*.rtf) Obogateno besedilo (*.rtf) + Rich Text Format (RTF) Obogateno besedilo (RTF) + Right Desno + Right (vertical) Desno (navpično) + Right Arrow with Text Above Puščica desno z besedilom zgoraj + Right Arrow with Text Below Puščica desno z besedilom spodaj + Right Border Desna obroba + Right Bracket Twice Dvojni desni oklepaj + Right Margin Desni rob + Right Only Samo desno + Right border Desna obroba + Right indent Desni zamik + Right-to-Left Section Odsek od desne proti levi + Right-to-Left Table Tabela od desne proti levi + Right-to-Left Text Direction Smer besedila od desne proti levi + Right-to-left Od desne proti levi + Right: Desno: + Rightwards Arrow Above Puščica desno zgoraj + Rightwards Harpoon Above Harpuna desno zgoraj + Roots of ax²+bx+c Ničli ax²+bx+c + Rotate Zasukaj + Rotate Left 90° Zasukaj levo za 90° + Rotate Right 90° Zasukaj desno za 90° + Rotate all text 270° Zasukaj vse besedilo za 270° + Rotate all text 90° Zasukaj vse besedilo za 90° + Rotation Zasuk + Rotation: Zasuk: + Rounded Rectangle Zaobljen pravokotnik + Row Vrstica + Row: Vrstica: + Rows & Columns Vrstice in stolpci + Ruby text: Besedilo ruby: + Ruler Ravnilo + Rules Pravila + Run-in V vrstici + Russian Ruščina + Résumé Življenjepis + SAMPLE VZOREC + SIST02 SIST02 + Sat: Sob.: + Save Shrani + Save As Shrani kot + Save a copy in another format. Printing goes through a PDF you can print from any viewer. Shranite kopijo v drugi obliki. Tiskanje poteka prek datoteke PDF, ki jo lahko natisnete iz katerega koli pregledovalnika. + Save as Template Shrani kot predlogo + Save documents Shranjevanje dokumentov + Save the document to turn on AutoSave Shranite dokument, da vklopite samodejno shranjevanje + Save the document to turn on AutoSave (a file you have only opened isn't rewritten until you save it) Shranite dokument, da vklopite samodejno shranjevanje (datoteka, ki ste jo samo odprli, se ne prepiše, dokler je ne shranite) + Save these pages as PDF… Shrani te strani kot PDF … + Saved Shranjeno + Scale Merilo + Scale Drawing Prilagodi merilo risbe + Scatter Raztreseni + Script Pisava + Scripts Pisave + Scroll ribbon left Pomakni trak levo + Scroll ribbon right Pomakni trak desno + Search Iskanje + Search Commands Iskanje ukazov + Search Options Možnosti iskanja + Search commands and help Iskanje ukazov in pomoči + Search document Iskanje po dokumentu + Search for templates Iskanje predlog + Search icons Iskanje ikon + Secant Function Funkcija sekansa + Section Break Prelom odseka + Section Breaks Prelomi odsekov + Section Character Znak za odsek + Section Mark Oznaka odseka + Section Properties Lastnosti odseka + Section direction: Smer odseka: + Sections Odseki + See Glejte + Select Izberi + Select All Izberi vse + Select Cell Izberi celico + Select Column Izberi stolpec + Select Heading and Content Izberi naslov in vsebino + Select Line Izberi vrstico + Select Objects Izberi predmete + Select Paragraph Izberi odstavek + Select Range Izberi obseg + Select Recipients Izberi prejemnike + Select Row Izberi vrstico + Select Sentence Izberi stavek + Select Table Izberi tabelo + Select Table Cells by Clicking Izbiraj celice tabele s klikom + Select Text Izberi besedilo + Select Text with Similar Formatting Izberi besedilo s podobnim oblikovanjem + Select Word Izberi besedo + Select a picture, shape, chart or table to describe it. Izberite sliko, obliko, grafikon ali tabelo, ki jih želite opisati. + Select recipients first to choose from their fields. Najprej izberite prejemnike, da lahko izbirate med njihovimi polji. + Select recipients first to match their fields. Najprej izberite prejemnike, da lahko uskladite njihova polja. + Select text, then tick who can still edit it. Izberite besedilo in nato označite, kdo ga še lahko ureja. + Select\nRecipients Izberi\nprejemnike + Selected Fields Izbrana polja + Selected pages Izbrane strani + Selection Izbor + Selection Pane Podokno za izbor + Send Backward Pomakni nazaj + Send the document straight to the system print dialog, or save a copy in another format below. Pošljite dokument neposredno v sistemsko pogovorno okno za tiskanje ali spodaj shranite kopijo v drugi obliki. + Sentence Stavek + Sentence case. Kot v stavku. + Separator: Ločilo: + Sequence name: Ime zaporedja: + Serbian (Cyrillic) Srbščina (cirilica) + Serbian (Latin) Srbščina (latinica) + Series name Ime niza + Series {n} Niz {n} + Set Nastavi + Set As Default Nastavi kot privzeto + Set Caret Nastavi kazalec + Set Character Formatting Nastavi oblikovanje znakov + Set Date Nastavi datum + Set Equation Nastavi enačbo + Set Numbering Value Nastavi vrednost oštevilčevanja + Set Paragraph Formatting Nastavi oblikovanje odstavka + Set as Default Nastavi kot privzeto + Setting: Nastavitev: + Settings… Nastavitve … + Shading Senčenje + Shadow Senca + Shadow, glow and soft edges Senca, sij in mehki robovi + Shape Effects Učinki oblike + Shape Fill Polnilo oblike + Shape Format Oblikovanje oblike + Shape Outline Obris oblike + Shape Styles Slogi oblik + Shapes Oblike + Share Deli + Shift+Tab Shift+Tab + Shipping labels Nalepke za pošiljke + Shortcut key: Bližnjična tipka: + Show Prikaži + Show All Levels Prikaži vse ravni + Show Comments Prikaži komentarje + Show First Line Only Prikaži samo prvo vrstico + Show KeyTips (Alt or F10) Prikaži namige za tipke (Alt ali F10) + Show Level Prikaži raven + Show Level 1 Prikaži raven 1 + Show Level 2 Prikaži raven 2 + Show Level 3 Prikaži raven 3 + Show Level 4 Prikaži raven 4 + Show Level 5 Prikaži raven 5 + Show Level 6 Prikaži raven 6 + Show Level 7 Prikaži raven 7 + Show Level 8 Prikaži raven 8 + Show Level 9 Prikaži raven 9 + Show Markup Prikaži oznake + Show Notes Prikaži opombe + Show Text Formatting Prikaži oblikovanje besedila + Show all formatting marks Prikaži vse oznake oblikovanja + Show all revisions inline Prikaži vse revizije v besedilu + Show changes at: Prikaži spremembe pri: + Show changes in: Prikaži spremembe v: + Show documents with their colours inverted, like View › Switch Modes. Saving, printing and PDFs are unchanged. Prikaži dokumente z obrnjenimi barvami, kot Pogled › Preklopi način. Shranjevanje, tiskanje in PDF-ji ostanejo nespremenjeni. + Show every bibliography field Prikaži vsa polja bibliografije + Show in the Styles gallery Prikaži v galeriji slogov + Show only comments and formatting in balloons V oblačkih prikaži samo komentarje in oblikovanje + Show or Hide Style Prikaži ali skrij slog + Show revisions in balloons Prikaži revizije v oblačkih + Show rulers Prikaži ravnila + Show the Start screen when WordCraft starts Ob zagonu programa WordCraft prikaži začetni zaslon + Show the community button in the title bar Prikaži gumb skupnosti v naslovni vrstici + Show the equation in the line of text Prikaži enačbo v vrstici besedila + Show the equation on a line of its own Prikaži enačbo v svoji vrstici + Show the mini toolbar when text is selected Ob izbiri besedila prikaži mini orodno vrstico + Show/Hide ¶ Prikaži/skrij ¶ + Show\nComments Prikaži\nkomentarje + Showing the first {count} items. Prikazanih je prvih {count} elementov. + Shows a tree: each item below the item it belongs to. Prikaže drevo: vsak element pod elementom, ki mu pripada. + Shows items around a central idea: the first item is the centre. Prikaže elemente okrog osrednje zamisli: prvi element je središče. + Shows items of equal weight as blocks in rows. Prikaže enakovredne elemente kot bloke v vrsticah. + Shows overlapping ideas as overlapping circles. Prikaže prekrivajoče se zamisli kot prekrivajoče se kroge. + Shows parts of a whole as quadrants. Prikaže dele celote kot kvadrante. + Shows parts that build on each other, largest at the bottom. Prikaže dele, ki gradijo drug na drugem, največji na dnu. + Shows steps in order, left to right, with arrows between them. Prikaže korake po vrsti, od leve proti desni, s puščicami med njimi. + Shows steps that repeat, around a circle. Prikaže ponavljajoče se korake v krogu. + Shrink Font 1 Point Pomanjšaj pisavo za 1 točko + Shrink text Skrči besedilo + Side to Side Drugo ob drugem + Side\nto Side Drugo ob\ndrugem + Sides of a right triangle Stranice pravokotnega trikotnika + Signature Line Podpisna vrstica + Sine Function Funkcija sinusa + Sine and cosine series of f(x) Sinusna in kosinusna vrsta f(x) + Sine theta Sinus theta + Single Enojno + Single Brackets Enojni oklepaji + Single Closing Quote Enojni zaključni narekovaj + Single Left Angle Enojni levi lomljeni oklepaj + Single Left Bar Enojna leva navpičnica + Single Left Brace Enojni levi zaviti oklepaj + Single Left Bracket Enojni levi oglati oklepaj + Single Left Parenthesis Enojni levi okrogli oklepaj + Single Opening Quote Enojni začetni narekovaj + Single Right Brace Enojni desni zaviti oklepaj + Single Right Bracket Enojni desni oglati oklepaj + Single Right Parenthesis Enojni desni okrogli oklepaj + Single Spacing Enojni razmik + Single label Posamezna nalepka + Site name Ime spletnega mesta + Size Velikost + Size (pt): Velikost (pt): + Size and Crop… Velikost in obrezovanje … + Size: Velikost: + Sketchbook Skicirka + Skewed Fraction Poševni ulomek + Skip Citations and Bibliography Preskoči citate in bibliografijo + Skip Record If Preskoči zapis, če + Skip Record If… Preskoči zapis, če … + Skip citations & bibliography Preskoči citate in bibliografijo + Slant Down Nagib navzdol + Slant Up Nagib navzgor + Small Caps Pomanjšane velike črke + Small caps Pomanjšane velike črke + Small change Majhna sprememba + Smart quotes instead of straight quotes Pametni narekovaji namesto ravnih narekovajev + SmartArt SmartArt + SmartArt Design Načrt SmartArt + SmartArt Styles Slogi SmartArt + Smooth scrolling and zooming, and menus and buttons that fade in. Off shows every change at once. Gladko pomikanje in povečevanje ter meniji in gumbi, ki se postopno prikažejo. Če je izklopljeno, se vsaka sprememba prikaže takoj. + Soft Edges Mehki robovi + Soft Glow Mehak sij + Solid Accent Polni poudarek + Sort Razvrsti + Sort order: Vrstni red razvrščanja: + Source Manager Upravitelj virov + Source code: github.com/storytold/wordcraft Izvorna koda: github.com/storytold/wordcraft + Source type: Vrsta vira: + Sources in this document: Viri v tem dokumentu: + South Jug + Space Presledek + Space after Razmik za + Space after: {pt} pt Razmik za: {pt} pt + Space before Razmik pred + Space before/after: Razmik pred/za: + Space before: {pt} pt Razmik pred: {pt} pt + Spaces Presledki + Spacing Razmik + Spacing (pt): Razmik (pt): + Spacing: Razmik: + Spanish (Mexico) Španščina (Mehika) + Spanish (Spain) Španščina (Španija) + Sparse Matrices Redke matrike + Special Posebno + Special Characters Posebni znaki + Specify height: Določi višino: + Speech Govor + Speed Hitrost + Spelling Črkovanje + Spelling & Grammar Črkovanje in slovnica + Spelling &\nGrammar Črkovanje\nin slovnica + Spelling Suggestions Predlogi za črkovanje + Spelling and grammar are checked in English (United States). Črkovanje in slovnica se preverjata v angleščini (Združene države). + Split Razdeli + Split Cells Razdeli celice + Split Table Razdeli tabelo + Spreadsheet Table Tabela preglednice + Square Kvadrat + Square Root Kvadratni koren + Square Root with Degree Koren s stopnjo + Square Union Kvadratna unija + Stack Object Zloženi predmet + Stack Object (Three) Zloženi predmet (trije) + Stacked Zloženo + Stacked Bar Zloženi palični + Stacked Column Zloženi stolpčni + Stacked Fraction Zloženi ulomek + Stacked, no bar Zloženo, brez ulomkove črte + Standard Standardno + Standard Colors Standardne barve + Star Zvezda + Start Enforcing Protection Začni uveljavljati zaščito + Start Mail Merge Začni spajanje dokumentov + Start Mail\nMerge Začni spajanje\ndokumentov + Start at: Začni pri: + Start-up Zagon + State Pokrajina + Statement 5.50" × 8.50" Statement 5.50" × 8.50" + Statistics Statistika + Status Bar Vrstica stanja + Stop Ustavi + Stop Protection Ustavi zaščito + Stop Reading Ustavi branje + Stopped Ustavljeno + Straight Arrow Connector Raven puščični povezovalnik + Straight Connector Raven povezovalnik + Strikethrough Prečrtano + Stroke {n} of {total} Poteza {n} od {total} + Strong Krepko poudarjeno + Structure Actions Dejanja strukture + Structures Strukture + Studio Studio + Studio cover Naslovnica Studio + Studio handbook (sample) Priročnik Studio (vzorec) + Style Slog + Style Inspector Pregledovalnik slogov + Style Set Nabor slogov + Style based on: Slog temelji na: + Style: Slog: + Style: APA Slog: APA + Style: {style} Slog: {style} + Styles Slogi + Styles Pane Podokno slogov + Styles: Slogi: + Subentry: Podvnos: + Subject Zadeva + Subscript Podpisano + Subscript-Superscript Podpisano-nadpisano + Subscripts and Superscripts Podpisani in nadpisani indeksi + Subset: Podnabor: + Subtitle Podnaslov + Subtle Emphasis Rahel poudarek + Subtle Reference Rahel sklic + Sum Vsota + Sum of cosines Vsota kosinusov + Sum or difference of sines Vsota ali razlika sinusov + Summation Seštevanje + Summation from 0 to n Vsota od 0 do n + Summation over binomials Vsota binomskih koeficientov + Summation with Limits Vsota z mejama + Summation with Lower Limit Vsota s spodnjo mejo + Summation with Subscript Limit Vsota z mejo v indeksu + Summation with Subscript-Superscript Limits Vsota z mejama v indeksu + Summations Vsote + Superscript Nadpisano + Suppress line numbers Skrij številke vrstic + Surface Integral Ploskovni integral + Surface Integral with Limits Ploskovni integral z mejama + Surface Integral with Stacked Limits Ploskovni integral z zloženima mejama + Swap footnotes and endnotes Zamenjaj sprotne in končne opombe + Swedish Švedščina + Switch Modes Preklopi način + Switch\nModes Preklopi\nnačin + Symbol Simbol + Symbols Simboli + Synonyms Sopomenke + System Sistem + TC entry fields Polja vnosov TC + TOA Heading Naslov kazala virov + TOC Heading Naslov kazala vsebine + TOC {n} Kazalo vsebine {n} + Tab Tabulator + Tab Character Znak tabulatorja + Tab character Znak tabulatorja + Tab characters Znaki tabulatorja + Tab leader: Vodilni znak tabulatorja: + Tab stop position: Položaj tabulatorja: + Tab stops to be cleared: Tabulatorji za brisanje: + Table Tabela + Table Column Width Širina stolpca tabele + Table Design Načrt tabele + Table Grid Mreža tabele + Table Layout Postavitev tabele + Table Properties Lastnosti tabele + Table Properties… Lastnosti tabele … + Table Row Height Višina vrstice tabele + Table Style Options Možnosti sloga tabele + Table Styles Slogi tabel + Table direction: Smer tabele: + Table of Authorities Kazalo virov + Table of Contents Kazalo vsebine + Table of Contents Options Možnosti kazala vsebine + Table of Contents Styles Slogi kazala vsebine + Table of Figures Kazalo slik + Table of\nContents Kazalo\nvsebine + Table style Slog tabele + Tables Tabele + Tabloid 11.00" × 17.00" Tabloid 11.00" × 17.00" + Tabs Tabulatorji + Tabs… Tabulatorji … + Tabular List Tabelarični seznam + Tag Oznaka + Tangent Function Funkcija tangensa + Tangent formula Formula za tangens + Technology Tehnologija + Templates Predloge + Templates and Add-ins Predloge in dodatki + Text Besedilo + Text Box Polje z besedilom + Text Direction Smer besedila + Text Effects Učinki besedila + Text Effects and Typography Učinki besedila in tipografija + Text Fill Polnilo besedila + Text Highlight Color Barva označevanja besedila + Text Outline Obris besedila + Text Pane Podokno z besedilom + Text Wrapping Obtekanje besedila + Text boxes Polja z besedilom + Text from File Besedilo iz datoteke + Text indent at: Zamik besedila pri: + Text over Colon Equal Besedilo nad dvopičjem in enačajem + Text over Equal Besedilo nad enačajem + Text snaps to characters Besedilo se poravna na znake + Text to display: Prikazano besedilo: + Text: Besedilo: + Text\nBox Polje z\nbesedilom + Text\nDirection Smer\nbesedila + That format doesn't keep everything, so the document is still open. Save it as a Word document (.docx) to go on. Ta oblika ne ohrani vsega, zato dokument ostaja odprt. Za nadaljevanje ga shranite kot dokument Word (.docx). + That password isn't right. Check it (passwords are case-sensitive) and try again. Geslo ni pravilno. Preverite ga (gesla razlikujejo velike in male črke) in poskusite znova. + The clipboard is empty. Odložišče je prazno. + The date it is today Današnji datum + The document has no bookmarks yet. Dokument še nima zaznamkov. + The document is read only, except for regions marked editable. Dokument je samo za branje, razen področij, označenih za urejanje. + The document will be saved with a password. Save it to protect it. Dokument bo shranjen z geslom. Shranite ga, da ga zaščitite. + The new style takes the formatting of the current paragraph. Nov slog prevzame oblikovanje trenutnega odstavka. + The password is optional; without it anyone can stop the protection. Geslo ni obvezno; brez njega lahko zaščito ustavi kdor koli. + The password is removed. Save the document to keep the change. Geslo je odstranjeno. Shranite dokument, da ohranite spremembo. + The passwords don't match. Gesli se ne ujemata. + The printer, two-sided printing and pages per sheet are chosen in that dialog. Tiskalnik, obojestransko tiskanje in število strani na list izberete v tem pogovornem oknu. + The recipient-list field that fills each part of an address block and greeting line. Polje s seznama prejemnikov, ki izpolni posamezni del bloka z naslovom in pozdravne vrstice. + The spelling and grammar check is complete. Preverjanje črkovanja in slovnice je končano. + The system file dialog isn't available, so WordCraft shows its own. To get the system dialog, install a file chooser portal (such as xdg-desktop-portal-gtk) or zenity. Sistemsko pogovorno okno za datoteke ni na voljo, zato WordCraft prikaže svoje. Za sistemsko okno namestite portal za izbiro datotek (npr. xdg-desktop-portal-gtk) ali zenity. + The time right now Trenutni čas + Theme Colors Barve teme + Theme Fonts Pisave teme + Theme List Seznam tem + Themes Teme + Then insert: Potem vstavi: + Thesaurus Slovar sopomenk + These marks show even when Show/Hide ¶ is off. Te oznake so prikazane tudi, ko je Prikaži/skrij ¶ izklopljeno. + These merge fields aren't in the recipient list: Teh polj spajanja ni na seznamu prejemnikov: + Thick Debelo + Thickness Debelina + This document Ta dokument + This field has no options. To polje nima možnosti. + This folder can't be read: {error} Te mape ni mogoče prebrati: {error} + This font has no characters to show. Ta pisava nima znakov za prikaz. + This page Ta stran + This password type isn't supported. Ta vrsta gesla ni podprta. + This point forward Od tu naprej + This section Ta odsek + Three Tri + Through Skozi + Tight Tesno + Tilde Tilda + Tips ({n}) Namigi ({n}) + Title Naslov + Title Bar Naslovna vrstica + Title from the document properties Naslov iz lastnosti dokumenta + Title: Naslov: + To open Word documents with WordCraft by default: select one in Finder, choose File › Get Info, pick WordCraft under Open with, then click Change All. Če želite dokumente Word privzeto odpirati s programom WordCraft: enega izberite v Finderju, izberite Datoteka › Informacije, pod Odpri z izberite WordCraft in kliknite Spremeni vse. + Today Danes + Toggle Check Box Preklopi potrditveno polje + Toggle Field Codes Preklopi kode polj + Toggle Unicode Character Preklopi znak Unicode + Tools Orodja + Top Zgoraj + Top Border Zgornja obroba + Top Margin Zgornji rob + Top and Bottom Zgoraj in spodaj + Top of Page Vrh strani + Top: Zgoraj: + Total Skupaj + Total Row Vrstica s skupno vsoto + Track Changes Sledenje spremembam + Track Changes Options Možnosti sledenja spremembam + Track Changes: On Sledenje spremembam: vklopljeno + Track formatting Sledi oblikovanju + Track\nChanges Sledenje\nspremembam + Track\nChanges ✓ Sledenje\nspremembam ✓ + Tracked changes Sledene spremembe + Tracking Razmik med znaki + Trademark Blagovna znamka + Trademark Sign Znak za blagovno znamko + Transform Preoblikuj + Translate Prevedi + Transparency Prosojnost + Transport Promet + Triangle Trikotnik + Triangle Down Trikotnik navzdol + Triangle Up Trikotnik navzgor + Trigonometric Functions Trigonometrične funkcije + Triple Trojno + Triple Dot Trojna pika + Triple Integral Trojni integral + Triple Integral with Limits Trojni integral z mejami + Triple Integral with Stacked Limits Trojni integral z zloženimi mejami + Tue Tor + Turabian Turabian + Turkish Turščina + Two Dva + Type Vrsta + Type Text Vtipkaj besedilo + Type a New List… Vnesi nov seznam … + Type a command, e.g. "insert table" Vtipkajte ukaz, npr. »vstavi tabelo« + Type at least one recipient. Vnesite vsaj enega prejemnika. + Type equations in LaTeX Vnašaj enačbe v LaTeX-u + Type equations in the linear format (UnicodeMath) Vnašaj enačbe v linearni obliki (UnicodeMath) + Type in Equation Tipkanje v enačbi + Type normal (non-math) text in the equation V enačbo vnesite navadno (nematematično) besedilo + Type the categories down the side and a column of values per series. Kategorije vnesite navzdol ob strani, za vsak niz pa stolpec vrednosti. + Type the field's name and its switches, for example PAGE \\* roman. Vnesite ime polja in njegova stikala, na primer PAGE \\* roman. + Type the password to stop the protection. Vnesite geslo, da ustavite zaščito. + Type the recipients, or load them from a file. Vnesite prejemnike ali jih naložite iz datoteke. + Type: Vrsta: + Typing Tipkanje + Typing replaces selected text Tipkanje zamenja izbrano besedilo + UPPERCASE VELIKE ČRKE + URGENT NUJNO + URL URL + Ukrainian Ukrajinščina + Unchecked symbol Simbol za neoznačeno polje + Underbar Spodnja črta + Underbrace Spodnji zaviti oklepaj + Underline Podčrtano + Underline Words Only Podčrtaj samo besede + Underline color Barva podčrtaja + Undo Razveljavi + Undo 1 Action Razveljavi 1 dejanje + Undo Actions Razveljavi dejanja + Undo History Zgodovina razveljavitev + Undo {action} Razveljavi: {action} + Undo {n} Actions Razveljavi dejanja: {n} + Unformatted Text Neoblikovano besedilo + Ungroup Razdruži + Union Unija + Union of intersections Unija presekov + Union with Limits Unija z mejama + Union with Lower Limit Unija s spodnjo mejo + Union with Plus Unija s plusom + Union with Subscript-Superscript Limits Unija z mejama v indeksu + Unions and Intersections Unije in preseki + Unlink Fields Prekini povezavo polj + Unlink\nCitations Prekini povezavo\ncitatov + Unlock Fields Odkleni polja + Up Navzgor + Up Diagonal Dots Diagonalne pike navzgor + Up one level Eno raven višje + Update Field Posodobi polje + Update Index Posodobi stvarno kazalo + Update Style to Match Selection Posodobi slog, da se ujema z izborom + Update Styles from Template Posodobi sloge iz predloge + Update Table Posodobi tabelo + Update Table of Figures Posodobi kazalo slik + Update fields before printing Pred tiskanjem posodobi polja + Update to Match Posodobi za ujemanje + Update to Match Selection Posodobi, da se ujema z izborom + Use "a" before a consonant sound Uporabi »a« pred soglasnikom (angleščina) + Use "an" before a vowel sound Uporabi »an« pred samoglasnikom (angleščina) + Use an Existing List… Uporabi obstoječi seznam … + Use system setting Uporabi sistemsko nastavitev + Use the Insert key to switch overtype on and off S tipko Insert vklopi in izklopi prepisovanje + Use wildcards Uporabi nadomestne znake + User Name Uporabniško ime + User name: Uporabniško ime: + Username Uporabniško ime + Value Vrednost + Vector A Vektor A + Vector AB Vektor AB + Version Različica + Version History Zgodovina različic + Version {version} ({build}) Različica {version} ({build}) + Vertical Navpično + Vertical Alignment Navpična poravnava + Vertical Bars Navpičnice + Vertical Dots Navpične pike + Vertical alignment: Navpična poravnava: + View Pogled + View Alt Text… Pokaži nadomestno besedilo … + View Gridlines Pokaži mrežne črte + View State Stanje pogleda + Views Pogledi + Visual effects Vizualni učinki + Voice Glas + Volume Letnik + Volume Integral Prostorninski integral + Volume Integral with Limits Prostorninski integral z mejami + Volume Integral with Stacked Limits Prostorninski integral z zloženimi mejami + W: Š: + Waiting for Zotero… Čakanje na Zotero … + Warm Gradient with Outline Topel preliv z obrisom + Warnings ({n}) Opozorila ({n}) + Watermark Vodni žig + Wave Val + Weather Vreme + Web Layout Spletna postavitev + Web page (*.html) Spletna stran (*.html) + Web\nLayout Spletna\npostavitev + Website Spletno mesto + Wed Sre + Weight Debelina + West Zahod + When this document was first created Kdaj je bil ta dokument ustvarjen + When this document was most recently printed Kdaj je bil ta dokument nazadnje natisnjen + When this document was most recently saved Kdaj je bil ta dokument nazadnje shranjen + White Space Prazen prostor + White space Prazen prostor + Who the document properties name as author Kdo je v lastnostih dokumenta naveden kot avtor + Whole Table Cela tabela + Whole document Cel dokument + Whole page Cela stran + Whole words Cele besede + Wide Široko + Widow/Orphan Control Nadzor vdov in sirot + Widow/Orphan control Nadzor vdov in sirot + Width and spacing Širina in razmik + Width: Širina: + Wildcards (regex) Nadomestni znaki (regex) + Window Okno + With: Z: + Word Count Število besed + Word Left Beseda levo + Word Right Beseda desno + Word XML Document (*.xml) Dokument Word XML (*.xml) + Word document Dokument Word + Word document (*.docx) Dokument Word (*.docx) + Word level Besedna raven + WordArt WordArt + WordArt Styles Slogi WordArt + WordCraft WordCraft + WordCraft is now the default app for Word documents. WordCraft je zdaj privzeta aplikacija za dokumente Word. + Words Besede + Words only Samo besede + Wrap Text Obtekaj besedilo + Wrap Text Sides Strani obtekanja besedila + Wrap\nText Obtekaj\nbesedilo + Write & Insert Fields Pisanje in vstavljanje polj + Write a reply Napišite odgovor + Year Leto + Yes Da + Yields Daje + Your changes will be lost if you don't save them. Če sprememb ne shranite, bodo izgubljene. + Your system doesn't report an animation setting, so animation effects stay on. Sistem ne sporoča nastavitve animacij, zato animacijski učinki ostanejo vklopljeni. + Your system has animation effects turned off. Animacijski učinki so v sistemu izklopljeni. + Your system has animation effects turned on. Animacijski učinki so v sistemu vklopljeni. + Zoom Povečava + Zoom In Povečaj + Zoom Out Pomanjšaj + Zotero Zotero + Zotero: cancelled Zotero: preklicano + Zotero: done Zotero: končano + a, b, c, … a, b, c, … + development build razvojna različica + from Unicode (hex) iz Unicode (šestnajstiško) + getartcraft.com/apps/wordcraft getartcraft.com/apps/wordcraft + i, ii, iii, … i, ii, iii, … + lowercase male črke + n×n Identity Matrix Enotska matrika n×n + n×n Matrix Matrika n×n + pt pt + tOGGLE cASE zAMENJAJ vELIKOST + x bar x s črto + {cols}×{rows} Table Tabela {cols}×{rows} + {contributors} contributors · {commits} commits Sodelavci: {contributors} · Potrditve: {commits} + {count} items highlighted Označeni elementi: {count} + {count} of {total} pages will print Natisnjenih bo strani: {count} od {total} + {group} settings Nastavitve: {group} + {key}+Click to follow link {key}+klik za odpiranje povezave + {language} is now the document's default language. {language} je zdaj privzeti jezik dokumenta. + {name} already exists. Choose Save again to replace it. Datoteka {name} že obstaja. Znova izberite Shrani, da jo zamenjate. + {name} is protected with a password. {name} je zaščiten z geslom. + {name} wasn't found. {name} ni bilo mogoče najti. + {n} entries marked. Označeni vnosi: {n}. + {n} lines Vrstice: {n} + {n} pt {n} pt + {selected} of {words} words Besede: {selected} od {words} + {size} pt {size} pt + {words} words Besede: {words} + “{text}” ({row}, {series}) isn't a number. »{text}« ({row}, {series}) ni število. + ✓ None ✓ Brez + ➕ New comment ➕ Nov komentar + 📂 Browse… 📂 Prebrskaj … +preview Title Naslov diff --git a/crates/ui-egui/src/i18n/tests.rs b/crates/ui-egui/src/i18n/tests.rs index e23fa4552e..be9902ce86 100644 --- a/crates/ui-egui/src/i18n/tests.rs +++ b/crates/ui-egui/src/i18n/tests.rs @@ -988,8 +988,9 @@ fn polish_covers_every_key_of_every_other_catalog() { let keys = |source| parse_entries(source).0.into_iter().map(|e| e.source).collect::>(); let polish = keys(lang("pl").0.source); // Italian and Hungarian still carry "Style: APA", which the interface no longer shows; + // Slovenian carries the union of every catalog's keys. // Hungarian and Bulgarian also name the citation styles, and Bulgarian has a few one-line - for other in Lang::all().filter(|l| *l != Lang::EN && !matches!(l.code(), "pl" | "it" | "hu" | "bg" | "vi" | "sv" | "hr")) { + for other in Lang::all().filter(|l| *l != Lang::EN && !matches!(l.code(), "pl" | "it" | "hu" | "bg" | "vi" | "sv" | "hr" | "sl")) { let mut missing: Vec = keys(other.0.source).difference(&polish).cloned().collect(); missing.sort(); assert!(missing.is_empty(), "pl lacks {}'s {missing:?}", other.code()); @@ -1549,5 +1550,96 @@ fn bundled_interface_fonts_cover_vietnamese_letters() { assert_ne!(face.glyph_for(*ch), 0, "{family} {style} lacks {ch:?}"); } } - assert!(!lang("vi").uses_cjk()); + assert!(!lang("vi").uses_cjk());} + +#[test] +fn slovenian_locales_and_saved_preference_work_without_changing_the_document() { + let sl = lang("sl"); + for tag in ["sl", "sl-SI", "sl_SI.UTF-8", "SL-si", "sl-Latn-SI"] { + assert_eq!(lang_from_tag(tag), Some(sl), "{tag}"); + } + assert_eq!(first_supported([UNSUPPORTED, "sl-SI", "en-US"]), Some(sl)); + let mut app = crate::WordApp::new(wordcraft_engine::Session::new(wordcraft_doc::Document::new()), Default::default()); + let before = serde_json::to_value(&app.session.doc).unwrap(); + let result = app.run("ui.language", serde_json::json!({"value": "SL"})).unwrap(); + assert_eq!(result["effective"], "sl"); + assert_eq!(app.ui.language, "sl"); + assert!(result["available"].as_array().unwrap().iter().any(|l| l["code"] == "sl" && l["name"] == "Slovenščina")); + assert_eq!(serde_json::to_value(&app.session.doc).unwrap(), before); + let saved = serde_json::to_string(&app.ui).unwrap(); + let restored: crate::UiState = serde_json::from_str(&saved).unwrap(); + assert_eq!(Lang::from_pref(&restored.language), sl); +} + +#[test] +fn slovenian_covers_every_key_of_every_other_catalog() { + use std::collections::HashSet; + let keys = |source| parse_entries(source).0.into_iter().map(|e| e.source).collect::>(); + let slovenian = keys(lang("sl").0.source); + assert!(parse_entries(lang("sl").0.source).1.is_empty()); + for other in Lang::all().filter(|l| *l != Lang::EN && l.code() != "sl") { + let mut missing: Vec = keys(other.0.source).difference(&slovenian).cloned().collect(); + missing.sort(); + // Italian and Hungarian still carry "Style: APA", which the interface no longer shows. + missing.retain(|k| k != "Style: APA" || !matches!(other.code(), "it" | "hu")); + assert!(missing.is_empty(), "sl lacks {}'s {missing:?}", other.code()); + } + let sl = lang("sl"); + for (source, expected) in [ + ("File", "Datoteka"), + ("Home", "Osnovno"), + ("Insert", "Vstavljanje"), + ("Layout", "Postavitev"), + ("References", "Sklici"), + ("Review", "Pregled"), + ("View", "Pogled"), + ("Save", "Shrani"), + ("Bold", "Krepko"), + ("Italic", "Ležeče"), + ("Underline", "Podčrtano"), + ("Track Changes", "Sledenje spremembam"), + ("Insert Citation", "Vstavi citat"), + ("Citations", "Citati"), + ("unknown future label", "unknown future label"), + ] { + assert_eq!(tr(sl, source), expected, "{source}"); + } + // The search hint's example must find the command it names. + assert_eq!(tr(sl, "Insert Table"), "Vstavi tabelo"); + assert!(tr(sl, "Type a command, e.g. \"insert table\"").contains("vstavi tabelo")); + assert_eq!(fmt(tr(sl, "Page {page} of {pages}"), &[("page", "3"), ("pages", "9")]), "Stran 3 od 9"); + // Count-neutral wording is grammatical for every count (Slovenian has singular, dual and plural). + for count in [0, 1, 2, 3, 5, 101, 102] { + let words = count.to_string(); + assert_eq!(fmt(tr(sl, "{words} words"), &[("words", &words)]), format!("Besede: {count}")); + assert_eq!(fmt(tr(sl, "{selected} of {words} words"), &[("selected", "1"), ("words", &words)]), format!("Besede: 1 od {count}")); + } +} + +#[test] +fn bundled_interface_fonts_cover_slovenian_letters() { + let db = wordcraft_fonts::FontDb::with_font_dirs(Vec::new()); + for (family, style) in [("Inter", "Regular"), ("Inter", "Medium"), ("Inter", "SemiBold"), ("JetBrains Mono", "Regular")] { + let face = db.face(family, style); + assert_eq!(face.family, family, "must use the bundled interface face"); + for ch in "ČčŠšŽž»«–…".chars() { + assert_ne!(face.glyph_for(ch), 0, "{family} {style} lacks {ch}"); + } + } + assert!(!lang("sl").uses_cjk()); +} + + +#[test] +fn slovenian_has_no_keys_outside_the_other_catalogs() { + use std::collections::HashSet; + let keys = |source| parse_entries(source).0.into_iter().map(|e| e.source).collect::>(); + let slovenian = keys(lang("sl").0.source); + let mut union = HashSet::new(); + for other in Lang::all().filter(|l| *l != Lang::EN && l.code() != "sl") { + union.extend(keys(other.0.source)); + } + let mut extra: Vec = slovenian.difference(&union).cloned().collect(); + extra.sort(); + assert!(extra.is_empty(), "sl has {} keys no other catalog has: {extra:?}", extra.len()); } diff --git a/docs/localization-parity.md b/docs/localization-parity.md index 8d1c5e3e85..91b65c302c 100644 --- a/docs/localization-parity.md +++ b/docs/localization-parity.md @@ -19,9 +19,9 @@ native-speaker review (human). ## What WordCraft ships (measured) -- **23 interface languages**: English plus `zh-hans`, `zh-hant`, `ja`, Korean `ko` (#1216), `uk`, `es`, `pt-br`, - Serbian `sr`/`sr-latn` (#250), Estonian `et`, Turkish `tr`, Russian `ru`, Italian `it` (#594), German `de` (#574), Polish `pl`, Czech `cs`, Hungarian `hu`, French `fr`, Bulgarian `bg`, Vietnamese `vi` (#1434), Swedish `sv` (#1219) and Croatian `hr` (#1271). Catalogs have 2,061–2,504 entries - each; Polish has every key of every other catalog, including the built-in style names and the equation gallery, except the citation style names only Hungarian, Bulgarian and Swedish translate so far (a test checks it). A test (`every_tab_and_command_is_translated`) enforces that every ribbon tab, command +- **24 interface languages**: English plus `zh-hans`, `zh-hant`, `ja`, Korean `ko` (#1216), `uk`, `es`, `pt-br`, + Serbian `sr`/`sr-latn` (#250), Estonian `et`, Turkish `tr`, Russian `ru`, Italian `it` (#594), German `de` (#574), Polish `pl`, Czech `cs`, Hungarian `hu`, French `fr`, Bulgarian `bg`, Vietnamese `vi` (#1434), Swedish `sv` (#1219), Croatian `hr` (#1271) and Slovenian `sl`. Catalogs have 2,061–2,507 entries + each; Polish has every key of every other catalog, including the built-in style names and the equation gallery, except the citation style names only Hungarian, Bulgarian, Swedish and Slovenian translate so far (a test checks it). A test (`every_tab_and_command_is_translated`) enforces that every ribbon tab, command label and ribbon location is translated in every language. `pl`, `pl-PL` and `pl_PL.UTF-8` system locales pick Polish automatically. - Of the 675 English strings a scan extracts from `tl!(…)` calls and command labels/locations, each @@ -59,8 +59,8 @@ shared items below. | Korean | ko | 2,463 entries (all catalog keys and preview context) | most; newer panes English | Hangul ✅ (Korean-first embedded/system UI font fallback); macOS Korean IME fixes pending (#163, #164) | no | partial | 3–5 | | Vietnamese | vi | 2,448 entries; every key of the reference catalogs (#1434); 98% of extracted, ~95% of visible | most | Latin ✅ | no | partial | 0–1 (review) | -Other shipped languages: **16** — Traditional Chinese (`zh-hant`), Ukrainian (`uk`), Serbian -Cyrillic and Latin (`sr`, `sr-latn`, #250), Estonian (`et`), Turkish (`tr`), Russian (`ru`), Italian (`it`, #594), Polish (`pl`), Czech (`cs`), Hungarian (`hu`), French (`fr`), Bulgarian (`bg`), Vietnamese (`vi`, #1434), Swedish (`sv`, #1219) and Croatian (`hr`, #1271), all partial. +Other shipped languages: **17** — Traditional Chinese (`zh-hant`), Ukrainian (`uk`), Serbian +Cyrillic and Latin (`sr`, `sr-latn`, #250), Estonian (`et`), Turkish (`tr`), Russian (`ru`), Italian (`it`, #594), Polish (`pl`), Czech (`cs`), Hungarian (`hu`), French (`fr`), Bulgarian (`bg`), Vietnamese (`vi`, #1434), Swedish (`sv`, #1219), Croatian (`hr`, #1271) and Slovenian (`sl`), all partial. Swedish (`sv-SE` and `sv-FI` system locales, `{"value":"sv"}`) has 2,504 entries. Counts use count-neutral wording (`{words} ord`: the noun is the same in the singular and the plural). The catalog is an AI-assisted draft whose terminology and running app were reviewed by a native @@ -77,6 +77,9 @@ Bulgarian (`bg-BG` system locales, `{"value":"bg"}`) has 2,419 entries: every ke catalog, the equation gallery's labels and everything the interface audit finds (the audit checks Bulgarian as it does Russian). Counts use count-neutral wording (`Думи: 21`). Bulgarian text is hyphenated by the Bulgarian syllable rules (#826); Bulgarian spelling and grammar are not included. +Slovenian (`sl-SI` system locales, `{"value":"sl"}`) has 2,463 entries: every key of every other +catalog. Counts use count-neutral wording (`Besede: 21`); terminology: [`localization-sl.md`](localization-sl.md). +Slovenian proofing is not included. Hungarian follows `hu-HU` system locales and `{"value":"hu"}`, with a complete catalog of 2,419 entries: every key of the Russian catalog, built-in style names shown translated, plus the citation style names. Hungarian proofing is not included; terminology and conventions: [`localization-hu.md`](localization-hu.md). @@ -218,6 +221,7 @@ WordCraft keeps 2,54 cm). | 2026-10-11 | minor | Font list shows installed fonts by their names in the interface language (宋体 for SimSun in Chinese, Japanese and Korean names likewise) and finds them by either name; a Chinese, Japanese or Korean font is stored as `w:eastAsia` too (#505) | | 2026-10-11 | minor | Chinese/Japanese interface fonts: a test checks every catalog character has a glyph; an installed CJK font follows the embedded faces whenever they miss one; macOS font asset folders searched for PingFang (#486) | | 2026-10-10 | minor | Russian interface (#300; 1,524 entries, built-in style names shown translated; covers everything the interface audit finds) | +| 2026-10-12 | minor | Slovenian interface catalog (2,463 entries), locale selection, saved preference, audit coverage and font checks | | 2026-10-12 | minor | Bulgarian interface catalog (2,419 entries), locale selection, saved preference, audit coverage and font checks | | 2026-10-13 | minor | Croatian interface catalog (2,507 entries, built-in style names shown translated), `hr-*` system locales, saved preference and font coverage checks; the template name "Letter" has its own `template` context so the paper size keeps its name; no Croatian proofing | | 2026-10-11 | minor | Hungarian interface catalog (2,419 entries, built-in style names shown translated; covers everything the interface audit finds), locale selection, saved preference and font coverage checks | diff --git a/docs/localization-sl.md b/docs/localization-sl.md new file mode 100644 index 0000000000..ff64910ca7 --- /dev/null +++ b/docs/localization-sl.md @@ -0,0 +1,58 @@ +# Slovenian interface + +> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-12 · **Change:** minor (Slovenian interface catalog and regression checks) · **Target:** WordCraft 0.5.0 + +Choose **File › Options › Interface language › Slovenščina**, or follow a Slovenian system display +language (`sl-SI`, `sl_SI.UTF-8`). The catalog code is `sl`; `ui.language` with `{"value":"sl"}` +selects it explicitly, and `WORDCRAFT_LANGUAGE=sl-SI` does the same for a headless screenshot or a +launch while the preference is `auto`. Document text, command ids, CLI/MCP/control responses and +file paths do not change. + +`crates/ui-egui/src/i18n/sl.tsv` has 2,463 entries: every key of every other catalog (built-in +style names, equation gallery labels, paper sizes, watermarks, greetings and citation style names +included). It is written from the meaning of the English labels; no proprietary translation +resources were consulted. + +## Conventions + +- Ribbon tabs, groups, panes and dialog titles are nouns (`Vstavljanje`, `Pisava`, `Podokno slogov`). +- Commands and buttons use the imperative (`Shrani`, `Vstavi tabelo`, `Sprejmi vse spremembe`); + options and toggles use the imperative as well (`Preverjaj črkovanje med tipkanjem`). +- Messages address the user with the polite plural (`Shranite dokument`). +- Only the first word of a label is capitalised. +- Slovenian has singular, dual and plural; the catalog has no plural forms, so counts use + count-neutral wording with a colon (`Besede: 21`, `Razveljavi dejanja: {n}`). +- Placeholders never take a case ending; the wording puts them after a colon instead. +- Quotation marks »…«; decimal comma in prose (`Razmik vrstic 1,5`); paper sizes keep inches as in + the source. +- Text columns and table columns are both `stolpci`, as in common Slovenian usage. + +## Terminology + +| Source | Slovenian | +|---|---| +| File / Home / Insert / Draw / Design / Layout | Datoteka / Osnovno / Vstavljanje / Risanje / Načrt / Postavitev | +| References / Mailings / Review / View / Help | Sklici / Pošiljanje / Pregled / Pogled / Pomoč | +| Font / Paragraph / Styles | Pisava / Odstavek / Slogi | +| Header / Footer / Heading | Glava / Noga / Naslov | +| Footnote / Endnote / Caption | Sprotna opomba / Končna opomba / Napis | +| Table of Contents / Index | Kazalo vsebine / Stvarno kazalo | +| Citation / Citation Style / Bibliography | Citat / Slog citiranja / Bibliografija | +| Track Changes / Accept / Reject | Sledenje spremembam / Sprejmi / Zavrni | +| Comment / Markup | Komentar / Oznake | +| Mail Merge / Merge Field | Spajanje dokumentov / Polje spajanja | +| Section / Section Break | Odsek / Prelom odseka | +| Undo / Redo / Repeat | Razveljavi / Uveljavi znova / Ponovi | +| Ribbon / Pane / Quick Access Toolbar | Trak / Podokno / Orodna vrstica za hitri dostop | +| Bold / Italic / Underline / Strikethrough | Krepko / Ležeče / Podčrtano / Prečrtano | +| Hyphenation / Proofing | Deljenje besed / Preverjanje | +| Character Spacing / Tracking | Razmik med znaki | +| Watermark | Vodni žig | +| Text box / Content control | Polje z besedilom / Kontrolnik vsebine | + +## Verification and remaining scope + +Tests in `crates/ui-egui/src/i18n/tests.rs` check locale selection, the saved preference, that the +catalog covers every key of every other catalog, key terms, the search hint example and the bundled +fonts' coverage of č, š, ž. Slovenian spelling, grammar and hyphenation are not included. Native +speaker review is welcome. From dfdce752dc786927ff1c2a1b5d7591460f6ad000 Mon Sep 17 00:00:00 2001 From: Filip Strajnar Date: Thu, 15 Oct 2026 19:14:42 +0200 Subject: [PATCH 035/336] i18n(sl): add the 155 strings main gained since the catalog was drafted Rebased on main. sl.tsv now has 2,614 entries, every key of every other catalog, including the native print settings (Tiskalnik, Odtenki sive, Obojestransko tiskanje), mail merge filter and sort, Go To, bookmarks, privacy options, interface size and dictionary folder strings. Three keys main renamed were replaced. - Slovenian added alongside Vietnamese, Swedish and Croatian in the language list, the audit list and the Polish coverage exclusions. - New test: sl has no keys outside the other catalogs. - localization-parity.md, localization-sl.md and ROADMAP.md counts, language lists and status lines updated. --- ROADMAP.md | 6 +- crates/ui-egui/src/i18n/sl.tsv | 606 +++++++++++++++++++------------ crates/ui-egui/src/i18n/tests.rs | 4 +- docs/localization-parity.md | 8 +- docs/localization-sl.md | 19 +- 5 files changed, 400 insertions(+), 243 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 748ca93b54..9f3c72be08 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -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-14 · **Change:** 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-14 · **Change:** trivial (progress log: print-area strings translated in every catalog; 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-15 · **Change:** trivial (Slovenian interface catalog, 2,614 entries); 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-15 · **Change:** trivial (Slovenian interface catalog, 2,614 entries); previously trivial (progress log: print-area strings translated in every catalog; 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 @@ -369,7 +369,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | 2026-10-12 | Native saves use exclusively created sibling temporary files, synchronize before replacement and clean up on failure; existing temporary-name files/links and original files on failed saves are protected. Synthetic CLI and focused tests on macOS; no new Word parity or readiness claim. | | 2026-10-11 | Field codes like Word's field keys (#339): show codes (Alt+F9, Shift+F9), edit, unlink, lock and step through fields | | 2026-10-11 | Word's common keyboard shortcuts (#330): 101 → 125 of ~250 | -| 2026-10-15 | Slovenian (`sl`) interface: every key of every other catalog, `sl-SI` system locales and a saved language choice; locale, catalog and bundled-font tests; no Slovenian proofing resources | +| 2026-10-15 | Slovenian (`sl`) interface: 2,614 catalog entries (every key of every other catalog, including native print settings), `sl-SI` system locales and a saved language choice; locale, catalog and bundled-font tests; no Slovenian proofing resources | | 2026-10-13 | Swedish (`sv`) interface: 2,504 catalog entries (every key of the reference catalog, the equation gallery, everything the interface audit finds, and 44 newer strings no catalog had yet, among them the native print settings), `sv-SE`/`sv-FI` system locales and a saved language choice; locale, catalog and bundled-font tests; AI-assisted, terminology and the running app reviewed by a native speaker; no Swedish proofing resources | | 2026-10-12 | Bulgarian (`bg`) interface: 2,419 catalog entries (every key of the reference catalog, the equation gallery and everything the interface audit finds), `bg-BG` system locales and a saved language choice; locale, catalog and bundled-font tests; no Bulgarian proofing resources | | 2026-10-13 | Croatian (`hr`) interface: 2,507 catalog entries (every key of the reference catalog, built-in style names and the equation gallery), `hr-*` system locales and a saved language choice; locale, catalog and bundled-font tests; no Croatian proofing yet | diff --git a/crates/ui-egui/src/i18n/sl.tsv b/crates/ui-egui/src/i18n/sl.tsv index 8e95bf21f6..b3de584a43 100644 --- a/crates/ui-egui/src/i18n/sl.tsv +++ b/crates/ui-egui/src/i18n/sl.tsv @@ -58,8 +58,7 @@ A table of contents Kazalo vsebine A xor B A xor B A, B, C, … A, B, C, … - APA APA - ASAP ČIM PREJ + a, b, c, … a, b, c, … AaBbCcDd AaBbČ莞 AaBbYyZz AaBbČčŠšŽž About O programu @@ -78,12 +77,18 @@ Accessed on Datum dostopa Accessibility Dostopnost Accessibility Pane Podokno za dostopnost + Account Račun Acute Ostrivec Add Dodaj Add >> Dodaj >> + Add a comment Dodaj komentar + Add a Table Dodaj tabelo + Add at least one field. Dodajte vsaj eno polje. Add Chart Element Dodaj element grafikona + Add Condition Dodaj pogoj Add Field Dodaj polje Add Font… Dodaj pisavo … + Add line numbering Dodaj oštevilčenje vrstic Add New Source… Dodaj nov vir … Add Object to Selection Dodaj predmet v izbor Add Pen Dodaj pero @@ -91,19 +96,16 @@ Add Series Dodaj niz Add Shape Dodaj obliko Add Space Before Paragraph Dodaj razmik pred odstavkom - Add Text Dodaj besedilo - Add a Table Dodaj tabelo - Add a comment Dodaj komentar - Add at least one field. Dodajte vsaj eno polje. - Add line numbering Dodaj oštevilčenje vrstic Add space between Asian and Latin text Dodaj presledek med azijskim in latiničnim besedilom Add space between Asian text and numbers Dodaj presledek med azijskim besedilom in številkami Add tab stop at: Dodaj tabulator pri: + Add Text Dodaj besedilo Add the chapter number Dodaj številko poglavja Add to Dictionary Dodaj v slovar Add to Document Dodaj v dokument Add to Quick Access Toolbar Dodaj v orodno vrstico za hitri dostop Add to Style Gallery Dodaj v galerijo slogov + Add whether it is above or below Dodajte, ali je zgoraj ali spodaj Add/Edit\nBibliography Dodaj/uredi\nbibliografijo Add/Edit\nCitation Dodaj/uredi\ncitat Add\nNote Dodaj\nopombo @@ -142,24 +144,26 @@ All - Professional Vse – profesionalno All Borders Vse obrobe All Caps Vse velike črke + All caps Vse velike črke All Commands Vsi ukazi + All files Vse datoteke All Levels Vse ravni All Markup Vse oznake - All caps Vse velike črke - All files Vse datoteke All pages Vse strani All pages in the range Vse strani v obsegu All symbol sets Vsi nabori simbolov - Allow Latin text to wrap in the middle of a word Dovoli prelom latiničnega besedila sredi besede + All system fonts are already available. Vse sistemske pisave so že na voljo. Allow hanging punctuation Dovoli viseča ločila + Allow Latin text to wrap in the middle of a word Dovoli prelom latiničnega besedila sredi besede Allow line breaks Dovoli prelome vrstic Allow row to break across pages Dovoli prelom vrstice čez strani Alphabetical Po abecedi Alt Text Nadomestno besedilo - Alt Text Pane Podokno za nadomestno besedilo Alt text Nadomestno besedilo + Alt Text Pane Podokno za nadomestno besedilo Alt: Nadomestno: Alt\nText Nadomestno\nbesedilo + And In Angle Brackets Lomljeni oklepaji Angle Brackets with Separator Lomljeni oklepaji z ločilom Angle Brackets with Two Separators Lomljeni oklepaji z dvema ločiloma @@ -175,13 +179,14 @@ Any Letter Poljubna črka Anyone can open this document. Encrypt it with a password to keep it private. Ta dokument lahko odpre kdor koli. Če želite, da ostane zaseben, ga šifrirajte z geslom. Anyone who opens the document will need this password. Vsak, ki odpre dokument, bo potreboval to geslo. + APA APA Apply Uporabi + Apply as you type Uporabi med tipkanjem Apply Asian line-breaking rules (kinsoku) Uporabi azijska pravila za prelom vrstic (kinsoku) Apply Character Style Uporabi znakovni slog + Apply formatting to: Uporabi oblikovanje za: Apply Style Uporabi slog Apply Styles Uporabi sloge - Apply as you type Uporabi med tipkanjem - Apply formatting to: Uporabi oblikovanje za: Apply to: Uporabi za: Arabic Arabske številke Arch Down Lok navzdol @@ -194,8 +199,10 @@ Artistic Effects Umetniški učinki As Recommended Kot je priporočeno As you type Med tipkanjem - Asian Typography Azijska tipografija + ASAP ČIM PREJ + Ascending Naraščajoče Asian text font: Pisava za azijsko besedilo: + Asian Typography Azijska tipografija Assign Dodeli At least Najmanj At least {n} Najmanj {n} @@ -205,6 +212,7 @@ Attach… Pripni … Author Avtor Auto Samodejno + Auto ({percent}%) Samodejno ({percent} %) AutoCorrect Samopopravki AutoCorrect Options Možnosti samopopravkov AutoFit Samodejno prilagodi @@ -212,6 +220,17 @@ AutoFit Contents Samodejno prilagodi vsebini AutoFit Window Samodejno prilagodi oknu AutoFormat As You Type Samooblikovanje med tipkanjem + Automatic Samodejno + Automatic ({language}) Samodejno ({language}) + Automatic ({region}) Samodejno ({region}) + Automatic bulleted lists Samodejni označeni seznami + Automatic follows your system's language. Menus and commands change; your documents don't. Samodejno sledi jeziku sistema. Spremenijo se meniji in ukazi, vaši dokumenti ne. + Automatic numbered lists Samodejni oštevilčeni seznami + Automatic Table 1 (3 levels) Samodejna tabela 1 (3 ravni) + Automatic Table 2 (2 levels) Samodejna tabela 2 (2 ravni) + Automatically hyphenate document Samodejno deli besede v dokumentu + Automatically Update Document Styles Samodejno posodobi sloge dokumenta + Automatically update document styles Samodejno posodobi sloge dokumenta AutoSave Samodejno shranjevanje AutoSave documents you have saved in WordCraft Samodejno shranjuj dokumente, ki ste jih shranili v programu WordCraft AutoSave failed: {error}. Save the document to turn AutoSave back on. Samodejno shranjevanje ni uspelo: {error}. Shranite dokument, da znova vklopite samodejno shranjevanje. @@ -220,17 +239,6 @@ AutoSave saves every change to the file (needs a saved document) Samodejno shranjevanje shrani vsako spremembo v datoteko (dokument mora biti shranjen) AutoSave writes your changes to the file a moment after you stop typing. Samodejno shranjevanje zapiše spremembe v datoteko trenutek po tem, ko nehate tipkati. AutoText Samobesedilo - Automatic Samodejno - Automatic ({language}) Samodejno ({language}) - Automatic ({region}) Samodejno ({region}) - Automatic Table 1 (3 levels) Samodejna tabela 1 (3 ravni) - Automatic Table 2 (2 levels) Samodejna tabela 2 (2 ravni) - Automatic bulleted lists Samodejni označeni seznami - Automatic follows your system's language. Menus and commands change; your documents don't. Samodejno sledi jeziku sistema. Spremenijo se meniji in ukazi, vaši dokumenti ne. - Automatic numbered lists Samodejni oštevilčeni seznami - Automatically Update Document Styles Samodejno posodobi sloge dokumenta - Automatically hyphenate document Samodejno deli besede v dokumentu - Automatically update document styles Samodejno posodobi sloge dokumenta B4 (JIS) 10.12" × 14.33" B4 (JIS) 10.12" × 14.33" B5 (JIS) 7.17" × 10.12" B5 (JIS) 7.17" × 10.12" Backspace Vračalka @@ -265,8 +273,8 @@ Binomial expansion of (x+a)ⁿ Binomski razvoj (x+a)ⁿ Blank Prazno Blank (Three Columns) Prazno (trije stolpci) - Blank Page Prazna stran Blank document Prazen dokument + Blank Page Prazna stran Block\nAuthors Blokiraj\navtorje Blue: Modra: Body Text Telo besedila @@ -277,8 +285,8 @@ Book title Naslov knjige Bookmark Zaznamek Bookmark name: Ime zaznamka: - Border Painter Čopič za obrobe Border lines Črte obrobe + Border Painter Čopič za obrobe Border\nPainter Čopič za\nobrobe Borders Obrobe Borders and Shading Obrobe in senčenje @@ -286,8 +294,10 @@ Both Sides Obe strani Bottom Spodaj Bottom Border Spodnja obroba + Bottom Left Cell Spodnja leva celica Bottom Margin Spodnji rob Bottom of Page Dno strani + Bottom Right Cell Spodnja desna celica Bottom: Spodaj: Box Okvir Boxed Formula Uokvirjena formula @@ -312,9 +322,9 @@ Bullet Library Knjižnica oznak Bullets Oznake By author Po avtorju - CONFIDENTIAL ZAUPNO Calendar Koledar Can't be deleted Ni mogoče izbrisati + can't split at the first row tabele ni mogoče razdeliti pri prvi vrstici Cancel Prekliči Canvas Member Element platna Capitalize Each Word Vsaka Beseda Z Veliko Začetnico @@ -349,6 +359,7 @@ Change Case Spremeni velikost črk Change Chart Type Spremeni vrsto grafikona Change Colors Spremeni barve + Change in y over change in x Sprememba y glede na spremembo x Change Layout Spremeni postavitev Change List Level Spremeni raven seznama Change Password… Spremeni geslo … @@ -356,7 +367,6 @@ Change Picture… Spremeni sliko … Change Shape Spremeni obliko Change Structure Spremeni strukturo - Change in y over change in x Sprememba y glede na spremembo x Change to Display Spremeni v prikazno obliko Change to Inline Spremeni v vrstično obliko Change to Level 1 Spremeni v raven 1 @@ -376,13 +386,13 @@ Chapters begin at: Poglavja se začnejo pri: Character Znak Character Border Obroba znakov + Character code: Koda znaka: + Character in Range Znak v obsegu + Character level Znakovna raven Character Position Položaj znakov Character Scale Merilo znakov Character Shading Senčenje znakov Character Spacing Razmik med znaki - Character code: Koda znaka: - Character in Range Znak v obsegu - Character level Znakovna raven Character spacing Razmik med znaki Character style Znakovni slog Characters Znaki @@ -397,8 +407,8 @@ Check Accessibility Preveri dostopnost Check Box Content Control Kontrolnik vsebine potrditvenega polja Check Compatibility Preveri združljivost - Check Spelling as You Type Preverjaj črkovanje med tipkanjem Check for Errors Preveri napake + Check Spelling as You Type Preverjaj črkovanje med tipkanjem Check spelling as you type Preverjaj črkovanje med tipkanjem Check\nAccessibility Preveri\ndostopnost Checked styles are allowed: Dovoljeni so označeni slogi: @@ -408,9 +418,9 @@ Chicago Chicago Chinese (Simplified) Kitajščina (poenostavljena) Chinese (Traditional) Kitajščina (tradicionalna) - Choose List Item Izberi element seznama Choose a command Izberite ukaz Choose a shortcut Izberite bližnjico + Choose List Item Izberi element seznama Choose the sheet that holds the recipients. Izberite list, na katerem so prejemniki. Circle Krog Circle area from its radius Ploščina kroga iz polmera @@ -461,6 +471,8 @@ Column Stolpec Column Break Prelom stolpca Column Selection Izbor stolpca + Column width Širina stolpca + Column {n} Stolpec {n} Column: Stolpec: Columns Stolpci Columns: Stolpci: @@ -471,8 +483,8 @@ Commands: Ukazi: Comment Komentar Comments Komentarji - Comments Pane Podokno s komentarji Comments beside each page and tracked changes in PDFs, prints and images; a comment list at the end of web pages, Markdown and text. Komentarji ob vsaki strani in sledene spremembe v datotekah PDF, natisih in slikah; seznam komentarjev na koncu spletnih strani, Markdowna in besedila. + Comments Pane Podokno s komentarji Commits Potrditve Common Accent Objects Pogosti naglasni predmeti Common Brackets Pogosti oklepaji @@ -489,18 +501,22 @@ Company Podjetje Compare Primerjaj Compare Documents Primerjaj dokumente + Compare to Primerjaj z Compared: {changes} changes Primerjano – število sprememb: {changes} Comparing with another file needs the desktop app. Za primerjavo z drugo datoteko potrebujete namizno aplikacijo. + Comparison Primerjava Comparison settings Nastavitve primerjave Compress Pictures Stisni slike Compress punctuation at the start of a line Stisni ločila na začetku vrstice Conference Konferenca Conference paper Konferenčni prispevek + CONFIDENTIAL ZAUPNO Confirm password: Potrdite geslo: Connect Shapes Poveži oblike Container Title Naslov vsebnika - Content Control Properties Lastnosti kontrolnika vsebine + Contains Vsebuje Content Control at Selection Kontrolnik vsebine pri izboru + Content Control Properties Lastnosti kontrolnika vsebine Contents can't be edited Vsebine ni mogoče urejati Context Kontekst Continuous Neprekinjeno @@ -513,10 +529,10 @@ Controls Kontrolniki Conversions Pretvorbe Convert Pretvori + Convert between built-up and linear form Pretvori med sestavljeno in linearno obliko Convert Notes Pretvori opombe Convert Text to Table Pretvori besedilo v tabelo Convert Text to Table… Pretvori besedilo v tabelo … - Convert between built-up and linear form Pretvori med sestavljeno in linearno obliko Convert to Shapes Pretvori v oblike Convert to Text Pretvori v besedilo Copies Kopije @@ -526,12 +542,13 @@ Coproduct with Subscript-Superscript Limits Koprodukt z mejama v indeksu Copy Kopiraj Copy Formatting Kopiraj oblikovanje - Copy Styles Kopiraj sloge Copy of the text a bookmark marks Kopija besedila, ki ga označuje zaznamek + Copy Styles Kopiraj sloge + Copy to Kopiraj v Copyright Avtorske pravice Copyright Sign Znak za avtorske pravice - Correct TWo INitial CApitals Popravi DVe ZAčetni VEliki črki Correct accidental usage of cAPS LOCK key Popravi nenamerno uporabo tipke cAPS LOCK + Correct TWo INitial CApitals Popravi DVe ZAčetni VEliki črki Corrections Popravki Cosecant Function Funkcija kosekansa Cosine 2x Kosinus 2x @@ -547,12 +564,13 @@ Cover Page Naslovnica Craft Craft Create Ustvari + Create a Style Ustvari slog + Create an interactive outline of your document.\n\nIt's a great way to keep track of where you are or quickly move your content around.\n\nTo get started, go to the Home tab and apply Heading styles to the headings in your document. Ustvarite interaktivni oris dokumenta.\n\nTako boste vedno vedeli, kje ste, in lahko hitro premikali vsebino.\n\nZa začetek na zavihku Osnovno naslovom v dokumentu dodelite sloge naslovov. Create Graphic Ustvari grafiko Create Link Ustvari povezavo Create New Style Ustvari nov slog Create Source Ustvari vir - Create a Style Ustvari slog - Create an interactive outline of your document.\n\nIt's a great way to keep track of where you are or quickly move your content around.\n\nTo get started, go to the Home tab and apply Heading styles to the headings in your document. Ustvarite interaktivni oris dokumenta.\n\nTako boste vedno vedeli, kje ste, in lahko hitro premikali vsebino.\n\nZa začetek na zavihku Osnovno naslovom v dokumentu dodelite sloge naslovov. + Croatian Hrvaščina Crop Obreži Cross-reference Navzkrižni sklic Cross-reference: Navzkrižni sklic: @@ -560,19 +578,20 @@ Current Trenutno Current - Linear Trenutno – linearno Current - Professional Trenutno – profesionalno - Current Position Trenutni položaj Current page Trenutna stran + Current Position Trenutni položaj Current style: {style} Trenutni slog: {style} Curved Arrow Connector Ukrivljen puščični povezovalnik Curved Connector Ukrivljen povezovalnik Custom Po meri + Custom dictionary Slovar po meri Custom Margins… Robovi po meri … + Custom mark: Oznaka po meri: + Custom range Obseg po meri + Custom size Velikost po meri Custom Table of Contents… Kazalo vsebine po meri … Custom Watermark Vodni žig po meri Custom Watermark… Vodni žig po meri … - Custom dictionary Slovar po meri - Custom mark: Oznaka po meri: - Custom range Obseg po meri Customize Prilagodi Customize Keyboard Prilagodi tipkovnico Customize Quick Access Toolbar Prilagodi orodno vrstico za hitri dostop @@ -583,9 +602,6 @@ Cut Izreži Cycle Cikel Czech Češčina - DO NOT COPY NE KOPIRAJ - DOI DOI - DRAFT OSNUTEK Danish Danščina Dark Temno Dark Mode Temni način @@ -596,10 +612,10 @@ Data Podatki Data Labels Oznake podatkov Date & Time Datum in ura - Date Picker Content Control Kontrolnik vsebine za izbiro datuma Date and Time Datum in ura Date format Oblika datuma Date formats: Oblike datuma: + Date Picker Content Control Kontrolnik vsebine za izbiro datuma Day Dan Decimal Decimalno Decorative objects only add visual interest: screen readers skip them. Okrasni predmeti so zgolj vizualni dodatek: bralniki zaslona jih preskočijo. @@ -615,10 +631,12 @@ Deflate Izprazni Delete Izbriši Delete Back in Equation Izbriši nazaj v enačbi + Delete Bookmark Izbriši zaznamek Delete Cells Izbriši celice Delete Clipboard Item Izbriši element odložišča Delete Columns Izbriši stolpce Delete Comment Izbriši komentar + Delete in Equation Izbriši v enačbi Delete Label Izbriši nalepko Delete Next Word Izbriši naslednjo besedo Delete Previous Word Izbriši prejšnjo besedo @@ -627,7 +645,6 @@ Delete Style Izbriši slog Delete Table Izbriši tabelo Delete Table Style Izbriši slog tabele - Delete in Equation Izbriši v enačbi Deleted Izbrisano Deletions: Izbrisi: Delivery address: Naslov za dostavo: @@ -636,6 +653,7 @@ Demote Znižaj raven Demote to Body Text Znižaj na telo besedila Derivative as a limit Odvod kot limita + Descending Padajoče Describe what this object shows, for people who can't see it. Opišite, kaj prikazuje ta predmet, za ljudi, ki ga ne vidijo. Describe what this table shows, for people who can't see it. Opišite, kaj prikazuje ta tabela, za ljudi, ki je ne vidijo. Description: Opis: @@ -644,10 +662,13 @@ Detach Template Odklopi predlogo Detect language automatically Samodejno zaznaj jezik Developer Razvijalec + development build razvojna različica Diagonal Diagonalno Diagonal Dots Diagonalne pike Diamond Romb Dictate Narekuj + Dictionaries Slovarji + Dictionaries folder: Mapa s slovarji: Different First Page Drugačna prva stran Different Odd & Even Pages Drugačne lihe in sode strani Differential Diferencial @@ -665,33 +686,37 @@ Display name Prikazno ime Display text Prikazano besedilo Distribute Columns Enakomerno razporedi stolpce - Distribute Rows Enakomerno razporedi vrstice Distribute letters Razporedi črke + Distribute Rows Enakomerno razporedi vrstice Distribute spacing Razporedi razmike Distributed Porazdeljeno Do Not Check Spelling or Grammar Ne preverjaj črkovanja ali slovnice - Do Not Show in TOC Ne prikaži v kazalu vsebine Do not check spelling or grammar Ne preverjaj črkovanja ali slovnice + DO NOT COPY NE KOPIRAJ + Do Not Show in TOC Ne prikaži v kazalu vsebine Do you want to save changes to {name}? Ali želite shraniti spremembe v {name}? Document Dokument Document End Konec dokumenta Document Formatting Oblikovanje dokumenta Document Grid Mreža dokumenta Document Information Podatki o dokumentu + Document name Ime dokumenta Document Property Lastnost dokumenta Document Start Začetek dokumenta + Document template: Predloga dokumenta: Document Text Besedilo dokumenta Document Title Naslov dokumenta - Document template: Predloga dokumenta: Document1 Dokument1 Document\nPreferences Nastavitve\ndokumenta Documents Dokumenti Documents you open will show up here. Tukaj bodo prikazani dokumenti, ki jih odprete. - Don't Save Ne shrani + Does not contain Ne vsebuje + DOI DOI Don't add space between paragraphs of the same style Ne dodajaj razmika med odstavki istega sloga Don't capitalize after: Brez velike začetnice za: Don't hyphenate Ne deli besed Don't read Zotero, Mendeley or EndNote citations and bibliographies Ne beri citatov in bibliografij iz Zotera, Mendeleya ali EndNota + Don't Save Ne shrani Dot Pika Dot dash Pika črta Dot-dash Pika-črta @@ -714,12 +739,13 @@ Double Spacing Dvojni razmik Double Square Brackets Dvojni oglati oklepaji Double Strikethrough Dvojno prečrtano - Double Underline Dvojno podčrtano - Double Vertical Bars Dvojne navpičnice Double strikethrough Dvojno prečrtano Double summation Dvojna vsota + Double Underline Dvojno podčrtano + Double Vertical Bars Dvojne navpičnice Doughnut Kolobarni Down Navzdol + DRAFT OSNUTEK Draft Osnutek Drag a rectangle to draw a table, or a line across cells to split them. Esc stops. Povlecite pravokotnik, da narišete tabelo, ali črto čez celice, da jih razdelite. Esc ustavi. Draw Risanje @@ -737,6 +763,7 @@ E-mail Messages E-poštna sporočila Edit Comment Uredi komentar Edit Data Uredi podatke + Edit Document Uredi dokument Edit Equation Uredi enačbo Edit Field Code Uredi kodo polja Edit Field… Uredi polje … @@ -770,9 +797,9 @@ Enclosure Obroba Encrypt with Password Šifriraj z geslom Encrypt with Password… Šifriraj z geslom … - End of Word Konec besede End of document Konec dokumenta End of section Konec odseka + End of Word Konec besede Endnote Reference Sklic na končno opombo Endnote Text Besedilo končne opombe Endnotes Končne opombe @@ -781,19 +808,23 @@ English (United States) Angleščina (Združene države) Engrave Vgravirano Enlarge symbol Povečaj simbol + Enter bookmark name: Vnesite ime zaznamka: Enter formatting for number: Vnesite oblikovanje za številko: Enter in Equation Enter v enačbi Enter page number: Vnesite številko strani: Entry marked. Vnos je označen. + Envelope #10 Kuverta #10 Envelope #10 4.12" × 9.50" Ovojnica #10 4.12" × 9.50" Envelope Address Naslov na ovojnici + Envelope DL Kuverta DL Envelope DL 4.33" × 8.66" Ovojnica DL 4.33" × 8.66" Envelope Return Povratni naslov na ovojnici Envelope size: Velikost ovojnice: Envelopes Ovojnice - Equal Equal Dvojni enačaj Equal by Definition Enako po definiciji Equal column width Enaka širina stolpcev + Equal Equal Dvojni enačaj + Equal to Enako Equals: Enako: Equation Enačba Equation Gallery Galerija enačb @@ -805,6 +836,8 @@ Errors ({n}) Napake ({n}) Estonian Estonščina Euler's identity Eulerjeva identiteta + Even Banded Columns Sodi pasasti stolpci + Even Banded Rows Sode pasaste vrstice Even Page Soda stran Even pages only Samo sode strani Every change is recorded as a tracked change. Vsaka sprememba se zabeleži kot sledena sprememba. @@ -817,22 +850,23 @@ Exceptions (optional) Izjeme (neobvezno) Exceptions… Izjeme … Executive 7.25" × 10.50" Executive 7.25" × 10.50" + Existing File or Web Page Obstoječa datoteka ali spletna stran Expand Razširi Expand or Collapse Heading Razširi ali strni naslov Exponential Eksponentno Exponential series Eksponentna vrsta Export Izvozi - Export PDF Izvozi PDF - Export Page as PNG Izvozi stran kot PNG Export a copy to share (PDF, Word document) Izvozi kopijo za deljenje (PDF, dokument Word) + Export Page as PNG Izvozi stran kot PNG + Export PDF Izvozi PDF Exported {path} Izvoženo: {path} Expression Izraz Extend Selection Razširi izbor Extend Selection by Units Razširi izbor po enotah Extra space Dodaten prostor Field Polje - Field Codes Kode polj Field code: Koda polja: + Field Codes Kode polj Field codes: Kode polj: Field name Ime polja Field name: Ime polja: @@ -850,16 +884,20 @@ Fill color: Barva polnila: Filling in forms Izpolnjevanje obrazcev Film Film + Filter and Sort Filtriranje in razvrščanje + Filter Recipients Filtriraj prejemnike Find Najdi + Find and Replace Iskanje in zamenjava Find Next Najdi naslednje Find Next Editable Region Najdi naslednje področje za urejanje Find Previous Najdi prejšnje Find Recipient Najdi prejemnika Find What Expression Izraz za iskanje Find What Text Besedilo za iskanje - Find and Replace Iskanje in zamenjava Find what: Najdi: + Find “{text}” in the document Najdi »{text}« v dokumentu Find: Najdi: + Finding printers… Iskanje tiskalnikov… Finish Dokončaj Finish & Merge Dokončaj in spoji Finish &\nMerge Dokončaj\nin spoji @@ -867,10 +905,10 @@ Finnish Finščina First Prvi First Column Prvi stolpec - First Name Ime First commit Prva potrditev First line Prva vrstica First line: Prva vrstica: + First Name Ime First row of data contains column headers Prva vrstica podatkov vsebuje glave stolpcev Fit Prilagodi Fix Position on Page Pritrdi položaj na strani @@ -882,21 +920,22 @@ Follow number with: Za številko vstavi: Font Pisava Font Color Barva pisave - Font Dialog Pogovorno okno Pisava - Font Size Velikost pisave Font color Barva pisave Font color: Barva pisave: + Font Dialog Pogovorno okno Pisava + Font Size Velikost pisave Font: Pisava: Font: {font} Pisava: {font} Fonts Pisave Font… Pisava … Footer Noga + Footnote and Endnote Sprotna in končna opomba Footnote Reference Sklic na sprotno opombo Footnote Text Besedilo sprotne opombe - Footnote and Endnote Sprotna in končna opomba Footnotes Sprotne opombe Footnotes and endnotes Sprotne in končne opombe Footnotes to endnotes Sprotne opombe v končne opombe + For which item: Za kateri element: Format Oblika Format Page Numbers Oblikuj številke strani Format Page Numbers… Oblikuj številke strani … @@ -910,34 +949,43 @@ Formatting marks Oznake oblikovanja Formatting, case changes, white space and tables are compared; the other settings are kept for later. Primerjajo se oblikovanje, spremembe velikosti črk, presledki in tabele; druge nastavitve so shranjene za pozneje. Formula Formula + Formula: Formula: Forward Naprej Fraction Ulomek Fractions (1/2) as fraction characters (½) Ulomke (1/2) v znake ulomkov (½) French (France) Francoščina (Francija) From text: Iz besedila: + from Unicode (hex) iz Unicode (šestnajstiško) Full page of the same label Cela stran iste nalepke + Full Screen Celozaslonski način + Full\nScreen Celozaslonski\nnačin Function Funkcija Functions Funkcije - GB/T 7714 GB/T 7714 - GOST (name sort) GOST (razvrščanje po imenu) - GOST (title sort) GOST (razvrščanje po naslovu) Gallery Galerija + GB/T 7714 GB/T 7714 General Splošno Geometry Geometrija German (Germany) Nemščina (Nemčija) + getartcraft.com/apps/wordcraft getartcraft.com/apps/wordcraft Glow Sij Glow Colors Barve sija Go Back Nazaj Go To Pojdi na - Go To (⌘⌥G) Pojdi na (⌘⌥G) - Go To… Pojdi na … Go to Pojdi na + Go To (⌘⌥G) Pojdi na (⌘⌥G) + Go to what: Pojdi na: + Go To… Pojdi na … Good afternoon Dober dan Good evening Dober večer Good morning Dobro jutro + GOST (name sort) GOST (razvrščanje po imenu) + GOST (title sort) GOST (razvrščanje po naslovu) Grab bag Mešanica Graphic Grafika Grave Krativec + Grayscale Odtenki sive + Greater than Večje od + Greater than or equal Večje ali enako Greek Grščina Greek Letters Grške črke Green: Zelena: @@ -954,19 +1002,21 @@ Grouping Character Below Združevalni znak spodaj Grow Font 1 Point Povečaj pisavo za 1 točko H: V: - HTML Format Oblika HTML Hanging Indent Viseči zamik Harvard (Anglia) Harvard (Anglia) Hat Strešica Header Glava Header & Footer Glava in noga Header Row Vrstica z glavo + Header row Vrstica z glavo Header/Footer Position Položaj glave/noge Headers and footers Glave in noge + Heading Naslov Heading 1 Naslov 1 Heading 2 Naslov 2 Heading 3 Naslov 3 Heading styles (levels 1–{n}) Slogi naslovov (ravni 1–{n}) + Heading text Besedilo naslova Heading {n} Naslov {n} Headings Naslovi Heart Srce @@ -975,11 +1025,12 @@ Help Pomoč Hex: Hex: Hidden Skrito + Hidden bookmarks Skriti zaznamki Hidden text Skrito besedilo Hide Codes Skrij kode + Hide from style lists Skrij s seznamov slogov Hide Ink Skrij črnilo Hide White Space Skrij prazen prostor - Hide from style lists Skrij s seznamov slogov Hide\nInk Skrij\nčrnilo Hierarchy Hierarhija Highlight Označi @@ -995,7 +1046,9 @@ How many pages the whole document has Koliko strani ima celoten dokument How many pages this section has Koliko strani ima ta odsek How many words the document holds Koliko besed vsebuje dokument + HTML Format Oblika HTML Hue: Odtenek: + Hunspell .aff and .dic files here (pl_PL, pt_BR, it_IT, hr_HR, tr_TR) are used before the system's. Datoteke Hunspell .aff in .dic v tej mapi (pl_PL, pt_BR, it_IT, hr_HR, tr_TR) imajo prednost pred sistemskimi. Hyperbolic Cosecant Function Funkcija hiperboličnega kosekansa Hyperbolic Cosine Function Funkcija hiperboličnega kosinusa Hyperbolic Cotangent Function Funkcija hiperboličnega kotangensa @@ -1012,12 +1065,11 @@ Hyphens (--) as dashes (—) Vezaje (--) v pomišljaje (—) Hypotenuse Hipotenuza I, II, III, … I, II, III, … - IEEE IEEE - ISO 690 (author-date) ISO 690 (avtor-datum) - ISO 690 (numerical) ISO 690 (številčno) + i, ii, iii, … i, ii, iii, … Icon Color Barva ikone Icons Ikone Identity Matrices Enotske matrike + IEEE IEEE If…Then…Else Če…Potem…Sicer If…Then…Else… Če…Potem…Sicer… Ignore All Prezri vse @@ -1031,9 +1083,10 @@ Immersive Reader Poglobljeni bralnik Immersive\nReader Poglobljeni\nbralnik In Default apps, choose WordCraft for the document types you want it to open. V privzetih aplikacijah izberite WordCraft za vrste dokumentov, ki naj jih odpira. + In equations, typing a name after a backslash replaces it with its symbol. Ko v enačbi za poševnico nazaj vtipkate ime, ga zamenja ustrezni simbol. In Front of Text Pred besedilom In Line with Text V vrstici z besedilom - In equations, typing a name after a backslash replaces it with its symbol. Ko v enačbi za poševnico nazaj vtipkate ime, ga zamenja ustrezni simbol. + In the margin Na robu Inches Palci Include level number from: Vključi številko ravni od: Include page numbers Vključi številke strani @@ -1046,8 +1099,8 @@ Indents Zamiki Indents and Spacing Zamiki in razmiki Index Stvarno kazalo - Index Heading Naslov stvarnega kazala Index and Tables Kazala in tabele + Index Heading Naslov stvarnega kazala Index {n} Stvarno kazalo {n} Inflate Napihni Info Informacije @@ -1061,6 +1114,7 @@ Inline V vrstici Input Format Vnosna oblika Insert Vstavljanje + Insert a new equation (Alt+=) Vstavi novo enačbo (Alt+=) Insert Above Vstavi zgoraj Insert Below Vstavi spodaj Insert Caption Vstavi napis @@ -1078,7 +1132,9 @@ Insert Merge Field Vstavi polje spajanja Insert Merge\nField Vstavi polje\nspajanja Insert New Equation Vstavi novo enačbo + Insert one text or another, depending on a field's value. Vstavi eno ali drugo besedilo, odvisno od vrednosti polja. Insert Page Field Vstavi polje s številko strani + Insert reference to: Vstavi sklic na: Insert Right Vstavi desno Insert Row or Column by Clicking Vstavi vrstico ali stolpec s klikom Insert Rows Above Vstavi vrstice zgoraj @@ -1092,8 +1148,6 @@ Insert Table of Figures Vstavi kazalo slik Insert Table… Vstavi tabelo … Insert Time Field Vstavi polje s časom - Insert a new equation (Alt+=) Vstavi novo enačbo (Alt+=) - Insert one text or another, depending on a field's value. Vstavi eno ali drugo besedilo, odvisno od vrednosti polja. Insert\nAbove Vstavi\nzgoraj Insert\nCaption Vstavi\nnapis Insert\nCitation Vstavi\ncitat @@ -1101,6 +1155,8 @@ Insertions and Deletions Vstavitve in izbrisi Insertions: Vstavitve: Inside Borders Notranje obrobe + Inside Horizontal Border Notranja vodoravna obroba + Inside Vertical Border Notranja navpična obroba Inside: Znotraj: Inspect Document Preglej dokument Inspect Styles at Selection Preglej sloge pri izboru @@ -1114,10 +1170,10 @@ Intense Quote Izrazit citat Intense Reference Izrazit sklic Interface Vmesnik - Interface Theme Tema vmesnika Interface language Jezik vmesnika Interface language: Jezik vmesnika: Interface size: Velikost vmesnika: + Interface Theme Tema vmesnika Interface theme: Tema vmesnika: Interface\nTheme Tema\nvmesnika Internet addresses as links Internetne naslove v povezave @@ -1140,6 +1196,10 @@ Inverse Sine Function Funkcija inverznega sinusa Inverse Tangent Function Funkcija inverznega tangensa Ionosphere Ionosfera + Is blank Je prazno + Is not blank Ni prazno + ISO 690 (author-date) ISO 690 (avtor-datum) + ISO 690 (numerical) ISO 690 (številčno) Issue Številka Italian Italijanščina Italic Ležeče @@ -1152,8 +1212,8 @@ Justified Obojestransko Justify Poravnaj obojestransko Keep Lines Together Obdrži vrstice skupaj - Keep Text Only Ohrani samo besedilo Keep lines together Obdrži vrstice skupaj + Keep Text Only Ohrani samo besedilo Keep the password somewhere safe: without it nobody, WordCraft included, can open the document. Leave both boxes empty to remove the password. Geslo shranite na varno: brez njega dokumenta ne more odpreti nihče, niti WordCraft. Če želite geslo odstraniti, pustite obe polji prazni. Keep with Next Obdrži z naslednjim Keep with next Obdrži z naslednjim @@ -1167,6 +1227,7 @@ Labels Nalepke Landscape Ležeče Language Jezik + Language Preferences… Jezikovne nastavitve… Large Veliko Large\nOperator Veliki\noperator Largest Only Samo največji @@ -1174,39 +1235,42 @@ Lasso Select Izbira z lasom Last Zadnji Last Column Zadnji stolpec - Last Name Priimek Last commit Zadnja potrditev + Last Name Priimek Last, First; Last, First Priimek, Ime; Priimek, Ime Latin text font: Pisava za latinično besedilo: Layout Postavitev Layout Summary Povzetek postavitve Layouts Postavitve Leader Vodilni znak - Leave Equation Zapusti enačbo Leave a recipient out of the merge when a field has this value. Izpusti prejemnika iz spajanja, kadar ima polje to vrednost. + Leave Equation Zapusti enačbo + Leave out the date and time of comments and tracked changes when saving Pri shranjevanju izpusti datum in uro komentarjev ter sprememb s sledenjem Leave the equation (Esc) Zapusti enačbo (Esc) Leave the label out of the caption Izpusti oznako iz napisa Left Levo Left Arrow with Text Above Puščica levo z besedilom zgoraj Left Arrow with Text Below Puščica levo z besedilom spodaj Left Border Leva obroba + Left border Leva obroba Left Bracket Twice Dvojni levi oklepaj + Left indent Levi zamik Left Margin Levi rob Left Only Samo levo Left Right Arrow Above Puščica levo-desno zgoraj Left Right Arrow with Text Above Puščica levo-desno z besedilom zgoraj Left Right Arrow with Text Below Puščica levo-desno z besedilom spodaj Left Subscript-Superscript Levi podpisani in nadpisani indeks - Left border Leva obroba - Left indent Levi zamik - Left-to-Right Text Direction Smer besedila od leve proti desni Left-to-right Od leve proti desni + Left-to-Right Text Direction Smer besedila od leve proti desni Left: Levo: Leftwards Arrow Above Puščica levo zgoraj Leftwards Harpoon Above Harpuna levo zgoraj Legal 8.50" × 14.00" Legal 8.50" × 14.00" Legal style numbering Pravni slog oštevilčenja Legend Legenda + Less than Manjše od + Less than or equal Manjše ali enako Letter Pismo Letter 8.50" × 11.00" Letter 8.50" × 11.00" Letter headings Naslovi s črkami @@ -1226,27 +1290,27 @@ Light Svetlo Light with Outline Svetlo z obrisom Limit Limita - Limit Example Primer limite Limit and Limita in Limit and\nLog Limita in\nlogaritem Limit consecutive hyphens to: Omeji zaporedne vezaje na: + Limit Example Primer limite Limit formatting to a selection of styles Omeji oblikovanje na izbrane sloge Line Črta + Line and Page Breaks Prelomi vrstic in strani + Line and Paragraph Spacing Razmik vrstic in odstavkov + Line between Črta med Line Break Prelom vrstice + Line breaking Prelamljanje vrstic + Line delta Razlika vrstic Line End Konec vrstice Line Number Številka vrstice Line Numbering Options… Možnosti oštevilčenja vrstic … Line Numbers Številke vrstic Line Spacing Razmik vrstic - Line Spacing Options… Možnosti razmika vrstic … - Line Start Začetek vrstice - Line and Page Breaks Prelomi vrstic in strani - Line and Paragraph Spacing Razmik vrstic in odstavkov - Line between Črta med - Line breaking Prelamljanje vrstic - Line delta Razlika vrstic Line spacing Razmik vrstic + Line Spacing Options… Možnosti razmika vrstic … Line spacing: Razmik vrstic: + Line Start Začetek vrstice Linear Linearno Linear Fraction Linearni ulomek Lines Vrstice @@ -1260,18 +1324,23 @@ Link entries to their headings Poveži vnose z njihovimi naslovi Link level to style: Poveži raven s slogom: Link to Previous Poveži s prejšnjim + Link to: Povezava do: Linked style (paragraph and character) Povezan slog (odstavek in znak) Links Povezave Links and References Povezave in sklici Link… Povezava … List Seznam List Definition Definicija seznama + List entries Vnosi seznama List Paragraph Odstavek seznama List Table 3 – Blue Seznamska tabela 3 – modra List Table 3 – Green Seznamska tabela 3 – zelena List Table 3 – Orange Seznamska tabela 3 – oranžna List Table 3 – Purple Seznamska tabela 3 – vijolična - List entries Vnosi seznama + Load System Installed Fonts… Naloži nameščene sistemske pisave… + Loaded {count} system fonts. Naloženih sistemskih pisav: {count}. + Local font access is not supported by your browser. Vaš brskalnik ne podpira dostopa do lokalnih pisav. + Local font access was denied. Dostop do lokalnih pisav je bil zavrnjen. Location Mesto Location: {path} Mesto: {path} Lock Fields Zakleni polja @@ -1282,9 +1351,8 @@ Logical AND with Limits Logični IN z mejama Logical OR Logični ALI Logical OR with Limits Logični ALI z mejama + lowercase male črke Lum: Svetl.: - MIT OR Apache-2.0. Copyright (c) 2026 ArtCraft Team and the WordCraft contributors. MIT OR Apache-2.0. Copyright (c) 2026 ArtCraft Team in sodelavci projekta WordCraft. - MLA MLA Macros Makri Made from the author and year when empty Če je prazno, se ustvari iz avtorja in leta Magazine or newspaper article Članek v reviji ali časopisu @@ -1292,31 +1360,31 @@ Mailings Pošiljanje Main entry: Glavni vnos: Make WordCraft the default for Word documents… Nastavi WordCraft kot privzeti program za dokumente Word … - Makes the ribbon, menus and dialogs larger or smaller, on top of your system's display scaling. Document zoom is separate. Poveča ali pomanjša trak, menije in pogovorna okna, poleg sistemskega merila zaslona. Povečava dokumenta je ločena. + Makes the ribbon, menus and dialogs larger or smaller. Auto follows your system's display scaling; 100% is one screen pixel per point. Document zoom is separate. Poveča ali pomanjša trak, menije in pogovorna okna. Samodejno sledi sistemskemu merilu zaslona; 100 % je ena zaslonska točka na točko. Povečava dokumenta je ločena. Manage Sources Upravljaj vire Manage Styles Upravljaj sloge Manage Styles… Upravljaj sloge … Manual Hyphenation Ročno deljenje besed + Manual hyphenation is complete. Ročno deljenje besed je končano. Manual Line Break Ročni prelom vrstice Manual Page Break Ročni prelom strani - Manual hyphenation is complete. Ročno deljenje besed je končano. Manual… Ročno … Many pages Več strani Margins Robovi Mark Označi Mark All Označi vse + Mark as decorative Označi kot okrasno Mark Citation Označi citat Mark Entry Označi vnos Mark Entry… Označi vnos … - Mark Index Entry Označi vnos stvarnega kazala - Mark as decorative Označi kot okrasno Mark grammar errors as you type Označuj slovnične napake med tipkanjem + Mark Index Entry Označi vnos stvarnega kazala Mark selected text as: Označi izbrano besedilo kot: Mark\nCitation Označi\ncitat Mark\nEntry Označi\nvnos Markdown (*.md) Markdown (*.md) - Match Fields Uskladi polja Match case Razlikuj velike in male črke + Match Fields Uskladi polja Match prefix Ujemanje predpone Match suffix Ujemanje pripone Math AutoCorrect Matematični samopopravki @@ -1326,6 +1394,7 @@ Maximum Example Primer maksimuma Measured By Merjeno po Measurement units: Merske enote: + Medium Srednje Merge Spoji Merge Cells Spoji celice Merge Formatting Združi oblikovanje @@ -1338,7 +1407,10 @@ Minimum Minimum Minus Equal Minus enako Mirror indents Zrcalni zamiki + Mirror margins Zrcalni robovi Mirrored Zrcalno + MIT OR Apache-2.0. Copyright (c) 2026 ArtCraft Team and the WordCraft contributors. MIT OR Apache-2.0. Copyright (c) 2026 ArtCraft Team in sodelavci projekta WordCraft. + MLA MLA Model Model Models Modeli Moderate Zmerno @@ -1352,9 +1424,12 @@ More Colors… Več barv … More Columns… Več stolpcev … More Commands… Več ukazov … + More Paper Sizes… Več velikosti papirja… More Symbols… Več simbolov … More templates → Več predlog → Move Down Premakni navzdol + Move in Equation Premik v enačbi + Move or Resize Premakni ali spremeni velikost Move Paragraph Down Premakni odstavek navzdol Move Paragraph Up Premakni odstavek navzgor Move Past Citation Premakni se za citat @@ -1362,19 +1437,20 @@ Move Selected Ink Premakni izbrano črnilo Move Table by Dragging Premikaj tabelo z vlečenjem Move Up Premakni navzgor - Move in Equation Premik v enačbi - Move or Resize Premakni ali spremeni velikost Move with Text Premikaj z besedilom Moved from Premaknjeno iz + Moved objects snap to the page's edges and centre, the margins and other objects. Hold Alt to move freely. Premaknjeni predmeti se prilepijo na robove in središče strani, na robove in na druge predmete. Držite Alt za prosto premikanje. Moved to Premaknjeno v Moves Premiki Multilevel List Večravenski seznam Multiple Pages Več strani + My list has Moj seznam ima Name Ime Name (A–Z) Ime (A–Ž) Name of the file this document is saved as Ime datoteke, v katero je shranjen ta dokument Name: Ime: - Narrow 0.5" all Ozki 0.5" povsod + Narrow Ozko + Narrow {size} all Ozki {size} povsod Natural Logarithm Naravni logaritem Nature Narava Navigation Krmarjenje @@ -1382,22 +1458,22 @@ Negated Relations Zanikane relacije New Nov New Comment Nov komentar + New comments and tracked changes are recorded under this name and these initials. Novi komentarji in sledene spremembe se beležijo pod tem imenom in temi začetnicami. New Document Nov dokument + New document Nov dokument New Drawing Canvas Novo risalno platno New Entry Nov vnos + New from Template Nov dokument iz predloge New Group Nova skupina New Label Nova nalepka New Label… Nova nalepka … New Paragraph Nov odstavek + New shortcut: Nova bližnjica: New Style… Nov slog … New Tab Nov zavihek New Table Style Nov slog tabele New Table Style… Nov slog tabele … New Window Novo okno - New comments and tracked changes are recorded under this name and these initials. Novi komentarji in sledene spremembe se beležijo pod tem imenom in temi začetnicami. - New document Nov dokument - New from Template Nov dokument iz predloge - New shortcut: Nova bližnjica: New word Nova beseda New\nComment Nov\nkomentar New\nWindow Novo\nokno @@ -1413,28 +1489,32 @@ Next Sentence Naslednji stavek Next sentence Naslednji stavek No Ne + No accessibility issues found. Ni najdenih težav z dostopnostjo. No Border Brez obrobe + No changes (read only) Brez sprememb (samo za branje) No Color Brez barve + No contributor data was built into this copy. V to kopijo niso vgrajeni podatki o sodelavcih. + No document is open. Create a document or open one first. Ni odprtega dokumenta. Najprej ustvarite dokument ali odprite enega. + No errors found: every merge field is in the recipient list. Ni napak: vsa polja spajanja so na seznamu prejemnikov. No Fill Brez polnila No Glow Brez sija + No grid Brez mreže + No header row Brez vrstice z glavo + No icons match your search. Nobena ikona se ne ujema z iskanjem. + No limit Brez omejitve No Markup Brez oznak + No model credits were built into this copy. V to kopijo ni vgrajenih podatkov o modelih. No Outline Brez obrisa No Paragraph Space Brez razmika med odstavki + No printers found. Configure a system printer or export a PDF. Ni najdenih tiskalnikov. Nastavite sistemski tiskalnik ali izvozite PDF. No Reflection Brez odseva No Shadow Brez sence No Soft Edges Brez mehkih robov - No Spacing Brez razmika - No Transform Brez preoblikovanja - No accessibility issues found. Ni najdenih težav z dostopnostjo. - No changes (read only) Brez sprememb (samo za branje) - No contributor data was built into this copy. V to kopijo niso vgrajeni podatki o sodelavcih. - No errors found: every merge field is in the recipient list. Ni napak: vsa polja spajanja so na seznamu prejemnikov. - No grid Brez mreže - No icons match your search. Nobena ikona se ne ujema z iskanjem. - No limit Brez omejitve - No model credits were built into this copy. V to kopijo ni vgrajenih podatkov o modelih. No sources yet. Virov še ni. + No Spacing Brez razmika + No spelling dictionary for {language} is installed, so it isn't checked. WordCraft looks for the Hunspell dictionary {name} in the WORDCRAFT_DICTIONARIES folder and in the usual system locations. Za jezik {language} ni nameščenega slovarja za črkovanje, zato se ne preverja. WordCraft poišče slovar Hunspell {name} v mapi WORDCRAFT_DICTIONARIES in na običajnih sistemskih mestih. No templates match your search. Nobena predloga se ne ujema z iskanjem. + No Transform Brez preoblikovanja No words yet. Add to Dictionary on a misspelled word puts it here. Besed še ni. Ukaz Dodaj v slovar na napačno črkovani besedi jo doda sem. No-Width Non Break Nedeljivi presledek brez širine No-Width Optional Break Neobvezni prelom brez širine @@ -1442,21 +1522,23 @@ Nonbreaking Space Nedeljivi presledek None Brez Normal Navadno - Normal 1" all Navadni 1" povsod + Normal {size} all Navadni {size} povsod Normal Indent Navadni zamik + Normal probability density Gostota normalne porazdelitve + Normal speed Navadna hitrost Normal Style Navadni slog Normal Table Navadna tabela Normal Text Navadno besedilo Normal Word Document Navadni dokument Word - Normal probability density Gostota normalne porazdelitve - Normal speed Navadna hitrost North Sever Norwegian (Bokmål) Norveščina (bokmål) Not Ne Not available right now Trenutno ni na voljo + Not equal to Ni enako Not linked: click a text box Ni povezano: kliknite polje z besedilom Not saved Ni shranjeno Not saved yet Še ni shranjeno + Not set: the system's usual places Ni nastavljeno: običajna sistemska mesta Not used by any command Ni uporabljeno v nobenem ukazu Not {name} Ni {name} Nothing Nič @@ -1471,6 +1553,7 @@ Number of rows: Število vrstic: Number of the page the field sits on Številka strani, na kateri je polje Number of the section the field sits in Številka odseka, v katerem je polje + Number only, without the label's words Samo številka, brez besed oznake Number style for this level: Slog številk za to raven: Number the equation; the number sits at the right margin (or type # then the number) Oštevilčite enačbo; številka je ob desnem robu (ali vtipkajte # in številko) Numbering Oštevilčevanje @@ -1478,8 +1561,11 @@ Numbering… Oštevilčevanje … Numerals Števke Numerals: Števke: - OK V redu + n×n Identity Matrix Enotska matrika n×n + n×n Matrix Matrika n×n Object Predmet + Odd Banded Columns Lihi pasasti stolpci + Odd Banded Rows Lihe pasaste vrstice Odd Page Liha stran Odd pages only Samo lihe strani Off Izklopljeno @@ -1495,17 +1581,20 @@ Offset: Top Odmik: zgoraj Offset: Top Left Odmik: zgoraj levo Offset: Top Right Odmik: zgoraj desno + OK V redu Omit Izpusti On Vklopljeno - On: WordCraft opens on File › Home, with a blank document, the templates and your recent documents. Off: it opens a blank document. Vklopljeno: WordCraft se odpre na Datoteka › Osnovno s praznim dokumentom, predlogami in nedavnimi dokumenti. Izklopljeno: odpre prazen dokument. On: the caret glides to its new place when you type or use the arrow keys, and its blink fades. Off: it jumps and blinks on and off. Vklopljeno: kazalec med tipkanjem ali uporabo puščičnih tipk zdrsne na novo mesto, utripanje pa pojema. Izklopljeno: kazalec skoči in utripa. + On: WordCraft opens on File › Home, with a blank document, the templates and your recent documents. Off: it opens a blank document. Vklopljeno: WordCraft se odpre na Datoteka › Osnovno s praznim dokumentom, predlogami in nedavnimi dokumenti. Izklopljeno: odpre prazen dokument. One Ena - One Page Ena stran One half Polovica One label per recipient Ena nalepka na prejemnika One line per item. Enter adds an item. En element na vrstico. Enter doda element. + One Page Ena stran + One-sided Enostransko Only allow this kind of editing: Dovoli samo to vrsto urejanja: Only comments can be added, except in regions marked editable. Dodajati je mogoče samo komentarje, razen v področjih, označenih za urejanje. + Only for the selected text Samo za izbrano besedilo Only form fields can be filled in. Izpolnjevati je mogoče samo polja obrazca. Only the allowed styles can be applied. Uporabiti je mogoče samo dovoljene sloge. Only web (http, https) and email (mailto) links can be followed Slediti je mogoče samo spletnim (http, https) in e-poštnim (mailto) povezavam @@ -1521,6 +1610,7 @@ Optional Hyphen Neobvezni vezaj Options Možnosti Options… Možnosti … + Or Ali Or save a copy in another format: Ali shranite kopijo v drugi obliki: Ordinals (1st) as superscript Vrstilnike (1.) kot nadpisano Organizer Organizator @@ -1533,13 +1623,13 @@ Otherwise insert: Sicer vstavi: Outline Oris Outline Level Raven orisa - Outline State Stanje orisa - Outline Tools Orodja za oris Outline level Raven orisa Outline levels Ravni orisa + Outline State Stanje orisa + Outline Tools Orodja za oris Outlining Orisovanje - Outside Borders Zunanje obrobe Outside border Zunanja obroba + Outside Borders Zunanje obrobe Outside: Zunaj: Outward Brackets Navzven obrnjeni oklepaji Oval Elipsa @@ -1548,31 +1638,29 @@ Overbrace Zgornji zaviti oklepaj Overtype Prepisovanje Overtype (typing replaces the characters after the caret) Prepisovanje (tipkanje zamenja znake za kazalcem) - PDF document (*.pdf) Dokument PDF (*.pdf) - PRs PR-ji Page Stran Page Background Ozadje strani Page Border Obroba strani Page Borders Obrobe strani Page Break Prelom strani Page Break Before Prelom strani pred + Page break before Prelom strani pred Page Breaks Prelomi strani Page Color Barva strani + Page display Prikaz strani Page Down Stran navzdol + Page image (*.png) Slika strani (*.png) Page Movement Premikanje po straneh Page Number Številka strani - Page Setup Nastavitev strani - Page Up Stran navzgor - Page Width Širina strani - Page X of Y Stran X od Y - Page break before Prelom strani pred - Page display Prikaz strani - Page image (*.png) Slika strani (*.png) Page number style Slog številk strani Page numbers at the right margin Številke strani ob desnem robu Page on which a bookmarked passage falls Stran, na kateri je odlomek z zaznamkom + Page Setup Nastavitev strani Page title Naslov strani + Page Up Stran navzgor + Page Width Širina strani Page width Širina strani + Page X of Y Stran X od Y Page {page} Stran {page} Page {page} of {pages} Stran {page} od {pages} Page\nBorders Obrobe\nstrani @@ -1581,16 +1669,18 @@ Pages Strani Pages like 1-3, 5 Strani, npr. 1-3, 5 Pages of bookmark: Strani zaznamka: + Paper Papir + Paper size: Velikost papirja: Paragraph Odstavek Paragraph Character Znak za odstavek Paragraph Details Podrobnosti odstavka Paragraph Down Odstavek navzdol Paragraph Mark Oznaka odstavka + Paragraph marks Oznake odstavkov Paragraph Settings Nastavitve odstavka Paragraph Spacing Razmik med odstavki - Paragraph Up Odstavek navzgor - Paragraph marks Oznake odstavkov Paragraph style Slog odstavka + Paragraph Up Odstavek navzgor Paragraphs Odstavki Paragraph… Odstavek … Parentheses Okrogli oklepaji @@ -1601,16 +1691,18 @@ Paste Prilepi Paste (⌘V) Prilepi (⌘V) Paste All Prilepi vse + Paste as: Prilepi kot: Paste Clipboard Item Prilepi element odložišča Paste Formatting Prilepi oblikovanje + Paste function: Funkcija lepljenja: Paste Special Posebno lepljenje Paste Special… Posebno lepljenje … - Paste as: Prilepi kot: Paste: Keep Text Only Prilepi: ohrani samo besedilo Paste: Merge Formatting Prilepi: združi oblikovanje Pastes the clipboard's picture. Prilepi sliko iz odložišča. Pause Začasno ustavi Paused Začasno ustavljeno + PDF document (*.pdf) Dokument PDF (*.pdf) Pen Pero Pencil Svinčnik Pens Peresa @@ -1629,10 +1721,11 @@ Picture Styles Slogi slik Pictures Slike Pie Tortni + Place in This Document Mesto v tem dokumentu Placeholder text Nadomestno besedilo Plain Table 1 Navadna tabela 1 - Plain Text Content Control Kontrolnik vsebine z navadnim besedilom Plain text (*.txt) Navadno besedilo (*.txt) + Plain Text Content Control Kontrolnik vsebine z navadnim besedilom Play Predvajaj Play/Pause Predvajaj/začasno ustavi Plus Equal Plus enako @@ -1664,13 +1757,23 @@ Previous Sentence Prejšnji stavek Previous sentence Prejšnji stavek Print Natisni + Print failed: the printing worker stopped. Tiskanje ni uspelo: proces za tiskanje se je ustavil. + Print failed: {error} Tiskanje ni uspelo: {error} + Print job submitted. Tiskalni posel je poslan. + Print job submitted: {receipt} Tiskalni posel je poslan: {receipt} Print Layout Postavitev tiskanja Print Markup Natisni oznake - Print failed: {error} Tiskanje ni uspelo: {error} + Print settings Nastavitve tiskanja Print the page color Natisni barvo strani Print to PDF Natisni v PDF Print\nLayout Postavitev\ntiskanja + Printer Tiskalnik + Printer default Privzeto za tiskalnik + Printer discovery failed. Iskanje tiskalnikov ni uspelo. Printing Tiskanje + Privacy for this document Zasebnost za ta dokument + Privacy Options Možnosti zasebnosti + Privacy Settings Nastavitve zasebnosti Process Proces Product Produkt Product of A Produkt A @@ -1687,6 +1790,8 @@ Properties Lastnosti Protect Zaščiti Protect Document Zaščiti dokument + PRs PR-ji + pt pt Publisher Založnik Put the caret in a content control first. Najprej postavite kazalec v kontrolnik vsebine. Put the default commands back on the Quick Access Toolbar? Želite v orodno vrstico za hitri dostop vrniti privzete ukaze? @@ -1709,6 +1814,7 @@ Reading Column Width Širina stolpca za branje Reading Highlight Poudarek pri branju Reading Line Spacing Razmik vrstic za branje + Reading Mode Način branja Reading Speed Hitrost branja Reading Text Size Velikost besedila za branje Reads the clipboard's rich text and keeps its formatting. Prebere obogateno besedilo iz odložišča in ohrani njegovo oblikovanje. @@ -1721,17 +1827,20 @@ Recipient List Seznam prejemnikov Recipient lists Seznami prejemnikov Recipients: {count} Prejemniki: {count} - Recolor Selected Ink Prebarvaj izbrano črnilo Recolor a one-color picture, such as an icon Prebarvaj enobarvno sliko, na primer ikono + Recolor Selected Ink Prebarvaj izbrano črnilo Record Macro Posnemi makro + Recover Obnovi Recover Unsaved Documents Obnovi neshranjene dokumente Rectangle Pravokotnik Red: Rdeča: Redo Uveljavi znova Reduce Hanging Indent Zmanjšaj viseči zamik + Reference type: Vrsta sklica: References Sklici Reflection Odsev Refresh Osveži + Refresh printers Osveži tiskalnike Region: Regija: Registered Registrirano Registered Sign Znak za registrirano znamko @@ -1744,35 +1853,39 @@ Remove Odstrani Remove Added Font Odstrani dodano pisavo Remove Background Odstrani ozadje + Remove condition Odstrani pogoj Remove Content Control Odstrani kontrolnik vsebine - Remove Footer Odstrani nogo - Remove Header Odstrani glavo - Remove Hyperlink Odstrani hiperpovezavo - Remove Password Odstrani geslo - Remove Pen Odstrani pero - Remove Space After Paragraph Odstrani razmik za odstavkom - Remove Table of Contents Odstrani kazalo vsebine - Remove Watermark Odstrani vodni žig Remove every custom shortcut and restore the built-in keys? Želite odstraniti vse bližnjice po meri in obnoviti vgrajene tipke? Remove every custom tab and group and show all tabs again? Želite odstraniti vse zavihke in skupine po meri ter znova prikazati vse zavihke? Remove field Odstrani polje + Remove Footer Odstrani nogo Remove from Quick Access Toolbar Odstrani iz orodne vrstice za hitri dostop + Remove Header Odstrani glavo + Remove Hyperlink Odstrani hiperpovezavo + Remove my name from comments, changes and file properties when saving Pri shranjevanju odstrani moje ime iz komentarjev, sprememb in lastnosti datoteke + Remove Password Odstrani geslo + Remove Pen Odstrani pero Remove recipient Odstrani prejemnika Remove row Odstrani vrstico Remove series Odstrani niz + Remove Space After Paragraph Odstrani razmik za odstavkom + Remove Table of Contents Odstrani kazalo vsebine Remove the control once edited Po urejanju odstrani kontrolnik Remove the space before the punctuation Odstrani presledek pred ločilom + Remove Watermark Odstrani vodni žig + Rename Preimenuj + Rename Document Preimenuj dokument Rename Style Preimenuj slog Rename… Preimenuj … Reopen Znova odpri Repeat Ponovi - Repeat Header Rows Ponovi vrstice z glavo Repeat as header row on each page Ponovi kot vrstico z glavo na vsaki strani + Repeat Header Rows Ponovi vrstice z glavo Repeating Section Content Control Kontrolnik vsebine s ponavljajočim se odsekom Replace Zamenjaj Replace All Zamenjaj vse - Replace Document Text Zamenjaj besedilo dokumenta Replace as you type Zamenjuj med tipkanjem + Replace Document Text Zamenjaj besedilo dokumenta Replace text as you type Zamenjuj besedilo med tipkanjem Replace with: Zamenjaj z: Replace: Zamenjaj: @@ -1781,6 +1894,7 @@ Report Poročilo Research Raziskovanje Researcher Raziskovalec + Researcher needs an online service; WordCraft keeps your documents offline. Raziskovalec potrebuje spletno storitev; WordCraft dokumente hrani brez povezave. Reset Ponastavi Reset All… Ponastavi vse … Reset Character Formatting Ponastavi oblikovanje znakov @@ -1794,9 +1908,9 @@ Resize Table by Dragging Spreminjaj velikost tabele z vlečenjem Resolve Razreši Resolved Razrešeno + Restart at 1 Začni znova pri 1 Restart Each Page Začni znova na vsaki strani Restart Each Section Začni znova v vsakem odseku - Restart at 1 Začni znova pri 1 Restart list after: Začni seznam znova po: Restrict Editing Omeji urejanje Restrict\nEditing Omeji\nurejanje @@ -1822,24 +1936,24 @@ Right Arrow with Text Above Puščica desno z besedilom zgoraj Right Arrow with Text Below Puščica desno z besedilom spodaj Right Border Desna obroba + Right border Desna obroba Right Bracket Twice Dvojni desni oklepaj + Right indent Desni zamik Right Margin Desni rob Right Only Samo desno - Right border Desna obroba - Right indent Desni zamik + Right-to-left Od desne proti levi Right-to-Left Section Odsek od desne proti levi Right-to-Left Table Tabela od desne proti levi Right-to-Left Text Direction Smer besedila od desne proti levi - Right-to-left Od desne proti levi Right: Desno: Rightwards Arrow Above Puščica desno zgoraj Rightwards Harpoon Above Harpuna desno zgoraj Roots of ax²+bx+c Ničli ax²+bx+c Rotate Zasukaj - Rotate Left 90° Zasukaj levo za 90° - Rotate Right 90° Zasukaj desno za 90° Rotate all text 270° Zasukaj vse besedilo za 270° Rotate all text 90° Zasukaj vse besedilo za 90° + Rotate Left 90° Zasukaj levo za 90° + Rotate Right 90° Zasukaj desno za 90° Rotation Zasuk Rotation: Zasuk: Rounded Rectangle Zaobljen pravokotnik @@ -1853,17 +1967,17 @@ Russian Ruščina Résumé Življenjepis SAMPLE VZOREC - SIST02 SIST02 Sat: Sob.: Save Shrani - Save As Shrani kot Save a copy in another format. Printing goes through a PDF you can print from any viewer. Shranite kopijo v drugi obliki. Tiskanje poteka prek datoteke PDF, ki jo lahko natisnete iz katerega koli pregledovalnika. + Save As Shrani kot Save as Template Shrani kot predlogo Save documents Shranjevanje dokumentov Save the document to turn on AutoSave Shranite dokument, da vklopite samodejno shranjevanje Save the document to turn on AutoSave (a file you have only opened isn't rewritten until you save it) Shranite dokument, da vklopite samodejno shranjevanje (datoteka, ki ste jo samo odprli, se ne prepiše, dokler je ne shranite) Save these pages as PDF… Shrani te strani kot PDF … Saved Shranjeno + Saving {name} was cancelled, so it is still open and nothing was lost. Shranjevanje dokumenta {name} je bilo preklicano, zato je še vedno odprt in nič ni bilo izgubljeno. Scale Merilo Scale Drawing Prilagodi merilo risbe Scatter Raztreseni @@ -1873,21 +1987,23 @@ Scroll ribbon right Pomakni trak desno Search Iskanje Search Commands Iskanje ukazov - Search Options Možnosti iskanja Search commands and help Iskanje ukazov in pomoči Search document Iskanje po dokumentu Search for templates Iskanje predlog Search icons Iskanje ikon + Search Options Možnosti iskanja Secant Function Funkcija sekansa Section Break Prelom odseka Section Breaks Prelomi odsekov Section Character Znak za odsek + Section direction: Smer odseka: Section Mark Oznaka odseka Section Properties Lastnosti odseka - Section direction: Smer odseka: Sections Odseki See Glejte Select Izberi + Select a picture, shape, chart or table to describe it. Izberite sliko, obliko, grafikon ali tabelo, ki jih želite opisati. + Select a printer Izberite tiskalnik Select All Izberi vse Select Cell Izberi celico Select Column Izberi stolpec @@ -1897,17 +2013,17 @@ Select Paragraph Izberi odstavek Select Range Izberi obseg Select Recipients Izberi prejemnike + Select recipients first to choose from their fields. Najprej izberite prejemnike, da lahko izbirate med njihovimi polji. + Select recipients first to match their fields. Najprej izberite prejemnike, da lahko uskladite njihova polja. Select Row Izberi vrstico Select Sentence Izberi stavek Select Table Izberi tabelo Select Table Cells by Clicking Izbiraj celice tabele s klikom Select Text Izberi besedilo Select Text with Similar Formatting Izberi besedilo s podobnim oblikovanjem - Select Word Izberi besedo - Select a picture, shape, chart or table to describe it. Izberite sliko, obliko, grafikon ali tabelo, ki jih želite opisati. - Select recipients first to choose from their fields. Najprej izberite prejemnike, da lahko izbirate med njihovimi polji. - Select recipients first to match their fields. Najprej izberite prejemnike, da lahko uskladite njihova polja. Select text, then tick who can still edit it. Izberite besedilo in nato označite, kdo ga še lahko ureja. + select two or more cells to merge za združitev izberite dve ali več celic + Select Word Izberi besedo Select\nRecipients Izberi\nprejemnike Selected Fields Izbrana polja Selected pages Izbrane strani @@ -1925,13 +2041,14 @@ Series {n} Niz {n} Set Nastavi Set As Default Nastavi kot privzeto + Set as Default Nastavi kot privzeto Set Caret Nastavi kazalec Set Character Formatting Nastavi oblikovanje znakov Set Date Nastavi datum Set Equation Nastavi enačbo Set Numbering Value Nastavi vrednost oštevilčevanja Set Paragraph Formatting Nastavi oblikovanje odstavka - Set as Default Nastavi kot privzeto + Set Proofing Language… Nastavi jezik preverjanja… Setting: Nastavitev: Settings… Nastavitve … Shading Senčenje @@ -1948,9 +2065,20 @@ Shipping labels Nalepke za pošiljke Shortcut key: Bližnjična tipka: Show Prikaži + Show alignment guides when moving pictures and shapes Pri premikanju slik in oblik prikaži vodila za poravnavo + Show all formatting marks Prikaži vse oznake oblikovanja Show All Levels Prikaži vse ravni + Show all revisions inline Prikaži vse revizije v besedilu + Show changes at: Prikaži spremembe pri: + Show changes in: Prikaži spremembe v: Show Comments Prikaži komentarje + Show documents with their colours inverted, like View › Switch Modes. Saving, printing and PDFs are unchanged. Prikaži dokumente z obrnjenimi barvami, kot Pogled › Preklopi način. Shranjevanje, tiskanje in PDF-ji ostanejo nespremenjeni. + Show every bibliography field Prikaži vsa polja bibliografije Show First Line Only Prikaži samo prvo vrstico + Show in the Styles gallery Prikaži v galeriji slogov + Show its list number Prikaži njegovo številko na seznamu + Show its number relative to this field Prikaži njegovo številko glede na to polje + Show its number with all higher levels Prikaži njegovo številko z vsemi višjimi ravnmi Show KeyTips (Alt or F10) Prikaži namige za tipke (Alt ali F10) Show Level Prikaži raven Show Level 1 Prikaži raven 1 @@ -1964,23 +2092,16 @@ Show Level 9 Prikaži raven 9 Show Markup Prikaži oznake Show Notes Prikaži opombe - Show Text Formatting Prikaži oblikovanje besedila - Show all formatting marks Prikaži vse oznake oblikovanja - Show all revisions inline Prikaži vse revizije v besedilu - Show changes at: Prikaži spremembe pri: - Show changes in: Prikaži spremembe v: - Show documents with their colours inverted, like View › Switch Modes. Saving, printing and PDFs are unchanged. Prikaži dokumente z obrnjenimi barvami, kot Pogled › Preklopi način. Shranjevanje, tiskanje in PDF-ji ostanejo nespremenjeni. - Show every bibliography field Prikaži vsa polja bibliografije - Show in the Styles gallery Prikaži v galeriji slogov Show only comments and formatting in balloons V oblačkih prikaži samo komentarje in oblikovanje Show or Hide Style Prikaži ali skrij slog Show revisions in balloons Prikaži revizije v oblačkih Show rulers Prikaži ravnila - Show the Start screen when WordCraft starts Ob zagonu programa WordCraft prikaži začetni zaslon + Show Text Formatting Prikaži oblikovanje besedila Show the community button in the title bar Prikaži gumb skupnosti v naslovni vrstici Show the equation in the line of text Prikaži enačbo v vrstici besedila Show the equation on a line of its own Prikaži enačbo v svoji vrstici Show the mini toolbar when text is selected Ob izbiri besedila prikaži mini orodno vrstico + Show the Start screen when WordCraft starts Ob zagonu programa WordCraft prikaži začetni zaslon Show/Hide ¶ Prikaži/skrij ¶ Show\nComments Prikaži\nkomentarje Showing the first {count} items. Prikazanih je prvih {count} elementov. @@ -1998,12 +2119,13 @@ Side\nto Side Drugo ob\ndrugem Sides of a right triangle Stranice pravokotnega trikotnika Signature Line Podpisna vrstica - Sine Function Funkcija sinusa Sine and cosine series of f(x) Sinusna in kosinusna vrsta f(x) + Sine Function Funkcija sinusa Sine theta Sinus theta Single Enojno Single Brackets Enojni oklepaji Single Closing Quote Enojni zaključni narekovaj + Single label Posamezna nalepka Single Left Angle Enojni levi lomljeni oklepaj Single Left Bar Enojna leva navpičnica Single Left Brace Enojni levi zaviti oklepaj @@ -2014,7 +2136,7 @@ Single Right Bracket Enojni desni oglati oklepaj Single Right Parenthesis Enojni desni okrogli oklepaj Single Spacing Enojni razmik - Single label Posamezna nalepka + SIST02 SIST02 Site name Ime spletnega mesta Size Velikost Size (pt): Velikost (pt): @@ -2022,10 +2144,10 @@ Size: Velikost: Sketchbook Skicirka Skewed Fraction Poševni ulomek + Skip citations & bibliography Preskoči citate in bibliografijo Skip Citations and Bibliography Preskoči citate in bibliografijo Skip Record If Preskoči zapis, če Skip Record If… Preskoči zapis, če … - Skip citations & bibliography Preskoči citate in bibliografijo Slant Down Nagib navzdol Slant Up Nagib navzgor Small Caps Pomanjšane velike črke @@ -2040,9 +2162,12 @@ Soft Glow Mehak sij Solid Accent Polni poudarek Sort Razvrsti + Sort by Razvrsti po + Sort by: Razvrsti po: Sort order: Vrstni red razvrščanja: - Source Manager Upravitelj virov + Sort Recipients Razvrsti prejemnike Source code: github.com/storytold/wordcraft Izvorna koda: github.com/storytold/wordcraft + Source Manager Upravitelj virov Source type: Vrsta vira: Sources in this document: Viri v tem dokumentu: South Jug @@ -2067,8 +2192,8 @@ Spelling Črkovanje Spelling & Grammar Črkovanje in slovnica Spelling &\nGrammar Črkovanje\nin slovnica - Spelling Suggestions Predlogi za črkovanje Spelling and grammar are checked in English (United States). Črkovanje in slovnica se preverjata v angleščini (Združene države). + Spelling Suggestions Predlogi za črkovanje Split Razdeli Split Cells Razdeli celice Split Table Razdeli tabelo @@ -2087,10 +2212,10 @@ Standard Standardno Standard Colors Standardne barve Star Zvezda + Start at: Začni pri: Start Enforcing Protection Začni uveljavljati zaščito Start Mail Merge Začni spajanje dokumentov Start Mail\nMerge Začni spajanje\ndokumentov - Start at: Začni pri: Start-up Zagon State Pokrajina Statement 5.50" × 8.50" Statement 5.50" × 8.50" @@ -2111,9 +2236,10 @@ Studio cover Naslovnica Studio Studio handbook (sample) Priročnik Studio (vzorec) Style Slog - Style Inspector Pregledovalnik slogov - Style Set Nabor slogov Style based on: Slog temelji na: + Style Inspector Pregledovalnik slogov + Style name: Ime sloga: + Style Set Nabor slogov Style: Slog: Style: APA Slog: APA Style: {style} Slog: {style} @@ -2122,6 +2248,8 @@ Styles: Slogi: Subentry: Podvnos: Subject Zadeva + Submit print job Pošlji tiskalni posel + Submitting print job… Pošiljanje tiskalnega posla… Subscript Podpisano Subscript-Superscript Podpisano-nadpisano Subscripts and Superscripts Podpisani in nadpisani indeksi @@ -2129,6 +2257,7 @@ Subtitle Podnaslov Subtle Emphasis Rahel poudarek Subtle Reference Rahel sklic + Suggested Predlagano Sum Vsota Sum of cosines Vsota kosinusov Sum or difference of sines Vsota ali razlika sinusov @@ -2153,10 +2282,6 @@ Symbols Simboli Synonyms Sopomenke System Sistem - TC entry fields Polja vnosov TC - TOA Heading Naslov kazala virov - TOC Heading Naslov kazala vsebine - TOC {n} Kazalo vsebine {n} Tab Tabulator Tab Character Znak tabulatorja Tab character Znak tabulatorja @@ -2167,49 +2292,53 @@ Table Tabela Table Column Width Širina stolpca tabele Table Design Načrt tabele + Table direction: Smer tabele: Table Grid Mreža tabele Table Layout Postavitev tabele - Table Properties Lastnosti tabele - Table Properties… Lastnosti tabele … - Table Row Height Višina vrstice tabele - Table Style Options Možnosti sloga tabele - Table Styles Slogi tabel - Table direction: Smer tabele: Table of Authorities Kazalo virov Table of Contents Kazalo vsebine Table of Contents Options Možnosti kazala vsebine Table of Contents Styles Slogi kazala vsebine Table of Figures Kazalo slik Table of\nContents Kazalo\nvsebine + Table Properties Lastnosti tabele + Table Properties… Lastnosti tabele … + Table Row Height Višina vrstice tabele Table style Slog tabele + Table Style Options Možnosti sloga tabele + Table Styles Slogi tabel Tables Tabele Tabloid 11.00" × 17.00" Tabloid 11.00" × 17.00" Tabs Tabulatorji Tabs… Tabulatorji … Tabular List Tabelarični seznam Tag Oznaka - Tangent Function Funkcija tangensa + Take the last match on the page Vzemi zadnje ujemanje na strani Tangent formula Formula za tangens + Tangent Function Funkcija tangensa + TC entry fields Polja vnosov TC Technology Tehnologija Templates Predloge Templates and Add-ins Predloge in dodatki Text Besedilo Text Box Polje z besedilom + Text boxes Polja z besedilom Text Direction Smer besedila Text Effects Učinki besedila Text Effects and Typography Učinki besedila in tipografija Text Fill Polnilo besedila - Text Highlight Color Barva označevanja besedila - Text Outline Obris besedila - Text Pane Podokno z besedilom - Text Wrapping Obtekanje besedila - Text boxes Polja z besedilom Text from File Besedilo iz datoteke + Text Highlight Color Barva označevanja besedila Text indent at: Zamik besedila pri: + Text of the nearest paragraph with a given style (running headers) Besedilo najbližjega odstavka z danim slogom (tekoča glava) + Text Outline Obris besedila Text over Colon Equal Besedilo nad dvopičjem in enačajem Text over Equal Besedilo nad enačajem + Text Pane Podokno z besedilom + Text size Velikost besedila Text snaps to characters Besedilo se poravna na znake Text to display: Prikazano besedilo: + Text Wrapping Obtekanje besedila Text: Besedilo: Text\nBox Polje z\nbesedilom Text\nDirection Smer\nbesedila @@ -2220,6 +2349,8 @@ The document has no bookmarks yet. Dokument še nima zaznamkov. The document is read only, except for regions marked editable. Dokument je samo za branje, razen področij, označenih za urejanje. The document will be saved with a password. Save it to protect it. Dokument bo shranjen z geslom. Shranite ga, da ga zaščitite. + The document you are editing keeps the names and dates; only the saved file leaves them out. Dokument, ki ga urejate, ohrani imena in datume; izpusti jih samo shranjena datoteka. + The margins don't fit on a page this size; make the margins smaller first. Robovi ne sodijo na stran te velikosti; najprej pomanjšajte robove. The new style takes the formatting of the current paragraph. Nov slog prevzame oblikovanje trenutnega odstavka. The password is optional; without it anyone can stop the protection. Geslo ni obvezno; brez njega lahko zaščito ustavi kdor koli. The password is removed. Save the document to keep the change. Geslo je odstranjeno. Shranite dokument, da ohranite spremembo. @@ -2233,16 +2364,22 @@ Theme Fonts Pisave teme Theme List Seznam tem Themes Teme + Then by Nato po Then insert: Potem vstavi: + there is no ink to replay ni rokopisa za predvajanje + there's no citation after the caret za kazalcem ni citata Thesaurus Slovar sopomenk These marks show even when Show/Hide ¶ is off. Te oznake so prikazane tudi, ko je Prikaži/skrij ¶ izklopljeno. These merge fields aren't in the recipient list: Teh polj spajanja ni na seznamu prejemnikov: + These settings will be used for new documents. Te nastavitve bodo uporabljene za nove dokumente. Thick Debelo Thickness Debelina This document Ta dokument + This document has no bookmarks. Ta dokument nima zaznamkov. This field has no options. To polje nima možnosti. This folder can't be read: {error} Te mape ni mogoče prebrati: {error} This font has no characters to show. Ta pisava nima znakov za prikaz. + this item is already at the top level ta element je že na najvišji ravni This page Ta stran This password type isn't supported. Ta vrsta gesla ni podprta. This point forward Od tu naprej @@ -2257,16 +2394,22 @@ Title from the document properties Naslov iz lastnosti dokumenta Title: Naslov: To open Word documents with WordCraft by default: select one in Finder, choose File › Get Info, pick WordCraft under Open with, then click Change All. Če želite dokumente Word privzeto odpirati s programom WordCraft: enega izberite v Finderju, izberite Datoteka › Informacije, pod Odpri z izberite WordCraft in kliknite Spremeni vse. + TOA Heading Naslov kazala virov + TOC Heading Naslov kazala vsebine + TOC {n} Kazalo vsebine {n} Today Danes + tOGGLE cASE zAMENJAJ vELIKOST Toggle Check Box Preklopi potrditveno polje Toggle Field Codes Preklopi kode polj Toggle Unicode Character Preklopi znak Unicode Tools Orodja Top Zgoraj - Top Border Zgornja obroba - Top Margin Zgornji rob Top and Bottom Zgoraj in spodaj + Top Border Zgornja obroba + Top Left Cell Zgornja leva celica + Top Margin Zgornji rob Top of Page Vrh strani + Top Right Cell Zgornja desna celica Top: Zgoraj: Total Skupaj Total Row Vrstica s skupno vsoto @@ -2293,19 +2436,26 @@ Triple Integral Trojni integral Triple Integral with Limits Trojni integral z mejami Triple Integral with Stacked Limits Trojni integral z zloženimi mejami + Trust Center Središče zaupanja Tue Tor Turabian Turabian Turkish Turščina Two Dva + Two-sided printing Obojestransko tiskanje + Two-sided: long edge Obojestransko: po daljšem robu + Two-sided: short edge Obojestransko: po krajšem robu Type Vrsta - Type Text Vtipkaj besedilo - Type a New List… Vnesi nov seznam … + Type +2 or -1 to move from the page you are on. Vnesite +2 ali -1 za premik od trenutne strani. Type a command, e.g. "insert table" Vtipkajte ukaz, npr. »vstavi tabelo« + Type a New List… Vnesi nov seznam … + Type a page number, or +2 / -1. Vnesite številko strani ali +2 / -1. Type at least one recipient. Vnesite vsaj enega prejemnika. + Type Character Code Vnesite kodo znaka Type equations in LaTeX Vnašaj enačbe v LaTeX-u Type equations in the linear format (UnicodeMath) Vnašaj enačbe v linearni obliki (UnicodeMath) Type in Equation Tipkanje v enačbi Type normal (non-math) text in the equation V enačbo vnesite navadno (nematematično) besedilo + Type Text Vtipkaj besedilo Type the categories down the side and a column of values per series. Kategorije vnesite navzdol ob strani, za vsak niz pa stolpec vrednosti. Type the field's name and its switches, for example PAGE \\* roman. Vnesite ime polja in njegova stikala, na primer PAGE \\* roman. Type the password to stop the protection. Vnesite geslo, da ustavite zaščito. @@ -2313,16 +2463,13 @@ Type: Vrsta: Typing Tipkanje Typing replaces selected text Tipkanje zamenja izbrano besedilo - UPPERCASE VELIKE ČRKE - URGENT NUJNO - URL URL Ukrainian Ukrajinščina Unchecked symbol Simbol za neoznačeno polje Underbar Spodnja črta Underbrace Spodnji zaviti oklepaj Underline Podčrtano - Underline Words Only Podčrtaj samo besede Underline color Barva podčrtaja + Underline Words Only Podčrtaj samo besede Undo Razveljavi Undo 1 Action Razveljavi 1 dejanje Undo Actions Razveljavi dejanja @@ -2345,18 +2492,22 @@ Up Diagonal Dots Diagonalne pike navzgor Up one level Eno raven višje Update Field Posodobi polje + Update fields before printing Pred tiskanjem posodobi polja Update Index Posodobi stvarno kazalo Update Style to Match Selection Posodobi slog, da se ujema z izborom Update Styles from Template Posodobi sloge iz predloge Update Table Posodobi tabelo Update Table of Figures Posodobi kazalo slik - Update fields before printing Pred tiskanjem posodobi polja Update to Match Posodobi za ujemanje Update to Match Selection Posodobi, da se ujema z izborom + UPPERCASE VELIKE ČRKE + URGENT NUJNO + URL URL Use "a" before a consonant sound Uporabi »a« pred soglasnikom (angleščina) Use "an" before a vowel sound Uporabi »an« pred samoglasnikom (angleščina) Use an Existing List… Uporabi obstoječi seznam … Use system setting Uporabi sistemsko nastavitev + Use the browser's full screen (F11) Uporabi celozaslonski način brskalnika (F11) Use the Insert key to switch overtype on and off S tipko Insert vklopi in izklopi prepisovanje Use wildcards Uporabi nadomestne znake User Name Uporabniško ime @@ -2370,9 +2521,9 @@ Version {version} ({build}) Različica {version} ({build}) Vertical Navpično Vertical Alignment Navpična poravnava + Vertical alignment: Navpična poravnava: Vertical Bars Navpičnice Vertical Dots Navpične pike - Vertical alignment: Navpična poravnava: View Pogled View Alt Text… Pokaži nadomestno besedilo … View Gridlines Pokaži mrežne črte @@ -2385,6 +2536,8 @@ Volume Integral with Limits Prostorninski integral z mejami Volume Integral with Stacked Limits Prostorninski integral z zloženimi mejami W: Š: + Wait for the print job to finish submitting before closing. Pred zapiranjem počakajte, da se tiskalni posel pošlje. + Waiting for the save to finish… Čakanje na konec shranjevanja… Waiting for Zotero… Čakanje na Zotero … Warm Gradient with Outline Topel preliv z obrisom Warnings ({n}) Opozorila ({n}) @@ -2404,9 +2557,9 @@ White Space Prazen prostor White space Prazen prostor Who the document properties name as author Kdo je v lastnostih dokumenta naveden kot avtor - Whole Table Cela tabela Whole document Cel dokument Whole page Cela stran + Whole Table Cela tabela Whole words Cele besede Wide Široko Widow/Orphan Control Nadzor vdov in sirot @@ -2416,13 +2569,14 @@ Wildcards (regex) Nadomestni znaki (regex) Window Okno With: Z: + Word 2003 XML Document (*.xml) Dokument Word 2003 XML (*.xml) Word Count Število besed - Word Left Beseda levo - Word Right Beseda desno - Word XML Document (*.xml) Dokument Word XML (*.xml) Word document Dokument Word Word document (*.docx) Dokument Word (*.docx) + Word Left Beseda levo Word level Besedna raven + Word Right Beseda desno + Word XML Document (*.xml) Dokument Word XML (*.xml) WordArt WordArt WordArt Styles Slogi WordArt WordCraft WordCraft @@ -2434,6 +2588,7 @@ Wrap\nText Obtekaj\nbesedilo Write & Insert Fields Pisanje in vstavljanje polj Write a reply Napišite odgovor + x bar x s črto Year Leto Yes Da Yields Daje @@ -2447,24 +2602,20 @@ Zotero Zotero Zotero: cancelled Zotero: preklicano Zotero: done Zotero: končano - a, b, c, … a, b, c, … - development build razvojna različica - from Unicode (hex) iz Unicode (šestnajstiško) - getartcraft.com/apps/wordcraft getartcraft.com/apps/wordcraft - i, ii, iii, … i, ii, iii, … - lowercase male črke - n×n Identity Matrix Enotska matrika n×n - n×n Matrix Matrika n×n - pt pt - tOGGLE cASE zAMENJAJ vELIKOST - x bar x s črto {cols}×{rows} Table Tabela {cols}×{rows} {contributors} contributors · {commits} commits Sodelavci: {contributors} · Potrditve: {commits} {count} items highlighted Označeni elementi: {count} {count} of {total} pages will print Natisnjenih bo strani: {count} od {total} + {count} of {total} recipients will be merged. Združenih bo {count} od {total} prejemnikov. {group} settings Nastavitve: {group} {key}+Click to follow link {key}+klik za odpiranje povezave + {kind}, {columns} × {rows}, {size} ({paper}) {kind}, {columns} × {rows}, {size} ({paper}) + {language} (no dictionary) {language} (ni slovarja) {language} is now the document's default language. {language} je zdaj privzeti jezik dokumenta. + {language}: no dictionary found {language}: slovar ni bil najden + {language}: {file} {language}: {file} + {name} {height} × {width} in {name} {height} × {width} in + {name} {height} × {width} mm {name} {height} × {width} mm {name} already exists. Choose Save again to replace it. Datoteka {name} že obstaja. Znova izberite Shrani, da jo zamenjate. {name} is protected with a password. {name} je zaščiten z geslom. {name} wasn't found. {name} ni bilo mogoče najti. @@ -2473,6 +2624,7 @@ {n} pt {n} pt {selected} of {words} words Besede: {selected} od {words} {size} pt {size} pt + {width} × {height} mm {width} × {height} mm {words} words Besede: {words} “{text}” ({row}, {series}) isn't a number. »{text}« ({row}, {series}) ni število. ✓ None ✓ Brez diff --git a/crates/ui-egui/src/i18n/tests.rs b/crates/ui-egui/src/i18n/tests.rs index be9902ce86..3a4cd95577 100644 --- a/crates/ui-egui/src/i18n/tests.rs +++ b/crates/ui-egui/src/i18n/tests.rs @@ -1550,7 +1550,8 @@ fn bundled_interface_fonts_cover_vietnamese_letters() { assert_ne!(face.glyph_for(*ch), 0, "{family} {style} lacks {ch:?}"); } } - assert!(!lang("vi").uses_cjk());} + assert!(!lang("vi").uses_cjk()); +} #[test] fn slovenian_locales_and_saved_preference_work_without_changing_the_document() { @@ -1629,7 +1630,6 @@ fn bundled_interface_fonts_cover_slovenian_letters() { assert!(!lang("sl").uses_cjk()); } - #[test] fn slovenian_has_no_keys_outside_the_other_catalogs() { use std::collections::HashSet; diff --git a/docs/localization-parity.md b/docs/localization-parity.md index 91b65c302c..0b19e1a32c 100644 --- a/docs/localization-parity.md +++ b/docs/localization-parity.md @@ -1,6 +1,6 @@ # Localization parity with Microsoft Word -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** trivial (print-area strings translated in every catalog; previously trivial: Italian: the interface audit checks the Italian catalog, `Privacy Settings` wording; previously trivial: Italian grammar: `po'`, `qual è`, `È`, `un`/`un'` rules; more elisions and truncations in spelling; previously trivial: Czech interface catalog completed, 2,447 entries; previously trivial: interface audit runs in CI again: size labels translated, 20 strings in every catalog, Read Aloud error race fixed, #1128; previously trivial: Review › Language reaches every language again — Set Proofing Language… and Set As Default, #860; previously minor: Italian proofing: it-IT spelling from the installed Hunspell dictionary, accent-first suggestions, conservative grammar with Italian messages; previously minor: Portuguese proofing; previously trivial: Turkish interface catalog completed, 2,325 entries, #774; previously minor: Hungarian interface added; previously trivial: sorting and table numbers follow the text's language, #836, #837; previously minor: Polish interface; Polish proofing — spelling, grammar, hyphenation — and a proofing language per run; previously minor: Turkish interface added; previously major: first version; catalogs measured from `crates/ui-egui/src/i18n`, Word's languages from its bundle listing) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-15 · **Change:** trivial (Slovenian interface catalog: every key of every other catalog, 2,614 entries); previously trivial (print-area strings translated in every catalog; previously trivial: Italian: the interface audit checks the Italian catalog, `Privacy Settings` wording; previously trivial: Italian grammar: `po'`, `qual è`, `È`, `un`/`un'` rules; more elisions and truncations in spelling; previously trivial: Czech interface catalog completed, 2,447 entries; previously trivial: interface audit runs in CI again: size labels translated, 20 strings in every catalog, Read Aloud error race fixed, #1128; previously trivial: Review › Language reaches every language again — Set Proofing Language… and Set As Default, #860; previously minor: Italian proofing: it-IT spelling from the installed Hunspell dictionary, accent-first suggestions, conservative grammar with Italian messages; previously minor: Portuguese proofing; previously trivial: Turkish interface catalog completed, 2,325 entries, #774; previously minor: Hungarian interface added; previously trivial: sorting and table numbers follow the text's language, #836, #837; previously minor: Polish interface; Polish proofing — spelling, grammar, hyphenation — and a proofing language per run; previously minor: Turkish interface added; previously major: first version; catalogs measured from `crates/ui-egui/src/i18n`, Word's languages from its bundle listing) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 Interface languages, script support and proofing per language. How translation works (catalog format, adding a language, clean-room rule): `crates/ui-egui/src/i18n/mod.rs`. @@ -20,7 +20,7 @@ native-speaker review (human). ## What WordCraft ships (measured) - **24 interface languages**: English plus `zh-hans`, `zh-hant`, `ja`, Korean `ko` (#1216), `uk`, `es`, `pt-br`, - Serbian `sr`/`sr-latn` (#250), Estonian `et`, Turkish `tr`, Russian `ru`, Italian `it` (#594), German `de` (#574), Polish `pl`, Czech `cs`, Hungarian `hu`, French `fr`, Bulgarian `bg`, Vietnamese `vi` (#1434), Swedish `sv` (#1219), Croatian `hr` (#1271) and Slovenian `sl`. Catalogs have 2,061–2,507 entries + Serbian `sr`/`sr-latn` (#250), Estonian `et`, Turkish `tr`, Russian `ru`, Italian `it` (#594), German `de` (#574), Polish `pl`, Czech `cs`, Hungarian `hu`, French `fr`, Bulgarian `bg`, Vietnamese `vi` (#1434), Swedish `sv` (#1219), Croatian `hr` (#1271) and Slovenian `sl`. Catalogs have 2,061–2,614 entries each; Polish has every key of every other catalog, including the built-in style names and the equation gallery, except the citation style names only Hungarian, Bulgarian, Swedish and Slovenian translate so far (a test checks it). A test (`every_tab_and_command_is_translated`) enforces that every ribbon tab, command label and ribbon location is translated in every language. `pl`, `pl-PL` and `pl_PL.UTF-8` system locales pick Polish automatically. @@ -77,7 +77,7 @@ Bulgarian (`bg-BG` system locales, `{"value":"bg"}`) has 2,419 entries: every ke catalog, the equation gallery's labels and everything the interface audit finds (the audit checks Bulgarian as it does Russian). Counts use count-neutral wording (`Думи: 21`). Bulgarian text is hyphenated by the Bulgarian syllable rules (#826); Bulgarian spelling and grammar are not included. -Slovenian (`sl-SI` system locales, `{"value":"sl"}`) has 2,463 entries: every key of every other +Slovenian (`sl-SI` system locales, `{"value":"sl"}`) has 2,614 entries: every key of every other catalog. Counts use count-neutral wording (`Besede: 21`); terminology: [`localization-sl.md`](localization-sl.md). Slovenian proofing is not included. Hungarian follows `hu-HU` system locales and `{"value":"hu"}`, with a complete catalog of 2,419 entries: every key of @@ -221,7 +221,7 @@ WordCraft keeps 2,54 cm). | 2026-10-11 | minor | Font list shows installed fonts by their names in the interface language (宋体 for SimSun in Chinese, Japanese and Korean names likewise) and finds them by either name; a Chinese, Japanese or Korean font is stored as `w:eastAsia` too (#505) | | 2026-10-11 | minor | Chinese/Japanese interface fonts: a test checks every catalog character has a glyph; an installed CJK font follows the embedded faces whenever they miss one; macOS font asset folders searched for PingFang (#486) | | 2026-10-10 | minor | Russian interface (#300; 1,524 entries, built-in style names shown translated; covers everything the interface audit finds) | -| 2026-10-12 | minor | Slovenian interface catalog (2,463 entries), locale selection, saved preference, audit coverage and font checks | +| 2026-10-15 | trivial | Slovenian interface catalog (2,614 entries: every key of every other catalog), locale selection, saved preference and font checks | | 2026-10-12 | minor | Bulgarian interface catalog (2,419 entries), locale selection, saved preference, audit coverage and font checks | | 2026-10-13 | minor | Croatian interface catalog (2,507 entries, built-in style names shown translated), `hr-*` system locales, saved preference and font coverage checks; the template name "Letter" has its own `template` context so the paper size keeps its name; no Croatian proofing | | 2026-10-11 | minor | Hungarian interface catalog (2,419 entries, built-in style names shown translated; covers everything the interface audit finds), locale selection, saved preference and font coverage checks | diff --git a/docs/localization-sl.md b/docs/localization-sl.md index ff64910ca7..6cf6466b6d 100644 --- a/docs/localization-sl.md +++ b/docs/localization-sl.md @@ -1,6 +1,6 @@ # Slovenian interface -> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-12 · **Change:** minor (Slovenian interface catalog and regression checks) · **Target:** WordCraft 0.5.0 +> **Last reviewed:** 2026-10-15 · **Last updated:** 2026-10-15 · **Change:** trivial (catalog completed to every key of every other catalog, 2,614 entries) · **Target:** WordCraft 0.7.0 Choose **File › Options › Interface language › Slovenščina**, or follow a Slovenian system display language (`sl-SI`, `sl_SI.UTF-8`). The catalog code is `sl`; `ui.language` with `{"value":"sl"}` @@ -8,9 +8,9 @@ selects it explicitly, and `WORDCRAFT_LANGUAGE=sl-SI` does the same for a headle launch while the preference is `auto`. Document text, command ids, CLI/MCP/control responses and file paths do not change. -`crates/ui-egui/src/i18n/sl.tsv` has 2,463 entries: every key of every other catalog (built-in -style names, equation gallery labels, paper sizes, watermarks, greetings and citation style names -included). It is written from the meaning of the English labels; no proprietary translation +`crates/ui-egui/src/i18n/sl.tsv` has 2,614 entries: every key of every other catalog (built-in +style names, equation gallery labels, paper sizes, watermarks, greetings, citation style names and +native print settings included). It is written from the meaning of the English labels; no proprietary translation resources were consulted. ## Conventions @@ -48,11 +48,16 @@ resources were consulted. | Hyphenation / Proofing | Deljenje besed / Preverjanje | | Character Spacing / Tracking | Razmik med znaki | | Watermark | Vodni žig | +| Grayscale | Odtenki sive | +| Printer / Print | Tiskalnik / Natisni | +| Reading Mode | Način branja | +| Full Screen | Celozaslonski način | +| Recover | Obnovi | | Text box / Content control | Polje z besedilom / Kontrolnik vsebine | ## Verification and remaining scope Tests in `crates/ui-egui/src/i18n/tests.rs` check locale selection, the saved preference, that the -catalog covers every key of every other catalog, key terms, the search hint example and the bundled -fonts' coverage of č, š, ž. Slovenian spelling, grammar and hyphenation are not included. Native -speaker review is welcome. +catalog covers every key of every other catalog and has no keys outside them, key terms, the search +hint example and the bundled fonts' coverage of č, š, ž. Slovenian spelling, grammar and hyphenation +are not included. Native speaker review is welcome. From 68479ee33f4b7be110d373b5629e32f95c312b3a Mon Sep 17 00:00:00 2001 From: Divyanshu Gupta Date: Thu, 15 Oct 2026 23:38:07 +0530 Subject: [PATCH 036/336] fix(formats): support column break in RTF import and export --- crates/formats/src/rtf.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/formats/src/rtf.rs b/crates/formats/src/rtf.rs index c8b002a205..4ad5796cd5 100644 --- a/crates/formats/src/rtf.rs +++ b/crates/formats/src/rtf.rs @@ -42,6 +42,7 @@ pub fn esc(s: &str) -> String { '\t' => o.push_str("\\tab "), '\n' => o.push_str("\\line "), '\u{000C}' => o.push_str("\\page "), + '\u{000E}' => o.push_str("\\column "), '\u{00A0}' => o.push_str("\\~"), '\u{00AD}' => o.push_str("\\-"), '\u{2011}' => o.push_str("\\_"), @@ -1596,6 +1597,7 @@ impl Reader { "line" => self.ch('\n'), "tab" => self.ch('\t'), "page" => self.ch('\u{000C}'), + "column" => self.ch('\u{000E}'), "emdash" => self.ch('—'), "endash" => self.ch('–'), "bullet" => self.ch('•'), @@ -2684,5 +2686,12 @@ mod tests { #[test] fn escapes() { assert_eq!(esc("a{b}\\c\té😀"), "a\\{b\\}\\\\c\\tab \\u233?\\u-10179?\\u-8704?"); + assert_eq!(esc("col1\u{000E}col2"), "col1\\column col2"); + } + + #[test] + fn column_break_import() { + let f = parse(br"{\rtf1\ansi \pard a\column b\par}").unwrap(); + assert_eq!(texts(&f), ["Normal:a\u{000E}b"]); } } From 9e477da0eefa3d52aaf38156f7bfbb50830028da Mon Sep 17 00:00:00 2001 From: Divyanshu Gupta Date: Thu, 15 Oct 2026 23:45:12 +0530 Subject: [PATCH 037/336] fix(formats): support small and big tags and relative/large font-size keywords in HTML --- crates/formats/src/html.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/crates/formats/src/html.rs b/crates/formats/src/html.rs index 7f29a9bf29..5e8e741c5e 100644 --- a/crates/formats/src/html.rs +++ b/crates/formats/src/html.rs @@ -496,11 +496,12 @@ fn css_len_pt(v: &str) -> Option { match v.as_str() { "xx-small" => Some(7.0), "x-small" => Some(7.5), - "small" => Some(10.0), + "small" | "smaller" => Some(10.0), "medium" => Some(12.0), - "large" => Some(13.5), + "large" | "larger" => Some(13.5), "x-large" => Some(18.0), "xx-large" => Some(24.0), + "xxx-large" => Some(36.0), _ => num(&v).map(|p| p * 0.75), } } @@ -1011,6 +1012,8 @@ impl<'r> Builder<'r> { "code" | "kbd" | "tt" | "samp" => fmt.code = true, "sup" => fmt.sup = true, "sub" => fmt.sub = true, + "small" => fmt.size = Some(fmt.size.map_or(10.0, |s| (s * 0.833).max(1.0))), + "big" => fmt.size = Some(fmt.size.map_or(14.0, |s| (s * 1.2).min(1638.0))), "mark" => fmt.background = Some(wordcraft_doc::Rgb(255, 255, 0)), "font" => { if let Some(c) = attr(attrs, "color").and_then(parse_color) { @@ -1785,4 +1788,18 @@ mod tests { fn entities() { assert_eq!(unescape("a <b> AB &bogus; &"), "a AB &bogus; &"); } + + #[test] + fn small_big_and_css_font_sizes() { + let f = parse("

small big huge tiny

"); + let FBlock::Para(p) = &f.blocks[0] else { panic!() }; + let sz = |i: usize| match &p.inlines[i] { + Inline::Text(_, f) => f.size, + _ => None, + }; + assert_eq!(sz(0), Some(10.0)); + assert_eq!(sz(2), Some(14.0)); + assert_eq!(sz(4), Some(36.0)); + assert_eq!(sz(6), Some(10.0)); + } } From 62bb20187dd8b0e8b89ea4b49e4c323a7a0c5e00 Mon Sep 17 00:00:00 2001 From: Mex <44814932+MMexicann@users.noreply.github.com> Date: Thu, 15 Oct 2026 21:48:27 +0200 Subject: [PATCH 038/336] review (#1690): reject malformed explicit comment IDs --- ROADMAP.md | 4 + crates/engine/src/cmd/review.rs | 22 ++++- crates/engine/tests/comment_ids.rs | 136 +++++++++++++++++++++++++++++ docs/file-format-parity.md | 5 ++ docs/gaps.md | 5 ++ 5 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 crates/engine/tests/comment_ids.rs diff --git a/ROADMAP.md b/ROADMAP.md index 40407d18b6..67acfc8133 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -12,6 +12,8 @@ agent control. This page is the summary; the assessment is [`docs/gaps.md`](docs/gaps.md), and milestones and the current focus are in [`docs/roadmap.md`](docs/roadmap.md). +> **Comment command update:** 2026-10-14 · malformed explicit IDs rejected; synthetic refusal, undo/redo and DOCX save/reopen checks (#1690 finding 3). Readiness estimates unchanged. + ## Headline numbers (2026-10-10) | Number | Value | Kind | @@ -160,6 +162,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | 2026-10-12 | Password-protected Word 97-2003 `.doc` files open after a password prompt (XOR obfuscation, RC4, RC4 CryptoAPI; wrong passwords are rejected by the stored verifier, #1079); written from the public specs and tested on synthetic files only, AES-encrypted `.doc` files are still refused | | 2026-10-12 | Comments can show only for the selected text (#1055): Track Changes Options › Comments › "Only for the selected text" drops the margin column, and the comment under the caret, or the clicked one, floats beside the page (over its edge in a narrow window). The default stays "In the margin", as in Word; `review.trackingOptions {"commentDisplay"}` for agents | | 2026-10-12 | Clicking inside a selected comment's balloon moves the caret in its text (#1013); the click used to leave the balloon, so the caret could only be moved with the arrow keys. A press on any balloon now belongs to the balloon, not the page behind it | +| 2026-10-14 | Comment commands refuse explicit fractional, negative or wrong-type IDs (#1690, finding 3). Malformed IDs no longer truncate floats or fall back to the caret; omitted IDs still use the caret for Delete and Resolve. Refused commands keep the document, selection and undo/redo history; valid operations have synthetic repeated DOCX save/reopen coverage | | 2026-10-12 | Page background pictures and textures (Design › Page Color › Fill Effects) are drawn (tiled or stretched) and kept on save instead of being dropped from the package (#999); PDF export paints them too | | 2026-10-12 | Holding a click on the ribbon or toolbar no longer scrolls the document up (#1019): a double-click in the header or footer area left a text drag running, so the next press anywhere drove the edge auto-scroll; a drag now ends with the button, wherever it's released | | 2026-10-12 | Tracked insertions and deletions inside a field result (a `=SUM(ABOVE)` subtotal) are kept as revisions: Accept All and Reject All act on them and a save writes the `w:del` back (#1037) | @@ -425,6 +428,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | 2026-10-13 | trivial | Progress log: Insert › Bookmark deletes and goes to bookmarks (#1236) | | 2026-10-12 | trivial | Progress log: an option to show only the selected text's comment (#1055) | | 2026-10-12 | trivial | Progress log: clicking in a comment's text moves its caret (#1013) | +| 2026-10-14 | trivial | Comment commands reject malformed explicit IDs instead of truncating floats or targeting the caret (#1690, finding 3); synthetic command, undo/redo and DOCX save/reopen coverage; readiness estimates unchanged | | 2026-10-12 | trivial | Progress log: a press on the ribbon no longer scrolls the document (#1019) | | 2026-10-12 | trivial | Progress log: Ctrl+drag copies an object (#1014) | | 2026-10-12 | trivial | Document title opens Rename Document; the account badge opens Options › General for user name and initials. | diff --git a/crates/engine/src/cmd/review.rs b/crates/engine/src/cmd/review.rs index 43041df602..c2834e03ef 100644 --- a/crates/engine/src/cmd/review.rs +++ b/crates/engine/src/cmd/review.rs @@ -12,15 +12,17 @@ use crate::{CmdError, CmdResult, CommandSpec, Selection, Session, p}; pub fn specs() -> Vec { vec![ CommandSpec::new("review.newComment", "New Comment", "Review › Comments", new_comment).key(super::mac_or("Mod+Alt+A / Mod+Alt+M", "Mod+Alt+M")).params(r#"{"text": string}"#), - CommandSpec::new("review.reply", "Reply", "Review › Comments", reply).params(r#"{"id": n, "text": string}"#), - CommandSpec::new("review.editComment", "Edit Comment", "Review › Comments", edit_comment).params(r#"{"id": n, "text": string}"#), - CommandSpec::new("review.deleteComment", "Delete", "Review › Comments", delete_comment).params(r#"{"id"?: n, "all"?: bool}"#), + CommandSpec::new("review.reply", "Reply", "Review › Comments", reply).params(r#"{"id": non-negative JSON integer, "text": string}"#), + CommandSpec::new("review.editComment", "Edit Comment", "Review › Comments", edit_comment).params(r#"{"id": non-negative JSON integer, "text": string}"#), + CommandSpec::new("review.deleteComment", "Delete", "Review › Comments", delete_comment).params(r#"{"id"?: non-negative JSON integer (omit to use the caret), "all"?: bool}"#), CommandSpec::new("review.resolveComment", "Resolve", "Review › Comments", |s, v| { + validate_comment_id(v)?; let id = p::u64(v, "id").map(|x| x as u32).or_else(|| comment_at_caret(s)).ok_or_else(|| CmdError::Params("`id` required".into()))?; let c = s.doc.comments.get_mut(&id).ok_or_else(|| CmdError::Params("no such comment".into()))?; c.resolved = p::bool(v, "value").unwrap_or(!c.resolved); sel_result(s) - }), + }) + .params(r#"{"id"?: non-negative JSON integer (omit to use the caret), "value"?: bool (omit to toggle)}"#), CommandSpec::new("review.nextComment", "Next", "Review › Comments", |s, _| nav_comment(s, 1)).pure(), CommandSpec::new("review.previousComment", "Previous", "Review › Comments", |s, _| nav_comment(s, -1)).pure(), CommandSpec::new("review.comments", "Show Comments", "Review › Comments", list_comments).pure(), @@ -144,7 +146,17 @@ fn new_comment(s: &mut Session, v: &Value) -> CmdResult { Ok(json!({"id": id, "story": part})) } +/// An omitted ID permits caret targeting; an explicit malformed value must not. +/// Unlike the general numeric parameter helper, comment IDs never coerce floats. +fn validate_comment_id(v: &Value) -> Result<(), CmdError> { + if v.get("id").is_some_and(|id| id.as_u64().is_none()) { + return Err(CmdError::Params("`id` must be a non-negative JSON integer".into())); + } + Ok(()) +} + fn reply(s: &mut Session, v: &Value) -> CmdResult { + validate_comment_id(v)?; let parent = p::u64(v, "id").map(|x| x as u32).ok_or_else(|| CmdError::Params("`id` required".into()))?; if !s.doc.comments.contains_key(&parent) { return Err(CmdError::Params("no such comment".into())); @@ -168,6 +180,7 @@ fn comment_id(s: &Session) -> Result { /// Replace a comment's (or reply's) text; each line becomes a paragraph. fn edit_comment(s: &mut Session, v: &Value) -> CmdResult { + validate_comment_id(v)?; let id = p::u64(v, "id").and_then(|x| u32::try_from(x).ok()).ok_or_else(|| CmdError::Params("`id` required".into()))?; let text = p::req_str(v, "text")?; let part = s.doc.comments.get(&id).map(|c| c.part).ok_or_else(|| CmdError::Params("no such comment".into()))?; @@ -258,6 +271,7 @@ fn thread_of(s: &Session, id: u32) -> Vec { } fn delete_comment(s: &mut Session, v: &Value) -> CmdResult { + validate_comment_id(v)?; let ids: Vec = if p::bool(v, "all").unwrap_or(false) { s.doc.comments.keys().copied().collect() } else { diff --git a/crates/engine/tests/comment_ids.rs b/crates/engine/tests/comment_ids.rs new file mode 100644 index 0000000000..495cc552b1 --- /dev/null +++ b/crates/engine/tests/comment_ids.rs @@ -0,0 +1,136 @@ +//! #1690, finding 3: malformed command IDs must never target another comment. +use serde_json::{Value, json}; +use wordcraft_doc::{Document, StoryRef}; +use wordcraft_engine::{CmdError, Session}; + +fn commented() -> Session { + let mut s = Session::new(Document::new()); + s.run("text.insert", &json!({"text": "Keep this text"})).unwrap(); + s.run("select.text", &json!({"text": "this"})).unwrap(); + assert_eq!(s.run("review.newComment", &json!({"text": "Keep this comment"})).unwrap()["id"], 0); + s.run("select.text", &json!({"text": "this"})).unwrap(); + s.run("select.collapse", &json!({})).unwrap(); + s +} + +fn invalid_ids_leave_state_untouched(command: &str) { + for id in [json!(-1), json!(0.5), json!(0.0), json!("0"), Value::Null, json!(false)] { + for at_comment in [true, false] { + let mut s = commented(); + if !at_comment { + s.run("caret.docEnd", &json!({})).unwrap(); + s.run("text.newParagraph", &json!({})).unwrap(); + } + // Keep a redo step too: a refused command must not clear it. + s.run("format.bold", &json!({})).unwrap(); + s.run("edit.undo", &json!({})).unwrap(); + assert!(s.can_redo()); + let before = (s.doc.clone(), s.sel.clone(), s.undo_labels(), s.redo_label().map(str::to_owned), s.dirty); + let result = s.run(command, &json!({"id": id, "text": "Replacement"})); + assert!(matches!(result, Err(CmdError::Params(_))), "{command}, id={id}, at_comment={at_comment}: {result:?}"); + assert_eq!( + (s.doc.clone(), s.sel.clone(), s.undo_labels(), s.redo_label().map(str::to_owned), s.dirty), + before, + "{command}, id={id}, at_comment={at_comment}" + ); + let back = wordcraft_docx::read(&wordcraft_docx::write(&s.doc).unwrap()).unwrap(); + assert_eq!(back.comments.len(), 1); + let comment = back.comments.get(&0).unwrap(); + assert!(!comment.resolved); + assert_eq!(back.plain_text(wordcraft_doc::StoryRef::Part(comment.part)), "Keep this comment"); + } + } +} + +#[test] +fn delete_comment_rejects_invalid_ids() { + invalid_ids_leave_state_untouched("review.deleteComment"); +} + +#[test] +fn resolve_comment_rejects_invalid_ids() { + invalid_ids_leave_state_untouched("review.resolveComment"); +} + +#[test] +fn reply_rejects_invalid_comment_ids() { + invalid_ids_leave_state_untouched("review.reply"); +} + +#[test] +fn edit_comment_rejects_invalid_ids() { + invalid_ids_leave_state_untouched("review.editComment"); +} + +#[test] +fn delete_all_rejects_an_explicit_invalid_id() { + let mut s = commented(); + let before = s.doc.clone(); + assert!(matches!(s.run("review.deleteComment", &json!({"all": true, "id": null})), Err(CmdError::Params(_)))); + assert_eq!(s.doc, before); + for params in [json!({"all": true}), json!({"all": true, "id": 0})] { + let mut s = commented(); + let before = s.doc.clone(); + s.run("review.deleteComment", ¶ms).unwrap(); + assert!(s.doc.comments.is_empty()); + s.run("edit.undo", &json!({})).unwrap(); + assert_eq!(s.doc, before); + } +} + +#[test] +fn largest_comment_id_is_accepted_without_a_caret_fallback() { + let mut s = commented(); + let comment = s.doc.comments.remove(&0).unwrap(); + s.doc.comments.insert(u32::MAX, comment); + for object in &mut s.doc.para_mut(StoryRef::Body, &wordcraft_doc::Path::top(0)).unwrap().objects { + if let wordcraft_doc::para::InlineObject::CommentStart { id } | wordcraft_doc::para::InlineObject::CommentEnd { id } = object { + *id = u32::MAX; + } + } + s.run("caret.docEnd", &json!({})).unwrap(); + s.run("text.newParagraph", &json!({})).unwrap(); + s.run("review.resolveComment", &json!({"id": u32::MAX, "value": true})).unwrap(); + assert!(s.doc.comments.get(&u32::MAX).unwrap().resolved); + s.run("review.editComment", &json!({"id": u32::MAX, "text": "Still here"})).unwrap(); + s.run("review.deleteComment", &json!({"id": u32::MAX})).unwrap(); + assert!(s.doc.comments.is_empty()); + s.run("edit.undo", &json!({})).unwrap(); + assert!(s.doc.comments.contains_key(&u32::MAX)); +} + +#[test] +fn valid_comment_ids_and_omitted_caret_ids_keep_undo_redo_and_docx_fidelity() { + for command in ["review.resolveComment", "review.deleteComment", "review.reply", "review.editComment"] { + let params = json!({"id": 0, "text": "Changed comment"}); + let mut variants = vec![params]; + if matches!(command, "review.resolveComment" | "review.deleteComment") { + variants.push(json!({})); + } + for params in variants { + let mut s = commented(); + let before = s.doc.clone(); + let depth = s.undo_depth(); + s.run(command, ¶ms).unwrap(); + let after = s.doc.clone(); + assert_ne!(after, before); + assert_eq!(s.undo_depth(), depth + 1); + s.run("edit.undo", &json!({})).unwrap(); + assert_eq!(s.doc, before); + s.run("edit.redo", &json!({})).unwrap(); + assert_eq!(s.doc, after); + let comments = |doc: &Document| { + doc.comments + .values() + .map(|c| (c.author.clone(), c.initials.clone(), c.date.clone(), c.resolved, c.parent, doc.plain_text(StoryRef::Part(c.part)))) + .collect::>() + }; + let mut back = after.clone(); + for _ in 0..2 { + back = wordcraft_docx::read(&wordcraft_docx::write(&back).unwrap()).unwrap(); + assert_eq!(comments(&back), comments(&after), "{command}"); + assert_eq!(back.plain_text(StoryRef::Body), after.plain_text(StoryRef::Body), "{command}"); + } + } + } +} diff --git a/docs/file-format-parity.md b/docs/file-format-parity.md index 38a1638f16..ec7a657d27 100644 --- a/docs/file-format-parity.md +++ b/docs/file-format-parity.md @@ -12,6 +12,10 @@ gives ~64%; we discount it to ~60% because DOCX fidelity has never been checked files. DOCX dominates the weight: a Word user sends and receives .docx; everything else is occasional. +> **Comment command update:** 2026-10-14 · malformed explicit IDs rejected; synthetic refusal, undo/redo and DOCX save/reopen checks (#1690 finding 3). Readiness estimates unchanged. + +Comment commands reject explicit negative, floating-point and wrong-type IDs before editing or using the caret. Valid Delete, Resolve, Reply and Edit operations, including omitted caret IDs for Delete/Resolve, have undo/redo and two synthetic DOCX save/reopen checks preserving body and comment data (`crates/engine/tests/comment_ids.rs`). This does not extend the modern-comment format coverage listed below. + ## Legacy DOC/DOT document properties The binary Word reader imports SummaryInformation text properties: title, subject, author, @@ -224,6 +228,7 @@ attributes inside stories and style or list definitions; a regenerated theme. Te | 2026-10-13 | trivial | DOCX: the document's watermark is written as a VML text shape in every header, adding a header where pages had none (#1290) | | 2026-10-13 | trivial | DOCX: Word's VML text watermark in headers is read as the document's watermark (#1290) | | 2026-10-13 | trivial | Rounded rectangles keep their own corner radius: `roundRect` `adj` read, written back and drawn (#1476) | +| 2026-10-14 | trivial | Comment commands reject malformed explicit IDs instead of truncating floats or targeting the caret (#1690, finding 3); synthetic command, undo/redo and DOCX save/reopen coverage; readiness estimates unchanged | | 2026-10-13 | trivial | Replace All checks content-control locks before editing; a refused batch leaves the document and undo history unchanged; existing deletion-lock rules reused, with tracked/untracked regression coverage | | 2026-10-14 | trivial | VML preset shapes (`o:spt`, `_x0000_tNN` shapetypes) drawn like the DrawingML preset of the same shape (the nearest shape when WordCraft has none) and kept on save, e.g. Word's right arrow `_x0000_t13` (#1377) | | 2026-10-14 | trivial | RTF import (#1653): Mac OS Roman (code page 10000) for `\mac` documents, `\fcharset77` fonts and `\cpg`/`\ansicpg` 10000, and DOS text: `\pc` (437), `\pca` (850), the OEM pages through `\cpg` and `\fcharset254` (was read as Windows-1252); a code page number out of range is ignored instead of truncated to another; East Asian double-byte code pages still to do | diff --git a/docs/gaps.md b/docs/gaps.md index 3d1d7fdafd..ebe0230548 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -20,6 +20,10 @@ establish cross-platform reliability. Document renaming: desktop renaming requires a filesystem that supports hard links (exFAT/FAT drives and some network shares refuse it). Behaviour: [UI parity](ui-parity.md). +> **Comment command update:** 2026-10-14 · malformed explicit IDs rejected; synthetic refusal, undo/redo and DOCX save/reopen checks (#1690 finding 3). Readiness estimates unchanged. + +Comment command safety (#1690, finding 3): Delete, Resolve, Reply and Edit reject explicit fractional, negative or wrong-type IDs before editing, rather than truncating floats or using the caret. Synthetic tests cover refusal without document or history changes, valid operations with undo/redo and repeated DOCX saves. This narrows the data-path risks in gap #3; the other audit findings and broader stability gate are outside this change; oversized integer conversion is being addressed in open PR #1787. + ## Ranked | # | Gap | B | Evidence | Impact | Hours | Doc | @@ -103,6 +107,7 @@ The same gaps grouped the way the parity documents are, for agents working in on | 2026-10-13 | trivial | Row 1: Word's text watermark in DOCX headers is drawn (#1290) | | 2026-10-12 | trivial | Re-audited every row against main: #9 narrowed to encrypted `.doc` (#281), #15 sources saved in DOCX (#790), #10 group landed (#267), #11 French landed (#729), #12 Draw 10/11, #16 #215 closed, #17 `para.asianTypography` live, #3 closed reports and 197 open issues, #8/#19/#21/#25/#27 open PRs named, evidence counts refreshed | | 2026-10-12 | trivial | Document title opens Rename Document; the account badge opens Options › General for user name and initials. | +| 2026-10-14 | trivial | Comment commands reject malformed explicit IDs instead of truncating floats or targeting the caret (#1690, finding 3); synthetic command, undo/redo and DOCX save/reopen coverage; readiness estimates unchanged | | 2026-10-13 | trivial | Replace All checks content-control locks before editing; a refused batch leaves the document and undo history unchanged; existing deletion-lock rules reused, with tracked/untracked regression coverage | | 2026-10-13 | trivial | #18: Windows Magnifier follows the caret onto a new line after Enter or Shift+Enter (#1119); real screen-reader testing remains | | 2026-10-12 | trivial | Sample-text formulas on Enter (#1067) implemented; numerical limits not compared in Word | From a4e2cebc07a728ba48c72f3a4cbc3685a1dabe54 Mon Sep 17 00:00:00 2001 From: Divyanshu Gupta Date: Fri, 16 Oct 2026 02:05:49 +0530 Subject: [PATCH 039/336] fix(formats): support borderless and bordered tables in LaTeX import and export --- crates/formats/src/latex.rs | 97 ++++++++++++++++++++++++++++++++++--- 1 file changed, 91 insertions(+), 6 deletions(-) diff --git a/crates/formats/src/latex.rs b/crates/formats/src/latex.rs index c7c555b85e..d1853d1ff8 100644 --- a/crates/formats/src/latex.rs +++ b/crates/formats/src/latex.rs @@ -339,8 +339,17 @@ impl Writer { vec![LINE_WIDTH / cols as f32; cols] }; let col = |w: f32| format!("p{{{}pt}}", num((w - 2.0 * TABCOLSEP).max(12.0))); - let spec: String = widths.iter().map(|w| format!("{}|", col(*w))).collect(); - let mut o = format!("\\noindent\\begin{{tabular}}{{|{spec}}}\n\\hline\n"); + let spec: String = if t.borderless { + widths.iter().map(|w| col(*w)).collect::>().join(" ") + } else { + let s: String = widths.iter().map(|w| format!("{}|", col(*w))).collect(); + format!("|{s}") + }; + let mut o = if t.borderless { + format!("\\noindent\\begin{{tabular}}{{{spec}}}\n") + } else { + format!("\\noindent\\begin{{tabular}}{{{spec}}}\n\\hline\n") + }; for row in &t.rows { let mut cells = Vec::new(); let mut g = 0usize; @@ -357,8 +366,13 @@ impl Writer { body = format!("\\cellcolor[HTML]{{{}}}{body}", hex(sh)); } if span > 1 { - let left = if g == 0 { "|" } else { "" }; - body = format!("\\multicolumn{{{span}}}{{{left}{}|}}{{{body}}}", col(w)); + let col_spec = if t.borderless { + col(w) + } else { + let left = if g == 0 { "|" } else { "" }; + format!("{left}{}|", col(w)) + }; + body = format!("\\multicolumn{{{span}}}{{{col_spec}}}{{{body}}}"); } cells.push(body); g += span; @@ -367,7 +381,11 @@ impl Writer { continue; } o.push_str(&cells.join(" & ")); - o.push_str(" \\\\\n\\hline\n"); + if t.borderless { + o.push_str(" \\\\\n"); + } else { + o.push_str(" \\\\\n\\hline\n"); + } } o.push_str("\\end{tabular}\n"); self.out.push_str(&o); @@ -2578,6 +2596,7 @@ fn source_text(toks: &[Tok]) -> String { /// A `tabular` body as a table. fn table(toks: &[Tok], spec: &str, depth: usize) -> FTable { let (spec_cols, widths) = spec_widths(spec); + let mut has_rules = spec.contains('|'); // Split rows at `\\` and cells at `&`, outside braces and nested environments. let mut rows: Vec> = Vec::new(); let mut row: Vec<&[Tok]> = Vec::new(); @@ -2590,6 +2609,9 @@ fn table(toks: &[Tok], spec: &str, depth: usize) -> FTable { Tok::Close => brace = brace.saturating_sub(1), Tok::Begin(_) => env += 1, Tok::End(_) => env = env.saturating_sub(1), + Tok::Cmd(c) if brace == 0 && env == 0 && matches!(c.as_str(), "hline" | "cline" | "toprule" | "midrule" | "bottomrule") => { + has_rules = true; + } Tok::Amp if brace == 0 && env == 0 => { row.push(toks.get(start..i).unwrap_or_default()); start = i + 1; @@ -2611,7 +2633,7 @@ fn table(toks: &[Tok], spec: &str, depth: usize) -> FTable { if !row.is_empty() { rows.push(row); } - let mut out = FTable { rows: Vec::new(), widths: Vec::new(), borderless: false }; + let mut out = FTable { rows: Vec::new(), widths: Vec::new(), borderless: !has_rules }; // Rows still covered by a `\multirow` above, per grid column. let mut covering: Vec = Vec::new(); for r in rows { @@ -3075,4 +3097,67 @@ Caf\'e na\"ive \c{c}a \v{s}\'{e} and {\bfseries grouped bold} after. assert_eq!(bt.widths, vec![100.0, 200.0]); assert_eq!(bt.rows[1][0].colspan, 2); } + + #[test] + fn exported_borderless_tables_omit_rules() { + let mut t = FTable { rows: Vec::new(), widths: vec![100.0, 200.0], borderless: true }; + let cell = |s: &str| Cell { + blocks: vec![FBlock::Para({ + let mut p = Para::default(); + p.push_text(s, &Fmt::default()); + p + })], + ..Default::default() + }; + t.rows.push(vec![cell("a"), cell("b")]); + t.rows.push(vec![Cell { colspan: 2, ..cell("wide") }]); + let mut w = Writer::default(); + w.table(&t); + assert!(w.out.contains("\\begin{tabular}{p{88pt} p{188pt}}"), "{}", w.out); + assert!(!w.out.contains("\\hline"), "{}", w.out); + assert!(w.out.contains("\\multicolumn{2}{p{288pt}}{wide}"), "{}", w.out); + let back = parse(&w.out); + let FBlock::Table(bt) = &back.blocks[0] else { panic!() }; + assert!(bt.borderless, "parsed table should be borderless"); + assert_eq!(bt.widths, vec![100.0, 200.0]); + assert_eq!(bt.rows[1][0].colspan, 2); + } + + #[test] + fn table_borders_and_borderless_round_trip() { + use std::sync::Arc; + use wordcraft_doc::{Block, CharProps, Paragraph, Table, para_block}; + + // 1. Borderless table. + let mut doc = Document::new(); + let mut t_borderless = Table::new(2, 2, 400.0); + t_borderless.props.style = None; + for r in 0..2 { + for c in 0..2 { + t_borderless.rows[r].cells[c].blocks = vec![para_block(Paragraph::with_text("cell", CharProps::default()))]; + } + } + doc.body = vec![Arc::new(Block::Table(t_borderless))]; + let tex = export(&doc); + assert!(!tex.contains("\\hline")); + let back = import(tex.as_bytes()); + let Some(Block::Table(bt)) = back.body.first().map(|b| &**b) else { panic!() }; + assert!(bt.props.style.is_none(), "imported table should remain borderless"); + + // 2. Bordered table. + let mut doc_bordered = Document::new(); + let mut t_bordered = Table::new(2, 2, 400.0); + t_bordered.props.style = Some("TableGrid".to_string()); + for r in 0..2 { + for c in 0..2 { + t_bordered.rows[r].cells[c].blocks = vec![para_block(Paragraph::with_text("cell", CharProps::default()))]; + } + } + doc_bordered.body = vec![Arc::new(Block::Table(t_bordered))]; + let tex_bordered = export(&doc_bordered); + assert!(tex_bordered.contains("\\hline")); + let back_bordered = import(tex_bordered.as_bytes()); + let Some(Block::Table(bt_bordered)) = back_bordered.body.first().map(|b| &**b) else { panic!() }; + assert!(bt_bordered.props.style.is_some(), "imported table should remain bordered"); + } } From 16e18d8492cc712522e3ab25534fcc7309bcb431 Mon Sep 17 00:00:00 2001 From: Divyanshu Gupta Date: Fri, 16 Oct 2026 02:40:25 +0530 Subject: [PATCH 040/336] fix(docx, doc): support VML wrap side and case-insensitive float anchors --- crates/doc/src/para.rs | 17 +++++++---- crates/docx/src/read/story.rs | 53 ++++++++++++++++++++--------------- crates/docx/tests/fixtures.rs | 24 ++++++++++++++++ 3 files changed, 66 insertions(+), 28 deletions(-) diff --git a/crates/doc/src/para.rs b/crates/doc/src/para.rs index ab81cc3445..ba26bed87d 100644 --- a/crates/doc/src/para.rs +++ b/crates/doc/src/para.rs @@ -77,12 +77,17 @@ pub enum WrapText { impl WrapText { /// From the OOXML value (`bothSides`, `left`, `right`, `largest`). pub fn from_ooxml(v: &str) -> Option { - match v { - "bothSides" => Some(WrapText::BothSides), - "left" => Some(WrapText::Left), - "right" => Some(WrapText::Right), - "largest" => Some(WrapText::Largest), - _ => None, + let v = v.trim(); + if v.eq_ignore_ascii_case("bothSides") || v.eq_ignore_ascii_case("both") { + Some(WrapText::BothSides) + } else if v.eq_ignore_ascii_case("left") { + Some(WrapText::Left) + } else if v.eq_ignore_ascii_case("right") { + Some(WrapText::Right) + } else if v.eq_ignore_ascii_case("largest") { + Some(WrapText::Largest) + } else { + None } } diff --git a/crates/docx/src/read/story.rs b/crates/docx/src/read/story.rs index 9a39f6406a..de4aa8de7a 100644 --- a/crates/docx/src/read/story.rs +++ b/crates/docx/src/read/story.rs @@ -1405,7 +1405,10 @@ impl Reader<'_> { let type_id = shape.attr("type").map(|t| t.trim().trim_start_matches('#')); let shapetype = type_id.and_then(|id| pict.els().find(|e| e.name == "v:shapetype" && e.attr("id") == Some(id))); let attr = |name: &str| shape.attr(name).or_else(|| shapetype.and_then(|t| t.attr(name))); - let on = |name: &str| attr(name).is_none_or(|v| !matches!(v, "f" | "false" | "0")); + let on = |name: &str| attr(name).is_none_or(|v| { + let t = v.trim(); + !t.eq_ignore_ascii_case("f") && !t.eq_ignore_ascii_case("false") && t != "0" + }); let stroke = on("stroked").then(|| attr("strokecolor").map_or(Some(Rgb::BLACK), vml_color)).flatten(); let stroke_width = attr("strokeweight").and_then(|v| measure(v, 1.0)).unwrap_or(0.75).clamp(0.0, 100.0); let fill = on("filled").then(|| attr("fillcolor").map_or(Some(Rgb::WHITE), vml_color)).flatten(); @@ -1805,37 +1808,43 @@ fn vml_float(shape: &El, style: &str) -> Float { let off = |k: &str| get(k).and_then(|v| measure(v, 1.0)).unwrap_or(0.0); let max = crate::units::MAX_LEN_PT; let behind = get("z-index").and_then(int).is_some_and(|z| z < 0); - let wrap = match shape.child("w10:wrap").and_then(|w| w.attr("type")).unwrap_or("none") { - "square" => Wrap::Square, - "tight" => Wrap::Tight, - "through" => Wrap::Through, - "topAndBottom" => Wrap::TopAndBottom, + let wrap_el = shape.child("w10:wrap"); + let wrap_type = wrap_el.and_then(|w| w.attr("type")).unwrap_or("none"); + let wrap = match wrap_type { + t if t.eq_ignore_ascii_case("square") => Wrap::Square, + t if t.eq_ignore_ascii_case("tight") => Wrap::Tight, + t if t.eq_ignore_ascii_case("through") => Wrap::Through, + t if t.eq_ignore_ascii_case("topAndBottom") || t.eq_ignore_ascii_case("top-and-bottom") => Wrap::TopAndBottom, _ if behind => Wrap::BehindText, _ => Wrap::InFrontOfText, }; - let h_rel = match get("mso-position-horizontal-relative").unwrap_or("text") { - "page" => Anchor::Page, - "margin" => Anchor::Margin, - "char" => Anchor::Character, - "left-margin-area" => Anchor::LeftMargin, - "right-margin-area" => Anchor::RightMargin, - "inner-margin-area" => Anchor::InsideMargin, - "outer-margin-area" => Anchor::OutsideMargin, + let wrap_text = wrap_el.and_then(|w| w.attr("side")).and_then(wordcraft_doc::para::WrapText::from_ooxml).unwrap_or_default(); + let h_rel_str = get("mso-position-horizontal-relative").unwrap_or("text"); + let h_rel = match h_rel_str { + s if s.eq_ignore_ascii_case("page") => Anchor::Page, + s if s.eq_ignore_ascii_case("margin") => Anchor::Margin, + s if s.eq_ignore_ascii_case("char") => Anchor::Character, + s if s.eq_ignore_ascii_case("left-margin-area") => Anchor::LeftMargin, + s if s.eq_ignore_ascii_case("right-margin-area") => Anchor::RightMargin, + s if s.eq_ignore_ascii_case("inner-margin-area") => Anchor::InsideMargin, + s if s.eq_ignore_ascii_case("outer-margin-area") => Anchor::OutsideMargin, _ => Anchor::Column, }; - let v_rel = match get("mso-position-vertical-relative").unwrap_or("text") { - "page" => Anchor::Page, - "margin" => Anchor::Margin, - "line" => Anchor::Line, - "top-margin-area" => Anchor::TopMargin, - "bottom-margin-area" => Anchor::BottomMargin, - "inner-margin-area" => Anchor::InsideMargin, - "outer-margin-area" => Anchor::OutsideMargin, + let v_rel_str = get("mso-position-vertical-relative").unwrap_or("text"); + let v_rel = match v_rel_str { + s if s.eq_ignore_ascii_case("page") => Anchor::Page, + s if s.eq_ignore_ascii_case("margin") => Anchor::Margin, + s if s.eq_ignore_ascii_case("line") => Anchor::Line, + s if s.eq_ignore_ascii_case("top-margin-area") => Anchor::TopMargin, + s if s.eq_ignore_ascii_case("bottom-margin-area") => Anchor::BottomMargin, + s if s.eq_ignore_ascii_case("inner-margin-area") => Anchor::InsideMargin, + s if s.eq_ignore_ascii_case("outer-margin-area") => Anchor::OutsideMargin, _ => Anchor::Paragraph, }; let dist = |k: &str| get(k).and_then(|v| measure(v, 1.0)).unwrap_or(0.0).clamp(0.0, 1584.0); let mut f = Float { wrap, + wrap_text, h_rel, v_rel, x: (off("left") + off("margin-left")).clamp(-max, max), diff --git a/crates/docx/tests/fixtures.rs b/crates/docx/tests/fixtures.rs index 2c36a3bb40..a41914b94d 100644 --- a/crates/docx/tests/fixtures.rs +++ b/crates/docx/tests/fixtures.rs @@ -2830,3 +2830,27 @@ fn row_table_borders_round_trip() { let again = wordcraft_docx::read(&saved).unwrap(); assert_eq!(d.body, again.body); } + +#[test] +fn vml_shape_wrap_side_and_anchor_case_insensitivity() { + let body = r##" + + + + + + + + "##; + let doc = wordcraft_docx::read(&docx(body, &[], &[])).unwrap(); + let p = doc.body.iter().find_map(|b| b.as_para()).unwrap(); + let shape = match p.objects.first() { + Some(InlineObject::Shape { fill, float, .. }) => (fill, float), + o => panic!("expected shape, got {o:?}"), + }; + assert_eq!(*shape.0, None); + assert_eq!(shape.1.wrap, wordcraft_doc::para::Wrap::Square); + assert_eq!(shape.1.wrap_text, wordcraft_doc::para::WrapText::Left); + assert_eq!(shape.1.h_rel, wordcraft_doc::para::Anchor::Page); + assert_eq!(shape.1.v_rel, wordcraft_doc::para::Anchor::Margin); +} From 8711f5cfd1e6a30a79a67b91fcebcbc325108425 Mon Sep 17 00:00:00 2001 From: Divyanshu Gupta Date: Fri, 16 Oct 2026 03:05:52 +0530 Subject: [PATCH 041/336] fix(docx): inspect textbox style for v-text-anchor and handle case-insensitivity --- crates/docx/src/read/story.rs | 16 ++++++++++------ crates/docx/tests/fixtures.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/crates/docx/src/read/story.rs b/crates/docx/src/read/story.rs index 9a39f6406a..39617bd362 100644 --- a/crates/docx/src/read/story.rs +++ b/crates/docx/src/read/story.rs @@ -1437,14 +1437,18 @@ impl Reader<'_> { } part.body.inset = (inset != wordcraft_doc::TEXT_BOX_INSET).then_some(inset); // Resize shape to fit text (DrawingML's `a:spAutoFit`). - let fit = vml_style(tb.attr("style").unwrap_or(""), "mso-fit-shape-to-text"); + let fit = vml_style(tb.attr("style").unwrap_or(""), "mso-fit-shape-to-text") + .or_else(|| vml_style(style, "mso-fit-shape-to-text")); part.body.auto_fit = fit.is_some_and(|v| v.eq_ignore_ascii_case("t") || v.eq_ignore_ascii_case("true")); // Where the text sits between the box's top and bottom (DrawingML's `anchor`). - if let Some(a) = vml_style(style, "v-text-anchor") { - part.body.anchor = match a { - a if a.starts_with("middle") => wordcraft_doc::props::VAlign::Center, - a if a.starts_with("bottom") => wordcraft_doc::props::VAlign::Bottom, - _ => wordcraft_doc::props::VAlign::Top, + if let Some(a) = vml_style(tb.attr("style").unwrap_or(""), "v-text-anchor").or_else(|| vml_style(style, "v-text-anchor")) { + let al = a.trim().to_ascii_lowercase(); + part.body.anchor = if al.starts_with("middle") || al == "center" { + wordcraft_doc::props::VAlign::Center + } else if al.starts_with("bottom") { + wordcraft_doc::props::VAlign::Bottom + } else { + wordcraft_doc::props::VAlign::Top }; } } diff --git a/crates/docx/tests/fixtures.rs b/crates/docx/tests/fixtures.rs index 2c36a3bb40..96cbcba62c 100644 --- a/crates/docx/tests/fixtures.rs +++ b/crates/docx/tests/fixtures.rs @@ -2830,3 +2830,29 @@ fn row_table_borders_round_trip() { let again = wordcraft_docx::read(&saved).unwrap(); assert_eq!(d.body, again.body); } + +#[test] +fn vml_textbox_style_anchor_and_fit() { + let body = r#" + + + + + + Centered text + + + + + + "#; + let doc = wordcraft_docx::read(&docx(body, &[], &[])).unwrap(); + let p = doc.body.first().unwrap().as_para().unwrap(); + let story_id = match p.objects.first() { + Some(InlineObject::Shape { story: Some(id), .. }) => *id, + o => panic!("expected shape with story, got {o:?}"), + }; + let tb = doc.parts.get(&story_id).unwrap(); + assert_eq!(tb.body.anchor, wordcraft_doc::props::VAlign::Center); + assert!(tb.body.auto_fit); +} From 07ecf526a689c3904e7f86015ef938eafe6d5b2f Mon Sep 17 00:00:00 2001 From: Divyanshu Gupta Date: Fri, 16 Oct 2026 03:21:50 +0530 Subject: [PATCH 042/336] fix(formats): support case-insensitive and spaced !important in HTML CSS --- crates/formats/src/html.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/crates/formats/src/html.rs b/crates/formats/src/html.rs index 7f29a9bf29..838965e7a1 100644 --- a/crates/formats/src/html.rs +++ b/crates/formats/src/html.rs @@ -506,12 +506,23 @@ fn css_len_pt(v: &str) -> Option { } } +fn strip_important(v: &str) -> &str { + let s = v.trim(); + if let Some(pos) = s.rfind('!') { + let after = s[pos + 1..].trim(); + if after.eq_ignore_ascii_case("important") { + return s[..pos].trim(); + } + } + s +} + /// Apply a `style` attribute to formatting / block properties. fn apply_style(css: &str, f: &mut Fmt, align: &mut Option, preserve: &mut bool, page_break: &mut bool) { for decl in css.split(';').take(64) { let Some((k, v)) = decl.split_once(':') else { continue }; let k = k.trim().to_ascii_lowercase(); - let v = v.trim().trim_end_matches("!important").trim(); + let v = strip_important(v); let lv = v.to_ascii_lowercase(); match k.as_str() { "font-weight" => f.bold = lv.starts_with("bold") || lv.parse::().is_ok_and(|w| w >= 600), @@ -1785,4 +1796,23 @@ mod tests { fn entities() { assert_eq!(unescape("a <b> AB &bogus; &"), "a AB &bogus; &"); } + + #[test] + fn css_important_casing_and_spacing() { + let f = parse("

Styled

"); + assert_eq!(f.blocks.len(), 1); + let p = match &f.blocks[0] { + FBlock::Para(p) => p, + b => panic!("expected para, got {b:?}"), + }; + assert_eq!(p.align, Some(Align::Center)); + match &p.inlines[0] { + Inline::Text(_, fmt) => { + assert_eq!(fmt.color, Some(wordcraft_doc::props::Rgb(0xFF, 0x00, 0x00))); + assert_eq!(fmt.size, Some(16.0)); + assert!(fmt.bold); + } + i => panic!("expected text inline, got {i:?}"), + } + } } From f9808ae4950fa5f1514cf9d8aae31aa69b1438e1 Mon Sep 17 00:00:00 2001 From: Divyanshu Gupta Date: Fri, 16 Oct 2026 03:41:06 +0530 Subject: [PATCH 043/336] fix(docx): correctly parse emu and case-insensitive units in measure --- crates/docx/src/units.rs | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/crates/docx/src/units.rs b/crates/docx/src/units.rs index 4572ed94de..a2470d9a79 100644 --- a/crates/docx/src/units.rs +++ b/crates/docx/src/units.rs @@ -13,13 +13,39 @@ pub fn measure(s: &str, per_pt: f32) -> Option { if t.is_empty() || t.len() > 40 { return None; } - let split = t.find(|c: char| !(c.is_ascii_digit() || c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E')).unwrap_or(t.len()); + let bytes = t.as_bytes(); + let mut split = t.len(); + let mut in_exp = false; + for (i, &b) in bytes.iter().enumerate() { + if b.is_ascii_digit() || b == b'.' { + in_exp = false; + continue; + } + if (b == b'-' || b == b'+') && (i == 0 || in_exp) { + in_exp = false; + continue; + } + if b == b'e' || b == b'E' { + let rest = &bytes[i + 1..]; + let after_sign = match rest.first() { + Some(&s) if s == b'+' || s == b'-' => &rest[1..], + _ => rest, + }; + if after_sign.first().is_some_and(|c| c.is_ascii_digit()) { + in_exp = true; + continue; + } + } + split = i; + break; + } let (num, unit) = t.split_at(split); let v: f64 = num.parse().ok()?; if !v.is_finite() { return None; } - let pt = match unit.trim() { + let unit_lower = unit.trim().to_ascii_lowercase(); + let pt = match unit_lower.as_str() { "" => v / per_pt as f64, "pt" => v, "in" => v * 72.0, @@ -98,6 +124,15 @@ mod tests { assert_eq!(measure("1e400", 20.0), None); assert_eq!(measure("99999999999999", 20.0), Some(MAX_LEN_PT)); assert_eq!(measure("x", 20.0), None); + assert_eq!(measure("12700emu", 20.0), Some(1.0)); + assert_eq!(measure("25400EMU", 20.0), Some(2.0)); + assert_eq!(measure("1IN", 20.0), Some(72.0)); + assert_eq!(measure("12PT", 2.0), Some(12.0)); + assert_eq!(measure("2.54CM", 20.0), Some(72.0)); + assert_eq!(measure("25.4MM", 20.0), Some(72.0)); + assert_eq!(measure("10PX", 20.0), Some(7.5)); + assert_eq!(measure("1e2pt", 20.0), Some(100.0)); + assert_eq!(measure("1e+2pt", 20.0), Some(100.0)); assert_eq!(int("12.6"), Some(13)); assert_eq!(u32_of("-5"), Some(0)); } From 4818b7b8f244cec9b9ec0d591c7d6448012c72b2 Mon Sep 17 00:00:00 2001 From: Divyanshu Gupta Date: Fri, 16 Oct 2026 03:44:21 +0530 Subject: [PATCH 044/336] fix(formats): parse case-insensitive and pica units in ODT lengths --- crates/formats/src/odt.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/formats/src/odt.rs b/crates/formats/src/odt.rs index da438604b7..e8609117bb 100644 --- a/crates/formats/src/odt.rs +++ b/crates/formats/src/odt.rs @@ -947,7 +947,7 @@ fn column_sep(a: &Attrs) -> bool { /// Length → points. fn length(v: &str) -> Option { - let v = v.trim(); + let v = v.trim().to_ascii_lowercase(); let num = |s: &str| s.trim().parse::().ok().filter(|x| x.is_finite()); if let Some(n) = v.strip_suffix("pt") { num(n) @@ -957,12 +957,12 @@ fn length(v: &str) -> Option { num(n).map(|x| x * 72.0 / 2.54) } else if let Some(n) = v.strip_suffix("mm") { num(n).map(|x| x * 72.0 / 25.4) - } else if let Some(n) = v.strip_suffix("pc") { + } else if let Some(n) = v.strip_suffix("pc").or_else(|| v.strip_suffix("pi")) { num(n).map(|x| x * 12.0) } else if let Some(n) = v.strip_suffix("px") { num(n).map(|x| x * 0.75) } else { - num(v) + num(&v) } } @@ -2362,6 +2362,13 @@ mod tests { assert_eq!(length("1in"), Some(72.0)); assert_eq!(length("12pt"), Some(12.0)); assert!((length("2.54cm").unwrap() - 72.0).abs() < 0.01); + assert_eq!(length("1IN"), Some(72.0)); + assert_eq!(length("12PT"), Some(12.0)); + assert!((length("2.54CM").unwrap() - 72.0).abs() < 0.01); + assert!((length("25.4MM").unwrap() - 72.0).abs() < 0.01); + assert_eq!(length("1pc"), Some(12.0)); + assert_eq!(length("1PI"), Some(12.0)); + assert_eq!(length("10PX"), Some(7.5)); assert_eq!(length("x"), None); } From 5817874b6dd65795fe3c956b748a173cf21dfffb Mon Sep 17 00:00:00 2001 From: Divyanshu Gupta Date: Fri, 16 Oct 2026 03:47:04 +0530 Subject: [PATCH 045/336] fix(formats): parse leading plus and case-insensitive units in LaTeX lengths --- crates/formats/src/latex.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/crates/formats/src/latex.rs b/crates/formats/src/latex.rs index c7c555b85e..26a1f6eeda 100644 --- a/crates/formats/src/latex.rs +++ b/crates/formats/src/latex.rs @@ -1669,11 +1669,12 @@ fn parse_color(model: Option<&str>, spec: &str) -> Option { /// A TeX length in points (`3cm`, `0.4\linewidth`), if it is one. fn length_pt(s: &str) -> Option { - let s = s.trim(); + let s = s.trim().strip_prefix('+').unwrap_or(s.trim()); let split = s.find(|c: char| !(c.is_ascii_digit() || c == '.' || c == '-')).unwrap_or(s.len()); let (n, unit) = s.split_at(split); let n: f32 = if n.is_empty() { 1.0 } else { n.parse().ok()? }; - let k = match unit.trim() { + let unit = unit.trim().to_ascii_lowercase(); + let k = match unit.as_str() { "pt" => 1.0, "bp" => 72.27 / 72.0, "in" => 72.27, @@ -2804,6 +2805,16 @@ mod tests { assert!(widths.len() <= wordcraft_doc::table::MAX_COLS); } + #[test] + fn latex_length_pt_case_and_plus() { + assert_eq!(length_pt("12pt"), Some(12.0)); + assert_eq!(length_pt("12PT"), Some(12.0)); + assert_eq!(length_pt("+12pt"), Some(12.0)); + assert_eq!(length_pt("1IN"), Some(72.27)); + assert_eq!(length_pt("+2cm"), Some(56.9)); + assert_eq!(length_pt("0.5\\TEXTWIDTH"), Some(LINE_WIDTH * 0.5)); + } + const ARTICLE: &str = r#"\documentclass[11pt]{article} \usepackage[utf8]{inputenc} \usepackage{amsmath} % maths From 51fc220092faf036577fc6608e65a9bf2ce8e4f8 Mon Sep 17 00:00:00 2001 From: Divyanshu Gupta Date: Fri, 16 Oct 2026 04:03:21 +0530 Subject: [PATCH 046/336] fix(formats): support asian/complex scripts and underline/strike types in ODT --- crates/formats/src/odt.rs | 85 ++++++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 14 deletions(-) diff --git a/crates/formats/src/odt.rs b/crates/formats/src/odt.rs index da438604b7..9b7f904f04 100644 --- a/crates/formats/src/odt.rs +++ b/crates/formats/src/odt.rs @@ -1061,24 +1061,49 @@ impl Styles { "text-properties" => { if let Some((_, s)) = &mut cur { let d = &mut s.fmt; - if let Some(w) = get(&a, "fo:font-weight") { - d.bold = Some(w == "bold" || w.parse::().is_ok_and(|n| n >= 600)); + let is_bold = |w: &str| { + let w = w.trim(); + w.eq_ignore_ascii_case("bold") || w.parse::().is_ok_and(|n| n >= 600) + }; + let weights: Vec<&str> = [get(&a, "fo:font-weight"), get(&a, "style:font-weight-asian"), get(&a, "style:font-weight-complex")] + .into_iter() + .flatten() + .collect(); + if !weights.is_empty() { + d.bold = Some(weights.iter().any(|&w| is_bold(w))); } - if let Some(v) = get(&a, "fo:font-style") { - d.italic = Some(v == "italic" || v == "oblique"); + let is_italic = |v: &str| { + let v = v.trim(); + v.eq_ignore_ascii_case("italic") || v.eq_ignore_ascii_case("oblique") + }; + let styles: Vec<&str> = [get(&a, "fo:font-style"), get(&a, "style:font-style-asian"), get(&a, "style:font-style-complex")] + .into_iter() + .flatten() + .collect(); + if !styles.is_empty() { + d.italic = Some(styles.iter().any(|&v| is_italic(v))); } - if let Some(v) = get(&a, "style:text-underline-style") { - d.underline = Some(v != "none"); + let is_not_none = |v: &str| !v.trim().eq_ignore_ascii_case("none"); + let ul_attrs: Vec<&str> = [get(&a, "style:text-underline-style"), get(&a, "style:text-underline-type")] + .into_iter() + .flatten() + .collect(); + if !ul_attrs.is_empty() { + d.underline = Some(ul_attrs.iter().any(|&v| is_not_none(v))); } - if let Some(v) = get(&a, "style:text-line-through-style") { - d.strike = Some(v != "none"); + let strike_attrs: Vec<&str> = [get(&a, "style:text-line-through-style"), get(&a, "style:text-line-through-type")] + .into_iter() + .flatten() + .collect(); + if !strike_attrs.is_empty() { + d.strike = Some(strike_attrs.iter().any(|&v| is_not_none(v))); } if let Some(v) = get(&a, "style:text-position") { let v = v.trim(); let first = v.split_whitespace().next().unwrap_or(""); let pct = first.trim_end_matches('%').parse::().unwrap_or(0.0); - d.sup = Some(first == "super" || pct > 0.0); - d.sub = Some(first == "sub" || pct < 0.0); + d.sup = Some(first.eq_ignore_ascii_case("super") || pct > 0.0); + d.sub = Some(first.eq_ignore_ascii_case("sub") || pct < 0.0); } if let Some(c) = get(&a, "fo:color").and_then(model::parse_color) { d.color = Some(c); @@ -1091,7 +1116,7 @@ impl Styles { } if let Some(fam) = get(&a, "fo:font-family").or_else(|| get(&a, "style:font-name")) { let fam = fam.split(',').next().unwrap_or("").trim().trim_matches(['\'', '"']).to_string(); - if model::is_mono(&fam) || get(&a, "style:font-pitch") == Some("fixed") { + if model::is_mono(&fam) || get(&a, "style:font-pitch").is_some_and(|p| p.trim().eq_ignore_ascii_case("fixed")) { d.code = Some(true); } else if !fam.is_empty() { d.font = Some(fam); @@ -1115,20 +1140,20 @@ impl Styles { } "paragraph-properties" => { if let Some((_, s)) = &mut cur { - s.align = match get(&a, "fo:text-align") { + s.align = match get(&a, "fo:text-align").map(|v| v.trim().to_ascii_lowercase()).as_deref() { Some("center") => Some(Align::Center), Some("end") | Some("right") => Some(Align::Right), Some("justify") => Some(Align::Justify), Some("start") | Some("left") => Some(Align::Left), _ => s.align, }; - if get(&a, "fo:break-before") == Some("page") { + if get(&a, "fo:break-before").is_some_and(|b| b.trim().eq_ignore_ascii_case("page")) { s.page_break = true; } if let Some(v) = get(&a, "style:page-number") { s.page_number = Some(v.trim().parse::().ok().map(|n| n.min(32_767))); } - if get(&a, "fo:border-bottom").is_some_and(|b| b != "none") { + if get(&a, "fo:border-bottom").is_some_and(|b| !b.trim().eq_ignore_ascii_case("none")) { s.border_bottom = true; } } @@ -2920,4 +2945,36 @@ mod tests { assert_eq!(marks(&back), marks(&doc)); assert_eq!(marks(&back).len(), 1); } + + #[test] + fn test_asian_complex_and_extended_text_properties() { + let content = format!( + r#" + + + + + + + Asian + Complex + Underlined + Struck + Centered + "# + ); + let f = parse(&package(&content, None)).unwrap(); + let ps = paras(&f); + assert_eq!(ps.len(), 5); + + let fmt = |p: &Para| p.inlines.iter().find_map(|i| if let Inline::Text(_, f) = i { Some(f.clone()) } else { None }).unwrap(); + + assert!(fmt(ps[0]).bold); + assert!(fmt(ps[1]).italic); + assert!(fmt(ps[2]).underline); + assert!(fmt(ps[3]).strike); + assert!(fmt(ps[4]).bold); + assert_eq!(ps[4].align, Some(Align::Center)); + assert!(ps[4].page_break); + } } From 772cccf03809d591eb3d89f82bf98b205dd0d888 Mon Sep 17 00:00:00 2001 From: Divyanshu Gupta Date: Fri, 16 Oct 2026 04:39:59 +0530 Subject: [PATCH 047/336] fix(formats): support xs:time, string-value, and case-insensitive cell types in spreadsheets --- crates/formats/src/sheet.rs | 112 ++++++++++++++++++++++++++---------- 1 file changed, 83 insertions(+), 29 deletions(-) diff --git a/crates/formats/src/sheet.rs b/crates/formats/src/sheet.rs index 658f0f6072..2d5b61cb6d 100644 --- a/crates/formats/src/sheet.rs +++ b/crates/formats/src/sheet.rs @@ -666,19 +666,24 @@ fn xlsx_rows(pkg: &mut Package<'_>, path: &str) -> Result>, Stri fn xlsx_value(c: &XCell, shared: &[String], formats: &[Option], date1904: bool) -> String { let v = c.value.trim(); - match c.kind.as_str() { - "s" => v.parse::().ok().and_then(|i| shared.get(i)).cloned().unwrap_or_default(), - "inlineStr" => c.inline.clone(), - "str" | "e" => c.value.clone(), - "b" => if v == "1" || v.eq_ignore_ascii_case("true") { "TRUE" } else { "FALSE" }.into(), - "d" => iso_datetime(v).unwrap_or_else(|| v.to_string()), - _ if v.is_empty() => c.inline.clone(), - _ => { - let Ok(x) = v.parse::() else { return v.to_string() }; - match formats.get(c.style).copied().flatten() { - Some(kind) => serial_date(x, date1904, kind).unwrap_or_else(|| number(x)), - None => number(x), - } + let kind = c.kind.trim(); + if kind.eq_ignore_ascii_case("s") { + v.parse::().ok().and_then(|i| shared.get(i)).cloned().unwrap_or_default() + } else if kind.eq_ignore_ascii_case("inlineStr") { + if c.inline.is_empty() { c.value.clone() } else { c.inline.clone() } + } else if kind.eq_ignore_ascii_case("str") || kind.eq_ignore_ascii_case("e") { + c.value.clone() + } else if kind.eq_ignore_ascii_case("b") { + if v == "1" || v.eq_ignore_ascii_case("true") { "TRUE" } else { "FALSE" }.into() + } else if kind.eq_ignore_ascii_case("d") { + iso_datetime(v).unwrap_or_else(|| v.to_string()) + } else if v.is_empty() { + c.inline.clone() + } else { + let Ok(x) = v.parse::() else { return v.to_string() }; + match formats.get(c.style).copied().flatten() { + Some(kind) => serial_date(x, date1904, kind).unwrap_or_else(|| number(x)), + None => number(x), } } } @@ -763,22 +768,31 @@ fn iso_datetime(v: &str) -> Option { Some(out) } -/// An ISO 8601 duration time (`PT13H45M00S`) as `hh:mm[:ss]`. +/// An ISO 8601 duration time (`PT13H45M00S`) or clock time (`13:45[:00]`) as `hh:mm[:ss]`. fn iso_time(v: &str) -> Option { - let t = v.trim().strip_prefix("PT")?; - let (mut h, mut m, mut s) = (0u64, 0u64, 0f64); - let mut num = String::new(); - for c in t.chars() { - match c { - 'H' => h = std::mem::take(&mut num).parse().ok()?, - 'M' => m = std::mem::take(&mut num).parse().ok()?, - 'S' => s = std::mem::take(&mut num).parse().ok()?, - c if num.len() < 20 => num.push(c), - _ => return None, + let t = v.trim(); + if let Some(t) = t.strip_prefix("PT") { + let (mut h, mut m, mut s) = (0u64, 0u64, 0f64); + let mut num = String::new(); + for c in t.chars() { + match c { + 'H' => h = std::mem::take(&mut num).parse().ok()?, + 'M' => m = std::mem::take(&mut num).parse().ok()?, + 'S' => s = std::mem::take(&mut num).parse().ok()?, + c if num.len() < 20 => num.push(c), + _ => return None, + } } + let s = s.round() as u64; + return Some(if s == 0 { format!("{h:02}:{m:02}") } else { format!("{h:02}:{m:02}:{s:02}") }); } - let s = s.round() as u64; - Some(if s == 0 { format!("{h:02}:{m:02}") } else { format!("{h:02}:{m:02}:{s:02}") }) + // xs:time: hh:mm[:ss[.sss]][Z|+...] + let hms: Vec = t.split(['Z', '+', '-']).next().unwrap_or("").split(':').filter_map(|p| p.split('.').next().and_then(|x| x.parse().ok())).collect(); + if let [h, mi, rest @ ..] = hms.as_slice() { + let s = rest.first().copied().unwrap_or(0); + return Some(if s == 0 { format!("{h:02}:{mi:02}") } else { format!("{h:02}:{mi:02}:{s:02}") }); + } + None } // --------------------------------------------------------------------------------------------- @@ -827,11 +841,13 @@ fn ods_read(xml: &[u8], want: Option) -> Result<(Vec, Vec { let repeat = attr(e, b"number-columns-repeated").and_then(|v| v.trim().parse().ok()).unwrap_or(1u64).max(1); let kind = attr(e, b"value-type").unwrap_or_default(); - let value = match kind.as_str() { - "float" => attr(e, b"value"), + let kind_lower = kind.trim().to_ascii_lowercase(); + let value = match kind_lower.as_str() { + "float" | "percentage" | "currency" => attr(e, b"value"), "date" => attr(e, b"date-value"), "time" => attr(e, b"time-value"), "boolean" => attr(e, b"boolean-value"), + "string" => attr(e, b"string-value"), _ => None, }; // A covered cell's own content is hidden by the merge. @@ -935,11 +951,16 @@ fn ods_read(xml: &[u8], want: Option) -> Result<(Vec, Vec String { let v = c.value.as_deref().map(str::trim); - match (c.kind.as_str(), v) { + let kind = c.kind.trim().to_ascii_lowercase(); + match (kind.as_str(), v) { ("float", Some(v)) => v.parse::().map(number).unwrap_or_else(|_| c.text.clone()), ("date", Some(v)) => iso_datetime(v).unwrap_or_else(|| c.text.clone()), ("time", Some(v)) => iso_time(v).unwrap_or_else(|| c.text.clone()), ("boolean", Some(v)) => if v.eq_ignore_ascii_case("true") || v == "1" { "TRUE" } else { "FALSE" }.into(), + ("percentage" | "currency", Some(v)) if c.text.trim().is_empty() => { + v.parse::().map(number).unwrap_or_else(|_| c.text.clone()) + } + ("string", Some(v)) if c.text.trim().is_empty() => v.to_string(), _ => c.text.clone(), } } @@ -1066,4 +1087,37 @@ mod tests { assert_eq!(number(1234567.0), "1234567"); assert_eq!(number(0.1 + 0.2), "0.3"); } + + #[test] + fn test_sheet_extended_value_parsing() { + assert_eq!(iso_time("13:45:00").as_deref(), Some("13:45")); + assert_eq!(iso_time("13:45:30.500").as_deref(), Some("13:45:30")); + assert_eq!(iso_time("13:45:30Z").as_deref(), Some("13:45:30")); + assert_eq!(iso_time("PT13H45M").as_deref(), Some("13:45")); + + // ODS with string-value and percentage without text:p, and case-insensitive float + let content = r#" + + + + + + + + "#; + let book = zip(&[("mimetype", "application/vnd.oasis.opendocument.spreadsheet"), ("content.xml", content)]); + let (_, rows) = read_rows(&book, Pick::Index(0)).unwrap(); + assert_eq!(rows.len(), 1); + assert_eq!(rows[0], vec!["Hello", "0.15", "42", "14:30"]); + + // XLSX with case-insensitive cell types + let sheet = r#" + 1 + World + 0 + "#; + let (_, rows) = read_rows(&xlsx(sheet), Pick::Index(0)).unwrap(); + assert_eq!(rows.len(), 1); + assert_eq!(rows[0], vec!["TRUE", "World", "Name"]); + } } From 48ff1e52be8c57e3e10165bd069f50e4876d43c8 Mon Sep 17 00:00:00 2001 From: Divyanshu Gupta Date: Fri, 16 Oct 2026 05:08:58 +0530 Subject: [PATCH 048/336] fix(formats): support bullet, top, bot, hslash, accents, bm, and mod in LaTeX math --- crates/formats/src/latex.rs | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/crates/formats/src/latex.rs b/crates/formats/src/latex.rs index c7c555b85e..9bbdd48c8c 100644 --- a/crates/formats/src/latex.rs +++ b/crates/formats/src/latex.rs @@ -584,6 +584,8 @@ const MATH_SYMBOLS: &[(char, &str)] = &[ ('⌈', "lceil"), ('⌉', "rceil"), ('°', "circ"), + ('•', "bullet"), + ('⊤', "top"), ]; /// Extra input spellings mapped to the same symbols. @@ -610,6 +612,9 @@ const MATH_ALIASES: &[(&str, char)] = &[ ("Vert", '‖'), ("lvert", '|'), ("rvert", '|'), + ("bot", '⊥'), + ("hslash", 'ℏ'), + ("colon", ':'), ]; /// Operator names written upright in both formats. @@ -965,7 +970,7 @@ fn math_seq(t: &[MTok], pos: &mut usize, depth: usize, in_opt: bool) -> String { } } "text" | "textrm" | "textit" | "textbf" | "mathrm" | "mathit" | "mathbf" | "mathsf" | "mathtt" | "mathcal" | "mathbb" - | "mathfrak" | "boldsymbol" | "operatorname" | "mbox" | "hbox" | "displaystyle" | "textstyle" | "scriptstyle" => { + | "mathfrak" | "boldsymbol" | "bm" | "pmb" | "operatorname" | "mbox" | "hbox" | "displaystyle" | "textstyle" | "scriptstyle" => { if t.get(*pos) == Some(&MTok::Open) || !matches!(name, "displaystyle" | "textstyle" | "scriptstyle") { let a = math_arg(t, pos, depth); o.push_str(&a); @@ -977,6 +982,15 @@ fn math_seq(t: &[MTok], pos: &mut usize, depth: usize, in_opt: bool) -> String { "vec" => o.push_str(&format!("{}⃗", math_arg(t, pos, depth))), "dot" => o.push_str(&format!("{}̇", math_arg(t, pos, depth))), "ddot" => o.push_str(&format!("{}̈", math_arg(t, pos, depth))), + "acute" => o.push_str(&format!("{}́", math_arg(t, pos, depth))), + "grave" => o.push_str(&format!("{}̀", math_arg(t, pos, depth))), + "breve" => o.push_str(&format!("{}̆", math_arg(t, pos, depth))), + "check" => o.push_str(&format!("{}̌", math_arg(t, pos, depth))), + "pmod" => { + let a = math_arg(t, pos, depth); + o.push_str(&format!(" (mod {a})")); + } + "bmod" => o.push_str(" mod "), "begin" | "end" => { // Inner environments (`matrix`, `cases`, `aligned`…): keep the content. let _ = math_arg(t, pos, depth); @@ -2992,6 +3006,14 @@ Caf\'e na\"ive \c{c}a \v{s}\'{e} and {\bfseries grouped bold} after. (r"\left( a \right)", "( a )"), (r"\sin x + \text{if } y", "sin x + if y"), (r"a \cdot b \times c \neq d", "a ⋅ b × c ≠ d"), + (r"a \bullet b", "a • b"), + (r"x \top y \bot z", "x ⊤ y ⊥ z"), + (r"f \colon X \to Y", "f : X → Y"), + (r"\hbar \hslash", "ℏ ℏ"), + (r"\bm{v}", "v"), + (r"\acute{x} \grave{y} \breve{z} \check{w}", "x́ ỳ z̆ w̌"), + (r"a \equiv b \pmod{m}", "a ≡ b (mod m)"), + (r"a \bmod b", "a mod b"), (r"\unknowncmd", r"\unknowncmd"), ] { assert_eq!(latex_to_linear(tex), linear, "{tex}"); @@ -3001,6 +3023,8 @@ Caf\'e na\"ive \c{c}a \v{s}\'{e} and {\bfseries grouped bold} after. ("α ≤ β", r"\alpha \leq \beta"), ("αβ", r"\alpha\beta"), ("πr", r"\pi r"), + ("a • b", r"a \bullet b"), + ("x ⊤ y", r"x \top y"), ("a_(i+1)", r"a_{i+1}"), ("50% & #", r"50\% \& \#"), ("√(3&x)", r"\sqrt[3]{x}"), From c9bebab058b75edbdd58e315138df3a58d89f445 Mon Sep 17 00:00:00 2001 From: Divyanshu Gupta Date: Fri, 16 Oct 2026 07:04:27 +0530 Subject: [PATCH 049/336] fix(mathml): support linethickness=none and menclose notations --- crates/formats/src/mathml.rs | 73 ++++++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/crates/formats/src/mathml.rs b/crates/formats/src/mathml.rs index bbb135844e..7b57a64b2b 100644 --- a/crates/formats/src/mathml.rs +++ b/crates/formats/src/mathml.rs @@ -568,8 +568,11 @@ fn elem(e: &El, depth: usize, budget: &mut usize) -> Arg { vec![MNode::Run(MRun { text: t, nor: true, ..Default::default() })] } "mfrac" => { - let thin = e.attr("linethickness").is_some_and(|t| t.trim().trim_end_matches(|c: char| c.is_alphabetic()).trim() == "0"); - let kind = if e.attr("bevelled") == Some("true") { + let thin = e.attr("linethickness").is_some_and(|t| { + let s = t.trim(); + s.eq_ignore_ascii_case("none") || s.trim_end_matches(|c: char| c.is_alphabetic() || c == '%').trim() == "0" + }); + let kind = if e.attr("bevelled").is_some_and(|b| b.eq_ignore_ascii_case("true")) { FracKind::Skewed } else if thin { FracKind::NoBar @@ -680,10 +683,30 @@ fn elem(e: &El, depth: usize, budget: &mut usize) -> Arg { "mphantom" => vec![MNode::Phant { show: false, zero_wid: false, zero_asc: false, zero_desc: false, e: children(e, d, budget) }], "menclose" => { let n = e.attr("notation").unwrap_or("longdiv"); - let has = |k: &str| n.split_whitespace().any(|w| w == k); - if has("box") || has("roundedbox") { + let has = |k: &str| n.split_whitespace().any(|w| w.eq_ignore_ascii_case(k)); + if has("radical") { + return vec![MNode::Rad { deg: Vec::new(), deg_hide: true, e: children(e, d, budget) }]; + } + if has("box") || has("roundedbox") || has("circle") { return vec![MNode::BorderBox { hide: [false; 4], strike: [false; 4], e: children(e, d, budget) }]; } + if has("actuarial") { + return vec![MNode::BorderBox { hide: [false, true, true, false], strike: [false; 4], e: children(e, d, budget) }]; + } + let has_top = has("top"); + let has_bottom = has("bottom"); + let has_left = has("left"); + let has_right = has("right"); + let has_hstrike = has("horizontalstrike"); + let has_vstrike = has("verticalstrike"); + let has_upstrike = has("updiagonalstrike") || has("cross"); + let has_downstrike = has("downdiagonalstrike") || has("cross"); + + if has_top || has_bottom || has_left || has_right || has_hstrike || has_vstrike || has_upstrike || has_downstrike { + let hide = [!has_top, !has_bottom, !has_left, !has_right]; + let strike = [has_hstrike, has_vstrike, has_upstrike, has_downstrike]; + return vec![MNode::BorderBox { hide, strike, e: children(e, d, budget) }]; + } children(e, d, budget) } // The first child of `semantics` is the presentation; annotations aren't content. @@ -892,4 +915,46 @@ mod tests { let m = to_mathml(&n, false, "z"); assert!(m.ends_with("")); } + + #[test] + fn test_mfrac_linethickness_none_and_menclose_notations() { + let el_frac = El { + name: "mfrac".into(), + attrs: vec![("linethickness".into(), "none".into())], + children: vec![ + Item::El(El { name: "mi".into(), attrs: vec![], children: vec![Item::Text("n".into())] }), + Item::El(El { name: "mi".into(), attrs: vec![], children: vec![Item::Text("k".into())] }), + ], + }; + let mut budget = MAX_NODES; + let tree_frac = elem(&el_frac, 0, &mut budget); + assert!(matches!(tree_frac.as_slice(), [MNode::Frac { kind: FracKind::NoBar, .. }]), "{tree_frac:?}"); + + let el_strike = El { + name: "menclose".into(), + attrs: vec![("notation".into(), "horizontalstrike".into())], + children: vec![Item::El(El { name: "mi".into(), attrs: vec![], children: vec![Item::Text("x".into())] })], + }; + let mut budget = MAX_NODES; + let tree_strike = elem(&el_strike, 0, &mut budget); + assert!(matches!(tree_strike.as_slice(), [MNode::BorderBox { strike: [true, false, false, false], .. }]), "{tree_strike:?}"); + + let el_cross = El { + name: "menclose".into(), + attrs: vec![("notation".into(), "cross".into())], + children: vec![Item::El(El { name: "mi".into(), attrs: vec![], children: vec![Item::Text("x".into())] })], + }; + let mut budget = MAX_NODES; + let tree_cross = elem(&el_cross, 0, &mut budget); + assert!(matches!(tree_cross.as_slice(), [MNode::BorderBox { strike: [false, false, true, true], .. }]), "{tree_cross:?}"); + + let el_rad = El { + name: "menclose".into(), + attrs: vec![("notation".into(), "radical".into())], + children: vec![Item::El(El { name: "mi".into(), attrs: vec![], children: vec![Item::Text("x".into())] })], + }; + let mut budget = MAX_NODES; + let tree_rad = elem(&el_rad, 0, &mut budget); + assert!(matches!(tree_rad.as_slice(), [MNode::Rad { deg_hide: true, .. }]), "{tree_rad:?}"); + } } From 0137a5157afed480f1588b0ac98f8af672970afd Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Fri, 16 Oct 2026 04:15:02 +0200 Subject: [PATCH 050/336] Fix numbered list levels that start at 0 or are overridden to 1 Found while testing crates/doc/src/numbering.rs:319 Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Sonnet 5.5 --- crates/doc/src/numbering.rs | 65 ++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/crates/doc/src/numbering.rs b/crates/doc/src/numbering.rs index ac04c0a026..2d41563d79 100644 --- a/crates/doc/src/numbering.rs +++ b/crates/doc/src/numbering.rs @@ -285,6 +285,16 @@ pub struct Counters { state: HashMap, /// Which nums have started (to apply start overrides once). seen: HashMap, + /// Per abstract list: whether each level has counted yet (a value of 0 can be a real count when a + /// level starts at 0). + flags: HashMap, +} + +/// Whether a level has counted yet, and the value its next item takes if a start override says so. +#[derive(Default, Debug, Clone, Copy)] +struct LevelFlags { + started: bool, + first: Option, } impl Counters { @@ -307,29 +317,31 @@ impl Counters { return None; } let st = self.state.entry(abs.id).or_insert([0; 9]); + let fl = self.flags.entry(abs.id).or_default(); if let std::collections::hash_map::Entry::Vacant(e) = self.seen.entry(num) { e.insert(true); for (l, start) in &n.start_overrides { - if let Some(s) = st.get_mut(*l as usize) { - *s = start.saturating_sub(1); + if let (Some(s), Some(f)) = (st.get_mut(*l as usize), fl.get_mut(*l as usize)) { + *s = 0; + *f = LevelFlags { started: false, first: Some(*start) }; } } } - if let Some(v) = st.get_mut(lv) { - *v = if *v == 0 { def.start.max(if def.start == 0 { 0 } else { 1 }) } else { v.saturating_add(1) }; - if *v == 0 { - *v = def.start; - } + if let (Some(v), Some(f)) = (st.get_mut(lv), fl.get_mut(lv)) { + *v = if f.started { v.saturating_add(1) } else { f.first.unwrap_or(def.start) }; + *f = LevelFlags { started: true, first: None }; } // Deeper levels restart, unless they never restart or only restart after a level above // this one (Word's lvlRestart). - for (d, deeper) in st.iter_mut().enumerate().skip(lv + 1) { + for (d, (deeper, f)) in st.iter_mut().zip(fl.iter_mut()).enumerate().skip(lv + 1) { let restarts = level_def(d).is_none_or(|l| l.restart && l.restart_after.is_none_or(|k| lv < k as usize)); if restarts { *deeper = 0; + *f = LevelFlags::default(); } } let vals = *st; + let started = *fl; if def.format == NumFormat::Bullet { return Some((def.text.clone(), def)); } @@ -342,7 +354,8 @@ impl Counters { chars.next(); let k = (d as usize).saturating_sub(1).min(8); let val = vals.get(k).copied().unwrap_or(0); - let val = if val == 0 { level_def(k).map(|l| l.start).unwrap_or(1) } else { val }; + let counted = started.get(k).is_some_and(|f| f.started); + let val = if counted { val } else { level_def(k).map(|l| l.start).unwrap_or(1) }; // Legal numbering (w:isLgl) shows every level in Arabic digits, whatever its own // format (二 of a Chinese outline becomes 2 in "2.1"). let fmt = match level_def(k).map(|l| l.format).unwrap_or(NumFormat::Decimal) { @@ -406,6 +419,40 @@ mod tests { assert_eq!(l(2), "(1)", "level 1 restarts level 3"); } + #[test] + fn a_level_starting_at_zero_counts_up() { + let mut n = Numbering::default(); + let id = n.add_list(ListKind::Numbered); + if let Some(a) = n.abstracts.first_mut() { + a.levels[0].start = 0; + } + let mut c = Counters::default(); + let labels: Vec = (0..4).map(|_| c.next_label(&n, id, 0).unwrap().0).collect(); + assert_eq!(labels, ["0.", "1.", "2.", "3."]); + } + + #[test] + fn a_start_override_of_one_or_zero_is_honoured() { + let mut n = Numbering::default(); + let id = n.add_list(ListKind::Numbered); + if let Some(a) = n.abstracts.first_mut() { + a.levels[0].start = 5; + } + if let Some(num) = n.nums.iter_mut().find(|x| x.id == id) { + num.start_overrides = vec![(0, 1)]; + } + let mut c = Counters::default(); + let labels: Vec = (0..2).map(|_| c.next_label(&n, id, 0).unwrap().0).collect(); + assert_eq!(labels, ["1.", "2."]); + + if let Some(num) = n.nums.iter_mut().find(|x| x.id == id) { + num.start_overrides = vec![(0, 0)]; + } + let mut c = Counters::default(); + let labels: Vec = (0..3).map(|_| c.next_label(&n, id, 0).unwrap().0).collect(); + assert_eq!(labels, ["0.", "1.", "2."]); + } + #[test] fn numbered_labels() { let mut n = Numbering::default(); From 193b82d5705396e4b91186361a1f021150eb8919 Mon Sep 17 00:00:00 2001 From: Nghiathan13 Date: Fri, 16 Oct 2026 10:35:52 +0700 Subject: [PATCH 051/336] #2277: preview rotated object pixels while dragging --- ROADMAP.md | 6 +- crates/ui-egui/src/objects.rs | 183 +++++++++++++++++++++++++++++++++- docs/gaps.md | 5 +- docs/ui-parity.md | 5 +- 4 files changed, 190 insertions(+), 9 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 40407d18b6..d97b8d29a2 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -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-14 · **Change:** 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-14 · **Change:** trivial (progress log: print-area strings translated in every catalog; 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-16 · **Change:** trivial (live rotation preview while dragging, #2277; 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-16 · **Change:** trivial (live rotation preview while dragging, #2277; previously trivial: progress log: print-area strings translated in every catalog; 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 @@ -135,6 +135,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm ## Progress log | Date | What landed | +| 2026-10-16 | Pictures and shapes without text stories turn visibly while dragging the rotation handle (#2277), with Shift snapping to 15° steps; the document changes only on release through one `arrange.rotation` command, and Escape cancels the preview | | 2026-10-14 | DOCX row table border exceptions render and survive saving. Explicit cell borders win; a row exception does not change other rows. | | 2026-10-14 | Tab stops edited on the ruler: the corner selector picks Left/Center/Right/Decimal/Bar, a click sets a stop (Alt skips the snap), dragging moves it with a guide line and position tooltip, dragging off the ruler clears it; markers draw per kind and mirror for RTL; one `para.tabs` call and one undo step per gesture | | 2026-10-12 | Fields keep `w:dirty` through DOCX saves (#877): a begin marker flagged by a generator that couldn't compute a result (pandoc-style TOC placeholders) keeps the flag on open/save — as a buffered field or a range field — so Word still refreshes it on open; computing a result (Update Field, Update Table) clears it, like Word's F9 | @@ -403,6 +404,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | Date | Change | Summary | |---|---|---| +| 2026-10-16 | trivial | Progress log: live rotation preview while dragging (#2277); readiness estimates unchanged | | 2026-10-14 | trivial | Progress log: row table border exceptions | | 2026-10-12 | minor | Czech proofing review: short-word splits rank after deletion, NOSUGGEST excludes suggestions while accepting typed forms, affix comments apply only to headers, ratios keep spaces and repeated-word messages use Czech quotes; synthetic engine end-to-end and latency checks | | 2026-10-12 | minor | Czech spelling from the installed Hunspell dictionary, runtime-table suggestions and generic checks; Czech interface writes cs-CZ documents; no Czech hyphenation patterns bundled; estimates unchanged | diff --git a/crates/ui-egui/src/objects.rs b/crates/ui-egui/src/objects.rs index 37af3f7b87..32c8b0b918 100644 --- a/crates/ui-egui/src/objects.rs +++ b/crates/ui-egui/src/objects.rs @@ -450,10 +450,11 @@ fn behind_drag_at(app: &WordApp, layout: &DocLayout, pages: &[Rect], scale: f32, .map(|o| (o, Grab::Move)) } -/// Whether a drag shows the object's pixels (moved, or scaled for an unturned picture) rather -/// than an outline: resizing reflows a text box (and redraws a shape). +/// Whether a drag shows the object's pixels rather than an outline. Resizing reflows a text +/// box (and redraws a shape). Text-box text doesn't yet follow its frame's committed rotation, +/// so turning one keeps the outline preview rather than promising rotated text on release. fn shows_pixels(d: &ObjectDrag) -> bool { - (d.picture && d.object.spin.is_identity()) || d.drag.grab == Grab::Move + (d.picture && d.object.spin.is_identity()) || d.drag.grab == Grab::Move || (d.drag.grab == Grab::Rotate && d.object.text_box.is_none()) } /// `o` drawn alone over its rotated bounds, as a texture, at the canvas's `scale` (screen points @@ -520,6 +521,18 @@ pub fn paint(app: &WordApp, painter: &Painter, t: &Tokens, layout: &DocLayout, p if let Some(d) = app.canvas.obj_drag.as_ref().filter(|d| d.drag.moved) { let Some(to) = screen(pages, scale, d.drag.to_page, d.drag.rect) else { return }; if d.drag.grab == Grab::Rotate { + if let Some(tex) = &d.preview + && let Some(bounds) = screen(pages, scale, d.drag.page, d.object.bounds()) + { + // The cached pixels already contain the starting rotation and flips. Turn + // only by the additional angle; changing UVs would mirror them a second time. + let mut mesh = egui::Mesh::with_texture(tex.id()); + let uv = Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0)); + mesh.add_rect_with_uv(bounds, uv, Color32::from_white_alpha(PREVIEW_OPACITY)); + let delta = wordcraft_geom::normalize_degrees(d.drag.deg - d.object.spin.deg); + mesh.rotate(egui::emath::Rot2::from_angle(delta.to_radians()), to.center()); + painter.add(egui::Shape::mesh(mesh)); + } frame::paint_turning(painter, t, to, d.drag.deg); return; } @@ -912,4 +925,168 @@ mod tests { button(&h, end, false); steps(&mut h, 3); } + + /// A settled canvas for the rotation regressions, without taking focus on the desktop. + fn rotation_window(app: WordApp) -> crate::table_resize::tests::H { + let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(1440.0, 900.0)).with_step_dt(0.25).build_ui_state( + |ui, app: &mut WordApp| { + let ctx = ui.ctx().clone(); + app.logic(&ctx); + app.ui(ui); + }, + app, + ); + crate::table_resize::tests::steps(&mut h, 6); + h + } + + /// #2277: the actual picture pixels follow the rotation handle before release, including + /// pictures whose cached preview already contains a rotation and either reflection. + #[test] + fn rotation_drag_turns_picture_pixels_live_and_commits_once_or_cancels() { + use crate::table_resize::tests::{H, button, steps}; + for (initial, flip_h, flip_v) in [(0.0, false, false), (30.0, true, false), (90.0, false, true), (315.0, true, true)] { + // Four original colour quadrants make both accidental extra rotation and reflected + // UVs observable. Sample near the long ends after a quarter turn, outside the old + // picture, so the stationary page image cannot satisfy the pixel assertions. + let colours = [[230, 20, 20, 255], [20, 200, 20, 255], [20, 20, 230, 255], [230, 200, 20, 255]]; + let image = image::RgbaImage::from_fn(80, 40, |x, y| { + let quadrant = match (x < 40, y < 20) { + (true, true) => 0, + (false, true) => 1, + (false, false) => 2, + (true, false) => 3, + }; + image::Rgba(colours[quadrant]) + }); + let mut bytes = Vec::new(); + image.write_to(&mut std::io::Cursor::new(&mut bytes), image::ImageFormat::Png).unwrap(); + let picture = InlineObject::Image { + media: "rotation-test".into(), + w: 200.0, + h: 100.0, + alt: String::new(), + float: wordcraft_doc::para::Float { + wrap: Wrap::InFrontOfText, + x: 150.0, + y: 180.0, + rot: initial, + flip_h, + flip_v, + ..Default::default() + }, + crop: [0.0; 4], + ole: None, + }; + let (mut app, _) = app_with(picture); + app.session.doc.media.insert("rotation-test".into(), Arc::new(bytes)); + app.session.touch(); + app.run("view.commentsPane", json!({"value": false})).unwrap(); + app.run("select.range", json!({"anchor": Pos::body(0, 0), "focus": Pos::body(0, OBJ.len_utf8())})).unwrap(); + let mut h: H = rotation_window(app); + let depth = h.state().session.undo_depth(); + let original = serde_json::to_value(&h.state().session.doc).unwrap(); + let o = h.state_mut().session.layout().object(&Pos::body(0, 0), 0).unwrap(); + let (cx, cy) = (o.rect.x + o.rect.w / 2.0, o.rect.y + o.rect.h / 2.0); + let center = crate::canvas::page_to_screen(h.state_mut(), 0, cx, cy).unwrap(); + let top = crate::canvas::page_to_screen(h.state_mut(), 0, cx, o.rect.y).unwrap(); + let radius = center.distance(top) + 22.0; + let handle = |degrees: f32| center + egui::emath::Rot2::from_angle(degrees.to_radians()) * egui::vec2(0.0, -radius); + let target = wordcraft_geom::normalize_degrees(initial + 90.0); + let start = handle(initial); + let end = handle(target); + h.event(egui::Event::PointerMoved(start)); + button(&h, start, true); + h.step(); + h.event(egui::Event::PointerMoved(end)); + steps(&mut h, 2); + let d = h.state().canvas.obj_drag.as_ref().expect("rotating"); + assert_eq!(d.drag.grab, Grab::Rotate); + assert_eq!(d.drag.deg, target); + let texture = d.preview.as_ref().expect("cached picture preview").id(); + assert_eq!(serde_json::to_value(&h.state().session.doc).unwrap(), original, "dragging must not edit the document"); + assert_eq!(h.state().session.undo_depth(), depth); + let rendered = h.render().unwrap(); + if initial == 0.0 + && let Ok(path) = std::env::var("WORDCRAFT_ROTATION_SHOT") + { + rendered.save(path).unwrap(); + } + let ppp = h.ctx.pixels_per_point(); + let spin = wordcraft_geom::Spin::new(target, flip_h, flip_v); + for ((x, y), colour) in [(-75.0, -20.0), (75.0, -20.0), (75.0, 20.0), (-75.0, 20.0)].into_iter().zip(colours) { + let (x, y) = spin.apply(cx, cy, cx + x, cy + y); + let at = crate::canvas::page_to_screen(h.state_mut(), 0, x, y).unwrap(); + let pixel = rendered.get_pixel((at.x * ppp).round() as u32, (at.y * ppp).round() as u32).0; + for (actual, expected) in pixel.into_iter().zip(colour).take(3) { + if expected > 150 { + assert!(actual > 170, "live quadrant missing: {initial}/{flip_h}/{flip_v} {colour:?} -> {pixel:?}"); + } else { + assert!(actual < 150, "live quadrant missing: {initial}/{flip_h}/{flip_v} {colour:?} -> {pixel:?}"); + } + } + } + // A second angle reuses the raster rather than regenerating it on pointer moves. + let next = handle(target + 15.0); + h.event(egui::Event::PointerMoved(next)); + steps(&mut h, 2); + assert_eq!(h.state().canvas.obj_drag.as_ref().unwrap().preview.as_ref().unwrap().id(), texture); + let final_angle = h.state().canvas.obj_drag.as_ref().unwrap().drag.deg; + button(&h, next, false); + steps(&mut h, 3); + assert!(h.state().canvas.obj_drag.is_none()); + assert_eq!(h.state().session.undo_depth(), depth + 1, "one command on release"); + assert_eq!(selected(h.state()).unwrap().1.frame().unwrap().2.rot, final_angle); + h.state_mut().run("edit.undo", json!({})).unwrap(); + steps(&mut h, 3); + assert_eq!(serde_json::to_value(&h.state().session.doc).unwrap(), original, "one undo restores everything"); + + h.event(egui::Event::PointerMoved(start)); + button(&h, start, true); + h.step(); + h.event(egui::Event::PointerMoved(end)); + steps(&mut h, 2); + assert!(h.state().canvas.obj_drag.as_ref().unwrap().preview.is_some()); + h.event(egui::Event::Key { key: egui::Key::Escape, physical_key: None, pressed: true, repeat: false, modifiers: Default::default() }); + h.step(); + button(&h, end, false); + steps(&mut h, 3); + assert!(h.state().canvas.obj_drag.is_none()); + assert_eq!(h.state().session.undo_depth(), depth, "Escape adds no undo step"); + assert_eq!(serde_json::to_value(&h.state().session.doc).unwrap(), original, "Escape cancels without an edit"); + } + } + + #[test] + fn rotation_drag_keeps_text_boxes_outline_only_until_their_text_can_rotate() { + use crate::table_resize::tests::{button, steps}; + let mut app = WordApp::new(wordcraft_engine::Session::new(Document::new()), Default::default()); + app.run("insert.textBox", json!({"text": "Text stays upright on release"})).unwrap(); + let bx = app.session.layout().find_object(0, |o| o.text_box.is_some()).unwrap(); + app.run("select.range", json!({"anchor": bx.pos(), "focus": Pos { off: bx.off + OBJ.len_utf8(), ..bx.pos() }})).unwrap(); + app.run("arrange.bounds", json!({"page": 0, "x": 150, "y": 180, "width": 200, "height": 100})).unwrap(); + let mut h = rotation_window(app); + let original = serde_json::to_value(&h.state().session.doc).unwrap(); + let depth = h.state().session.undo_depth(); + let bx = h.state_mut().session.layout().find_object(0, |o| o.text_box.is_some()).unwrap(); + let (cx, cy) = (bx.rect.x + bx.rect.w / 2.0, bx.rect.y + bx.rect.h / 2.0); + let center = crate::canvas::page_to_screen(h.state_mut(), 0, cx, cy).unwrap(); + let top = crate::canvas::page_to_screen(h.state_mut(), 0, cx, bx.rect.y).unwrap(); + let radius = center.distance(top) + 22.0; + let (start, end) = (center - egui::vec2(0.0, radius), center + egui::vec2(radius, 0.0)); + h.event(egui::Event::PointerMoved(start)); + button(&h, start, true); + h.step(); + h.event(egui::Event::PointerMoved(end)); + steps(&mut h, 2); + let d = h.state().canvas.obj_drag.as_ref().expect("turning the text box frame"); + assert_eq!((d.drag.grab, d.drag.deg), (Grab::Rotate, 90.0)); + assert!(d.preview.is_none(), "do not preview rotated text that would snap upright on release"); + assert_eq!(serde_json::to_value(&h.state().session.doc).unwrap(), original); + assert_eq!(h.state().session.undo_depth(), depth); + button(&h, end, false); + steps(&mut h, 3); + assert_eq!(selected(h.state()).unwrap().1.frame().unwrap().2.rot, 90.0); + assert_eq!(h.state().session.undo_depth(), depth + 1); + } } diff --git a/docs/gaps.md b/docs/gaps.md index 3d1d7fdafd..4920061cab 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -1,6 +1,6 @@ # Where WordCraft falls short of Word -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** trivial (VML freeforms read and kept on save; previously trivial: 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: floating-table overlap ignores empty line-break lines; previously trivial: copies keep their formatting on the clipboard as HTML, #1523; previously trivial: sample-text formulas on Enter, #1067; previously trivial: #29: copied equations as MathML, OMML open as #1297, #816; previously trivial: the Font list no longer keeps every previewed font in memory, #848; 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 Table and Eraser landed, #303; previously minor: beta distance restated: ~15 points, ~150–230 h; previously: first version; every known shortfall from the 2026-10-10 re-measure, ranked) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-16 · **Change:** trivial (live rotation preview while dragging, #2277; previously trivial: VML freeforms read and kept on save; previously trivial: 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: floating-table overlap ignores empty line-break lines; previously trivial: copies keep their formatting on the clipboard as HTML, #1523; previously trivial: sample-text formulas on Enter, #1067; previously trivial: #29: copied equations as MathML, OMML open as #1297, #816; previously trivial: the Font list no longer keeps every previewed font in memory, #848; 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 Table and Eraser landed, #303; previously minor: beta distance restated: ~15 points, ~150–230 h; previously: first version; every known shortfall from the 2026-10-10 re-measure, ranked) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 > **Printing update:** 2026-10-12 · direct desktop printing through craft-print; details below. @@ -33,7 +33,7 @@ Document renaming: desktop renaming requires a filesystem that supports hard lin | 7 | **Dialog depth**: 54 modal dialogs vs Word's ~100 | B | `Dialog` enum in `crates/ui-egui/src/dialogs.rs` | Power users reach for dialogs the ribbon alone doesn't cover | 11–19 | [ui-parity.md](ui-parity.md) | | 9 | **Password-protected `.doc` files** (AES) can't be opened; password-protected `.docx` opens and saves (agile encryption, #55 closed by #281) | | `crates/docx/src/crypt.rs`; `crates/docbin/src/lib.rs` opens XOR/RC4/RC4 CryptoAPI `.doc` with a password (#1079, untested on Word-written files; AES `.doc` refused) | Corporate and legal users with old files | 3–5 | [file-format-parity.md](file-format-parity.md) | | 8 | **Proofing covers English, Polish, Portuguese, Italian, Croatian, Turkish, Russian and Czech only**; no dictionaries, grammar or hyphenation for the other ~42 languages Word proofs (text in them is left unmarked) | | `crates/proof` `ProofLang::PROOFED`: English, 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) and Russian (the installed `ru_RU` dictionary, 2026-10-12), a language per run; Bulgarian has hyphenation only (#826); 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 and Russian | 20–40 | [localization-parity.md](localization-parity.md) | -| 10 | **Objects**: text in a freely rotated text box (free rotation and flips landed, #332; 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**: 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) | | 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); missing: Ink to Math, two-stroke arrows and arrows with a closed triangle head, 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), no combo/3D/radar/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) | @@ -87,6 +87,7 @@ The same gaps grouped the way the parity documents are, for agents working in on | Date | Change | Summary | |---|---|---| +| 2026-10-16 | trivial | #10 narrowed: pictures and shapes without text stories preview rotation while dragging (#2277); remaining object gaps and estimates unchanged | | 2026-10-14 | trivial | Row table border exceptions fixed; other row table exceptions remain unsupported | | 2026-10-12 | minor | Czech proofing review: short-word splits rank after deletion, NOSUGGEST excludes suggestions while accepting typed forms, affix comments apply only to headers, ratios keep spaces and repeated-word messages use Czech quotes; synthetic engine end-to-end and latency checks | | 2026-10-12 | minor | Czech spelling from the installed Hunspell dictionary, runtime-table suggestions and generic checks; Czech interface writes cs-CZ documents; no Czech hyphenation patterns bundled; estimates unchanged | diff --git a/docs/ui-parity.md b/docs/ui-parity.md index b1c503f5c5..14cc8c05c3 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -1,6 +1,6 @@ # UI parity with Microsoft Word -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** 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-16 · **Change:** trivial (live rotation preview while dragging, #2277; 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 @@ -33,7 +33,7 @@ Document title opens Rename Document; the account badge opens Options › Genera | Rulers: indents, tabs, margins, table columns | indents draggable (one undo, #62); tab stops: corner selector (Left/Center/Right/Decimal/Bar), click to set, drag to move, drag off to clear, per-kind markers, Alt skips the snap, one undo step per gesture, double-click opens the Tabs dialog (#320); table column and row markers drag their borders (one undo) and in a cell the indents are measured from it (#700) | ✅ | 90% | 1 | | On-canvas objects: select, drag, resize handles, nudge | ✅ tested (#228); a picture or shape behind text can be dragged across the overlapping text (body and header/footer), whether or not it was selected first, while a click without movement still edits that text | ✅ | 75% | — | | On-canvas objects: select, drag, Ctrl+drag to copy, resize handles, nudge | ✅ tested (#228, #1014) | ✅ | 75% | — | -| Rotation handle, crop handles on canvas, alignment guides, smart guides | rotation handle (Shift: 15° steps), turned frames resize along their axes (#332); crop handles: Picture Format › Crop puts the picture in crop mode, black crop handles trim a side (a corner, two) keeping the scale and the visible part in place, the cropped-away part shows dimmed, Esc/Enter/a click outside/Crop again keep the crop, one undo step per drag (unturned pictures; no Crop to Shape, Aspect Ratio or Fill/Fit); alignment guides, our own design: a moved picture, shape or text box snaps its left, centre or right (top, middle or bottom) to the page's edges and centre, the margins and the other floating objects on the page within 6 screen points, x and y independently, turned objects by their rotated bounds, with a thin line across the page for each guide while dragging; Alt moves freely; File › Options › Advanced › Editing (`ui.alignmentGuides`) turns them off. Smart guides missing | Word for Mac 16.113.4 shows no guide lines and snaps inconsistently (to the page's left edge, not its top, right or bottom; Word check K); Word for Windows' guides not observed | 75% | 1–2 | +| Rotation handle, crop handles on canvas, alignment guides, smart guides | rotation handle (Shift: 15° steps), pictures and shapes without text stories preview their content turning while the pointer is held; release commits one `arrange.rotation` command and Escape cancels (#2277); text boxes retain a frame-only rotation preview, with text staying upright; turned frames resize along their axes (#332); crop handles: Picture Format › Crop puts the picture in crop mode, black crop handles trim a side (a corner, two) keeping the scale and the visible part in place, the cropped-away part shows dimmed, Esc/Enter/a click outside/Crop again keep the crop, one undo step per drag (unturned pictures; no Crop to Shape, Aspect Ratio or Fill/Fit); alignment guides, our own design: a moved picture, shape or text box snaps its left, centre or right (top, middle or bottom) to the page's edges and centre, the margins and the other floating objects on the page within 6 screen points, x and y independently, turned objects by their rotated bounds, with a thin line across the page for each guide while dragging; Alt moves freely; File › Options › Advanced › Editing (`ui.alignmentGuides`) turns them off. Smart guides missing | Word for Mac 16.113.4 shows no guide lines and snaps inconsistently (to the page's left edge, not its top, right or bottom; Word check K); Word for Windows' guides not observed | 75% | 1–2 | | Tables on the page: drag column and row borders, resize handle past the bottom-right corner; select columns, rows and cells; "+" insert buttons; move handle | ✅ resize cursors, dashed guide, one undo step per drag, selection kept; Shift moves one column; Esc cancels (#217); Ctrl shares the change among the columns on the right, the left edge moves a left-aligned table's indent (a centred or right-aligned one keeps its alignment), a border of cells selected across moves only in their rows, Alt shows the column widths on the ruler (#711); double-click a column border to AutoFit it (a nested table's within the cell around it, #971); selection arrows above a column, left of a row and along a cell's left edge (a drag selects more; the cells are shaded and a column is edited cell by cell); "+" left of a row border and above a column border; move handle past the top-left corner (a click selects the table, a drag floats it where it's dropped); an ordinary drag, Shift+arrows or Shift+click from one cell into another select the rectangle of whole cells between them, and into or out of a table whole rows, their cells shaded whole (#971). Missing: a single selected cell's border; The Cell Size Height and Width boxes set every selected row and column (#1242). | ✅ | 95% | 1 | | Selection: word/sentence/paragraph clicks, dragging after a double-click/triple-click/Ctrl+click extends by whole words/paragraphs/sentences (#549), Shift extend, F8 extend mode, column (Alt+drag, #237) | ✅ | ✅ | 90% | 1 | | Zoom: Ctrl+scroll, pinch (#179), zoom buttons (#232), pages side by side when zoomed out (#251), still one per row after One Page or Page Width (#768) | ✅ | ✅ | 85% | — | @@ -59,6 +59,7 @@ Document title opens Rename Document; the account badge opens Options › Genera | Date | Change | Summary | |---|---|---| +| 2026-10-16 | trivial | Rotation handle: pictures and shapes without text stories turn with the preview while the pointer is held; text boxes retain frame-only previews; release commits one `arrange.rotation` command, Escape cancels (#2277) | | 2026-10-13 | trivial | New/Modify Table Style: No Border on a part that inherits its lines hides them; emptying the font box makes the part's font inherited again (#1288) | | 2026-10-14 | trivial | Tab stops edited on the ruler: corner kind selector, click to set, drag to move or off to clear, per-kind markers mirrored for RTL; each gesture one `para.tabs` call and one undo step; Rulers 85% → 90% | | 2026-10-12 | trivial | Tables on the page: one selected cell (Select Cell, or a click along its left edge) is a cell selection when its border is dragged: the border moves in its row only (#962) | From 72453d9def96b1bbed3b75d19a68873ff4819ae2 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Thu, 15 Oct 2026 23:57:51 -0400 Subject: [PATCH 052/336] ci: switch to shared nix workflow from craft-common-actions --- .github/workflows/nix.yml | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 18392587a8..01cb9cd663 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -18,16 +18,6 @@ concurrency: cancel-in-progress: true jobs: - eval: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1 - - name: nix flake check --no-build - run: | - if [ -f flake.nix ]; then - nix flake check --no-build --all-systems - else - echo "No flake.nix present in this repository; skipping check." - fi + nix: + # Intentionally tracked at @main (not SHA-pinned) so org-wide CI improvements, security fixes, and build optimizations propagate without PR churn across all craftapp repos. + uses: storytold/craft-common-actions/.github/workflows/nix.yml@main From 2ffdf353a18e6db5676b5fdc17f8720075103f0f Mon Sep 17 00:00:00 2001 From: rabis <340973947+rabis7575@users.noreply.github.com> Date: Fri, 16 Oct 2026 06:56:17 +0200 Subject: [PATCH 053/336] #2310: PDF: lam-alef drawn as two glyphs at the same x extracts in reading order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some fonts (the static Noto Naskh Arabic builds) draw لا as a lam and a zero-advance alef with their own clusters at one x. visual_order kept the tie in logical order, so the right-to-left run's ranges didn't decrease, krilla dropped /ReversedChars and السلام extracted as السالم. Ties between clusters now go in reverse text order when right to left (text order otherwise). Co-Authored-By: Claude Opus 5.5 --- ROADMAP.md | 4 +++- crates/pdf/src/lib.rs | 11 +++++++++-- crates/pdf/src/tests.rs | 15 +++++++++++++++ docs/file-format-parity.md | 5 +++-- 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 40407d18b6..75c4f568ae 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -3,7 +3,7 @@ **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-14 · **Change:** 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-14 · **Change:** trivial (progress log: print-area strings translated in every catalog; 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-16 · **Change:** trivial (progress log: lam-alef in exported PDFs extracts whole with fonts that draw it as two glyphs, #2310; previously trivial: progress log: print-area strings translated in every catalog; 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 @@ -200,6 +200,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | 2026-10-14 | RTF keeps tracked changes (gap 19): insertions and deletions open as tracked changes with their authors and dates (`\revtbl`, `\revised`, `\deleted`, `\revdttm`) instead of plain text, so deleted text no longer reappears as normal text; saving to RTF writes them back, and Accept All / Reject All work after reopening | | 2026-10-14 | Alignment guides: moving a picture, shape or text box snaps its edges or centre to the page's edges and centre, the margins and the other floating objects on the page, with a thin line across the page for each guide while dragging; Alt moves freely; File › Options › Advanced › Editing turns them off (`ui.alignmentGuides`). Our own design: Word for Mac shows no guides | | 2026-10-14 | ODT files open with their sections (#1379): a paragraph that switches to another page style starts a new section on a new page, with that page's size, orientation, margins and columns and, when the file asks, page numbering restarting; a two-column `text:section` becomes a continuous two-column section. Before, the whole document was one portrait section, so landscape pages, columns and restarts were lost on open and on save to DOCX | +| 2026-10-16 | Arabic words with lam-alef copy and search whole in exported PDFs with fonts that draw لا as a lam and a zero-width alef at one spot, such as the static Noto Naskh Arabic builds (#2310): السلام no longer reads back as السالم. Clusters drawn at the same place now go in reverse text order, so viewers reverse the run as a whole again. The page looks the same | | 2026-10-14 | Hebrew and Arabic in exported PDFs copy and search as whole words (#1383): right-to-left glyphs are written left to right as they appear, the order Chrome and Edge (PDFium) and poppler read text back from (checked by hand), instead of in reading order, which they reversed (`שלום` came back as `םולש`, Arabic in pieces). Lam-alef stays whole and vowel marks follow their letters, though Chrome and poppler can still break a fully vowelled word with spaces. The page looks the same. Readers that take glyphs strictly in file order get only plain Hebrew runs right, through `/ReversedChars`; screen readers are unverified | | 2026-10-14 | HTML import (#1381): header cells (``) are bold unless their own CSS says otherwise, and `text-align` on a cell, its row, row group or table aligns the cell's paragraphs (a paragraph's own alignment wins), so `wordcraft-cli convert page.html out.docx` writes the bold header and `w:jc` right-aligned cells. HTML, Markdown and plain text, which give no page size, open on the region's paper (A4 in Italy, Letter in the US) like a new blank document, in the app and in `wordcraft-cli`; documents with their own page size keep it | | 2026-10-14 | Custom page sizes (#1433): Layout › Size ends with More Paper Sizes…, which opens Page Setup's new Paper group — a Paper size list (the named sizes, or Custom size) and Width and Height in the measurement unit; OK sets the size only when it changed, and a wider-than-tall size comes out landscape. `layout.size` now clamps each side to Word's 0.1"–22" and refuses a page the margins don't fit (the margins rule: 36 pt of text room), and Page Setup checks the size and margins together before applying either | @@ -457,6 +458,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | 2026-10-14 | trivial | Progress log: alignment guides while dragging pictures and shapes | | 2026-10-14 | trivial | Progress log: ODT sections on import (#1379) | | 2026-10-14 | trivial | Progress log: right-to-left text in exported PDFs extracts as whole words (#1383) | +| 2026-10-16 | trivial | Progress log: lam-alef drawn as two glyphs at one x extracts whole from exported PDFs (#2310) | | 2026-10-14 | trivial | Progress log: HTML header cells, cell alignment and region paper for opened HTML/Markdown/text (#1381) | | 2026-10-14 | trivial | Progress log: custom page size from More Paper Sizes… and Page Setup (#1433) | | 2026-10-13 | trivial | Progress log: highlighted text on a dark page (#1252) | diff --git a/crates/pdf/src/lib.rs b/crates/pdf/src/lib.rs index 38a5f46da3..2a6e720a7a 100644 --- a/crates/pdf/src/lib.rs +++ b/crates/pdf/src/lib.rs @@ -335,8 +335,15 @@ fn visual_order(text: &str, glyphs: &[(u32, f32, f32)], clusters: &[std::ops::Ra _ => groups.push((*x, vec![i])), } } - // Stable: clusters at the same x keep their order, as do glyphs sharing their text. - groups.sort_by(|a, b| a.0.total_cmp(&b.0)); + // Clusters at the same x go in text order, reversed when right to left: some fonts draw + // lam-alef as a lam and a zero-width alef at one x, and a right-to-left run whose ranges + // don't decrease loses its reading order (#2310). Stable for glyphs sharing their text. + let tie = |idx: &[usize]| -> i64 { + let Some(r) = idx.first().and_then(|&g| clusters.get(g)) else { return 0 }; + let start = i64::try_from(r.start).unwrap_or(i64::MAX); + if text.get(r.clone()).is_some_and(|t| t.chars().any(wordcraft_fonts::is_rtl)) { -start } else { start } + }; + groups.sort_by(|a, b| a.0.total_cmp(&b.0).then_with(|| tie(&a.1).cmp(&tie(&b.1)))); groups .into_iter() .flat_map(|(_, mut idx)| { diff --git a/crates/pdf/src/tests.rs b/crates/pdf/src/tests.rs index 9a01541e6d..7b15fd7561 100644 --- a/crates/pdf/src/tests.rs +++ b/crates/pdf/src/tests.rs @@ -1394,6 +1394,21 @@ fn left_to_right_glyphs_keep_their_order() { assert!(left_to_right(&glyphs)); } +/// A lam-alef some fonts draw as a lam and a zero-width alef at the same x goes in reverse +/// text order, so the run's ranges still decrease and viewers read لا, not ال (#2310). +/// Left-to-right clusters at one x keep their text order. +#[test] +fn clusters_at_the_same_x_go_in_reading_order() { + let text = "سلا"; + let glyphs = [(3, 20.0, 10.0), (1, 10.0, 10.0), (2, 10.0, 0.0)]; + let ranges = [0..2, 2..4, 4..6]; + assert_eq!(visual_order(text, &glyphs, &ranges, &ranges), [2, 1, 0]); + let text = "ab"; + let glyphs = [(1, 10.0, 0.0), (2, 10.0, 5.0)]; + let ranges = [0..1, 1..2]; + assert_eq!(visual_order(text, &glyphs, &ranges, &ranges), [0, 1]); +} + /// Right-to-left glyphs (reading order, drawn right to left) are written left to right a /// cluster at a time. A letter's vowel mark takes its own character and goes before the letter, /// so a viewer reversing the word reads letter then mark; a lam-alef drawn as two glyphs keeps diff --git a/docs/file-format-parity.md b/docs/file-format-parity.md index 38a1638f16..847dc7ff20 100644 --- a/docs/file-format-parity.md +++ b/docs/file-format-parity.md @@ -1,6 +1,6 @@ # File-format parity with Microsoft Word -> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-14 · **Change:** trivial (every table style part round-trips, #714); previously trivial: VML freeforms read and kept on save; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: `flowChartOr` draws its circle and cross spokes, as in Word; previously trivial: RTF: Mac OS Roman and DOS text, #1653; previously trivial: VML text boxes keep their internal margins, fit to their text and vertical alignment; hidden drawings not shown; previously trivial: PDF export draws horizontal and vertical lines inside groups, #1474; previously trivial: symbol-font text exported as Unicode, #776; previously trivial: text-box margins, Resize shape to fit text and freeforms in groups, #743; previously trivial: formatted paste from the system clipboard's HTML and RTF, #687; previously trivial: independent complex-script formatting inheritance and round-trip regression tests; previously trivial: import/export fixes for TXT, RTF, ODT, HTML and LaTeX, #433–#440; previously major: first version; every format Word reads or writes, measured from the readers and writers on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4, plus Word for Windows' extra formats +> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-16 · **Change:** trivial (PDF export: lam-alef drawn as two glyphs at one x extracts in reading order, #2310); previously trivial: every table style part round-trips, #714; previously trivial: VML freeforms read and kept on save; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: `flowChartOr` draws its circle and cross spokes, as in Word; previously trivial: RTF: Mac OS Roman and DOS text, #1653; previously trivial: VML text boxes keep their internal margins, fit to their text and vertical alignment; hidden drawings not shown; previously trivial: PDF export draws horizontal and vertical lines inside groups, #1474; previously trivial: symbol-font text exported as Unicode, #776; previously trivial: text-box margins, Resize shape to fit text and freeforms in groups, #743; previously trivial: formatted paste from the system clipboard's HTML and RTF, #687; previously trivial: independent complex-script formatting inheritance and round-trip regression tests; previously trivial: import/export fixes for TXT, RTF, ODT, HTML and LaTeX, #433–#440; previously major: first version; every format Word reads or writes, measured from the readers and writers on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4, plus Word for Windows' extra formats Every format Word opens or saves, what WordCraft does with it, and how it's tested. Fidelity is the share of a typical real-world file's content and formatting that survives (read: shows correctly; @@ -39,7 +39,7 @@ Weights are the share of a Word user's file traffic (estimate). Read/write: ✅ | `.docx` Strict Open XML | 1% | R/W | ✅ (namespace mapping in `xml.rs`) | ❌ | ~60% | 2–4 | one fixture | Writing Strict is rare | | `.docm` / `.dotx` / `.dotm` | 3% | R/W | ✅ | ✅ | ~65% | incl. above | `macro_packages.rs` | Macros and signatures kept on save (#172); macros never run | | `.doc` Word 97-2003 (and `.dot`) | 8% | R/W | 🟡 | ❌ | ~60% | 25–40 | `crates/docbin/src/tests.rs`, `tests/drawing.rs` | Spec-based reader ([MS-DOC]): text, formatting, styles, sections, headers/footers, tables, lists, notes, fields, bookmarks; inline and floating pictures (PNG, JPEG, DIB, TIFF, EMF, WMF; Macintosh PICT as a sized placeholder) with crop, alt text, position and wrap; basic shapes, lines, groups and text boxes ([MS-ODRAW]). Table cell shading from every SHD record (`sprmTDefTableShd`, `sprmTDefTableShdRaw` and their 2nd/3rd ranges, with the Word 97 `Shd80` as fallback; a clear pattern shows its background colour); the row-end mark is not a cell, and each table has its own grid and properties (#1380). Horizontally merged rows stored as fewer, wider cells come with #1123. Freeform shapes, z-order and Word 6/95 files are dropped or refused. Password-protected files (XOR obfuscation, RC4, RC4 CryptoAPI) open after a password prompt, from the public specs; not yet tried on a file Word wrote (AES-encrypted ones are refused). No writer. DIB conversion caps BMP bytes (header included) and encoded PNG at the supplied BLIP byte limit, and uses it as the decoder's allocation limit; a BMP fallback is kept if it fits (related #1334 and #1554). | -| PDF export | 8% | W | — | ✅ | ~80% | 6–10 | `crates/pdf/src/tests.rs` | krilla: embedded subsets, selectable text incl. field results (#133); Hebrew and Arabic copy and search as whole words (right-to-left glyphs written in visual order, #1383); All Caps and Small Caps text extracts as the capitals shown, as from Word's PDFs, and never changes the case of other text (#1473), links, outline, tagging. Fonts it can't subset become outlines (#132). Print Markup (#589): comments in balloons beside each page (scaled to its paper) and tracked changes; HTML/Markdown/text copies end with a comment list. Horizontal and vertical lines inside groups are drawn (#1474). No PDF/A option, no "best for printing/online" choice | +| PDF export | 8% | W | — | ✅ | ~80% | 6–10 | `crates/pdf/src/tests.rs` | krilla: embedded subsets, selectable text incl. field results (#133); Hebrew and Arabic copy and search as whole words (right-to-left glyphs written in visual order, #1383, also with fonts that draw lam-alef as two glyphs at one x, #2310); All Caps and Small Caps text extracts as the capitals shown, as from Word's PDFs, and never changes the case of other text (#1473), links, outline, tagging. Fonts it can't subset become outlines (#132). Print Markup (#589): comments in balloons beside each page (scaled to its paper) and tracked changes; HTML/Markdown/text copies end with a comment list. Horizontal and vertical lines inside groups are drawn (#1474). No PDF/A option, no "best for printing/online" choice | | PDF open (PDF Reflow, Word for Windows) | 1% | R | ❌ | — | 0% | 30–50 | — | Converting a PDF into an editable document. A sibling app (PdfCraft) may supply the parser | | `.rtf` Rich Text Format | 3% | R/W | 🟡 | 🟡 | ~50% | 10–15 | `crates/formats/src/tests.rs` | ~1,500 lines: text, character/paragraph formatting, styles, lists, tables, pictures (`\shppict`, #154), fields, bookmarks. 8-bit text in the document's code page (`\ansicpg`) or the current font's (`\fcharset`, `\cpg`): Cyrillic, Central European, Greek, Turkish, Hebrew, Arabic, Baltic, Vietnamese, Thai (was Windows-1252 only), Mac OS Roman (`\mac`, `\fcharset77`) and DOS code pages (`\pc` 437, `\pca` 850, `\cpg` 437/737/850/852/857/866, `\fcharset254`) (#1653). Double-byte East Asian code pages (Japanese `\fcharset128`, Chinese, Korean) not decoded yet. Pasted from the system clipboard where the platform offers it (Windows, Wayland) when there is no HTML (#687). Tracked insertions and deletions read and written (`\revtbl`, `\revised`/`\deleted`, authors, `\revdttm` dates), so Accept/Reject All work after opening or a round trip; paragraph-mark and formatting revisions not yet. Sections on import (#1378): each `\sect` ends a section with its own page size and orientation, margins, header/footer distance, columns, break type, page-number restart, start and format, title page and headers/footers (those it leaves out link to the previous); export still writes one section. Thin: comments, notes, section export, RTL | | `.odt` OpenDocument Text | 2% | R/W | 🟡 | 🟡 | ~50% | 8–12 | `crates/formats/src/tests.rs`, `crates/formats/src/odt.rs` unit tests, `crates/engine/tests/odt_sections.rs` | Styles, lists (#87), tables, pictures, headers/footers, page breaks (#86), page size, orientation and margins (#810), sections: one per master-page change on a paragraph or table (next page, with its own page size, orientation, margins, columns and page-numbering restart) and per columned `text:section` (continuous, with its columns and separator line) (#1379); tracked insertions and deletions with author and date, inserted and deleted paragraph marks and text inserted then deleted included (ODF change tracking; format changes read as none; pictures and tables inside a tracked deletion are dropped). No footnotes, comments, fields or RTL | @@ -238,6 +238,7 @@ attributes inside stories and style or list definitions; a regenerated theme. Te | 2026-10-14 | trivial | RTF reads and writes tracked insertions and deletions with their authors and dates (gap 19); deleted text no longer opens as normal text | | 2026-10-14 | trivial | ODT import reads sections: a paragraph naming another master page starts a new-page section with that page's size, orientation, margins and columns, and its `style:page-number` restart; a columned `text:section` is a continuous section with its columns (#1379) | | 2026-10-14 | trivial | PDF export writes right-to-left runs' glyphs in visual order, so Chrome, Edge (PDFium) and poppler extract Hebrew and Arabic as whole words (vowelled words keep their letters in order but may extract with spaces inside), and Ctrl+F finds them (#1383). `/ReversedChars` (for strict file-order readers) covers plain runs and vowelled runs whose marks split into their own glyphs, not runs with ligatures or mixed directions | +| 2026-10-16 | trivial | PDF export: right-to-left clusters drawn at the same x (lam-alef as a lam and a zero-width alef, as in the static Noto Naskh Arabic builds) are written in reverse text order, so the run keeps `/ReversedChars` and السلام no longer extracts as السالم (#2310) | | 2026-10-14 | trivial | HTML: `` bold, cell `text-align` (own or inherited) reaches the cell's paragraphs; HTML, Markdown and plain text open on the region's paper, in the app and `wordcraft-cli` (#1381) | | 2026-10-13 | trivial | PDF export draws horizontal and vertical lines inside groups (#1474) | | 2026-10-14 | trivial | RTF import keeps sections: page size and orientation, margins, columns, break type, page numbering and each section's headers and footers (#1378) | From e5ffb15cb545830d5bf5949028eb02cc119d8563 Mon Sep 17 00:00:00 2001 From: harshpsi9194 Date: Fri, 16 Oct 2026 11:18:03 +0530 Subject: [PATCH 054/336] fix(ui): borrow fallback fonts and preserve visual order for recent files (#2067) --- crates/ui-egui/src/backstage.rs | 6 ++-- crates/ui-egui/src/canvas.rs | 23 ++++++++++++- crates/ui-egui/src/lib.rs | 57 +++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 3 deletions(-) diff --git a/crates/ui-egui/src/backstage.rs b/crates/ui-egui/src/backstage.rs index b89665ad3b..c244880b0d 100644 --- a/crates/ui-egui/src/backstage.rs +++ b/crates/ui-egui/src/backstage.rs @@ -353,8 +353,10 @@ fn open_list(app: &mut WordApp, ui: &mut Ui) { ui.painter().rect_filled(r, 4.0, t.hover); } icons::paint(ui.painter(), Rect::from_center_size(pos2(r.min.x + 18.0, r.center().y), vec2(22.0, 22.0)), "printLayout", t.icon, t.accent); - ui.painter().text(pos2(r.min.x + 40.0, r.min.y + 13.0), Align2::LEFT_CENTER, &name, medium(13.0), t.text); - ui.painter().text(pos2(r.min.x + 40.0, r.min.y + 29.0), Align2::LEFT_CENTER, &p, regular(11.0), t.text_dim); + let display_name = crate::canvas::bidi_display(&name); + let display_p = crate::canvas::bidi_display(&p); + ui.painter().text(pos2(r.min.x + 40.0, r.min.y + 13.0), Align2::LEFT_CENTER, display_name.as_ref(), medium(13.0), t.text); + ui.painter().text(pos2(r.min.x + 40.0, r.min.y + 29.0), Align2::LEFT_CENTER, display_p.as_ref(), regular(11.0), t.text_dim); if resp.clicked() { let _ = app.run("file.open", json!({"path": p})); app.ui.backstage = false; diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 4eca0d304b..255a18551d 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -1841,10 +1841,24 @@ fn card_shapes(painter: &egui::Painter, t: &Tokens, card: Rect, color: Color32, } /// Does `text` hold right-to-left letters (Arabic, Hebrew, Syriac, Thaana, N'Ko …)? -fn has_rtl(text: &str) -> bool { +pub(crate) fn has_rtl(text: &str) -> bool { text.chars().any(|c| matches!(unicode_bidi::bidi_class(c), unicode_bidi::BidiClass::R | unicode_bidi::BidiClass::AL)) } +/// Put `text` into visual display order for egui when it contains right-to-left characters. +/// Uses an LTR base paragraph direction so left-aligned UI elements (recent files, paths) preserve +/// trailing extensions and numbers in their proper visual order. +pub(crate) fn bidi_display(text: &str) -> std::borrow::Cow<'_, str> { + if !has_rtl(text) { + return std::borrow::Cow::Borrowed(text); + } + let info = unicode_bidi::BidiInfo::new(text, Some(unicode_bidi::Level::ltr())); + match info.paragraphs.first() { + Some(p) => std::borrow::Cow::Owned(visual_row(&info, p, text, 0..text.len())), + None => std::borrow::Cow::Borrowed(text), + } +} + /// The row `line` of paragraph `p` of `para` in display order, for egui: the bidi runs left to /// right as they are shown, and inside a right-to-left run its words reversed. The letters of each /// word stay in logical order, because egui's shaper already runs an Arabic word right to left. @@ -3622,6 +3636,13 @@ mod tests { assert_eq!(visual_row(&info, p, text, 0..text.len() + 5), ""); } + #[test] + fn bidi_display_preserves_extensions_and_numbers() { + assert_eq!(bidi_display("report.docx").as_ref(), "report.docx"); + assert_eq!(bidi_display("نواقص 2026.docx").as_ref(), "2026 نواقص.docx"); + assert_eq!(bidi_display("مذكرة داخلية.docx").as_ref(), "داخلية مذكرة.docx"); + } + #[test] fn a_balloon_edits_its_comment() { let mut a = WordApp::new(wordcraft_engine::Session::new(wordcraft_doc::Document::new()), Default::default()); diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 662516fbd6..7f88cf6a15 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -472,6 +472,8 @@ pub struct WordApp { /// The document (id, revision) whose comment text [`WordApp::ensure_comment_fonts`] last /// looked at. comment_fonts_for: Option<(u64, u64)>, + /// The list of recent document paths [`WordApp::ensure_recent_fonts`] last looked at. + recent_fonts_for: Vec, fonts_frames: u32, /// The effective interface theme last installed in egui ([`theme::apply`]). applied_theme: Option, @@ -599,6 +601,7 @@ impl WordApp { fonts_system_cjk: false, want_system_cjk: false, comment_fonts_for: None, + recent_fonts_for: Vec::new(), fonts_frames: 0, applied_theme: None, native_scale: 1.0, @@ -1711,6 +1714,32 @@ impl WordApp { } } + /// Recent documents in Backstage (and the Start screen) show file names and paths with the + /// interface faces. Borrow document fallback faces for characters in non-Latin scripts (#2067), + /// as the caption and comments do. + fn ensure_recent_fonts(&mut self) { + if self.recent_fonts_for.as_slice() == self.ui.recent.as_slice() { + return; + } + self.recent_fonts_for = self.ui.recent.clone(); + for p in &self.ui.recent { + if p.chars().any(wordcraft_fonts::is_cjk) { + self.want_system_cjk = true; + } + let new = theme::faces_for_preedit(p, &self.canvas.ime_faces, theme::caption_char_covered, |c| { + wordcraft_fonts::FontDb::global().fallback_for(c, u32::MAX) + }); + if !new.is_empty() { + for face in new { + if !self.canvas.ime_faces.iter().any(|f| f.id() == face.id()) { + self.canvas.ime_faces.push(face); + } + } + self.styled = false; + } + } + } + /// Document title for the title bar (and the suggested file name). An untitled document is /// named in the interface language, as `Document1` is in English. pub fn title_stem(&self) -> String { @@ -1758,6 +1787,7 @@ impl WordApp { // in this frame (dialog, drop, control channel) is picked up again at the end. self.ensure_caption_fonts(); self.ensure_comment_fonts(); + self.ensure_recent_fonts(); self.install_ui_fonts(ctx, lang); if let Some(answer) = self.desktop_theme.take_change() { self.desktop_dark = Some(answer); @@ -1868,6 +1898,7 @@ impl WordApp { // The open above may have changed the file name the caption draws. self.ensure_caption_fonts(); self.ensure_comment_fonts(); + self.ensure_recent_fonts(); self.install_ui_fonts(ctx, lang); self.report_dirty(); } @@ -2561,6 +2592,32 @@ mod tests { assert_eq!(arabic.canvas.ime_faces.len(), n, "the face is added once"); } + /// Recent documents in Backstage and the Start screen draw file names and paths with the + /// interface faces, which have no Arabic, Hebrew, Thai …: a recent document borrows a + /// document fallback face that covers it (#2067). + #[test] + fn recent_documents_borrow_fallback_faces() { + let ctx = egui::Context::default(); + let frame = |app: &mut WordApp| { + ctx.run_ui(egui::RawInput::default(), |ui| app.logic(ui.ctx())).drop_without_applying_deltas(); + }; + let mut latin = app(); + latin.ui.recent.push("/home/ice/report.docx".into()); + frame(&mut latin); + assert!(latin.canvas.ime_faces.is_empty(), "a Latin file name needs no extra face"); + + let mut arabic = app(); + arabic.ui.recent.push("/home/ice/نواقص 2026.docx".into()); + frame(&mut arabic); + let letter = 'ن'; + if wordcraft_fonts::FontDb::global().fallback_for(letter, u32::MAX).is_some_and(|f| f.data().len() <= 16 << 20) { + assert!(arabic.canvas.ime_faces.iter().any(|f| f.covers(letter)), "the recent list borrows a face that covers the file name"); + } + let n = arabic.canvas.ime_faces.len(); + frame(&mut arabic); + assert_eq!(arabic.canvas.ime_faces.len(), n, "the face is added once"); + } + fn app() -> WordApp { WordApp::new(Session::new(wordcraft_doc::Document::new()), Services::default()) } From 02fc9e6f9a785d938df49d0e17607bd891f362a1 Mon Sep 17 00:00:00 2001 From: Diego Guerini Date: Fri, 16 Oct 2026 08:10:22 +0200 Subject: [PATCH 055/336] #2388: AltGr types its characters instead of opening KeyTips on Linux MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Wayland, AltGr (Right Alt on Polish, German, Czech… layouts) arrives as the Right Alt key followed by a modifiers event without Alt, since it turns on Level 3 Shift (Mod5), not Alt. The KeyTips state machine read that event as Alt let go with nothing pressed in between and opened KeyTips while AltGr was still held, so the next letter went to the badges instead of typing ś, ł, ż. A modifiers event without Alt no longer ends the press while an Alt key is still down by key events (its key-up does), and a release opens KeyTips only if the Alt modifier was on during the press (Left Alt always counts), so a bare AltGr tap doesn't open them either. Right Alt that is Alt (US layout) still does. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/keys.rs | 52 +++++++++++++++++++++++++++++++++++ crates/ui-egui/src/keytips.rs | 19 +++++++++++-- crates/ui-egui/src/lib.rs | 10 +++++++ docs/typing-parity.md | 4 ++- docs/ui-parity.md | 5 ++-- 5 files changed, 84 insertions(+), 6 deletions(-) diff --git a/crates/ui-egui/src/keys.rs b/crates/ui-egui/src/keys.rs index 8723b5a9e0..6bfebbe923 100644 --- a/crates/ui-egui/src/keys.rs +++ b/crates/ui-egui/src/keys.rs @@ -713,6 +713,58 @@ mod tests { assert_eq!(a.ui.keytips, crate::keytips::Phase::Tabs); } + /// #2388: AltGr (Right Alt on a Polish layout under Wayland) arrives as the Right Alt key + /// followed by a modifiers event without Alt (it's Level 3 Shift). That event read as Alt + /// let go opened keytips while AltGr was still held, and the next letter went to them + /// instead of typing ś, ł, ż. A bare AltGr tap doesn't open keytips either; a Right Alt + /// that is Alt (US layout) still does. + #[test] + fn altgr_types_its_characters_without_keytips() { + if cfg!(target_os = "macos") { + return; + } + let doc_text = |a: &WordApp| a.session.doc.plain_text(wordcraft_doc::StoryRef::Body); + let ctx = egui::Context::default(); + let mut a = WordApp::new(wordcraft_engine::Session::new(wordcraft_doc::Document::new()), crate::Services::default()); + let none = Modifiers::NONE; + let key = |key, pressed, modifiers| egui::Event::Key { key, physical_key: Some(key), pressed, repeat: false, modifiers }; + let frame = |a: &mut WordApp, events: Vec| { + ctx.run_ui(egui::RawInput { events, ..Default::default() }, |ui| { + crate::keytips::logic(a, ui.ctx()); + canvas_events(a, ui.ctx()); + }) + .drop_without_applying_deltas(); + }; + let altgr_down = || vec![key(Key::AltRight, true, none), egui::Event::ModifiersChanged(none)]; + let altgr_up = || vec![key(Key::AltRight, false, none), egui::Event::ModifiersChanged(none)]; + // Held through three letters, one frame per event batch. + frame(&mut a, altgr_down()); + assert_eq!(a.ui.keytips, crate::keytips::Phase::Off, "AltGr down doesn't open keytips"); + for (k, t) in [(Key::S, "ś"), (Key::L, "ł"), (Key::Z, "ż")] { + frame(&mut a, vec![key(k, true, none), egui::Event::Text(t.into())]); + frame(&mut a, vec![key(k, false, none)]); + assert_eq!(a.ui.keytips, crate::keytips::Phase::Off, "{t}"); + } + frame(&mut a, altgr_up()); + assert_eq!(doc_text(&a).trim_end(), "śłż"); + assert_eq!(a.ui.keytips, crate::keytips::Phase::Off); + // All in one frame. + frame(&mut a, [altgr_down(), vec![key(Key::A, true, none), egui::Event::Text("ą".into()), key(Key::A, false, none)], altgr_up()].concat()); + assert_eq!(doc_text(&a).trim_end(), "śłżą"); + assert_eq!(a.ui.keytips, crate::keytips::Phase::Off); + // A bare AltGr tap, in two frames or one. + frame(&mut a, altgr_down()); + frame(&mut a, altgr_up()); + assert_eq!(a.ui.keytips, crate::keytips::Phase::Off, "an AltGr tap isn't an Alt tap"); + frame(&mut a, [altgr_down(), altgr_up()].concat()); + assert_eq!(a.ui.keytips, crate::keytips::Phase::Off); + // Right Alt that is Alt still opens keytips. + let alt = Modifiers { alt: true, ..Default::default() }; + frame(&mut a, vec![key(Key::AltRight, true, none), egui::Event::ModifiersChanged(alt)]); + frame(&mut a, vec![key(Key::AltRight, false, alt), egui::Event::ModifiersChanged(none)]); + assert_eq!(a.ui.keytips, crate::keytips::Phase::Tabs); + } + fn alt_frame_app() -> (egui::Context, WordApp) { let a = WordApp::new(wordcraft_engine::Session::new(wordcraft_doc::Document::new()), crate::Services::default()); (egui::Context::default(), a) diff --git a/crates/ui-egui/src/keytips.rs b/crates/ui-egui/src/keytips.rs index 45c8a1c9e5..c4e7790be1 100644 --- a/crates/ui-egui/src/keytips.rs +++ b/crates/ui-egui/src/keytips.rs @@ -642,18 +642,24 @@ pub fn suppressed(app: &WordApp, ctx: &egui::Context) -> bool { /// Native backends report Alt through both key and modifier events, in either order. Track /// the transition once; the key-up event can still carry the old `alt: true` modifier state. +/// A release is a keytip tap only if the Alt modifier was on during the press: AltGr (Right Alt +/// on Polish, German, Czech… layouts) is a Right Alt key without it on Linux and Ctrl+Alt on +/// Windows, and types characters instead (#2388). fn alt_transition(app: &mut WordApp, pressed: bool, modifiers: egui::Modifiers) -> bool { let other_modifier = modifiers.ctrl || modifiers.shift || modifiers.command || modifiers.mac_cmd; if pressed { if !app.ui.alt_pressed { app.ui.alt_chord_used = false; + app.ui.alt_modifier_seen = false; } app.ui.alt_pressed = true; app.ui.alt_chord_used |= other_modifier; + app.ui.alt_modifier_seen |= modifiers.alt; false } else { let released = std::mem::take(&mut app.ui.alt_pressed); - released && !std::mem::take(&mut app.ui.alt_chord_used) && !other_modifier + let seen = std::mem::take(&mut app.ui.alt_modifier_seen) || modifiers.alt; + released && !std::mem::take(&mut app.ui.alt_chord_used) && !other_modifier && seen } } @@ -670,6 +676,8 @@ pub fn logic(app: &mut WordApp, ctx: &egui::Context) { app.ui.keytip_prefix.clear(); app.ui.alt_pressed = false; app.ui.alt_chord_used = false; + app.ui.alt_key_down = false; + app.ui.alt_modifier_seen = false; app.keytip_rects.clear(); ctx.data_mut(|d| { d.insert_temp(phase_id(), Phase::Off); @@ -690,7 +698,10 @@ pub fn logic(app: &mut WordApp, ctx: &egui::Context) { match e { egui::Event::Key { key, pressed, modifiers, .. } => { if is_alt(*key) { - alt_released |= alt_transition(app, *pressed, *modifiers); + app.ui.alt_key_down = *pressed; + // Left Alt is never AltGr, whatever modifiers the event carries. + let modifiers = egui::Modifiers { alt: modifiers.alt || *key == egui::Key::AltLeft, ..*modifiers }; + alt_released |= alt_transition(app, *pressed, modifiers); } else if *pressed { other_key = true; app.ui.alt_chord_used = true; @@ -699,7 +710,9 @@ pub fn logic(app: &mut WordApp, ctx: &egui::Context) { } } } - egui::Event::ModifiersChanged(modifiers) => { + // Alt off while an Alt key is still down (AltGr on Linux turns Level 3 Shift on, not + // Alt): the key's release ends the press, not this. + egui::Event::ModifiersChanged(modifiers) if !(app.ui.alt_key_down && !modifiers.alt) => { alt_released |= alt_transition(app, modifiers.alt, *modifiers); } egui::Event::PointerButton { pressed: true, .. } => { diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 662516fbd6..2ddb2c03eb 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -283,6 +283,14 @@ pub struct UiState { /// that follows ends a chord (Alt+click, Alt+drag column selection), not a keytip tap. #[serde(skip)] pub alt_chord_used: bool, + /// An Alt key is down according to key events. Its modifier can go before the key does: + /// AltGr on Linux reports the Right Alt key with Level 3 Shift, not Alt (#2388). + #[serde(skip)] + pub alt_key_down: bool, + /// Since the last Alt press the Alt modifier was on, or the key was Left Alt: a Right Alt + /// that never turns it on is AltGr, which types characters and never opens keytips (#2388). + #[serde(skip)] + pub alt_modifier_seen: bool, /// View › Switch Modes: show pages dark (white text on black), kept between runs. Only the /// pages: the interface follows [`UiState::theme`] (#312). pub dark_page: bool, @@ -363,6 +371,8 @@ impl Default for UiState { keytips: crate::keytips::Phase::Off, alt_pressed: false, alt_chord_used: false, + alt_key_down: false, + alt_modifier_seen: false, dark_page: false, caret_animation: true, keyboard: wordcraft_engine::KeyMap::default(), diff --git a/docs/typing-parity.md b/docs/typing-parity.md index f4de6ecab6..cf5ba819e0 100644 --- a/docs/typing-parity.md +++ b/docs/typing-parity.md @@ -1,6 +1,6 @@ # Typing parity with Word -> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-14 · **Change:** trivial (Shift+Delete cuts the selection on every platform, #1669; previously trivial: Select All then Delete removes a table the document starts with, #1198; previously trivial: Alt+numpad character codes, #961; previously trivial: Down and Up from a line start beside a table go into it, #1007; previously trivial: sample-text formulas on Enter, #1067; previously trivial: Track Changes: Accept All after a tracked delete from a paragraph end keeps the next paragraph; Reject All after typing into an empty paragraph keeps it (#864); previously minor: Correct TWo INitial CApitals, cAPS LOCK and table-cell capitals, #544; previously trivial: complex-script caret and Backspace, #604; Move Paragraph Up/Down, Promote/Demote, Go Back and Alt+X keys, #343) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 +> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-16 · **Change:** trivial (AltGr types its characters without opening KeyTips on Linux, #2388; previously trivial: Shift+Delete cuts the selection on every platform, #1669; previously trivial: Select All then Delete removes a table the document starts with, #1198; previously trivial: Alt+numpad character codes, #961; previously trivial: Down and Up from a line start beside a table go into it, #1007; previously trivial: sample-text formulas on Enter, #1067; previously trivial: Track Changes: Accept All after a tracked delete from a paragraph end keeps the next paragraph; Reject All after typing into an empty paragraph keeps it (#864); previously minor: Correct TWo INitial CApitals, cAPS LOCK and table-cell capitals, #544; previously trivial: complex-script caret and Backspace, #604; Move Paragraph Up/Down, Promote/Demote, Go Back and Alt+X keys, #343) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 What Word does when you type, observed black-box (same keystrokes typed into Word and WordCraft, results read back paragraph by paragraph) and pinned by tests in @@ -79,6 +79,7 @@ Windows/Linux keys are given first, macOS second. | Alt+X (no macOS default) | the hex code before the caret (up to six digits, `U+` optional) or the selected code becomes its character; otherwise the character before the caret becomes its code (`é` ↔ `00E9`); one undo step | `alt_x_toggles_between_a_character_and_its_code` | | Shift+Backspace / Shift+Delete | Shift+Backspace erases like Backspace (#707). Shift+Delete cuts the selection to the clipboard and does nothing without one (Word for Windows, observed 2026-10-14). Windows delivers Shift+Delete as a Cut; macOS and Linux deliver the key, which did nothing (#1669) | `shift_delete_cuts_the_selection_and_shift_backspace_erases` | | Alt+numpad code (Windows and Linux; not macOS) | hold Alt, type a decimal code, release Alt: the character appears at the caret as typed text (one undo step). With a leading 0 the code is the ANSI code page — Windows-1252 by default (`0233` → é, `0128` → €); without one, 1–255 is the OEM code page — 437 by default and in US English (`233` → Θ, `1` → ☺, `7` → •), 850 for Western European locales and English outside the US (`155` → ø; `233` is Ú there, per the CP850 mapping file — é is `130` in both). On Windows the system locale picks both pages (`text.typeCode` `ansi`/`oem`): Central European 1250/852 (Polish `0185` and `165` → ą), Cyrillic 1251/866, Greek 1253/737, Turkish 1254/857, and for codes up to 255 the character Windows composes itself replaces ours (it knows every code page, double-byte ones too); Linux keeps 1252 and 437/850. Above 255, with or without the 0, the Unicode character with that decimal value (`8364` → €). Control characters, 0, unassigned bytes and values past Unicode type nothing; auto-repeat of a held digit adds nothing, and switching windows drops a half-typed code. Word counts only keypad digits; egui reports keypad and top-row digits alike, so top-row digits with Alt alone compose codes too. An Alt code never opens KeyTips | `alt_numpad_codes_map_like_word_on_windows`, `alt_numpad_code_types_one_character`, ui-egui `alt_numpad_code_types_its_character_without_keytips` | +| AltGr (Right Alt on Polish, German, Czech… layouts) | AltGr+letter types the layout's character (AltGr+S → ś, AltGr+L → ł) and a bare AltGr tap does nothing; neither opens KeyTips. On Linux AltGr is the Right Alt key with Level 3 Shift, not Alt, so its modifiers event no longer reads as Alt let go (#2388); on Windows it is Ctrl+Alt. Right Alt on a layout without AltGr (US) is Alt and opens KeyTips like Left Alt | ui-egui `altgr_types_its_characters_without_keytips` | | Shift+Right (⇧→) at the end of a cell's text | the whole cell is selected first (its end-of-cell mark); the next Shift+Right selects the next cell with it. In an empty cell the first press selects the cell | `shift_right_at_the_end_of_a_cell_selects_the_cell_first` | ## AutoCorrect and AutoFormat as you type @@ -196,6 +197,7 @@ Word. | Date | Change | Summary | |---|---|---| +| 2026-10-16 | trivial | AltGr on Linux (Wayland) types ś, ł, ż… instead of opening KeyTips: the modifiers event without Alt that follows the Right Alt key no longer reads as Alt let go, and a bare AltGr tap doesn't open KeyTips (#2388) | | 2026-10-14 | trivial | Shift+Delete cuts the selection, and does nothing without one, on macOS and Linux too, as in Word for Windows (#1669); Shift+Backspace already erased (#707) | | 2026-10-13 | trivial | Select All then Delete or Backspace removes a table the document starts with (#1198) | | 2026-10-12 | trivial | Editing keys: Down and Up from the start of a line beside a table go into it instead of skipping it (#1007) | diff --git a/docs/ui-parity.md b/docs/ui-parity.md index b1c503f5c5..81ab034372 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -1,6 +1,6 @@ # UI parity with Microsoft Word -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** 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-16 · **Change:** trivial (AltGr doesn't open KeyTips, #2388; 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 @@ -13,7 +13,7 @@ Document title opens Rename Document; the account badge opens Options › Genera | Area | WordCraft | Word | Parity | Hours | |---|---|---|---|---| | Ribbon tabs and groups | All Word tabs including contextual Table Design/Layout, Picture Format, Shape Format, Header & Footer, Equation, Outlining (Outline view, #493); ribbon overflow (#43, #129). Draw tab: Select, Eraser, Pen, Pencil, Highlighter with colour and thickness (#307); Lasso (drag to move, Delete, recolour), Add Pen gallery (right-click to remove), Ink to Shape (lines, arrows, rectangles, ellipses, polygons), Ink Replay with play/pause/rewind/forward bar (#395); missing: Ink to Math. The app opens on Home every launch, as in Word: the active tab is not restored between runs, but going File and back to the editor returns to the tab that was showing, contextual tabs included | Same plus Draw | 85% | (Draw in features) | -| Keytips (Alt / F10) | Ribbon badges (#43) and numbered Quick Access Toolbar badges in the user's button order (#620); File › Options › Advanced › Display switch saved per user (`ui.keytips`); Alt leaves macOS Option typing alone | ✅ | 85% | 1–2 | +| Keytips (Alt / F10) | Ribbon badges (#43) and numbered Quick Access Toolbar badges in the user's button order (#620); File › Options › Advanced › Display switch saved per user (`ui.keytips`); Alt leaves macOS Option typing alone, and AltGr leaves typing alone on Linux and Windows (#2388) | ✅ | 85% | 1–2 | | Mini-toolbar on selection | ✅ (#43, two rows #535) | ✅ | 80% | 1–2 | | Colour drop-downs: theme/standard colours, Recent Colors, More Colors… → Colors dialog (Standard honeycomb, Custom hue/saturation field with lightness, RGB/HSL and hex) | ✅ Font Color (ribbon, mini toolbar, Font dialog), Shading, Page Color, Shape Fill/Outline/Glow, pen colours, dialog colour fields (#606); no Gradient or eyedropper | ✅ | 90% | 1 | | Quick Access Toolbar | AutoSave, then the user's commands (Save, Undo, Redo by default); add, remove and reorder from its … menu (common commands, More Commands…), right-click on any ribbon button, or Customize Ribbon; saved per user (#379, #376). Undo's ▾ list: recent steps newest first, hover highlights the steps a click undoes ("Undo 3 Actions"), one click undoes them all (#701; `edit.undoTo`, `edit.undoHistory`). Not shown below the ribbon | customizable | 90% | 0.5 | @@ -59,6 +59,7 @@ Document title opens Rename Document; the account badge opens Options › Genera | Date | Change | Summary | |---|---|---| +| 2026-10-16 | trivial | KeyTips: AltGr (Right Alt with Level 3 Shift on Linux) is not an Alt tap, held or tapped, so AltGr+letter types instead of reaching the badges (#2388) | | 2026-10-13 | trivial | New/Modify Table Style: No Border on a part that inherits its lines hides them; emptying the font box makes the part's font inherited again (#1288) | | 2026-10-14 | trivial | Tab stops edited on the ruler: corner kind selector, click to set, drag to move or off to clear, per-kind markers mirrored for RTL; each gesture one `para.tabs` call and one undo step; Rulers 85% → 90% | | 2026-10-12 | trivial | Tables on the page: one selected cell (Select Cell, or a click along its left edge) is a cell selection when its border is dragged: the border moves in its row only (#962) | From 425e01dec9de10e29d371bd0cf64f3bed57e5d9c Mon Sep 17 00:00:00 2001 From: Fin Warman Date: Fri, 16 Oct 2026 09:10:01 +0100 Subject: [PATCH 056/336] 244: preserve list metrics in keep-next lookahead --- crates/layout/src/lib.rs | 13 ++++++----- crates/layout/src/tests.rs | 47 ++++++++++++++++++++++++++++++++++++++ docs/layout-parity.md | 3 ++- 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/crates/layout/src/lib.rs b/crates/layout/src/lib.rs index 3ff6484361..632b7dde95 100644 --- a/crates/layout/src/lib.rs +++ b/crates/layout/src/lib.rs @@ -3176,7 +3176,7 @@ fn place_para_in_flow(ctx: &mut Ctx, pb: &mut PageBuilder, p: &Paragraph, block: // next alone moves a paragraph only when it would fit here by itself (Word for Windows): one // that doesn't breaks as usual, its last lines going over with the next paragraph. if pl.rp.keep_lines || (pl.rp.keep_next && pb.y + pl.height <= pb.bottom + 0.01) { - let need = pl.height + if pl.rp.keep_next { next_first_line(ctx, block, width, 0) } else { 0.0 }; + let need = pl.height + if pl.rp.keep_next { next_first_line(ctx, block, width, 0, &mut ctx.counters.clone()) } else { 0.0 }; // (Against the whole column: balanced columns grow to hold it.) if pb.y + need > pb.bottom && !pb.at_top() && need <= pb.uncapped_bottom() - pb.top { pb.advance(block, body_top); @@ -3601,15 +3601,17 @@ fn space_after_kept(ctx: &Ctx, block: usize, rp: &wordcraft_doc::resolve::Resolv /// of it under keep lines together — or all of it and what it keeps with in turn when it is /// kept with the next too (a chain moves together in Word). Word carries a kept heading over /// when the paragraph after it cannot start on the page (#966). -fn next_first_line(ctx: &mut Ctx, block: usize, width: f32, depth: usize) -> f32 { +fn next_first_line(ctx: &mut Ctx, block: usize, width: f32, depth: usize, counters: &mut Counters) -> f32 { match ctx.doc.body.get(block + 1).map(|b| &**b) { Some(Block::Para(p)) => { - // Don't advance list counters for a lookahead: lay out without the label. + // Keep list indents and label metrics without consuming the document counters. let rp = ctx.doc.styles.resolve_para(&p.props); + let label = + p.props.numbering.or(rp.numbering).filter(|n| n.num != 0).and_then(|n| counters.next_label(&ctx.doc.numbering, n.num, n.level)); let env = para::ParaEnv { doc: ctx.doc, width, - label: None, + label, fields: &ctx.fields, show_hidden: ctx.opts.show_hidden, field_codes: ctx.opts.field_codes, @@ -3623,10 +3625,9 @@ fn next_first_line(ctx: &mut Ctx, block: usize, width: f32, depth: usize) -> f32 grid: ctx.grid, fit_objects: false, }; - let _ = rp; let pl = para::layout_para(p, &env); if pl.rp.keep_next && depth < 8 { - pl.rp.space_before + pl.height + pl.rp.space_after + next_first_line(ctx, block + 1, width, depth + 1) + pl.rp.space_before + pl.height + pl.rp.space_after + next_first_line(ctx, block + 1, width, depth + 1, counters) } else { // The room this one needs to start on the page with the kept paragraph before // it (#966). diff --git a/crates/layout/src/tests.rs b/crates/layout/src/tests.rs index d6865b0d92..00d215f864 100644 --- a/crates/layout/src/tests.rs +++ b/crates/layout/src/tests.rs @@ -7263,3 +7263,50 @@ fn row_nil_overrides_style() { d.insert_block(StoryRef::Body, &Path::top(1), wordcraft_doc::Block::Table(t)).unwrap(); assert_eq!(rules_on_page0(&lay(&d)), vec![2.0]); } + +#[test] +fn keep_next_counts_list_indent() { + use wordcraft_doc::props::{LineSpacing, NumRef}; + let mut doc = Document::new(); + doc.last_section.page_w = 240.0; + doc.last_section.page_h = 200.0; + doc.last_section.margin_top = 20.0; + doc.last_section.margin_bottom = 20.0; + doc.last_section.margin_left = 20.0; + doc.last_section.margin_right = 20.0; + let id = doc.numbering.add_list(wordcraft_doc::ListKind::Numbered); + let level = &mut doc.numbering.abstracts[0].levels[0]; + level.indent = 100.0; + level.hanging = 0.0; + let props = ParaProps { + space_before: Some(0.0), + space_after: Some(0.0), + line_spacing: Some(LineSpacing::Exactly(12.0)), + widow_control: Some(true), + ..Default::default() + }; + let para = |text: &str, props: ParaProps| { + let mut p = + Paragraph::with_text(text, wordcraft_doc::CharProps { font: Some("Source Sans 3".into()), size: Some(12.0), ..Default::default() }); + p.props = props; + wordcraft_doc::para_block(p) + }; + doc.body = vec![ + para("Filler", ParaProps { line_spacing: Some(LineSpacing::Exactly(136.0)), ..props.clone() }), + para("Heading", ParaProps { keep_next: Some(true), keep_lines: Some(true), ..props.clone() }), + para("Alpha beta gamma delta", ParaProps { numbering: Some(NumRef { num: id, level: 0 }), ..props }), + ]; + let layout = lay(&doc); + assert_eq!(layout.caret(&Pos::body(2, 0)).unwrap().page, 1, "the indented list cannot start below the heading"); + assert_eq!(layout.caret(&Pos::body(1, 0)).unwrap().page, 1, "the kept heading must follow it"); + let labels: Vec<_> = layout + .pages + .iter() + .flat_map(|p| &p.items) + .filter_map(|item| match item { + Placed::Lines { para, .. } => para.label.as_ref().map(|label| label.text.as_str()), + _ => None, + }) + .collect(); + assert_eq!(labels, ["1."], "lookahead must not consume a list counter"); +} diff --git a/docs/layout-parity.md b/docs/layout-parity.md index eb6ffad0a2..40c026edfb 100644 --- a/docs/layout-parity.md +++ b/docs/layout-parity.md @@ -1,6 +1,6 @@ # Layout and pagination parity with Microsoft Word -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** trivial (consistent cell widths override a stale table grid; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: page-break-only list paragraphs hide their labels; previously trivial: floating-table overlap ignores empty line-break lines; previously trivial: paragraph spacing adds up when the document turns HTML auto spacing off; previously trivial: an inherited footer sizes its percentage tables by its own section; previously trivial: an empty section mark stays on a full page; previously trivial: a fitting row with less room than its minimum height moves on whole; oversized rows still split; previously trivial: text past an exact-height row is cut off; previously trivial: a list number stays with text after a leading page break; previously trivial: a bookmark after a page break takes no line; previously trivial: a page break followed by a page break before or a next-page section starts one page; previously trivial: All Caps and Small Caps draw ß as SS, #1581; previously trivial: justified vertical page alignment, #1320; previously trivial: page numbers restart at 0; previously trivial: a list label moves with its first line when the paragraph is centred or end-aligned (#1159); previously trivial: PAGE fields in a paragraph split across pages show the page they are on, #1352; previously trivial: a dragged floating object anchors in the column it lands in, #884; previously trivial: a paragraph ending on a page with footnotes keeps its space after above the separator, #1116; previously trivial: symbol-font text drawn as Unicode symbols, #776; previously trivial: large-table scrolling measured and fixed, #811; previously trivial: text boxes use their own margins; Resize shape to fit text never cuts text, #743; previously trivial: complex-script shaping and Myanmar line breaking, #604; previously trivial: body page totals, table-cell footnotes and bookmark link pages fixed, #450 #451 #455; previously major: first version; line breaking, pagination and object placement checked against Word's behaviour from the layout source on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-15 · **Change:** trivial (keep-with-next lookahead retains list indents and label metrics without advancing counters; previously trivial: consistent cell widths override a stale table grid; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: page-break-only list paragraphs hide their labels; previously trivial: floating-table overlap ignores empty line-break lines; previously trivial: paragraph spacing adds up when the document turns HTML auto spacing off; previously trivial: an inherited footer sizes its percentage tables by its own section; previously trivial: an empty section mark stays on a full page; previously trivial: a fitting row with less room than its minimum height moves on whole; oversized rows still split; previously trivial: text past an exact-height row is cut off; previously trivial: a list number stays with text after a leading page break; previously trivial: a bookmark after a page break takes no line; previously trivial: a page break followed by a page break before or a next-page section starts one page; previously trivial: All Caps and Small Caps draw ß as SS, #1581; previously trivial: justified vertical page alignment, #1320; previously trivial: page numbers restart at 0; previously trivial: a list label moves with its first line when the paragraph is centred or end-aligned (#1159); previously trivial: PAGE fields in a paragraph split across pages show the page they are on, #1352; previously trivial: a dragged floating object anchors in the column it lands in, #884; previously trivial: a paragraph ending on a page with footnotes keeps its space after above the separator, #1116; previously trivial: symbol-font text drawn as Unicode symbols, #776; previously trivial: large-table scrolling measured and fixed, #811; previously trivial: text boxes use their own margins; Resize shape to fit text never cuts text, #743; previously trivial: complex-script shaping and Myanmar line breaking, #604; previously trivial: body page totals, table-cell footnotes and bookmark link pages fixed, #450 #451 #455; previously major: first version; line breaking, pagination and object placement checked against Word's behaviour from the layout source on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 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, @@ -159,6 +159,7 @@ No overall parity estimate changes from these probes. | Date | Change | Summary | |---|---|---| +| 2026-10-15 | trivial | Keep-with-next lookahead uses copied list counters so list indents and label metrics match placement. An original regression keeps a heading with an indented list that needs two lines. | | 2026-10-14 | trivial | Row table border exceptions draw without leaking into other rows | | 2026-10-14 | trivial | Consistent cell widths override a stale table grid | | 2026-10-14 | trivial | Paragraphs inside complex-field instructions take no flow height; empty result paragraphs keep their line. | From 3e2cf5edb445111d84495d139f8e3e6957124411 Mon Sep 17 00:00:00 2001 From: Fin Warman Date: Fri, 16 Oct 2026 11:25:51 +0100 Subject: [PATCH 057/336] DOCX: preserve text boxes containing pictures (task 256) --- ROADMAP.md | 4 +++- crates/docx/src/read/story.rs | 4 ++-- crates/docx/tests/text_boxes.rs | 38 +++++++++++++++++++++++++++++++++ docs/file-format-parity.md | 3 ++- docs/gaps.md | 3 ++- 5 files changed, 47 insertions(+), 5 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 40407d18b6..4ce94a605b 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **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-14 · **Change:** 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-16 · **Change:** trivial (drawing dispatch uses the direct graphic-data child so nested pictures keep their text boxes; 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-14 · **Change:** trivial (progress log: print-area strings translated in every catalog; 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. @@ -135,6 +135,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm ## Progress log | Date | What landed | +| 2026-10-16 | Drawing dispatch uses the direct graphic-data child so nested pictures keep their text boxes. | | 2026-10-14 | DOCX row table border exceptions render and survive saving. Explicit cell borders win; a row exception does not change other rows. | | 2026-10-14 | Tab stops edited on the ruler: the corner selector picks Left/Center/Right/Decimal/Bar, a click sets a stop (Alt skips the snap), dragging moves it with a guide line and position tooltip, dragging off the ruler clears it; markers draw per kind and mirror for RTL; one `para.tabs` call and one undo step per gesture | | 2026-10-12 | Fields keep `w:dirty` through DOCX saves (#877): a begin marker flagged by a generator that couldn't compute a result (pandoc-style TOC placeholders) keeps the flag on open/save — as a buffered field or a range field — so Word still refreshes it on open; computing a result (Update Field, Update Table) clears it, like Word's F9 | @@ -403,6 +404,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | Date | Change | Summary | |---|---|---| +| 2026-10-16 | trivial | Drawing dispatch uses the direct graphic-data child so nested pictures keep their text boxes. Synthetic regressions. | | 2026-10-14 | trivial | Progress log: row table border exceptions | | 2026-10-12 | minor | Czech proofing review: short-word splits rank after deletion, NOSUGGEST excludes suggestions while accepting typed forms, affix comments apply only to headers, ratios keep spaces and repeated-word messages use Czech quotes; synthetic engine end-to-end and latency checks | | 2026-10-12 | minor | Czech spelling from the installed Hunspell dictionary, runtime-table suggestions and generic checks; Czech interface writes cs-CZ documents; no Czech hyphenation patterns bundled; estimates unchanged | diff --git a/crates/docx/src/read/story.rs b/crates/docx/src/read/story.rs index 9a39f6406a..2ad50d6b87 100644 --- a/crates/docx/src/read/story.rs +++ b/crates/docx/src/read/story.rs @@ -999,8 +999,8 @@ impl Reader<'_> { self.read_canvas(sc, c, rels, w, h, float) } else if let Some(g) = gd.child("wpg:wgp") { self.read_group(sc, g, rels, w, h, float)? - } else if gd.find("a:blip").is_some() { - self.read_pic(gd, rels, w, h, alt.clone(), float)? + } else if let Some(pic) = gd.child("pic:pic") { + self.read_pic(pic, rels, w, h, alt.clone(), float)? } else { let wsp = gd.find("wps:wsp")?; let mut obj = self.read_wsp(sc, wsp, rels, w, h, float); diff --git a/crates/docx/tests/text_boxes.rs b/crates/docx/tests/text_boxes.rs index 7efe7b0405..9fefa22ebc 100644 --- a/crates/docx/tests/text_boxes.rs +++ b/crates/docx/tests/text_boxes.rs @@ -195,3 +195,41 @@ fn hidden_drawings_stay_hidden() { assert_eq!((marked("wp:docPr"), marked("wps:cNvPr")), (3, 1), "{xml}"); assert_eq!(hidden(&wordcraft_docx::read(&saved).unwrap()), want); } + +#[test] +fn picture_in_box_keeps_its_frame() { + use wordcraft_doc::{Paragraph, Pos, StoryRef}; + + let mut d = Document::from_text("Anchor"); + let body = TextBody { anchor: VAlign::Center, inset: Some([0; 4]), ..Default::default() }; + let outer = text_box(&mut d, "Label", body); + let id = outer.text_box().unwrap(); + let mut bytes = std::io::Cursor::new(Vec::new()); + image::RgbaImage::from_pixel(10, 4, image::Rgba([20, 40, 60, 255])).write_to(&mut bytes, image::ImageFormat::Png).unwrap(); + let media = d.add_media(bytes.into_inner(), "png"); + let image = InlineObject::Image { + media: media.clone(), + w: 90.0, + h: 36.0, + alt: "Synthetic marker".into(), + float: Default::default(), + crop: [0.0; 4], + ole: None, + }; + let mut p = Paragraph::new(); + p.insert_object(0, image, &Default::default()).unwrap(); + d.parts.get_mut(&id).unwrap().blocks.push(para_block(p)); + d.insert_object(&Pos::body(0, 0), outer, &Default::default()).unwrap(); + + let read = wordcraft_docx::read(&wordcraft_docx::write(&d).unwrap()).unwrap(); + let ids = boxes(&read); + assert_eq!(ids.len(), 1, "a nested picture must not replace its text box"); + let id = ids[0]; + assert_eq!(read.parts[&id].body, body); + assert!(read.plain_text(StoryRef::Part(id)).starts_with("Label")); + let p = read.para(StoryRef::Part(id), &wordcraft_doc::Path::top(1)).unwrap(); + assert!(matches!(&p.objects[0], InlineObject::Image { w, h, alt, .. } + if *w == 90.0 && *h == 36.0 && alt == "Synthetic marker")); + let p = read.para(StoryRef::Body, &wordcraft_doc::Path::top(0)).unwrap(); + assert!(matches!(&p.objects[0], InlineObject::Shape { w, h, .. } if *w == 144.0 && *h == 72.0)); +} diff --git a/docs/file-format-parity.md b/docs/file-format-parity.md index 38a1638f16..f8ef733662 100644 --- a/docs/file-format-parity.md +++ b/docs/file-format-parity.md @@ -1,6 +1,6 @@ # File-format parity with Microsoft Word -> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-14 · **Change:** trivial (every table style part round-trips, #714); previously trivial: VML freeforms read and kept on save; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: `flowChartOr` draws its circle and cross spokes, as in Word; previously trivial: RTF: Mac OS Roman and DOS text, #1653; previously trivial: VML text boxes keep their internal margins, fit to their text and vertical alignment; hidden drawings not shown; previously trivial: PDF export draws horizontal and vertical lines inside groups, #1474; previously trivial: symbol-font text exported as Unicode, #776; previously trivial: text-box margins, Resize shape to fit text and freeforms in groups, #743; previously trivial: formatted paste from the system clipboard's HTML and RTF, #687; previously trivial: independent complex-script formatting inheritance and round-trip regression tests; previously trivial: import/export fixes for TXT, RTF, ODT, HTML and LaTeX, #433–#440; previously major: first version; every format Word reads or writes, measured from the readers and writers on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4, plus Word for Windows' extra formats +> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-16 · **Change:** trivial (drawing dispatch uses the direct graphic-data child so nested pictures keep their text boxes; previously trivial: every table style part round-trips, #714); previously trivial: VML freeforms read and kept on save; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: `flowChartOr` draws its circle and cross spokes, as in Word; previously trivial: RTF: Mac OS Roman and DOS text, #1653; previously trivial: VML text boxes keep their internal margins, fit to their text and vertical alignment; hidden drawings not shown; previously trivial: PDF export draws horizontal and vertical lines inside groups, #1474; previously trivial: symbol-font text exported as Unicode, #776; previously trivial: text-box margins, Resize shape to fit text and freeforms in groups, #743; previously trivial: formatted paste from the system clipboard's HTML and RTF, #687; previously trivial: independent complex-script formatting inheritance and round-trip regression tests; previously trivial: import/export fixes for TXT, RTF, ODT, HTML and LaTeX, #433–#440; previously major: first version; every format Word reads or writes, measured from the readers and writers on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4, plus Word for Windows' extra formats Every format Word opens or saves, what WordCraft does with it, and how it's tested. Fidelity is the share of a typical real-world file's content and formatting that survives (read: shows correctly; @@ -210,6 +210,7 @@ attributes inside stories and style or list definitions; a regenerated theme. Te | Date | Change | Summary | |---|---|---| +| 2026-10-16 | trivial | Drawing dispatch uses the direct graphic-data child so nested pictures keep their text boxes. Synthetic regressions. | | 2026-10-13 | trivial | A cell's explicit top vertical alignment (`w:vAlign w:val="top"`) is kept on read and write and beats a table style that centres or bottom-aligns (#1288) | | 2026-10-14 | trivial | Row table border exceptions survive DOCX saves | | 2026-10-12 | trivial | Fields keep `w:dirty` through DOCX round trips — on buffered fields and range-field begin markers alike — and computing a result (Update Field, Update Table) clears it like Word's F9, so a generator's placeholder TOC still refreshes when Word opens the file until WordCraft replaces the placeholder (#877) | diff --git a/docs/gaps.md b/docs/gaps.md index 3d1d7fdafd..c9f4d22fef 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -1,6 +1,6 @@ # Where WordCraft falls short of Word -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** trivial (VML freeforms read and kept on save; previously trivial: 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: floating-table overlap ignores empty line-break lines; previously trivial: copies keep their formatting on the clipboard as HTML, #1523; previously trivial: sample-text formulas on Enter, #1067; previously trivial: #29: copied equations as MathML, OMML open as #1297, #816; previously trivial: the Font list no longer keeps every previewed font in memory, #848; 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 Table and Eraser landed, #303; previously minor: beta distance restated: ~15 points, ~150–230 h; previously: first version; every known shortfall from the 2026-10-10 re-measure, ranked) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-16 · **Change:** trivial (drawing dispatch uses the direct graphic-data child so nested pictures keep their text boxes; previously trivial: VML freeforms read and kept on save; previously trivial: 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: floating-table overlap ignores empty line-break lines; previously trivial: copies keep their formatting on the clipboard as HTML, #1523; previously trivial: sample-text formulas on Enter, #1067; previously trivial: #29: copied equations as MathML, OMML open as #1297, #816; previously trivial: the Font list no longer keeps every previewed font in memory, #848; 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 Table and Eraser landed, #303; previously minor: beta distance restated: ~15 points, ~150–230 h; previously: first version; every known shortfall from the 2026-10-10 re-measure, ranked) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 > **Printing update:** 2026-10-12 · direct desktop printing through craft-print; details below. @@ -87,6 +87,7 @@ The same gaps grouped the way the parity documents are, for agents working in on | Date | Change | Summary | |---|---|---| +| 2026-10-16 | trivial | Drawing dispatch uses the direct graphic-data child so nested pictures keep their text boxes. Synthetic regressions. | | 2026-10-14 | trivial | Row table border exceptions fixed; other row table exceptions remain unsupported | | 2026-10-12 | minor | Czech proofing review: short-word splits rank after deletion, NOSUGGEST excludes suggestions while accepting typed forms, affix comments apply only to headers, ratios keep spaces and repeated-word messages use Czech quotes; synthetic engine end-to-end and latency checks | | 2026-10-12 | minor | Czech spelling from the installed Hunspell dictionary, runtime-table suggestions and generic checks; Czech interface writes cs-CZ documents; no Czech hyphenation patterns bundled; estimates unchanged | From da47503f1dcd9dcfb4ef3ce66368a73ebc3acfa1 Mon Sep 17 00:00:00 2001 From: Nghiathan13 Date: Fri, 16 Oct 2026 17:39:37 +0700 Subject: [PATCH 058/336] #2360: a text frame in the paragraph defaults frames nothing Apple Pages writes a sizeless w:framePr (xAlign left, y 0, hRule exact) into w:pPrDefault. WordCraft read it as the default paragraph frame, so every paragraph inherited it. The header's title of traditional_skills_job_form was drawn from the page's left edge over the first table, the footer's page number left the bottom of the page, and table rows came out shorter than in Word. Word ignores that frame: its PDF of the same file shows the title at the left margin and "1/4" in the footer. Taking just that element out of styles.xml gives WordCraft the same page. The reader now drops the frame from the paragraph defaults and keeps the rest of them. Frames from styles and direct formatting are unchanged, and the default frame isn't written back on save. Co-Authored-By: Claude Opus 5.5 --- ROADMAP.md | 4 +++- crates/docx/src/read/mod.rs | 4 ++++ crates/docx/tests/fixtures.rs | 39 +++++++++++++++++++++++++++++++++++ docs/file-format-parity.md | 3 ++- 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 40407d18b6..f8b65e5a98 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **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-14 · **Change:** 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-16 · **Change:** trivial (progress log: a text frame in the paragraph defaults frames nothing, #2360; 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-14 · **Change:** trivial (progress log: print-area strings translated in every catalog; 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. @@ -135,6 +135,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm ## Progress log | Date | What landed | +| 2026-10-16 | DOCX: a text frame (`w:framePr`) in the document's paragraph defaults frames no paragraph, as in Word (#2360). Files from Apple Pages carry a sizeless one at the top left, which put every paragraph in the corner of the page: a header's title over the first table, the footer's page number out of sight, table rows of the wrong height | | 2026-10-14 | DOCX row table border exceptions render and survive saving. Explicit cell borders win; a row exception does not change other rows. | | 2026-10-14 | Tab stops edited on the ruler: the corner selector picks Left/Center/Right/Decimal/Bar, a click sets a stop (Alt skips the snap), dragging moves it with a guide line and position tooltip, dragging off the ruler clears it; markers draw per kind and mirror for RTL; one `para.tabs` call and one undo step per gesture | | 2026-10-12 | Fields keep `w:dirty` through DOCX saves (#877): a begin marker flagged by a generator that couldn't compute a result (pandoc-style TOC placeholders) keeps the flag on open/save — as a buffered field or a range field — so Word still refreshes it on open; computing a result (Update Field, Update Table) clears it, like Word's F9 | @@ -403,6 +404,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | Date | Change | Summary | |---|---|---| +| 2026-10-16 | trivial | Progress log: a text frame in the paragraph defaults frames nothing (#2360) | | 2026-10-14 | trivial | Progress log: row table border exceptions | | 2026-10-12 | minor | Czech proofing review: short-word splits rank after deletion, NOSUGGEST excludes suggestions while accepting typed forms, affix comments apply only to headers, ratios keep spaces and repeated-word messages use Czech quotes; synthetic engine end-to-end and latency checks | | 2026-10-12 | minor | Czech spelling from the installed Hunspell dictionary, runtime-table suggestions and generic checks; Czech interface writes cs-CZ documents; no Czech hyphenation patterns bundled; estimates unchanged | diff --git a/crates/docx/src/read/mod.rs b/crates/docx/src/read/mod.rs index 523b50c085..72451c4dd6 100644 --- a/crates/docx/src/read/mod.rs +++ b/crates/docx/src/read/mod.rs @@ -806,6 +806,10 @@ impl Reader<'_> { } if let Some(p) = dd.child("w:pPrDefault").and_then(|d| d.child("w:pPr")) { sheet.default_para = self.pc.ppr(p).0; + // Word makes no paragraph a text frame from the document defaults: the sizeless + // top-left `w:framePr` Apple Pages writes there would put every paragraph, the + // headers' and footers' included, at the page's corner (#2360). + sheet.default_para.frame = None; } } if sheet.default_chr.size.is_none() { diff --git a/crates/docx/tests/fixtures.rs b/crates/docx/tests/fixtures.rs index 2c36a3bb40..248d7adbef 100644 --- a/crates/docx/tests/fixtures.rs +++ b/crates/docx/tests/fixtures.rs @@ -2634,6 +2634,45 @@ fn text_frames_read_and_round_trip() { assert!(q.iter().any(|x| x.props.drop_cap == Some(2) && x.props.frame.is_none())); } +/// #2360: a `w:framePr` in the document defaults, the sizeless top-left one Apple Pages writes, +/// frames no paragraph, as in Word: a header's title and the body's text stay in the text instead +/// of moving to the page's corner, and the default isn't written back. The defaults' other +/// properties are kept, and a style's own frame still applies. +#[test] +fn a_frame_in_the_paragraph_defaults_frames_nothing() { + let styles = format!( + r#""# + ); + let header = format!(r#"Title"#); + let body = r#"BodyBoxed"#; + let bytes = docx( + body, + &[("rIdS", "styles", "styles.xml"), ("rIdH", "header", "header1.xml")], + &[("word/styles.xml", &styles), ("word/header1.xml", &header)], + ); + let check = |d: &Document| { + assert!(d.styles.default_para.frame.is_none(), "{:?}", d.styles.default_para.frame); + assert_eq!(d.styles.default_para.space_after, Some(6.0), "the defaults' spacing is kept"); + let frame = |p: &Paragraph| d.styles.resolve_para(&p.props).frame; + let body = paras(d); + assert_eq!(body[0].text, "Body"); + assert!(frame(body[0]).is_none(), "body text is not a frame"); + let boxed = frame(body[1]).expect("a style's frame still applies"); + assert_eq!((boxed.h_anchor.as_str(), boxed.x_align.as_deref()), ("margin", Some("right"))); + let hid = d.last_section.headers.default.expect("a header"); + let title = d.parts.get(&hid).unwrap().blocks.iter().find_map(|b| b.as_para()).expect("the title"); + assert_eq!(title.text, "Title"); + assert!(frame(title).is_none(), "the header's title is not a frame"); + }; + let d = wordcraft_docx::read(&bytes).unwrap(); + check(&d); + let saved = wordcraft_docx::write(&d).unwrap(); + let xml = text(&unzip(&saved), "word/styles.xml"); + let defaults = xml.split("").next().unwrap_or_default(); + assert!(!defaults.contains("w:framePr"), "the default frame isn't written back: {defaults}"); + check(&wordcraft_docx::read(&saved).unwrap()); +} + /// A legacy VML freeform (`v:shape` with a `path`, ECMA-376 Part 4): a rule across a header, /// drawn in a 1000 × 1000 coordinate space, outlined, its fill transparent. It is read as a /// freeform of the shape's size and kept when saved (as DrawingML custom geometry) and read back. diff --git a/docs/file-format-parity.md b/docs/file-format-parity.md index 38a1638f16..07c11e1b8d 100644 --- a/docs/file-format-parity.md +++ b/docs/file-format-parity.md @@ -1,6 +1,6 @@ # File-format parity with Microsoft Word -> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-14 · **Change:** trivial (every table style part round-trips, #714); previously trivial: VML freeforms read and kept on save; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: `flowChartOr` draws its circle and cross spokes, as in Word; previously trivial: RTF: Mac OS Roman and DOS text, #1653; previously trivial: VML text boxes keep their internal margins, fit to their text and vertical alignment; hidden drawings not shown; previously trivial: PDF export draws horizontal and vertical lines inside groups, #1474; previously trivial: symbol-font text exported as Unicode, #776; previously trivial: text-box margins, Resize shape to fit text and freeforms in groups, #743; previously trivial: formatted paste from the system clipboard's HTML and RTF, #687; previously trivial: independent complex-script formatting inheritance and round-trip regression tests; previously trivial: import/export fixes for TXT, RTF, ODT, HTML and LaTeX, #433–#440; previously major: first version; every format Word reads or writes, measured from the readers and writers on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4, plus Word for Windows' extra formats +> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-16 · **Change:** trivial (a text frame in the paragraph defaults frames nothing, #2360; previously trivial: every table style part round-trips, #714); previously trivial: VML freeforms read and kept on save; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: `flowChartOr` draws its circle and cross spokes, as in Word; previously trivial: RTF: Mac OS Roman and DOS text, #1653; previously trivial: VML text boxes keep their internal margins, fit to their text and vertical alignment; hidden drawings not shown; previously trivial: PDF export draws horizontal and vertical lines inside groups, #1474; previously trivial: symbol-font text exported as Unicode, #776; previously trivial: text-box margins, Resize shape to fit text and freeforms in groups, #743; previously trivial: formatted paste from the system clipboard's HTML and RTF, #687; previously trivial: independent complex-script formatting inheritance and round-trip regression tests; previously trivial: import/export fixes for TXT, RTF, ODT, HTML and LaTeX, #433–#440; previously major: first version; every format Word reads or writes, measured from the readers and writers on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4, plus Word for Windows' extra formats Every format Word opens or saves, what WordCraft does with it, and how it's tested. Fidelity is the share of a typical real-world file's content and formatting that survives (read: shows correctly; @@ -210,6 +210,7 @@ attributes inside stories and style or list definitions; a regenerated theme. Te | Date | Change | Summary | |---|---|---| +| 2026-10-16 | trivial | A `w:framePr` in `w:pPrDefault` (Apple Pages writes a sizeless one at the top left) frames no paragraph and isn't written back, as Word ignores it; headers, footers and table rows of such files lay out as in Word (#2360) | | 2026-10-13 | trivial | A cell's explicit top vertical alignment (`w:vAlign w:val="top"`) is kept on read and write and beats a table style that centres or bottom-aligns (#1288) | | 2026-10-14 | trivial | Row table border exceptions survive DOCX saves | | 2026-10-12 | trivial | Fields keep `w:dirty` through DOCX round trips — on buffered fields and range-field begin markers alike — and computing a result (Update Field, Update Table) clears it like Word's F9, so a generator's placeholder TOC still refreshes when Word opens the file until WordCraft replaces the placeholder (#877) | From 0d1ede12912652b07cb28ab75b98600f0a4bfbab Mon Sep 17 00:00:00 2001 From: Nghiathan13 Date: Fri, 16 Oct 2026 18:27:15 +0700 Subject: [PATCH 059/336] #2376: a text file saved over itself closes without asking Opening note.txt, typing, saving with Ctrl+S and closing still asked "Do you want to save changes to note.txt?". Save in that prompt wrote the file but left the window open, and only Don't Save closed it. file.save marked the document saved only for formats that keep everything (.docx, .odt, .rtf...). A save in any other format counted as a copy, even over the file the document was opened from, so a .txt or .md document could never become clean. The Save Changes prompt then treated its own Save as "not saved" and cancelled the close. A save over the document's own file now marks it saved in any format. Save As to plain text, Markdown or a page image elsewhere is still a copy that leaves the document unsaved, as before. AutoSave still covers only the formats that keep everything. Co-Authored-By: Claude Opus 5.5 --- ROADMAP.md | 4 +++- crates/engine/src/cmd/file.rs | 5 ++++- crates/engine/src/tests.rs | 35 ++++++++++++++++++++++++++++++++++ crates/ui-egui/src/lib.rs | 36 +++++++++++++++++++++++++++++++++-- docs/ui-parity.md | 3 ++- 5 files changed, 78 insertions(+), 5 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 40407d18b6..dad80536b4 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **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-14 · **Change:** 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-16 · **Change:** trivial (progress log: a text file saved over itself closes without asking, #2376; 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-14 · **Change:** trivial (progress log: print-area strings translated in every catalog; 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. @@ -135,6 +135,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm ## Progress log | Date | What landed | +| 2026-10-16 | A plain-text or Markdown file opened and saved is saved: closing it no longer asks to save again, and Save in that prompt closes the window (#2376). Saving over the document's own file used to count as a copy, like Save As to plain text elsewhere, so the prompt came back after every save and only Don't Save closed the app | | 2026-10-14 | DOCX row table border exceptions render and survive saving. Explicit cell borders win; a row exception does not change other rows. | | 2026-10-14 | Tab stops edited on the ruler: the corner selector picks Left/Center/Right/Decimal/Bar, a click sets a stop (Alt skips the snap), dragging moves it with a guide line and position tooltip, dragging off the ruler clears it; markers draw per kind and mirror for RTL; one `para.tabs` call and one undo step per gesture | | 2026-10-12 | Fields keep `w:dirty` through DOCX saves (#877): a begin marker flagged by a generator that couldn't compute a result (pandoc-style TOC placeholders) keeps the flag on open/save — as a buffered field or a range field — so Word still refreshes it on open; computing a result (Update Field, Update Table) clears it, like Word's F9 | @@ -403,6 +404,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | Date | Change | Summary | |---|---|---| +| 2026-10-16 | trivial | Progress log: a text file saved over itself closes without asking (#2376) | | 2026-10-14 | trivial | Progress log: row table border exceptions | | 2026-10-12 | minor | Czech proofing review: short-word splits rank after deletion, NOSUGGEST excludes suggestions while accepting typed forms, affix comments apply only to headers, ratios keep spaces and repeated-word messages use Czech quotes; synthetic engine end-to-end and latency checks | | 2026-10-12 | minor | Czech spelling from the installed Hunspell dictionary, runtime-table suggestions and generic checks; Czech interface writes cs-CZ documents; no Czech hyphenation patterns bundled; estimates unchanged | diff --git a/crates/engine/src/cmd/file.rs b/crates/engine/src/cmd/file.rs index ecdff7f5fb..0906060177 100644 --- a/crates/engine/src/cmd/file.rs +++ b/crates/engine/src/cmd/file.rs @@ -298,7 +298,10 @@ fn save(s: &mut Session, v: &Value) -> CmdResult { let copy = super::insert::saved_copy(s); crate::io::save_path_with(&path, copy.as_ref().unwrap_or(&s.doc), password, &opts).map_err(CmdError::Failed)?; let encrypted = password.is_some() && crate::io::can_encrypt(&path.to_string_lossy()); - if ["docx", "docm", "dotx", "dotm", "odt", "rtf", "json", "xml"].contains(&ext.as_str()) { + // A save in a format that doesn't keep everything (plain text, Markdown, a page image) is a + // copy, except over the document's own file: a note.txt opened and saved is saved (#2376). + let own_file = s.path.as_deref() == Some(path.as_path()); + if own_file || ["docx", "docm", "dotx", "dotm", "odt", "rtf", "json", "xml"].contains(&ext.as_str()) { s.path = Some(path.clone()); s.dirty = false; } diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 038786e02a..edd2b9d943 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -3192,6 +3192,41 @@ fn mailings_results_are_new_untitled_documents() { let _ = std::fs::remove_dir_all(&dir); } +/// #2376: a note.txt opened and saved stayed unsaved, so closing asked to save it forever. Saving +/// the document's own file is a save in any format; Save As to plain text elsewhere is a copy. +#[cfg(not(target_arch = "wasm32"))] +#[test] +fn saving_a_text_file_over_itself_saves_the_document() { + let dir = std::env::temp_dir().join(format!("wordcraft-engine-own-txt-{}", std::process::id())); + let _ = std::fs::remove_dir_all(&dir); + std::fs::create_dir_all(&dir).unwrap(); + for name in ["note.txt", "note.md"] { + let path = dir.join(name); + std::fs::write(&path, b"").unwrap(); + let mut s = s(); + run(&mut s, "file.open", json!({"path": path.to_string_lossy()})); + run(&mut s, "text.insert", json!({"text": "Hello"})); + assert!(s.dirty); + assert_eq!(run(&mut s, "file.save", json!({}))["saved"], true); + assert!(!s.dirty, "{name}: saved over itself"); + assert!(std::fs::read_to_string(&path).unwrap().contains("Hello"), "{name}: written"); + + // Saving the same file by name is the same save. + run(&mut s, "text.insert", json!({"text": " again"})); + run(&mut s, "file.save", json!({"path": path.to_string_lossy()})); + assert!(!s.dirty, "{name}: saved by its own path"); + + // Plain text somewhere else is still a copy: the document keeps its file and its changes. + run(&mut s, "text.insert", json!({"text": "!"})); + let copy = dir.join(format!("copy-{name}")); + run(&mut s, "file.saveAs", json!({"path": copy.to_string_lossy()})); + assert!(copy.exists()); + assert!(s.dirty, "{name}: a copy elsewhere leaves it unsaved"); + assert_eq!(s.path.as_deref(), Some(path.as_path())); + } + let _ = std::fs::remove_dir_all(&dir); +} + #[cfg(not(target_arch = "wasm32"))] #[test] fn html_pictures_load_relative_to_the_file() { diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 662516fbd6..180a866cda 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -1037,7 +1037,8 @@ impl WordApp { /// Save before New/Open/Close (and the mailings that replace the document): true once the /// document is safely written. A save in a format that doesn't keep everything (a page /// image, plain text) writes a copy and leaves the document unsaved, so it doesn't count: - /// the command is cancelled and the document stays. A save that fails (no permission, the + /// the command is cancelled and the document stays. A save over the document's own file + /// (a note.txt that was opened) is a save in any format (#2376). A save that fails (no permission, the /// file locked by another app, a missing folder) is the save's error ([`Self::save_failed`] /// tells the user). /// In the browser the save picker may write the file later ([`PromptSave::Later`], #1315). @@ -2264,7 +2265,8 @@ impl AutoSaveBlock { /// Whether saving to `name` keeps the whole document (the formats `file.save` treats as the /// document's own file, including macro-enabled documents and templates); anything else is a -/// copy that leaves it unsaved. AutoSave writes only these formats. +/// copy that leaves it unsaved, unless it is the file the document was opened from (#2376). +/// AutoSave writes only these formats. pub fn keeps_everything(name: &str) -> bool { let ext = std::path::Path::new(name).extension().map(|e| e.to_string_lossy().to_ascii_lowercase()).unwrap_or_default(); matches!(ext.as_str(), "docx" | "docm" | "dotx" | "dotm" | "odt" | "rtf" | "json" | "xml") @@ -4101,6 +4103,36 @@ mod tests { let _ = std::fs::remove_dir_all(&dir); } + /// #2376: a text file opened, edited and saved still asked to save on close, and Save in that + /// prompt wrote the file but left the window open; only Don't Save closed it. + #[test] + fn a_text_file_saved_over_itself_closes() { + let dir = scratch("close-txt"); + let path = dir.join("note.txt"); + std::fs::write(&path, b"").unwrap(); + + // Saved with Ctrl+S, then closed: nothing to ask. + let mut a = app(); + a.run("file.open", json!({"path": path.to_string_lossy()})).unwrap(); + a.run("text.insert", json!({"text": "Hello"})).unwrap(); + a.run("file.save", json!({})).unwrap(); + assert!(a.close_requested(), "a saved text file closes"); + assert_eq!(prompt(&a), None); + + // Edited again, then closed: Save in the prompt writes the file and lets the window close. + let mut a = app(); + a.run("file.open", json!({"path": path.to_string_lossy()})).unwrap(); + a.run("text.insert", json!({"text": "Revised "})).unwrap(); + assert!(!a.close_requested()); + assert_eq!(prompt(&a), Some("saveChanges")); + let r = a.run("ui.saveChanges", json!({"answer": "save"})).unwrap(); + assert_eq!(r["done"], true, "{r}"); + assert!(std::fs::read_to_string(&path).unwrap().contains("Revised Hello"), "the edit is on disk"); + assert!(a.quit_requested, "the app may close"); + assert!(a.close_requested(), "the close that follows goes ahead"); + let _ = std::fs::remove_dir_all(&dir); + } + /// #1445: when Save in the close prompt can't write the file, the prompt's answer is an error /// that says so plainly, the status bar shows it, and the document stays open, still edited. #[test] diff --git a/docs/ui-parity.md b/docs/ui-parity.md index b1c503f5c5..4bcd2cab42 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -1,6 +1,6 @@ # UI parity with Microsoft Word -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** 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-16 · **Change:** trivial (Save Changes prompt: a text file saved over itself closes, #2376; 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 @@ -59,6 +59,7 @@ Document title opens Rename Document; the account badge opens Options › Genera | Date | Change | Summary | |---|---|---| +| 2026-10-16 | trivial | Save Changes prompt: a plain-text or Markdown file the document was opened from counts as saved once Save writes it, so closing after Ctrl+S asks nothing and Save in the prompt closes the window. Save As to such a format elsewhere is still a copy that leaves the document unsaved (#2376) | | 2026-10-13 | trivial | New/Modify Table Style: No Border on a part that inherits its lines hides them; emptying the font box makes the part's font inherited again (#1288) | | 2026-10-14 | trivial | Tab stops edited on the ruler: corner kind selector, click to set, drag to move or off to clear, per-kind markers mirrored for RTL; each gesture one `para.tabs` call and one undo step; Rulers 85% → 90% | | 2026-10-12 | trivial | Tables on the page: one selected cell (Select Cell, or a click along its left edge) is a cell selection when its border is dragged: the border moves in its row only (#962) | From b407859f74997777daa739edba7aff6919f73159 Mon Sep 17 00:00:00 2001 From: Nghiathan13 Date: Fri, 16 Oct 2026 18:52:15 +0700 Subject: [PATCH 060/336] #2174: HYPERLINK fields read as links Word for the web writes each table of contents entry as a HYPERLINK complex field around the heading's text, a tab and a PAGEREF field. The DOCX reader buffered the whole field into one field object with a flat text result, so the tab was drawn as a box instead of reaching the dotted right tab stop, the page number was no longer a field of its own, and the link could not be followed. Rewriting those fields as w:hyperlink elements makes WordCraft draw the TOC as Word does. A HYPERLINK field, complex or simple, now reads the way a w:hyperlink does: its result stays ordinary content carrying the link (url, url#bookmark or #bookmark from \l), with nested fields kept. It is written back as w:hyperlink; the tooltip (\o) and target frame (\t) are dropped. A HYPERLINK field with no target stays a field. Co-Authored-By: Claude Opus 5.5 --- ROADMAP.md | 4 +- crates/docx/src/read/story.rs | 76 +++++++++++++++++++++++++++++++++ crates/docx/tests/toc_ranges.rs | 68 +++++++++++++++++++++++++++++ docs/file-format-parity.md | 5 ++- 4 files changed, 150 insertions(+), 3 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 40407d18b6..ac3cd94490 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **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-14 · **Change:** 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-16 · **Change:** trivial (progress log: `HYPERLINK` fields read as links, #2174; 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-14 · **Change:** trivial (progress log: print-area strings translated in every catalog; 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. @@ -135,6 +135,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm ## Progress log | Date | What landed | +| 2026-10-16 | DOCX: a `HYPERLINK` field (complex or simple) reads as a link, as a `w:hyperlink` does: its result stays ordinary text carrying the link (`url`, `url#bookmark` or `#bookmark`), so a tab in it reaches its tab stop and a `PAGEREF` in it stays a field (#2174). A table of contents from Word for the web, whose entries are such fields, showed a box for each tab and no dot leaders; the entries now open as in Word and their links work | | 2026-10-14 | DOCX row table border exceptions render and survive saving. Explicit cell borders win; a row exception does not change other rows. | | 2026-10-14 | Tab stops edited on the ruler: the corner selector picks Left/Center/Right/Decimal/Bar, a click sets a stop (Alt skips the snap), dragging moves it with a guide line and position tooltip, dragging off the ruler clears it; markers draw per kind and mirror for RTL; one `para.tabs` call and one undo step per gesture | | 2026-10-12 | Fields keep `w:dirty` through DOCX saves (#877): a begin marker flagged by a generator that couldn't compute a result (pandoc-style TOC placeholders) keeps the flag on open/save — as a buffered field or a range field — so Word still refreshes it on open; computing a result (Update Field, Update Table) clears it, like Word's F9 | @@ -403,6 +404,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | Date | Change | Summary | |---|---|---| +| 2026-10-16 | trivial | Progress log: `HYPERLINK` fields read as links (#2174) | | 2026-10-14 | trivial | Progress log: row table border exceptions | | 2026-10-12 | minor | Czech proofing review: short-word splits rank after deletion, NOSUGGEST excludes suggestions while accepting typed forms, affix comments apply only to headers, ratios keep spaces and repeated-word messages use Czech quotes; synthetic engine end-to-end and latency checks | | 2026-10-12 | minor | Czech spelling from the installed Hunspell dictionary, runtime-table suggestions and generic checks; Czech interface writes cs-CZ documents; no Czech hyphenation patterns bundled; estimates unchanged | diff --git a/crates/docx/src/read/story.rs b/crates/docx/src/read/story.rs index 9a39f6406a..59a6611341 100644 --- a/crates/docx/src/read/story.rs +++ b/crates/docx/src/read/story.rs @@ -99,6 +99,69 @@ fn is_range_instr(instr: &str) -> bool { super::is_toc(instr) || instr.trim_start().get(..5).is_some_and(|k| k.eq_ignore_ascii_case("ADDIN")) } +/// Where a `HYPERLINK` field goes, as a `w:hyperlink` reads: `url`, `url#bookmark` (`\l`) or +/// `#bookmark`. None for any other field, or one with no target. +fn hyperlink_target(instr: &str) -> Option { + // Only a `HYPERLINK` code is split up (a field's code may run to megabytes). + if !instr.split_whitespace().next().is_some_and(|k| k.eq_ignore_ascii_case("HYPERLINK")) { + return None; + } + let args = field_args(instr); + let mut args = args.iter(); + if !args.next().is_some_and(|(k, quoted)| !quoted && k.eq_ignore_ascii_case("HYPERLINK")) { + return None; + } + let (mut url, mut anchor) = (None, None); + while let Some((a, quoted)) = args.next() { + if *quoted || !a.starts_with('\\') { + url = url.or(Some(a.as_str())); + continue; + } + // `\l` names a bookmark, `\o` a tooltip and `\t` a target frame; `\m` and `\n` take none. + let value = matches!(a.to_ascii_lowercase().as_str(), "\\l" | "\\o" | "\\t").then(|| args.next()).flatten(); + if a.eq_ignore_ascii_case("\\l") { + anchor = value.map(|(v, _)| v.as_str()); + } + } + let (url, anchor) = (url.map(str::trim).filter(|u| !u.is_empty()), anchor.map(str::trim).filter(|a| !a.is_empty())); + match (url, anchor) { + (Some(u), Some(a)) => Some(format!("{u}#{a}")), + (Some(u), None) => Some(u.to_string()), + (None, Some(a)) => Some(format!("#{a}")), + (None, None) => None, + } +} + +/// A field code's words: the field's name, its switches and its arguments, each with whether +/// it was quoted. In quotes, `\"` is a quote and `\\` a backslash. +fn field_args(instr: &str) -> Vec<(String, bool)> { + let mut out = Vec::new(); + let mut chars = instr.chars().peekable(); + while let Some(c) = chars.next() { + if c.is_whitespace() { + continue; + } + let mut word = String::new(); + if c == '"' { + while let Some(c) = chars.next() { + match c { + '"' => break, + '\\' if matches!(chars.peek(), Some('"' | '\\')) => word.extend(chars.next()), + _ => word.push(c), + } + } + out.push((word, true)); + } else { + word.push(c); + while let Some(c) = chars.next_if(|c| !c.is_whitespace() && *c != '"') { + word.push(c); + } + out.push((word, false)); + } + } + out +} + /// The last paragraph of a block list: its last block, or inside a final table the last /// paragraph of its last cell (nested tables to [`MAX_TABLE_DEPTH`]). fn last_para_mut(out: &mut Blocks, depth: usize) -> Option<&mut Paragraph> { @@ -849,6 +912,19 @@ impl Reader<'_> { self.emit_obj(sc, pb, InlineObject::FieldEnd, &f.props); return; } + // A `HYPERLINK` field is a link, as a `w:hyperlink` is: its result stays ordinary content + // carrying the link, so a tab in it reaches its tab stop and the `PAGEREF` in a table of + // contents entry (Word for the web writes them this way) stays a field of its own (#2174). + if !nested_in_instr && let Some(target) = hyperlink_target(&f.instr) { + for (it, mut p) in f.buf { + p.link.get_or_insert_with(|| target.clone()); + match it { + Item::Text(t) => self.emit_text(sc, pb, &t, &p), + Item::Obj(o) => self.emit_obj(sc, pb, *o, &p), + } + } + return; + } // A cached result holding a picture, chart or shape (`INCLUDEPICTURE`'s embedded // picture) can't be a text result: keep the field as a range around it so the picture // stays in the document and is saved again. The linked source is never read. diff --git a/crates/docx/tests/toc_ranges.rs b/crates/docx/tests/toc_ranges.rs index d8010bd490..15d8fa3f02 100644 --- a/crates/docx/tests/toc_ranges.rs +++ b/crates/docx/tests/toc_ranges.rs @@ -175,3 +175,71 @@ fn section_break_keeps_an_explicit_range_and_toc_prefix_is_not_a_toc() { ); } } + +/// The paragraph's characters other than objects, each with the link it carries. +fn linked_chars(p: &wordcraft_doc::Paragraph) -> Vec<(char, Option)> { + p.text.char_indices().filter(|(_, c)| *c != wordcraft_doc::para::OBJ).map(|(i, c)| (c, p.props_of_char(i).link.clone())).collect() +} + +/// #2174: Word for the web writes each table of contents entry as a `HYPERLINK` complex field +/// around the heading's text, a tab and a `PAGEREF` field. The entry reads as linked text, as a +/// `w:hyperlink` would: the tab is a tab (it reaches the dotted right tab stop) and the page +/// number stays a field of its own, through a save. +#[test] +fn hyperlink_field_entries_read_as_linked_text_with_their_page_field() { + let entry = |anchor: &str, text: &str, page: &str| { + format!( + r#"{}{text}{}{page}{END}{END}"#, + start(&format!(r#"HYPERLINK \l "{anchor}" "#)), + start(&format!(r#"PAGEREF {anchor} \h"#)) + ) + }; + let ppr = r#""#; + let body = format!( + r#"{ppr}{}{}{ppr}{}{END}After."#, + start(r#"TOC \o \z \u \h"#), + entry("_Toc1", "Intro", "1"), + entry("_Toc2", "Title 2", "2") + ); + let doc = wordcraft_docx::read(&package(&body)).unwrap(); + let saved = wordcraft_docx::read(&wordcraft_docx::write(&doc).unwrap()).unwrap(); + for d in [&doc, &saved] { + for (i, anchor, text, page) in [(0, "_Toc1", "Intro", "1"), (1, "_Toc2", "Title 2", "2")] { + let p = d.body[i].as_para().unwrap(); + assert_eq!(p.plain_text(), format!("{text}\t{page}")); + let link = Some(format!("#{anchor}")); + assert_eq!(linked_chars(p), format!("{text}\t").chars().map(|c| (c, link.clone())).collect::>()); + assert!(p.objects.iter().all(|o| !matches!(o, InlineObject::Field { instr, .. } if instr.starts_with("HYPERLINK")))); + let pageref = format!(r#"PAGEREF {anchor} \h"#); + assert!(p.objects.iter().any(|o| matches!(o, InlineObject::Field { instr, result, .. } if *instr == pageref && result == page))); + } + assert_eq!(d.field_ranges(StoryRef::Body).len(), 1); + assert_eq!(d.body[2].as_para().unwrap().plain_text(), "After."); + } +} + +/// A `HYPERLINK` field's target, whatever the field's spelling: a web address with a bookmark, +/// quoted words with spaces, a tooltip or target frame before it, and a simple field. One with +/// no target stays a field. +#[test] +fn hyperlink_field_targets_follow_the_field_code() { + let simple = |code: &str| format!(r#"Go"#); + let complex = |code: &str| format!(r#"{}Go{END}"#, start(code)); + for (para, link) in [ + (complex(r#"HYPERLINK "https://example.org/a" \l "sec""#), Some("https://example.org/a#sec")), + (complex(r#"hyperlink \o "A tip" \t "_blank" "https://example.org/""#), Some("https://example.org/")), + (complex(r#"HYPERLINK "C:\\Docs\\My file.docx""#), Some(r"C:\Docs\My file.docx")), + (simple(r#"HYPERLINK \l "Sec" \m"#), Some("#Sec")), + (simple("HYPERLINK https://example.org/b"), Some("https://example.org/b")), + (complex("HYPERLINK"), None), + (complex(r#"HYPERLINKS "https://example.org/""#), None), + ] { + let doc = wordcraft_docx::read(&package(¶)).unwrap(); + let p = doc.body[0].as_para().unwrap(); + assert_eq!(p.plain_text(), "Go"); + match link { + Some(l) => assert_eq!(linked_chars(p), "Go".chars().map(|c| (c, Some(l.to_string()))).collect::>(), "{para}"), + None => assert!(matches!(p.objects.as_slice(), [InlineObject::Field { result, .. }] if result == "Go"), "{para}"), + } + } +} diff --git a/docs/file-format-parity.md b/docs/file-format-parity.md index 38a1638f16..0b33421ae2 100644 --- a/docs/file-format-parity.md +++ b/docs/file-format-parity.md @@ -1,6 +1,6 @@ # File-format parity with Microsoft Word -> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-14 · **Change:** trivial (every table style part round-trips, #714); previously trivial: VML freeforms read and kept on save; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: `flowChartOr` draws its circle and cross spokes, as in Word; previously trivial: RTF: Mac OS Roman and DOS text, #1653; previously trivial: VML text boxes keep their internal margins, fit to their text and vertical alignment; hidden drawings not shown; previously trivial: PDF export draws horizontal and vertical lines inside groups, #1474; previously trivial: symbol-font text exported as Unicode, #776; previously trivial: text-box margins, Resize shape to fit text and freeforms in groups, #743; previously trivial: formatted paste from the system clipboard's HTML and RTF, #687; previously trivial: independent complex-script formatting inheritance and round-trip regression tests; previously trivial: import/export fixes for TXT, RTF, ODT, HTML and LaTeX, #433–#440; previously major: first version; every format Word reads or writes, measured from the readers and writers on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4, plus Word for Windows' extra formats +> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-16 · **Change:** trivial (`HYPERLINK` fields read as links, #2174; previously trivial: every table style part round-trips, #714); previously trivial: VML freeforms read and kept on save; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: `flowChartOr` draws its circle and cross spokes, as in Word; previously trivial: RTF: Mac OS Roman and DOS text, #1653; previously trivial: VML text boxes keep their internal margins, fit to their text and vertical alignment; hidden drawings not shown; previously trivial: PDF export draws horizontal and vertical lines inside groups, #1474; previously trivial: symbol-font text exported as Unicode, #776; previously trivial: text-box margins, Resize shape to fit text and freeforms in groups, #743; previously trivial: formatted paste from the system clipboard's HTML and RTF, #687; previously trivial: independent complex-script formatting inheritance and round-trip regression tests; previously trivial: import/export fixes for TXT, RTF, ODT, HTML and LaTeX, #433–#440; previously major: first version; every format Word reads or writes, measured from the readers and writers on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4, plus Word for Windows' extra formats Every format Word opens or saves, what WordCraft does with it, and how it's tested. Fidelity is the share of a typical real-world file's content and formatting that survives (read: shows correctly; @@ -74,7 +74,7 @@ Measured by grepping `crates/docx/src/read` and `src/write` for the OOXML elemen | Footnotes, endnotes, comments | ✅ | ✅ | The document's own note separators and continuation notice (`w:type` `separator`, `continuationSeparator`, `continuationNotice`) round-trip as ids -1, 0 and 1 (#352); note options (`w:pos`, `w:numFmt`, `w:numStart`, `w:numRestart`) round-trip in the settings and in each `w:sectPr` (#385); `commentsExtended` written; `commentsIds` (durable ids) and `commentsExtensible` (UTC dates) read and regenerated consistently with the written paragraph ids; `people.xml` carried verbatim | | Tracked insertions and deletions (`w:ins`/`w:del`), paragraph-mark revisions | ✅ | ✅ | #125, #244 | | Formatting revisions (`w:rPrChange`, `w:pPrChange`, `w:sectPrChange`, `w:tblPrChange`, `w:trPrChange`, `w:tcPrChange`, `w:numberingChange`) | ✅ | ✅ | #41: kept on open and save, schema order (change element last); changes inside `styles.xml` are dropped. RTF/ODT ignore them | -| Fields (`w:fldChar`, `w:fldSimple`), TOC, cross-references, `ADDIN` citations | ✅ | ✅ | Field codes Word supports but we don't evaluate keep their cached result. Instruction-only paragraphs carry hidden marks instead of blank lines; an empty result paragraph keeps its line. Imported TOCs preserve actual result boundaries, formatted entries and nested fields without relying on TOC styles. `EQ \o` (overstrike, enclosed characters) is drawn; enclosed characters are written as Word stores them, their style in the code's run sizes (#297); a cached picture (`INCLUDEPICTURE`) stays as the content of a range field (#465) | +| Fields (`w:fldChar`, `w:fldSimple`), TOC, cross-references, `ADDIN` citations | ✅ | ✅ | Field codes Word supports but we don't evaluate keep their cached result. Instruction-only paragraphs carry hidden marks instead of blank lines; an empty result paragraph keeps its line. Imported TOCs preserve actual result boundaries, formatted entries and nested fields without relying on TOC styles. `EQ \o` (overstrike, enclosed characters) is drawn; enclosed characters are written as Word stores them, their style in the code's run sizes (#297); a cached picture (`INCLUDEPICTURE`) stays as the content of a range field (#465). A `HYPERLINK` field reads as a link, its result ordinary text (tabs, nested `PAGEREF`), and is written back as `w:hyperlink`; its tooltip (`\o`) and target frame (`\t`) are dropped (#2174) | | Move tracking (`w:moveFrom`/`w:moveTo`, `w:moveFromRangeStart/End`, `w:moveToRangeStart/End`), moved paragraph marks | ✅ | ✅ | #400: both ends linked by the range name; ranges rewritten around each moved stretch (ids renumbered); a move missing an end or a range end opens as a plain deletion/insertion. ODT/PDF export the result (moved-away text hidden like deletions); RTF writes a move as a plain tracked deletion and insertion | | Content controls (`w:sdt`) | ✅ | ✅ | Block, inline, row and cell level, nested; title, tag, id, lock, placeholder, temporary, rich/plain text, `w14:checkbox`, combo box, drop-down list, date, picture, building block gallery, `w15` repeating sections; other `w:sdtPr` children (data binding, appearance) kept verbatim (#381). A TOC's control is rebuilt by the TOC. Replace All respects content-edit and control-deletion locks and refuses a batch before making any edit | | Restrict Editing (`w:documentProtection` edit/formatting/enforcement and password hash, `w:locked` styles, `w:permStart`/`w:permEnd` exceptions) | ✅ | ✅ | #412: SHA-512 hash with salt and spin count written; Word's legacy `w:cryptAlgorithmSid`/`w:hash` form (SHA-1/-256/-384/-512, ECMA-376 Part 4 §14.8.1) is kept and verified; MD2/MD4/MD5, `w:algIdExt` and other unsupported forms are refused, never unlocked; `w:colFirst`/`w:colLast` table-column ranges read as plain ranges; forms mode (`w:edit="forms"`) lets edits inside fillable content controls (`w:lock` content-editable, innermost wins) run, everything else rolls back; row-wrapped controls and control deletion remain refused; `review.restrict` reports `formFields`; Find Next Region covers form fields (the pane button shows for forms documents); under active protection, `developer.properties` refuses `lockEdit`/`lockDelete` changes — as Word blocks property edits until Stop Protection — while other properties (title, tag, placeholder…) stay editable | @@ -210,6 +210,7 @@ attributes inside stories and style or list definitions; a regenerated theme. Te | Date | Change | Summary | |---|---|---| +| 2026-10-16 | trivial | A `HYPERLINK` field reads as a link with its result as ordinary text, so a Word for the web table of contents keeps its tabs, dot leaders and page fields (#2174) | | 2026-10-13 | trivial | A cell's explicit top vertical alignment (`w:vAlign w:val="top"`) is kept on read and write and beats a table style that centres or bottom-aligns (#1288) | | 2026-10-14 | trivial | Row table border exceptions survive DOCX saves | | 2026-10-12 | trivial | Fields keep `w:dirty` through DOCX round trips — on buffered fields and range-field begin markers alike — and computing a result (Update Field, Update Table) clears it like Word's F9, so a generator's placeholder TOC still refreshes when Word opens the file until WordCraft replaces the placeholder (#877) | From e30e7fd13283edf468a4bfdd6e927afcb9cc19e8 Mon Sep 17 00:00:00 2001 From: midasmatterhorn <248507632+midasmatterhorn@users.noreply.github.com> Date: Thu, 15 Oct 2026 00:06:33 +0300 Subject: [PATCH 061/336] engine: Update Field evaluates DOCPROPERTY against custom and built-in properties --- crates/engine/src/cmd/fields.rs | 29 +++++++++++++++++++++ crates/engine/src/cmd/references.rs | 40 +++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/crates/engine/src/cmd/fields.rs b/crates/engine/src/cmd/fields.rs index b1a025e1f5..3659ed87ca 100644 --- a/crates/engine/src/cmd/fields.rs +++ b/crates/engine/src/cmd/fields.rs @@ -277,6 +277,35 @@ mod tests { s } + /// #1798: Update Field evaluates `DOCPROPERTY` against custom and built-in document + /// properties (with a `\* MERGEFORMAT` switch), instead of leaving the cached result. + #[test] + fn update_fields_evaluates_docproperty() { + let mut s = Session::new(wordcraft_doc::Document::new()); + s.run("file.properties", &json!({"custom": {"ProjectName": "Atlas"}})).unwrap(); + s.run("insert.field", &json!({"instr": "DOCPROPERTY \"ProjectName\" \\* MERGEFORMAT", "result": "stale"})).unwrap(); + s.run("text.insert", &json!({"text": " "})).unwrap(); + s.doc.core.title = "Report".into(); + s.run("insert.field", &json!({"instr": "DOCPROPERTY Title", "result": ""})).unwrap(); + s.run("references.updateFields", &json!({})).unwrap(); + let results: Vec = s + .doc + .body + .first() + .and_then(|b| b.as_para()) + .map(|p| { + p.objects + .iter() + .filter_map(|o| match o { + InlineObject::Field { result, .. } => Some(result.clone()), + _ => None, + }) + .collect() + }) + .unwrap_or_default(); + assert_eq!(results, ["Atlas", "Report"]); + } + fn page_text(s: &mut Session) -> String { let l = s.layout(); let mut out = String::new(); diff --git a/crates/engine/src/cmd/references.rs b/crates/engine/src/cmd/references.rs index 3e1956d900..f883cd5d53 100644 --- a/crates/engine/src/cmd/references.rs +++ b/crates/engine/src/cmd/references.rs @@ -898,6 +898,14 @@ pub fn update_fields(s: &mut Session) -> Result<(), CmdError> { "TITLE" => updates.push((k, s.doc.core.title.clone())), "AUTHOR" => updates.push((k, s.doc.core.creator.clone())), "NUMWORDS" => updates.push((k, s.doc.word_count().to_string())), + // DOCPROPERTY : a custom property first, then a built-in one (#1798). + "DOCPROPERTY" => { + if let Some(name) = doc_property_arg(instr) + && let Some(v) = doc_property(&s.doc, &name) + { + updates.push((k, v)); + } + } _ => {} } } @@ -956,6 +964,38 @@ pub fn refresh_date_fields(doc: &mut wordcraft_doc::Document) { } } +/// The property name of a `DOCPROPERTY` field: the word (or quoted text) after the field name, +/// before any switch (`DOCPROPERTY "Project Name" \* MERGEFORMAT`). +fn doc_property_arg(instr: &str) -> Option { + let rest = instr.trim().split_once(char::is_whitespace)?.1.trim_start(); + let name = match rest.strip_prefix('"') { + Some(q) => q.split('"').next().unwrap_or(""), + None => rest.split([' ', '\\']).next().unwrap_or(""), + }; + (!name.is_empty()).then(|| name.to_string()) +} + +/// The value of a document property under the name a `DOCPROPERTY` field uses: a custom property +/// when one has this name, else the built-in property (English names, case-insensitively, as +/// Word's field accepts). `None` for a name this application doesn't know. +fn doc_property(doc: &wordcraft_doc::Document, name: &str) -> Option { + if let Some(v) = doc.custom_prop(name) { + return Some(v.to_string()); + } + let c = &doc.core; + Some(match name.trim().to_ascii_lowercase().as_str() { + "title" => c.title.clone(), + "subject" => c.subject.clone(), + "author" | "creator" => c.creator.clone(), + "keywords" => c.keywords.clone(), + "comments" | "description" => c.description.clone(), + "category" => c.category.clone(), + "lastmodifiedby" | "last modified by" => c.last_modified_by.clone(), + "revisionnumber" | "revision number" | "revision" => c.revision.to_string(), + _ => return None, + }) +} + /// A field's `\@` date picture: the quoted text after it (switches may follow), or one word. pub fn date_picture(instr: &str) -> Option { let rest = instr.split_once("\\@")?.1.trim_start(); From 09f065c11f98f3c5c234beec84e17805842749fb Mon Sep 17 00:00:00 2001 From: Fin Warman Date: Fri, 16 Oct 2026 13:48:31 +0100 Subject: [PATCH 062/336] 286: resolve installed PostScript font names --- crates/fonts/src/fontdb.rs | 54 ++++++++++++++++++++++++------ crates/fonts/src/tests_sysfonts.rs | 28 ++++++++++++++++ docs/layout-parity.md | 5 ++- 3 files changed, 76 insertions(+), 11 deletions(-) diff --git a/crates/fonts/src/fontdb.rs b/crates/fonts/src/fontdb.rs index 5f07295138..2f5d4aa49b 100644 --- a/crates/fonts/src/fontdb.rs +++ b/crates/fonts/src/fontdb.rs @@ -60,6 +60,8 @@ pub struct FontFace { /// The family's names in other languages (宋体 for SimSun), from the `name` table. Lookups by /// family name accept them; the font list shows them in a matching interface language. pub local_names: Vec, + /// Exact OpenType name ID 6, distinct from family and style names. + postscript: Option, /// usWeightClass-style weight (400 = regular). pub weight: f32, pub italic: bool, @@ -179,9 +181,11 @@ impl FontFace { } pen.0 } - /// Is `family` this face's typographic or legacy family name? + /// Is `family` this face's typographic, legacy or PostScript name? pub fn named(&self, family: &str) -> bool { - self.family.eq_ignore_ascii_case(family) || self.legacy_family.eq_ignore_ascii_case(family) + self.family.eq_ignore_ascii_case(family) + || self.legacy_family.eq_ignore_ascii_case(family) + || self.postscript.as_ref().is_some_and(|n| n.eq_ignore_ascii_case(family)) } /// Does the face map `c` to a glyph? pub fn covers(&self, c: char) -> bool { @@ -290,6 +294,7 @@ struct CatalogEntry { family: String, style: String, legacy: String, + postscript: Option, local: Vec, /// A Chinese, Japanese or Korean font ([`FontFace::is_east_asian`]). east_asian: bool, @@ -748,6 +753,8 @@ struct Found { style: String, /// The legacy family name ([`FontFace::legacy_family`]). legacy: String, + #[cfg_attr(target_arch = "wasm32", allow(dead_code))] + postscript: Option, /// The family's names in other languages ([`FontFace::local_names`]). local: Vec, /// A Chinese, Japanese or Korean font ([`FontFace::is_east_asian`]); the scan's catalog keeps it. @@ -775,13 +782,13 @@ fn font_faces(f: &skrifa::FontRef<'_>, index: u32) -> Vec { let legacy = legacy_family(&family, &style); // Name ID 1 names the default instance only: keep the names of the whole family. let local = local.iter().filter(|n| n.english.eq_ignore_ascii_case(&family)).cloned().collect(); - named.push(Found { index, family: family.clone(), style, legacy, local, east_asian, coords }); + named.push(Found { index, family: family.clone(), style, legacy, postscript: None, local, east_asian, coords }); } if !named.is_empty() { return named; } let legacy = id1.filter(|n| !n.eq_ignore_ascii_case(&family)).unwrap_or_else(|| legacy_family(&family, &style)); - vec![Found { index, family, style, legacy, local, east_asian, coords: Vec::new() }] + vec![Found { index, family, style, legacy, postscript: name(f, &[StringId::POSTSCRIPT_NAME]), local, east_asian, coords: Vec::new() }] } /// Parse every face in `data` (a font file or collection); a variable font yields one face per @@ -835,6 +842,7 @@ fn make_face(bytes: FontBytes, index: u32, family: String, style: String, legacy style, legacy_family, local_names: Vec::new(), + postscript: coords.is_empty().then(|| name(&f, &[StringId::POSTSCRIPT_NAME])).flatten(), weight, italic, upem: m.units_per_em.max(1) as f64, @@ -1158,6 +1166,7 @@ impl FontDb { family: f.family, style: f.style, legacy: f.legacy, + postscript: f.postscript, local: f.local, east_asian: f.east_asian, path: p.clone(), @@ -1186,7 +1195,11 @@ impl FontDb { let cat = self.read_catalog(); let families: Vec<&str> = cat .iter() - .filter(|c| c.family.eq_ignore_ascii_case(family) || c.legacy.eq_ignore_ascii_case(family)) + .filter(|c| { + c.family.eq_ignore_ascii_case(family) + || c.legacy.eq_ignore_ascii_case(family) + || c.postscript.as_ref().is_some_and(|n| n.eq_ignore_ascii_case(family)) + }) .map(|c| c.family.as_str()) .collect(); let mut p: Vec<_> = cat.iter().filter(|c| families.iter().any(|f| c.family.eq_ignore_ascii_case(f))).map(|c| c.path.clone()).collect(); @@ -1201,7 +1214,7 @@ impl FontDb { /// Resolve a family + style to a face, falling back to the closest style of the family, then to /// Source Sans 3 Regular. `family` is a typographic family ("Roboto") or a face's legacy family /// name ("Roboto Light", whose styles are Light and Light Italic). Installed system fonts are - /// found by name whatever ran before. + /// found by name whatever ran before. An exact PostScript name selects its face. pub fn face(&self, family: &str, style: &str) -> Arc { if let Some(f) = self.find(family, style) { return f; @@ -1299,19 +1312,23 @@ impl FontDb { } /// Is `family` available (loaded, or installed on the system)? English and localized family - /// names both count. + /// names and exact PostScript face names count. pub fn has_family(&self, family: &str) -> bool { if self.is_loaded(family) { return true; } #[cfg(not(target_arch = "wasm32"))] - if self.read_catalog().iter().any(|c| c.family.eq_ignore_ascii_case(family) || c.legacy.eq_ignore_ascii_case(family)) { + if self.read_catalog().iter().any(|c| { + c.family.eq_ignore_ascii_case(family) + || c.legacy.eq_ignore_ascii_case(family) + || c.postscript.as_ref().is_some_and(|n| n.eq_ignore_ascii_case(family)) + }) { return true; } self.english_family(family).is_some() } - /// Is a face named `family` (its typographic or legacy family) loaded? + /// Is a face named `family` (its typographic, legacy or PostScript name) loaded? pub fn is_loaded(&self, family: &str) -> bool { self.read_faces().iter().any(|f| f.named(family)) } @@ -1375,7 +1392,8 @@ impl FontDb { cands = faces.iter().filter(|f| f.legacy_family.eq_ignore_ascii_case(family)).collect(); } if cands.is_empty() { - return None; + // A PostScript name selects one exact face, regardless of the requested style. + return faces.iter().find(|f| f.postscript.as_ref().is_some_and(|n| n.eq_ignore_ascii_case(family))).cloned(); } let ns = norm(style); if let Some(f) = cands.iter().find(|f| norm(&f.style) == ns) { @@ -1790,4 +1808,20 @@ mod tests { assert_eq!(a.id(), b.id()); assert_eq!(a.legacy_family, "Inter Medium"); } + + #[test] + fn postscript_names_select_faces() { + let db = FontDb::with_font_dirs(vec![]); + for style in ["Regular", "Bold", "Italic", "Bold Italic"] { + let expected = db.face("Source Serif 4", style); + let f = expected.skrifa().unwrap(); + let ps = name(&f, &[StringId::POSTSCRIPT_NAME]).unwrap(); + assert!(db.has_family(&ps), "{ps}"); + let actual = db.face(&ps, "Regular"); + assert_eq!(actual.id(), expected.id(), "{ps}"); + assert_eq!(db.face(&ps.to_ascii_lowercase(), "Bold").id(), expected.id()); + } + assert!(!db.has_family("NotAFont-Bold")); + assert_eq!(db.face("Source Serif 4", "Regular").style, "Regular"); + } } diff --git a/crates/fonts/src/tests_sysfonts.rs b/crates/fonts/src/tests_sysfonts.rs index d5db20633d..6120f14932 100644 --- a/crates/fonts/src/tests_sysfonts.rs +++ b/crates/fonts/src/tests_sysfonts.rs @@ -456,3 +456,31 @@ fn font_list_previews_load_nothing_for_good() { assert_eq!(db.loaded_font_bytes(), used, "memory stays bounded across previews"); let _ = std::fs::remove_dir_all(&dir); } + +#[test] +fn catalog_postscript_names() { + let dir = font_dir("postscript"); + for path in [dir.join("Sysfont-Regular.ttf"), dir.join("Sub/Sysfont-Bold.TTF")] { + let mut bytes = std::fs::read(&path).unwrap(); + for (from, to) in [ + (b"SourceSans3".to_vec(), b"SysfontSans".to_vec()), + ("SourceSans3".encode_utf16().flat_map(u16::to_be_bytes).collect(), "SysfontSans".encode_utf16().flat_map(u16::to_be_bytes).collect()), + ] { + let mut offset = 0; + while let Some(at) = bytes[offset..].windows(from.len()).position(|w| w == from) { + bytes[offset + at..offset + at + to.len()].copy_from_slice(&to); + offset += at + to.len(); + } + } + std::fs::write(path, bytes).unwrap(); + } + let db = FontDb::with_font_dirs(vec![dir.clone()]); + assert!(!db.is_loaded("SysfontSans-Bold")); + assert!(db.has_family("SysfontSans-Bold")); + let face = db.face("SysfontSans-Bold", "Regular"); + assert_eq!((face.family.as_str(), face.style.as_str()), (FAMILY, "Bold")); + assert_eq!(db.face("sysfontsans-bold", "Italic").id(), face.id()); + assert_eq!(db.face(FAMILY, "Regular").style, "Regular"); + assert!(!has(&db.families(), "SysfontSans-Bold")); + std::fs::remove_dir_all(dir).unwrap(); +} diff --git a/docs/layout-parity.md b/docs/layout-parity.md index eb6ffad0a2..646f226d16 100644 --- a/docs/layout-parity.md +++ b/docs/layout-parity.md @@ -1,6 +1,6 @@ # Layout and pagination parity with Microsoft Word -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** trivial (consistent cell widths override a stale table grid; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: page-break-only list paragraphs hide their labels; previously trivial: floating-table overlap ignores empty line-break lines; previously trivial: paragraph spacing adds up when the document turns HTML auto spacing off; previously trivial: an inherited footer sizes its percentage tables by its own section; previously trivial: an empty section mark stays on a full page; previously trivial: a fitting row with less room than its minimum height moves on whole; oversized rows still split; previously trivial: text past an exact-height row is cut off; previously trivial: a list number stays with text after a leading page break; previously trivial: a bookmark after a page break takes no line; previously trivial: a page break followed by a page break before or a next-page section starts one page; previously trivial: All Caps and Small Caps draw ß as SS, #1581; previously trivial: justified vertical page alignment, #1320; previously trivial: page numbers restart at 0; previously trivial: a list label moves with its first line when the paragraph is centred or end-aligned (#1159); previously trivial: PAGE fields in a paragraph split across pages show the page they are on, #1352; previously trivial: a dragged floating object anchors in the column it lands in, #884; previously trivial: a paragraph ending on a page with footnotes keeps its space after above the separator, #1116; previously trivial: symbol-font text drawn as Unicode symbols, #776; previously trivial: large-table scrolling measured and fixed, #811; previously trivial: text boxes use their own margins; Resize shape to fit text never cuts text, #743; previously trivial: complex-script shaping and Myanmar line breaking, #604; previously trivial: body page totals, table-cell footnotes and bookmark link pages fixed, #450 #451 #455; previously major: first version; line breaking, pagination and object placement checked against Word's behaviour from the layout source on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-16 · **Change:** trivial (installed static fonts resolve by their exact PostScript names; previously trivial: consistent cell widths override a stale table grid; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: page-break-only list paragraphs hide their labels; previously trivial: floating-table overlap ignores empty line-break lines; previously trivial: paragraph spacing adds up when the document turns HTML auto spacing off; previously trivial: an inherited footer sizes its percentage tables by its own section; previously trivial: an empty section mark stays on a full page; previously trivial: a fitting row with less room than its minimum height moves on whole; oversized rows still split; previously trivial: text past an exact-height row is cut off; previously trivial: a list number stays with text after a leading page break; previously trivial: a bookmark after a page break takes no line; previously trivial: a page break followed by a page break before or a next-page section starts one page; previously trivial: All Caps and Small Caps draw ß as SS, #1581; previously trivial: justified vertical page alignment, #1320; previously trivial: page numbers restart at 0; previously trivial: a list label moves with its first line when the paragraph is centred or end-aligned (#1159); previously trivial: PAGE fields in a paragraph split across pages show the page they are on, #1352; previously trivial: a dragged floating object anchors in the column it lands in, #884; previously trivial: a paragraph ending on a page with footnotes keeps its space after above the separator, #1116; previously trivial: symbol-font text drawn as Unicode symbols, #776; previously trivial: large-table scrolling measured and fixed, #811; previously trivial: text boxes use their own margins; Resize shape to fit text never cuts text, #743; previously trivial: complex-script shaping and Myanmar line breaking, #604; previously trivial: body page totals, table-cell footnotes and bookmark link pages fixed, #450 #451 #455; previously major: first version; line breaking, pagination and object placement checked against Word's behaviour from the layout source on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 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, @@ -155,10 +155,13 @@ No overall parity estimate changes from these probes. | Large real-world documents (500+ pages, many pictures) | not measured | | gap | | Scrolling a 1,096-page document that is one 5,300-row table (#811) | ~2.4 ms idle, ~4.5 ms median scroll frame (was ~600 ms); opening still lays out for ~4–8 s | not measured | measured 2026-10-12, release, headless (`tests_perf::big_table_scroll_bench`) | +Installed static fonts also resolve by their OpenType PostScript name (name ID 6), retaining the named face rather than substituting an unrelated family. Missing faces still use the existing fallback rules. A local Best for printing control from Word for Mac 16.113.2 (build 26092012) retained the directly requested Cambria-Bold face. + ## Revision history | Date | Change | Summary | |---|---|---| +| 2026-10-16 | trivial | Exact PostScript names resolve installed static faces without changing family lookup | | 2026-10-14 | trivial | Row table border exceptions draw without leaking into other rows | | 2026-10-14 | trivial | Consistent cell widths override a stale table grid | | 2026-10-14 | trivial | Paragraphs inside complex-field instructions take no flow height; empty result paragraphs keep their line. | From 3c968aa7824daa66f60c0ae307c4d43e22e43402 Mon Sep 17 00:00:00 2001 From: Nghiathan13 Date: Fri, 16 Oct 2026 20:45:14 +0700 Subject: [PATCH 063/336] #2233, #2234: inline pictures drag to a dashed insertion cursor and stay inline Dragging an In Line with Text picture, shape or text box now moves it within the text, as in Word: a dashed insertion cursor follows the pointer through the text, and the drop puts the object there, still inline and selected, in one undo step (Ctrl+drag drops a copy there). Before, the drop floated it with Square wrapping where it was let go, and nothing showed where it would land. arrange.bounds and arrange.copyTo take {"to": Pos} for inline objects; x/y keep their old meaning for scripts. Floating objects drag as before. Co-Authored-By: Claude Opus 5.5 --- ROADMAP.md | 3 +- crates/engine/src/cmd/objects.rs | 189 +++++++++++++++++++++++++++++-- crates/ui-egui/src/canvas.rs | 7 ++ crates/ui-egui/src/objects.rs | 123 +++++++++++++++++++- docs/ui-parity.md | 5 +- 5 files changed, 310 insertions(+), 17 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 40407d18b6..7f0a62a197 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -3,7 +3,7 @@ **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-14 · **Change:** 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-14 · **Change:** trivial (progress log: print-area strings translated in every catalog; 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-16 · **Change:** trivial (progress log: inline pictures drag to an insertion cursor and stay inline, #2233, #2234; previously trivial: progress log: print-area strings translated in every catalog; 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 @@ -135,6 +135,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm ## Progress log | Date | What landed | +| 2026-10-16 | Dragging an In Line with Text picture, shape or text box moves it within the text, as in Word: a dashed insertion cursor follows the pointer through the text, and the drop puts the object there, still inline and selected, in one undo step (Ctrl+drag drops a copy there). Before, the drop floated it with Square wrapping where it was let go. Scripts and MCP use `arrange.bounds {"to": Pos}` / `arrange.copyTo {"to": Pos}` (#2233, #2234) | | 2026-10-14 | DOCX row table border exceptions render and survive saving. Explicit cell borders win; a row exception does not change other rows. | | 2026-10-14 | Tab stops edited on the ruler: the corner selector picks Left/Center/Right/Decimal/Bar, a click sets a stop (Alt skips the snap), dragging moves it with a guide line and position tooltip, dragging off the ruler clears it; markers draw per kind and mirror for RTL; one `para.tabs` call and one undo step per gesture | | 2026-10-12 | Fields keep `w:dirty` through DOCX saves (#877): a begin marker flagged by a generator that couldn't compute a result (pandoc-style TOC placeholders) keeps the flag on open/save — as a buffered field or a range field — so Word still refreshes it on open; computing a result (Update Field, Update Table) clears it, like Word's F9 | diff --git a/crates/engine/src/cmd/objects.rs b/crates/engine/src/cmd/objects.rs index dedcee062e..c326e00bb1 100644 --- a/crates/engine/src/cmd/objects.rs +++ b/crates/engine/src/cmd/objects.rs @@ -327,10 +327,10 @@ pub fn specs() -> Vec { .when(has_movable), CommandSpec::new("arrange.selectionPane", "Selection Pane", "Layout › Arrange", objects_list).pure(), CommandSpec::new("arrange.bounds", "Move or Resize", "Layout › Arrange", bounds) - .params(r#"{"width"?: pt, "height"?: pt, "x"?: pt, "y"?: pt, "page"?: n} (x/y: the top-left on page `page` (0-based, default its page); moving an inline object floats it)"#) + .params(r#"{"width"?: pt, "height"?: pt, "x"?: pt, "y"?: pt, "page"?: n, "to"?: Pos} (x/y: the top-left on page `page` (0-based, default its page); moving an inline object by x/y floats it. to: an In Line with Text object moves to that text position and stays inline, as a drag does)"#) .when(has_movable), CommandSpec::new("arrange.copyTo", "Copy to", "Layout › Arrange", copy_to) - .params(r#"{"x": pt, "y": pt, "page"?: n} (Ctrl+drag: a copy of the selected object lands with its top-left there and is selected; the original and the clipboard stay as they were)"#) + .params(r#"{"x": pt, "y": pt, "page"?: n} | {"to": Pos} (Ctrl+drag: a copy of the selected object lands with its top-left there, or an In Line with Text one at the text position `to`, and is selected; the original and the clipboard stay as they were)"#) .when(has_movable), CommandSpec::new("arrange.nudge", "Nudge", "Layout › Arrange", |s, v| { let d = |k| p::f32(v, k).unwrap_or(0.0).clamp(-MAX_OFFSET, MAX_OFFSET); @@ -681,6 +681,10 @@ fn bounds(s: &mut Session, v: &Value) -> CmdResult { (None, None) => None, _ => return Err(CmdError::Params("`x` and `y` go together".into())), }; + let in_text = text_target(v, &obj)?; + if in_text.is_some() && to.is_some() { + return Err(CmdError::Params("`to` or `x`/`y`, not both".into())); + } let (w0, h0) = obj_size(&obj); let min = min_size(&obj); let w = p::f32(v, "width").unwrap_or(w0).clamp(min, MAX_OFFSET); @@ -688,24 +692,92 @@ fn bounds(s: &mut Session, v: &Value) -> CmdResult { if (w, h) != (w0, h0) { with_obj(s, |o| o.set_size(w, h))?; } - let pos = match to { - Some((x, y)) => move_object(s, pos, p::u64(v, "page"), x, y, (w, h))?, - None => pos, + let pos = match (to, in_text) { + (Some((x, y)), _) => move_object(s, pos, p::u64(v, "page"), x, y, (w, h))?, + (None, Some(at)) => move_in_text(s, pos, &at)?, + (None, None) => pos, }; - s.sel = Selection { anchor: pos.clone(), focus: Pos { off: pos.off + wordcraft_doc::para::OBJ.len_utf8(), ..pos.clone() } }; + select_object(s, &pos); let o = s.doc.para_at(&pos).and_then(|p| p.object_at(pos.off)).cloned(); Ok(json!({"object": serde_json::to_value(o).unwrap_or(Value::Null), "pos": super::pos_json(&pos)})) } +/// Select the object whose character is at `pos`. +fn select_object(s: &mut Session, pos: &Pos) { + s.sel = Selection { anchor: pos.clone(), focus: Pos { off: pos.off + wordcraft_doc::para::OBJ.len_utf8(), ..pos.clone() } }; +} + +/// The text position in `v`'s `to`, for an In Line with Text `obj` (a floating one is placed by +/// `x`/`y`); None without one. +fn text_target(v: &Value, obj: &InlineObject) -> Result, CmdError> { + let Some(to) = v.get("to").filter(|t| !t.is_null()) else { return Ok(None) }; + if obj.is_floating() { + return Err(CmdError::Params("`to` places an In Line with Text object; move a floating one by `x` and `y`".into())); + } + super::parse_pos(to).map(Some).ok_or_else(|| CmdError::Params("bad `to`".into())) +} + +/// The text position `to` an In Line with Text object at `pos` can land at: in the object's own +/// story (a table cell there too), not inside a field's code or such (it's clamped to a +/// character). +fn landing(s: &Session, pos: &Pos, to: &Pos) -> Result { + if pos.path.depth() > 0 || !movable_story(s, pos.story) { + return Err(CmdError::Disabled("only objects in the body text, headers and footers can be moved".into())); + } + if to.story != pos.story || s.doc.para_at(to).is_none() { + return Err(CmdError::Params("`to` must be a position in the object's own text".into())); + } + Ok(s.doc.clamp(to)) +} + +/// Move the In Line with Text object at `pos` to the text position `to`, where it stays In Line +/// with Text, keeping its character formatting: Word's drag of an inline picture, which lands +/// where its dashed insertion cursor was (#2233, #2234). Dropped on itself, nothing changes. +/// Returns the object's position afterwards. +fn move_in_text(s: &mut Session, pos: Pos, to: &Pos) -> Result { + let to = landing(s, &pos, to)?; + let len = wordcraft_doc::para::OBJ.len_utf8(); + let same_para = to.path == pos.path; + if same_para && (to.off == pos.off || to.off == pos.off + len) { + return Ok(pos); + } + let para = s.doc.para_at(&pos).ok_or_else(|| CmdError::Failed("object vanished".into()))?; + let obj = para.object_at(pos.off).cloned().ok_or_else(|| CmdError::Failed("object vanished".into()))?; + let props = para.props_of_char(pos.off).clone(); + s.doc.delete_range(&pos, &Pos { off: pos.off + len, ..pos.clone() })?; + let mut at = to; + if same_para && at.off > pos.off { + at.off = at.off.saturating_sub(len); + } + let at = s.doc.clamp(&at); + s.doc.insert_object(&at, obj, &props)?; + Ok(at) +} + /// Ctrl+drag: copy the selected object (a text box gets its own story) and move the copy to -/// (x, y), leaving the original in place and the clipboard alone. The copy is selected. +/// (x, y), or for an In Line with Text object put it at the text position `to`, leaving the +/// original in place and the clipboard alone. The copy is selected. fn copy_to(s: &mut Session, v: &Value) -> CmdResult { - let (pos, _) = selected(s).ok_or_else(|| CmdError::Disabled("no picture or shape selected".into()))?; + let (pos, obj) = selected(s).ok_or_else(|| CmdError::Disabled("no picture or shape selected".into()))?; + let len = wordcraft_doc::para::OBJ.len_utf8(); + let end = Pos { off: pos.off + len, ..pos.clone() }; + if let Some(to) = text_target(v, &obj)? { + if p::f32(v, "x").is_some() || p::f32(v, "y").is_some() { + return Err(CmdError::Params("`to` or `x`/`y`, not both".into())); + } + let at = landing(s, &pos, &to)?; + let frag = s.doc.copy_range(&pos, &end); + s.sel = Selection::caret(at.clone()); + super::paste::insert(s, frag)?; + // The copy starts where it was pasted. + let copy = s.doc.clamp(&at); + select_object(s, ©); + let o = s.doc.para_at(©).and_then(|p| p.object_at(copy.off)).cloned(); + return Ok(json!({"object": serde_json::to_value(o).unwrap_or(Value::Null), "pos": super::pos_json(©)})); + } if p::f32(v, "x").is_none() || p::f32(v, "y").is_none() { return Err(CmdError::Params("`x` and `y` are required".into())); } - let len = wordcraft_doc::para::OBJ.len_utf8(); - let end = Pos { off: pos.off + len, ..pos.clone() }; let frag = s.doc.copy_range(&pos, &end); s.sel = Selection::caret(end.clone()); super::paste::insert(s, frag)?; @@ -1540,6 +1612,103 @@ mod tests { assert_eq!(count(&s), 1); } + /// The body's top-level paragraphs, an object's character shown as ▣. + fn body_text(s: &Session) -> String { + (0..2) + .filter_map(|i| s.doc.para(StoryRef::Body, &wordcraft_doc::Path::top(i))) + .map(|p| p.text.replace(wordcraft_doc::para::OBJ, "▣")) + .collect::>() + .join("\n") + } + + /// Two paragraphs, the second starting with an inline picture (bold, so its formatting can be + /// followed), selected. + fn inline_picture_session() -> Session { + let mut s = Session::new(wordcraft_doc::Document::new()); + s.run("text.insert", &json!({"text": "First paragraph."})).unwrap(); + s.run("text.newParagraph", &json!({})).unwrap(); + s.run("format.bold", &json!({})).unwrap(); + let data = super::super::insert::base64_encode(&png(20, 10)); + s.run("insert.picture", &json!({"data": data, "width": 100})).unwrap(); + s.run("format.bold", &json!({})).unwrap(); + s.run("text.insert", &json!({"text": " second paragraph."})).unwrap(); + let pic = Pos::body(1, 0); + s.run("select.range", &json!({"anchor": pic, "focus": Pos { off: pic.off + wordcraft_doc::para::OBJ.len_utf8(), ..pic.clone() }})).unwrap(); + assert!(selected(&s).is_some_and(|(_, o)| !o.is_floating())); + s + } + + /// #2234: dragging an In Line with Text picture into the text (`to`) moves it there and it stays + /// inline, with its formatting, selected, in one undo step; it never floats. + #[test] + fn an_inline_picture_dragged_into_the_text_stays_inline() { + let mut s = inline_picture_session(); + let text = body_text; + assert_eq!(text(&s), "First paragraph.\n▣ second paragraph."); + // Into the first paragraph, after "First". + let r = s.run("arrange.bounds", &json!({"to": Pos::body(0, 5)})).unwrap(); + assert_eq!(text(&s), "First▣ paragraph.\n second paragraph."); + assert_eq!(r["object"]["float"]["wrap"], "inline", "{r}"); + let (pos, obj) = selected(&s).expect("still selected"); + assert_eq!(pos, Pos::body(0, 5)); + assert!(!obj.is_floating()); + assert!(s.doc.para_at(&pos).unwrap().props_of_char(pos.off).bold == Some(true), "keeps its run's formatting"); + // Later in its own paragraph (after "paragraph", counted with it still there): the offset + // accounts for its own character leaving. + s.run("arrange.bounds", &json!({"to": Pos::body(0, 18)})).unwrap(); + assert_eq!(text(&s), "First paragraph▣.\n second paragraph."); + // Each move is one undo step. + s.run("edit.undo", &json!({})).unwrap(); + assert_eq!(text(&s), "First▣ paragraph.\n second paragraph."); + s.run("edit.undo", &json!({})).unwrap(); + assert_eq!(text(&s), "First paragraph.\n▣ second paragraph."); + // Dropped on itself (either side): nothing changes. + let here = selected(&s).unwrap().0; + s.run("arrange.bounds", &json!({"to": here})).unwrap(); + s.run("arrange.bounds", &json!({"to": Pos { off: here.off + wordcraft_doc::para::OBJ.len_utf8(), ..here.clone() }})).unwrap(); + assert_eq!(text(&s), "First paragraph.\n▣ second paragraph."); + assert_eq!(selected(&s).unwrap().0, here); + } + + /// `to` is only for In Line with Text objects, in their own text; bad targets change nothing. + #[test] + fn moving_into_the_text_refuses_bad_targets() { + let mut s = inline_picture_session(); + let before = body_text(&s); + for bad in [ + json!({"to": "junk"}), + json!({"to": {"story": "body", "path": [9], "off": 0}}), + json!({"to": Pos::body(0, 0), "x": 10, "y": 10}), + json!({"to": {"story": {"part": 99}, "path": [0], "off": 0}}), + ] { + assert!(s.run("arrange.bounds", &bad).is_err(), "{bad}"); + assert!(s.run("arrange.copyTo", &bad).is_err(), "{bad}"); + } + // A huge offset is clamped to the paragraph's end. + s.run("arrange.bounds", &json!({"to": {"story": "body", "path": [0], "off": 999_999}})).unwrap(); + assert_eq!(body_text(&s), "First paragraph.▣\n second paragraph."); + assert_ne!(body_text(&s), before); + // A floating object is moved by x/y, not into the text. + s.run("arrange.bounds", &json!({"page": 0, "x": 200, "y": 300})).unwrap(); + assert!(s.run("arrange.bounds", &json!({"to": Pos::body(1, 0)})).is_err()); + } + + /// Ctrl+drag of an inline picture into the text: a copy lands there, inline and selected; the + /// original stays. + #[test] + fn copying_an_inline_picture_into_the_text() { + let mut s = inline_picture_session(); + s.clipboard_text = "keep".into(); + s.run("arrange.copyTo", &json!({"to": Pos::body(0, 5)})).unwrap(); + assert_eq!(body_text(&s), "First▣ paragraph.\n▣ second paragraph."); + let (pos, obj) = selected(&s).expect("the copy is selected"); + assert_eq!(pos, Pos::body(0, 5)); + assert!(!obj.is_floating()); + assert_eq!(s.clipboard_text, "keep"); + s.run("edit.undo", &json!({})).unwrap(); + assert_eq!(body_text(&s), "First paragraph.\n▣ second paragraph."); + } + #[test] fn shapes_can_be_clicked_resized_and_dragged() { for kind in ["rectangle", "ellipse", "star", "arrow"] { diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 4eca0d304b..fac16ffe8a 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -3031,6 +3031,13 @@ fn caret_color_at(app: &mut WordApp, layout: &std::sync::Arc, page: u c } +/// The caret colour at page point (`x`, `y`) of page `page`, uncached: for a caret drawn while +/// dragging, such as an inline picture's insertion cursor. +pub(crate) fn caret_color_on(app: &WordApp, layout: &DocLayout, page: usize, x: f32, y: f32) -> Color32 { + let Some(p) = layout.pages.get(page) else { return Color32::BLACK }; + caret_contrast(wordcraft_render::background_at(&app.session.doc, p, &page_render_options(app, dims_body(app, layout)), x, y)) +} + /// Render options for page rasters shown on screen: on macOS, text is darkened the way the system /// draws it, so a page looks as it does in other Mac apps (exports never are). pub(crate) fn screen_render_options() -> wordcraft_render::RenderOptions { diff --git a/crates/ui-egui/src/objects.rs b/crates/ui-egui/src/objects.rs index 37af3f7b87..23e32c24ca 100644 --- a/crates/ui-egui/src/objects.rs +++ b/crates/ui-egui/src/objects.rs @@ -8,9 +8,13 @@ //! drag starts (#1010: a cut of the page would carry along whatever is behind it). The drop is //! one command, one relayout. //! -//! Moving one snaps it to alignment guides ([`crate::guides`]): the page's edges and centre, the -//! margins and the other floating objects on the page, shown as thin lines while dragging. Alt -//! moves freely; File › Options › Advanced turns them off (`ui.alignmentGuides`). +//! Dragging an In Line with Text object moves it within the text, as in Word: a dashed insertion +//! cursor follows the pointer through the text (#2233) and the drop puts it there, still inline +//! (`arrange.bounds` / `arrange.copyTo` with `to`, #2234). A floating one goes where it's dropped. +//! +//! Moving a floating one snaps it to alignment guides ([`crate::guides`]): the page's edges and +//! centre, the margins and the other floating objects on the page, shown as thin lines while +//! dragging. Alt moves freely; File › Options › Advanced turns them off (`ui.alignmentGuides`). //! //! A selected connector shows a handle at each end instead of a frame: dragging one onto a shape //! glues it to the nearest connection site there (`shape.connect`), elsewhere leaves it free. @@ -53,6 +57,9 @@ pub struct ObjectDrag { guides: Vec, /// The object drawn alone over its rotated bounds, for the preview: made once it moves. preview: Option, + /// Moving an In Line with Text object: the text position under the pointer, where it lands, + /// and the colour of the dashed insertion cursor drawn there. + drop_at: Option<(Pos, Color32)>, } /// How near (screen points) a press must be to a connector's end to grab it. @@ -201,7 +208,10 @@ pub fn pointer(app: &mut WordApp, ui: &Ui, resp: &egui::Response, pages: &[Rect] // Alignment guides: a move snaps to the page, the margins and the other objects // (Alt: freely). d.guides.clear(); - if d.drag.grab == Grab::Move && d.drag.moved && app.ui.alignment_guides && !ui.input(|i| i.modifiers.alt) { + if d.drag.grab == Grab::Move && d.drag.moved && !d.object.floating() { + // An inline one lands in the text: the insertion cursor follows the pointer. + d.drop_at = drop_point(app, layout, pages, scale, at, &d); + } else if d.drag.grab == Grab::Move && d.drag.moved && app.ui.alignment_guides && !ui.input(|i| i.modifiers.alt) { snap_move(layout, &mut d, scale); } } @@ -236,6 +246,7 @@ pub fn pointer(app: &mut WordApp, ui: &Ui, resp: &egui::Response, pages: &[Rect] guides: Vec::new(), preview: None, copy: false, + drop_at: None, }); return true; } @@ -264,6 +275,7 @@ pub fn pointer(app: &mut WordApp, ui: &Ui, resp: &egui::Response, pages: &[Rect] guides: Vec::new(), preview: None, copy: false, + drop_at: None, }); return false; } @@ -288,12 +300,27 @@ pub fn pointer(app: &mut WordApp, ui: &Ui, resp: &egui::Response, pages: &[Rect] guides: Vec::new(), preview: None, copy, + drop_at: None, }); } } true } +/// Where an In Line with Text object dragged to `at` (screen) lands: the nearest position of its +/// own story's text there, with the insertion cursor's colour (the same contrast as the caret's). +fn drop_point(app: &WordApp, layout: &DocLayout, pages: &[Rect], scale: f32, at: Pos2, d: &ObjectDrag) -> Option<(Pos, Color32)> { + let (page, x, y) = crate::canvas::page_at(pages, layout, scale, at)?; + let pos = layout.hit(page, x, y, d.object.story)?; + if let Some((was, color)) = &d.drop_at + && *was == pos + { + return Some((pos, *color)); // the paper under it is worked out once per position + } + let c = layout.caret_on(&pos, page)?; + Some((pos, crate::canvas::caret_color_on(app, layout, c.page, c.x, c.top + c.height / 2.0))) +} + /// Snap a moved object's bounds (rotated ones by their turned box) to the guides of the page /// it's over: the page's edges and centre, its margins, the other floating objects there. fn snap_move(layout: &DocLayout, d: &mut ObjectDrag, scale: f32) { @@ -481,6 +508,19 @@ fn drop(app: &mut WordApp, d: &ObjectDrag) { let _ = app.run("arrange.rotation", json!({"degrees": d.drag.deg})); return; } + // An In Line with Text object stays in the text, at the insertion cursor; dropped on + // itself, it doesn't move. + Grab::Move if !d.object.floating() => { + let Some((to, _)) = &d.drop_at else { return }; + let pos = d.object.pos(); + let on_itself = to.story == pos.story && to.path == pos.path && (to.off == pos.off || to.off == pos.off + OBJ.len_utf8()); + if d.copy { + let _ = app.run("arrange.copyTo", json!({"to": to})); + } else if !on_itself { + let _ = app.run("arrange.bounds", json!({"to": to})); + } + return; + } Grab::Move if d.copy => { let _ = app.run("arrange.copyTo", json!({"page": d.drag.to_page, "x": r.x, "y": r.y})); return; @@ -536,6 +576,7 @@ pub fn paint(app: &WordApp, painter: &Painter, t: &Tokens, layout: &DocLayout, p } frame::paint(painter, t, to, d.drag.deg, false, false, false); paint_guides(d, painter, t, layout, pages, scale); + paint_drop_caret(d, painter, layout, pages, scale); return; } // The others selected along with it (Shift+click): their outlines. @@ -556,6 +597,14 @@ pub fn paint(app: &WordApp, painter: &Painter, t: &Tokens, layout: &DocLayout, p } } +/// The dashed insertion cursor where a dragged In Line with Text object will land. +fn paint_drop_caret(d: &ObjectDrag, painter: &Painter, layout: &DocLayout, pages: &[Rect], scale: f32) { + let Some((pos, color)) = &d.drop_at else { return }; + let Some(c) = layout.caret_on(pos, d.drag.to_page) else { return }; + let Some(r) = screen(pages, scale, c.page, wordcraft_geom::Rect::new(c.x, c.top, 0.0, c.height)) else { return }; + painter.extend(egui::Shape::dashed_line(&[r.left_top(), r.left_bottom()], egui::Stroke::new(1.5, *color), 2.0, 2.0)); +} + /// A connector's end being dragged: a line from its other end to the pointer, and the connection /// sites of the shapes near the pointer (the one it would glue to, filled). fn paint_end_drag(app: &WordApp, d: &ObjectDrag, painter: &Painter, t: &Tokens, layout: &DocLayout, pages: &[Rect], scale: f32) { @@ -847,6 +896,72 @@ mod tests { assert_eq!(h.state().ui.keytips, crate::keytips::Phase::Off, "Alt during a drag isn't a keytip tap"); } + /// #2233, #2234: dragging an In Line with Text picture into another paragraph shows a dashed + /// insertion cursor at the text under the pointer (no alignment guides), and the drop puts the + /// picture there, still inline and selected. Dropped back where it was, nothing changes. + #[test] + fn an_inline_picture_drags_to_the_insertion_cursor_and_stays_inline() { + use crate::table_resize::tests::{H, button, steps}; + let mut a = WordApp::new( + wordcraft_engine::Session::new(Document::from_text("The first paragraph of text.\nThe second paragraph, where the picture starts.")), + Default::default(), + ); + a.run("view.commentsPane", json!({"value": false})).unwrap(); + let picture = + InlineObject::Image { media: String::new(), w: 80.0, h: 40.0, alt: String::new(), float: Default::default(), crop: [0.0; 4], ole: None }; + a.session.doc.insert_object(&Pos::body(1, 0), picture, &Default::default()).unwrap(); + a.session.touch(); + let mut h: H = egui_kittest::Harness::builder().with_size(egui::vec2(1440.0, 900.0)).with_step_dt(0.25).build_ui_state( + |ui, app: &mut WordApp| { + let ctx = ui.ctx().clone(); + app.logic(&ctx); + app.ui(ui); + }, + a, + ); + steps(&mut h, 6); + let o = h.state_mut().session.layout().object(&Pos::body(1, 0), 0).expect("laid out"); + assert_eq!(o.wrap, Wrap::Inline); + // Aim between "first" and " paragraph" in the first paragraph. + let target = Pos::body(0, "The first".len()); + let c = h.state_mut().session.layout().caret(&target).expect("caret"); + let at = |h: &mut H, x: f32, y: f32| crate::canvas::page_to_screen(h.state_mut(), 0, x, y).unwrap(); + let start = at(&mut h, o.rect.x + o.rect.w / 2.0, o.rect.y + o.rect.h / 2.0); + let end = at(&mut h, c.x + 0.5, c.top + c.height / 2.0); + h.event(egui::Event::PointerMoved(start)); + h.step(); + button(&h, start, true); + h.step(); + for k in 1..=5 { + h.event(egui::Event::PointerMoved(start + (end - start) * (k as f32 / 5.0))); + h.step(); + } + let d = h.state().canvas.obj_drag.as_ref().expect("dragging"); + assert_eq!(d.drop_at.as_ref().map(|(p, _)| p.clone()), Some(target.clone()), "the insertion cursor is at the text under the pointer"); + assert!(d.guides.is_empty(), "an inline object doesn't snap to guides"); + button(&h, end, false); + steps(&mut h, 3); + assert!(h.state().canvas.obj_drag.is_none()); + let (pos, obj) = selected(h.state()).expect("still selected"); + assert_eq!(pos, target, "landed at the insertion cursor"); + assert!(!obj.is_floating(), "still In Line with Text"); + assert_eq!(h.state().session.doc.para_at(&Pos::body(1, 0)).map(|p| p.objects.len()), Some(0), "left its old place"); + // Back onto itself: no change, no undo step. + let undo = h.state().session.undo_depth(); + let o = h.state_mut().session.layout().object(&target, 0).expect("laid out"); + let start = at(&mut h, o.rect.x + o.rect.w / 2.0, o.rect.y + o.rect.h / 2.0); + button(&h, start, true); + h.step(); + for v in [egui::vec2(12.0, 0.0), egui::vec2(0.0, 0.0)] { + h.event(egui::Event::PointerMoved(start + v)); + h.step(); + } + button(&h, start, false); + steps(&mut h, 3); + assert_eq!(selected(h.state()).map(|(p, _)| p), Some(target)); + assert_eq!(h.state().session.undo_depth(), undo); + } + /// #1010: dragging a shape off text, the preview is the shape alone, not the text that was /// behind it. An unfilled red rectangle in front of black text is dragged down into the empty /// page: its preview has its red outline and nothing dark inside. diff --git a/docs/ui-parity.md b/docs/ui-parity.md index b1c503f5c5..6485d221bd 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -1,6 +1,6 @@ # UI parity with Microsoft Word -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** 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-16 · **Change:** trivial (inline pictures drag to a dashed insertion cursor and stay inline, #2233, #2234); 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 @@ -32,7 +32,7 @@ Document title opens Rename Document; the account badge opens Options › Genera | Backstage (Home, New, Open, Info, Save As, Print, Share, Export, Options) | Home and New separate (#254), Info properties kept (#268), Options in Word's panes with working settings (#485), Print page with preview, page range and copies (#641); launched with nothing to open, the app starts on Home (the Start screen: Blank document, templates, Recent; no document is open until one is picked or opened, so Esc or Back shows an empty window with the ribbon greyed out but for Help, #1129), with File › Options › General › Start-up to turn it off (#766); Info › Inspect Document finds and removes hidden text by its effective formatting (direct, character or paragraph style) in every story: body, headers, footers, notes, comments and text boxes, and lists what it couldn't reach (#1344) | | 68% | 4–6 | | Rulers: indents, tabs, margins, table columns | indents draggable (one undo, #62); tab stops: corner selector (Left/Center/Right/Decimal/Bar), click to set, drag to move, drag off to clear, per-kind markers, Alt skips the snap, one undo step per gesture, double-click opens the Tabs dialog (#320); table column and row markers drag their borders (one undo) and in a cell the indents are measured from it (#700) | ✅ | 90% | 1 | | On-canvas objects: select, drag, resize handles, nudge | ✅ tested (#228); a picture or shape behind text can be dragged across the overlapping text (body and header/footer), whether or not it was selected first, while a click without movement still edits that text | ✅ | 75% | — | -| On-canvas objects: select, drag, Ctrl+drag to copy, resize handles, nudge | ✅ tested (#228, #1014) | ✅ | 75% | — | +| On-canvas objects: select, drag, Ctrl+drag to copy, resize handles, nudge | ✅ tested (#228, #1014); an In Line with Text object drags within the text as in Word: a dashed insertion cursor follows the pointer through the text and the drop (or Ctrl+drop copy) lands there, still inline (#2233, #2234) | ✅ | 75% | — | | Rotation handle, crop handles on canvas, alignment guides, smart guides | rotation handle (Shift: 15° steps), turned frames resize along their axes (#332); crop handles: Picture Format › Crop puts the picture in crop mode, black crop handles trim a side (a corner, two) keeping the scale and the visible part in place, the cropped-away part shows dimmed, Esc/Enter/a click outside/Crop again keep the crop, one undo step per drag (unturned pictures; no Crop to Shape, Aspect Ratio or Fill/Fit); alignment guides, our own design: a moved picture, shape or text box snaps its left, centre or right (top, middle or bottom) to the page's edges and centre, the margins and the other floating objects on the page within 6 screen points, x and y independently, turned objects by their rotated bounds, with a thin line across the page for each guide while dragging; Alt moves freely; File › Options › Advanced › Editing (`ui.alignmentGuides`) turns them off. Smart guides missing | Word for Mac 16.113.4 shows no guide lines and snaps inconsistently (to the page's left edge, not its top, right or bottom; Word check K); Word for Windows' guides not observed | 75% | 1–2 | | Tables on the page: drag column and row borders, resize handle past the bottom-right corner; select columns, rows and cells; "+" insert buttons; move handle | ✅ resize cursors, dashed guide, one undo step per drag, selection kept; Shift moves one column; Esc cancels (#217); Ctrl shares the change among the columns on the right, the left edge moves a left-aligned table's indent (a centred or right-aligned one keeps its alignment), a border of cells selected across moves only in their rows, Alt shows the column widths on the ruler (#711); double-click a column border to AutoFit it (a nested table's within the cell around it, #971); selection arrows above a column, left of a row and along a cell's left edge (a drag selects more; the cells are shaded and a column is edited cell by cell); "+" left of a row border and above a column border; move handle past the top-left corner (a click selects the table, a drag floats it where it's dropped); an ordinary drag, Shift+arrows or Shift+click from one cell into another select the rectangle of whole cells between them, and into or out of a table whole rows, their cells shaded whole (#971). Missing: a single selected cell's border; The Cell Size Height and Width boxes set every selected row and column (#1242). | ✅ | 95% | 1 | | Selection: word/sentence/paragraph clicks, dragging after a double-click/triple-click/Ctrl+click extends by whole words/paragraphs/sentences (#549), Shift extend, F8 extend mode, column (Alt+drag, #237) | ✅ | ✅ | 90% | 1 | @@ -59,6 +59,7 @@ Document title opens Rename Document; the account badge opens Options › Genera | Date | Change | Summary | |---|---|---| +| 2026-10-16 | trivial | On-canvas objects: dragging an In Line with Text picture, shape or text box shows a dashed insertion cursor in the text and drops it there, still inline, instead of floating it with Square wrapping (`arrange.bounds` / `arrange.copyTo` `to`, #2233, #2234) | | 2026-10-13 | trivial | New/Modify Table Style: No Border on a part that inherits its lines hides them; emptying the font box makes the part's font inherited again (#1288) | | 2026-10-14 | trivial | Tab stops edited on the ruler: corner kind selector, click to set, drag to move or off to clear, per-kind markers mirrored for RTL; each gesture one `para.tabs` call and one undo step; Rulers 85% → 90% | | 2026-10-12 | trivial | Tables on the page: one selected cell (Select Cell, or a click along its left edge) is a cell selection when its border is dragged: the border moves in its row only (#962) | From 7c4f41270a8e391cb30504d839c9dbf992229142 Mon Sep 17 00:00:00 2001 From: Nghiathan13 Date: Sat, 17 Oct 2026 03:41:30 +0700 Subject: [PATCH 064/336] #2583: the context menu offers the ribbon's paste choices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Right-clicking the page offered only Cut, Copy and Paste, even with formatted text on the clipboard. Word's context menu lets you choose how to paste there. The context menu now has the same items as Home › Paste's menu, in the same order and with the same shortcuts: Paste (the clipboard's formatting kept, Word's Keep Source Formatting), Keep Text Only, Merge Formatting and Paste Special…. They run the same commands (edit.paste, edit.pasteText, edit.pasteMerge, edit.pasteSpecial), and system_clipboard::fill already reads the system clipboard's text, HTML and RTF for all of them. Every label was already translated in the 22 interface catalogs. Co-Authored-By: Claude Opus 5.5 --- ROADMAP.md | 4 +- crates/ui-egui/src/canvas.rs | 80 ++++++++++++++++++++++++++++++++++++ docs/ui-parity.md | 3 +- 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 40407d18b6..3870cfb6b9 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **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-14 · **Change:** 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-17 · **Change:** trivial (progress log: the ribbon's paste choices in the context menu, #2583; 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-14 · **Change:** trivial (progress log: print-area strings translated in every catalog; 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. @@ -135,6 +135,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm ## Progress log | Date | What landed | +| 2026-10-17 | The document's right-click menu offers the ribbon's Paste menu, item for item (#2583): Paste (the clipboard's formatting kept, Word's Keep Source Formatting), Keep Text Only, Merge Formatting and Paste Special…, where it had a single Paste. Same commands and shortcuts, with the system clipboard | | 2026-10-14 | DOCX row table border exceptions render and survive saving. Explicit cell borders win; a row exception does not change other rows. | | 2026-10-14 | Tab stops edited on the ruler: the corner selector picks Left/Center/Right/Decimal/Bar, a click sets a stop (Alt skips the snap), dragging moves it with a guide line and position tooltip, dragging off the ruler clears it; markers draw per kind and mirror for RTL; one `para.tabs` call and one undo step per gesture | | 2026-10-12 | Fields keep `w:dirty` through DOCX saves (#877): a begin marker flagged by a generator that couldn't compute a result (pandoc-style TOC placeholders) keeps the flag on open/save — as a buffered field or a range field — so Word still refreshes it on open; computing a result (Update Field, Update Table) clears it, like Word's F9 | @@ -403,6 +404,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | Date | Change | Summary | |---|---|---| +| 2026-10-17 | trivial | Progress log: the ribbon's paste choices in the context menu (#2583) | | 2026-10-14 | trivial | Progress log: row table border exceptions | | 2026-10-12 | minor | Czech proofing review: short-word splits rank after deletion, NOSUGGEST excludes suggestions while accepting typed forms, affix comments apply only to headers, ratios keep spaces and repeated-word messages use Czech quotes; synthetic engine end-to-end and latency checks | | 2026-10-12 | minor | Czech spelling from the installed Hunspell dictionary, runtime-table suggestions and generic checks; Czech interface writes cs-CZ documents; no Czech hyphenation patterns bundled; estimates unchanged | diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 4eca0d304b..c46d31f00a 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -2844,7 +2844,12 @@ fn context_menu(app: &mut WordApp, ui: &mut Ui) { } item(ui, app, "Cut", "edit.cut", json!({})); item(ui, app, "Copy", "edit.copy", json!({})); + // How to paste, chosen before pasting (#2583): the ribbon's Paste menu, item for item. Paste + // keeps the clipboard's formatting (Word's Keep Source Formatting). item(ui, app, "Paste", "edit.paste", json!({})); + item(ui, app, "Keep Text Only", "edit.pasteText", json!({})); + item(ui, app, "Merge Formatting", "edit.pasteMerge", json!({})); + item(ui, app, "Paste Special…", "edit.pasteSpecial", json!({})); ui.separator(); if let Some(syn) = app.canvas.context_synonyms.clone().and_then(|v| v.as_array().cloned()).filter(|a| !a.is_empty()) { ui.menu_button(tl!("Synonyms"), |ui| { @@ -3829,6 +3834,81 @@ mod tests { assert_eq!(text.matches("Fresh heading").count(), 2, "the new heading is listed in the table: {text}"); } + /// #2583: the context menu offers the ribbon's Paste menu, item for item, and each pastes its + /// own way: Paste keeps the copied bold and colour (Word's Keep Source Formatting), Merge + /// Formatting keeps only the bold, Keep Text Only neither; Paste Special… opens its dialog. + #[test] + fn context_menu_offers_the_ribbons_paste_items() { + use egui_kittest::kittest::Queryable; + use wordcraft_doc::props::{Rgb, TextColor}; + let red = Some(TextColor::Rgb(Rgb(0xC0, 0, 0))); + let items = [ + ("Paste", "edit.paste"), + ("Keep Text Only", "edit.pasteText"), + ("Merge Formatting", "edit.pasteMerge"), + ("Paste Special…", "edit.pasteSpecial"), + ]; + for (choice, pasted) in [ + ("Paste", Some((true, red))), + ("Merge Formatting", Some((true, None))), + ("Keep Text Only", Some((false, None))), + ("Paste Special…", None), + ] { + let mut a = WordApp::new(wordcraft_engine::Session::new(wordcraft_doc::Document::from_text("Bold\nTarget")), Default::default()); + a.session + .doc + .format_range(&Pos::body(0, 0), &Pos::body(0, 4), &|c| { + c.bold = Some(true); + c.color = red; + }) + .unwrap(); + a.session.relayout(); + a.run("select.text", json!({"text": "Bold"})).unwrap(); + a.run("edit.copy", json!({})).unwrap(); + 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 c = h.state_mut().session.layout().caret(&Pos::body(1, 6)).expect("laid out"); + let at = page_to_screen(h.state_mut(), c.page, c.x, c.top + c.height / 2.0).expect("on screen"); + 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(); + // Each item reads as its name and its shortcut (none for Merge Formatting). + let labels: Vec = items.iter().map(|(name, id)| format!("{name} {}", crate::widgets::shortcut_text(h.state(), id))).collect(); + for l in &labels { + assert!(h.query_by_label(l).is_some(), "{choice}: {l:?} is offered"); + } + let k = items.iter().position(|(name, _)| *name == choice).unwrap(); + h.get_by_label(&labels[k]).click(); + for _ in 0..3 { + h.step(); + } + match pasted { + Some((bold, colour)) => { + let doc = &h.state().session.doc; + assert_eq!(doc.plain_text(StoryRef::Body), "Bold\nTargetBold", "{choice}: pasted at the end of the second paragraph"); + let p = doc.para_at(&Pos::body(1, 0)).expect("the target paragraph"); + assert_eq!(p.props_of_char(7).bold == Some(true), bold, "{choice}: bold"); + assert_eq!(p.props_of_char(7).color, colour, "{choice}: colour"); + } + None => { + assert!(matches!(h.state().dialog, Some(crate::dialogs::Dialog::PasteSpecial { .. })), "{choice}: the dialog opens"); + } + } + } + } + /// #996: on macOS a Control-click is the right-click (the trackpad's and the one-button /// mouse's way to it), so it opens the same menu and leaves the caret where the menu needs it. #[cfg(target_os = "macos")] diff --git a/docs/ui-parity.md b/docs/ui-parity.md index b1c503f5c5..1f9cad576e 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -1,6 +1,6 @@ # UI parity with Microsoft Word -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** 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-17 · **Change:** trivial (the ribbon's paste choices in the context menu, #2583; 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 @@ -59,6 +59,7 @@ Document title opens Rename Document; the account badge opens Options › Genera | Date | Change | Summary | |---|---|---| +| 2026-10-17 | trivial | Context menu: Paste, Keep Text Only, Merge Formatting and Paste Special… replace the single Paste, the same items as Home › Paste's menu (#2583). Word's context menu shows its Paste Options as a row of icons; here they are labelled items with their shortcuts, as on the ribbon | | 2026-10-13 | trivial | New/Modify Table Style: No Border on a part that inherits its lines hides them; emptying the font box makes the part's font inherited again (#1288) | | 2026-10-14 | trivial | Tab stops edited on the ruler: corner kind selector, click to set, drag to move or off to clear, per-kind markers mirrored for RTL; each gesture one `para.tabs` call and one undo step; Rulers 85% → 90% | | 2026-10-12 | trivial | Tables on the page: one selected cell (Select Cell, or a click along its left edge) is a cell selection when its border is dragged: the border moves in its row only (#962) | From 6a607791055ca2ccd21a7725e6c0e316fabc6fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=B3=D0=B4=D0=B0=20=D0=9C=2E=D0=93=2E?= Date: Sat, 17 Oct 2026 12:50:50 +0300 Subject: [PATCH 065/336] Fonts: Times New Roman keeps Liberation Serif under the roman-font rule (#1613) #1613 draws a missing font in Cambria when the font table classes it as roman, as Word does. Times New Roman is roman in every font table, so where it isn't installed (Linux, the web build) every Times document came out in Caladea: lines 2.1 pt taller at 12 pt and different line breaks. Word always has Times New Roman, so it never applies that rule to it. The rule now leaves alone a font whose substitutes are chosen by its name (a metric-compatible clone or the same design under another name: Times, Arial, Courier New, Palatino, the symbol, Hebrew and Persian fonts). Fonts with no clone of their own (Cambria, Georgia, brand fonts) still follow it. Checked on 251 real documents against PDFs from Word: exact page counts 201 -> 229, page breaks in the same place 45.4% -> 51.9%. --- crates/fonts/src/word.rs | 46 ++++++++++++++++++++++++++++---------- crates/layout/src/para.rs | 6 ++++- crates/layout/src/tests.rs | 14 ++++++++++++ docs/file-format-parity.md | 3 ++- 4 files changed, 55 insertions(+), 14 deletions(-) diff --git a/crates/fonts/src/word.rs b/crates/fonts/src/word.rs index 18daef4a11..a1b908afe2 100644 --- a/crates/fonts/src/word.rs +++ b/crates/fonts/src/word.rs @@ -25,6 +25,18 @@ pub struct Resolved { /// metric-compatible come first (Carlito ↔ Calibri, Caladea ↔ Cambria, Liberation ↔ Arial / /// Times New Roman / Courier New), then common system fonts, then our bundled families. fn substitutes(family: &str) -> &'static [&'static str] { + substitutes_by_name(family).0 +} + +/// Does `family` have substitutes of its own, picked by its name (a metric-compatible clone such +/// as Liberation Serif for Times New Roman, or the same design under another name), rather than +/// the generic lists any unknown font gets? +pub fn has_named_substitutes(family: &str) -> bool { + substitutes_by_name(charset_base_family(family)).1 +} + +/// [`substitutes`], and whether they were chosen for this family by name. +fn substitutes_by_name(family: &str) -> (&'static [&'static str], bool) { const SANS: &[&str] = &["Carlito", "Helvetica Neue", "Arial", "Liberation Sans", "Noto Sans", "DejaVu Sans", "Source Sans 3"]; const SERIF: &[&str] = &["Caladea", "Times New Roman", "Liberation Serif", "Georgia", "Noto Serif", "DejaVu Serif", "Source Serif 4"]; const MONO: &[&str] = &["Courier New", "Liberation Mono", "Menlo", "Consolas", "DejaVu Sans Mono", "JetBrains Mono"]; @@ -68,20 +80,21 @@ fn substitutes(family: &str) -> &'static [&'static str] { ]; let f = family.to_ascii_lowercase(); if is_hebrew(&f) { - return HEBREW; + return (HEBREW, true); } if is_persian_naskh(&f) { - return NASKH; + return (NASKH, true); } if is_persian_sans(&f) { - return ARABIC_SANS; + return (ARABIC_SANS, true); } match f.as_str() { - "palatino linotype" | "book antiqua" | "palatino" => PALATINO, - "arial" | "helvetica" | "arial nova" => ARIAL, - "times new roman" | "times" => TIMES, - "courier new" | "courier" | "consolas" | "lucida console" | "cascadia code" | "cascadia mono" => MONO, - "symbol" | "wingdings" | "wingdings 2" | "wingdings 3" | "segoe ui symbol" | "webdings" => SYMBOL, + "palatino linotype" | "book antiqua" | "palatino" => (PALATINO, true), + "arial" | "helvetica" | "arial nova" => (ARIAL, true), + "times new roman" | "times" => (TIMES, true), + "courier new" | "courier" | "consolas" | "lucida console" | "cascadia code" | "cascadia mono" => (MONO, true), + "symbol" | "wingdings" | "wingdings 2" | "wingdings 3" | "segoe ui symbol" | "webdings" => (SYMBOL, true), + // Serif faces with no open clone of their own: the generic serif list, as for any font. "cambria" | "cambria math" | "georgia" @@ -93,10 +106,10 @@ fn substitutes(family: &str) -> &'static [&'static str] { | "bookman old style" | "sitka text" | "sitka" - | "aptos serif" => SERIF, - _ if f.contains("mono") || f.contains("code") => MONO, - _ if f.contains("serif") && !f.contains("sans") => SERIF, - _ => SANS, + | "aptos serif" => (SERIF, false), + _ if f.contains("mono") || f.contains("code") => (MONO, false), + _ if f.contains("serif") && !f.contains("sans") => (SERIF, false), + _ => (SANS, false), } } @@ -363,4 +376,13 @@ mod tests { // Generic names with "Hebrew" in them also get Hebrew substitutions assert!(substitutes("SomeFont Hebrew").contains(&"Noto Sans Hebrew")); } + #[test] + fn fonts_with_a_clone_have_named_substitutes() { + for f in ["Times New Roman", "Times New Roman Cyr", "Arial", "Courier New", "Palatino Linotype", "Symbol"] { + assert!(has_named_substitutes(f), "{f}"); + } + for f in ["No Such Brand Font", "Cambria", "Georgia", "Brand Serif"] { + assert!(!has_named_substitutes(f), "{f}"); + } + } } diff --git a/crates/layout/src/para.rs b/crates/layout/src/para.rs index 6b1ea46fd4..5d39905852 100644 --- a/crates/layout/src/para.rs +++ b/crates/layout/src/para.rs @@ -671,10 +671,14 @@ fn east_asian(c: char) -> bool { const MISSING_ROMAN_FONT: &str = "Cambria"; /// The family to look `font` up as: [`MISSING_ROMAN_FONT`] for a missing roman font, else `font`. +/// A font with substitutes of its own keeps them: Times New Roman is roman in every font table, +/// and where it isn't installed Liberation Serif has its metrics; Word never meets it missing. fn lookup_family<'f>(doc: &Document, font: &'f str) -> &'f str { + use wordcraft_fonts::word::{charset_base_family, has_named_substitutes}; let db = wordcraft_fonts::FontDb::global(); let roman = doc.settings.roman_fonts.iter().any(|f| f.eq_ignore_ascii_case(font)); - if roman && !db.has_family(font) && !db.has_family(wordcraft_fonts::word::charset_base_family(font)) { MISSING_ROMAN_FONT } else { font } + let missing = !db.has_family(font) && !db.has_family(charset_base_family(font)); + if roman && missing && !has_named_substitutes(font) { MISSING_ROMAN_FONT } else { font } } impl<'a> Builder<'a> { diff --git a/crates/layout/src/tests.rs b/crates/layout/src/tests.rs index d6865b0d92..93c9385b79 100644 --- a/crates/layout/src/tests.rs +++ b/crates/layout/src/tests.rs @@ -750,6 +750,20 @@ fn a_missing_roman_font_is_drawn_as_word_draws_it() { assert_ne!(face(false), cambria); } +#[test] +fn a_missing_roman_font_with_its_own_substitute_keeps_it() { + // Times New Roman is roman in every font table. Where it isn't installed it keeps its metric + // clone (Liberation Serif), not Cambria: Word, which always has it, never draws it missing. + let mut d = Document::from_text("Serif"); + d.settings.roman_fonts = vec!["Times New Roman".into()]; + let p = d.para_mut(StoryRef::Body, &Path::top(0)).unwrap(); + p.runs[0].props.font = Some("Times New Roman".into()); + let l = lay(&d); + let Some(Placed::Lines { para, .. }) = l.pages[0].items.iter().find(|i| matches!(i, Placed::Lines { .. })) else { panic!("no text") }; + let first = para.clusters.first().expect("a cluster"); + assert_eq!(para.styles[first.style as usize].face, wordcraft_fonts::word::resolve("Times New Roman", false, false).face); +} + #[test] fn an_empty_section_mark_stays_on_a_full_page() { use wordcraft_doc::section::SectionProps; diff --git a/docs/file-format-parity.md b/docs/file-format-parity.md index 38a1638f16..8153650e00 100644 --- a/docs/file-format-parity.md +++ b/docs/file-format-parity.md @@ -1,6 +1,6 @@ # File-format parity with Microsoft Word -> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-14 · **Change:** trivial (every table style part round-trips, #714); previously trivial: VML freeforms read and kept on save; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: `flowChartOr` draws its circle and cross spokes, as in Word; previously trivial: RTF: Mac OS Roman and DOS text, #1653; previously trivial: VML text boxes keep their internal margins, fit to their text and vertical alignment; hidden drawings not shown; previously trivial: PDF export draws horizontal and vertical lines inside groups, #1474; previously trivial: symbol-font text exported as Unicode, #776; previously trivial: text-box margins, Resize shape to fit text and freeforms in groups, #743; previously trivial: formatted paste from the system clipboard's HTML and RTF, #687; previously trivial: independent complex-script formatting inheritance and round-trip regression tests; previously trivial: import/export fixes for TXT, RTF, ODT, HTML and LaTeX, #433–#440; previously major: first version; every format Word reads or writes, measured from the readers and writers on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4, plus Word for Windows' extra formats +> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-17 · **Change:** trivial (Times New Roman keeps Liberation Serif under the roman-font rule; previously trivial: every table style part round-trips, #714); previously trivial: VML freeforms read and kept on save; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: `flowChartOr` draws its circle and cross spokes, as in Word; previously trivial: RTF: Mac OS Roman and DOS text, #1653; previously trivial: VML text boxes keep their internal margins, fit to their text and vertical alignment; hidden drawings not shown; previously trivial: PDF export draws horizontal and vertical lines inside groups, #1474; previously trivial: symbol-font text exported as Unicode, #776; previously trivial: text-box margins, Resize shape to fit text and freeforms in groups, #743; previously trivial: formatted paste from the system clipboard's HTML and RTF, #687; previously trivial: independent complex-script formatting inheritance and round-trip regression tests; previously trivial: import/export fixes for TXT, RTF, ODT, HTML and LaTeX, #433–#440; previously major: first version; every format Word reads or writes, measured from the readers and writers on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4, plus Word for Windows' extra formats Every format Word opens or saves, what WordCraft does with it, and how it's tested. Fidelity is the share of a typical real-world file's content and formatting that survives (read: shows correctly; @@ -218,6 +218,7 @@ attributes inside stories and style or list definitions; a regenerated theme. Te | 2026-10-14 | trivial | DrawingML `flowChartOr` preserves its circle and crossing spokes, including when grouped or rotated | | 2026-10-14 | trivial | `minorBidi`/`majorBidi` theme fonts name the theme's complex-script font (`a:cs`); an empty one names nothing, so the inherited font applies, as measured in Word | | 2026-10-14 | trivial | A missing font the font table classes as roman is drawn in Cambria, as measured in Word (Calibri for the other classes, as before) | +| 2026-10-17 | trivial | The roman-font rule leaves fonts with a substitute of their own alone: Times New Roman (roman in every font table) is drawn in Liberation Serif where it isn't installed, not Caladea. On a 251-document corpus checked against Word PDFs, exact page counts 201 → 229 | | 2026-10-14 | minor | Legacy DOC DIB helper honours the supplied byte limit for its BMP header/copy, decoder and PNG output, with a bounded BMP fallback. Generated 1-pixel regression only; resource-exhaustion consequences unmeasured, fidelity/readiness estimates unchanged. Related: #1334 and #1554 | | 2026-10-14 | trivial | Right triangles (`rtTriangle`) keep their right angle at the bottom left instead of becoming isosceles triangles, and save back as `rtTriangle` | | 2026-10-13 | trivial | Section `w:vAlign="both"` reads as Justified and writes back as `both` (#1320) | From e60c69977516bb812b139d605cedc1bb21324b3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=B3=D0=B4=D0=B0=20=D0=9C=2E=D0=93=2E?= Date: Sat, 17 Oct 2026 13:30:45 +0300 Subject: [PATCH 066/336] Layout: a Symbol bullet's line keeps Symbol's height without the font Word sizes a line by the fonts in it, and a bullet in Symbol (Word's default, U+F0B7) has a taller ascent than Calibri or Times New Roman. Without Symbol (Linux, macOS without Office, the web build) WordCraft draws the bullet from another face and used that face's height, so every bulleted line was shorter than Word's: 14.5 pt against 15.1 pt in Calibri 11 pt at 1.08 spacing, 14.9 against 15.7 in Times New Roman 12 pt. symbol_font_metrics gives Symbol's and Wingdings' ascent and descent as Word's PDFs state them, and a run in one of them that isn't installed takes them. The label's ascent now comes from its style, so the existing rule (the label's extra ascent added unscaled) sees them. Measured in Word 2016: Calibri 11 / Times 12 bullets 15.08 / 15.72 pt, WordCraft now 15.07 / 15.75 pt. On 251 real documents against Word PDFs, page breaks at the same word 45.4% -> 50.1%. --- crates/fonts/src/word.rs | 12 ++++++++++++ crates/layout/src/para.rs | 9 ++++++--- crates/layout/src/tests.rs | 27 +++++++++++++++++++++++++++ docs/layout-parity.md | 5 +++-- 4 files changed, 48 insertions(+), 5 deletions(-) diff --git a/crates/fonts/src/word.rs b/crates/fonts/src/word.rs index 18daef4a11..a86e5745d7 100644 --- a/crates/fonts/src/word.rs +++ b/crates/fonts/src/word.rs @@ -100,6 +100,18 @@ fn substitutes(family: &str) -> &'static [&'static str] { } } +/// Ascent and descent (em) of Word's symbol fonts, which aren't open fonts. Their characters are +/// drawn from other faces (`wordcraft_doc::symbol_fonts`), but Word sizes a line by the font +/// itself: a Symbol bullet makes its line taller than the text alone. The values are those Word's +/// PDFs give the fonts (`/Ascent`, `/Descent`). +pub fn symbol_font_metrics(family: &str) -> Option<(f64, f64)> { + match family.trim().to_ascii_lowercase().as_str() { + "symbol" => Some((1.005, 0.216)), + "wingdings" => Some((0.899, 0.205)), + _ => None, + } +} + /// The font a Windows 3.1-era charset name stands for: `Times New Roman Cyr`, `Arial CE`, /// `Courier New Baltic`, `Arial (Hebrew)`… are aliases Windows keeps (its FontSubstitutes) for /// the font itself, and old Russian and Central European documents name them. Other names are diff --git a/crates/layout/src/para.rs b/crates/layout/src/para.rs index 6b1ea46fd4..d77119095f 100644 --- a/crates/layout/src/para.rs +++ b/crates/layout/src/para.rs @@ -715,7 +715,11 @@ impl<'a> Builder<'a> { } } let size = rc.draw_size() * if small { 0.8 } else { 1.0 }; - let (a, d) = wordcraft_fonts::word::line_metrics(&face); + let (a, d) = match wordcraft_fonts::word::symbol_font_metrics(&rc.font) { + // A symbol font that isn't installed keeps its own height, as in Word. + Some((a, d)) if !wordcraft_fonts::FontDb::global().has_family(&rc.font) => (a * face.upem, d * face.upem), + _ => wordcraft_fonts::word::line_metrics(&face), + }; let k = size as f64 / face.upem.max(1.0); // A fallback face (letters the requested font lacks) is emboldened or slanted when it has // no bold or italic of its own. @@ -1533,8 +1537,7 @@ pub fn layout_para(p: &Paragraph, env: &ParaEnv) -> ParaLayout { for c in &cl { style = c.style; if let Some(st) = b.styles.get(style as usize) { - let a = wordcraft_fonts::word::line_metrics(&st.face).0 * st.size as f64 / st.face.upem.max(1.0); - ascent = ascent.max(a as f32 + st.shift.max(0.0)); + ascent = ascent.max(st.ascent + st.shift.max(0.0)); } let g0 = glyphs.len(); for g in b.glyphs.get(c.g0 as usize..c.g1 as usize).into_iter().flatten() { diff --git a/crates/layout/src/tests.rs b/crates/layout/src/tests.rs index d6865b0d92..faff1692a2 100644 --- a/crates/layout/src/tests.rs +++ b/crates/layout/src/tests.rs @@ -1043,6 +1043,33 @@ fn symbol_font_bullet_without_the_font_draws_a_bullet() { assert_eq!(label.glyphs.len(), 1); } +#[test] +fn a_symbol_bullet_sizes_its_line_by_the_symbol_font() { + // Measured in Word: a Symbol bullet makes its line taller than the text alone (Calibri 11 pt + // at 1.08 spacing: 15.1 pt, not 14.5 pt), the extra ascent added unscaled. Symbol isn't an open + // font; without it the bullet is drawn from another face but its line keeps Symbol's height. + if !wordcraft_fonts::word::resolve("Symbol", false, false).substituted { + return; + } + let first_line = |label_font: &str| { + let mut d = Document::from_text("one"); + let id = d.numbering.add_list(wordcraft_doc::ListKind::BulletChar('\u{F0B7}')); + for a in &mut d.numbering.abstracts { + if let Some(l) = a.levels.first_mut() { + l.chr.font = Some(label_font.into()); + } + } + d.format_paragraphs(&Pos::body(0, 0), &Pos::body(0, 0), &|p| p.numbering = Some(wordcraft_doc::props::NumRef { num: id, level: 0 })).unwrap(); + let l = lay(&d); + let Some(Placed::Lines { para, .. }) = l.pages[0].items.iter().find(|i| matches!(i, Placed::Lines { .. })) else { panic!("no text") }; + let size = para.styles.iter().map(|s| s.size).fold(0.0f32, f32::max); + (para.label.as_ref().map_or(0.0, |lab| lab.ascent), size) + }; + let (ascent, size) = first_line("Symbol"); + let want = (1.005 * f64::from(size)) as f32; + assert!((ascent - want).abs() < 0.01, "{ascent} vs {want}"); +} + #[test] fn wingdings_text_draws_and_reads_as_its_unicode_symbols() { // Word stores symbol-font text both ways: plain codes ("J" = smiley) and private-use codes diff --git a/docs/layout-parity.md b/docs/layout-parity.md index eb6ffad0a2..a3ca40a411 100644 --- a/docs/layout-parity.md +++ b/docs/layout-parity.md @@ -1,6 +1,6 @@ # Layout and pagination parity with Microsoft Word -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** trivial (consistent cell widths override a stale table grid; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: page-break-only list paragraphs hide their labels; previously trivial: floating-table overlap ignores empty line-break lines; previously trivial: paragraph spacing adds up when the document turns HTML auto spacing off; previously trivial: an inherited footer sizes its percentage tables by its own section; previously trivial: an empty section mark stays on a full page; previously trivial: a fitting row with less room than its minimum height moves on whole; oversized rows still split; previously trivial: text past an exact-height row is cut off; previously trivial: a list number stays with text after a leading page break; previously trivial: a bookmark after a page break takes no line; previously trivial: a page break followed by a page break before or a next-page section starts one page; previously trivial: All Caps and Small Caps draw ß as SS, #1581; previously trivial: justified vertical page alignment, #1320; previously trivial: page numbers restart at 0; previously trivial: a list label moves with its first line when the paragraph is centred or end-aligned (#1159); previously trivial: PAGE fields in a paragraph split across pages show the page they are on, #1352; previously trivial: a dragged floating object anchors in the column it lands in, #884; previously trivial: a paragraph ending on a page with footnotes keeps its space after above the separator, #1116; previously trivial: symbol-font text drawn as Unicode symbols, #776; previously trivial: large-table scrolling measured and fixed, #811; previously trivial: text boxes use their own margins; Resize shape to fit text never cuts text, #743; previously trivial: complex-script shaping and Myanmar line breaking, #604; previously trivial: body page totals, table-cell footnotes and bookmark link pages fixed, #450 #451 #455; previously major: first version; line breaking, pagination and object placement checked against Word's behaviour from the layout source on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-17 · **Change:** trivial (symbol fonts keep their line height without the font; previously trivial: consistent cell widths override a stale table grid; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: page-break-only list paragraphs hide their labels; previously trivial: floating-table overlap ignores empty line-break lines; previously trivial: paragraph spacing adds up when the document turns HTML auto spacing off; previously trivial: an inherited footer sizes its percentage tables by its own section; previously trivial: an empty section mark stays on a full page; previously trivial: a fitting row with less room than its minimum height moves on whole; oversized rows still split; previously trivial: text past an exact-height row is cut off; previously trivial: a list number stays with text after a leading page break; previously trivial: a bookmark after a page break takes no line; previously trivial: a page break followed by a page break before or a next-page section starts one page; previously trivial: All Caps and Small Caps draw ß as SS, #1581; previously trivial: justified vertical page alignment, #1320; previously trivial: page numbers restart at 0; previously trivial: a list label moves with its first line when the paragraph is centred or end-aligned (#1159); previously trivial: PAGE fields in a paragraph split across pages show the page they are on, #1352; previously trivial: a dragged floating object anchors in the column it lands in, #884; previously trivial: a paragraph ending on a page with footnotes keeps its space after above the separator, #1116; previously trivial: symbol-font text drawn as Unicode symbols, #776; previously trivial: large-table scrolling measured and fixed, #811; previously trivial: text boxes use their own margins; Resize shape to fit text never cuts text, #743; previously trivial: complex-script shaping and Myanmar line breaking, #604; previously trivial: body page totals, table-cell footnotes and bookmark link pages fixed, #450 #451 #455; previously major: first version; line breaking, pagination and object placement checked against Word's behaviour from the layout source on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 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, @@ -25,7 +25,7 @@ font can't match Word's metrics. | Distributed / Thai distributed alignment | 🟡 | read; layout untested | 1–2 | | Default font metrics (Aptos, Calibri, Cambria…) | 🟡 | Carlito/Caladea/Liberation are metric-compatible for Calibri/Cambria/Arial/Times/Courier; **Aptos (Word's default since 2024) has no metric-compatible open substitute**, so new Word documents wrap differently ([typing-parity.md](typing-parity.md) Known gaps). Needs an owner decision (commission or license a metric-matched font) | 6–10 + owner | | Colour emoji and colour fonts on screen and in page renders: COLR v0/v1 paint graphs (solid fills, linear, radial and sweep gradients, composite modes) and bitmap strikes (Apple Color Emoji `sbix`, Noto Color Emoji `CBDT`) drawn in the font's colours at the layout's position and size; a dark page doesn't invert them, the same as pictures | 🟡 | `fonts::color` (skrifa), `render::color_glyph`; `render::tests_color_glyphs::emoji_are_drawn_in_colour` and `emoji_keep_their_colours_on_a_dark_page` (system font, skip without one), `render::color_glyph::tests`, `fonts::color::tests` (#1269). CBDT is verified on a real page with Noto Color Emoji; COLR v0/v1 and sbix are implemented to the OpenType spec and tested with synthetic data, but not yet seen on a real font (Segoe UI Emoji, COLRv1 Noto, Apple Color Emoji). PDF export already drew them through krilla | 1–2 | -| Symbol fonts (Symbol, Wingdings, Wingdings 2/3, Webdings) without the proprietary font: each character, stored as U+F020–U+F0FF or a plain 8-bit code, is drawn as its Unicode equivalent from a face that has it; PDF text and copies read as those symbols | ✅ | `doc::symbol_fonts` tables (Adobe Symbol table and Unicode L2/12-368), `layout::tests::wingdings_text_draws_and_reads_as_its_unicode_symbols` (#776). Shapes come from Noto Sans Symbols / Symbols 2, DejaVu Sans or Segoe UI Symbol, so they differ from Word's; Wingdings 0xFF (not in Unicode) still has no symbol | — | +| Symbol fonts (Symbol, Wingdings, Wingdings 2/3, Webdings) without the proprietary font: each character, stored as U+F020–U+F0FF or a plain 8-bit code, is drawn as its Unicode equivalent from a face that has it; PDF text and copies read as those symbols | ✅ | `doc::symbol_fonts` tables (Adobe Symbol table and Unicode L2/12-368), `layout::tests::wingdings_text_draws_and_reads_as_its_unicode_symbols` (#776). Shapes come from Noto Sans Symbols / Symbols 2, DejaVu Sans or Segoe UI Symbol, so they differ from Word's; Wingdings 0xFF (not in Unicode) still has no symbol. A line keeps the symbol font's own height (Symbol, Wingdings: the ascent and descent Word's PDFs give them), so a Symbol bullet makes its line as tall as in Word, `layout::tests::a_symbol_bullet_sizes_its_line_by_the_symbol_font` | — | | Indents in characters (`w:firstLineChars`, `w:leftChars`…), measured in the paragraph mark's font size | ✅ | #534, `layout::tests::character_indents_use_the_paragraph_mark_size`; spacing in lines (`w:beforeLines`/`w:afterLines`) uses the stored points (needs the document grid) | — | | Line spacing (single, 1.5, double, at least, exactly, multiple), spacing before/after, contextual spacing | 🟡 | Contextual spacing measured in body paragraphs and headers (below), with a four-paragraph table-cell regression; list-label ascent overflow is unscaled by multiple spacing and uses descent for automatic and at-least spacing (#601); other line-spacing combinations remain (#108, #135 open) | 3–5 | | Kerning only where Kerning for fonts asks for it (`w:kern`, at or above its size) | ✅ | Measured in Word for Windows: no `w:kern` or `w:kern="0"` is unkerned; WordCraft kerned every run, so its lines held more text than Word's. `layout::tests::kerning_follows_kerning_for_fonts_like_word` | — | @@ -159,6 +159,7 @@ No overall parity estimate changes from these probes. | Date | Change | Summary | |---|---|---| +| 2026-10-17 | trivial | A Symbol or Wingdings run without the font keeps that font's line height: a Symbol bullet's line is 15.1 pt in Calibri 11 pt at 1.08 spacing, as in Word (was 14.5 pt). On a 251-document corpus checked against Word PDFs, page breaks at the same word 45.4% → 50.1% | | 2026-10-14 | trivial | Row table border exceptions draw without leaking into other rows | | 2026-10-14 | trivial | Consistent cell widths override a stale table grid | | 2026-10-14 | trivial | Paragraphs inside complex-field instructions take no flow height; empty result paragraphs keep their line. | From 0c9575230c63f6414381bd88c6873ddaf7f046ac Mon Sep 17 00:00:00 2001 From: Martin Ball Date: Sat, 17 Oct 2026 11:39:44 +0100 Subject: [PATCH 067/336] #2625: recovery does not retry a snapshot that cannot be copied until the document changes A failure caused by the snapshot itself (size limit, folder or disk budget, a value the format cannot keep) now keeps the recorded revision, so tick does not rewrite up to 256 MiB every interval while nothing changes. Transient failures (locks, I/O) still retry at the next interval. Co-Authored-By: Claude Opus 5.5 (1M context) --- ROADMAP.md | 1 + crates/engine/src/recovery/desktop.rs | 96 ++++++++++++++++++++++----- docs/recovery.md | 5 +- 3 files changed, 85 insertions(+), 17 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 40407d18b6..dbbb5b5918 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -379,6 +379,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | 2026-10-12 | Native drawing-level Word ink preserved through DOCX edits and repeated saves, including its InkML part and optional VML picture fallback; native ink rendering and editing remain missing | | 2026-10-12 | List-label line height: automatic spacing scales body text before adding label ascent overflow; AtLeast spacing keeps text descent. Original Word for Mac probes and deterministic layout tests cover symbol/decimal labels, wrapped lines, fixed/minimum spacing and inline pictures. Overall readiness unchanged; [evidence](docs/layout-parity.md#list-label-line-spacing) | | 2026-10-12 | Local desktop recovery: unsaved drafts and edits are snapshotted in the profile; File › Recover restores an unsaved copy or confirms discard. Full model storage includes media and opaque parts. Password sessions skip plaintext snapshots. See [recovery.md](docs/recovery.md); stability estimates remain unchanged. | +| 2026-10-17 | Recovery: a snapshot that fails on its own size, the folder or disk budget, or a value the format cannot keep is not rewritten every 10 s while nothing changes; it waits for the next edit (#2625). Transient failures still retry. | | 2026-10-12 | Legacy table paragraph spacing: `allowSpaceOfSameStyleInTable` survives DOCX save; enabled modes 11/12/14 keep contextual same-style spacing in cells. Mode 15 and non-cell controls unchanged; measured against Word for Mac 16.113.3 | | 2026-10-11 | Contextual paragraph spacing: same-style first-only 12/8 pt and 10/12 pt gaps corrected to 0 pt and 2 pt in body and header layout, with per-side controls; measured against Word 16.113.3. Ordinary overlap remains #108 | | 2026-10-11 | Citations and bibliography in all 12 of Word's styles: Harvard, ISO 690 (author-date and numerical), Turabian, GB/T 7714, GOST (name and title sort) and SIST02 join APA, MLA, Chicago and IEEE; numbered styles count in order of first citation (#383) | diff --git a/crates/engine/src/recovery/desktop.rs b/crates/engine/src/recovery/desktop.rs index 676a6ed0cd..b339ee5862 100644 --- a/crates/engine/src/recovery/desktop.rs +++ b/crates/engine/src/recovery/desktop.rs @@ -25,6 +25,29 @@ pub(crate) struct Snapshot { generation: u64, pub document: Document, } +/// A failed snapshot write. `permanent` failures come from the snapshot itself (its size, the +/// folder or disk budget, a value the format cannot keep): writing the same document again would +/// fail the same way, so it is not retried until the document changes (#2625). +#[derive(Debug)] +struct SaveError { + message: String, + permanent: bool, +} +impl SaveError { + fn permanent(message: impl Into) -> Self { + Self { message: message.into(), permanent: true } + } +} +impl From for SaveError { + fn from(message: String) -> Self { + Self { message, permanent: false } + } +} +impl From<&str> for SaveError { + fn from(message: &str) -> Self { + message.to_owned().into() + } +} struct Entry { info: RecoveryInfo, path: PathBuf, @@ -222,7 +245,7 @@ impl Store { } Ok(used) } - fn save(&mut self, s: Snapshot) -> Result<(), String> { + fn save(&mut self, s: Snapshot) -> Result<(), SaveError> { let id = s.info.id.clone(); let result = self.write_snapshot(s); // Include failures before temp-file creation (quota checks, locking, etc.). @@ -235,11 +258,11 @@ impl Store { } result } - fn write_snapshot(&mut self, mut s: Snapshot) -> Result<(), String> { + fn write_snapshot(&mut self, mut s: Snapshot) -> Result<(), SaveError> { let dir = self.root.join(&s.info.id); if !dir.exists() { if self.directories()?.len() >= MAX_RECORDS { - return Err("recovery folder is full; discard an older copy".into()); + return Err(SaveError::permanent("recovery folder is full; discard an older copy")); } private_dir(&dir).map_err(|e| e.to_string())?; } @@ -253,16 +276,20 @@ impl Store { let limit = MAX_BYTES.min(DISK_BUDGET.saturating_sub(self.disk_used()?)); let tmp = dir.join(format!("pending-{}.tmp", new_id()?)); let final_path = dir.join(format!("snapshot-{:020}.json", s.generation)); - let result = (|| { + let result = (|| -> Result<(), SaveError> { let file = private_file(&tmp, true).map_err(|e| e.to_string())?; - let mut writer = Limited { inner: BufWriter::new(file), remaining: limit }; - serde_json::to_writer(&mut writer, &s).map_err(|e| e.to_string())?; - writer.flush().map_err(|e| e.to_string())?; + let mut writer = Limited { inner: BufWriter::new(file), remaining: limit, exceeded: false }; + let written = serde_json::to_writer(&mut writer, &s).map_err(|e| e.to_string()).and_then(|()| writer.flush().map_err(|e| e.to_string())); + if let Err(e) = written { + return Err(SaveError { message: e, permanent: writer.exceeded }); + } writer.inner.get_ref().sync_all().map_err(|e| e.to_string())?; drop(writer); - let check = read_snapshot(&tmp)?; + // The copy was written and synchronized; one that does not decode back to the same + // document fails the same way every time. + let check = read_snapshot(&tmp).map_err(SaveError::permanent)?; if check.document != s.document { - return Err("recovery format did not preserve the full document".into()); + return Err(SaveError::permanent("recovery format did not preserve the full document")); } // A new generation is never written over the last valid file. fs::rename(&tmp, &final_path).map_err(|e| e.to_string())?; @@ -329,10 +356,12 @@ impl Store { struct Limited { inner: W, remaining: u64, + exceeded: bool, } impl Write for Limited { fn write(&mut self, bytes: &[u8]) -> io::Result { if bytes.len() as u64 > self.remaining { + self.exceeded = true; return Err(io::Error::other("recovery snapshot or folder size limit exceeded")); } let n = self.inner.write(bytes)?; @@ -357,6 +386,8 @@ enum Outcome { struct Event { documents: Vec, outcome: Result, + /// The failure would repeat for the same document; see [`SaveError`]. + permanent: bool, } pub(crate) struct Recovery { tx: mpsc::SyncSender, @@ -381,24 +412,28 @@ impl Recovery { let mut store = match Store::new(root) { Ok(s) => s, Err(e) => { - let _ = events.send(Event { documents: Vec::new(), outcome: Err(e) }); + let _ = events.send(Event { documents: Vec::new(), outcome: Err(e), permanent: false }); wake(); return; } }; let initial = store.scan().map(|_| Outcome::Done); - if events.send(Event { documents: store.inventory(), outcome: initial }).is_err() { + if events.send(Event { documents: store.inventory(), outcome: initial, permanent: false }).is_err() { return; } wake(); while let Ok(job) = jobs.recv() { + let mut permanent = false; let result = match job { - Job::Save(s) => store.save(*s).map(|_| Outcome::Done), + Job::Save(s) => store.save(*s).map(|_| Outcome::Done).map_err(|e| { + permanent = e.permanent; + e.message + }), Job::Scan => store.scan().map(|_| Outcome::Done), Job::Load(id, expected) => store.load(&id).map(|s| Outcome::Loaded(Box::new(s), expected)), Job::Discard(id) => store.discard(&id).map(|_| Outcome::Discarded(id)), }; - if events.send(Event { documents: store.inventory(), outcome: result }).is_err() { + if events.send(Event { documents: store.inventory(), outcome: result, permanent }).is_err() { break; } wake(); @@ -437,6 +472,7 @@ impl Recovery { self.busy = false; self.ready = true; self.documents = e.documents; + let event_permanent = e.permanent; match e.outcome { Ok(Outcome::Done) => None, Ok(Outcome::Discarded(id)) => { @@ -448,7 +484,11 @@ impl Recovery { Ok(Outcome::Loaded(s, expected)) => Some((*s, expected)), Err(e) => { self.error = Some(e); - self.revision = None; + // A transient failure (a lock, an I/O error) is retried at the next + // interval; one the snapshot itself causes waits for the next edit. + if !event_permanent { + self.revision = None; + } self.due = Instant::now() + INTERVAL; None } @@ -690,9 +730,10 @@ mod tests { #[test] fn size_limit_and_lossy_values_fail_without_committing() { - let mut out = Limited { inner: Vec::new(), remaining: 3 }; + let mut out = Limited { inner: Vec::new(), remaining: 3, exceeded: false }; assert!(out.write_all(b"four").is_err()); assert!(out.inner.is_empty()); + assert!(out.exceeded); let t = Temp::new(); let id = new_id().unwrap(); let mut store = Store::new(t.0.clone()).unwrap(); @@ -701,11 +742,34 @@ mod tests { let bytes = fs::read(&first).unwrap(); let mut invalid = snapshot(&id, "invalid"); invalid.document.last_section.page_w = f32::NAN; - assert!(store.save(invalid).is_err()); + assert!(store.save(invalid).is_err_and(|e| e.permanent)); assert_eq!(fs::read(first).unwrap(), bytes); assert_eq!(Store::files(&t.0.join(id)).unwrap().len(), 1); } + #[test] + fn snapshot_that_cannot_be_copied_is_not_retried_until_the_document_changes() { + // #2625: a failure caused by the snapshot itself (here a value the format cannot keep; + // in the field, the 256 MiB limit) used to be rewritten every interval while dirty. + let t = Temp::new(); + let mut s = session(&t.0); + s.run("document.setText", &json!({"text": "Synthetic draft"})).unwrap(); + s.doc.last_section.page_w = f32::NAN; + save_now(&mut s); + assert!(s.recovery_error().is_some()); + assert!(s.recovery_wait().is_none()); + s.recovery.as_mut().unwrap().due = Instant::now(); + s.recovery_tick(); + assert!(!s.recovery_busy(), "the unchanged document was snapshotted again"); + assert!(s.recovery_error().is_some()); + // An edit tries again. + s.doc.last_section.page_w = 612.0; + s.run("text.insert", &json!({"text": "!"})).unwrap(); + save_now(&mut s); + assert!(s.recovery_error().is_none()); + assert_eq!(s.recovery_documents().len(), 1); + } + #[test] fn failed_pruning_cannot_push_a_record_beyond_the_discovery_limit() { let t = Temp::new(); diff --git a/docs/recovery.md b/docs/recovery.md index 00ce845061..26b5bc802d 100644 --- a/docs/recovery.md +++ b/docs/recovery.md @@ -50,7 +50,10 @@ Windows is therefore unverified. Limits are 256 MiB of encoded JSON per snapshot, 2 GiB of snapshot files per profile, 128 records and 16 generations per record during discovery. Ordinarily only two valid generations remain. Copies are retained until saved or explicitly discarded; there is no automatic age-based deletion. -Over-limit writes report an error and retain the previous valid copy. JSON stores binary parts +Over-limit writes report an error and retain the previous valid copy. A failure caused by the +snapshot itself (the size limit, the folder or disk budget, a value the format cannot keep) is +not retried until the document changes; other failures, such as a lock or I/O error, are tried +again at the next interval. JSON stores binary parts as byte arrays, so it can be substantially larger than DOCX. This first version has a fixed interval, English fallback for new labels, and no cloud, version-history or browser recovery. From 4a9f24f6429061027b24dc42802336e5db647d58 Mon Sep 17 00:00:00 2001 From: vangel76 Date: Sat, 17 Oct 2026 12:51:07 +0200 Subject: [PATCH 068/336] eqarr rows without an align marker centre across the array; they no longer widen the alignment columns A row of an m:eqArr with no `&` was right-aligned at the first column edge and counted into the first column width, shifting the wider rows. Per Word, such a row is centred across the whole array and takes no part in the column widths. Regression test eqarr_rows_without_an_align_marker_are_centred fails without this. Closes #2617. --- crates/layout/src/math.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/crates/layout/src/math.rs b/crates/layout/src/math.rs index bab58ef984..444fee2441 100644 --- a/crates/layout/src/math.rs +++ b/crates/layout/src/math.rs @@ -1498,7 +1498,8 @@ impl Ctx { } let ncols = segs_rows.iter().map(|r| r.len()).max().unwrap_or(1); let mut col_w = vec![0.0f32; ncols]; - for r in &segs_rows { + // Rows without `&` take no part in the alignment columns (they centre instead). + for r in segs_rows.iter().filter(|r| ncols == 1 || r.len() > 1) { for (i, b) in r.iter().enumerate() { if let Some(c) = col_w.get_mut(i) { *c = c.max(b.w); @@ -1517,8 +1518,8 @@ impl Ctx { let dx = if ncols == 1 { 0.0 } else if single { - // A row without `&` aligns with the first column's right edge. - cw - bw + // A row without `&` is centred across the whole array. + (total - bw) / 2.0 } else if i % 2 == 0 { cw - bw } else { @@ -2208,6 +2209,21 @@ mod tests { assert_eq!(math_alnum('A', true, false, MScr::Roman), '𝐀'); } + #[test] + fn eqarr_rows_without_an_align_marker_are_centred() { + use wordcraft_doc::math::*; + // A row without `&` must not widen the alignment columns; it centres across the + // array instead of right-aligning at the first column's edge. + let rows: Vec> = vec![vec![MNode::Run(MRun::new("mmm"))], vec![MNode::Run(MRun::new("x&yyyyyyyy"))]]; + let math = Math { nodes: vec![MNode::EqArr { rows }], ..Default::default() }; + let ls = lines(&lay_in(&math, "", true, 400.0, &MathProps::default())); + assert_eq!(ls.len(), 2, "{ls:?}"); + let left: Vec = ls.iter().map(|(_, gs)| gs.iter().map(|(x, _)| *x).fold(f32::MAX, |a, b| if b < a { b } else { a })).collect(); + assert!(left.iter().all(|x| *x >= -0.5), "nothing hangs left of the array: {left:?}"); + // The short row starts right of the aligned row's first segment. + assert!(ls[0].1.first().is_some_and(|(x, _)| *x > left[1] + 1.0), "short row centred, not flush left: {ls:?}"); + } + #[test] fn hostile_trees_never_panic() { use wordcraft_doc::math::*; From bea8ab8704b3e5a0742c8381333ab643a4376e5d Mon Sep 17 00:00:00 2001 From: vangel76 Date: Sat, 17 Oct 2026 12:54:00 +0200 Subject: [PATCH 069/336] M-parity: ROADMAP progress row for the eqarr centring fix (#2617) --- ROADMAP.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ROADMAP.md b/ROADMAP.md index 40407d18b6..3c925a4b58 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -135,6 +135,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm ## Progress log | Date | What landed | +| 2026-10-17 | Equation arrays (`m:eqArr`): a row without an `&` align marker centres across the array and no longer widens the first column, so wider rows keep their places (First Property of Radicals, #2617) | | 2026-10-14 | DOCX row table border exceptions render and survive saving. Explicit cell borders win; a row exception does not change other rows. | | 2026-10-14 | Tab stops edited on the ruler: the corner selector picks Left/Center/Right/Decimal/Bar, a click sets a stop (Alt skips the snap), dragging moves it with a guide line and position tooltip, dragging off the ruler clears it; markers draw per kind and mirror for RTL; one `para.tabs` call and one undo step per gesture | | 2026-10-12 | Fields keep `w:dirty` through DOCX saves (#877): a begin marker flagged by a generator that couldn't compute a result (pandoc-style TOC placeholders) keeps the flag on open/save — as a buffered field or a range field — so Word still refreshes it on open; computing a result (Update Field, Update Table) clears it, like Word's F9 | From 80b4b824190e9b565838fe215fa55cb7ce2f845c Mon Sep 17 00:00:00 2001 From: Nghiathan13 <201675788+Nghiathan13@users.noreply.github.com> Date: Sat, 17 Oct 2026 19:25:55 +0700 Subject: [PATCH 070/336] =?UTF-8?q?#2738:=20Home=20=E2=80=BA=20Underline?= =?UTF-8?q?=20drop-down=20shows=20its=20lines,=20None,=20More=20Underlines?= =?UTF-8?q?=E2=80=A6=20and=20Underline=20Color?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each underline style in the drop-down is now a sample of its own line, drawn in code in the theme's text colour (Single, Double, Thick, Dotted, Dashed, Dot-dash, the missing Dot-dot-dash, Wave), named for tooltips and assistive technology. Words only uses format.wordUnderline (its own Ctrl+Shift+W); None removes the underline and shows no shortcut; More Underlines… opens the Font dialog; Underline Color › offers Automatic and the theme/standard colours with More Colors…. - engine: format.underlineColor {"color": "RRGGBB" | "auto"} sets or clears CharProps::underline_color and leaves the style alone; format.state reports underlineColor; catalog row Home|Font|Underline Color (parity checklist regenerated: 490/505). - i18n: Dot-dot-dash, More Underlines… and Underline Color in all 22 catalogs (Underline Color reuses each catalog's "Underline color"). - Tests: underline_color_sets_and_clears_the_underlines_own_colour (engine, DOCX round trip, undo, junk params), underline_menu_entries_apply_their_style (UI: every sample is a named entry that applies its style; Words only, None, Underline Color › Automatic). Co-Authored-By: Claude Opus 5.5 --- ROADMAP.md | 6 +- crates/engine/src/catalog.rs | 1 + crates/engine/src/cmd/format.rs | 14 +++ crates/engine/src/tests.rs | 32 ++++++ crates/ui-egui/src/i18n/bg.tsv | 3 + crates/ui-egui/src/i18n/cs.tsv | 3 + crates/ui-egui/src/i18n/de.tsv | 3 + crates/ui-egui/src/i18n/es.tsv | 3 + crates/ui-egui/src/i18n/et.tsv | 3 + crates/ui-egui/src/i18n/fr.tsv | 3 + crates/ui-egui/src/i18n/hr.tsv | 3 + crates/ui-egui/src/i18n/hu.tsv | 3 + crates/ui-egui/src/i18n/it.tsv | 3 + crates/ui-egui/src/i18n/ja.tsv | 3 + crates/ui-egui/src/i18n/ko.tsv | 3 + crates/ui-egui/src/i18n/pl.tsv | 3 + crates/ui-egui/src/i18n/pt-br.tsv | 3 + crates/ui-egui/src/i18n/ru.tsv | 3 + crates/ui-egui/src/i18n/sr-latn.tsv | 3 + crates/ui-egui/src/i18n/sr.tsv | 3 + crates/ui-egui/src/i18n/sv.tsv | 3 + crates/ui-egui/src/i18n/tr.tsv | 3 + crates/ui-egui/src/i18n/uk.tsv | 3 + crates/ui-egui/src/i18n/vi.tsv | 3 + crates/ui-egui/src/i18n/zh-hans.tsv | 3 + crates/ui-egui/src/i18n/zh-hant.tsv | 3 + crates/ui-egui/src/ribbon.rs | 148 +++++++++++++++++++++++++--- docs/parity-checklist.md | 6 +- docs/ui-parity.md | 3 +- 29 files changed, 256 insertions(+), 20 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 40407d18b6..84d4d574b6 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **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-14 · **Change:** 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-17 · **Change:** trivial (progress log: Home › Underline drop-down, #2738; 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-14 · **Change:** trivial (progress log: print-area strings translated in every catalog; 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. @@ -16,7 +16,7 @@ agent control. This page is the summary; the assessment is | Number | Value | Kind | | 2026-10-12 | Table formulas: cell references and ranges (`=B2+B3`, `=SUM(B2:C3)`), arithmetic and comparisons, IF, AND, OR, NOT, TRUE, FALSE, DEFINED and the `\#` number format; ABOVE and LEFT sum the run of numbers next to the cell as Word does; an unknown function or a syntax error is an error instead of a sum; Update Field (F9) recalculates formula fields | -| Ribbon/menu catalog coverage | **487 / 502 (97.0%)** | measured (`cargo xtask parity` → [`docs/parity-checklist.md`](docs/parity-checklist.md)) | +| Ribbon/menu catalog coverage | **490 / 505 (97.0%)** | measured (`cargo xtask parity` → [`docs/parity-checklist.md`](docs/parity-checklist.md)) | | **Feature breadth** (weighted, incl. dialog options, styles, languages beyond the ribbon) | **~80%** | estimated | | **Ready for real work** (full target) | **~60%** (55–63%) | estimated, additive weighted sum over the dimensions | | **Mainstream practitioner** | **~55%** | estimated: weekly-work depth × discounts for interaction (×0.93), stability (×0.90), file exchange (×0.90) | @@ -135,6 +135,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm ## Progress log | Date | What landed | +| 2026-10-17 | Home › Underline's drop-down shows each underline as a sample of its line, adds Dot-dot-dash, None and More Underlines…, and an Underline Color submenu backed by the new `format.underlineColor` command (catalog 490/505) (#2738) | | 2026-10-14 | DOCX row table border exceptions render and survive saving. Explicit cell borders win; a row exception does not change other rows. | | 2026-10-14 | Tab stops edited on the ruler: the corner selector picks Left/Center/Right/Decimal/Bar, a click sets a stop (Alt skips the snap), dragging moves it with a guide line and position tooltip, dragging off the ruler clears it; markers draw per kind and mirror for RTL; one `para.tabs` call and one undo step per gesture | | 2026-10-12 | Fields keep `w:dirty` through DOCX saves (#877): a begin marker flagged by a generator that couldn't compute a result (pandoc-style TOC placeholders) keeps the flag on open/save — as a buffered field or a range field — so Word still refreshes it on open; computing a result (Update Field, Update Table) clears it, like Word's F9 | @@ -403,6 +404,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | Date | Change | Summary | |---|---|---| +| 2026-10-17 | trivial | Progress log: Home › Underline drop-down (#2738) | | 2026-10-14 | trivial | Progress log: row table border exceptions | | 2026-10-12 | minor | Czech proofing review: short-word splits rank after deletion, NOSUGGEST excludes suggestions while accepting typed forms, affix comments apply only to headers, ratios keep spaces and repeated-word messages use Czech quotes; synthetic engine end-to-end and latency checks | | 2026-10-12 | minor | Czech spelling from the installed Hunspell dictionary, runtime-table suggestions and generic checks; Czech interface writes cs-CZ documents; no Czech hyphenation patterns bundled; estimates unchanged | diff --git a/crates/engine/src/catalog.rs b/crates/engine/src/catalog.rs index c6a36a3789..b1ff96eaed 100644 --- a/crates/engine/src/catalog.rs +++ b/crates/engine/src/catalog.rs @@ -56,6 +56,7 @@ Home|Font|Bold|format.bold Home|Font|Italic|format.italic Home|Font|Underline|format.underline Home|Font|Double Underline|format.doubleUnderline +Home|Font|Underline Color|format.underlineColor Home|Font|Strikethrough|format.strikethrough Home|Font|Subscript|format.subscript Home|Font|Superscript|format.superscript diff --git a/crates/engine/src/cmd/format.rs b/crates/engine/src/cmd/format.rs index 4b1fac70e8..61b2b51a41 100644 --- a/crates/engine/src/cmd/format.rs +++ b/crates/engine/src/cmd/format.rs @@ -102,6 +102,8 @@ pub fn specs() -> Vec { CommandSpec::new("format.growFont1", "Grow Font 1 Point", "Home › Font", |s, _| nudge_size(s, 1.0)).key("Mod+]"), CommandSpec::new("format.shrinkFont1", "Shrink Font 1 Point", "Home › Font", |s, _| nudge_size(s, -1.0)).key("Mod+["), CommandSpec::new("format.color", "Font Color", "Home › Font", color).params(r#"{"color": "RRGGBB" | "auto"}"#), + CommandSpec::new("format.underlineColor", "Underline Color", "Home › Font › Underline", underline_color) + .params(r#"{"color": "RRGGBB" | "auto"} (the underline's own colour; "auto" follows the text colour)"#), CommandSpec::new("format.highlight", "Text Highlight Color", "Home › Font", highlight) .params( r#"{"color": "yellow|brightGreen|turquoise|pink|blue|red|darkBlue|teal|green|violet|darkRed|darkYellow|gray50|gray25|black|none"}"#, @@ -366,6 +368,17 @@ fn color(s: &mut Session, v: &Value) -> CmdResult { apply(s, &|x| x.color = Some(tc)) } +/// `format.underlineColor`: the underline's own colour (`w:u w:color`), or `auto` to drop it so the +/// underline follows the text colour again. The underline style is left as it is. +fn underline_color(s: &mut Session, v: &Value) -> CmdResult { + let c = p::req_str(v, "color")?; + let rgb = match TextColor::parse(c).ok_or_else(|| CmdError::Params("color must be RRGGBB or auto".into()))? { + TextColor::Auto => None, + TextColor::Rgb(rgb) => Some(rgb), + }; + apply(s, &|x| x.underline_color = rgb) +} + fn highlight(s: &mut Session, v: &Value) -> CmdResult { let c = p::str(v, "color").unwrap_or("yellow"); let h = Highlight::ALL @@ -677,6 +690,7 @@ pub fn state(s: &Session) -> Value { "bold": all.iter().all(|r| r.bold), "italic": all.iter().all(|r| r.italic), "underline": all.iter().all(|r| r.underline != Underline::None), + "underlineColor": same(&|r| json!(r.underline_color.map_or_else(|| "auto".to_string(), |c| c.hex()))), "strike": all.iter().all(|r| r.strike), "border": all.iter().all(|r| r.border.is_some()), "subscript": all.iter().all(|r| r.vert_align == VertAlign::Subscript), diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 038786e02a..00401c16a1 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -4986,6 +4986,38 @@ fn colour_commands_take_any_custom_hex() { assert_eq!(s.doc.settings.page_color, wordcraft_doc::Rgb::parse("123ABC")); } +/// #2738: Home › Underline › Underline Color sets the underline's own colour and leaves its style +/// alone; `auto` drops it again. One undo step each, kept through a DOCX round trip, and junk is +/// a params error that changes nothing. +#[test] +fn underline_color_sets_and_clears_the_underlines_own_colour() { + let mut s = s(); + run(&mut s, "text.insert", json!({"text": "Lanterns"})); + run(&mut s, "select.all", json!({})); + run(&mut s, "format.underline", json!({"style": "wave"})); + assert_eq!(run(&mut s, "format.state", json!({})).get("underlineColor"), Some(&json!("auto"))); + let style = |s: &Session| s.doc.para_at(&wordcraft_doc::Pos::body(0, 0)).unwrap().runs[0].props.underline; + let wave = style(&s); + for (given, want) in [("C00000", "C00000"), ("#1b5e20", "1B5E20")] { + run(&mut s, "format.underlineColor", json!({"color": given})); + assert_eq!(run(&mut s, "format.state", json!({})).get("underlineColor"), Some(&json!(want))); + assert_eq!(style(&s), wave, "the style is kept"); + } + let back = wordcraft_docx::read(&wordcraft_docx::write(&s.doc).unwrap()).unwrap(); + let para = back.para_at(&wordcraft_doc::Pos::body(0, 0)).unwrap(); + assert_eq!(para.runs[0].props.underline_color, wordcraft_doc::Rgb::parse("1B5E20")); + run(&mut s, "edit.undo", json!({})); + assert_eq!(run(&mut s, "format.state", json!({})).get("underlineColor"), Some(&json!("C00000"))); + run(&mut s, "format.underlineColor", json!({"color": "auto"})); + assert_eq!(run(&mut s, "format.state", json!({})).get("underlineColor"), Some(&json!("auto"))); + assert_eq!(style(&s), wave); + let doc = s.doc.clone(); + for junk in [json!({}), json!({"color": "purple"}), json!({"color": 12}), json!({"color": null}), json!({"color": "#12"})] { + assert!(s.run("format.underlineColor", &junk).is_err(), "{junk}"); + } + assert_eq!(s.doc, doc); +} + /// A document whose content is another format than its extension says opens by what it holds, /// as in Word: RTF saved as `.doc` (accounting systems and report generators save their documents /// that way), a `.docx` package renamed `.doc`, and RTF renamed `.docx`. diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 66d02b6cf9..85e4f68dc3 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -2621,3 +2621,6 @@ # Reading Mode column widths (#1040) Narrow Тясна Medium Средна + Dot-dot-dash Точка-точка-тире + More Underlines… Още подчертавания… + Underline Color Цвят на подчертаването diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 02e380d826..688a1339d1 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -2624,3 +2624,6 @@ preview Title Ukázka # Reading Mode column widths (#1040) Narrow Úzká Medium Střední + Dot-dot-dash Dvojitě čerchované + More Underlines… Další podtržení… + Underline Color Barva podtržení diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 6a9f286f88..197857292d 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -2232,3 +2232,6 @@ # Reading Mode column widths (#1040) Narrow Schmal Medium Mittel + Dot-dot-dash Punkt-Punkt-Strich + More Underlines… Weitere Unterstreichungen… + Underline Color Unterstreichungsfarbe diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 794cef7de7..f8db1ee555 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -2599,3 +2599,6 @@ preview Title Título # Reading Mode column widths (#1040) Narrow Estrecha Medium Media + Dot-dot-dash Punto, punto y raya + More Underlines… Más subrayados… + Underline Color Color de subrayado diff --git a/crates/ui-egui/src/i18n/et.tsv b/crates/ui-egui/src/i18n/et.tsv index b37c9ac9d0..9381617466 100644 --- a/crates/ui-egui/src/i18n/et.tsv +++ b/crates/ui-egui/src/i18n/et.tsv @@ -2267,3 +2267,6 @@ # Reading Mode column widths (#1040) Narrow Kitsas Medium Keskmine + Dot-dot-dash Punkt-punkt-kriipsjoon + More Underlines… Rohkem allajooni… + Underline Color Allajoone värv diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 78f281359a..396d454bb7 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -2240,3 +2240,6 @@ # Reading Mode column widths (#1040) Narrow Étroite Medium Moyenne + Dot-dot-dash Tiret-point-point + More Underlines… Autres soulignements… + Underline Color Couleur de soulignement diff --git a/crates/ui-egui/src/i18n/hr.tsv b/crates/ui-egui/src/i18n/hr.tsv index 2fda6e1926..61a7fa7b1c 100644 --- a/crates/ui-egui/src/i18n/hr.tsv +++ b/crates/ui-egui/src/i18n/hr.tsv @@ -2632,3 +2632,6 @@ preview Title Naslov Bottom Right Cell Donja desna ćelija Inside Horizontal Border Unutarnji vodoravni obrub Inside Vertical Border Unutarnji okomiti obrub + Dot-dot-dash Točka-točka-crtica + More Underlines… Više podcrtavanja… + Underline Color Boja podcrtavanja diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 73d3f752ee..e35413ddea 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -2628,3 +2628,6 @@ # Reading Mode column widths (#1040) Narrow Keskeny Medium Közepes + Dot-dot-dash Pont-pont-vonal + More Underlines… További aláhúzások… + Underline Color Aláhúzás színe diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 0edbd298a2..3021b78eb9 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -2641,3 +2641,6 @@ preview Title Esempio # Reading Mode column widths (#1040) Narrow Stretta Medium Media + Dot-dot-dash Punto punto linea + More Underlines… Altre sottolineature… + Underline Color Colore sottolineatura diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 9c92f9fcfd..f187722c03 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -2233,3 +2233,6 @@ # Reading Mode column widths (#1040) Narrow 狭い Medium 中 + Dot-dot-dash 二点鎖線 + More Underlines… その他の下線… + Underline Color 下線の色 diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 96002d803a..ec5cea352d 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -2488,3 +2488,6 @@ preview Title 제목 Bottom Right Cell 오른쪽 아래 셀 Inside Horizontal Border 안쪽 가로 테두리 Inside Vertical Border 안쪽 세로 테두리 + Dot-dot-dash 이점쇄선 + More Underlines… 밑줄 더 보기… + Underline Color 밑줄 색 diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index dd9969a3b6..888daffae9 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -2622,3 +2622,6 @@ preview Title Tytuł # Reading Mode column widths (#1040) Narrow Wąska Medium Średnia + Dot-dot-dash Kreska-kropka-kropka + More Underlines… Więcej podkreśleń… + Underline Color Kolor podkreślenia diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 5e44995910..13836875c7 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -2245,3 +2245,6 @@ # Reading Mode column widths (#1040) Narrow Estreita Medium Média + Dot-dot-dash Ponto-ponto-traço + More Underlines… Mais sublinhados… + Underline Color Cor do sublinhado diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index a9c41168b7..e4ac9031be 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -2630,3 +2630,6 @@ preview Title Пример # Reading Mode column widths (#1040) Narrow Узкая Medium Средняя + Dot-dot-dash Штрихпунктирный с двумя точками + More Underlines… Другие подчёркивания… + Underline Color Цвет подчёркивания diff --git a/crates/ui-egui/src/i18n/sr-latn.tsv b/crates/ui-egui/src/i18n/sr-latn.tsv index 81d9f4f4af..3ac7fd7527 100644 --- a/crates/ui-egui/src/i18n/sr-latn.tsv +++ b/crates/ui-egui/src/i18n/sr-latn.tsv @@ -2243,3 +2243,6 @@ # Reading Mode column widths (#1040) Narrow Uska Medium Srednja + Dot-dot-dash Tačka-tačka-crta + More Underlines… Još podvlačenja… + Underline Color Boja podvlačenja diff --git a/crates/ui-egui/src/i18n/sr.tsv b/crates/ui-egui/src/i18n/sr.tsv index 42bbeaf7b6..fbbf82a246 100644 --- a/crates/ui-egui/src/i18n/sr.tsv +++ b/crates/ui-egui/src/i18n/sr.tsv @@ -2247,3 +2247,6 @@ # Reading Mode column widths (#1040) Narrow Уска Medium Средња + Dot-dot-dash Тачка-тачка-црта + More Underlines… Још подвлачења… + Underline Color Боја подвлачења diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 6052c9a231..2adf5b40de 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -2647,3 +2647,6 @@ preview Title Titel Bottom Right Cell Nedre högra cellen Inside Horizontal Border Inre vågrät kantlinje Inside Vertical Border Inre lodrät kantlinje + Dot-dot-dash Punkt-punkt-streck + More Underlines… Fler understrykningar… + Underline Color Understrykningsfärg diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 8ace2dd7b6..dd017cbfed 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -2629,3 +2629,6 @@ preview Title Örnek # Reading Mode column widths (#1040) Narrow Dar Medium Orta + Dot-dot-dash Nokta-nokta-çizgi + More Underlines… Diğer Alt Çizgiler… + Underline Color Alt Çizgi Rengi diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index ca2070d95d..8d66dd9728 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -2245,3 +2245,6 @@ # Reading Mode column widths (#1040) Narrow Вузька Medium Середня + Dot-dot-dash Штрихпунктирна з двома крапками + More Underlines… Інші підкреслення… + Underline Color Колір підкреслення diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index c1d28add3d..dd17d2f7a2 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -2672,3 +2672,6 @@ template Letter Thư # Reading Mode column widths (#1040) Narrow Hẹp Medium Trung bình + Dot-dot-dash Nét chấm chấm gạch + More Underlines… Kiểu gạch chân khác… + Underline Color Màu gạch chân diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 07b0861daa..85fb7b180b 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -2245,3 +2245,6 @@ # Reading Mode column widths (#1040) Narrow 窄 Medium 中等 + Dot-dot-dash 双点划线 + More Underlines… 其他下划线… + Underline Color 下划线颜色 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 7b75dc9783..0ca26df75b 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -2234,3 +2234,6 @@ # Reading Mode column widths (#1040) Narrow 窄 Medium 中等 + Dot-dot-dash 雙點虛線 + More Underlines… 其他底線… + Underline Color 底線色彩 diff --git a/crates/ui-egui/src/ribbon.rs b/crates/ui-egui/src/ribbon.rs index 0dd38891ed..fd860fbed9 100644 --- a/crates/ui-egui/src/ribbon.rs +++ b/crates/ui-egui/src/ribbon.rs @@ -485,6 +485,100 @@ pub(crate) fn size_menu(ui: &mut Ui, app: &mut WordApp) { mi(ui, app, crate::dialogs::PAPER_LABELS.more, "ui.dialog", json!({"name": "pageSetup"})); } +/// The underline styles Home › Underline offers as line samples: (`format.underline` style, name). +const UNDERLINE_STYLES: [(&str, &str); 8] = [ + ("single", "Single"), + ("double", "Double"), + ("thick", "Thick"), + ("dotted", "Dotted"), + ("dash", "Dashed"), + ("dotDash", "Dot-dash"), + ("dotDotDash", "Dot-dot-dash"), + ("wave", "Wave"), +]; + +/// Home › Font › Underline's drop-down (#2738): a sample of each line, Words only, None, More +/// Underlines… (the Font dialog) and Underline Color. +fn underline_menu(ui: &mut Ui, app: &mut WordApp) { + for (style, name) in UNDERLINE_STYLES { + underline_item(ui, app, style, name); + } + mi(ui, app, "Words only", "format.wordUnderline", json!({})); + // Not `mi`: Ctrl+U beside None would read as its shortcut, but Ctrl+U toggles a single underline. + let enabled = crate::widgets::enabled(app, "format.underline"); + if ui.add_enabled(enabled, egui::Button::new(tl!("None")).min_size(vec2(200.0, 0.0))).clicked() { + crate::widgets::run_from_ribbon(app, "format.underline", json!({"value": false})); + ui.close(); + } + ui.separator(); + mi(ui, app, "More Underlines…", "format.fontDialog", json!({})); + ui.menu_button(tl!("Underline Color"), |ui| { + mi(ui, app, "Automatic", "format.underlineColor", json!({"color": "auto"})); + let theme = app.session.doc.settings.theme_colors.clone(); + crate::colors::menu(ui, app, &theme, Target::cmd("format.underlineColor")); + }); +} + +/// A menu entry drawn as a sample of its underline; its name is the tooltip and what assistive +/// technology reads. +fn underline_item(ui: &mut Ui, app: &mut WordApp, style: &str, name: &str) { + let enabled = crate::widgets::enabled(app, "format.underline"); + let resp = ui.add_enabled(enabled, egui::Button::new("").min_size(vec2(200.0, 22.0))); + let label = tl!(name); + resp.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, enabled, label)); + let t = Tokens::get(ui.ctx()); + let color = if enabled { t.text } else { t.text_disabled }; + paint_underline(ui.painter(), resp.rect.shrink2(vec2(10.0, 0.0)), style, color); + if resp.on_hover_text(label).clicked() { + crate::widgets::run_from_ribbon(app, "format.underline", json!({"style": style})); + ui.close(); + } +} + +/// A line in `style` across `r` at its middle: our own drawing of each underline kind. +fn paint_underline(p: &egui::Painter, r: Rect, style: &str, color: egui::Color32) { + let y = r.center().y.round() + 0.5; + let (x0, x1) = (r.left(), r.right()); + let line = |y: f32, w: f32| { + p.line_segment([pos2(x0, y), pos2(x1, y)], Stroke::new(w, color)); + }; + // Repeats a pattern of (length, gap) pairs along the line; a length of 0 is a dot. + let pattern = |pat: &[(f32, f32)]| { + let mut x = x0; + 'run: loop { + for &(len, gap) in pat { + if x >= x1 { + break 'run; + } + if len == 0.0 { + p.circle_filled(pos2(x + 0.75, y), 0.75, color); + x += 1.5 + gap; + } else { + p.line_segment([pos2(x, y), pos2((x + len).min(x1), y)], Stroke::new(1.0, color)); + x += len + gap; + } + } + } + }; + match style { + "double" => { + line(y - 1.5, 1.0); + line(y + 1.5, 1.0); + } + "thick" => line(y, 2.5), + "dotted" => pattern(&[(0.0, 2.0)]), + "dash" => pattern(&[(6.0, 4.0)]), + "dotDash" => pattern(&[(6.0, 3.0), (0.0, 3.0)]), + "dotDotDash" => pattern(&[(6.0, 3.0), (0.0, 2.5), (0.0, 3.0)]), + "wave" => { + let pts: Vec<_> = + (0..=((x1 - x0) as usize)).map(|i| pos2(x0 + i as f32, y + 1.6 * (i as f32 * std::f32::consts::TAU / 6.0).sin())).collect(); + p.add(egui::Shape::line(pts, Stroke::new(1.0, color))); + } + _ => line(y, 1.0), + } +} + pub(crate) fn mi(ui: &mut Ui, app: &mut WordApp, label: &str, id: &str, params: Value) { mi_text(ui, app, tl!(label), id, params); } @@ -803,20 +897,7 @@ fn home(app: &mut WordApp, ui: &mut Ui) { crate::widgets::row(ui, |ui| { small(ui, app, "bold", None, "Bold", "format.bold", json!({}), flag("bold")); small(ui, app, "italic", None, "Italic", "format.italic", json!({}), flag("italic")); - split(ui, app, "underline", "Underline", "format.underline", json!({}), flag("underline"), None, |ui, app| { - for (l, s) in [ - ("Single", "single"), - ("Double", "double"), - ("Thick", "thick"), - ("Dotted", "dotted"), - ("Dashed", "dash"), - ("Dot-dash", "dotDash"), - ("Wave", "wave"), - ("Words only", "words"), - ] { - mi(ui, app, l, "format.underline", json!({"style": s})); - } - }); + split(ui, app, "underline", "Underline", "format.underline", json!({}), flag("underline"), None, underline_menu); small(ui, app, "strike", None, "Strikethrough", "format.strikethrough", json!({}), flag("strike")); small(ui, app, "subscript", None, "Subscript", "format.subscript", json!({}), flag("subscript")); small(ui, app, "superscript", None, "Superscript", "format.superscript", json!({}), flag("superscript")); @@ -2710,6 +2791,45 @@ mod tests { assert!(next_x >= gallery.right() + 8.0 - 0.01, "{next_x} {gallery:?}"); } + /// #2738: Home › Underline's drop-down: every line sample is a named entry (assistive + /// technology, tooltips) that applies its own style; Words only, None and Underline Color › + /// Automatic do what they say. + #[test] + fn underline_menu_entries_apply_their_style() { + use egui_kittest::kittest::Queryable; + use wordcraft_doc::props::Underline; + let mut a = WordApp::new(Session::new(wordcraft_doc::Document::from_text("Lanterns")), crate::Services::default()); + a.run("select.all", json!({})).unwrap(); + let mut h = egui_kittest::Harness::builder().with_size(vec2(400.0, 700.0)).build_ui_state(|ui, app: &mut WordApp| underline_menu(ui, app), a); + h.run_steps(2); + let run = + |h: &egui_kittest::Harness<'_, WordApp>| h.state().session.doc.para_at(&wordcraft_doc::Pos::body(0, 0)).unwrap().runs[0].props.clone(); + for (_, name) in UNDERLINE_STYLES { + assert!(h.query_by_label(name).is_some(), "{name} is a named entry"); + } + // Entries made with `mi` carry their shortcut text in their label (Words only: Ctrl+Shift+W). + for (name, want) in + [("Double", Underline::Double), ("Dot-dot-dash", Underline::DotDotDash), ("Wave", Underline::Wave), ("Words only", Underline::Words)] + { + h.get_by_label_contains(name).click(); + h.run_steps(2); + assert_eq!(run(&h).underline, Some(want), "{name}"); + } + h.get_by_label("None").click(); + h.run_steps(2); + assert_eq!(run(&h).underline, Some(Underline::None)); + h.state_mut().run("format.underlineColor", json!({"color": "C00000"})).unwrap(); + assert_eq!(run(&h).underline_color, wordcraft_doc::Rgb::parse("C00000")); + // The submenu opens on a click once the pointer is on it. + h.get_by_label("Underline Color").hover(); + h.run(); + h.get_by_label("Underline Color").click(); + h.run(); + h.get_by_label_contains("Automatic").click(); + h.run_steps(2); + assert_eq!(run(&h).underline_color, None); + } + /// #658: menu buttons grey out (and don't open) while the command their menu is about is /// disabled: the Table Layout menus outside a table; menus with no command stay on. #[test] diff --git a/docs/parity-checklist.md b/docs/parity-checklist.md index 0f22c6d717..8a9375bcc3 100644 --- a/docs/parity-checklist.md +++ b/docs/parity-checklist.md @@ -2,20 +2,20 @@ Generated by `cargo xtask parity` (`wordcraft-cli parity --markdown`): the word-processor feature catalog (`crates/engine/src/catalog.rs`) compared with the live command registry. -**487 of 502 catalog features have commands (97.0%).** +**490 of 505 catalog features have commands (97.0%).** | Tab | Live | Total | |---|---|---| | File | 22 | 22 | | Quick Access | 5 | 5 | | Title Bar | 1 | 1 | -| Home | 102 | 103 | +| Home | 103 | 104 | | Insert | 47 | 53 | | Draw | 10 | 11 | | Design | 10 | 10 | | Layout | 22 | 22 | | References | 25 | 25 | -| Mailings | 17 | 17 | +| Mailings | 19 | 19 | | Review | 29 | 31 | | View | 25 | 29 | | Developer | 11 | 11 | diff --git a/docs/ui-parity.md b/docs/ui-parity.md index b1c503f5c5..5df5527ee3 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -1,6 +1,6 @@ # UI parity with Microsoft Word -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** 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-17 · **Change:** trivial (Home › Underline drop-down shows its lines, #2738; 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 @@ -59,6 +59,7 @@ Document title opens Rename Document; the account badge opens Options › Genera | Date | Change | Summary | |---|---|---| +| 2026-10-17 | trivial | Home › Underline's drop-down (#2738): each style is a sample of its own line (Single, Double, Thick, Dotted, Dashed, Dot-dash, Dot-dot-dash, Wave), named for tooltips and assistive technology; Words only, None, More Underlines… (the Font dialog) and Underline Color › (Automatic, theme and standard colours, More Colors…) on the new `format.underlineColor` | | 2026-10-13 | trivial | New/Modify Table Style: No Border on a part that inherits its lines hides them; emptying the font box makes the part's font inherited again (#1288) | | 2026-10-14 | trivial | Tab stops edited on the ruler: corner kind selector, click to set, drag to move or off to clear, per-kind markers mirrored for RTL; each gesture one `para.tabs` call and one undo step; Rulers 85% → 90% | | 2026-10-12 | trivial | Tables on the page: one selected cell (Select Cell, or a click along its left edge) is a cell selection when its border is dragged: the border moves in its row only (#962) | From 7817c137a92cd1b4ea5fd8896c5584c8216e5aa6 Mon Sep 17 00:00:00 2001 From: sanches Date: Sat, 17 Oct 2026 17:31:20 +0300 Subject: [PATCH 071/336] i18n: add Dutch and Danish interfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two new interface languages, each a complete catalog of 2,585 entries: every key the parity-class catalogs hold (the Polish-complete set — the keys Italian, Hungarian, Bulgarian, Vietnamese, Swedish and Croatian still carry beyond it are deprecated interface strings, so they are not carried here). Written from the meaning of the English labels with each language's standard software vocabulary: Dutch informal-neutral je, Danish informal du with accented imperatives. Registered in the language list (23 -> 25) with locale matching (nl-NL/nl-BE, da-DK). The preferred-language test now uses the private-use tag where it used nl as its unsupported example, and checks that nl wins over da now that both are languages. Supersedes #1070, whose Korean and Swedish were landed by other pull requests; its Dutch and Danish catalogs are the ones carried here, updated to today's key set. --- crates/ui-egui/src/i18n/da.tsv | 2592 ++++++++++++++++++++++++++++++ crates/ui-egui/src/i18n/mod.rs | 7 +- crates/ui-egui/src/i18n/nl.tsv | 2592 ++++++++++++++++++++++++++++++ crates/ui-egui/src/i18n/tests.rs | 4 +- 4 files changed, 5191 insertions(+), 4 deletions(-) create mode 100644 crates/ui-egui/src/i18n/da.tsv create mode 100644 crates/ui-egui/src/i18n/nl.tsv diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv new file mode 100644 index 0000000000..96e1d15b12 --- /dev/null +++ b/crates/ui-egui/src/i18n/da.tsv @@ -0,0 +1,2592 @@ +# Danish (da) interface catalog. Columns, separated by a TAB: +# context English source translation +# escapes \n \t \\ inside a column. {{name}} placeholders must appear in both +# columns, and a trailing … in one needs one in the other. +# Lines starting with # and blank lines are ignored. A missing entry shows in English. +# +# Clean-room: written from the meaning of WordCraft's English labels in standard Danish. + (no suggestions) (ingen forslag) + 1.5 Line Spacing 1,5 Linjeafstand + A free, open-source word processor written from scratch in Rust.\nPart of the Crafting Apps from the ArtCraft team. En gratis open source-tekstbehandler skrevet fra bunden i Rust.\nEn del af Crafting Apps fra ArtCraft-teamet. + About Om + About WordCraft Om WordCraft + Accept Acceptér + Accept All Changes Acceptér alle ændringer + Accept This Change Acceptér denne ændring + Accessibility Tilgængelighed + Add Tilføj + Add Space Before Paragraph Tilføj mellemrum før afsnit + Add Text Tilføj tekst + Add a Table Tilføj en tabel + Add a comment Tilføj en kommentar + Add to Dictionary Tilføj til ordbog + Add to Style Gallery Tilføj til stilgalleri + Address Block Adresseblok + Address: Adresse: + Address\nBlock Adresse\nblok + Adjust Justér + Advanced Avanceret + Advanced Find Avanceret søgning + Advanced Find… Avanceret søgning… + After: Efter: + Agents Agenter + Align Justering + Align Left Venstrejustér + Align Right Højrejustér + Alignment Justering + All Borders Alle kanter + All Caps Alle store bogstaver + All Markup Alle markeringer + All caps Alle store bogstaver + Alt Text Alternativ tekst + Apply Character Style Anvend tegnstil + Apply Style Anvend stil + Arrange Arranger + Arrange\nAll Arranger\nalle + Arrow Pil + Artistic Effects Kunstneriske effekter + Author Forfatter + AutoCorrect Options Indstillinger for AutoCorrect + AutoFit Tilpas automatisk + AutoFit Contents Tilpas automatisk indhold + AutoFit Window Tilpas automatisk til vindue + AutoSave AutoSave + AutoSave saves every change to the file (needs a saved document) AutoSave gemmer alle ændringer i filen (kræver et gemt dokument) + AutoText AutoText + Automatic Automatisk + Automatic ({language}) Automatisk ({language}) + Automatic Table 1 (3 levels) Automatisk tabel 1 (3 niveauer) + Automatic Table 2 (2 levels) Automatisk tabel 2 (2 niveauer) + Automatic follows your system's language. Menus and commands change; your documents don't. Automatisk følger dit systems sprog. Menuer og kommandoer ændrer sig; dine dokumenter gør ikke. + Backspace Backspace + Banded Columns Stribede kolonner + Banded Rows Stribede rækker + Before: Før: + Bibliography Bibliografi + Binary assets added Binære ressourcer tilføjet + Binary assets removed Binære ressourcer fjernet + Blank Tom + Blank (Three Columns) Tom (tre kolonner) + Blank Page Tom side + Blank document Tomt dokument + Block\nAuthors Blokér\nforfattere + Bold Fed + Bookmark Bogmærke + Bookmark name: Bogmærkenavn: + Border Painter Kantværktøj + Border\nPainter Kant-\nværktøj + Borders Kanter + Bottom Border Nederste kant + Bottom Margin Nederste sidekant + Bottom of Page Nederst på siden + Bottom: Nederst: + Box Boks + Breaks Skift + Bring Forward Flyt fremad + Bullet Library Punktbibliotek + Bullets Punktnumre + Calendar Kalender + Cancel Annuller + Capitalize Each Word Sæt initialer i hvert ord + Captions Billedtekster + Cell Margins Cellemargener + Cell Size Celleformat + Cell\nMargins Celle\nmargener + Center Centrer + Change Skift + Change Case Skift store/små bogstaver + Change List Level Skift listeniveau + Change Picture Skift billede + Change Shape Skift figur + Changes Ændringer + Character Position Tegnposition + Character Scale Tegnskalering + Character Shading Tegnskygge + Character Spacing Tegnafstand + Characters Tegn + Characters (no spaces) Tegn (uden mellemrum) + Characters (with spaces) Tegn (med mellemrum) + Chart Diagram + Check Accessibility Kontrollér tilgængelighed + Check Compatibility Kontrollér kompatibilitet + Check Spelling as You Type Kontrollér stavning under skrivning + Check for Errors Søg efter fejl + Check\nAccessibility Kontrollér\ntilgængelighed + Citation Style Citatformat + Citations & Bibliography Citater og bibliografi + Clear Ryd + Clear All Ryd alle + Clear All Formatting Ryd al formatering + Clear Character Formatting Ryd tegnformatering + Clear Character Style Ryd tegnstil + Clear Formatting Level Ryd formateringsniveau + Clear Paragraph Formatting Ryd afsnitsformatering + Clipboard Udklipsholder + Close Luk + Close Header and Footer Luk sidehoved og sidefod + Collapse Selection Skjul markering + Color Farve + Color: Farve: + Colors Farver + Column Kolonne + Column Break Kolonneskift + Columns Kolonner + Combine Flet + Commands Kommandoer + Comment Kommentar + Comments Kommentarer + Comments Pane Kommentarrude + Community\nDiscord Fællesskab\nDiscord + Compact Kompakt + Compare Sammenlign + Compress Pictures Komprimér billeder + Continuous Kontinuerlig + Convert Konvertér + Convert Text to Table Konvertér tekst til tabel + Convert Text to Table… Konvertér tekst til tabel… + Convert to Text Konvertér til tekst + Copy Kopiér + Copy Formatting Kopiér formatering + Corrections Rettelser + Count words in text boxes and notes Tæl ord i tekstfelter og noter + Cover Page Forside + Create Opret + Create New Style Opret ny stil + Create a Style Opret en stil + Create an interactive outline of your document.\n\nIt's a great way to keep track of where you are or quickly move your content around.\n\nTo get started, go to the Home tab and apply Heading styles to the headings in your document. Opret en interaktiv disposition af dit dokument.\n\nDet er en fin måde at holde styr på, hvor du er, eller hurtigt at flytte rundt på indholdet.\n\nFor at komme i gang skal du gå til fanen Start og anvende Overskrift-stile på overskrifterne i dit dokument. + Crop Beskær + Cross-reference Krydshenvisning + Current Position Nuværende placering + Current style: {style} Nuværende stil: {style} + Custom Margins… Brugerdefinerede sidekanter… + Custom Watermark Brugerdefineret vandmærke + Custom Watermark… Brugerdefineret vandmærke… + Customize Quick Access Toolbar Tilpas værktøjslinjen Hurtig adgang + Cut Klip + Dark Mode Mørk tilstand + Dashed Stiplet + Data Data + Date & Time Dato og klokkeslæt + Decrease Font Size Formindr skriftstørrelse + Decrease Indent Formindr indrykning + Default Standard + Define New Bullet Definer nyt punktnummer + Define New Number Format Definer nyt talformat + Delete Slet + Delete Cells Slet celler + Delete Columns Slet kolonner + Delete Comment Slet kommentar + Delete Next Word Slet næste ord + Delete Previous Word Slet forrige ord + Delete Rows Slet rækker + Delete Style Slet stil + Delete Table Slet tabel + Design Design + Diagonal Diagonal + Diamond Rombediamond + Dictate Diktér + Different First Page Forskellige første side + Different Odd & Even Pages Forskellige ulige og lige sider + Direct formatting Direkte formatering + Discord Discord + Discord community Discord-fællesskab + Display Vis + Display for Review Vis til gennemsyn + Distribute Columns Fordel kolonner jævnt + Distribute Rows Fordel rækker jævnt + Distributed Fordelt + Do Not Show in TOC Vis ikke i indholdsfortegnelse + Document End Dokumentets slutning + Document Formatting Dokumentformatering + Document Property Dokumentegenskab + Document Start Dokumentets begyndelse + Document Text Dokumenttekst + Document Title Dokumenttitel + Documents you open will show up here. Dokumenter, du åbner, vises her. + Dot-dash Prikstreg + Dotted Prikket + Double Dobbelt + Double Spacing Dobbelt linjeafstand + Double Strikethrough Dobbelt gennemstrege + Double Underline Dobbelt understreget + Down Ned + Draft Kladde + Draw Tegn + Drawing Tools Tegneværktøjer + Drawing\nCanvas Tegne\nlærred + Drop Cap Stort begyndelsesbogstav + Drops all formatting and pastes plain text. Fjerner al formatering og sætter ren tekst ind. + Edit Comment Redigér kommentar + Edit Footer Redigér sidefod + Edit Header Redigér sidehoved + Edit Recipient List Redigér modtagerliste + Edit\nRecipient List Redigér\nmodtagerliste + Edited Redigeret + Editing Redigering + Editing header/footer Redigerer sidehoved/sidefod + Editing — edit the document directly Redigering — redigér dokumentet direkte + Editor Redaktør + Effects Effekter + Emboss Præg + Encrypt with Password Krypter med adgangskode + English (United States) Engelsk (USA) + Engrave Ritse ind + Enter page number: Angiv sidetal: + Envelopes Kuverter + Eraser Viskelæder + Even Page Lige side + Every command is available to scripts and AI agents: run `wordcraft-cli mcp` for an MCP server, or start the app with `--control ` for the JSON control channel. Alle kommandoer er tilgængelige for scripts og AI-agenter: kør `wordcraft-cli mcp` for en MCP-server, eller start appen med `--control ` for JSON-kanalen. + Export Eksportér + Export PDF Eksportér PDF + Export Page as PNG Eksportér side som PNG + Export a copy to share (PDF, Word document) Eksportér en kopi til deling (PDF, Word-dokument) + Exported {path} Eksporteret {path} + Extend Selection Udvid markering + Field Felt + File Filer + Find Søg + Find Next Søg næste + Find Previous Søg forrige + Find Recipient Find modtager + Find and Replace Søg og erstat + Find what: Find: + Finish Færdig + Finish & Merge Færdigér og flet + Finish &\nMerge Færdigér og\nflet + First Column Første kolonne + First commit Første commit + First line: Første linje: + Fixed Column Width Fast kolonnebredde + Focus Fokus + Font Skrifttype + Font Color Skriftfarve + Font Dialog Skrifttype-dialog + Font Size Skriftstørrelse + Font color: Skriftfarve: + Font: Skrifttype: + Fonts Skrifttyper + Footer Sidefod + Footnote and Endnote Fodnote og slutnote + Footnotes Fodnoter + Format Format + Format Page Numbers Formatér sidetal + Format Page Numbers… Formatér sidetal… + Format Painter Formateringsmaler + Formatted Text Formateret tekst + Formatting at Selection Formatering af markering + Formula Formel + General Generelt + Go To Gå til + Go To (⌘⌥G) Gå til (⌘⌥G) + Go To… Gå til… + Go to Gå til + Grab bag Blandet + Greeting Line Hilseslinje + Greeting\nLine Hilses\nlinje + Gridlines Gitterlinjer + Group Grupper + Grow Font 1 Point Forøg skriftstørrelse med 1 punkt + HTML Format HTML-format + Hanging Indent Hængende indrykning + Header Sidehoved + Header & Footer Sidehoved og sidefod + Header Row Overskriftsrække + Header/Footer Position Placering af sidehoved/sidefod + Heading 1 Overskrift 1 + Heading 2 Overskrift 2 + Heading 3 Overskrift 3 + Headings Overskrifter + Heart Hjerte + Help Hjælp + Hidden Skjult + Highlight Merge Fields Fremhæv fletfelter + Highlight\nMerge Fields Fremhæv\nfletfelter + Highlighter Tekstmarkering + Home Start + Horizontal Vandret + Horizontal Line Vandret linje + Hyphenation Orddeling + Icons Ikoner + Ignore All Ignorer alle + Illustrations Illustrationer + Immersive Fordybelse + Immersive Reader Fordybelseslæser + Immersive\nReader Fordybelses\nlæser + Increase Font Size Forøg skriftstørrelse + Increase Indent Forøg indrykning + Indent Indrykning + Indentation Indrykning + Indents Indrykninger + Index Indeks + Info Info + Ink to\nMath Håndskrift\ntil formel + Ink to\nShape Håndskrift\ntil figur + Ink\nReplay Gentag\nhåndskrift + Insert Indsæt + Insert Above Indsæt ovenfor + Insert Below Indsæt nedenfor + Insert Caption Indsæt billedtekst + Insert Caption… Indsæt billedtekst… + Insert Citation Indsæt citat + Insert Endnote Indsæt slutnote + Insert Footnote Indsæt fodnote + Insert Hyperlink Indsæt hyperlink + Insert Index Indsæt indeks + Insert Left Indsæt til venstre + Insert Merge Field Indsæt fletfelt + Insert Merge\nField Indsæt flet\nfelt + Insert Right Indsæt til højre + Insert Shapes Indsæt figurer + Insert Table Indsæt tabel + Insert Table of Authorities Indsæt tabel over retskilder + Insert Table of Figures Indsæt tabel over figurer + Insert Table… Indsæt tabel… + Insert\nAbove Indsæt\novenfor + Insert\nCaption Indsæt\nbilledtekst + Insert\nCitation Indsæt\ncitat + Insert\nFootnote Indsæt\nfodnote + Inside Borders Indvendige kanter + Inspect Document Kontrollér dokument + Inspect Styles at Selection Kontrollér stile ved markering + Interface language: Grænsefladesprog: + Italic Kursiv + Join the ArtCraft community on Discord Deltag i ArtCraft-fællesskabet på Discord + Join us on Discord: discord.gg/artcraft Følg os på Discord: discord.gg/artcraft + Justify Justér + Keep Lines Together Hold linjer sammen + Keep Text Only Behold kun tekst + Keep lines together Hold linjer sammen + Keep with Next Hold sammen med næste + Keep with next Hold sammen med næste + Keeps the fonts and formatting of the copied text. Bevarer skrifttyper og formatering af den kopierede tekst. + Labels Etiketter + Landscape Liggende + Language Sprog + Lasso Lasso + Last Column Sidste kolonne + Last commit Seneste commit + Layout Layout + Layout Summary Layoutoversigt + Left Venstre + Left Border Venstre kant + Left Margin Venstre sidekant + Left: Venstre: + Letter Letter + Line Linje + Line Break Linjeskift + Line End Linjeslut + Line Numbers Linjenumre + Line Spacing Linjeafstand + Line Spacing Options… Indstillinger for linjeafstand… + Line Start Linjestart + Line and Page Breaks Linje- og sideskift + Line and Paragraph Spacing Afstand mellem linjer og afsnit + Line delta Linjedelta + Line spacing: Linjeafstand: + Lines Linjer + Lines added Tilføjede linjer + Lines deleted Slettede linjer + Link Link + Link to Previous Opret forbindelse til forrige + Links Links + Location: {path} Placering: {path} + MIT OR Apache-2.0. Copyright (c) 2026 ArtCraft Team and the WordCraft contributors. MIT OR Apache-2.0. Copyright (c) 2026 ArtCraft Team og WordCraft-bidragsyderne. + Macros Makroer + Mailings Post + Manage Sources Administrer kilder + Many pages Mange sider + Margins Sidekanter + Mark Citation Markér citat + Mark Entry Markér post + Mark\nCitation Markér\ncitat + Mark\nEntry Markér\npost + Markdown (*.md) Markdown (*.md) + Match Fields Match felter + Match case Match store/små bogstaver + Matrix Matrix + Merge Flet + Merge Cells Flet celler + Merge Formatting Flet formatering + Merged PRs Fletede PR'er + Mirrored Spejlet + Moderate Moderat + Modify Style Rediger stil + Modify… Rediger… + More templates → Flere skabeloner → + Move or Resize Flyt eller ændr størrelse + Multilevel List Liste med flere niveauer + Multiple Pages Flere sider + Name (A–Z) Navn (A–Å) + Name: Navn: + Navigation Navigation + Navigation Pane Navigationsrude + New Ny + New Comment Ny kommentar + New Paragraph Nyt afsnit + New Style… Ny stil… + New Window Nyt vindue + New from Template Ny fra skabelon + New\nComment Ny\nkommentar + New\nWindow Nyt\nvindue + Next Næste + Next Change Næste ændring + Next Comment Næste kommentar + Next Footnote Næste fodnote + Next Page Næste side + Next Record Næste post + Next Section Næste sektion + No Border Ingen kant + No Color Ingen farve + No Markup Ingen markeringer + No Paragraph Space Ingen afstand mellem afsnit + No contributor data was built into this copy. Denne kopi indeholder ingen data om bidragydere. + No model credits were built into this copy. Denne kopi indeholder ingen oplysninger om modeller. + No templates match your search. Ingen skabeloner matcher din søgning. + Nonbreaking Hyphen Ikke-brydende bindestreg + Nonbreaking Space Ikke-brydende mellemrum + None Ingen + Normal Style Normal stil + Not available right now Ikke tilgængelig i øjeblikket + Not saved Ikke gemt + Not saved yet Ikke gemt endnu + Nudge Skub + Number of columns: Antal kolonner: + Number of rows: Antal rækker: + Numbering Nummerering + OK OK + Object Objekt + Odd Page Ulige side + Office 2003 Default Office 2003-standard + One En + One Page Én side + Open Åbn + OpenDocument Text (*.odt) OpenDocument-tekst (*.odt) + Optional Hyphen Valgfri bindestreg + Options Indstillinger + Orientation Retning + Outline Disposition + Outline Level Dispositionsniveau + Outside Borders Ydre kanter + Oval Oval + PDF document (*.pdf) PDF-dokument (*.pdf) + Page Side + Page Background Sidebaggrund + Page Borders Siderammer + Page Break Sideskift + Page Break Before Sideskift før + Page Breaks Sideskift + Page Color Sidefarve + Page Down Side ned + Page Movement Sidebevægelse + Page Number Sidetal + Page Setup Sideopsætning + Page Up Side op + Page Width Sidebredde + Page X of Y Side X af Y + Page break before Sideskift før + Page image (*.png) Sidebillede (*.png) + Page width Sidebredde + Page {page} Side {page} + Page {page} of {pages} Side {page} af {pages} + Page\nBorders Side\nrammer + Page\nColor Side\nfarve + Page\nNumber Side\ntal + Pages Sider + Paragraph Afsnit + Paragraph Details Afsnitsdetaljer + Paragraph Down Afsnit ned + Paragraph Settings Afsnitsindstillinger + Paragraph Spacing Afstand mellem afsnit + Paragraph Up Afsnit op + Paragraphs Afsnit + Paste Sæt ind + Paste (⌘V) Sæt ind (⌘V) + Paste Formatting Sæt formatering ind + Paste Special Sæt ind specielt + Paste Special… Sæt ind specielt… + Paste as: Sæt ind som: + Paste: Keep Text Only Sæt ind: Behold kun tekst + Paste: Merge Formatting Sæt ind: Flet formatering + Pen Pen + Pencil Blyant + Picture Border Billedramme + Picture Format Billedformat + Picture Styles Billedstile + Pictures Billeder + Plain text (*.txt) Almindelig tekst (*.txt) + Portrait Stående + Position Position + Preview Results Forhåndsvis resultater + Preview\nResults Forhåndsvis\nresultater + Previous Forrige + Previous Change Forrige ændring + Previous Comment Forrige kommentar + Previous Record Forrige post + Previous Section Forrige sektion + Print Udskriv + Print Layout Udskriftslayout + Print\nLayout Udskrifts\nlayout + Proofing Korrekturlæsning + Proofing Issues Korrekturlæsningsproblemer + Properties Egenskaber + Protect Beskyt + Protect Document Beskyt dokument + Quick Access Toolbar Værktøjslinjen Hurtig adgang + Quick Parts Hurtige dele + Quick Tables Hurtige tabeller + Read Aloud Læs højt + Read Mode Læsetilstand + Read\nAloud Læs\nhøjt + Read\nMode Læse\ntilstand + Reads the clipboard's rich text and keeps its formatting. Læser rich text fra udklipsholderen og bevarer formateringen. + Reads the clipboard's web content and keeps its formatting. Læser webindhold fra udklipsholderen og bevarer formateringen. + Picture Billede + Pastes the clipboard's picture. Sætter billedet fra udklipsholderen ind. + Reading Highlight Fremhævning ved læsning + Recent Seneste + Record Macro Optag makro + Recover Unsaved Documents Gendan usikrede dokumenter + Rectangle Rektangel + Redo Gentag + Reduce Hanging Indent Reducer hængende indrykning + References Referencer + Reject Afvis + Reject All Changes Afvis alle ændringer + Reject Change Afvis ændring + Relaxed Afslappet + Remove Background Fjern baggrund + Remove Footer Fjern sidefod + Remove Header Fjern sidehoved + Remove Hyperlink Fjern hyperlink + Remove Space After Paragraph Fjern afstand efter afsnit + Remove Table of Contents Fjern indholdsfortegnelse + Remove Watermark Fjern vandmærke + Reopen Åbn igen + Repeat Gentag + Repeat Header Rows Gentag overskriftsrækker + Replace Erstat + Replace All Erstat alle + Replace Document Text Erstat dokumenttekst + Replace with: Erstat med: + Replay Afspil igen + Reply Svar + Report Rapport + Research Forskning + Researcher Forsker + Reset Picture Nulstil billede + Reset to Normal Nulstil til Normal + Resolve Løs + Resolved Løst + Restart Each Page Genstart på hver side + Restart Each Section Genstart i hver sektion + Restart at 1 Genstart ved 1 + Restrict Editing Begræns redigering + Restrict\nEditing Begræns\nredigering + Result Resultat + Results Resultater + Reverse the order Vend rækkefølgen + Reviewing Gennemser + Reviewing Pane Gennemserude + Reviewing — edits become suggestions Gennemser — ændringer bliver forslag + Rich Text Format (*.rtf) Rich Text Format (*.rtf) + Rich Text Format (RTF) Rich Text Format (RTF) + Right Højre + Right Border Højre kant + Right Margin Højre sidekant + Right-to-Left Text Direction Tekstretning fra højre til venstre + Left-to-Right Text Direction Tekstretning fra venstre til højre + Right: Højre: + Rotate Roter + Rounded Rectangle Afrundet rektangel + Rows & Columns Rækker og kolonner + Ruler Lineal + Rules Regler + Résumé CV + Save Gem + Save As Gem som + Save a copy in another format. Printing goes through a PDF you can print from any viewer. Gem en kopi i et andet format. Udskrivning sker via en PDF, som du kan udskrive fra enhvisningsprogram. + Save as Template Gem som skabelon + Saved Gemt + Search Commands Søg kommandoer + Search commands and help Søg i kommandoer og hjælp + Search document Søg i dokument + Search for templates Søg efter skabeloner + Section Breaks Sektionsskift + Section Properties Sektionsegenskaber + Sections Sektioner + Select Markér + Select All Markér alt + Select Cell Markér celle + Select Line Markér linje + Select Objects Markér objekter + Select Paragraph Markér afsnit + Select Range Markér område + Select Recipients Vælg modtagere + Select Row Markér række + Select Sentence Markér sætning + Select Table Markér tabel + Select Text Markér tekst + Select Text with Similar Formatting Markér tekst med lignende formatering + Select Word Markér ord + Select\nRecipients Vælg\nmodtagere + Selection Markering + Selection Pane Markeringsrude + Send Backward Send bagud + Sentence case. Første bogstav i sætning med stort. + Set Caret Sæt indsættelsespunkt + Set Character Formatting Angiv tegnformatering + Set Numbering Value Angiv nummereringsværdi + Set Paragraph Formatting Angiv afsnitsformatering + Set as Default Angiv som standard + Shading Skygge + Shadow Skygge + Shape Fill Figurfyldning + Shape Format Figurformat + Shape Outline Figurkontur + Shape Styles Figurstile + Shapes Figurer + Share Del + Shift+Tab Shift+Tab + Show Vis + Show Comments Vis kommentarer + Show Markup Vis markeringer + Show Notes Vis noter + Show all formatting marks Vis alle formateringstegn + Show rulers Vis linealer + Show the community button in the title bar Vis fællesskabsknappen på titellinjen + Show/Hide ¶ Vis/skjul ¶ + Show\nComments Vis\nkommentarer + Shrink Font 1 Point Formindsk skrift med 1 punkt + Side to Side Side om side + Side\nto Side Side\nom side + Signature Line Signaturlinje + Single Enkelt + Single Spacing Enkelt linjeafstand + Size Størrelse + Size: Størrelse: + Small Caps Små kapitaler + Small caps Små kapitaler + SmartArt SmartArt + Sort Sortér + Source code: github.com/storytold/wordcraft Kildekode: github.com/storytold/wordcraft + Space before/after: Mellemrum før/efter: + Spacing Afstand + Spacing (pt): Afstand (pt): + Speech Tale + Spelling & Grammar Stavekontrol og grammatik + Spelling &\nGrammar Stavekontrol og\ngrammatik + Spelling Suggestions Staveforslag + Split Opdel + Split Cells Opdel celler + Split Table Opdel tabel + Spreadsheet Table Regnearkstabel + Standard Colors Standardfarver + Star Stjerne + Start Mail Merge Start serielbrev + Start Mail\nMerge Start\nserielbrev + Statistics Statistik + Status Bar Statuslinje + Strikethrough Gennemstreget + Studio cover Studioomslag + Studio handbook (sample) Studiohåndbog (eksempel) + Style Stil + Style Inspector Stilinspektør + Style Set Stilsæt + Style based on: Stil baseret på: + Styles Stile + Styles Pane Stilrude + Subscript Sænket skrift + Superscript Hævet skrift + Switch Modes Skift tilstand + Switch\nModes Skift\ntilstand + Symbol Symbol + Symbols Symboler + Synonyms Synonymer + Tab Tabulator + Table Tabel + Table Column Width Tabelkolonnebredde + Table Design Tabeldesign + Table Layout Tabelayout + Table Properties Tabelegenskaber + Table Properties… Tabelegenskaber… + Table Row Height Tabelrækkehøjde + Table Style Options Indstillinger for tabelstil + Table Styles Tabelstile + Table of Authorities Tabel over juridiske kilder + Table of Contents Indholdsfortegnelse + Table of\nContents Indholds-\nfortegnelse + Tables Tabeller + Tabs Tabulatorer + Tabular List Tabellarisk liste + Text Tekst + Text Box Tekstboks + Text Direction Tekstretning + Text Effects Teksteffekter + Text Effects and Typography Teksteffekter og typografi + Text Highlight Color Farve til tekstmarkering + Text Wrapping Tekstombrydning + Text from File Tekst fra fil + Text to display: Tekst, der skal vises: + Text: Tekst: + Text\nBox Tekst-\nboks + Text\nDirection Tekst-\nretning + The clipboard is empty. Udklipsholderen er tom. + The new style takes the formatting of the current paragraph. Den nye stil bruger formateringen af det aktuelle afsnit. + Theme Colors Temafarver + Theme Fonts Temaskrifttyper + Theme List Temapliste + Themes Temaer + Thesaurus Synonymsordbog + Thick Tyk + Three Tre + Tight Tæt + Top Border Øvre kant + Top Margin Øvre sidekant + Top of Page Øverst på siden + Top: Øverst: + Total Row Sumrække + Track Changes Følg ændringer + Track Changes: On Følg ændringer: Til + Tracking Sporing + Translate Oversæt + Transparency Gennemsigtighed + Triangle Trekant + Two To + Type Text Skriv tekst + Type a command, e.g. "insert table" Skriv en kommando, f.eks. »indsæt tabel« + UPPERCASE Store bogstaver + Underline Understreget + Underline Words Only Understreg kun ord + Undo Fortryd + Undo {action} Fortryd {action} + Unformatted Text Uformateret tekst + Up Op + Update Field Opdatér felt + Update Index Opdatér indeks + Update Style to Match Selection Opdatér stil efter markeringen + Update Table Opdatér tabel + Update Table of Figures Opdatér figurliste + Update to Match Opdatér efter + Update to Match Selection Opdatér efter markeringen + User Name Brugernavn + User name: Brugernavn: + Version History Versionshistorik + Version {version} ({build}) Version {version} ({build}) + Vertical Lodret + Vertical Alignment Vertikal justering + View Visning + View Gridlines Vis gitterlinjer + View State Visningstilstand + Views Visninger + Voice Stemme + Watermark Vandmærke + Wave Bølge + Web Layout Weblayout + Web\nLayout Web\nlayout + Web page (*.html) Webside (*.html) + Whole page Hele siden + Whole words Hele ord + Wide Bred + Widow/Orphan Control Kontrol af enlige linjer + Widow/Orphan control Kontrol af enlige linjer + Wildcards (regex) Jokertegn (regulære udtryk) + Window Vindue + Word Count Ordantal + Word Left Ord til venstre + Word Right Ord til højre + Word document (*.docx) Word-dokument (*.docx) + WordArt WordArt + WordCraft WordCraft + Words Ord + Words only Kun ord + Wrap Text Ombryd tekst + Wrap\nText Ombryd\ntekst + Write & Insert Fields Skriv og indsæt felter + Write a reply Skriv et svar + Zoom Zoom + Zoom In Zoom ind + Zoom Out Zoom ud + development build udviklingsversion + lowercase små bogstaver + {group} settings Indstillinger for {group} + {key}+Click to follow link {key}+klik for at følge linket + {selected} of {words} words Valgt: {selected} af {words} + {words} words Ord: {words} + ➕ New comment ➕ Ny kommentar + 📂 Browse… 📂 Gennemse… + Equation Formel + Review Gennemse + Track\nChanges Følg\nændringer + Track\nChanges ✓ Følg\nændringer ✓ + Scroll ribbon left Rul båndet til venstre + Scroll ribbon right Rul båndet til højre + Apply Styles Anvend stile + Dark page (white text on black) Mørk side (hvid tekst på sort) + Show documents with their colours inverted, like View › Switch Modes. Saving, printing and PDFs are unchanged. Vis dokumenter med omvendte farver, som Visning › Skift tilstand. Gem, udskrivning og PDF’er er uændrede. + Only web (http, https) and email (mailto) links can be followed Kun weblinks (http, https) og e-mail-links (mailto) kan følges + Character Border Tegnramme + Direction: Retning: + Right-to-left Højre til venstre + Left-to-right Venstre til højre + Zotero Zotero + Citations Citater + Document Dokument + Add/Edit\nCitation Tilføj/redigér\ncitat + Add\nNote Tilføj\nnote + Add/Edit\nBibliography Tilføj/redigér\nbibliografi + Refresh Opdatér + Document\nPreferences Dokument\nindstillinger + Unlink\nCitations Fjern\ncitater + Waiting for Zotero… Venter på Zotero… + Zotero: done Zotero: færdig + Zotero: cancelled Zotero: annulleret + Yes Ja + No Nej + Move Past Citation Flyt forbi citat + Move Past\nCitation Flyt forbi\ncitat + Next Sentence Næste sætning + Previous Sentence Forrige sætning + Next sentence Næste sætning + Previous sentence Forrige sætning + Play/Pause Afspil/pause + Read Aloud Status Status for oplæsning + Reading Speed Læsehastighed + Skip Citations and Bibliography Spring citater og bibliografi over + Skip citations & bibliography Spring citater og bibliografi over + Stop Reading Stop læsningen + Don't read Zotero, Mendeley or EndNote citations and bibliographies Læs ikke citater og bibliografier fra Zotero, Mendeley eller EndNote + Play Afspil + Pause Pause + Stop Stop + Sentence Sætning + Paused Sat på pause + Finished Afsluttet + Stopped Stoppet + Speed Hastighed + Normal speed Normal hastighed + AutoSave documents you have saved in WordCraft Gem automatisk dokumenter, du har gemt i WordCraft + AutoSave failed: {error}. Save the document to turn AutoSave back on. AutoSave mislykkedes: {error}. Gem dokumentet for at slå AutoSave til igen. + Couldn't download {name}: {error} Kunne ikke hente {name}: {error} + Do you want to save changes to {name}? Vil du gemme ændringer i {name}? + Don't Save Gem ikke + That format doesn't keep everything, so the document is still open. Save it as a Word document (.docx) to go on. Det format bevarer ikke alt, så dokumentet er stadig åbent. Gem det som et Word-dokument (.docx) for at fortsætte. + Your changes will be lost if you don't save them. Dine ændringer går tabt, hvis du ikke gemmer dem. + Change Structure Skift struktur + Change to Display Skift til visning + Change to Inline Skift til integreret + Conversions Konverteringer + Delete Back in Equation Slet tilbage i formel + Delete in Equation Slet i formel + Edit Equation Redigér formel + Enter in Equation Enter i formel + Equation Gallery Formelgalleri + Equation Info Formeloplysninger + Equation Number Formelnummer + Input Format Indtastningsformat + Insert Structure Indsæt struktur + Insert Symbol Indsæt symbol + Justification Justering + Leave Equation Forlad formel + Move in Equation Flyt i formel + Normal Text Normal tekst + Set Equation Angiv formel + Structure Actions Strukturhandlinger + Structures Strukturer + Tools Værktøjer + Type in Equation Skriv i formel + Inline Integreret + Number Nummer + Close Equation Luk formel + Insert New Equation Indsæt ny formel + Built-In Indbygget + Professional Professionel + Linear Lineær + Fraction Brøk + Script Indeks + Radical Radikal + Integral Integral + Large\nOperator Stor\noperator + Bracket Parentes + Function Funktion + Accent Accent + Limit and\nLog Grænse og\nlog + Operator Operator + Equation Options Formelindstillinger + All symbol sets Alle symbolmængder + Send the document straight to the system print dialog, or save a copy in another format below. Send dokumentet direkte til systemets udskrivningsdialog, eller gem en kopi i et andet format nedenfor. + Or save a copy in another format: Eller gem en kopi i et andet format: + Opening print dialog… Åbner udskrivningsdialog… + Print failed: {error} Udskrivning mislykkedes: {error} + Couldn't open {path}: {error} Kunne ikke åbne {path}: {error} + Column Selection Kolonnevalg + New Table Style Ny tabelstil + Modify Table Style Redigér tabelstil + New Table Style… Ny tabelstil… + Modify Table Style… Redigér tabelstil… + Apply formatting to: Anvend formatering på: + Whole Table Hele tabellen + Fill color: Udfyldningsfarve: + {count} items highlighted Fremhævede elementer: {count} + {n} pt {n} pt + Not {name} Ikke {name} + Level {n} Niveau {n} + Body Text Brødtekst + {n} lines Linjer: {n} + At least {n} Mindst {n} + Exactly {n} Nøjagtigt {n} + Centered Centreret + Character spacing Tegnafstand + Don't add space between paragraphs of the same style Tilføj ikke mellemrum mellem afsnit i samme stil + Don't hyphenate Ingen orddeling + Double strikethrough Dobbelt gennemstreget + Drop cap Store forbogstav + First line Første linje + Font color Skriftfarve + Highlight Fremhævning + Justified Justeret + Left indent Venstre indrykning + Line spacing Linjeafstand + Outline level Outlineniveau + Raised/lowered Hævet/sænket + Right indent Højre indrykning + Scale Skalering + Space after Mellemrum efter + Space before Mellemrum før + Suppress line numbers Skjul linjenumre + Underline color Understregningsfarve + Letters Breve + E-mail Messages E-mailbeskeder + Directory Mappe + Normal Word Document Almindeligt Word-dokument + If…Then…Else… Hvis…Så…Ellers… + If…Then…Else Hvis…Så…Ellers + Merge Record # Fletningspost # + Skip Record If… Spring post over, hvis… + Skip Record If Spring post over, hvis + Equals: Lig med: + Then insert: Indsæt derefter: + Otherwise insert: Indsæt ellers: + Courtesy Title Høflighedsbetegnelse + First Name Fornavn + Last Name Efternavn + Company Virksomhed + Address Adresse + City By + State Delstat + Postal Code Postnummer + Country Land + (not matched) (ikke fundet) + Leave a recipient out of the merge when a field has this value. Spring en modtager over i serielbrevet, når et felt har denne værdi. + Insert one text or another, depending on a field's value. Indsæt den ene eller den anden tekst, afhængigt af et felts værdi. + Select recipients first to match their fields. Vælg modtagere først for at matche deres felter. + The recipient-list field that fills each part of an address block and greeting line. Modtagerlistefeltet, der udfylder hver del af en adresseblok og en hilseslinje. + No errors found: every merge field is in the recipient list. Ingen fejl fundet: alle fletningsfelter findes i modtagerlisten. + These merge fields aren't in the recipient list: Disse fletningsfelter findes ikke i modtagerlisten: + Add at least one field. Tilføj mindst ét felt. + Add Field Tilføj felt + Every field needs a name. Alle felter skal have et navn. + Field name Feltnavn + Field names must be different. Feltnavne skal være forskellige. + Fields: Felter: + Field: Felt: + Find: Find: + New Entry Ny post + Recipient List Modtagerliste + Recipients: {count} Modtagere: {count} + Remove field Fjern felt + Remove recipient Fjern modtager + Select recipients first to choose from their fields. Vælg modtagere først for at vælge blandt deres felter. + Type a New List… Skriv en ny liste… + Type at least one recipient. Skriv mindst én modtager. + Type the recipients, or load them from a file. Skriv modtagerne, eller indlæs dem fra en fil. + Use an Existing List… Brug en eksisterende liste… + Interface theme: Grænsefladetema: + Interface\nTheme Grænseflade\ntema + Interface Theme Grænsefladetema + Light Lys + Dark Mørkt + Use system setting Brug systemets indstilling + A file dialog is already open. En fildialogboks er allerede åben. + Height: Højde: + Width: Bredde: + Row Række + Cell Celle + Preferred width: Ønsket bredde: + Alignment: Justering: + Indent from left: Indrykning fra venstre: + Specify height: Angiv højde: + Exactly Nøjagtigt + At least Mindst + Allow row to break across pages Tillad, at rækker kan brydes på tværs af sider + Repeat as header row on each page Gentag som sidehovedsrække på hver side + Vertical alignment: Lodret justering: + Top Top + Bottom Bund + Alt text Alternativ tekst + Alt: Alt: + W: B: + H: H: + Reset Nulstil + Reset Crop Nulstil beskæring + Insert a new equation (Alt+=) Indsæt en ny formel (Alt+=) + Leave the equation (Esc) Forlad formlen (Esc) + Preview Forhåndsvisning + getartcraft.com/apps/wordcraft getartcraft.com/apps/wordcraft + English (United Kingdom) Engelsk (Storbritannien) + Polish Polsk + Do Not Check Spelling or Grammar Kontrollér ikke stavning eller grammatik + Possible spelling mistake Mulig stavefejl + Extra space Ekstra mellemrum + Capitalize the first word of a sentence Sæt stort bogstav i starten af en sætning + Remove the space before the punctuation Fjern mellemrummet før tegnsætningen + Use "an" before a vowel sound Brug „an” før en vokal + Use "a" before a consonant sound Brug „a” før en konsonant + The spelling and grammar check is complete. Kontrollen af stavning og grammatik er fuldført. + Font… Skrifttype… + Paragraph… Afsnit… + Link… Hyperlink… + Change Picture… Skift billede… + Size and Crop… Størrelse og beskæring… + Insert Rows Above Indsæt rækker ovenfor + Insert Rows Below Indsæt rækker nedenfor + Insert Columns to the Left Indsæt kolonner til venstre + Insert Columns to the Right Indsæt kolonner til højre + (picture or object) (billede eller objekt) + A password is required to open this document. Der skal indtastes en adgangskode for at åbne dette dokument. + Add Object to Selection Føj objekt til markering + Add space between Asian and Latin text Tilføj mellemrum mellem asiatisk og latinisk tekst + Add space between Asian text and numbers Tilføj mellemrum mellem asiatisk tekst og tal + Allow Latin text to wrap in the middle of a word Tillad, at latinisk tekst brydes midt i et ord + Allow hanging punctuation Tillad hængende tegnsætning + Alphabetical Alfabetisk + Anyone can open this document. Encrypt it with a password to keep it private. Alle kan åbne dette dokument. Krypter det med en adgangskode for at holde det privat. + Anyone who opens the document will need this password. Alle, der åbner dokumentet, skal bruge denne adgangskode. + Apply Asian line-breaking rules (kinsoku) Anvend asiatiske regler for linjeskift (kinsoku) + As Recommended Som anbefalet + Asian Typography Asiatisk typografi + AutoSave isn't available in the browser: it can't write to your files. Save downloads a copy. AutoSave er ikke tilgængelig i browseren: den kan ikke skrive til dine filer. Gem henter en kopi ned. + AutoSave only keeps .docx, .odt and .rtf files; {name} can't keep all formatting. Save as .docx to turn on AutoSave. AutoSave gemmer kun .docx-, .odt- og .rtf-filer; {name} kan ikke gemme al formatering. Gem som .docx for at aktivere AutoSave. + Based on: {style} Baseret på: {style} + Browse… Gennemse… + Built-in style Indbygget stil + Built-in styles can't be deleted. Indbyggede stile kan ikke slettes. + Change Password… Skift adgangskode… + Character style Tegnstil + Click an item to paste it. Klik på et element for at sætte det ind. + Clipboard Items Elementer i udklipsholder + Clipboard Pane Udklipsholderrude + Color: {color} Farve: {color} + Compress punctuation at the start of a line Komprimér tegnsætning ved starten af en linje + Confirm password: Bekræft adgangskode: + Delete Clipboard Item Slet element i udklipsholder + Delete Table Style Slet tabelstil + Encrypt with Password… Krypter med adgangskode… + Font: {font} Skrifttype: {font} + Glow Glød + Glow Colors Glødfarver + Hide from style lists Skjul i stilelister + Indents and Spacing Indrykninger og afstand + Keep the password somewhere safe: without it nobody, WordCraft included, can open the document. Leave both boxes empty to remove the password. Opbevar adgangskoden et sikkert sted: uden den kan ingen, heller ikke WordCraft, åbne dokumentet. Lad begge felter være tomme for at fjerne adgangskoden. + Line breaking Linjeskift + Linked style (paragraph and character) Tilknyttet stil (afsnit og tegn) + Manage Styles Administrer stile + Manage Styles… Administrer stile… + No Glow Ingen glød + No Shadow Ingen skygge + No Soft Edges Ingen bløde kanter + Nothing collected yet. Items you copy or cut appear here. Intet indsamlet endnu. Elementer, du kopierer eller klipper, vises her. + Offset: Bottom Forskydning: Bund + Offset: Bottom Left Forskydning: Bund til venstre + Offset: Bottom Right Forskydning: Bund til højre + Offset: Center Forskydning: Center + Offset: Left Forskydning: Venstre + Offset: Right Forskydning: Højre + Offset: Top Forskydning: Top + Offset: Top Left Forskydning: Top til venstre + Offset: Top Right Forskydning: Top til højre + Paragraph style Afsnitstil + Password Adgangskode + Password: Adgangskode: + Paste All Sæt alle ind + Paste Clipboard Item Sæt element fra udklipsholder ind + Remove Password Fjern adgangskode + Save the document to turn on AutoSave Gem dokumentet for at aktivere AutoSave + Save the document to turn on AutoSave (a file you have only opened isn't rewritten until you save it) Gem dokumentet for at aktivere AutoSave (en fil, du kun har åbnet, overskrives ikke, før du gemmer den) + Shadow, glow and soft edges Skygge, glød og bløde kanter + Shape Effects Figureffekter + Show in the Styles gallery Vis i stilgalleriet + Show or Hide Style Vis eller skjul stil + Soft Edges Bløde kanter + Sort order: Sorteringsrækkefølge: + Space after: {pt} pt Afstand efter: {pt} pt + Space before: {pt} pt Afstand før: {pt} pt + Table style Tabelstil + That password isn't right. Check it (passwords are case-sensitive) and try again. Adgangskoden er ikke korrekt. Kontrollér den (adgangskoder skelner mellem store og små bogstaver), og prøv igen. + The document will be saved with a password. Save it to protect it. Dokumentet gemmes med en adgangskode. Gem det for at beskytte det. + The password is removed. Save the document to keep the change. Adgangskoden er fjernet. Gem dokumentet for at beholde ændringen. + The passwords don't match. Adgangskoderne er ikke ens. + Ungroup Fjern gruppering + Weight Vægt + {name} is protected with a password. {name} er beskyttet med en adgangskode. + {size} pt {size} pt + % of all commits % af alle commits + (All) (Alle) + (no style) (ingen stil) + (none) (ingen) + (normal text) (normal tekst) + 1/4 Em Space 1/4 em-mellemrum + 3-D 3-D + A mail merge field Et fletfelt + A numbered sequence (figures, tables…) En nummereret rækkefølge (figurer, tabeller…) + A table of contents En indholdsfortegnelse + AaBbYyZz AaBbYyZz + Add tab stop at: Tilføj tabulatorstop ved: + Align Bottom Center Justér nederst i midten + Align Bottom Left Justér nederst til venstre + Align Bottom Right Justér nederst til højre + Align Center Justér i midten + Align Center Left Justér midt til venstre + Align Center Right Justér midt til højre + Align Top Center Justér øverst i midten + Align Top Left Justér øverst til venstre + Align Top Right Justér øverst til højre + Aligned at: Justeret ved: + All Alle + Apply to: Anvend på: + Atelier Atelier + Balloons Taleballoner + Bar Streg + Between Mellem + Borders and Shading Kanter og skygge + Borders and Shading… Kanter og skygge… + Bullet Punkt + By author Efter forfatter + Categories: Kategorier: + Changed lines: Ændrede linjer: + Character Tegn + Character code: Tegnkode: + Click a border between two cells to merge them. Esc stops. Klik på en kant mellem to celler for at flette dem. Esc afbryder. + Click level to modify: Klik på det niveau, du vil ændre: + Col #: Kol. nr.: + Color only Kun farve + Commits Commits + Copy of the text a bookmark marks Kopi af den tekst, et bogmærke markerer + Copyright Copyright + Craft Craft + Custom Brugerdefineret + Darkroom Mørkekammer + Date and Time Dato og klokkeslæt + Date formats: Datoformater: + Decimal Decimal + Default tab stops: Standardtabulatorstop: + Define New Multilevel List Definer ny flerniveauliste + Define New Multilevel List… Definer ny flerniveauliste… + Deletions: Sletninger: + Description: Beskrivelse: + Display name Vist navn + Document Information Dokumentoplysninger + Document1 Dokument1 + Dot dash Prikstreg + Double Closing Quote Dobbelt lukket anførselstegn + Double Opening Quote Dobbelt åbent anførselstegn + Drag a rectangle to draw a table, or a line across cells to split them. Esc stops. Træk et rektangel for at tegne en tabel, eller en linje gennem celler for at opdele dem. Esc afbryder. + Draw Ink Tegn med blæk + Draw Table Tegn tabel + Ellipsis Ellipse + Em Dash Lang tankestreg + Em Space Em-mellemrum + En Dash Kort tankestreg + En Space En-mellemrum + Enter formatting for number: Angiv formatering for nummer: + Equal column width Lige kolonnebredde + Erase Ink Slet blæk + Field Codes Feltkoder + Field code: Feltkode: + Field codes: Feltkoder: + Field name: Feltnavn: + Field names: Feltnavne: + Field properties Feltegenskaber + Field… Felt… + Fill Udfyldning + First Første + Flip Horizontal Vend vandret + Flip Vertical Vend lodret + Follow number with: Efter nummer: + Format: Format: + Formatted Formateret + Formatting Formatering + Gallery Galleri + Hide Codes Skjul koder + Hide Ink Skjul blæk + Hide\nInk Skjul\nblæk + How many pages the whole document has Antal sider i hele dokumentet + How many pages this section has Antal sider i denne sektion + How many words the document holds Antal ord i dokumentet + Include level number from: Inkludér nummer på niveau fra: + Index and Tables Indeks og tabeller + Ink Blæk + Insertions and Deletions Indsættelser og sletninger + Insertions: Indsættelser: + Interface size: Grænsefladestørrelse: + Last Sidste + Leader Udfyldningstegn + Left border Venstre kant + Legal style numbering Juridisk nummerering + Line between Linje mellem + Lines +/− Linjer +/− + Link level to style: Tilknyt niveau til stil: + Links and References Links og referencer + List Definition Listedefinition + Mail Merge Serielbrev + Model Model + Mon Man + More Columns… Flere kolonner… + More Symbols… Flere symboler… + Name Navn + Name of the file this document is saved as Navnet på den fil, dokumentet er gemt som + No-Width Non Break Nullbredde uden brud + No-Width Optional Break Nullbredde med valgfrit brud + North Nord + Nothing Intet + Number alignment: Nummerjustering: + Number format Nummerformat + Number of the page the field sits on Nummeret på den side, feltet står på + Number of the section the field sits in Nummeret på den sektion, feltet står i + Number style for this level: Nummerstil for dette niveau: + Outside border Ydre kant + PRs PR'er + Page Border Sideramme + Page on which a bookmarked passage falls Siden, hvor det bogmærkede afsnit ligger + Paragraph Mark Afsnitsmærke + Pens Penne + Pick a field from the list Vælg et felt på listen + Preserve formatting during updates Bevar formatering under opdateringer + Presets Forudindstillinger + Real name Rigligt navn + Recently used symbols: Senest brugte symboler: + Registered Registreret + Restart list after: Genstart listen efter: + Right border Højre kant + Rotate Left 90° Roter 90° til venstre + Rotate Right 90° Roter 90° til højre + Rotation Rotation + Rotation: Rotation: + Section Mark Sektionsmærke + Sequence name: Sekvensnavn: + Set Angiv + Setting: Indstilling: + Shortcut key: Genvejstast: + Show all revisions inline Vis alle revisioner i teksten + Show only comments and formatting in balloons Vis kun kommentarer og formatering i balloner + Show revisions in balloons Vis revisioner i balloner + Single Closing Quote Enkelt lukkende anførselstegn + Single Opening Quote Enkelt åbnende anførselstegn + Sketchbook Skitsebog + South Syd + Space Mellemrum + Spacing: Afstand: + Special Characters Specialtegn + Start at: Start ved: + Studio Studio + Style: Stil: + Style: {style} Stil: {style} + Subset: Delsæt: + Sum Sum + Tab character Tabulator + Tab stop position: Tabulatorposition: + Tab stops to be cleared: Tabulatorer, der skal fjernes: + Tabs… Tabulatorer… + Text indent at: Tekstindrykning ved: + The date it is today Dagens dato + The document has no bookmarks yet. Dokumentet har endnu ingen bogmærker. + The time right now Det aktuelle klokkeslæt + Thickness Tykkelse + This field has no options. Dette felt har ingen indstillinger. + This font has no characters to show. Denne skrifttype har ingen tegn at vise. + This point forward Fra dette punkt + This section Denne sektion + Title from the document properties Titel fra dokumentegenskaberne + Total I alt + Track Changes Options Indstillinger for Følg ændringer + Track formatting Følg formatering + Trademark Varemærke + Triple Tredobbelt + Tue Tir + Type the field's name and its switches, for example PAGE \\* roman. Skriv feltets navn og dets parametre, for eksempel PAGE \\* roman. + Username Brugernavn + Version Version + Wed Ons + West Vest + When this document was first created Da dokumentet først blev oprettet + When this document was most recently printed Da dokumentet sidst blev udskrevet + When this document was most recently saved Da dokumentet sidst blev gemt + Who the document properties name as author Hvem dokumentegenskaberne angiver som forfatter + Whole document Hele dokumentet + Width and spacing Bredde og afstand + from Unicode (hex) fra Unicode (hex) + pt pt + {cols}×{rows} Table {cols}×{rows} tabel + {contributors} contributors · {commits} commits Bidragsydere: {contributors} · commits: {commits} + Copyright Sign Copyright-symbol + Edit Field Code Rediger feltkode + Edit Field… Rediger felt… + Insert Date Field Indsæt datofelt + Insert Page Field Indsæt sidefelt + Insert Time Field Indsæt tidsfelt + Lock Fields Lås felter + Next Field Næste felt + Previous Field Forrige felt + Registered Sign Symbol for registreret varemærke + Reset Character Formatting Nulstil tegnformatering + Reset Paragraph Formatting Nulstil afsnitsformatering + Selected Fields Valgte felter + Toggle Field Codes Slå feltkoder til/fra + Trademark Sign Symbol for varemærke + Unlink Fields Fjern feltforbindelse + Unlock Fields Lås felter op + All Commands Alle kommandoer + Assign Tildel + Choose a command Vælg en kommando + Choose a shortcut Vælg en genvej + Commands: Kommandoer: + Customize Tilpas + Customize Keyboard Tilpas tastatur + Customize… Tilpas… + Demote Sænk + Draw\nTable Tegn\nTabel + Go Back Gå tilbage + Keyboard shortcuts: Tastaturgenveje: + Keys for this command: Taster til denne kommando: + Move Paragraph Down Flyt afsnit ned + Move Paragraph Up Flyt afsnit op + New shortcut: Ny genvej: + Not used by any command Bruges ikke af nogen kommando + Now used by {command} Bruges nu af {command} + Press a key combination Tryk på en tastekombination + Promote Hæv + Remove Fjern + Remove every custom shortcut and restore the built-in keys? Fjern alle tilpassede genveje og genopret de indbyggede taster? + Reset All… Nulstil alle… + Toggle Unicode Character Slå Unicode-tegn til/fra + (Custom) (Tilpasset) + << Remove << Fjern + Add >> Tilføj >> + Add to Quick Access Toolbar Føj til værktøjslinjen Hurtig adgang + Customize Quick Access Toolbar… Tilpas værktøjslinjen Hurtig adgang… + Customize Ribbon Tilpas bånd + Customize the Ribbon… Tilpas båndet… + More Commands… Flere kommandoer… + New Group Ny gruppe + New Tab Nyt faneblad + Pick a custom group (New Group makes one) Vælg en tilpasset gruppe (Ny gruppe opretter en) + Put the default commands back on the Quick Access Toolbar? Læg standardkommandoerne tilbage på værktøjslinjen Hurtig adgang? + Remove every custom tab and group and show all tabs again? Fjern alle tilpassede faneblader og grupper, og vis alle faneblader igen? + Remove from Quick Access Toolbar Fjern fra værktøjslinjen Hurtig adgang + Rename… Omdøb… + Reset… Nulstil… + Ribbon Bånd + Ribbon and Quick Access Toolbar: Bånd og værktøjslinjen Hurtig adgang: + Ribbon tabs Båndfaneblader + Align Text Justér tekst + Apply Anvend + Arrows Pile + Base text: Grundtekst: + Behind Text Bag tekst + Below text Under tekst + Both Sides Begge sider + Break Link Ophæv forbindelse + Canvas Member Lærredselement + Choose the sheet that holds the recipients. Vælg det ark, der indeholder modtagerne. + Circle Cirkel + Click the empty text box the text should continue in (Esc cancels) Klik på den tomme tekstboks, hvor teksten skal fortsætte (Esc annullerer) + Communication Kommunikation + Continuous numbering Fortsat nummerering + Convert Notes Konvertér noter + Create Link Opret forbindelse + Custom mark: Tilpasset mærke: + Distribute letters Fordel bogstaver + Distribute spacing Fordel afstand + Drawing Canvas Lærred + Enclose Characters Omslut tegn + Enclosure Omslutning + End of document Slut på dokumentet + End of section Slut på sektionen + Endnotes Slutnoter + Endnotes to footnotes Slutnoter til fodnoter + Enlarge symbol Forstør symbol + Expand Udvid + First row of data contains column headers Første række data indeholder kolonneoverskrifter + Fit Tilpas + Footnotes to endnotes Fodnoter til slutnoter + Icon Color Ikonfarve + In Front of Text Foran tekst + In Line with Text På linje med tekst + Largest Only Kun største + Left Only Kun venstre + Location Placering + Middle Midt + Moved from Flyttet fra + Moved to Flyttet til + Nature Natur + New Drawing Canvas Nyt lærred + No icons match your search. Ingen ikoner matcher din søgning. + Not linked: click a text box Ikke forbundet: klik på en tekstboks + Office Kontor + Offset (pt): Forskydning (pt): + People Personer + Phonetic Guide Fonetisk vejledning + Recolor a one-color picture, such as an icon Genfarv en ensfarvet figur, f.eks. et ikon + Right (vertical) Højre (lodret) + Right Only Kun højre + Right-to-Left Section Sektion fra højre mod venstre + Right-to-Left Table Tabel fra højre mod venstre + Rotate all text 270° Roter al tekst 270° + Rotate all text 90° Roter al tekst 90° + Ruby text: Rubi-tekst: + Scale Drawing Skaler tegning + Search icons Søg i ikoner + Section direction: Sektionsretning: + Shrink text Formindsk tekst + Size (pt): Størrelse (pt): + Square Kvadrat + Stacked Stablet + Swap footnotes and endnotes Byt fodnoter og slutnoter + Table direction: Tabelretning: + Technology Teknologi + Through Igennem + Top and Bottom Top og bund + Transport Transport + Weather Vejr + Wrap Text Sides Sider for tekstindpakning + Characters per line: Tegn pr. linje: + Document Grid Dokumentgitter + Lines and characters Linjer og tegn + Lines only Kun linjer + Lines per page: Linjer pr. side: + No grid Intet gitter + Text snaps to characters Tekst låses til tegn + A, B, C, … A, B, C, … + Above the item Over elementet + Accessed on Besøgt den + Accessibility Pane Tilgængelighedspanel + Add New Source… Tilføj ny kilde… + Add Pen Tilføj pen + Add line numbering Tilføj linjenummerering + Add the chapter number Tilføj kapitelnummeret + Add to Document Føj til dokumentet + Add\nPen Tilføj\npen + Address labels Adressetiketter + Alt Text Pane Alternativtekst-rude + Alt\nText Alternativ\ntekst + Arabic Arabisk + Atmosphere Atmosfære + Auto Auto + Automatically hyphenate document Orddel dokumentet automatisk + Below the item Under elementet + Book Bog + Book title Bogtitel + Caption Billedtekst + Caption Numbering Nummerering af billedtekster + Caption text Billedtekst + Caption: Billedtekst: + Chapter in a book Kapitel i en bog + Chapters begin at: Kapitler begynder ved: + Chinese (Simplified) Kinesisk (forenklet) + Chinese (Traditional) Kinesisk (traditionelt) + Collect entries from: Indsaml poster fra: + Column: Kolonne: + Columns: Kolonner: + Conference Konference + Conference paper Konferencepaper + Container Title Beholdertitel + Convert to Shapes Konvertér til figurer + Count by: Tæl efter: + Create Source Opret kilde + Cross-reference: Krydshenvisning: + Custom Table of Contents… Brugerdefineret indholdsfortegnelse… + Czech Tjekisk + DOI DOI + Danish Dansk + Day Dag + Decorative objects only add visual interest: screen readers skip them. Dekorative objekter giver kun visuel interesse: skærmlæsere springer dem over. + Delete Label Slet etiket + Delete Selected Ink Slet valgt håndskrift + Delivery address: Leveringsadresse: + Describe what this object shows, for people who can't see it. Beskriv, hvad dette objekt viser, for personer, der ikke kan se det. + Describe what this table shows, for people who can't see it. Beskriv, hvad denne tabel viser, for personer, der ikke kan se den. + Detect language automatically Registrer sprog automatisk + Do not check spelling or grammar Kontrollér ikke stavning eller grammatik + Dutch Hollandsk + Edit Source Rediger kilde + Edition Udgave + Edit… Rediger… + Entry marked. Element markeret. + Envelope size: Kuvertstørrelse: + Errors ({n}) Fejl ({n}) + Estonian Estisk + Example: {sample} Eksempel: {sample} + Figure Figur + Film Film + Finnish Finsk + Forward Fremad + French (France) Fransk (Frankrig) + From text: Fra tekst: + Full page of the same label Hele siden med den samme etiket + German (Germany) Tysk (Tyskland) + Greek Græsk + Heading styles (levels 1–{n}) Overskriftsstile (niveau 1–{n}) + Heading {n} Overskrift {n} + Hebrew Hebraisk + Hyphenate at: Orddel ved: + Hyphenate words in CAPS Orddel ord med store bogstaver + Hyphenation Options… Indstillinger for orddeling… + Hyphenation zone: Orddelingszone: + I, II, III, … I, II, III, … + Include page numbers Inkludér sidetal + Indented Med indrykning + Ink Replay Afspil håndskrift + Ink to Shape Håndskrift til figur + Institution Institution + Ionosphere Ionosfære + Issue Hæfte + Italian Italiensk + Japanese Japansk + Journal Tidsskrift + Korean Koreansk + Label: Etiket: + Lasso Select Lassomarkering + Last, First; Last, First Efternavn, Fornavn; Efternavn, Fornavn + Leave the label out of the caption Lad etiketten være uden for billedteksten + Letter headings Bogstavsoverskrifter + Levels: Niveauer: + Limit consecutive hyphens to: Begræns antal på hinanden følgende orddelinger til: + Line Numbering Options… Indstillinger for linjenummerering… + Link entries to their headings Knyt elementer til deres overskrifter + Made from the author and year when empty Uddannes af forfatter og år, når feltet er tomt + Magazine or newspaper article Artikel i magasin eller avis + Main entry: Hovedelement: + Manual Hyphenation Manuel orddeling + Manual hyphenation is complete. Manuel orddeling er fuldført. + Manual… Manuelt… + Mark Markér + Mark All Markér alle + Mark Entry… Markér element… + Mark Index Entry Markér indekselement + Mark as decorative Markér som dekorativ + Mark selected text as: Markér valgt tekst som: + Mesosphere Mesosfære + Month Måned + Move Selected Ink Flyt valgt håndskrift + New Document Nyt dokument + New Label Ny etiket + New Label… Ny etiket… + New… Ny… + No accessibility issues found. Ingen tilgængelighedsproblemer fundet. + No limit Ingen grænse + No sources yet. Ingen kilder endnu. + Norwegian (Bokmål) Norsk (bokmål) + Numbering: Nummerering: + Numbering… Nummerering… + Omit Udelad + One label per recipient Én etiket pr. modtager + Options… Indstillinger… + Other Andet + Other styles and their TOC level: Andre stile og deres niveau i indholdsfortegnelsen: + Outline levels Dispositionsniveauer + Page number style Stil for sidetal + Page numbers at the right margin Sidetal ved højre sidekant + Page title Sidetitel + Pages of bookmark: Sider i bogmærke: + Periodical Periodisk skrift + Portuguese (Brazil) Portugisisk (Brasilien) + Portuguese (Portugal) Portugisisk (Portugal) + Position: Position: + Publisher Udgiver + Recolor Selected Ink Skift farve på valgt håndskrift + Remove Pen Fjern pen + Return address labels Returadresseetiketter + Return address: Returadresse: + Rewind Spol tilbage + Row: Række: + Run-in På linje med + Russian Russisk + See Se + Select a picture, shape, chart or table to describe it. Vælg et billede, en figur, et diagram eller en tabel for at beskrive den. + Separator: Skilletegn: + Serbian (Cyrillic) Serbisk (cyrillisk) + Serbian (Latin) Serbisk (latinsk) + Set As Default Angiv som standard + Shipping labels Forsendelsesetiketter + Show every bibliography field Vis alle bibliografifelter + Single label Enkelt etiket + Site name Webstedsnavn + Source Manager Kildehåndtering + Source type: Kildetype: + Sources in this document: Kilder i dette dokument: + Spanish (Mexico) Spansk (Mexico) + Spanish (Spain) Spansk (Spanien) + Stroke {n} of {total} Streg {n} af {total} + Styles: Stile: + Subentry: Underelement: + Swedish Svensk + TC entry fields TC-elementfelter + Tab leader: Udfyldningstegn for tabulator: + Table of Contents Options Indstillinger for indholdsfortegnelse + Table of Contents Styles Stile for indholdsfortegnelse + Tag Tag + This page Denne side + Tips ({n}) Tips ({n}) + Title Titel + Title: Titel: + Turkish Tyrkisk + Type: Type: + URL URL + Ukrainian Ukrainsk + View Alt Text… Vis alternativ tekst… + Volume Bind + Warnings ({n}) Advarsler ({n}) + Website Websted + Year År + a, b, c, … a, b, c, … + i, ii, iii, … i, ii, iii, … + {language} is now the document's default language. {language} er nu dokumentets standardsprog. + {n} entries marked. Markerede elementer: {n}. + 0 or More Characters 0 eller flere tegn + 1. Formatting restrictions 1. Formateringsbegrænsninger + 2. Editing restrictions 2. Redigeringsbegrænsninger + 3. Start enforcement 3. Start håndhævelse + << Less << Mindre + Above Chart Over diagram + Accent Gradient Accentfarve med gradient + Accent with Reflection Accentfarve med refleksion + Accent {n} Accentfarve {n} + Add Chart Element Tilføj diagramelement + Add Font… Tilføj skrifttype… + Add Row Tilføj række + Add Series Tilføj serie + Add Shape Tilføj figur + Add\nShape Tilføj\nfigur + Added Fonts Tilføjede skrifttyper + All Levels Alle niveauer + All files Alle filer + All pages Alle sider + All pages in the range Alle sider i området + Allow line breaks Tillad linjeskift + Animate the caret (it glides and fades) Animér markøren (den glider og falmer) + Animation Animation + Animation effects: Animationseffekter: + Another file Anden fil + Any Character Vilkårligt tegn + Any Digit Vilkårligt ciffer + Any Letter Vilkærligt bogstav + Apply as you type Anvend under indtastning + Arch Down Bue ned + Arch Up Bue op + Area Område + As you type Under indtastning + Asian text font: Skrifttype til asiatisk tekst: + Attach Template Vedhæft skabelon + Attach… Vedhæft… + AutoCorrect AutoCorrect + AutoFormat As You Type Autoformat under indtastning + AutoSave writes your changes to the file a moment after you stop typing. AutoSave gemmer dine ændringer i filen et øjeblik, efter at du holder op med at skrive. + Automatic bulleted lists Automatiske punktlister + Automatic numbered lists Automatiske nummererede lister + Automatically Update Document Styles Opdatér dokumentstile automatisk + Automatically update document styles Opdatér dokumentstile automatisk + Basic Cycle Grundlæggende cyklus + Basic List Grundlæggende liste + Basic Matrix Grundlæggende matrix + Basic Process Grundlæggende proces + Basic Pyramid Grundlæggende pyramide + Basic Radial Grundlæggende radial + Basic Venn Grundlæggende Venn + Beginning of Word Begyndelse af ord + Blue: Blå: + Border lines Kantlinjer + Browse in WordCraft… Gennemse i WordCraft… + Building Block Gallery Content Control Indholdskontrol til byggeblokgalleri + Can't be deleted Kan ikke slettes + Capitalize first letter of sentences Skriv første bogstav i sætninger med stort + Capitalize first letter of table cells Skriv første bogstav i tabelceller med stort + Capitalize names of days Skriv navne på dage med stort + Caret Character Markørtegn + Case changes Ændring af skrivetype + Categories Kategorier + Category {n} Kategori {n} + Centimeters Centimeter + Change Chart Type Skift diagramtype + Change Colors Skift farver + Change Layout Skift layout + Change\nChart Type Skift\ndiagramtype + Change\nColors Skift\nfarver + Character in Range Tegn i området + Character level Tegnniveau + Chart Design Diagramdesign + Chart Layouts Diagramlayouts + Chart Title Diagramtitel + Check Box Content Control Indholdskontrol til afkrydsningsfelt + Check spelling as you type Kontrollér stavning under indtastning + Checked styles are allowed: Tilladte stile: + Checked symbol Afkrydset symbol + Chevron Down Pil ned + Chevron Up Pil op + Choose List Item Vælg listeelement + Click to switch back to inserting Klik for at gå tilbage til indsættelse + Clipboard Contents Udklipsholderens indhold + Close Outline View Luk dispositionsvisning + Close Outline\nView Luk\ndispositionsvisning + Clustered Bar Samlet søjle + Clustered Column Samlet kolonne + Collapse Skjul + Collate copies Sortér kopier + Color model: Farvemodel: + Colorful Farverig + Colors: Farver: + Combine Documents Sammensæt dokumenter + Combo Box Content Control Indholdskontrol til kombinationsfelt + Comments beside each page and tracked changes in PDFs, prints and images; a comment list at the end of web pages, Markdown and text. Kommentarer ved siden af hver side og sporede ændringer i PDF'er, udskrifter og billeder; en liste med kommentarer i slutningen af websider, Markdown og tekst. + Compare Documents Sammenlign dokumenter + Compared: {changes} changes Sammenlignet: {changes} ændringer + Comparing with another file needs the desktop app. Sammenligning med en anden fil kræver computerappen. + Comparison settings Sammenligningsindstillinger + Connect Shapes Forbind figurer + Content Control Properties Indholdskontrolgens egenskaber + Content Control at Selection Indholdskontrolelement ved markering + Contents can't be edited Indholdet kan ikke redigeres + Controls Kontrolelementer + Copies Kopier + Copy Styles Kopiér stile + Create Graphic Opret grafik + Current Nuværende + Current page Nuværende side + Curved Arrow Connector Krum pilforbindelse + Curved Connector Krum forbindelse + Custom dictionary Brugerdefineret ordbog + Custom range Brugerdefineret område + Cycle Cyklus + Dark with Shadow Mørk med skygge + Data Labels Datalabels + Date Picker Content Control Indholdskontrolelement: Datovælger + Date format Datoformat + Deflate Komprimér + Deleted Slettet + Demote to Body Text Sænk til brødtekst + Design Mode Designtilstand + Detach Template Frigør skabelon + Developer Udvikler + Display text Vis tekst + Document template: Dokumentskabelon: + Documents Dokumenter + Don't capitalize after: Brug ikke store bogstaver efter: + Doughnut Donut + Drop-Down List Content Control Indholdskontrolelement: Rulleliste + Edit Data Redigér data + Edit Items Redigér elementer + Edit\nData Redigér\ndata + Editing in this document is restricted. Redigering i dette dokument er begrænset. + Elbow Arrow Connector Vinklet pilforbindelse + Elbow Connector Vinklet forbindelse + End of Word Slut på ord + Even pages only Kun lige sider + Every change is recorded as a tracked change. Alle ændringer registreres som en sporet ændring. + Everyone Alle + Exceptions (optional) Undtagelser (valgfri) + Exceptions… Undtagelser… + Expand or Collapse Heading Udvid eller skjul overskrift + Expression Udtryk + Extend Selection by Units Udvid markering efter enheder + Fields Felter + File name: Filnavn: + Filling in forms Udfyldning af formularer + Find Next Editable Region Find næste redigerbare område + Find What Expression Find efter udtryk + Find What Text Find efter tekst + Fix Position on Page Fastgør placering på siden + Footnotes and endnotes Fodnoter og slutnoter + Formatting exceptions Formateringsundtagelser + Formatting marks Formattegn + Formatting, case changes, white space and tables are compared; the other settings are kept for later. Formatering, ændringer af store og små bogstaver, mellemrum og tabeller sammenlignes; de øvrige indstillinger gemmes til senere. + Fractions (1/2) as fraction characters (½) Brøker (1/2) som brøksymboler (½) + Graphic Grafik + Green: Grøn: + Headers and footers Sidehoved og sidefod + Hex: Hex: + Hidden text Skjult tekst + Hide White Space Skjul mellemrum + Hierarchy Hierarki + Hollow Hul + Home folder Hjemmappe + Hue: Nuance: + Hyphens (--) as dashes (—) Bindestreger (--) som tankestreger (—) + Ignore punctuation characters Ignorer tegnsætningstegn + Ignore web and file addresses Ignorer web- og filadresser + Ignore white-space characters Ignorer mellemrumstegn + Ignore words in capitals Ignorer ord med store bogstaver + Ignore words with numbers Ignorer ord med tal + In equations, typing a name after a backslash replaces it with its symbol. I formler erstattes et navn, der skrives efter en omvendt skråstreg, med dets symbol. + Inches Tommer + Inflate Udvid + Initials: Initialer: + Insert Chart Indsæt diagram + Insert SmartArt Indsæt SmartArt + Inside: Indeni: + Interface Brugerflade + Interface language Brugerfladesprog + Internet addresses as links Internetadresser som links + Label changes with: Mærk ændringer med: + Latin text font: Skrifttype for latinsk tekst: + Layouts Layouts + Legend Forklaring + Light with Outline Lys med kontur + Limit formatting to a selection of styles Begræns formatering til bestemte stile + List Liste + List entries Listeelementer + Lum: Lysstyrke: + Manual Line Break Manuelt linjeskift + Manual Page Break Manuelt sideskift + Mark grammar errors as you type Markér grammatikfejl, mens du skriver + Match prefix Match præfiks + Match suffix Match suffiks + Math AutoCorrect Automatisk rettelse af formler + Measurement units: Måleenheder: + Millimeters Millimeter + Mirror indents Spejl indrykninger + More >> Mere >> + More Colors… Flere farver… + Move Down Flyt ned + Move Up Flyt op + Move with Text Flyt med teksten + Moves Flytninger + New comments and tracked changes are recorded under this name and these initials. Nye kommentarer og sporede ændringer registreres under dette navn og disse initialer. + New document Nyt dokument + New word Nyt ord + No Fill Ingen udfyldning + No Outline Ingen kontur + No Reflection Ingen reflektion + No Transform Ingen transformation + No changes (read only) Ingen ændringer (skrivebeskyttet) + No words yet. Add to Dictionary on a misspelled word puts it here. Ingen ord endnu. Tilføj til ordbog på et ord med fejl, så lægges det her ind. + Not Ikke + Num Occurrences Antal forekomster + Odd pages only Kun ulige sider + Off Fra + On Til + On: the caret glides to its new place when you type or use the arrow keys, and its blink fades. Off: it jumps and blinks on and off. Til: markøren glider hen til sin nye placering, når du skriver eller bruger piletasterne, og dens blink toner ud. Fra: den hopper og blinker tydeligt. + One line per item. Enter adds an item. Én linje pr. element. Enter tilføjer et element. + Only allow this kind of editing: Tillad kun denne type redigering: + Only comments can be added, except in regions marked editable. Der kan kun tilføjes kommentarer, undtagen i områder, der er markeret til redigering. + Only form fields can be filled in. Der kan kun udfyldes formularfelter. + Only the allowed styles can be applied. Der kan kun anvendes tilladte stile. + Open a file with WordCraft's own browser when the system file dialog doesn't appear. Åbn en fil i WordCrafts egen filbrowser, når systemets filedialog ikke vises. + Ordinals (1st) as superscript Ordinaltal (1st) som hævet skrift + Organizer Organisator + Organizer… Organisator… + Original document Originaldokument + Outline State Dispositionstilstand + Outline Tools Dispositionsværktøjer + Outlining Disposition + Outside: Udenfor: + Overtype Overskrivning + Overtype (typing replaces the characters after the caret) Overskrivning (det indtastede erstatter tegnene efter markøren) + Page display Sidevisning + Pages like 1-3, 5 Sider som 1-3, 5 + Paragraph Character Afsnitssymbol + Paragraph marks Afsnitsmærker + Personalize Tilpas + Picas Picas + Picture Content Control Indholdskontrolelement: Billede + Pie Cirkeldiagram + Placeholder text Pladsholdertekst + Plain Text Content Control Indholdskontrolelement: Almindelig tekst + Points Punkt + Previous 1 or More Forrige 1 eller flere + Previous Page Forrige side + Print Markup Udskriv kommentarer og rettelser + Print the page color Udskriv sidens farve + Print to PDF Udskriv til PDF + Printing Udskrivning + Process Proces + Promote to Heading 1 Hæv til Overskrift 1 + Proofing language Sprog til stave- og grammatikontrol + Put the caret in a content control first. Sæt først markøren i et indholdskontrolelement. + Pyramid Pyramide + Recent Colors Seneste farver + Recent documents to show: Seneste dokumenter, der vises: + Recipient lists Modtagerlister + Red: Rød: + Reflection Reflektion + Regular expressions Regulære udtryk + Relationship Relation + Remove Added Font Fjern tilføjet skrifttype + Remove Content Control Fjern indholdskontrolelement + Remove row Fjern række + Remove series Fjern serie + Remove the control once edited Fjern kontrolelementet efter redigering + Rename Style Omdøb stil + Repeating Section Content Control Indholdskontrolelement: Gentaget afsnit + Replace as you type Erstat under indtastning + Replace text as you type Erstat tekst under indtastning + Replace: Erstat: + Reset Graphic Nulstil grafik + Reset\nGraphic Nulstil\ngrafik + Resize Table by Dragging Tilpas tabelstørrelse ved at trække + Select Column Vælg kolonne + Select Table Cells by Clicking Vælg tabelceller ved at klikke + Insert Row or Column by Clicking Indsæt række eller kolonne ved at klikke + Move Table by Dragging Flyt tabel ved at trække + AutoFit Column by Double-Clicking Tilpas kolonne automatisk ved at dobbeltklikke + Revised document Revideret dokument + Rich Text Content Control Indholdskontrolelement: Formateret tekst + Sat: Mætning: + Save documents Gem dokumenter + Save these pages as PDF… Gem disse sider som PDF… + Scatter Punktdiagram + Search Options Søgeindstillinger + Section Break Afsnitsskift + Section Character Afsnitstegn + Select Heading and Content Vælg overskrift og indhold + Select text, then tick who can still edit it. Vælg teksten, og markér derefter, hvem der stadig må redigere den. + Selected pages Valgte sider + Series name Serienavn + Series {n} Serie {n} + Set Date Angiv dato + Settings… Indstillinger… + Show All Levels Vis alle niveauer + Show First Line Only Vis kun første linje + Show Level Vis niveau + Show Level 1 Vis niveau 1 + Show Level 2 Vis niveau 2 + Show Level 3 Vis niveau 3 + Show Level 4 Vis niveau 4 + Show Level 5 Vis niveau 5 + Show Level 6 Vis niveau 6 + Show Level 7 Vis niveau 7 + Show Level 8 Vis niveau 8 + Show Level 9 Vis niveau 9 + Show Text Formatting Vis tekstformatering + Show changes at: Vis ændringer i niveau: + Show changes in: Vis ændringer i: + Show the mini toolbar when text is selected Vis miniværktøjslinjen, når tekst markeres + Showing the first {count} items. Viser de første {count} elementer. + Shows a tree: each item below the item it belongs to. Viser et træ: hvert element placeres under det element, det hører til. + Shows items around a central idea: the first item is the centre. Viser elementer omkring en central idé: det første element er centrum. + Shows items of equal weight as blocks in rows. Viser elementer med samme vægt som blokke i rækker. + Shows overlapping ideas as overlapping circles. Viser overlappende idéer som overlappende cirkler. + Shows parts of a whole as quadrants. Viser delene af en helhed som kvadranter. + Shows parts that build on each other, largest at the bottom. Viser dele, der bygger oven på hinanden, den største nederst. + Shows steps in order, left to right, with arrows between them. Viser trin i rækkefølge fra venstre til højre med pile mellem dem. + Shows steps that repeat, around a circle. Viser trin, der gentages, i en cirkel. + Slant Down Hældning ned + Slant Up Hældning op + Smart quotes instead of straight quotes Smarte anførselstegn i stedet for lige anførselstegn + SmartArt Design SmartArt-design + SmartArt Styles SmartArt-stile + Smooth scrolling and zooming, and menus and buttons that fade in. Off shows every change at once. Glat rulning og zoom, og menuer og knapper, der toner ind. Fra viser alle ændringer på én gang. + Soft Glow Blød glød + Solid Accent Massiv accent + Spaces Mellemrum + Special Særlige + Spelling Stavning + Spelling and grammar are checked in English (United States). Stavning og grammatik kontrolleres på engelsk (USA). + Stacked Bar Stablet søjlediagram + Stacked Column Stablet kolonnediagram + Standard Standard + Start Enforcing Protection Begynd at håndhæve beskyttelse + Stop Protection Stop beskyttelse + Straight Arrow Connector Lige pilforbindelse + Straight Connector Lige forbindelse + Tab Character Tabulator + Tab characters Tabulatorer + Templates Skabeloner + Templates and Add-ins Skabeloner og tilføjelsesprogrammer + Text Fill Tekstfyld + Text Outline Tekstkontur + Text Pane Tekstfelt + Text boxes Tekstfelter + The document is read only, except for regions marked editable. Dokumentet er skrivebeskyttet, undtagen områder, der er markeret som redigerbare. + The password is optional; without it anyone can stop the protection. Adgangskoden er valgfri; uden den kan alle slå beskyttelsen fra. + The printer, two-sided printing and pages per sheet are chosen in that dialog. Printer, dobbeltsidet udskrivning og sider pr. ark vælges i denne dialog. + The system file dialog isn't available, so WordCraft shows its own. To get the system dialog, install a file chooser portal (such as xdg-desktop-portal-gtk) or zenity. Systemets fildialog er ikke tilgængelig, så WordCraft viser sin egen. Få systemets dialog ved at installere en filvælgerportal (f.eks. xdg-desktop-portal-gtk) eller zenity. + These marks show even when Show/Hide ¶ is off. Disse tegn vises, selv når Vis/skjul ¶ er slået fra. + This document Dette dokument + This folder can't be read: {error} Denne mappe kan ikke læses: {error} + This password type isn't supported. Denne type adgangskode understøttes ikke. + Today I dag + Toggle Check Box Slå afkrydsningsfelt til/fra + Tracked changes Sporede ændringer + Transform Transformér + Triangle Down Trekant ned + Triangle Up Trekant op + Type Type + Type the categories down the side and a column of values per series. Indtast kategorierne ned langs siden og en kolonne med værdier for hver serie. + Type the password to stop the protection. Indtast adgangskoden for at slå beskyttelsen fra. + Typing replaces selected text Skrivning erstatter markeret tekst + Unchecked symbol Ikke-afkrydset symbol + Up one level Op ét niveau + Update Styles from Template Opdatér stile fra skabelon + Update fields before printing Opdatér felter før udskrivning + Use the Insert key to switch overtype on and off Brug Insert-tasten til at slå overskrivning til og fra + Use wildcards Brug jokertegn + Value Værdi + Visual effects Visuelle effekter + Warm Gradient with Outline Varm gradient med kontur + White Space Mellemrum + White space Mellemrum + With: Med: + Word XML Document (*.xml) Word XML-dokument (*.xml) + Word document Word-dokument + Word level Ordniveau + WordArt Styles WordArt-stile + Your system doesn't report an animation setting, so animation effects stay on. Dit system rapporterer ikke en animationsindstilling, så animationseffekter forbliver slået til. + Your system has animation effects turned off. Dit system har animationseffekter slået fra. + Your system has animation effects turned on. Dit system har animationseffekter slået til. + {count} of {total} pages will print {count} af {total} sider udskrives + {name} already exists. Choose Save again to replace it. {name} findes allerede. Vælg Gem igen for at erstatte den. + {name} wasn't found. {name} blev ikke fundet. + “{text}” ({row}, {series}) isn't a number. „{text}” ({row}, {series}) er ikke et tal. + Undo Actions Fortryd handlinger + Undo History Fortrydningshistorik + Undo 1 Action Fortryd 1 handling + Undo {n} Actions Fortryd handlinger: {n} + Start-up Opstart + Show the Start screen when WordCraft starts Vis startskærmen, når WordCraft starter + On: WordCraft opens on File › Home, with a blank document, the templates and your recent documents. Off: it opens a blank document. Til: WordCraft åbner på Filer › Start med et tomt dokument, skabeloner og dine seneste dokumenter. Fra: Der åbnes et tomt dokument. + Couldn't make WordCraft the default: {error} Kunne ikke gøre WordCraft til standardprogram: {error} + In Default apps, choose WordCraft for the document types you want it to open. I Standardapps vælger du WordCraft for de dokumenttyper, du vil have den til at åbne. + Make WordCraft the default for Word documents… Gør WordCraft til standardprogram for Word-dokumenter… + Opens .docx, .doc, .rtf and .odt files and Word templates with WordCraft from now on. Åbner .docx-, .doc-, .rtf- og .odt-filer samt Word-skabeloner med WordCraft fra nu af. + Opens Settings › Default apps, where you choose WordCraft for .docx and the other document types. Åbner Indstillinger › Standardapps, hvor du vælger WordCraft til .docx og de andre dokumenttyper. + To open Word documents with WordCraft by default: select one in Finder, choose File › Get Info, pick WordCraft under Open with, then click Change All. For at åbne Word-dokumenter med WordCraft som standard: vælg et i Finder, vælg Filer › Hent oplysninger, vælg WordCraft under Åbn med, og klik derefter på Skift alle. + WordCraft is now the default app for Word documents. WordCraft er nu standardprogram for Word-dokumenter. + Numerals: Tal: + Numerals Tal + Hindi Hindi + Context Kontekst + System System + Book Title Bogtitel + Category Kategori + Contributors Bidragydere + Emphasis Fremhævning + Endnote Reference Slutnotehenvisning + Endnote Text Slutnotetekst + Footnote Reference Fodnotehenvisning + Footnote Text Fodnotetekst + Hyperlink Hyperlink + Intense Emphasis Kraftig fremhævning + Intense Quote Kraftigt citat + Intense Reference Kraftig henvisning + Keywords Nøgleord + No Spacing Uden afstand + Quote Citat + Strong Fed + Subject Emne + Subtitle Undertitel + Subtle Emphasis Diskret fremhævning + Subtle Reference Diskret henvisning + TOC Heading Overskrift for indholdsfortegnelse + TOC {n} Indholdsfortegnelse {n} + List Paragraph Listeparagraf + Models Modeller + Show KeyTips (Alt or F10) Vis nøgletip (Alt eller F10) + Reading Læsning + Reading Column Width Læsekolonnens bredde + Reading Line Spacing Linjeafstand til læsning + Reading Text Size Tekststørrelse til læsning + Correct TWo INitial CApitals Rettelse af to store begyndelsesbogstaver + Correct accidental usage of cAPS LOCK key Ret uvedkommende brug af cAPS LOCK-tasten + Region: Område: + Automatic ({region}) Automatisk ({region}) + AaBbCcDd AaBbCcDd + Annotation Reference Annotationshenvisning + Annotation Subject Annotationsemne + Annotation Text Annotationstekst + Body text in a short paragraph to show spacing. Brødtekst i et kort afsnit for at vise afstand. + Default Paragraph Font Skrifttype til standardafsnit + Envelope Address Konvolutadresse + Envelope Return Returadresse på konvolut + Index Heading Indeksoverskrift + Index {n} Indeks {n} + Line Number Linjenummer + Normal Normal + Normal Indent Normal med indrykning + Normal Table Normaltabel + Table Grid Tabelgitter + Table of Figures Afbildningstabel +preview Title Titel + TOA Heading TOA-overskrift + Good evening God aften + Good morning Godmorgen + Good afternoon God eftermiddag + tOGGLE cASE sKIFT STORE/SMÅ BOGSTAVER + Change to Level 1 Skift til niveau 1 + Change to Level 2 Skift til niveau 2 + Change to Level 3 Skift til niveau 3 + Change to Level 4 Skift til niveau 4 + Change to Level 5 Skift til niveau 5 + Level 1 Niveau 1 + Level 2 Niveau 2 + Level 3 Niveau 3 + ASAP SNARST + CONFIDENTIAL FORTROLIG + DO NOT COPY IKKE TIL KOPIERING + DRAFT KLADDE + SAMPLE PRØVE + URGENT HASTENDE + Plain Table 1 Enkel tabel 1 + Grid Table 1 Light Gittertabel 1 lys + Grid Table 4 – Blue Gittertabel 4 – Blå + Grid Table 4 – Green Gittertabel 4 – Grøn + Grid Table 4 – Orange Gittertabel 4 – Orange + Grid Table 4 – Purple Gittertabel 4 – Lilla + List Table 3 – Blue Listetabel 3 – Blå + List Table 3 – Green Listetabel 3 – Grøn + List Table 3 – Orange Listetabel 3 – Orange + List Table 3 – Purple Listetabel 3 – Lilla + Convert between built-up and linear form Konvertér mellem professionel og lineær form + Justification of a display equation Justering af en displayformel + Number the equation; the number sits at the right margin (or type # then the number) Nummerér formlen; tallet placeres i højre margen (eller skriv # og derefter tallet) + Show the equation in the line of text Vis formlen i tekstlinjen + Show the equation on a line of its own Vis formlen på en linje for sig + Type equations in LaTeX Skriv formler i LaTeX + Type equations in the linear format (UnicodeMath) Skriv formler i det lineære format (UnicodeMath) + Type normal (non-math) text in the equation Skriv almindelig (ikke-matematisk) tekst i formlen + Sides of a right triangle Sider i en retvinklet trekant + Circle area from its radius Cirkelareal ud fra radius + Roots of ax²+bx+c Rødder i ax²+bx+c + Binomial expansion of (x+a)ⁿ Binomialudvikling af (x+a)ⁿ + Power series of (1+x)ⁿ Potensrække for (1+x)ⁿ + Euler's identity Eulers identitet + Derivative as a limit Den afledte som en grænseværdi + Exponential series Eksponentiel række + Integral of e^(−x²) Integral af e^(−x²) + Sine and cosine series of f(x) Sinus- og cosinusrække for f(x) + Sum or difference of sines Sum eller difference af sinusser + Sum of cosines Sum af cosinusser + Normal probability density Normalfordelingens tæthedsfunktion + Bayes' rule Bayes' sætning + Accents Akcenter + Basic Math Grundlæggende matematik + Boxed Formulas Indrammede formler + Cases and Stacks Tilfælde og stablinger + Common Accent Objects Almindelige akcentobjekter + Common Brackets Almindelige parenteser + Common Fractions Almindelige brøker + Common Functions Almindelige funktioner + Common Large Operators Almindelige store operatorer + Common Operator Structures Almindelige operatorstrukturer + Common Operators Almindelige operatorer + Common Radicals Almindelige rødder + Common Subscripts and Superscripts Almindelige indekser og hævede indekser + Contour Integrals Konturintegraler + Differentials Differentialer + Dots Prikker + Empty Matrices Tomme matricer + Functions Funktioner + Geometry Geometri + Greek Letters Græske bogstaver + Hyperbolic Functions Hyperbolske funktioner + Identity Matrices Identitetsmatricer + Integrals Integraler + Inverse Functions Inverse funktioner + Inverse Hyperbolic Functions Inverse hyperbolske funktioner + Letter-Like Symbols Bogstavlignende symboler + Matrices with Brackets Matricer med parenteser + Negated Relations Negerede relationer + Operator Structures Operatorstrukturer + Operators Operatorer + Other Large Operators Andre store operatorer + Overbars and Underbars Overstreger og understreger + Products and Coproducts Produkter og koprodukter + Radicals Rødder + Scripts Skriftformer + Single Brackets Enkeltstående parenteser + Sparse Matrices Tyndt besatte matricer + Subscripts and Superscripts Indekser og hævede indekser + Summations Summer + Trigonometric Functions Trigonometriske funktioner + Unions and Intersections Foreninger og fællesmængder + 1×2 Empty Matrix Tom matrix 1×2 + 1×3 Empty Matrix Tom matrix 1×3 + 2×1 Empty Matrix Tom matrix 2×1 + 2×2 Empty Matrix Tom matrix 2×2 + 2×3 Empty Matrix Tom matrix 2×3 + 3×1 Empty Matrix Tom matrix 3×1 + 3×2 Empty Matrix Tom matrix 3×2 + 3×3 Empty Matrix Tom matrix 3×3 + 2×2 Determinant Determinant 2×2 + 2×2 Identity Matrix Identitetsmatrix 2×2 + 2×2 Identity Matrix (sparse) Identitetsmatrix 2×2 (tyndt besat) + 3×3 Identity Matrix Identitetsmatrix 3×3 + 3×3 Identity Matrix (sparse) Identitetsmatrix 3×3 (tyndt besat) + 2×2 Matrix in Brackets 2×2-matrix i kantede parenteser + 2×2 Matrix in Double Bars 2×2-matrix i dobbelte lodstreger + 2×2 Matrix in Parentheses 2×2-matrix i runde parenteser + n×n Identity Matrix Identitetsmatrix n×n + n×n Matrix Matrix n×n + A xor B A xor B + Absolute value cases Tilfælde for absolut værdi + Acute Akut + Angle Brackets Vinkelparenteser + Angle Brackets with Separator Vinkelparenteser med adskiller + Angle Brackets with Two Separators Vinkelparenteser med to adskillere + Baseline Dots Prikker på basislinjen + Binomial Coefficient Binomialkoefficient + Binomial Coefficient (angle) Binomialkoefficient (vinkelparenteser) + Binomial Coefficient with Angle Brackets Binomialkoefficient med vinkelparenteser + Boxed Formula Indrammet formel + Boxed Formula (example) Indrammet formel (eksempel) + Braces Krøllede parenteser + Braces with Separator Krøllede parenteser med adskiller + Brackets Kantede parenteser + Brackets with Separators Kantede parenteser med adskillere + Breve Breve + Cases (Three Conditions) Tilfælde (tre betingelser) + Cases (Two Conditions) Tilfælde (to betingelser) + Ceiling Loft + Change in y over change in x Ændring i y i forhold til ændring i x + Check Hake + Circled Dot Prik i cirkel + Circled Plus Plus i cirkel + Circled Times Gangetegn i cirkel + Colon Equal Kolon med lighedstegn + Contour Integral Konturintegral + Contour Integral with Limits Konturintegral med grænser + Contour Integral with Stacked Limits Konturintegral med grænser over og under + Coproduct Koprodukt + Coproduct with Limits Koprodukt med grænser + Coproduct with Lower Limit Koprodukt med nedre grænse + Coproduct with Subscript-Superscript Limits Koprodukt med grænser i sub- og superskript + Cosecant Function Cosecansfunktion + Cosine 2x Cosinus 2x + Cosine Function Cosinusfunktion + Cotangent Function Cotangensfunktion + Cube Root Kubikrod + Delta Equal To Delta er lig med + Delta over arrow Delta over pil + Diagonal Dots Diagonale prikker + Differential Differentiale + Differential theta Differentiale theta + Differential x Differentiale x + Differential y Differentiale y + Dot Prik over tegn + Double Bar Dobbeltstreg over tegn + Double Dot Dobbeltprik over tegn + Double Integral Dobbeltintegral + Double Integral with Limits Dobbeltintegral med grænser + Double Integral with Stacked Limits Dobbeltintegral med grænser over og under + Double Left Arrow with Text Above Dobbelt venstrepil med tekst ovenfor + Double Left Arrow with Text Below Dobbelt venstrepil med tekst nedenfor + Double Left Right Arrow with Text Above Dobbelt venstre-højre-pil med tekst ovenfor + Double Left Right Arrow with Text Below Dobbelt venstre-højre-pil med tekst nedenfor + Double Right Arrow with Text Above Dobbelt højrepil med tekst ovenfor + Double Right Arrow with Text Below Dobbelt højrepil med tekst nedenfor + Double Square Brackets Dobbelte kantede parenteser + Double Vertical Bars Dobbeltlodrette streger + Double summation Dobbeltsum + Equal Equal Dobbelt lighedstegn + Equal by Definition Lig med definition + Exponential Eksponentialfunktion + Floor Gulv + Grave Grave + Grouping Character Above Grupperingstegn over + Grouping Character Below Grupperingstegn under + Hat Hat + Hyperbolic Cosecant Function Hyperbolsk cosecansfunktion + Hyperbolic Cosine Function Hyperbolsk cosinusfunktion + Hyperbolic Cotangent Function Hyperbolsk cotangensfunktion + Hyperbolic Secant Function Hyperbolsk secansfunktion + Hyperbolic Sine Function Hyperbolsk sinusfunktion + Hyperbolic Tangent Function Hyperbolsk tangensfunktion + Hypotenuse Hypotenuse + Integral with Limits Integral med grænser + Integral with Stacked Limits Integral med grænser over og under + Intersection Snitmængde + Intersection with Limits Snitmængde med grænser + Intersection with Lower Limit Snitmængde med nedre grænse + Intersection with Subscript-Superscript Limits Snitmængde med grænser i sub- og superskript + Inverse Cosecant Function Inverse cosecansfunktion + Inverse Cosine Function Inverse cosinusfunktion + Inverse Cotangent Function Inverse cotangensfunktion + Inverse Hyperbolic Cosecant Function Omvendt hyperbolsk cosecansfunktion + Inverse Hyperbolic Cosine Function Omvendt hyperbolsk cosinusfunktion + Inverse Hyperbolic Cotangent Function Omvendt hyperbolsk cotangensfunktion + Inverse Hyperbolic Secant Function Omvendt hyperbolsk secansfunktion + Inverse Hyperbolic Sine Function Omvendt hyperbolsk sinusfunktion + Inverse Hyperbolic Tangent Function Omvendt hyperbolsk tangensfunktion + Inverse Secant Function Inverse secansfunktion + Inverse Sine Function Inverse sinusfunktion + Inverse Tangent Function Inverse tangensfunktion + Left Arrow with Text Above Venstrepil med tekst ovenfor + Left Arrow with Text Below Venstrepil med tekst nedenfor + Left Bracket Twice Venstre kantet parentes to gange + Left Right Arrow Above Venstre-højre-pil over + Left Right Arrow with Text Above Venstre-højre-pil med tekst ovenfor + Left Right Arrow with Text Below Venstre-højre-pil med tekst nedenfor + Left Subscript-Superscript Venstre sub- og superskript + Leftwards Arrow Above Venstrevendt pil over + Leftwards Harpoon Above Venstrevendt harpon over + Limit Grænse + Limit Example Grænseeksempel + Linear Fraction Lineær brøk + Logarithm Logaritme + Logarithm with Base Logaritme med grundtal + Logical AND Logisk OG + Logical AND with Limits Logisk OG med grænser + Logical OR Logisk ELLER + Logical OR with Limits Logisk ELLER med grænser + Maximum Maksimum + Maximum Example Maksimumeksempel + Measured By Målt af + Midline Dots Prikker på midtlinjen + Minimum Minimum + Minus Equal Minus med lighedstegn + Natural Logarithm Naturlig logaritme + One half En halv + Outward Brackets Udadvendte kantede parenteser + Overbar Overstreg + Overbrace Overklamme + Parentheses Runde parenteser + Parentheses with Separator Runde parenteser med skilletegn + Partial Differential Partiel differentiale + Pi Over 2 Pi over 2 + Plus Equal Plus med lighedstegn + Product Produkt + Product of A Produkt af A + Product with Limits Produkt med grænser + Product with Lower Limit Produkt med nedre grænse + Product with Subscript-Superscript Limits Produkt med grænser i sub- og superskript + Quadratic Root Andengradsrod + Radical with Degree Rod med gradtal + Right Arrow with Text Above Højrepil med tekst ovenfor + Right Arrow with Text Below Højrepil med tekst nedenfor + Right Bracket Twice Højre kantet parentes to gange + Rightwards Arrow Above Højrevendt pil over + Rightwards Harpoon Above Højrevendt harpon over + Secant Function Secansfunktion + Sine Function Sinusfunktion + Sine theta Sinus theta + Single Left Angle Enkelt venstre vinkeltegn + Single Left Bar Enkelt venstre lodret streg + Single Left Brace Enkelt venstre krøllet parentes + Single Left Bracket Enkelt venstre kantet parentes + Single Left Parenthesis Enkelt venstre rund parentes + Single Right Brace Enkelt højre krøllet parentes + Single Right Bracket Enkelt højre kantet parentes + Single Right Parenthesis Enkelt højre rund parentes + Skewed Fraction Skrå brøk + Small change Lille ændring + Square Root Kvadratrod + Square Root with Degree Kvadratrod med gradtal + Square Union Kvadratisk foreningsmængde + Stack Object Stablet objekt + Stack Object (Three) Stablet objekt (tre) + Stacked Fraction Stablet brøk + Stacked, no bar Stablet uden streg + Subscript-Superscript Sub- og superskript + Summation Sum + Summation from 0 to n Sum fra 0 til n + Summation over binomials Sum over binomialer + Summation with Limits Sum med grænser + Summation with Lower Limit Sum med nedre grænse + Summation with Subscript Limit Sum med grænse i indeks + Summation with Subscript-Superscript Limits Sum med grænser i sub- og superskript + Surface Integral Fladeintegral + Surface Integral with Limits Fladeintegral med grænser + Surface Integral with Stacked Limits Fladeintegral med grænser over og under + Tangent Function Tangensfunktion + Tangent formula Tangentformel + Text over Colon Equal Tekst over kolon med lighedstegn + Text over Equal Tekst over lighedstegn + Tilde Tilde + Triple Dot Tre prikker over tegn + Triple Integral Tredobbeltintegral + Triple Integral with Limits Tredobbeltintegral med grænser + Triple Integral with Stacked Limits Tredobbeltintegral med grænser over og under + Underbar Understreg + Underbrace Underklamme + Union Foreningsmængde + Union of intersections Forening af snitmængder + Union with Limits Foreningsmængde med grænser + Union with Lower Limit Foreningsmængde med nedre grænse + Union with Plus Foreningsmængde med plus + Union with Subscript-Superscript Limits Foreningsmængde med grænser i sub- og superskript + Up Diagonal Dots Diagonale prikker opad + Vector A Vektor A + Vector AB Vektor AB + Vertical Bars Lodrette streger + Vertical Dots Lodrette prikker + Volume Integral Volumenintegral + Volume Integral with Limits Volumenintegral med grænser + Volume Integral with Stacked Limits Volumenintegral med grænser over og under + Yields Giver + x bar x med streg + All - Linear Alle – lineære + All - Professional Alle – professionelle + Current - Linear Nuværende – lineære + Current - Professional Nuværende – professionelle + Centered as Group Centreret som gruppe + ✓ None ✓ Ingen + Change to Level 6 Skift til niveau 6 + Change to Level 7 Skift til niveau 7 + Change to Level 8 Skift til niveau 8 + Change to Level 9 Skift til niveau 9 + Level 4 Niveau 4 + Level 5 Niveau 5 + Level 6 Niveau 6 + Level 7 Niveau 7 + Level 8 Niveau 8 + Level 9 Niveau 9 + Typing Skrivning + Search Søg + Title Bar Titellinje + In the margin I margenen + No document is open. Create a document or open one first. Der er ikke åbnet noget dokument. Opret et dokument, eller åbn et først. + Only for the selected text Kun for den markerede tekst + Delete Bookmark Slet bogmærke + Hidden bookmarks Skjulte bogmærker + Sort by: Sortér efter: + {language} (no dictionary) {language} (ingen ordbog) + No spelling dictionary for {language} is installed, so it isn't checked. WordCraft looks for the Hunspell dictionary {name} in the WORDCRAFT_DICTIONARIES folder and in the usual system locations. Der er ikke installeret nogen staveordbog til {language}, så den bliver ikke kontrolleret. WordCraft leder efter Hunspell-ordbogen {name} i mappen WORDCRAFT_DICTIONARIES og på de sædvanlige systemplaceringer. + Narrow {size} all Smal {size} på alle sider + Normal {size} all Normal {size} på alle sider + Type Character Code Angiv tegnkode + Auto ({percent}%) Auto ({percent}%) + Makes the ribbon, menus and dialogs larger or smaller. Auto follows your system's display scaling; 100% is one screen pixel per point. Document zoom is separate. Gør båndet, menuerne og dialogboksene større eller mindre. Auto følger systemets skærmskalering; 100% er én skærmpixel pr. punkt. Dokumentzoom er separat. + Croatian Kroatisk + Show alignment guides when moving pictures and shapes Vis justeringslinjer, når billeder og figurer flyttes + Moved objects snap to the page's edges and centre, the margins and other objects. Hold Alt to move freely. Flyttede objekter fastgøres til sidens kanter og midten, margenerne og andre objekter. Hold Alt nede for at flytte frit. + Word 2003 XML Document (*.xml) Word 2003 XML-dokument (*.xml) + Load System Installed Fonts… Indlæs systeminstallerede skrifttyper… + All system fonts are already available. Alle systemskrifttyper er allerede tilgængelige. + Local font access is not supported by your browser. Din browser understøtter ikke adgang til lokale skrifttyper. + Local font access was denied. Adgang til lokale skrifttyper blev nægtet. + Loaded {count} system fonts. Der blev indlæst {count} systemskrifttyper. + Odd Banded Rows Ulige stribede rækker + Even Banded Rows Lige stribede rækker + Odd Banded Columns Ulige stribede kolonner + Even Banded Columns Lige stribede kolonner + Top Left Cell Øverste venstre celle + Top Right Cell Øverste højre celle + Bottom Left Cell Nederste venstre celle + Bottom Right Cell Nederste højre celle + Inside Horizontal Border Vandret indre kant + Inside Vertical Border Lodret indre kant + Text of the nearest paragraph with a given style (running headers) Tekst fra det nærmeste afsnit med en bestemt typografi (løbende sidehoveder) + Style name: Typografinavn: + Filter Recipients Filtrér modtagere + Sort Recipients Sortér modtagere + Filter and Sort Filtrér og sortér + And Og + Or Eller + Comparison Sammenligning + Compare to Sammenlign med + Add Condition Tilføj betingelse + Remove condition Fjern betingelse + Sort by Sortér efter + Then by Derefter efter + Ascending Stigende + Descending Faldende + Equal to Er lig med + Not equal to Er ikke lig med + Less than Mindre end + Greater than Større end + Less than or equal Mindre end eller lig med + Greater than or equal Større end eller lig med + Is blank Er tom + Is not blank Er ikke tom + Contains Indeholder + Does not contain Indeholder ikke + {count} of {total} recipients will be merged. {count} af {total} modtagere bliver flettet. + Take the last match on the page Tag den sidste forekomst på siden + Show its list number Vis dets listenummer + Show its number relative to this field Vis dets nummer i forhold til dette felt + Show its number with all higher levels Vis dets nummer med alle overordnede niveauer + Number only, without the label's words Kun nummeret, uden etikettens ord + Add whether it is above or below Tilføj, om det er ovenfor eller nedenunder + Copy to Kopiér til + Rename Document Omdøb dokument + Document name Dokumentnavn + Rename Omdøb + Account Konto + Privacy Options Indstillinger for beskyttelse af personlige oplysninger + Trust Center Center for sikkerhed og beskyttelse + Privacy for this document Beskyttelse af personlige oplysninger for dette dokument + Remove my name from comments, changes and file properties when saving Fjern mit navn fra kommentarer, ændringer og filegenskaber, når der gemmes + Leave out the date and time of comments and tracked changes when saving Udelad dato og klokkeslæt for kommentarer og registrerede ændringer, når der gemmes + The document you are editing keeps the names and dates; only the saved file leaves them out. Det dokument, du redigerer, bevarer navnene og datoerne; det er kun den gemte fil, der udelader dem. + Privacy Settings Privatlivsindstillinger + Waiting for the save to finish… Venter på, at gemningen er færdig… + Saving {name} was cancelled, so it is still open and nothing was lost. Gemning af {name} blev annulleret, så dokumentet er stadig åbent, og intet er gået tabt. + Paper Papir + Paper size: Papirstørrelse: + Custom size Brugerdefineret størrelse + More Paper Sizes… Flere papirstørrelser… + The margins don't fit on a page this size; make the margins smaller first. Margenerne passer ikke på en side i denne størrelse; gør margenerne mindre først. + Full Screen Fuld skærm + Full\nScreen Fuld\nskærm + Use the browser's full screen (F11) Brug browserens fuldskærm (F11) + {name} {height} × {width} in {name} {height} × {width} tommer + {name} {height} × {width} mm {name} {height} × {width} mm + {width} × {height} mm {width} × {height} mm + {kind}, {columns} × {rows}, {size} ({paper}) {kind}, {columns} × {rows}, {size} ({paper}) + Researcher needs an online service; WordCraft keeps your documents offline. Researcher kræver en onlinetjeneste; WordCraft holder dine dokumenter offline. + These settings will be used for new documents. Disse indstillinger bruges til nye dokumenter. + can't split at the first row kan ikke opdele ved den første række + select two or more cells to merge markér to eller flere celler, der skal flettes + there is no ink to replay der er intet blæk at afspille igen + there's no citation after the caret der er intet citat efter markøren + this item is already at the top level dette element er allerede på øverste niveau + Envelope #10 Konvolut #10 + Envelope DL Konvolut DL + Reading Mode Læsetilstand + Column width Kolonnebredde + Text size Tekststørrelse + Edit Document Rediger dokument + Go to what: Gå til hvad: + Type +2 or -1 to move from the page you are on. Skriv +2 eller -1 for at flytte dig fra den side, du er på. + Suggested Foreslået + Set Proofing Language… Angiv korrektursprog… + Language Preferences… Sprogpræferencer… + Printer Printer + Select a printer Vælg en printer + Refresh printers Opdater printere + Two-sided printing Tosidet udskrivning + Printer default Printerstandard + One-sided Ensidet + Two-sided: long edge Tosidet: langsiden + Two-sided: short edge Tosidet: tværsiden + Grayscale Gråtoner + Finding printers… Søger efter printere… + No printers found. Configure a system printer or export a PDF. Der blev ikke fundet nogen printere. Konfigurér en systemprinter, eller eksportér en PDF. + Printer discovery failed. Søgning efter printere mislykkedes. + Submitting print job… Sender udskriftsjob… + Print job submitted. Udskriftsjob sendt. + Print job submitted: {receipt} Udskriftsjob sendt: {receipt} + Print failed: the printing worker stopped. Udskrivning mislykkedes: udskriftsprocessen stoppede. + Print settings Udskriftsindstillinger + Submit print job Send udskriftsjob + For which item: For hvilket element: + Heading Overskrift + Heading text Overskriftstekst + Insert reference to: Indsæt reference til: + Reference type: Referencetype: + Dictionaries Ordbøger + Dictionaries folder: Ordbogsmappe: + Not set: the system's usual places Ikke angivet: systemets sædvanlige placeringer + Hunspell .aff and .dic files here (pl_PL, pt_BR, it_IT, hr_HR, tr_TR) are used before the system's. Hunspell .aff- og .dic-filer her (pl_PL, pt_BR, it_IT, hr_HR, tr_TR) bruges før systemets. + {language}: {file} {language}: {file} + {language}: no dictionary found {language}: ingen ordbog fundet + Recover Gendan + Mirror margins Spejlede margener + Existing File or Web Page Eksisterende fil eller webside + Link to: Link til: + Place in This Document Sted i dette dokument + Column {n} Kolonne {n} + Header row Overskriftsrække + No header row Ingen overskriftsrække + My list has Min liste har + Formula: Formel: + Paste function: Indsæt funktion: + Narrow Smal + Medium Mellem diff --git a/crates/ui-egui/src/i18n/mod.rs b/crates/ui-egui/src/i18n/mod.rs index 092e376844..528241ba69 100644 --- a/crates/ui-egui/src/i18n/mod.rs +++ b/crates/ui-egui/src/i18n/mod.rs @@ -52,7 +52,7 @@ pub struct LangInfo { } /// The registry. English first: it is the fallback and the source language. -pub static LANGUAGES: [LangInfo; 23] = [ +pub static LANGUAGES: [LangInfo; 25] = [ LangInfo { code: "en", name: "English", source: "", prefer_hans: false, editing_lang: None, catalog: OnceLock::new() }, // Simplified Chinese; `zh`, `zh-CN`, `zh-SG` and `zh-Hans-*` resolve here (see `candidates`). LangInfo { @@ -164,7 +164,10 @@ pub static LANGUAGES: [LangInfo; 23] = [ // Swedish; sv, sv-SE, sv-FI and sv_SE.UTF-8 resolve here. LangInfo { code: "sv", name: "Svenska", source: include_str!("sv.tsv"), prefer_hans: false, editing_lang: None, catalog: OnceLock::new() }, // Croatian; `hr`, `hr-HR` and `hr_HR.UTF-8` resolve here. - LangInfo { code: "hr", name: "Hrvatski", source: include_str!("hr.tsv"), prefer_hans: false, editing_lang: None, catalog: OnceLock::new() }, + LangInfo { code: "hr", name: "Hrvatski", source: include_str!("hr.tsv"), prefer_hans: false, editing_lang: None, catalog: OnceLock::new() }, // Dutch; `nl`, `nl-NL`, `nl-BE` and `nl_NL.UTF-8` resolve here. + LangInfo { code: "nl", name: "Nederlands", source: include_str!("nl.tsv"), prefer_hans: false, editing_lang: None, catalog: OnceLock::new() }, + // Danish; `da`, `da-DK` and `da_DK.UTF-8` resolve here. + LangInfo { code: "da", name: "Dansk", source: include_str!("da.tsv"), prefer_hans: false, editing_lang: None, catalog: OnceLock::new() }, ]; impl LangInfo { diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv new file mode 100644 index 0000000000..0619a1f641 --- /dev/null +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -0,0 +1,2592 @@ +# Dutch (nl) interface catalog. Columns, separated by a TAB: +# context English source translation +# escapes \n \t \\ inside a column. {{name}} placeholders must appear in both +# columns, and a trailing … in one needs one in the other. +# Lines starting with # and blank lines are ignored. A missing entry shows in English. +# +# Clean-room: written from the meaning of WordCraft's English labels in standard Dutch. + (no suggestions) (geen suggesties) + 1.5 Line Spacing Regelafstand 1,5 + A free, open-source word processor written from scratch in Rust.\nPart of the Crafting Apps from the ArtCraft team. Een gratis, open source-tekstverwerker die volledig van scratch in Rust is geschreven.\nOnderdeel van de Crafting Apps van het team van ArtCraft. + About Over + About WordCraft Over WordCraft + Accept Accepteren + Accept All Changes Alle wijzigingen accepteren + Accept This Change Deze wijziging accepteren + Accessibility Toegankelijkheid + Add Toevoegen + Add Space Before Paragraph Ruimte vóór alinea toevoegen + Add Text Tekst toevoegen + Add a Table Een tabel toevoegen + Add a comment Een opmerking toevoegen + Add to Dictionary Toevoegen aan woordenboek + Add to Style Gallery Toevoegen aan stijlgalerij + Address Block Adresblok + Address: Adres: + Address\nBlock Adres\nblok + Adjust Aanpassen + Advanced Geavanceerd + Advanced Find Geavanceerd zoeken + Advanced Find… Geavanceerd zoeken… + After: Na: + Agents Agents + Align Uitlijnen + Align Left Links uitlijnen + Align Right Rechts uitlijnen + Alignment Uitlijning + All Borders Alle randen + All Caps Alle hoofdletters + All Markup Alle markeringen + All caps Alle hoofdletters + Alt Text Alt-tekst + Apply Character Style Tekenstijl toepassen + Apply Style Stijl toepassen + Arrange Schikken + Arrange\nAll Alles\nschikken + Arrow Pijl + Artistic Effects Artistieke effecten + Author Auteur + AutoCorrect Options AutoCorrect-opties + AutoFit Automatisch aanpassen + AutoFit Contents Inhoud automatisch aanpassen + AutoFit Window Venster automatisch aanpassen + AutoSave AutoSave + AutoSave saves every change to the file (needs a saved document) AutoSave slaat elke wijziging op in het bestand (vereist een opgeslagen document) + AutoText AutoTekst + Automatic Automatisch + Automatic ({language}) Automatisch ({language}) + Automatic Table 1 (3 levels) Automatische tabel 1 (3 niveaus) + Automatic Table 2 (2 levels) Automatische tabel 2 (2 niveaus) + Automatic follows your system's language. Menus and commands change; your documents don't. Automatisch volgt de taal van je systeem. Menu's en opdrachten veranderen; je documenten niet. + Backspace Backspace + Banded Columns Bandkolommen + Banded Rows Bandrijen + Before: Voor: + Bibliography Bibliografie + Binary assets added Binaire assets toegevoegd + Binary assets removed Binaire assets verwijderd + Blank Leeg + Blank (Three Columns) Leeg (drie kolommen) + Blank Page Lege pagina + Blank document Leeg document + Block\nAuthors Auteurs\nblokkeren + Bold Vet + Bookmark Bladwijzer + Bookmark name: Bladwijzernaam: + Border Painter Randpenseel + Border\nPainter Rand\npenseel + Borders Randen + Bottom Border Onderrand + Bottom Margin Ondermarge + Bottom of Page Onderaan de pagina + Bottom: Onderaan: + Box Kader + Breaks Einden + Bring Forward Naar voren brengen + Bullet Library Opsommingstekensbibliotheek + Bullets Opsommingstekens + Calendar Kalender + Cancel Annuleren + Capitalize Each Word Eerste letter van elk woord als hoofdletter + Captions Bijschriften + Cell Margins Cellmarges + Cell Size Celgrootte + Cell\nMargins Cel\nmarges + Center Centreren + Change Wijzigen + Change Case Letterkast wijzigen + Change List Level Lijstniveau wijzigen + Change Picture Afbeelding wijzigen + Change Shape Vorm wijzigen + Changes Wijzigingen + Character Position Tekenpositie + Character Scale Tekenschaal + Character Shading Tekenachtergrond + Character Spacing Tekenafstand + Characters Tekens + Characters (no spaces) Tekens (zonder spaties) + Characters (with spaces) Tekens (met spaties) + Chart Grafiek + Check Accessibility Toegankelijkheid controleren + Check Compatibility Compatibiliteit controleren + Check Spelling as You Type Spelling controleren tijdens het typen + Check for Errors Controleren op fouten + Check\nAccessibility Toegankelijkheid\ncontroleren + Citation Style Citaatstijl + Citations & Bibliography Citaten en bibliografie + Clear Wissen + Clear All Alles wissen + Clear All Formatting Alle opmaak wissen + Clear Character Formatting Tekenopmaak wissen + Clear Character Style Tekenstijl wissen + Clear Formatting Level Opmaakniveau wissen + Clear Paragraph Formatting Alineaopmaak wissen + Clipboard Klembord + Close Sluiten + Close Header and Footer Koptekst en voettekst sluiten + Collapse Selection Selectie inklappen + Color Kleur + Color: Kleur: + Colors Kleuren + Column Kolom + Column Break Kolomeinde + Columns Kolommen + Combine Samenvoegen + Commands Opdrachten + Comment Opmerking + Comments Opmerkingen + Comments Pane Opmerkingenpaneel + Community\nDiscord Discord-\ncommunity + Compact Compact + Compare Vergelijken + Compress Pictures Afbeeldingen comprimeren + Continuous Doorlopend + Convert Converteren + Convert Text to Table Tekst naar tabel converteren + Convert Text to Table… Tekst naar tabel converteren… + Convert to Text Naar tekst converteren + Copy Kopiëren + Copy Formatting Opmaak kopiëren + Corrections Correcties + Count words in text boxes and notes Woorden tellen in tekstvakken en notities + Cover Page Titelpagina + Create Maken + Create New Style Nieuwe stijl maken + Create a Style Een stijl maken + Create an interactive outline of your document.\n\nIt's a great way to keep track of where you are or quickly move your content around.\n\nTo get started, go to the Home tab and apply Heading styles to the headings in your document. Maak een interactieve inhoudsopgave van je document.\n\nZo houd je bij waar je bent en kun je je inhoud snel herordenen.\n\nGa om te beginnen naar het tabblad Start en pas Kop-stijlen toe op de koppen in je document. + Crop Bijsnijden + Cross-reference Kruisverwijzing + Current Position Huidige positie + Current style: {style} Huidige stijl: {style} + Custom Margins… Aangepaste marges… + Custom Watermark Aangepast watermerk + Custom Watermark… Aangepast watermerk… + Customize Quick Access Toolbar Snelle toegangstoolbalk aanpassen + Cut Knippen + Dark Mode Donkere modus + Dashed Gestreept + Data Gegevens + Date & Time Datum en tijd + Decrease Font Size Lettergrootte verkleinen + Decrease Indent Inspringen verkleinen + Default Standaard + Define New Bullet Nieuw opsommingsteken definiëren + Define New Number Format Nieuw nummerformaat definiëren + Delete Verwijderen + Delete Cells Cellen verwijderen + Delete Columns Kolommen verwijderen + Delete Comment Opmerking verwijderen + Delete Next Word Volgend woord verwijderen + Delete Previous Word Vorig woord verwijderen + Delete Rows Rijen verwijderen + Delete Style Stijl verwijderen + Delete Table Tabel verwijderen + Design Ontwerp + Diagonal Diagonaal + Diamond Ruit + Dictate Dicteren + Different First Page Eerste pagina anders + Different Odd & Even Pages Oneven en even pagina's anders + Direct formatting Directe opmaak + Discord Discord + Discord community Discord-community + Display Weergave + Display for Review Weergave voor revisies + Distribute Columns Kolommen gelijkmatig verdelen + Distribute Rows Rijen gelijkmatig verdelen + Distributed Uitgevuld + Do Not Show in TOC Niet weergeven in inhoudsopgave + Document End Einde document + Document Formatting Documentopmaak + Document Property Documenteigenschap + Document Start Begin document + Document Text Documenttekst + Document Title Documenttitel + Documents you open will show up here. Documenten die je opent verschijnen hier. + Dot-dash Stippelstreep + Dotted Gestippeld + Double Dubbel + Double Spacing Dubbele regelafstand + Double Strikethrough Dubbel doorhalen + Double Underline Dubbel onderstrepen + Down Omlaag + Draft Concept + Draw Tekenen + Drawing Tools Tekengereedschap + Drawing\nCanvas Teken\ncanvas + Drop Cap Verlaagd begin + Drops all formatting and pastes plain text. Verwijdert alle opmaak en plakt platte tekst. + Edit Comment Opmerking bewerken + Edit Footer Voettekst bewerken + Edit Header Koptekst bewerken + Edit Recipient List Ontvangerslijst bewerken + Edit\nRecipient List Ontvangers\nlijst bewerken + Edited Bewerkt + Editing Bewerken + Editing header/footer Koptekst/voettekst bewerken + Editing — edit the document directly Bewerken — werk rechtstreeks in het document + Editor Editor + Effects Effecten + Emboss Reliëf + Encrypt with Password Versleutelen met wachtwoord + English (United States) Engels (Verenigde Staten) + Engrave Graveer + Enter page number: Voer paginanummer in: + Envelopes Enveloppen + Eraser Gum + Even Page Even pagina + Every command is available to scripts and AI agents: run `wordcraft-cli mcp` for an MCP server, or start the app with `--control ` for the JSON control channel. Elke opdracht is beschikbaar voor scripts en AI-agents: voer `wordcraft-cli mcp` uit voor een MCP-server, of start de app met `--control ` voor het JSON-besturingskanaal. + Export Exporteren + Export PDF PDF exporteren + Export Page as PNG Pagina exporteren als PNG + Export a copy to share (PDF, Word document) Een kopie exporteren om te delen (PDF, Word-document) + Exported {path} {path} geëxporteerd + Extend Selection Selectie uitbreiden + Field Veld + File Bestand + Find Zoeken + Find Next Volgende zoeken + Find Previous Vorige zoeken + Find Recipient Ontvanger zoeken + Find and Replace Zoeken en vervangen + Find what: Zoeken naar: + Finish Voltooien + Finish & Merge Voltooien en samenvoegen + Finish &\nMerge Voltooien en\nsamenvoegen + First Column Eerste kolom + First commit Eerste commit + First line: Eerste regel: + Fixed Column Width Vaste kolombreedte + Focus Focus + Font Lettertype + Font Color Tekstkleur + Font Dialog Lettertypedialoog + Font Size Lettergrootte + Font color: Tekstkleur: + Font: Lettertype: + Fonts Lettertypen + Footer Voettekst + Footnote and Endnote Voetnoot en eindnoot + Footnotes Voetnoten + Format Opmaak + Format Page Numbers Paginanummers opmaken + Format Page Numbers… Paginanummers opmaken… + Format Painter Opmaakkwast + Formatted Text Opgemaakte tekst + Formatting at Selection Opmaak bij selectie + Formula Formule + General Algemeen + Go To Ga naar + Go To (⌘⌥G) Ga naar (⌘⌥G) + Go To… Ga naar… + Go to Ga naar + Grab bag Allerlei + Greeting Line Begeleidingsregel + Greeting\nLine Begeleidings-\nregel + Gridlines Rasterlijnen + Group Groeperen + Grow Font 1 Point Lettertype 1 punt vergroten + HTML Format HTML-opmaak + Hanging Indent Hangende inspringing + Header Koptekst + Header & Footer Koptekst en voettekst + Header Row Kopregel + Header/Footer Position Positie koptekst/voettekst + Heading 1 Kop 1 + Heading 2 Kop 2 + Heading 3 Kop 3 + Headings Koppen + Heart Hart + Help Help + Hidden Verborgen + Highlight Merge Fields Samenvoegvelden markeren + Highlight\nMerge Fields Samenvoegvelden\nmarkeren + Highlighter Markeerstift + Home Start + Horizontal Horizontaal + Horizontal Line Horizontale lijn + Hyphenation Woordafbreking + Icons Pictogrammen + Ignore All Alles negeren + Illustrations Illustraties + Immersive Meeslepend + Immersive Reader Meeslepende lezer + Immersive\nReader Meeslepende\nlezer + Increase Font Size Lettergrootte vergroten + Increase Indent Inspringen vergroten + Indent Inspringen + Indentation Inspringen + Indents Inspringen + Index Index + Info Info + Ink to\nMath Inkt naar\nvergelijking + Ink to\nShape Inkt naar\nvorm + Ink\nReplay Inkt\nafspelen + Insert Invoegen + Insert Above Boven invoegen + Insert Below Onder invoegen + Insert Caption Bijschrift invoegen + Insert Caption… Bijschrift invoegen… + Insert Citation Citaat invoegen + Insert Endnote Eindnoot invoegen + Insert Footnote Voetnoot invoegen + Insert Hyperlink Hyperlink invoegen + Insert Index Index invoegen + Insert Left Links invoegen + Insert Merge Field Samenvoegveld invoegen + Insert Merge\nField Samenvoegveld\ninvoegen + Insert Right Rechts invoegen + Insert Shapes Vormen invoegen + Insert Table Tabel invoegen + Insert Table of Authorities Tabel met juridische bronnen invoegen + Insert Table of Figures Afbeeldingentabel invoegen + Insert Table… Tabel invoegen… + Insert\nAbove Boven\ningevoegen + Insert\nCaption Bijschrift\ningevoegen + Insert\nCitation Citaat\ningevoegen + Insert\nFootnote Voetnoot\ningevoegen + Inside Borders Binnenranden + Inspect Document Document controleren + Inspect Styles at Selection Stijlen bij selectie controleren + Interface language: Interfacetaal: + Italic Cursief + Join the ArtCraft community on Discord Sluit je aan bij de ArtCraft-community op Discord + Join us on Discord: discord.gg/artcraft Volg ons op Discord: discord.gg/artcraft + Justify Uitvullen + Keep Lines Together Regels bij elkaar houden + Keep Text Only Alleen tekst behouden + Keep lines together Regels bij elkaar houden + Keep with Next Bij volgende houden + Keep with next Bij volgende houden + Keeps the fonts and formatting of the copied text. Behoudt de lettertypen en opmaak van de gekopieerde tekst. + Labels Labels + Landscape Liggend + Language Taal + Lasso Lasso + Last Column Laatste kolom + Last commit Laatste commit + Layout Lay-out + Layout Summary Lay-outoverzicht + Left Links + Left Border Linkerrand + Left Margin Linkermarge + Left: Links: + Letter Letter + Line Lijn + Line Break Regeleinde + Line End Einde van regel + Line Numbers Regelnummers + Line Spacing Regelafstand + Line Spacing Options… Regelafstandopties… + Line Start Begin van regel + Line and Page Breaks Regel- en pagina-einden + Line and Paragraph Spacing Regel- en alineaafstand + Line delta Regeldelta + Line spacing: Regelafstand: + Lines Regels + Lines added Regels toegevoegd + Lines deleted Regels verwijderd + Link Koppeling + Link to Previous Koppelen aan vorige + Links Koppelingen + Location: {path} Locatie: {path} + MIT OR Apache-2.0. Copyright (c) 2026 ArtCraft Team and the WordCraft contributors. MIT OR Apache-2.0. Copyright (c) 2026 ArtCraft Team en de WordCraft-bijdragers. + Macros Macro's + Mailings Mailings + Manage Sources Bronnen beheren + Many pages Veel pagina's + Margins Marges + Mark Citation Citaat markeren + Mark Entry Item markeren + Mark\nCitation Citaat\nmarkeren + Mark\nEntry Item\nmarkeren + Markdown (*.md) Markdown (*.md) + Match Fields Velden laten overeenkomen + Match case Hoofdlettergevoelig + Matrix Matrix + Merge Samenvoegen + Merge Cells Cellen samenvoegen + Merge Formatting Opmaak samenvoegen + Merged PRs Samengevoegde PR's + Mirrored Gespiegeld + Moderate Gemiddeld + Modify Style Stijl wijzigen + Modify… Wijzigen… + More templates → Meer sjablonen → + Move or Resize Verplaatsen of formaat wijzigen + Multilevel List Lijst met meerdere niveaus + Multiple Pages Meerdere pagina's + Name (A–Z) Naam (A–Z) + Name: Naam: + Navigation Navigatie + Navigation Pane Navigatievenster + New Nieuw + New Comment Nieuwe opmerking + New Paragraph Nieuwe alinea + New Style… Nieuwe stijl… + New Window Nieuw venster + New from Template Nieuw vanuit sjabloon + New\nComment Nieuwe\nopmerking + New\nWindow Nieuw\nvenster + Next Volgende + Next Change Volgende wijziging + Next Comment Volgende opmerking + Next Footnote Volgende voetnoot + Next Page Volgende pagina + Next Record Volgend record + Next Section Volgende sectie + No Border Geen rand + No Color Geen kleur + No Markup Geen markeringen + No Paragraph Space Geen alineaafstand + No contributor data was built into this copy. Er zijn geen gegevens over bijdragers opgenomen in deze build. + No model credits were built into this copy. Er zijn geen modelvermeldingen opgenomen in deze build. + No templates match your search. Er zijn geen sjablonen die overeenkomen met je zoekopdracht. + Nonbreaking Hyphen Niet-afbreekbaar koppelteken + Nonbreaking Space Niet-afbreekbare spatie + None Geen + Normal Style Normale stijl + Not available right now Nu niet beschikbaar + Not saved Niet opgeslagen + Not saved yet Nog niet opgeslagen + Nudge Duwen + Number of columns: Aantal kolommen: + Number of rows: Aantal rijen: + Numbering Nummering + OK OK + Object Object + Odd Page Oneven pagina + Office 2003 Default Standaard Office 2003 + One Eén + One Page Eén pagina + Open Openen + OpenDocument Text (*.odt) OpenDocument-tekst (*.odt) + Optional Hyphen Optioneel afbreekstreepje + Options Opties + Orientation Afdrukstand + Outline Contour + Outline Level Contourniveau + Outside Borders Buitenranden + Oval Ovaal + PDF document (*.pdf) PDF-document (*.pdf) + Page Pagina + Page Background Pagina-achtergrond + Page Borders Paginaranden + Page Break Pagina-einde + Page Break Before Pagina-einde voor + Page Breaks Pagina-einden + Page Color Pagina-kleur + Page Down Pagina omlaag + Page Movement Paginabeweging + Page Number Paginanummer + Page Setup Pagina-instelling + Page Up Pagina omhoog + Page Width Breedte van pagina + Page X of Y Pagina X van Y + Page break before Pagina-einde voor + Page image (*.png) Pagina-afbeelding (*.png) + Page width Breedte van pagina + Page {page} Pagina {page} + Page {page} of {pages} Pagina {page} van {pages} + Page\nBorders Pagina-\nranden + Page\nColor Pagina-\nkleur + Page\nNumber Pagina-\nnummer + Pages Pagina's + Paragraph Alinea + Paragraph Details Alinedetails + Paragraph Down Alinea omlaag + Paragraph Settings Alinea-instellingen + Paragraph Spacing Alineaafstand + Paragraph Up Alinea omhoog + Paragraphs Alinea's + Paste Plakken + Paste (⌘V) Plakken (⌘V) + Paste Formatting Opmaak plakken + Paste Special Plakken speciaal + Paste Special… Plakken speciaal… + Paste as: Plakken als: + Paste: Keep Text Only Plakken: alleen tekst behouden + Paste: Merge Formatting Plakken: opmaak samenvoegen + Pen Pen + Pencil Potlood + Picture Border Afbeeldingsrand + Picture Format Afbeeldingsopmaak + Picture Styles Afbeeldingsstijlen + Pictures Afbeeldingen + Plain text (*.txt) Platte tekst (*.txt) + Portrait Staand + Position Positie + Preview Results Resultaten bekijken + Preview\nResults Resultaten\nbekijken + Previous Vorige + Previous Change Vorige wijziging + Previous Comment Vorige opmerking + Previous Record Vorig record + Previous Section Vorige sectie + Print Afdrukken + Print Layout Afdruklay-out + Print\nLayout Afdruk-\nlay-out + Proofing Controleren + Proofing Issues Controleproblemen + Properties Eigenschappen + Protect Beveiligen + Protect Document Document beveiligen + Quick Access Toolbar Sneltoegangslijst + Quick Parts Sneldelen + Quick Tables Sneltabellen + Read Aloud Hardop lezen + Read Mode Leesmodus + Read\nAloud Hardop\nlezen + Read\nMode Lees-\nmodus + Reads the clipboard's rich text and keeps its formatting. Leest de tekst met opmaak van het klembord en behoudt de opmaak. + Reads the clipboard's web content and keeps its formatting. Leest de webinhoud van het klembord en behoudt de opmaak. + Picture Afbeelding + Pastes the clipboard's picture. Plakt de afbeelding van het klembord. + Reading Highlight Leesmarkering + Recent Recent + Record Macro Macro opnemen + Recover Unsaved Documents Niet-opgeslagen documenten herstellen + Rectangle Rechthoek + Redo Opnieuw + Reduce Hanging Indent Hangende inspringing verkleinen + References Verwijzingen + Reject Weigeren + Reject All Changes Alle wijzigingen weigeren + Reject Change Wijziging weigeren + Relaxed Ontspannen + Remove Background Achtergrond verwijderen + Remove Footer Voettekst verwijderen + Remove Header Koptekst verwijderen + Remove Hyperlink Hyperlink verwijderen + Remove Space After Paragraph Ruimte na alinea verwijderen + Remove Table of Contents Inhoudsopgave verwijderen + Remove Watermark Watermerk verwijderen + Reopen Opnieuw openen + Repeat Herhalen + Repeat Header Rows Koprijen herhalen + Replace Vervangen + Replace All Alles vervangen + Replace Document Text Documenttekst vervangen + Replace with: Vervangen door: + Replay Opnieuw afspelen + Reply Beantwoorden + Report Rapport + Research Onderzoek + Researcher Onderzoeker + Reset Picture Afbeelding opnieuw instellen + Reset to Normal Herstellen naar Normaal + Resolve Oplossen + Resolved Opgelost + Restart Each Page Op elke pagina opnieuw beginnen + Restart Each Section Op elke sectie opnieuw beginnen + Restart at 1 Beginnen bij 1 + Restrict Editing Bewerking beperken + Restrict\nEditing Bewerking\nbeperken + Result Resultaat + Results Resultaten + Reverse the order Volgorde omkeren + Reviewing Controleren + Reviewing Pane Controlevenster + Reviewing — edits become suggestions Controleren — wijzigingen worden suggesties + Rich Text Format (*.rtf) Rich Text Format (*.rtf) + Rich Text Format (RTF) Rich Text Format (RTF) + Right Rechts + Right Border Rechterrand + Right Margin Rechter marge + Right-to-Left Text Direction Tekstrichting rechts naar links + Left-to-Right Text Direction Tekstrichting links naar rechts + Right: Rechts: + Rotate Draaien + Rounded Rectangle Afgeronde rechthoek + Rows & Columns Rijen en kolommen + Ruler Liniaal + Rules Regels + Résumé Cv + Save Opslaan + Save As Opslaan als + Save a copy in another format. Printing goes through a PDF you can print from any viewer. Sla een kopie op in een ander formaat. Afdrukken verloopt via een PDF die je vanuit elke viewer kunt afdrukken. + Save as Template Opslaan als sjabloon + Saved Opgeslagen + Search Commands Opdrachten zoeken + Search commands and help Zoek opdrachten en help + Search document Document doorzoeken + Search for templates Zoeken naar sjablonen + Section Breaks Sectie-einden + Section Properties Sectie-eigenschappen + Sections Secties + Select Selecteren + Select All Alles selecteren + Select Cell Cel selecteren + Select Line Regel selecteren + Select Objects Objecten selecteren + Select Paragraph Alinea selecteren + Select Range Bereik selecteren + Select Recipients Ontvangers selecteren + Select Row Rij selecteren + Select Sentence Zin selecteren + Select Table Tabel selecteren + Select Text Tekst selecteren + Select Text with Similar Formatting Tekst met vergelijkbare opmaak selecteren + Select Word Woord selecteren + Select\nRecipients Ontvangers\nselecteren + Selection Selectie + Selection Pane Selectievenster + Send Backward Naar achteren verplaatsen + Sentence case. Zoals in een zin. + Set Caret Invoegteken plaatsen + Set Character Formatting Tekstopmaak instellen + Set Numbering Value Nummeringswaarde instellen + Set Paragraph Formatting Alineaopmaak instellen + Set as Default Instellen als standaard + Shading Arcering + Shadow Schaduw + Shape Fill Vormopvulling + Shape Format Vormopmaak + Shape Outline Vormrand + Shape Styles Vormstijlen + Shapes Vormen + Share Delen + Shift+Tab Shift+Tab + Show Tonen + Show Comments Opmerkingen tonen + Show Markup Markeringen tonen + Show Notes Notities tonen + Show all formatting marks Alle opmaaktekens tonen + Show rulers Linialen tonen + Show the community button in the title bar De communityknop in de titelbalk tonen + Show/Hide ¶ Tonen/verbergen ¶ + Show\nComments Opmerkingen\ntonen + Shrink Font 1 Point Lettertype 1 punt verkleinen + Side to Side Naast elkaar + Side\nto Side Naast\nelkaar + Signature Line Ondertekeningslijn + Single Enkelvoudig + Single Spacing Enkelvoudige regelafstand + Size Grootte + Size: Grootte: + Small Caps Kleine hoofdletters + Small caps Kleine hoofdletters + SmartArt SmartArt + Sort Sorteren + Source code: github.com/storytold/wordcraft Broncode: github.com/storytold/wordcraft + Space before/after: Ruimte voor/na: + Spacing Afstand + Spacing (pt): Afstand (pt): + Speech Spraak + Spelling & Grammar Spelling en grammatica + Spelling &\nGrammar Spelling en\ngrammatica + Spelling Suggestions Spellingsuggesties + Split Splitsen + Split Cells Cellen splitsen + Split Table Tabel splitsen + Spreadsheet Table Rekenbladtabel + Standard Colors Standaardkleuren + Star Ster + Start Mail Merge Afdrukken en samenvoegen starten + Start Mail\nMerge Afdrukken en\nsamenvoegen starten + Statistics Statistieken + Status Bar Statusbalk + Strikethrough Doorhalen + Studio cover Studiokaft + Studio handbook (sample) Studiohandboek (voorbeeld) + Style Stijl + Style Inspector Stijlcontrole + Style Set Stijlset + Style based on: Stijl op basis van: + Styles Stijlen + Styles Pane Stijlenvenster + Subscript Subscript + Superscript Superscript + Switch Modes Modi wisselen + Switch\nModes Modi\nwisselen + Symbol Symbool + Symbols Symbolen + Synonyms Synoniemen + Tab Tab + Table Tabel + Table Column Width Kolombreedte van tabel + Table Design Tabelontwerp + Table Layout Tabellay-out + Table Properties Tabeleigenschappen + Table Properties… Tabeleigenschappen… + Table Row Height Rijhoogte van tabel + Table Style Options Stijlopties voor tabel + Table Styles Tabelstijlen + Table of Authorities Tabel met juridische bronnen + Table of Contents Inhoudsopgave + Table of\nContents Inhouds-\nopgave + Tables Tabellen + Tabs Tabs + Tabular List Tabellarische lijst + Text Tekst + Text Box Tekstvak + Text Direction Tekstrichting + Text Effects Teksteffecten + Text Effects and Typography Teksteffecten en typografie + Text Highlight Color Tekstmarkeerkleur + Text Wrapping Tekstomloop + Text from File Tekst uit bestand + Text to display: Weer te geven tekst: + Text: Tekst: + Text\nBox Tekst-\nvak + Text\nDirection Tekst-\nrichting + The clipboard is empty. Het klembord is leeg. + The new style takes the formatting of the current paragraph. De nieuwe stijl neemt de opmaak van de huidige alinea over. + Theme Colors Themakleuren + Theme Fonts Themalettertypen + Theme List Themalijst + Themes Thema's + Thesaurus Thesaurus + Thick Dik + Three Drie + Tight Strak + Top Border Bovenrand + Top Margin Bovenmarge + Top of Page Bovenkant van pagina + Top: Boven: + Total Row Totalenrij + Track Changes Wijzigingen bijhouden + Track Changes: On Wijzigingen bijhouden: aan + Tracking Bijhouden + Translate Vertalen + Transparency Transparantie + Triangle Driehoek + Two Twee + Type Text Tekst invoeren + Type a command, e.g. "insert table" Typ een opdracht, bijvoorbeeld „tabel invoegen” + UPPERCASE HOOFDLETTERS + Underline Onderstrepen + Underline Words Only Alleen woorden onderstrepen + Undo Ongedaan maken + Undo {action} Ongedaan maken: {action} + Unformatted Text Onopgemaakte tekst + Up Omhoog + Update Field Veld bijwerken + Update Index Index bijwerken + Update Style to Match Selection Stijl bijwerken op selectie + Update Table Tabel bijwerken + Update Table of Figures Afbeeldingenlijst bijwerken + Update to Match Bijwerken om te laten overeenkomen + Update to Match Selection Bijwerken om te laten overeenkomen met selectie + User Name Gebruikersnaam + User name: Gebruikersnaam: + Version History Versiegeschiedenis + Version {version} ({build}) Versie {version} ({build}) + Vertical Verticaal + Vertical Alignment Verticaal uitlijnen + View Weergave + View Gridlines Rasterlijnen weergeven + View State Weergavestatus + Views Weergaven + Voice Stem + Watermark Watermerk + Wave Golf + Web Layout Web-lay-out + Web\nLayout Web-\nlay-out + Web page (*.html) Webpagina (*.html) + Whole page Hele pagina + Whole words Hele woorden + Wide Breed + Widow/Orphan Control Weduwen- en wezencontrole + Widow/Orphan control Weduwen- en wezencontrole + Wildcards (regex) Jokertekens (regex) + Window Venster + Word Count Woordentelling + Word Left Woord links + Word Right Woord rechts + Word document (*.docx) Word-document (*.docx) + WordArt WordArt + WordCraft WordCraft + Words Woorden + Words only Alleen woorden + Wrap Text Tekstterugloop + Wrap\nText Tekst-\nterugloop + Write & Insert Fields Velden schrijven en invoegen + Write a reply Een reactie schrijven + Zoom Zoom + Zoom In Inzoomen + Zoom Out Uitzoomen + development build ontwikkelbuild + lowercase kleine letters + {group} settings Instellingen voor {group} + {key}+Click to follow link {key}+klik om de koppeling te volgen + {selected} of {words} words {selected} van {words} woorden + {words} words {words} woorden + ➕ New comment ➕ Nieuwe opmerking + 📂 Browse… 📂 Bladeren… + Equation Vergelijking + Review Review + Track\nChanges Wijzigingen\nbijhouden + Track\nChanges ✓ Wijzigingen\nbijhouden ✓ + Scroll ribbon left Lint naar links schuiven + Scroll ribbon right Lint naar rechts schuiven + Apply Styles Stijlen toepassen + Dark page (white text on black) Donkere pagina (witte tekst op zwarte achtergrond) + Show documents with their colours inverted, like View › Switch Modes. Saving, printing and PDFs are unchanged. Documenten met omgekeerde kleuren tonen, zoals Weergave › Modus wisselen. Opslaan, afdrukken en pdf’s blijven ongewijzigd. + Only web (http, https) and email (mailto) links can be followed Alleen webkoppelingen (http, https) en e-mailkoppelingen (mailto) kunnen worden gevolgd + Character Border Tekenrand + Direction: Richting: + Right-to-left Rechts-naar-links + Left-to-right Links-naar-rechts + Zotero Zotero + Citations Citaten + Document Document + Add/Edit\nCitation Toevoegen/bewerken\ncitaat + Add\nNote Notitie\ntoevoegen + Add/Edit\nBibliography Toevoegen/bewerken\nbibliografie + Refresh Vernieuwen + Document\nPreferences Document-\nvoorkeuren + Unlink\nCitations Citaten\nontkoppelen + Waiting for Zotero… Wachten op Zotero… + Zotero: done Zotero: gereed + Zotero: cancelled Zotero: geannuleerd + Yes Ja + No Nee + Move Past Citation Voorbij citaat bewegen + Move Past\nCitation Voorbij\ncitaat bewegen + Next Sentence Volgende zin + Previous Sentence Vorige zin + Next sentence Volgende zin + Previous sentence Vorige zin + Play/Pause Afspelen/pauzeren + Read Aloud Status Status van voorlezen + Reading Speed Leessnelheid + Skip Citations and Bibliography Citaten en bibliografie overslaan + Skip citations & bibliography Citaten en bibliografie overslaan + Stop Reading Voorlezen stoppen + Don't read Zotero, Mendeley or EndNote citations and bibliographies Citaten en bibliografieën van Zotero, Mendeley of EndNote niet voorlezen + Play Afspelen + Pause Pauzeren + Stop Stoppen + Sentence Zin + Paused Gepauzeerd + Finished Voltooid + Stopped Gestopt + Speed Snelheid + Normal speed Normale snelheid + AutoSave documents you have saved in WordCraft AutoSave voor documenten die je in WordCraft hebt opgeslagen + AutoSave failed: {error}. Save the document to turn AutoSave back on. AutoSave is mislukt: {error}. Sla het document op om automatisch opslaan weer in te schakelen. + Couldn't download {name}: {error} Kan {name} niet downloaden: {error} + Do you want to save changes to {name}? Wil je de wijzigingen in {name} opslaan? + Don't Save Niet opslaan + That format doesn't keep everything, so the document is still open. Save it as a Word document (.docx) to go on. Dat formaat bewaart niet alles, dus het document blijft open. Sla het op als Word-document (.docx) om verder te gaan. + Your changes will be lost if you don't save them. Je wijzigingen gaan verloren als je ze niet opslaat. + Change Structure Structuur wijzigen + Change to Display Wijzigen naar weergegeven + Change to Inline Wijzigen naar inline + Conversions Conversies + Delete Back in Equation Terug verwijderen in vergelijking + Delete in Equation Verwijderen in vergelijking + Edit Equation Vergelijking bewerken + Enter in Equation Enter in vergelijking + Equation Gallery Vergelijkingengalerij + Equation Info Vergelijkingsinformatie + Equation Number Vergelijkingsnummer + Input Format Invoerindeling + Insert Structure Structuur invoegen + Insert Symbol Symbool invoegen + Justification Uitvulling + Leave Equation Vergelijking verlaten + Move in Equation Verplaatsen in vergelijking + Normal Text Normale tekst + Set Equation Vergelijking instellen + Structure Actions Structuurbewerkingen + Structures Structuren + Tools Hulpmiddelen + Type in Equation Typen in vergelijking + Inline Inline + Number Getal + Close Equation Vergelijking sluiten + Insert New Equation Nieuwe vergelijking invoegen + Built-In Ingebouwd + Professional Professioneel + Linear Lineair + Fraction Breuk + Script Index + Radical Wortel + Integral Integraal + Large\nOperator Grote\noperator + Bracket Haakje + Function Functie + Accent Accent + Limit and\nLog Limiet en\nlog + Operator Operator + Equation Options Vergelijkingsopties + All symbol sets Alle symboolsets + Send the document straight to the system print dialog, or save a copy in another format below. Stuur het document rechtstreeks naar het systeemvenster voor afdrukken, of sla hieronder een kopie op in een ander formaat. + Or save a copy in another format: Of sla een kopie op in een ander formaat: + Opening print dialog… Afdrukvenster wordt geopend… + Print failed: {error} Afdrukken mislukt: {error} + Couldn't open {path}: {error} Kan {path} niet openen: {error} + Column Selection Kolomselectie + New Table Style Nieuwe tabelstijl + Modify Table Style Tabelstijl wijzigen + New Table Style… Nieuwe tabelstijl… + Modify Table Style… Tabelstijl wijzigen… + Apply formatting to: Opmaak toepassen op: + Whole Table Hele tabel + Fill color: Vulkleur: + {count} items highlighted {count} items gemarkeerd + {n} pt {n} pt + Not {name} Niet {name} + Level {n} Niveau {n} + Body Text Bodytekst + {n} lines {n} regels + At least {n} Minstens {n} + Exactly {n} Precies {n} + Centered Gecentreerd + Character spacing Tekenafstand + Don't add space between paragraphs of the same style Geen ruimte tussen alinea’s van dezelfde stijl toevoegen + Don't hyphenate Geen afbreekstreepjes gebruiken + Double strikethrough Dubbel doorhalen + Drop cap Verlaagd begin + First line Eerste regel + Font color Tekstkleur + Highlight Markeren + Justified Uitvullen + Left indent Linkerinspringing + Line spacing Regelafstand + Outline level Outlineniveau + Raised/lowered Verhoogd/verlaagd + Right indent Rechterinspringing + Scale Schaal + Space after Ruimte na + Space before Ruimte voor + Suppress line numbers Regelnummers onderdrukken + Underline color Onderstreekskleur + Letters Brieven + E-mail Messages E-mailberichten + Directory Map + Normal Word Document Normaal Word-document + If…Then…Else… Als…Dan…Anders… + If…Then…Else Als…Dan…Anders + Merge Record # Samenvoegrecord # + Skip Record If… Record overslaan als… + Skip Record If Record overslaan als + Equals: Is gelijk aan: + Then insert: Dan invoegen: + Otherwise insert: Anders invoegen: + Courtesy Title Titel + First Name Voornaam + Last Name Achternaam + Company Bedrijf + Address Adres + City Plaats + State Provincie + Postal Code Postcode + Country Land + (not matched) (niet gekoppeld) + Leave a recipient out of the merge when a field has this value. Een ontvanger buiten de samenvoeging laten wanneer een veld deze waarde heeft. + Insert one text or another, depending on a field's value. De ene of andere tekst invoegen, afhankelijk van de waarde van een veld. + Select recipients first to match their fields. Selecteer eerst de ontvangers om hun velden te koppelen. + The recipient-list field that fills each part of an address block and greeting line. Het veld uit de ontvangerslijst dat elk deel van een adresblok en begroetingsregel vult. + No errors found: every merge field is in the recipient list. Geen fouten gevonden: elk samenvoegveld staat in de ontvangerslijst. + These merge fields aren't in the recipient list: Deze samenvoegvelden staan niet in de ontvangerslijst: + Add at least one field. Voeg minstens één veld toe. + Add Field Veld toevoegen + Every field needs a name. Elk veld heeft een naam nodig. + Field name Veldnaam + Field names must be different. Veldnamen moeten verschillend zijn. + Fields: Velden: + Field: Veld: + Find: Zoeken: + New Entry Nieuw item + Recipient List Ontvangerslijst + Recipients: {count} Ontvangers: {count} + Remove field Veld verwijderen + Remove recipient Ontvanger verwijderen + Select recipients first to choose from their fields. Selecteer eerst de ontvangers om uit hun velden te kiezen. + Type a New List… Een nieuwe lijst invoeren… + Type at least one recipient. Voer minstens één ontvanger in. + Type the recipients, or load them from a file. Voer de ontvangers in of laad ze uit een bestand. + Use an Existing List… Een bestaande lijst gebruiken… + Interface theme: Interfacethema: + Interface\nTheme Interface\nthema + Interface Theme Interfacethema + Light Licht + Dark Donker + Use system setting Systeeminstelling gebruiken + A file dialog is already open. Er is al een bestandsdialoogvenster geopend. + Height: Hoogte: + Width: Breedte: + Row Rij + Cell Cel + Preferred width: Voorkeursbreedte: + Alignment: Uitlijning: + Indent from left: Inspringen vanaf links: + Specify height: Hoogte opgeven: + Exactly Precies + At least Minimaal + Allow row to break across pages Rij mag over pagina's worden verdeeld + Repeat as header row on each page Telkens opnieuw als koprij op elke pagina + Vertical alignment: Verticale uitlijning: + Top Boven + Bottom Onder + Alt text Alt-tekst + Alt: Alt: + W: B: + H: H: + Reset Herstellen + Reset Crop Bijsnijden herstellen + Insert a new equation (Alt+=) Een nieuwe vergelijking invoegen (Alt+=) + Leave the equation (Esc) Vergelijking verlaten (Esc) + Preview Voorbeeld + getartcraft.com/apps/wordcraft getartcraft.com/apps/wordcraft + English (United Kingdom) Engels (Verenigd Koninkrijk) + Polish Pools + Do Not Check Spelling or Grammar Spelling en grammatica niet controleren + Possible spelling mistake Mogelijke spelfout + Extra space Extra spatie + Capitalize the first word of a sentence Begin een zin met hoofdletter + Remove the space before the punctuation Verwijder de spatie vóór de interpunctie + Use "an" before a vowel sound Gebruik „an” voor een klinkergeluid + Use "a" before a consonant sound Gebruik „a” voor een medeklinkergeluid + The spelling and grammar check is complete. De spelling- en grammaticacontrole is voltooid. + Font… Lettertype… + Paragraph… Alinea… + Link… Verwijzing… + Change Picture… Afbeelding wijzigen… + Size and Crop… Grootte en bijsnijden… + Insert Rows Above Rijen hierboven invoegen + Insert Rows Below Rijen hieronder invoegen + Insert Columns to the Left Kolommen links invoegen + Insert Columns to the Right Kolommen rechts invoegen + (picture or object) (afbeelding of object) + A password is required to open this document. Er is een wachtwoord vereist om dit document te openen. + Add Object to Selection Object toevoegen aan selectie + Add space between Asian and Latin text Spatie toevoegen tussen Aziatische en Latijnse tekst + Add space between Asian text and numbers Spatie toevoegen tussen Aziatische tekst en cijfers + Allow Latin text to wrap in the middle of a word Latijnse tekst midden in een woord laten afbreken + Allow hanging punctuation Hangende interpunctie toestaan + Alphabetical Alfabetisch + Anyone can open this document. Encrypt it with a password to keep it private. Iedereen kan dit document openen. Versleutel het met een wachtwoord om het privé te houden. + Anyone who opens the document will need this password. Iedereen die het document opent, heeft dit wachtwoord nodig. + Apply Asian line-breaking rules (kinsoku) Aziatische regels voor regeleinde toepassen (kinsoku) + As Recommended Zoals aanbevolen + Asian Typography Aziatische typografie + AutoSave isn't available in the browser: it can't write to your files. Save downloads a copy. AutoSave is niet beschikbaar in de browser: die kan niet naar je bestanden schrijven. Bij Opslaan wordt een kopie gedownload. + AutoSave only keeps .docx, .odt and .rtf files; {name} can't keep all formatting. Save as .docx to turn on AutoSave. AutoSave behoudt alleen .docx-, .odt- en .rtf-bestanden; {name} kan niet alle opmaak behouden. Sla op als .docx om AutoSave in te schakelen. + Based on: {style} Op basis van: {style} + Browse… Bladeren… + Built-in style Ingebouwde stijl + Built-in styles can't be deleted. Ingebouwde stijlen kunnen niet worden verwijderd. + Change Password… Wachtwoord wijzigen… + Character style Tekenstijl + Click an item to paste it. Klik op een item om het te plakken. + Clipboard Items Klemborditems + Clipboard Pane Klembordvenster + Color: {color} Kleur: {color} + Compress punctuation at the start of a line Interpunctie aan het begin van een regel samenpersen + Confirm password: Wachtwoord bevestigen: + Delete Clipboard Item Klemborditem verwijderen + Delete Table Style Tabelstijl verwijderen + Encrypt with Password… Versleutelen met wachtwoord… + Font: {font} Lettertype: {font} + Glow Schijnsel + Glow Colors Schijnselkleuren + Hide from style lists Verbergen in stijlenlijsten + Indents and Spacing Inspringen en afstand + Keep the password somewhere safe: without it nobody, WordCraft included, can open the document. Leave both boxes empty to remove the password. Bewaar het wachtwoord op een veilige plek: zonder het kan niemand, ook WordCraft niet, het document openen. Laat beide vakjes leeg om het wachtwoord te verwijderen. + Line breaking Regeleinde + Linked style (paragraph and character) Gekoppelde stijl (alinea en teken) + Manage Styles Stijlen beheren + Manage Styles… Stijlen beheren… + No Glow Geen schijnsel + No Shadow Geen schaduw + No Soft Edges Geen zachte randen + Nothing collected yet. Items you copy or cut appear here. Nog niets verzameld. Items die je kopieert of knipt verschijnen hier. + Offset: Bottom Verschuiving: onder + Offset: Bottom Left Verschuiving: linksonder + Offset: Bottom Right Verschuiving: rechtsonder + Offset: Center Verschuiving: midden + Offset: Left Verschuiving: links + Offset: Right Verschuiving: rechts + Offset: Top Verschuiving: boven + Offset: Top Left Verschuiving: linksboven + Offset: Top Right Verschuiving: rechtsboven + Paragraph style Alineastijl + Password Wachtwoord + Password: Wachtwoord: + Paste All Alles plakken + Paste Clipboard Item Klemborditem plakken + Remove Password Wachtwoord verwijderen + Save the document to turn on AutoSave Sla het document op om AutoSave in te schakelen + Save the document to turn on AutoSave (a file you have only opened isn't rewritten until you save it) Sla het document op om AutoSave in te schakelen (een bestand dat je alleen hebt geopend, wordt pas opnieuw geschreven als je het opslaat) + Shadow, glow and soft edges Schaduw, schijnsel en zachte randen + Shape Effects Vormeffecten + Show in the Styles gallery Weergeven in de stijlengalerij + Show or Hide Style Stijl tonen of verbergen + Soft Edges Zachte randen + Sort order: Sorteervolgorde: + Space after: {pt} pt Ruimte na: {pt} pt + Space before: {pt} pt Ruimte voor: {pt} pt + Table style Tabelstijl + That password isn't right. Check it (passwords are case-sensitive) and try again. Dat wachtwoord is onjuist. Controleer het (wachtwoorden zijn hoofdlettergevoelig) en probeer het opnieuw. + The document will be saved with a password. Save it to protect it. Het document wordt met een wachtwoord opgeslagen. Sla het op om het te beveiligen. + The password is removed. Save the document to keep the change. Het wachtwoord is verwijderd. Sla het document op om de wijziging te behouden. + The passwords don't match. De wachtwoorden komen niet overeen. + Ungroup Groep opheffen + Weight Dikte + {name} is protected with a password. {name} is beveiligd met een wachtwoord. + {size} pt {size} pt + % of all commits % van alle commits + (All) (Alle) + (no style) (geen stijl) + (none) (geen) + (normal text) (normale tekst) + 1/4 Em Space 1/4 emspatie + 3-D 3D + A mail merge field Een samenvoegveld + A numbered sequence (figures, tables…) Een genummerde reeks (figuren, tabellen…) + A table of contents Een inhoudsopgave + AaBbYyZz AaBbYyZz + Add tab stop at: Tabstop toevoegen op: + Align Bottom Center Uitlijnen onder midden + Align Bottom Left Uitlijnen onder links + Align Bottom Right Uitlijnen onder rechts + Align Center Uitlijnen midden + Align Center Left Uitlijnen midden links + Align Center Right Uitlijnen midden rechts + Align Top Center Uitlijnen boven midden + Align Top Left Uitlijnen boven links + Align Top Right Uitlijnen boven rechts + Aligned at: Uitlijnen op: + All Alle + Apply to: Toepassen op: + Atelier Atelier + Balloons Ballonnen + Bar Streep + Between Tussen + Borders and Shading Randen en arcering + Borders and Shading… Randen en arcering… + Bullet Opsommingsteken + By author Op auteur + Categories: Categorieën: + Changed lines: Gewijzigde regels: + Character Teken + Character code: Tekencode: + Click a border between two cells to merge them. Esc stops. Klik op een rand tussen twee cellen om ze samen te voegen. Esc stopt. + Click level to modify: Klik op een niveau om het te wijzigen: + Col #: Kol.: + Color only Alleen kleur + Commits Commits + Copy of the text a bookmark marks Kopie van de tekst die een bladwijzer markeert + Copyright Copyright + Craft Craft + Custom Aangepast + Darkroom Darkroom + Date and Time Datum en tijd + Date formats: Datumnotaties: + Decimal Decimaal + Default tab stops: Standaardtabstops: + Define New Multilevel List Nieuwe meerniveaulijst definiëren + Define New Multilevel List… Nieuwe meerniveaulijst definiëren… + Deletions: Verwijderingen: + Description: Beschrijving: + Display name Weergavenaam + Document Information Documentinformatie + Document1 Document1 + Dot dash Punt-streep + Double Closing Quote Dubbele sluitende aanhalingstekens + Double Opening Quote Dubbele openende aanhalingstekens + Drag a rectangle to draw a table, or a line across cells to split them. Esc stops. Sleep een rechthoek om een tabel te tekenen, of trek een lijn over cellen om ze te splitsen. Esc stopt. + Draw Ink Inkt tekenen + Draw Table Tabel tekenen + Ellipsis Ellips + Em Dash Em-dash + Em Space Emspatie + En Dash En-dash + En Space Enspatie + Enter formatting for number: Opmaak voor nummer opgeven: + Equal column width Gelijke kolombreedte + Erase Ink Inkt wissen + Field Codes Veldcodes + Field code: Veldcode: + Field codes: Veldcodes: + Field name: Veldnaam: + Field names: Veldnamen: + Field properties Veldeigenschappen + Field… Veld… + Fill Vulling + First Eerste + Flip Horizontal Horizontaal spiegelen + Flip Vertical Verticaal spiegelen + Follow number with: Volgnummer met: + Format: Opmaak: + Formatted Opgemaakt + Formatting Opmaak + Gallery Galerij + Hide Codes Codes verbergen + Hide Ink Inkt verbergen + Hide\nInk Verbergen\nInkt + How many pages the whole document has Hoeveel pagina's het hele document heeft + How many pages this section has Hoeveel pagina's deze sectie heeft + How many words the document holds Hoeveel woorden het document bevat + Include level number from: Niveaunummer opnemen vanaf: + Index and Tables Index en tabellen + Ink Inkt + Insertions and Deletions Invoegingen en verwijderingen + Insertions: Invoegingen: + Interface size: Interfacegrootte: + Last Laatste + Leader Opvulteken + Left border Linkerrand + Legal style numbering Juridische nummeringsstijl + Line between Lijn tussen + Lines +/− Regels +/− + Link level to style: Niveau koppelen aan stijl: + Links and References Koppelingen en verwijzingen + List Definition Lijstdefinitie + Mail Merge Afdrukken en samenvoegen + Model Model + Mon Ma + More Columns… Meer kolommen… + More Symbols… Meer symbolen… + Name Naam + Name of the file this document is saved as Naam van het bestand waarin dit document is opgeslagen + No-Width Non Break Niet afbreken zonder breedte + No-Width Optional Break Optioneel afbreken zonder breedte + North Noord + Nothing Niets + Number alignment: Nummeruitlijning: + Number format Getalnotatie + Number of the page the field sits on Nummer van de pagina waarop het veld staat + Number of the section the field sits in Nummer van de sectie waarin het veld staat + Number style for this level: Getalstijl voor dit niveau: + Outside border Buitenrand + PRs PR's + Page Border Paginarand + Page on which a bookmarked passage falls Pagina waarop een bladwijzerpassage valt + Paragraph Mark Alineamarkering + Pens Pennen + Pick a field from the list Kies een veld uit de lijst + Preserve formatting during updates Opmaak behouden tijdens updates + Presets Voorinstellingen + Real name Wettelijke naam + Recently used symbols: Onlangs gebruikte symbolen: + Registered Geregistreerd + Restart list after: Lijst opnieuw starten na: + Right border Rechterrand + Rotate Left 90° 90° linksom draaien + Rotate Right 90° 90° rechtsom draaien + Rotation Rotatie + Rotation: Rotatie: + Section Mark Sectiemarkering + Sequence name: Reeksnaam: + Set Instellen + Setting: Instelling: + Shortcut key: Sneltoets: + Show all revisions inline Alle revisies inline tonen + Show only comments and formatting in balloons Alleen opmerkingen en opmaak in ballonnen tonen + Show revisions in balloons Revisies in ballonnen tonen + Single Closing Quote Enkelvoudig sluitend aanhalingsteken + Single Opening Quote Enkelvoudig openend aanhalingsteken + Sketchbook Schetsboek + South Zuid + Space Spatie + Spacing: Afstand: + Special Characters Speciale tekens + Start at: Starten bij: + Studio Studio + Style: Stijl: + Style: {style} Stijl: {style} + Subset: Deelverzameling: + Sum Som + Tab character Tabteken + Tab stop position: Positie van tabstop: + Tab stops to be cleared: Te wissen tabstops: + Tabs… Tabstops… + Text indent at: Tekstinspringen op: + The date it is today De datum van vandaag + The document has no bookmarks yet. Het document bevat nog geen bladwijzers. + The time right now De huidige tijd + Thickness Dikte + This field has no options. Dit veld heeft geen opties. + This font has no characters to show. Dit lettertype bevat geen tekens om weer te geven. + This point forward Vanaf dit punt + This section Deze sectie + Title from the document properties Titel uit documenteigenschappen + Total Totaal + Track Changes Options Opties voor wijzigingen bijhouden + Track formatting Opmaak bijhouden + Trademark Handelsmerk + Triple Drievoudig + Tue Di + Type the field's name and its switches, for example PAGE \\* roman. Typ de veldnaam en de schakelaars, bijvoorbeeld PAGE \\* roman. + Username Gebruikersnaam + Version Versie + Wed Wo + West West + When this document was first created Wanneer dit document voor het eerst is gemaakt + When this document was most recently printed Wanneer dit document voor het laatst is afgedrukt + When this document was most recently saved Wanneer dit document voor het laatst is opgeslagen + Who the document properties name as author Auteur volgens documenteigenschappen + Whole document Hele document + Width and spacing Breedte en afstand + from Unicode (hex) uit Unicode (hex) + pt pt + {cols}×{rows} Table {cols}×{rows} tabel + {contributors} contributors · {commits} commits {contributors} bijdragers · {commits} commits + Copyright Sign Auteursrechtsteken + Edit Field Code Veldcode bewerken + Edit Field… Veld bewerken… + Insert Date Field Datumveld invoegen + Insert Page Field Paginaveld invoegen + Insert Time Field Tijdveld invoegen + Lock Fields Velden vergrendelen + Next Field Volgend veld + Previous Field Vorig veld + Registered Sign Geregistreerd handelsmerksteken + Reset Character Formatting Tekstopmaak herstellen + Reset Paragraph Formatting Alineaopmaak herstellen + Selected Fields Geselecteerde velden + Toggle Field Codes Veldcodes omschakelen + Trademark Sign Handelsmerksteken + Unlink Fields Velden loskoppelen + Unlock Fields Velden ontgrendelen + All Commands Alle opdrachten + Assign Toewijzen + Choose a command Kies een opdracht + Choose a shortcut Kies een sneltoets + Commands: Opdrachten: + Customize Aanpassen + Customize Keyboard Toetsenbord aanpassen + Customize… Aanpassen… + Demote Verlagen + Draw\nTable Tabel\ntekenen + Go Back Terug + Keyboard shortcuts: Sneltoetsen: + Keys for this command: Toetsen voor deze opdracht: + Move Paragraph Down Alinea omlaag verplaatsen + Move Paragraph Up Alinea omhoog verplaatsen + New shortcut: Nieuwe sneltoets: + Not used by any command Door geen enkele opdracht gebruikt + Now used by {command} Nu in gebruik door {command} + Press a key combination Druk op een toetsencombinatie + Promote Verhogen + Remove Verwijderen + Remove every custom shortcut and restore the built-in keys? Alle eigen sneltoetsen verwijderen en de standaardtoetsen herstellen? + Reset All… Alles herstellen… + Toggle Unicode Character Unicode-teken omschakelen + (Custom) (Aangepast) + << Remove << Verwijderen + Add >> Toevoegen >> + Add to Quick Access Toolbar Toevoegen aan snelkoppelingenlint + Customize Quick Access Toolbar… Snelkoppelingenlint aanpassen… + Customize Ribbon Lint aanpassen + Customize the Ribbon… Het lint aanpassen… + More Commands… Meer opdrachten… + New Group Nieuwe groep + New Tab Nieuw tabblad + Pick a custom group (New Group makes one) Kies een eigen groep (Nieuwe groep maakt er een) + Put the default commands back on the Quick Access Toolbar? De standaardopdrachten weer op het snelkoppelingenlint zetten? + Remove every custom tab and group and show all tabs again? Alle eigen tabbladen en groepen verwijderen en alle tabbladen weer tonen? + Remove from Quick Access Toolbar Verwijderen uit het snelkoppelingenlint + Rename… Naam wijzigen… + Reset… Herstellen… + Ribbon Lint + Ribbon and Quick Access Toolbar: Lint en snelkoppelingenlint: + Ribbon tabs Linttabbladen + Align Text Tekst uitlijnen + Apply Toepassen + Arrows Pijlen + Base text: Basistekst: + Behind Text Achter tekst + Below text Onder tekst + Both Sides Beide zijden + Break Link Link verbreken + Canvas Member Canvasobject + Choose the sheet that holds the recipients. Kies het werkblad met de geadresseerden. + Circle Cirkel + Click the empty text box the text should continue in (Esc cancels) Klik op het lege tekstvak waarin de tekst verdergaat (Esc annuleert) + Communication Communicatie + Continuous numbering Doorlopende nummering + Convert Notes Notities converteren + Create Link Link maken + Custom mark: Eigen markering: + Distribute letters Letters verdelen + Distribute spacing Afstand verdelen + Drawing Canvas Tekencanvas + Enclose Characters Tekens omlijnen + Enclosure Omraming + End of document Einde van document + End of section Einde van sectie + Endnotes Eindnoten + Endnotes to footnotes Eindnoten naar voetnoten + Enlarge symbol Symbool vergroten + Expand Uitvouwen + First row of data contains column headers Eerste gegevensrij bevat kolomkoppen + Fit Passen + Footnotes to endnotes Voetnoten naar eindnoten + Icon Color Pictogramkleur + In Front of Text Voor tekst + In Line with Text In lijn met tekst + Largest Only Alleen grootste + Left Only Alleen links + Location Locatie + Middle Midden + Moved from Verplaatst van + Moved to Verplaatst naar + Nature Natuur + New Drawing Canvas Nieuwe tekencanvas + No icons match your search. Geen pictogrammen komen overeen met je zoekopdracht. + Not linked: click a text box Niet gekoppeld: klik op een tekstvak + Office Kantoor + Offset (pt): Verschuiving (pt): + People Personen + Phonetic Guide Fonetische hulp + Recolor a one-color picture, such as an icon De kleur van een eenkleurige afbeelding, zoals een pictogram, wijzigen + Right (vertical) Rechts (verticaal) + Right Only Alleen rechts + Right-to-Left Section Rechts-naar-links-sectie + Right-to-Left Table Rechts-naar-links-tabel + Rotate all text 270° Alle tekst 270° draaien + Rotate all text 90° Alle tekst 90° draaien + Ruby text: Rubittekst: + Scale Drawing Tekening schalen + Search icons Pictogrammen zoeken + Section direction: Sectierichting: + Shrink text Tekst verkleinen + Size (pt): Grootte (pt): + Square Vierkant + Stacked Gestapeld + Swap footnotes and endnotes Voetnoten en eindnoten wisselen + Table direction: Tabelrichting: + Technology Technologie + Through Doorlopend + Top and Bottom Boven en onder + Transport Transport + Weather Weer + Wrap Text Sides Tekstzijden omvloeien + Characters per line: Tekens per regel: + Document Grid Documentraster + Lines and characters Regels en tekens + Lines only Alleen regels + Lines per page: Regels per pagina: + No grid Geen raster + Text snaps to characters Tekst aan tekens koppelen + A, B, C, … A, B, C, … + Above the item Boven het item + Accessed on Geraadpleegd op + Accessibility Pane Toegankelijkheidspaneel + Add New Source… Nieuwe bron toevoegen… + Add Pen Pen toevoegen + Add line numbering Regelnummering toevoegen + Add the chapter number Hoofdstuknummer toevoegen + Add to Document Aan document toevoegen + Add\nPen Pen\ntoevoegen + Address labels Adreslabels + Alt Text Pane Paneel alternatieve tekst + Alt\nText Alt-\ntekst + Arabic Arabisch + Atmosphere Atmosfeer + Auto Auto + Automatically hyphenate document Woorden in document automatisch afbreken + Below the item Onder het item + Book Boek + Book title Boektitel + Caption Bijschrift + Caption Numbering Nummering van bijschriften + Caption text Bijschrifttekst + Caption: Bijschrift: + Chapter in a book Hoofdstuk in een boek + Chapters begin at: Hoofdstukken beginnen bij: + Chinese (Simplified) Chinees (vereenvoudigd) + Chinese (Traditional) Chinees (traditioneel) + Collect entries from: Vermeldingen ophalen uit: + Column: Kolom: + Columns: Kolommen: + Conference Conferentie + Conference paper Conferentiepaper + Container Title Containertitel + Convert to Shapes Omzetten naar vormen + Count by: Tellen per: + Create Source Bron maken + Cross-reference: Kruisverwijzing: + Custom Table of Contents… Inhoudsopgave op maat… + Czech Tsjechisch + DOI DOI + Danish Deens + Day Dag + Decorative objects only add visual interest: screen readers skip them. Decoratieve objecten voegen alleen visuele aantrekking toe: schermlezers slaan ze over. + Delete Label Label verwijderen + Delete Selected Ink Geselecteerde inkt verwijderen + Delivery address: Bezorgadres: + Describe what this object shows, for people who can't see it. Beschrijf wat dit object toont, voor mensen die het niet kunnen zien. + Describe what this table shows, for people who can't see it. Beschrijf wat deze tabel toont, voor mensen die deze niet kunnen zien. + Detect language automatically Taal automatisch detecteren + Do not check spelling or grammar Spelling of grammatica niet controleren + Dutch Nederlands + Edit Source Bron bewerken + Edition Editie + Edit… Bewerken… + Entry marked. Item gemarkeerd. + Envelope size: Envelopformaat: + Errors ({n}) Fouten ({n}) + Estonian Estisch + Example: {sample} Voorbeeld: {sample} + Figure Figuur + Film Film + Finnish Fins + Forward Vooruit + French (France) Frans (Frankrijk) + From text: Uit tekst: + Full page of the same label Volledige pagina met hetzelfde label + German (Germany) Duits (Duitsland) + Greek Grieks + Heading styles (levels 1–{n}) Kopstijlen (niveaus 1–{n}) + Heading {n} Kop {n} + Hebrew Hebreeuws + Hyphenate at: Afbreken na: + Hyphenate words in CAPS Woorden in HOOFDLETTERS afbreken + Hyphenation Options… Afbreekopties… + Hyphenation zone: Afbreekzone: + I, II, III, … I, II, III, … + Include page numbers Paginanummers opnemen + Indented Met inspringing + Ink Replay Inkt afspelen + Ink to Shape Inkt naar vorm + Institution Instelling + Ionosphere Ionosfeer + Issue Nummer + Italian Italiaans + Japanese Japans + Journal Tijdschrift + Korean Koreaans + Label: Label: + Lasso Select Lassoselectie + Last, First; Last, First Achternaam, voornaam; Achternaam, voornaam + Leave the label out of the caption Label niet opnemen in het bijschrift + Letter headings Letterkoppen + Levels: Niveaus: + Limit consecutive hyphens to: Limiet voor opeenvolgende koppeltekens: + Line Numbering Options… Opties voor regelnummering… + Link entries to their headings Items aan hun koppen koppelen + Made from the author and year when empty Wordt gemaakt uit auteur en jaar als dit leeg is + Magazine or newspaper article Artikel in een tijdschrift of krant + Main entry: Hoofditem: + Manual Hyphenation Handmatig afbreken + Manual hyphenation is complete. Handmatig afbreken is voltooid. + Manual… Handmatig… + Mark Markeren + Mark All Alles markeren + Mark Entry… Item markeren… + Mark Index Entry Indexitem markeren + Mark as decorative Markeren als decoratief + Mark selected text as: Geselecteerde tekst markeren als: + Mesosphere Mesosfeer + Month Maand + Move Selected Ink Geselecteerde inkt verplaatsen + New Document Nieuw document + New Label Nieuw label + New Label… Nieuw label… + New… Nieuw… + No accessibility issues found. Geen problemen met toegankelijkheid gevonden. + No limit Geen limiet + No sources yet. Nog geen bronnen. + Norwegian (Bokmål) Noors (Bokmål) + Numbering: Nummering: + Numbering… Nummering… + Omit Overslaan + One label per recipient Eén label per ontvanger + Options… Opties… + Other Overig + Other styles and their TOC level: Overige stijlen en hun TOC-niveau: + Outline levels Outlineniveaus + Page number style Stijl paginanummer + Page numbers at the right margin Paginanummers bij de rechtermarge + Page title Paginatitel + Pages of bookmark: Pagina's van bladwijzer: + Periodical Periodiek + Portuguese (Brazil) Portugees (Brazilië) + Portuguese (Portugal) Portugees (Portugal) + Position: Positie: + Publisher Uitgever + Recolor Selected Ink Kleur van de geselecteerde inkt wijzigen + Remove Pen Pen verwijderen + Return address labels Labels met retouradres + Return address: Retouradres: + Rewind Terugspoelen + Row: Rij: + Run-in Doorlopend + Russian Russisch + See Zie + Select a picture, shape, chart or table to describe it. Selecteer een afbeelding, vorm, grafiek of tabel om deze te beschrijven. + Separator: Scheidingsteken: + Serbian (Cyrillic) Servisch (cyrillisch) + Serbian (Latin) Servisch (latijn) + Set As Default Instellen als standaard + Shipping labels Verzendlabels + Show every bibliography field Alle bibliografievelden tonen + Single label Eén label + Site name Sitenaam + Source Manager Bronnenbeheer + Source type: Brontype: + Sources in this document: Bronnen in dit document: + Spanish (Mexico) Spaans (Mexico) + Spanish (Spain) Spaans (Spanje) + Stroke {n} of {total} Streek {n} van {total} + Styles: Stijlen: + Subentry: Subitem: + Swedish Zweeds + TC entry fields TC-itemvelden + Tab leader: Opvulteken van tabstop: + Table of Contents Options Inhoudsopgaveopties + Table of Contents Styles Inhoudsopgavestijlen + Tag Tag + This page Deze pagina + Tips ({n}) Tips ({n}) + Title Titel + Title: Titel: + Turkish Turks + Type: Type: + URL URL + Ukrainian Oekraïens + View Alt Text… Alternatieve tekst weergeven… + Volume Volume + Warnings ({n}) Waarschuwingen ({n}) + Website Website + Year Jaar + a, b, c, … a, b, c, … + i, ii, iii, … i, ii, iii, … + {language} is now the document's default language. {language} is nu de standaardtaal van het document. + {n} entries marked. {n} items gemarkeerd. + 0 or More Characters 0 of meer tekens + 1. Formatting restrictions 1. Opmaakbeperkingen + 2. Editing restrictions 2. Wijzigingsbeperkingen + 3. Start enforcement 3. Beveiliging inschakelen + << Less << Minder + Above Chart Boven grafiek + Accent Gradient Verloop + Accent with Reflection Verloop met weerspiegeling + Accent {n} Accent {n} + Add Chart Element Grafiekelement toevoegen + Add Font… Lettertype toevoegen… + Add Row Rij toevoegen + Add Series Reeks toevoegen + Add Shape Vorm toevoegen + Add\nShape Vorm\ntoevoegen + Added Fonts Toegevoegde lettertypen + All Levels Alle niveaus + All files Alle bestanden + All pages Alle pagina's + All pages in the range Alle pagina's in het bereik + Allow line breaks Regeleindes toestaan + Animate the caret (it glides and fades) De invoegcursor animeren (die beweegt en vervaagt) + Animation Animatie + Animation effects: Animatie-effecten: + Another file Ander bestand + Any Character Willekeurig teken + Any Digit Willekeurig cijfer + Any Letter Willekeurige letter + Apply as you type Toepassen tijdens het typen + Arch Down Boog omlaag + Arch Up Boog omhoog + Area Gebied + As you type Tijdens het typen + Asian text font: Lettertype Aziatische tekst: + Attach Template Sjabloon koppelen + Attach… Koppelen… + AutoCorrect AutoCorrect + AutoFormat As You Type AutoOpmaak tijdens het typen + AutoSave writes your changes to the file a moment after you stop typing. AutoSave schrijft je wijzigingen kort nadat je stopt met typen naar het bestand. + Automatic bulleted lists Automatische opsommingen + Automatic numbered lists Automatische nummering + Automatically Update Document Styles Documentstijlen automatisch bijwerken + Automatically update document styles Documentstijlen automatisch bijwerken + Basic Cycle Basiscyclus + Basic List Basislijst + Basic Matrix Basismatrix + Basic Process Basisproces + Basic Pyramid Basispiramide + Basic Radial Basisradial + Basic Venn Basis-Venn + Beginning of Word Begin van woord + Blue: Blauw: + Border lines Randlijnen + Browse in WordCraft… Bladeren in WordCraft… + Building Block Gallery Content Control Inhoudsbesturingselement galerij bouwstenen + Can't be deleted Kan niet worden verwijderd + Capitalize first letter of sentences De eerste letter van zinnen hoofdletter maken + Capitalize first letter of table cells De eerste letter van tabelcellen hoofdletter maken + Capitalize names of days Namen van dagen hoofdletter maken + Caret Character Caret-teken + Case changes Letterkast wijzigen + Categories Categorieën + Category {n} Categorie {n} + Centimeters Centimeter + Change Chart Type Grafiektype wijzigen + Change Colors Kleuren wijzigen + Change Layout Lay-out wijzigen + Change\nChart Type Grafiektype\nwijzigen + Change\nColors Kleuren\nwijzigen + Character in Range Teken in bereik + Character level Tekenniveau + Chart Design Grafiekontwerp + Chart Layouts Grafieklay-outs + Chart Title Grafiektitel + Check Box Content Control Inhoudsbesturingselement selectievakje + Check spelling as you type Spelling controleren tijdens het typen + Checked styles are allowed: Aangevinkte stijlen zijn toegestaan: + Checked symbol Symbool voor aangevinkte status + Chevron Down Pijl omlaag + Chevron Up Pijl omhoog + Choose List Item Lijstitem kiezen + Click to switch back to inserting Klik om terug te keren naar invoegen + Clipboard Contents Klembordinhoud + Close Outline View Overzichtsweergave sluiten + Close Outline\nView Overzichts-\nweergave sluiten + Clustered Bar Gegroepeerde balk + Clustered Column Gegroepeerde kolom + Collapse Samenvouwen + Collate copies Exemplaren sorteren + Color model: Kleurmodel: + Colorful Kleurrijk + Colors: Kleuren: + Combine Documents Documenten samenvoegen + Combo Box Content Control Inhoudsbesturingselement combobox + Comments beside each page and tracked changes in PDFs, prints and images; a comment list at the end of web pages, Markdown and text. Opmerkingen naast elke pagina en bijgehouden wijzigingen in pdf's, afdrukken en afbeeldingen; een opmerkingenlijst aan het einde van webpagina's, Markdown en tekst. + Compare Documents Documenten vergelijken + Compared: {changes} changes Vergeleken: {changes} wijzigingen + Comparing with another file needs the desktop app. Vergelijken met een ander bestand vereist de desktopapp. + Comparison settings Vergelijkingsinstellingen + Connect Shapes Vormen verbinden + Content Control Properties Eigenschappen van inhoudsbesturingselement + Content Control at Selection Inhoudsbesturingselement bij selectie + Contents can't be edited Inhoud kan niet worden bewerkt + Controls Besturingselementen + Copies Kopieën + Copy Styles Stijlen kopiëren + Create Graphic Illustratie maken + Current Huidig + Current page Huidige pagina + Curved Arrow Connector Gebogen verbindingslijn met pijl + Curved Connector Gebogen verbindingslijn + Custom dictionary Aangepast woordenboek + Custom range Aangepast bereik + Cycle Cyclus + Dark with Shadow Donker met schaduw + Data Labels Gegevenslabels + Date Picker Content Control Inhoudsbesturingselement datumkiezer + Date format Datumnotatie + Deflate Samenknijpen + Deleted Verwijderd + Demote to Body Text Naar hoofdtekst verplaatsen + Design Mode Ontwerpmodus + Detach Template Sjabloon loskoppelen + Developer Ontwikkelaar + Display text Weergavetekst + Document template: Documentsjabloon: + Documents Documenten + Don't capitalize after: Niet met hoofdletter beginnen na: + Doughnut Donut + Drop-Down List Content Control Inhoudsbesturingselement vervolgkeuzelijst + Edit Data Gegevens bewerken + Edit Items Items bewerken + Edit\nData Gegevens\nbewerken + Editing in this document is restricted. Bewerken in dit document is beperkt. + Elbow Arrow Connector Hoekverbindingslijn met pijl + Elbow Connector Hoekverbindingslijn + End of Word Einde van woord + Even pages only Alleen even pagina's + Every change is recorded as a tracked change. Elke wijziging wordt vastgelegd als een bijgehouden wijziging. + Everyone Iedereen + Exceptions (optional) Uitzonderingen (optioneel) + Exceptions… Uitzonderingen… + Expand or Collapse Heading Kop uitvouwen of samenvouwen + Expression Expressie + Extend Selection by Units Selectie uitbreiden per eenheid + Fields Velden + File name: Bestandsnaam: + Filling in forms Formulieren invullen + Find Next Editable Region Volgende bewerkbare regio zoeken + Find What Expression Gezochte expressie + Find What Text Gezochte tekst + Fix Position on Page Positie op pagina vastzetten + Footnotes and endnotes Voetnoten en eindnoten + Formatting exceptions Uitzonderingen voor opmaak + Formatting marks Opmaakmarkeringen + Formatting, case changes, white space and tables are compared; the other settings are kept for later. Opmaak, hoofdlettergebruik, witruimte en tabellen worden vergeleken; de overige instellingen worden bewaard voor later. + Fractions (1/2) as fraction characters (½) Breuken (1/2) als breuktekens (½) + Graphic Illustratie + Green: Groen: + Headers and footers Kop- en voetteksten + Hex: Hex: + Hidden text Verborgen tekst + Hide White Space Witruimte verbergen + Hierarchy Hiërarchie + Hollow Hol + Home folder Thuismap + Hue: Tint: + Hyphens (--) as dashes (—) Koppeltekens (--) als streepjes (—) + Ignore punctuation characters Leestekens negeren + Ignore web and file addresses Web- en bestandsadressen negeren + Ignore white-space characters Witruimtetekens negeren + Ignore words in capitals Woorden in hoofdletters negeren + Ignore words with numbers Woorden met cijfers negeren + In equations, typing a name after a backslash replaces it with its symbol. In vergelijkingen vervangt het typen van een naam na een backslash deze door het bijbehorende symbool. + Inches Inches + Inflate Opblazen + Initials: Initialen: + Insert Chart Grafiek invoegen + Insert SmartArt SmartArt invoegen + Inside: Binnen: + Interface Interface + Interface language Interfacetaal + Internet addresses as links Internetadressen als koppelingen + Label changes with: Wijzigingen labelen met: + Latin text font: Lettertype voor Latijns tekst: + Layouts Lay-outs + Legend Legenda + Light with Outline Licht met omtrek + Limit formatting to a selection of styles Opmaak beperken tot een selectie stijlen + List Lijst + List entries Lijstitems + Lum: Lum: + Manual Line Break Handmatige regeleinde + Manual Page Break Handmatige pagina-einde + Mark grammar errors as you type Grammaticafouten markeren tijdens het typen + Match prefix Voorvoegsel overeenkomen + Match suffix Achtervoegsel overeenkomen + Math AutoCorrect Wiskundige AutoCorrect + Measurement units: Maateenheden: + Millimeters Millimeters + Mirror indents Spiegelende inspringing + More >> Meer >> + More Colors… Meer kleuren… + Move Down Omlaag verplaatsen + Move Up Omhoog verplaatsen + Move with Text Met tekst meenemen + Moves Verplaatsingen + New comments and tracked changes are recorded under this name and these initials. Nieuwe opmerkingen en bijgehouden wijzigingen worden vastgelegd onder deze naam en deze initialen. + New document Nieuw document + New word Nieuw woord + No Fill Geen vulling + No Outline Geen omtrek + No Reflection Geen spiegeling + No Transform Geen transformatie + No changes (read only) Geen wijzigingen (alleen lezen) + No words yet. Add to Dictionary on a misspelled word puts it here. Nog geen woorden. Met Toevoegen aan woordenboek wordt een foutspeld woord hier geplaatst. + Not Niet + Num Occurrences Aantal voorkomens + Odd pages only Alleen oneven pagina's + Off Uit + On Aan + On: the caret glides to its new place when you type or use the arrow keys, and its blink fades. Off: it jumps and blinks on and off. Aan: de invoercursor gleedt naar de nieuwe plaats wanneer je typt of de pijltoetsen gebruikt en het knipperen vervaagt. Uit: hij springt en knippert aan en uit. + One line per item. Enter adds an item. Eén regel per item. Enter voegt een item toe. + Only allow this kind of editing: Alleen dit soort bewerkingen toestaan: + Only comments can be added, except in regions marked editable. Er kunnen alleen opmerkingen worden toegevoegd, behalve in gebieden die als bewerkbaar zijn gemarkeerd. + Only form fields can be filled in. Alleen formuliervelden kunnen worden ingevuld. + Only the allowed styles can be applied. Alleen de toegestane stijlen kunnen worden toegepast. + Open a file with WordCraft's own browser when the system file dialog doesn't appear. Een bestand openen met de eigen browser van WordCraft wanneer het systeembestandsdialoogvenster niet verschijnt. + Ordinals (1st) as superscript Ordinalen (1st) als superscript + Organizer Organizer + Organizer… Organizer… + Original document Oorspronkelijk document + Outline State Outlinestatus + Outline Tools Outlinetools + Outlining Kopniveaus + Outside: Buiten: + Overtype Overschrijven + Overtype (typing replaces the characters after the caret) Overschrijven (typen vervangt de tekens na de invoercursor) + Page display Paginaweergave + Pages like 1-3, 5 Pagina's zoals 1-3, 5 + Paragraph Character Alineateken + Paragraph marks Alineamarkeringen + Personalize Personaliseren + Picas Pica's + Picture Content Control Inhoudsbesturingselement afbeelding + Pie Cirkeldiagram + Placeholder text Plaatstekst + Plain Text Content Control Inhoudsbesturingselement platte tekst + Points Punten + Previous 1 or More Vorige 1 of meer + Previous Page Vorige pagina + Print Markup Markeringen afdrukken + Print the page color Paginakleur afdrukken + Print to PDF Afdrukken naar PDF + Printing Afdrukken + Process Proces + Promote to Heading 1 Naar Kop 1 verplaatsen + Proofing language Controletaal + Put the caret in a content control first. Plaats de invoercursor eerst in een inhoudsbesturingselement. + Pyramid Piramide + Recent Colors Recente kleuren + Recent documents to show: Aantal te tonen recente documenten: + Recipient lists Ontvangerslijsten + Red: Rood: + Reflection Spiegeling + Regular expressions Reguliere expressies + Relationship Relatie + Remove Added Font Toegevoegd lettertype verwijderen + Remove Content Control Inhoudsbesturingselement verwijderen + Remove row Rij verwijderen + Remove series Reeks verwijderen + Remove the control once edited Besturingselement verwijderen na bewerken + Rename Style Stijl hernoemen + Repeating Section Content Control Inhoudsbesturingselement herhalende sectie + Replace as you type Vervangen tijdens het typen + Replace text as you type Tekst vervangen tijdens het typen + Replace: Vervangen: + Reset Graphic Illustratie opnieuw instellen + Reset\nGraphic Illustratie\nopnieuw instellen + Resize Table by Dragging Tabel formaat wijzigen door te slepen + Select Column Kolom selecteren + Select Table Cells by Clicking Tabelcellen selecteren door te klikken + Insert Row or Column by Clicking Rij of kolom invoegen door te klikken + Move Table by Dragging Tabel verplaatsen door te slepen + AutoFit Column by Double-Clicking Kolom automatisch aanpassen door te dubbelklikken + Revised document Herzien document + Rich Text Content Control Inhoudsbesturingselement opgemaakte tekst + Sat: Verz: + Save documents Documenten opslaan + Save these pages as PDF… Deze pagina's opslaan als PDF… + Scatter Spreiding + Search Options Zoekopties + Section Break Sectie-einde + Section Character Sectieteken + Select Heading and Content Kop en inhoud selecteren + Select text, then tick who can still edit it. Selecteer de tekst en geef aan wie deze nog mag bewerken. + Selected pages Geselecteerde pagina's + Series name Reeksnaam + Series {n} Reeks {n} + Set Date Datum instellen + Settings… Instellingen… + Show All Levels Alle niveaus tonen + Show First Line Only Alleen eerste regel tonen + Show Level Niveau tonen + Show Level 1 Niveau 1 tonen + Show Level 2 Niveau 2 tonen + Show Level 3 Niveau 3 tonen + Show Level 4 Niveau 4 tonen + Show Level 5 Niveau 5 tonen + Show Level 6 Niveau 6 tonen + Show Level 7 Niveau 7 tonen + Show Level 8 Niveau 8 tonen + Show Level 9 Niveau 9 tonen + Show Text Formatting Tekstopmaak tonen + Show changes at: Wijzigingen tonen op niveau: + Show changes in: Wijzigingen tonen in: + Show the mini toolbar when text is selected De minibalk weergeven wanneer tekst is geselecteerd + Showing the first {count} items. De eerste {count} items worden weergegeven. + Shows a tree: each item below the item it belongs to. Geeft een boomstructuur: elk item onder het item waartoe het behoort. + Shows items around a central idea: the first item is the centre. Geeft items rond een centraal idee weer: het eerste item is het middelpunt. + Shows items of equal weight as blocks in rows. Geeft items met gelijke waarde als blokken in rijen weer. + Shows overlapping ideas as overlapping circles. Geeft overlappende ideeën als overlappende cirkels weer. + Shows parts of a whole as quadrants. Geeft delen van een geheel als kwadranten weer. + Shows parts that build on each other, largest at the bottom. Geeft delen die op elkaar voortbouwen weer, het grootste onderaan. + Shows steps in order, left to right, with arrows between them. Geeft stappen in volgorde van links naar rechts weer, met pijlen ertussen. + Shows steps that repeat, around a circle. Geeft herhalende stappen rond een cirkel weer. + Slant Down Schuin omlaag + Slant Up Schuin omhoog + Smart quotes instead of straight quotes Gebogen aanhalingstekens in plaats van rechte + SmartArt Design SmartArt-ontwerp + SmartArt Styles SmartArt-stijlen + Smooth scrolling and zooming, and menus and buttons that fade in. Off shows every change at once. Vloeiend scrollen en zoomen, en menu's en knoppen die langzaam verschijnen. Uit toont alle wijzigingen tegelijk. + Soft Glow Zachte gloed + Solid Accent Massief accent + Spaces Spaties + Special Speciaal + Spelling Spelling + Spelling and grammar are checked in English (United States). Spelling en grammatica worden gecontroleerd in het Engels (Verenigde Staten). + Stacked Bar Gestapeld staafdiagram + Stacked Column Gestapeld kolomdiagram + Standard Standaard + Start Enforcing Protection Beveiliging activeren + Stop Protection Beveiliging stoppen + Straight Arrow Connector Rechte verbindingslijn met pijl + Straight Connector Rechte verbindingslijn + Tab Character Tabteken + Tab characters Tabtekens + Templates Sjablonen + Templates and Add-ins Sjablonen en invoegitems + Text Fill Tekstvulling + Text Outline Tekstomtrek + Text Pane Tekstgebied + Text boxes Tekstvakken + The document is read only, except for regions marked editable. Het document is alleen-lezen, behalve de gedeelten die als bewerkbaar zijn gemarkeerd. + The password is optional; without it anyone can stop the protection. Het wachtwoord is optioneel; zonder wachtwoord kan iedereen de beveiliging opheffen. + The printer, two-sided printing and pages per sheet are chosen in that dialog. De printer, dubbelzijdig afdrukken en pagina's per vel worden in dat dialoogvenster gekozen. + The system file dialog isn't available, so WordCraft shows its own. To get the system dialog, install a file chooser portal (such as xdg-desktop-portal-gtk) or zenity. Het systeemvenster voor bestanden is niet beschikbaar, dus toont WordCraft het eigen venster. Installeer een portaal voor bestandskiezer (zoals xdg-desktop-portal-gtk) of zenity om het systeemvenster te krijgen. + These marks show even when Show/Hide ¶ is off. Deze tekens worden ook weergegeven als „¶ weergeven/verbergen” uit staat. + This document Dit document + This folder can't be read: {error} Kan deze map niet worden gelezen: {error} + This password type isn't supported. Dit wachtwoordtype wordt niet ondersteund. + Today Vandaag + Toggle Check Box Selectievakje wisselen + Tracked changes Bijgehouden wijzigingen + Transform Transformeren + Triangle Down Driehoek omlaag + Triangle Up Driehoek omhoog + Type Type + Type the categories down the side and a column of values per series. Voer de categorijen aan de zijkant in en een kolom met waarden per reeks. + Type the password to stop the protection. Voer het wachtwoord in om de beveiliging op te heffen. + Typing replaces selected text Typen vervangt de geselecteerde tekst + Unchecked symbol Symbool voor niet aangevinkt + Up one level Eén niveau omhoog + Update Styles from Template Stijlen vanuit sjabloon bijwerken + Update fields before printing Velden bijwerken voor het afdrukken + Use the Insert key to switch overtype on and off Gebruik de Insert-toets om overtypen aan en uit te schakelen + Use wildcards Wildcards gebruiken + Value Waarde + Visual effects Visuele effecten + Warm Gradient with Outline Warm verloop met omtrek + White Space Witruimte + White space Witruimte + With: Met: + Word XML Document (*.xml) Word XML-document (*.xml) + Word document Word-document + Word level Woordniveau + WordArt Styles WordArt-stijlen + Your system doesn't report an animation setting, so animation effects stay on. Je systeem meldt geen animatie-instelling, dus animatie-effecten blijven aan. + Your system has animation effects turned off. In je systeem zijn animatie-effecten uitgeschakeld. + Your system has animation effects turned on. In je systeem zijn animatie-effecten ingeschakeld. + {count} of {total} pages will print {count} van {total} pagina's worden afgedrukt + {name} already exists. Choose Save again to replace it. {name} bestaat al. Kies nogmaals Opslaan om het te vervangen. + {name} wasn't found. {name} is niet gevonden. + “{text}” ({row}, {series}) isn't a number. „{text}” ({row}, {series}) is geen getal. + Undo Actions Acties ongedaan maken + Undo History Geschiedenis van ongedaan maken + Undo 1 Action 1 actie ongedaan maken + Undo {n} Actions {n} acties ongedaan maken + Start-up Opstarten + Show the Start screen when WordCraft starts Het startscherm tonen wanneer WordCraft start + On: WordCraft opens on File › Home, with a blank document, the templates and your recent documents. Off: it opens a blank document. Aan: WordCraft opent Bestand › Start met een leeg document, de sjablonen en je recente documenten. Uit: er wordt een leeg document geopend. + Couldn't make WordCraft the default: {error} Kan WordCraft niet de standaardapp maken: {error} + In Default apps, choose WordCraft for the document types you want it to open. Kies in Standaardapps WordCraft voor de documenttypen die je ermee wilt openen. + Make WordCraft the default for Word documents… WordCraft de standaardapp maken voor Word-documenten… + Opens .docx, .doc, .rtf and .odt files and Word templates with WordCraft from now on. Opent voortaan .docx-, .doc-, .rtf- en .odt-bestanden en Word-sjablonen met WordCraft. + Opens Settings › Default apps, where you choose WordCraft for .docx and the other document types. Opent Instellingen › Standaardapps, waar je WordCraft kiest voor .docx en de andere documenttypen. + To open Word documents with WordCraft by default: select one in Finder, choose File › Get Info, pick WordCraft under Open with, then click Change All. Om Word-documenten standaard met WordCraft te openen: selecteer er een in Finder, kies Bestand › Info, selecteer WordCraft bij Open met en klik daarna op Alles wijzigen. + WordCraft is now the default app for Word documents. WordCraft is nu de standaardapp voor Word-documenten. + Numerals: Cijfers: + Numerals Cijfers + Hindi Hindi + Context Context + System Systeem + Book Title Boektitel + Category Categorie + Contributors Medewerkers + Emphasis Nadruk + Endnote Reference Verwijzing naar eindnoot + Endnote Text Eindnoottekst + Footnote Reference Verwijzing naar voetnoot + Footnote Text Voetnoottekst + Hyperlink Hyperlink + Intense Emphasis Intensieve nadruk + Intense Quote Intensief citaat + Intense Reference Intensieve verwijzing + Keywords Trefwoorden + No Spacing Geen spatiëring + Quote Citaat + Strong Sterk + Subject Onderwerp + Subtitle Ondertitel + Subtle Emphasis Subiele nadruk + Subtle Reference Subiele verwijzing + TOC Heading Kop inhoudsopgave + TOC {n} Inhoudsopgave {n} + List Paragraph Lijstalinea + Models Modellen + Show KeyTips (Alt or F10) Sneltoetsen tonen (Alt of F10) + Reading Lezen + Reading Column Width Kolombreedte in leesmodus + Reading Line Spacing Regelafstand in leesmodus + Reading Text Size Tekstgrootte in leesmodus + Correct TWo INitial CApitals Twee hoofdletters aan het begin corrigeren + Correct accidental usage of cAPS LOCK key Onbedoeld gebruik van de cAPS LOCK-toets corrigeren + Region: Regio: + Automatic ({region}) Automatisch ({region}) + AaBbCcDd AaBbCcDd + Annotation Reference Aantekeningverwijzing + Annotation Subject Aantekeningonderwerp + Annotation Text Aantekeningstekst + Body text in a short paragraph to show spacing. Bodytekst in een korte alinea om de afstand te tonen. + Default Paragraph Font Standaardalinealettertype + Envelope Address Envelopeadres + Envelope Return Retouradres op envelop + Index Heading Indexkop + Index {n} Index {n} + Line Number Regelnummer + Normal Standaard + Normal Indent Standaard met inspringing + Normal Table Standaardtabel + Table Grid Tabelraster + Table of Figures Afbeeldingenlijst +preview Title Titel + TOA Heading Kop van verwijzingslijst + Good evening Goedenavond + Good morning Goedemorgen + Good afternoon Goedemiddag + tOGGLE cASE cASE wISSELEN + Change to Level 1 Wijzig naar niveau 1 + Change to Level 2 Wijzig naar niveau 2 + Change to Level 3 Wijzig naar niveau 3 + Change to Level 4 Wijzig naar niveau 4 + Change to Level 5 Wijzig naar niveau 5 + Level 1 Niveau 1 + Level 2 Niveau 2 + Level 3 Niveau 3 + ASAP ZO SNEL MOGELIJK + CONFIDENTIAL VERTROUWELIJK + DO NOT COPY NIET KOPIËREN + DRAFT CONCEPT + SAMPLE VOORBEELD + URGENT DRINGEND + Plain Table 1 Eenvoudige tabel 1 + Grid Table 1 Light Rastertabel 1 licht + Grid Table 4 – Blue Rastertabel 4 – blauw + Grid Table 4 – Green Rastertabel 4 – groen + Grid Table 4 – Orange Rastertabel 4 – oranje + Grid Table 4 – Purple Rastertabel 4 – paars + List Table 3 – Blue Lijsttabel 3 – blauw + List Table 3 – Green Lijsttabel 3 – groen + List Table 3 – Orange Lijsttabel 3 – oranje + List Table 3 – Purple Lijsttabel 3 – paars + Convert between built-up and linear form Schakelen tussen professioneel en lineair + Justification of a display equation Uitvullen van een weergegeven vergelijking + Number the equation; the number sits at the right margin (or type # then the number) De vergelijking nummeren; het nummer staat aan de rechtermarge (of typ # en het nummer) + Show the equation in the line of text De vergelijking in de tekstregel tonen + Show the equation on a line of its own De vergelijking op een eigen regel tonen + Type equations in LaTeX Vergelijkingen in LaTeX typen + Type equations in the linear format (UnicodeMath) Vergelijkingen in het lineaire formaat typen (UnicodeMath) + Type normal (non-math) text in the equation Normale tekst (geen wiskunde) in de vergelijking typen + Sides of a right triangle Zijden van een rechthoekige driehoek + Circle area from its radius Oppervlakte van een cirkel uit de straal + Roots of ax²+bx+c Wortels van ax²+bx+c + Binomial expansion of (x+a)ⁿ Binomiale uitbreiding van (x+a)ⁿ + Power series of (1+x)ⁿ Machtreeks van (1+x)ⁿ + Euler's identity Identiteit van Euler + Derivative as a limit Afgeleide als limiet + Exponential series Exponentiële reeks + Integral of e^(−x²) Integraal van e^(−x²) + Sine and cosine series of f(x) Sinus- en cosinusreeks van f(x) + Sum or difference of sines Som of verschil van sinussen + Sum of cosines Som van cosinussen + Normal probability density Normale verdelingsdichtheid + Bayes' rule Bayesregel + Accents Accenten + Basic Math Basiswiskunde + Boxed Formulas Formules in een kader + Cases and Stacks Gevallen en stapelingen + Common Accent Objects Veelgebruikte accentobjecten + Common Brackets Veelgebruikte haakjes + Common Fractions Veelgebruikte breuken + Common Functions Veelgebruikte functies + Common Large Operators Veelgebruikte grote operatoren + Common Operator Structures Veelgebruikte operatorstructuren + Common Operators Veelgebruikte operatoren + Common Radicals Veelgebruikte wortels + Common Subscripts and Superscripts Veelgebruikte subscripts en superscripts + Contour Integrals Contourintegralen + Differentials Differentialen + Dots Punten + Empty Matrices Lege matrices + Functions Functies + Geometry Meetkunde + Greek Letters Griekse letters + Hyperbolic Functions Hyperbolische functies + Identity Matrices Eenheidsmatrices + Integrals Integralen + Inverse Functions Omgekeerde functies + Inverse Hyperbolic Functions Omgekeerde hyperbolische functies + Letter-Like Symbols Letterachtige symbolen + Matrices with Brackets Matrices met haakjes + Negated Relations Ontkenende relaties + Operator Structures Operatorstructuren + Operators Operatoren + Other Large Operators Overige grote operatoren + Overbars and Underbars Overstrepen en onderstrepen + Products and Coproducts Producten en coproducten + Radicals Wortels + Scripts Scripts + Single Brackets Enkele haakjes + Sparse Matrices IJle matrices + Subscripts and Superscripts Subscripts en superscripts + Summations Sommen + Trigonometric Functions Trigonometrische functies + Unions and Intersections Verenigingen en doorsneden + 1×2 Empty Matrix Lege matrix 1×2 + 1×3 Empty Matrix Lege matrix 1×3 + 2×1 Empty Matrix Lege matrix 2×1 + 2×2 Empty Matrix Lege matrix 2×2 + 2×3 Empty Matrix Lege matrix 2×3 + 3×1 Empty Matrix Lege matrix 3×1 + 3×2 Empty Matrix Lege matrix 3×2 + 3×3 Empty Matrix Lege matrix 3×3 + 2×2 Determinant Determinant 2×2 + 2×2 Identity Matrix Eenheidsmatrix 2×2 + 2×2 Identity Matrix (sparse) Eenheidsmatrix 2×2 (ijl) + 3×3 Identity Matrix Eenheidsmatrix 3×3 + 3×3 Identity Matrix (sparse) Eenheidsmatrix 3×3 (ijl) + 2×2 Matrix in Brackets Matrix 2×2 in vierkante haakjes + 2×2 Matrix in Double Bars Matrix 2×2 met dubbele verticale strepen + 2×2 Matrix in Parentheses Matrix 2×2 in ronde haakjes + n×n Identity Matrix Eenheidsmatrix n×n + n×n Matrix Matrix n×n + A xor B A xor B + Absolute value cases Gevallen voor absolute waarde + Acute Acuut + Angle Brackets Hoekige haakjes + Angle Brackets with Separator Hoekige haakjes met scheidingsteken + Angle Brackets with Two Separators Hoekige haakjes met twee scheidingstekens + Baseline Dots Punten op de basislijn + Binomial Coefficient Binomiaalcoëfficiënt + Binomial Coefficient (angle) Binomiaalcoëfficiënt (hoekig) + Binomial Coefficient with Angle Brackets Binomiaalcoëfficiënt met hoekige haakjes + Boxed Formula Formule in een kader + Boxed Formula (example) Formule in een kader (voorbeeld) + Braces Accolades + Braces with Separator Accolades met scheidingsteken + Brackets Vierkante haakjes + Brackets with Separators Vierkante haakjes met scheidingstekens + Breve Brevis + Cases (Three Conditions) Gevallen (drie voorwaarden) + Cases (Two Conditions) Gevallen (twee voorwaarden) + Ceiling Plafond + Change in y over change in x Verandering in y gedeeld door verandering in x + Check Omgekeerd dakje + Circled Dot Omkringde punt + Circled Plus Omkringde plus + Circled Times Omkringd kruisteken + Colon Equal Dubbele punt gelijk + Contour Integral Contourintegraal + Contour Integral with Limits Contourintegraal met grenzen + Contour Integral with Stacked Limits Contourintegraal met gestapelde grenzen + Coproduct Coproduct + Coproduct with Limits Coproduct met grenzen + Coproduct with Lower Limit Coproduct met ondergrens + Coproduct with Subscript-Superscript Limits Coproduct met grenzen in subscript en superscript + Cosecant Function Cosecansfunctie + Cosine 2x Cosinus 2x + Cosine Function Cosinusfunctie + Cotangent Function Cotangentfunctie + Cube Root Derdemachtswortel + Delta Equal To Delta gelijk aan + Delta over arrow Delta boven pijl + Diagonal Dots Diagonale punten + Differential Differentiaal + Differential theta Differentiaal theta + Differential x Differentiaal x + Differential y Differentiaal y + Dot Punt + Double Bar Dubbele streep + Double Dot Dubbele punt + Double Integral Dubbele integraal + Double Integral with Limits Dubbele integraal met grenzen + Double Integral with Stacked Limits Dubbele integraal met gestapelde grenzen + Double Left Arrow with Text Above Dubbele linkerpijl met tekst erboven + Double Left Arrow with Text Below Dubbele linkerpijl met tekst eronder + Double Left Right Arrow with Text Above Dubbele links-rechtspijl met tekst erboven + Double Left Right Arrow with Text Below Dubbele links-rechtspijl met tekst eronder + Double Right Arrow with Text Above Dubbele rechterpijl met tekst erboven + Double Right Arrow with Text Below Dubbele rechterpijl met tekst eronder + Double Square Brackets Dubbele vierkante haken + Double Vertical Bars Dubbele verticale strepen + Double summation Dubbele som + Equal Equal Dubbel gelijkheidsteken + Equal by Definition Gelijk op basis van definitie + Exponential Exponentiële functie + Floor Vloer + Grave Accent grave + Grouping Character Above Groeperingsstreep erboven + Grouping Character Below Groeperingsstreep eronder + Hat Dakje + Hyperbolic Cosecant Function Hyperbolische cosecansfunctie + Hyperbolic Cosine Function Hyperbolische cosinusfunctie + Hyperbolic Cotangent Function Hyperbolische cotangentfunctie + Hyperbolic Secant Function Hyperbolische secansfunctie + Hyperbolic Sine Function Hyperbolische sinusfunctie + Hyperbolic Tangent Function Hyperbolische tangensfunctie + Hypotenuse Schuine zijde + Integral with Limits Integraal met grenzen + Integral with Stacked Limits Integraal met gestapelde grenzen + Intersection Doorsnede + Intersection with Limits Doorsnede met grenzen + Intersection with Lower Limit Doorsnede met ondergrens + Intersection with Subscript-Superscript Limits Doorsnede met grenzen in subscript en superscript + Inverse Cosecant Function Inverse cosecansfunctie + Inverse Cosine Function Inverse cosinusfunctie + Inverse Cotangent Function Inverse cotangentfunctie + Inverse Hyperbolic Cosecant Function Inverse hyperbolische cosecansfunctie + Inverse Hyperbolic Cosine Function Inverse hyperbolische cosinusfunctie + Inverse Hyperbolic Cotangent Function Inverse hyperbolische cotangentfunctie + Inverse Hyperbolic Secant Function Inverse hyperbolische secansfunctie + Inverse Hyperbolic Sine Function Inverse hyperbolische sinusfunctie + Inverse Hyperbolic Tangent Function Inverse hyperbolische tangensfunctie + Inverse Secant Function Inverse secansfunctie + Inverse Sine Function Inverse sinusfunctie + Inverse Tangent Function Inverse tangensfunctie + Left Arrow with Text Above Linkerpijl met tekst erboven + Left Arrow with Text Below Linkerpijl met tekst eronder + Left Bracket Twice Dubbele linkerhaak + Left Right Arrow Above Links-rechtspijl erboven + Left Right Arrow with Text Above Links-rechtspijl met tekst erboven + Left Right Arrow with Text Below Links-rechtspijl met tekst eronder + Left Subscript-Superscript Links subscript en superscript + Leftwards Arrow Above Linkerpijl erboven + Leftwards Harpoon Above Linkerharpoen erboven + Limit Grens + Limit Example Grensvoorbeeld + Linear Fraction Lineaire breuk + Logarithm Logaritme + Logarithm with Base Logaritme met grondtal + Logical AND Logische EN + Logical AND with Limits Logische EN met grenzen + Logical OR Logische OF + Logical OR with Limits Logische OF met grenzen + Maximum Maximum + Maximum Example Maximumvoorbeeld + Measured By Gemeten door + Midline Dots Middellijnspunten + Minimum Minimum + Minus Equal Min gelijk + Natural Logarithm Natuurlijke logaritme + One half Eén halve + Outward Brackets Naar buiten gerichte vierkante haken + Overbar Overstreep + Overbrace Accolade erboven + Parentheses Haakjes + Parentheses with Separator Haakjes met scheidingsteken + Partial Differential Partiële differentiaal + Pi Over 2 Pi gedeeld door 2 + Plus Equal Plus gelijk + Product Product + Product of A Product van A + Product with Limits Product met grenzen + Product with Lower Limit Product met ondergrens + Product with Subscript-Superscript Limits Product met grenzen in subscript en superscript + Quadratic Root Kwadratische wortel + Radical with Degree Wortel met graad + Right Arrow with Text Above Rechterpijl met tekst erboven + Right Arrow with Text Below Rechterpijl met tekst eronder + Right Bracket Twice Dubbele rechterhaak + Rightwards Arrow Above Rechterpijl erboven + Rightwards Harpoon Above Rechterharpoen erboven + Secant Function Secansfunctie + Sine Function Sinusfunctie + Sine theta Sinus theta + Single Left Angle Enkele linkerhoek + Single Left Bar Enkele linkerstreep + Single Left Brace Enkele linkeraccolade + Single Left Bracket Enkele linkerhaak + Single Left Parenthesis Enkel linkerhaakje + Single Right Brace Enkele rechteraccolade + Single Right Bracket Enkele rechterhaak + Single Right Parenthesis Enkel rechterhaakje + Skewed Fraction Schuine breuk + Small change Kleine verandering + Square Root Wortel + Square Root with Degree Wortel met graad + Square Union Vierkante vereniging + Stack Object Gestapeld object + Stack Object (Three) Gestapeld object (drie) + Stacked Fraction Gestapelde breuk + Stacked, no bar Gestapeld, zonder streep + Subscript-Superscript Subscript en superscript + Summation Som + Summation from 0 to n Som van 0 tot n + Summation over binomials Som over binomen + Summation with Limits Som met grenzen + Summation with Lower Limit Som met ondergrens + Summation with Subscript Limit Som met grens in subscript + Summation with Subscript-Superscript Limits Som met grenzen in subscript en superscript + Surface Integral Oppervlakte-integraal + Surface Integral with Limits Oppervlakte-integraal met grenzen + Surface Integral with Stacked Limits Oppervlakte-integraal met gestapelde grenzen + Tangent Function Tangensfunctie + Tangent formula Tangensformule + Text over Colon Equal Tekst boven dubbele punt-gelijk + Text over Equal Tekst boven gelijkheidsteken + Tilde Tilde + Triple Dot Drie punten + Triple Integral Drievoudige integraal + Triple Integral with Limits Drievoudige integraal met grenzen + Triple Integral with Stacked Limits Drievoudige integraal met gestapelde grenzen + Underbar Onderstreep + Underbrace Accolade eronder + Union Vereniging + Union of intersections Vereniging van doorsneden + Union with Limits Vereniging met grenzen + Union with Lower Limit Vereniging met ondergrens + Union with Plus Vereniging met plus + Union with Subscript-Superscript Limits Vereniging met grenzen in subscript en superscript + Up Diagonal Dots Oplopende diagonale punten + Vector A Vector A + Vector AB Vector AB + Vertical Bars Verticale strepen + Vertical Dots Verticale punten + Volume Integral Volume-integraal + Volume Integral with Limits Volume-integraal met grenzen + Volume Integral with Stacked Limits Volume-integraal met gestapelde grenzen + Yields Levert op + x bar x met streep + All - Linear Alle - lineair + All - Professional Alle - professioneel + Current - Linear Huidige - lineair + Current - Professional Huidige - professioneel + Centered as Group Gecentreerd als groep + ✓ None ✓ Geen + Change to Level 6 Overschakelen naar niveau 6 + Change to Level 7 Overschakelen naar niveau 7 + Change to Level 8 Overschakelen naar niveau 8 + Change to Level 9 Overschakelen naar niveau 9 + Level 4 Niveau 4 + Level 5 Niveau 5 + Level 6 Niveau 6 + Level 7 Niveau 7 + Level 8 Niveau 8 + Level 9 Niveau 9 + Typing Typen + Search Zoeken + Title Bar Titelbalk + In the margin In de marge + No document is open. Create a document or open one first. Er is geen document geopend. Maak eerst een document of open er een. + Only for the selected text Alleen voor de geselecteerde tekst + Delete Bookmark Bladwijzer verwijderen + Hidden bookmarks Verborgen bladwijzers + Sort by: Sorteren op: + {language} (no dictionary) {language} (geen woordenboek) + No spelling dictionary for {language} is installed, so it isn't checked. WordCraft looks for the Hunspell dictionary {name} in the WORDCRAFT_DICTIONARIES folder and in the usual system locations. Er is geen spellingwoordenboek voor {language} geïnstalleerd, dus de spelling wordt niet gecontroleerd. WordCraft zoekt het Hunspell-woordenboek {name} in de map WORDCRAFT_DICTIONARIES en op de gebruikelijke systeemlocaties. + Narrow {size} all Smal {size} rondom + Normal {size} all Normaal {size} rondom + Type Character Code Tekencode typen + Auto ({percent}%) Automatisch ({percent}%) + Makes the ribbon, menus and dialogs larger or smaller. Auto follows your system's display scaling; 100% is one screen pixel per point. Document zoom is separate. Maakt het lint, menu's en dialoogvensters groter of kleiner. Automatisch volgt de schaalinstelling van je beeldscherm; 100% is één schermpixel per punt. Documentzoom staat daar los van. + Croatian Kroatisch + Show alignment guides when moving pictures and shapes Uitlijningshulplijnen weergeven bij het verplaatsen van afbeeldingen en vormen + Moved objects snap to the page's edges and centre, the margins and other objects. Hold Alt to move freely. Verplaatste objecten worden uitgelijnd op de randen en het midden van de pagina, de marges en andere objecten. Houd Alt ingedrukt om vrij te verplaatsen. + Word 2003 XML Document (*.xml) Word 2003 XML-document (*.xml) + Load System Installed Fonts… Systeemlettertypen laden… + All system fonts are already available. Alle systeemlettertypen zijn al beschikbaar. + Local font access is not supported by your browser. Toegang tot lokale lettertypen wordt niet ondersteund door je browser. + Local font access was denied. Toegang tot lokale lettertypen is geweigerd. + Loaded {count} system fonts. {count} systeemlettertypen geladen. + Odd Banded Rows Oneven gebandeerde rijen + Even Banded Rows Even gebandeerde rijen + Odd Banded Columns Oneven gebandeerde kolommen + Even Banded Columns Even gebandeerde kolommen + Top Left Cell Linkerbovencel + Top Right Cell Rechterbovencel + Bottom Left Cell Linkerondercel + Bottom Right Cell Rechterondercel + Inside Horizontal Border Horizontale binnenrand + Inside Vertical Border Verticale binnenrand + Text of the nearest paragraph with a given style (running headers) Tekst van de dichtstbijzijnde alinea met een bepaalde stijl (lopende kopteksten) + Style name: Stijlnaam: + Filter Recipients Geadresseerden filteren + Sort Recipients Geadresseerden sorteren + Filter and Sort Filteren en sorteren + And En + Or Of + Comparison Vergelijking + Compare to Vergelijken met + Add Condition Voorwaarde toevoegen + Remove condition Voorwaarde verwijderen + Sort by Sorteren op + Then by Vervolgens op + Ascending Oplopend + Descending Aflopend + Equal to Gelijk aan + Not equal to Niet gelijk aan + Less than Kleiner dan + Greater than Groter dan + Less than or equal Kleiner dan of gelijk aan + Greater than or equal Groter dan of gelijk aan + Is blank Is leeg + Is not blank Is niet leeg + Contains Bevat + Does not contain Bevat niet + {count} of {total} recipients will be merged. {count} van de {total} geadresseerden worden samengevoegd. + Take the last match on the page Laatste overeenkomst op de pagina nemen + Show its list number Lijstnummer weergeven + Show its number relative to this field Nummer ten opzichte van dit veld weergeven + Show its number with all higher levels Nummer met alle hogere niveaus weergeven + Number only, without the label's words Alleen het nummer, zonder de woorden van het label + Add whether it is above or below Aangeven of het zich boven of onder bevindt + Copy to Kopiëren naar + Rename Document Documentnaam wijzigen + Document name Documentnaam + Rename Naam wijzigen + Account Account + Privacy Options Privacyopties + Trust Center Vertrouwenscentrum + Privacy for this document Privacy voor dit document + Remove my name from comments, changes and file properties when saving Mijn naam uit opmerkingen, revisies en bestandseigenschappen verwijderen bij het opslaan + Leave out the date and time of comments and tracked changes when saving Datum en tijd van opmerkingen en revisies weglaten bij het opslaan + The document you are editing keeps the names and dates; only the saved file leaves them out. Het document dat je bewerkt behoudt de namen en datums; alleen in het opgeslagen bestand worden ze weeggelaten. + Privacy Settings Privacyinstellingen + Waiting for the save to finish… Wachten tot het opslaan is voltooid… + Saving {name} was cancelled, so it is still open and nothing was lost. Het opslaan van {name} is geannuleerd, dus het document is nog geopend en er is niets verloren. + Paper Papier + Paper size: Papierformaat: + Custom size Aangepast formaat + More Paper Sizes… Meer papierformaten… + The margins don't fit on a page this size; make the margins smaller first. De marges passen niet op een pagina van dit formaat; maak de marges eerst kleiner. + Full Screen Volledig scherm + Full\nScreen Volledig\nscherm + Use the browser's full screen (F11) Volledig scherm van de browser gebruiken (F11) + {name} {height} × {width} in {name} {height} × {width} inch + {name} {height} × {width} mm {name} {height} × {width} mm + {width} × {height} mm {width} × {height} mm + {kind}, {columns} × {rows}, {size} ({paper}) {kind}, {columns} × {rows}, {size} ({paper}) + Researcher needs an online service; WordCraft keeps your documents offline. Onderzoeker heeft een onlinedienst nodig; WordCraft houdt je documenten offline. + These settings will be used for new documents. Deze instellingen worden gebruikt voor nieuwe documenten. + can't split at the first row kan niet splitsen op de eerste rij + select two or more cells to merge selecteer twee of meer cellen om samen te voegen + there is no ink to replay er is geen inkt om af te spelen + there's no citation after the caret er staat geen citaat na de cursor + this item is already at the top level dit item staat al op het hoogste niveau + Envelope #10 Envelop nr. 10 + Envelope DL Envelop DL + Reading Mode Leesmodus + Column width Kolombreedte + Text size Tekstgrootte + Edit Document Document bewerken + Go to what: Ga naar: + Type +2 or -1 to move from the page you are on. Typ +2 of -1 om vanaf de pagina waarop je je bevindt te navigeren. + Suggested Voorgesteld + Set Proofing Language… Controleertaal instellen… + Language Preferences… Taalvoorkeuren… + Printer Printer + Select a printer Selecteer een printer + Refresh printers Printers vernieuwen + Two-sided printing Dubbelzijdig afdrukken + Printer default Standaardinstelling van de printer + One-sided Eenzijdig + Two-sided: long edge Dubbelzijdig: lange rand + Two-sided: short edge Dubbelzijdig: korte rand + Grayscale Grijstinten + Finding printers… Printers zoeken… + No printers found. Configure a system printer or export a PDF. Geen printers gevonden. Configureer een systeemprinter of exporteer een PDF. + Printer discovery failed. Zoeken naar printers is mislukt. + Submitting print job… Afdruktaak wordt verzonden… + Print job submitted. Afdruktaak verzonden. + Print job submitted: {receipt} Afdruktaak verzonden: {receipt} + Print failed: the printing worker stopped. Afdrukken mislukt: het afdrukproces is gestopt. + Print settings Afdrukinstellingen + Submit print job Afdruktaak verzenden + For which item: Voor welk item: + Heading Kop + Heading text Koptekst + Insert reference to: Verwijzing invoegen naar: + Reference type: Verwijzingstype: + Dictionaries Woordenboeken + Dictionaries folder: Woordenboekenmap: + Not set: the system's usual places Niet ingesteld: de gebruikelijke systeemlocaties + Hunspell .aff and .dic files here (pl_PL, pt_BR, it_IT, hr_HR, tr_TR) are used before the system's. Hunspell .aff- en .dic-bestanden hier (pl_PL, pt_BR, it_IT, hr_HR, tr_TR) worden vóór die van het systeem gebruikt. + {language}: {file} {language}: {file} + {language}: no dictionary found {language}: geen woordenboek gevonden + Recover Herstellen + Mirror margins Marges spiegelen + Existing File or Web Page Bestaand bestand of webpagina + Link to: Koppeling naar: + Place in This Document Plaats in dit document + Column {n} Kolom {n} + Header row Koprij + No header row Geen koprij + My list has Mijn lijst heeft + Formula: Formule: + Paste function: Functie plakken: + Narrow Smal + Medium Gemiddeld diff --git a/crates/ui-egui/src/i18n/tests.rs b/crates/ui-egui/src/i18n/tests.rs index e23fa4552e..5c393ff805 100644 --- a/crates/ui-egui/src/i18n/tests.rs +++ b/crates/ui-egui/src/i18n/tests.rs @@ -51,8 +51,8 @@ fn the_first_supported_preferred_language_wins() { assert_eq!(first_supported(["en-US", "ja-JP"]), Some(Lang::EN)); assert_eq!(first_supported([UNSUPPORTED, "qtz"]), None); assert_eq!(first_supported(["fr-FR", "de-DE"]), Some(lang("fr"))); - assert_eq!(first_supported(["nl-NL", "de-DE"]), Some(lang("de"))); - assert_eq!(first_supported(["nl-NL", UNSUPPORTED]), None); + assert_eq!(first_supported([UNSUPPORTED, "de-DE"]), Some(lang("de"))); + assert_eq!(first_supported(["nl-NL", "da-DK"]), Some(lang("nl")), "nl wins over da now that both are languages"); assert_eq!(first_supported([]), None); } From 0fa438c7d19a09c5fc2f542508073c69b02a672b Mon Sep 17 00:00:00 2001 From: Naxvog Date: Wed, 14 Oct 2026 21:20:59 +0200 Subject: [PATCH 072/336] Hyphenation: German text follows the German patterns Runs in German (de, de-DE, de-AT, de-CH) hyphenated with the English patterns, giving breaks such as "unterlieg-ende" and "rechtli-cher". They now use hyph-utf8's German patterns for the reformed spelling (MIT, bundled unmodified as assets/hyphenation/de.pat). German gets a ProofLang of its own that, like Bulgarian, hyphenates only: no spelling or grammar marks yet (#1564). Portuguese, Italian and German share one pattern lookup. Co-Authored-By: Claude Opus 5.5 --- ATTRIBUTION.md | 1 + ROADMAP.md | 6 +- assets/hyphenation/de.pat | 36797 ++++++++++++++++++++++++++++++++ crates/proof/src/hyphen.rs | 57 +- crates/proof/src/lang.rs | 15 +- crates/proof/src/lib.rs | 16 +- crates/ui-egui/src/options.rs | 4 +- docs/gaps.md | 5 +- docs/localization-parity.md | 4 +- 9 files changed, 36865 insertions(+), 40 deletions(-) create mode 100644 assets/hyphenation/de.pat diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index 14b58afcdc..59b726a6c4 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -15,6 +15,7 @@ Generated-in-code art is original and has no file to list: the UI icon set (`cra | `assets/hyphenation/pl.pat` | Hanna Kołodziejska, Bogusław Jackowski, Marek Ryćko (Polish TeX hyphenation patterns, 1987–1995); hyph-utf8 package | https://github.com/hyphenation/tex-hyphen (`hyph-utf8/tex/generic/hyph-utf8/patterns/tex/hyph-pl.tex`, SHA-256 `83ddbbd21bc06a44bc4f4a4c49c38782e809cff5f026b764c044daa29e818843`), bundled unmodified | MIT (the file is offered under MIT or Knuth's public-domain terms; WordCraft uses MIT, whose notice is in the file's header) | 4,053 Liang patterns and 20 exception words; Polish hyphenation | | `assets/hyphenation/pt.pat` | Pedro J. de Rezende, J. Joao Dias Almeida, Leonardo Araujo, Aline Benevides (Portuguese TeX hyphenation patterns, 1987–2024); hyph-utf8 package | https://github.com/hyphenation/tex-hyphen (`hyph-utf8/tex/generic/hyph-utf8/patterns/tex/hyph-pt.tex`, SHA-256 `c110a39e501db372d1d755e1c64464321a2efa7d8e9617f66001000389b941f2`), bundled unmodified | BSD 3-clause (the file's licence header names it and carries the notice) | 426 Liang patterns and 2 exception words; Portuguese hyphenation | | `assets/hyphenation/it.pat` | Claudio Beccari (Italian TeX hyphenation patterns, 2008–2014); hyph-utf8 package | https://github.com/hyphenation/tex-hyphen (`hyph-utf8/tex/generic/hyph-utf8/patterns/tex/hyph-it.tex`, SHA-256 `6ce56ed6ed2ca7c0688838e366e6595e0429f984f4fc6e96ec045cebb1e76a47`), bundled unmodified | MIT (the file is offered under LPPL 1.3+ or MIT; WordCraft uses MIT, whose notice is in the file's header) | 355 Liang patterns, no exception words; Italian hyphenation | +| `assets/hyphenation/de.pat` | Deutschsprachige Trennmustermannschaft: Stephan Hennig, Werner Lemberg, Günter Milde, Sander van Geloven, Georg Pfeiffer, Gisbert W. Selke, Tobias Wendorf, Keno Wehr (German TeX hyphenation patterns, reformed spelling, 2013–2024); hyph-utf8 package | https://github.com/hyphenation/tex-hyphen (`hyph-utf8/tex/generic/hyph-utf8/patterns/tex/hyph-de-1996.tex`, SHA-256 `374ad1ce3263f8a2791ec070ef9b516c532dae64456f3fb656e617e8ae53a9d3`), bundled unmodified | MIT (the notice is in the file's header) | 36,709 Liang patterns, no exception words; German hyphenation (de-DE, de-AT, de-CH) | | `assets/app-icon/wordcraft-1024.png` | WordCraft contributors | original artwork drawn in SVG (`assets/app-icon/wordcraft.svg`), rendered by `packaging/icons.sh` | MIT OR Apache-2.0 | Placeholder app icon until the WordCraft mascot drawing lands | | `packaging/macos/dmg/background.svg` | @XusBadia | original work for this repository, derived from the WordCraft app icon (`assets/app-icon/wordcraft.svg`, referenced via ``, not copied); text is outlined Inter / JetBrains Mono glyph paths | MIT OR Apache-2.0, like the code ([`LICENSE-MIT`](LICENSE-MIT), [`LICENSE-APACHE`](LICENSE-APACHE)) | WordCraft macOS DMG window background (2026-10-08); used for the Finder window of the macOS DMG, rendered to `background.tiff` by `packaging/macos/dmg/generate.py` | | `packaging/macos/dmg/background.tiff` | @XusBadia | Rendered from `packaging/macos/dmg/background.svg` by `packaging/macos/dmg/generate.py` | MIT OR Apache-2.0, like the code ([`LICENSE-MIT`](LICENSE-MIT), [`LICENSE-APACHE`](LICENSE-APACHE)) | 1x + 2x HiDPI TIFF for the macOS DMG window; includes the app icon (see its row) and LittleCMS's built-in sRGB profile ("No copyright, use freely") | diff --git a/ROADMAP.md b/ROADMAP.md index 62a5e70db2..6591c241ab 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **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 (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 (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 (progress log: German text hyphenates by the German patterns); 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 (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-17 · **Change:** 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. @@ -117,7 +117,7 @@ Also shipped: Traditional Chinese (`zh-hant`), Ukrainian (`uk`), Serbian (`sr`, system-locale selection and a saved language choice (Italian also has 51 strings none of the others had yet); Estonian, Turkish and Italian proofing are not included. Hungarian (`hu`) has a complete 2,419-entry catalog, also without proofing. Vietnamese (`vi`, #1434) has a complete 2,448-entry catalog, also without proofing. -Proofing: English, Polish, Portuguese and Russian (spelling, grammar, hyphenation), plus Czech and Indonesian (system Hunspell spelling, generic checks), chosen per run from its language. Word ships +Proofing: English, Polish, Portuguese and Russian (spelling, grammar, hyphenation), plus Czech and Indonesian (system Hunspell spelling, generic checks), chosen per run from its language; German hyphenation only. Word ships 30 interface languages and proofing for ~50. Detail: [docs/localization-parity.md](docs/localization-parity.md). ## Upcoming @@ -219,6 +219,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | 2026-10-14 | Hebrew and Arabic in exported PDFs copy and search as whole words (#1383): right-to-left glyphs are written left to right as they appear, the order Chrome and Edge (PDFium) and poppler read text back from (checked by hand), instead of in reading order, which they reversed (`שלום` came back as `םולש`, Arabic in pieces). Lam-alef stays whole and vowel marks follow their letters, though Chrome and poppler can still break a fully vowelled word with spaces. The page looks the same. Readers that take glyphs strictly in file order get only plain Hebrew runs right, through `/ReversedChars`; screen readers are unverified | | 2026-10-14 | HTML import (#1381): header cells (``) are bold unless their own CSS says otherwise, and `text-align` on a cell, its row, row group or table aligns the cell's paragraphs (a paragraph's own alignment wins), so `wordcraft-cli convert page.html out.docx` writes the bold header and `w:jc` right-aligned cells. HTML, Markdown and plain text, which give no page size, open on the region's paper (A4 in Italy, Letter in the US) like a new blank document, in the app and in `wordcraft-cli`; documents with their own page size keep it | | 2026-10-14 | Custom page sizes (#1433): Layout › Size ends with More Paper Sizes…, which opens Page Setup's new Paper group — a Paper size list (the named sizes, or Custom size) and Width and Height in the measurement unit; OK sets the size only when it changed, and a wider-than-tall size comes out landscape. `layout.size` now clamps each side to Word's 0.1"–22" and refuses a page the margins don't fit (the margins rule: 36 pt of text room), and Page Setup checks the size and margins together before applying either | +| 2026-10-14 | German text hyphenates by German rules: runs in German (`de`, `de-DE`, `de-AT`, `de-CH`) use hyph-utf8's German patterns for the reformed spelling (MIT), so automatic hyphenation gives `un-ter-lie-gen-de` and `öf-fent-lich-recht-li-cher` instead of English-pattern breaks like `unterlieg-ende` and `rechtli-cher`; Swiss `ss` splits like any double consonant (`Stras-se`). German still has no spelling or grammar marks (#1564) | | 2026-10-13 | Highlighted text reads on a dark page (View › Switch Modes, #1252): highlights and character shading are dimmed, keeping their hue (yellow turns a dim olive, as in Word), and the text on them is drawn light (coloured text below 4.5:1 is drawn white, and underlines and strikethroughs follow the same rule), instead of white text on bright yellow; light page, print and exports unchanged | | 2026-10-13 | Full screen (#980): F11, or View › Window › Full Screen, fills the screen with the window (⌃⌘F on macOS); F11 again or Escape gives back the window as it was. Next Field, which Word puts on F11, keeps Word's Alt+F1 and Alt+Shift+F1 (and Shift+F11). The web app leaves full screen to the browser's own F11 | | 2026-10-13 | Double-clicking the top or bottom margin of a page creates its header or footer when it has none and puts the caret in it, as in Word (#960): the one that page shows (different first page, odd and even pages), only in Print Layout; one left empty is taken away again when you leave it, so nothing empty is saved. `insert.editHeader`/`insert.editFooter` take `{"page"}` for agents | @@ -419,6 +420,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | Date | Change | Summary | |---|---|---| +| 2026-10-18 | trivial | Progress log: German text hyphenates by the German patterns | | 2026-10-18 | trivial | Progress log: Different First Page headers reachable from page 1 (#2826) | | 2026-10-16 | trivial | Progress log: Web Layout fills the window width (#1876) | | 2026-10-17 | trivial | Progress log: DOCX glossary documents (building blocks) | diff --git a/assets/hyphenation/de.pat b/assets/hyphenation/de.pat new file mode 100644 index 0000000000..15641e1ec1 --- /dev/null +++ b/assets/hyphenation/de.pat @@ -0,0 +1,36797 @@ +% title: German Hyphenation Patterns (Reformed Orthography, 2006) +% +% notice: TeX-Trennmuster für die reformierte (2006) deutsche Rechtschreibung +% +% version: 2024-02-28 +% +% authors: +% - +% name: Deutschsprachige Trennmustermannschaft +% contact: trennmuster@dante.de +% +% copyright: Copyright (c) 2013-2024 +% Stephan Hennig, Werner Lemberg, Günter Milde, +% Sander van Geloven, Georg Pfeiffer, Gisbert W. Selke, +% Tobias Wendorf, Keno Wehr +% +% licence: +% name: MIT +% url: https://opensource.org/licenses/mit-license.php +% text: > +% Permission is hereby granted, free of charge, to any person +% obtaining a copy of this software and associated documentation +% files (the “Software”), to deal in the Software without +% restriction, including without limitation the rights to use, +% copy, modify, merge, publish, distribute, sublicense, and/or +% sell copies of the Software, and to permit persons to whom the +% Software is furnished to do so, subject to the following +% conditions: +% +% The above copyright notice and this permission notice shall be +% included in all copies or substantial portions of the Software. +% +% THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +% OTHER DEALINGS IN THE SOFTWARE. +% +% source: https://repo.or.cz/w/wortliste.git?a=commit;h=304aaa2188a75e57afe36bad0443d90a9c715b8e +% +% language: +% name: German, reformed spelling +% tag: de-1996 +% +% hyphenmins: +% generation: +% left: 2 +% right: 2 +% typesetting: +% left: 2 +% right: 2 +% +% texlive: +% encoding: ec +% babelname: ngerman +% use_old_patterns_comment: Kept for the sake of backward compatibility, but newer and better patterns by WL are available. +% legacy_patterns: dehyphn.tex +% message: German hyphenation patterns (reformed orthography) +% package: german +% +% =========================================================================== + +\message{German Hyphenation Patterns (Reformed Orthography, 2006) `dehyphn-x' 2024-02-28 (WL)} + +% +% The used patgen parameters are +% +% 1 1 | 1 3 | 2 3 1 +% 2 2 | 2 4 | 1 5 1 +% 3 3 | 3 5 | 1 6 1 +% 4 4 | 4 6 | 1 7 1 +% 5 5 | 5 12 | 1 8 1 +% 6 6 | 6 12 | 1 9 1 +% 7 7 | 7 12 | 1 9 1 +% 8 8 | 8 12 | 1 9 1 + +\patterns{% +.ab3a +.abb2 +.ab5erk +.ab5er4r +.abi2 +.ab3it +.ab3l +.ab5le +.ab3ol +.ab3or +.ab3sa +.ab5so +.ab3u +.ach4sa +.achse4 +.ach4sp +.achts4 +.ack4e +.ae2 +.aer6schl +.ago3 +.ag4r +.ag2u +.ai2s +.ak6t5en6de. +.ak6t5en6den +.ak6t5en6des +.al4bl +.alb3r +.al4bra +.alb6rec +.ale3u4 +.ali3 +.al5l6en +.alle4r5 +.al4lin +.allo3 +.all3s +.al4mau +.al4pl +.al5s4e +.al4t5ak +.al6t5ang +.alt6an6l +.altan6s +.al4tei +.al4t5e4l +.al6t5er6f +.al6t5er6p +.alter6s5 +.al4t3h +.al4ti +.al4tor +.al4t5ra +.al4tri +.al4tro +.alt3s4 +.al4t3u +.al4u +.alu3s +.ampe4 +.am5sta +.amt4s3 +.ana3c +.an3ae +.an5alg +.ana3t +.anden6ke +.an3e +.ang4a +.an5g4li +.ang4r +.angs4 +.angst5 +.ani4s +.anis5c +.ank2 +.anla6gentec +.an3n4a +.an3o2 +.an4ora +.ans2 +.ansch4 +.an4si. +.an5sp +.an4tag +.anti5s +.anz2 +.an5zw +.apfel5 +.app6le. +.are3n +.ar4m5ac +.ar4mar +.ar4mau +.ar4mei +.ar4mun +.ar4sc +.arsch5l +.ar4tan +.ar4tau +.ar4t5ei +.arter4 +.ar6t5erh +.ar6t5erk +.ar4t3r +.arz2 +.arzt3 +.as2b +.asbe4 +.asie4 +.as4ko +.as2p +.as4ta. +.as5tar +.as4zen +.ata3 +.ate4m3 +.at4h +.aue4n3 +.au2f3 +.auf5s4 +.auge4 +.au2s3 +.aussen6dung. +.auto6ren. +.auto6rin +.ax2 +.ärm4s +.är6schl +.ät2h +.ät2s +.bahner6 +.ba4ran +.bau5chi +.bauer6f +.bau3f4 +.beige4 +.bei6ge. +.be3n4a +.be3n4e +.be3r4a +.ber4e +.ber4ga +.berg5ab +.berg5ar +.berg5ast +.ber6g5e6b +.ber6gin +.ber4g5l +.ber4g5o +.ber4g5r +.berg5s +.ber4t5r +.be3s4a +.be6t5raum +.bi2e +.bi2o +.bi4os +.bi4t3a +.bi4t3r +.blau3 +.blei5s4 +.boge4 +.bogen5 +.bogens6 +.bor4d5i +.bo4s3k +.bre4a +.brecher6 +.bre6cherk +.brid4 +.briefen6 +.brie6f5end +.bruch5s +.brut5s +.bu6cher +.buchse6 +.bue5be +.bu4ser +.buster6 +.buß3 +.bü3be +.by2t +.ce4ra +.ch2 +.ch4a +.char6me. +.char6mes +.ch4i +.chi5er +.dab2 +.da6ch5en +.da3la +.dan4c +.da2r +.dar5in +.dar4m3 +.dar3u +.da4te. +.da4tes +.daue4 +.de4al +.de3ch +.de6ck5eri +.dein4 +.de5ins +.de5int +.de6m5ents +.de3na +.den4ka +.den4kl +.den4ko +.den4kr +.denta4 +.de3od +.de5ran +.de4rei +.de5r4en +.de5skr +.des4pe +.de4spo +.des5se +.de3st +.des4ti +.de3xa +.di2a +.di5ae4r +.di3ä4r +.di2e +.die5ne +.dien4s +.dienst5a6d +.dienst5r +.di5s4ti +.do4mo +.dorf3 +.dor6fer +.do4tr +.drah6t5en +.drucker6zeu +.due3b +.du6schr +.dü3b +.dy2s3 +.ebe4r +.echo3 +.eck5erk +.ede4l +.eh4e +.ehe3i4 +.ehein4 +.ehe3n +.ehre4 +.eh4r5er +.eichs4 +.eich5ste +.eide4r5 +.ei3e2 +.eier3 +.eigener6 +.ei4lau +.ein3a +.ei4nat +.eine4 +.ei4n5eb +.ei6n5en6g +.eins4p +.eins6tem +.einzel6lig +.eisen5 +.ei4spe +.eis3s4 +.ei4s3t +.ei6ste +.ei4s3u +.ei4tr +.ek5lam +.ek3li +.ek3s +.el4a +.el4b3i +.el4bl +.elb5s +.el4fei +.elfens6 +.el4fl +.en1 +.en4d3a +.en4d5ei +.en4d5er4 +.en5der. +.en6dost +.en4d3r +.end3s +.en4due +.en4dü +.en4g3l +.eng5sta +.enk2 +.enn4st +.en4t3a +.en4tä +.en4t5ei +.en4tem +.en5thalp +.en4thi +.en4t5io +.en4toe +.en4t3r +.en5trep +.en5trop +.ent3s4 +.epi3c +.ep3o4d +.er6b5aen +.er4b5än +.er4b5ei +.er4b5er +.er6brecht +.er4bu +.er6d5achs +.er4dan +.er4dar +.er4d5ei +.er4del +.erden6k +.er4d5ep +.er4d5er +.er6d5ess +.ere5ch +.er3ei +.er3er +.erfolgsauto6 +.er3in +.erk2 +.er3lö +.ern4s +.er3o4b +.er3oe +.ers2 +.er6stab +.er6st5ein +.er6ster6 +.erst5eri +.erst5ers +.er6stim +.er6stritt. +.er6stritten. +.er4z5el +.erzen4 +.er6z5eng +.er4z5er +.es3p +.es4s3t +.es3ta4 +.es3te +.es5tra +.est5ri +.et2h +.et2s +.eu1 +.euch4 +.eu3g2 +.eur4 +.eu3t +.eve4r +.ex3em +.fahrzeugin6 +.fan4gl +.fang5s +.faus6t5r +.fe3la +.felden5 +.fel4d5r +.fel6lau +.fel4s5a +.fel4sp +.fel4s5t +.fen2 +.fer4n5o +.fe4st +.fest5r +.fi3d +.fi5est +.fi4le. +.fi4len +.film5a +.fil4s3 +.fin4s +.fi2r +.flu4g3 +.flus6s5en +.former6 +.for5tu +.fra4s +.fs2 +.fu4sc +.fus4st +.ga4me. +.ga4mes +.gan4ga +.gang5sc +.garnen6 +.gar6n5end +.ga2s +.gas3e +.gas3s +.gas5tein +.gas6tras +.ga2t +.geb2 +.gebe4a +.gege4n5 +.gel4b5r +.ge3lu +.ge3na +.gen6aug +.ge3ne +.gene5cke +.gen4ei +.ge3nu +.ger4a +.ger4e +.ge3ro +.ge3ru +.ge3sa +.ges4e +.ges4i +.ges4p +.ge5stak +.ge7stei +.ge3t4u +.gi2u +.gla4s5c +.gol6der +.gs2 +.gu3ru +.hafe4 +.haf6ter +.haft5s4 +.hal4br +.han4a +.han4f +.hang5s +.ha5sche +.hauser6w +.hau4su +.haut3 +.hau4to +.he2r +.he5rae5 +.her6b5ra +.he5rer +.he3ri +.he6r5inn +.he5ros +.he4sp +.hete3 +.he3xa +.hi2n +.hin3a +.hin3u +.hi2p +.hi4s +.hitze5 +.ho4f5en +.hof3s4 +.ho4la +.ho4met +.ho3ra +.hor4t5r +.höch4s +.hu4fer +.hu3la +.hund4 +.hu3s4a +.ia2 +.ibe3 +.ima4ge +.imm4 +.immen4 +.in1 +.ind2 +.ine2 +.in3eg +.in3i +.ink4 +.inse4l5 +.in5skr +.inu3 +.io2d3 +.ioni5e +.ire3 +.is2a +.isar3 +.it2h +.joni3 +.jor3 +.kabe4 +.ka4b5l +.ka3la +.kal4ta +.kal4t5r +.ka4t5i4o +.ke3ra +.ker4bl +.ker4na +.ke3ro +.ke2t +.ki2e +.ko5be +.ko4ter +.kraf4 +.kraft5 +.ks2 +.kue3b +.ku4ran +.kü3b +.la5ert +.lage4n5 +.la3ho +.lan4d5a +.lan6de6mi +.lan4dr +.lan4gl +.lan4g5r +.lang5s +.lan3t +.lase4 +.las4t5r +.le4ar +.leder5 +.le4gas +.lei4bl +.lei4br +.leider6 +.lei6derf +.lei4tr +.le3n4i +.len4k5a +.len4kl +.len4kr +.les3t +.le3th +.lich6ter6sc +.li2f +.li5gn +.lin4kl +.li3o +.li4ve. +.lo4g5in +.lo4sc +.losch5 +.lo4sk +.loss4 +.los3t +.lo3th +.lo4tr +.lo5ver +.lus4t5e +.luster6 +.lu5t4h +.ly2s3 +.ma5lae +.mal5e4c +.ma5len +.mal4z5a +.man3d +.mani4e +.man5t +.mas4s5u +.ma4st +.ma4ß3u +.mat4c +.mate3 +.märz3 +.md2 +.mee4ru +.mel4a +.me3no +.men6schl +.men6schw +.me3n4u +.me3nü +.me4sa +.mes4sp +.mes4st +.me4to +.met6tend +.mi2e +.mie4t5r +.mi2f +.mil4z3 +.mi4s3a +.mi6scha +.mi6schei +.mi6sch5l +.mi6schn +.mi6scho +.mi6sch5r +.mi6sch5w +.mi2t +.mi4t3a +.mit3e +.mit5in +.mm2 +.mo2i +.mo4nau +.mon6dru +.möbe4 +.musiker6fo +.nabe4 +.nae4s5c +.na4ha +.nah5ei +.namens5 +.na4mes +.nas4s5c +.na3t4h +.na3to +.nä4s3c +.nebe4n +.nebens6 +.neider6 +.nei6d5err +.nere3 +.ner4f +.ne3ro +.ne2s +.neuer6b +.nich6t5e +.ni4e +.nie5r +.ni2l +.nil3a +.no6tentr +.no4t3h +.no4top +.not3r +.nude4 +.nul6ler +.nus2 +.nu4t3r +.oa3s +.obe4n +.obe6r5ei +.ob6ers +.ob3i4t +.ob3l +.obs4k +.ob4sta +.oe2 +.oh4r5ei +.oh4rer +.oper4 +.orge4 +.orgi3 +.ort4s5a +.ort4s5e +.ort4st +.os4t3a4 +.ost5e4c +.os4t5ei +.os6t5en6de +.os6teng +.oste6r5e +.os4tes +.os4ti +.osto4 +.ost5om +.ost5ran +.ost5ri +.ot3a +.ou4te +.ozo2 +.ölk2 +.pa3b2 +.pa4nen +.par4ka +.par4k5l +.par5th +.pas6sers +.paulet6 +.pe4c +.pe3la +.pel4za +.pel4zw +.pe3na +.peri3 +.pes4t5e +.pf2 +.pf4u +.ph2 +.po4lei +.po4p3l +.postei6 +.prach4 +.pu5ert +.ra5ch6e +.ra4d5ei +.ra4der +.ra3el +.ran4d5r +.ran6g5e6b +.ran4gl +.rar4s +.rau5f4r +.rau4m3 +.rau6schl +.rau6schw +.rau4se +.re4b3l +.re5cha +.re5chr +.rei4bl +.re5insz +.reise5i +.rei4st +.reli5e +.ren4n5a +.re3sk +.rester6 +.res6t5ers +.re5stit +.rest5re +.re3un +.ri4as +.ri3so +.ro2a +.roe4s +.roes5c +.ro4h3a +.roher4 +.ro6h5erz +.rom4a +.roset6te. +.rost5e6c +.ro4tan +.ro4tau +.rot5ei +.ro4t3r +.rö2s +.rös3c +.rue3b +.rue6ck5er6 +.ruh4r3 +.ru4m3e +.rump6f5en +.runder6 +.rü3b +.rü6ck5er6 +.sa3br +.sa4fe +.sali5e +.sami3 +.san6dac +.sau3c +.sch4 +.scha4c +.schaf6t5end +.scheiner6 +.sco2 +.se2a +.se2e +.see3i +.seein4 +.see5r +.se4ha +.seh5ere +.se4hi +.se4h3o +.sen4f +.senf5l +.senf5r +.sen4kr +.se5no +.sen3s +.se3ro +.se3r4u +.se2t +.sex3p +.sha2 +.si2e +.siege6s5 +.si5err +.si3es +.silbe4 +.si4te +.sit6zer +.ski3e +.ski3s4 +.sli2 +.so3n4a +.sour4 +.spani5er. +.spiege6lei +.st2 +.st4a +.sta4ge +.stau6be6cken. +.st4e +.ste4h5a +.ste4i +.steiner6k +.ster6na +.sto4n +.sto4re +.st4r +.stro6ma +.su6cherf +.su6cher6g +.sue3b +.sumpf5l +.ta3c +.tage4s5 +.ta5lar +.tan4k5a +.tan4k5l +.tan4ko +.tan6z5er +.ta3ra +.tart4 +.ta4tab +.ta4t3h +.ta4top +.ta4tor +.ta4tri +.ta4t3u +.tau4f5o +.tau6schl +.te2e +.te2f +.tehe5 +.tei4l5a +.tei4l5ä +.teiler6s +.tei6l5ersc +.tei4l5i +.teils4 +.te5in. +.te5le +.telle4 +.te3ra +.te4st +.test5ae +.test5ä +.te6ste +.test5ei +.tester6h +.test5r +.te4te +.te3xa +.texter6 +.tex4tr +.ti2a +.tie6f5ern +.ti4me. +.ti4mes +.ti2s +.tischen6 +.tisch5end +.tisch5l +.tisch5w +.tit2 +.tit4a +.tite4 +.tode4 +.to4der +.todes5 +.to4nar +.to4nat +.tonen4 +.to6n5eng +.toner6n +.toner6z +.to4nin +.tons4 +.to4pan +.top3e +.to4pl +.to4pr +.tor5an +.to4rei +.to4th +.to4t3r +.to2w +.tow3e +.tra4de +.trag5s +.tri5es +.trost5 +.tsa3 +.ts4c +.tse3 +.ts4u3 +.tu4e +.tu5end +.tu3ri +.tur4m3 +.ub2 +.ue2 +.ue4b +.ufe2 +.ufer3 +.uh3l +.ul2b3 +.ult4 +.um1 +.ume2 +.umo2 +.ums2 +.una2 +.unab3 +.un3dr +.une2 +.unen4t5 +.un3er +.un3g2 +.unge5n4 +.uni4t +.unk2 +.un3s4t +.ur3a4d +.ur3af +.ur3a4m +.ur4anb +.uran6fa +.ur6anfo +.ur4anh +.ur4anp +.ur3at +.ur3c +.ur3e +.ur4e3t +.ur4i +.ur3o4m +.ur3o4p +.uro3s +.ur3s2 +.ut3r +.ves3t +.vi3d +.vol6lerw +.wah4l3 +.wah4n3 +.wanderer6 +.ware4 +.waren5 +.wa2s +.we4b5ei +.weg3s4 +.weh4r5u +.weine4 +.wei6neb +.weines6 +.we6ing +.wei4ta +.welter6k +.welt5s +.wer6k5er6 +.wer4k5r +.wer4tr +.wese4 +.wetterer6 +.wi2e +.win6d5er5 +.winte4 +.wo2r2 +.worten6 +.wort5end +.xan3 +.xe1 +.ya2l +.zeit5r +.zeit5s4 +.zel4l5a +.zelle4 +.zel6leb +.zel4li +.zel4th +.ze3ro +.zi4e +.zie4l5u +.zin4ka +.zin4s5c +.zin4st +.zi6schl +.zol6lers +.zuch4 +.zu5ende. +.zu4gei +.zu6g5end +.zu4glo +.zu4g5ra +.zug5st +.zurue4 +.zurü4 +.zwe2 +.zweigen6 +.zwei6g5end +a1ab +a2aba +aa2be +a3ace +a3achs +a1ae2 +aa2gr +aags2 +a3akt +aal3ac +aal3an +aal3ec +aa4lei +aale3s +aalf2 +aal5la +aa2l3o +aal3ö +aals4t +aal5ste +a1am +2a1an +a3an. +a2anä +aan3e +a1a2q +aa2r3a +aa3re +aa4r3ei +aa4r5ers +3aarg4 +aar3in +aa4rit +aa4r3oe +aar3ö +aarp2 +aar3t +aar3z +3aase +a3ass +aa2su +aata2 +aa2t3h +aa2t3r +aat2s3 +aatsche6 +a1au +a1ä2 +2abah +abais4 +ab4ak +2abal +ab3alt +ab2am +2aban +2abas +4abat +abat4t5a +ab3auf +ab1ä +2abäd +2abär +ab2äu +abbe4p +4abbine +abb5rig +1abd +abda3c +2abe. +2abec +ab3eic +abe3id +ab3eil +abein4s +abein4t +4a3b4el +abe4l5ac +abel5a4d +4aben. +abe4nin +a4berd +aber4ka +ab3erz +aber4zi +aber4zo +aber4zö +2abes +abes4p +ab5esse +abes4t +2a3bet +1abf +1abg +1abh +4abil +ab3inf +ab3inh +ab3ins +ab3iso +abi3s4t +1abk +ab1l +a2bla +ab3lag +3ablas +3ablau +a4bläu +4ab4le. +3a4bled +a4bleg +3ablei +4a3blem +4ab4ler +5ablesu +4ablet +a4bleu +a4blin +a4bloe +ab4log +a2blö +1abn +ab2o +2abod +2aboe +2abol +a2bon +abor5ang +abo5rato +abor3s +2abot +2ab2ö +ab1r +a4b3rec +ab3rie +ab4rot +2abrö +3abruf +1abs +2abs. +4absaer +4absap +4absar +4absär +ab3sc +ab3se +2absf +ab3so +4ab4sof +abs2p +4abspi +abs4pr +4abst. +ab3sta +ab3sti +ab4s5tro +3abtei +abtes3 +2abu +a4bueb +ab4ug +a2bum +a4bunt +ab3usu +a2büb +1abw +2aby +1abz +ab5zun +2ac. +2aca +1ac1c +a3cee +a3cem +a3cen +2ach. +ach3ab +ach5aen +ach3ak +ach5all +ach3an +a5chari +ach5art +ach3as +ach3au +ach3än +2achb +a3che +a4ch5e4ck +ach5e4he +ach5eic +ach5ein +ach5eis +a4ch3ep +ach5erbe. +ach5erben +ach5erbes +ach5erfu +ach5erfü +ach5erke +ach5erkl +ach5er6kr +a4ch5erl +a6cherne +a4chero +ach5eroe +a4ch5erö +acher6öf +a4ch5erw +a4ch5erz +ache5str +ach5eta +a4ch3eu +2achf +achgene6r +2achh +4achi +a3chi. +ach3i4d +a3chig +ach5ind +a3chip +ach3ir +2achk +2ach3l +ach5le +2ach3m +ach5mi +ach3n +2acho +a3cho. +ach3o4b +ach5ord +ach5org +ach5o4ri +a3chov +ach3öf +2achp +2ach3r +4achs. +4achsc +4achses +4achso +4achsp +achst4 +ach5stre +ach3su +ach5sze +ach4tak +ach6tant +acht5erfu +4achtg +ach4t5in +4achtk +ach6traeume. +ach6traeumen. +ach6träume. +ach6träumen. +ach6trit +4achtsc +achts6tu +ach4t5um +ach3u4f +ach3uh +a3chus +2achv +2ach3w +a1ci +acin3 +4ack. +a4ckeg +4a3cken +a4ckerd +4ackes +a4ck3in +2ackl +4acks +acksau6s +ack5sta +acksta6g +ack5sti +2a1cl +a3co +acon4n +ac2p +acre2 +2acu +2ad. +4ada. +ad4ab. +ad4abr +a2dad +a2dae +ad2ag +a2d3a2k +ad3ang +3adap +ada4rer +a6d5ar6ten +4adas +a4daut +1a2dä +ad3än +ad1c +2a3de. +ade4al +ad5eben +2a3dec +ade3e +adelei6n +adel6spo +3adelu +2aden +ade4nat +adenes4 +adenk4r +aden4se +ade3o2 +2adep +ade5ram +a4derg +a4derig +ader4sa +a5deru +2ades +ade5s4p +ades4s +ade5sta +ade5stel +ade5str +2adf +adgas3 +2adh +adie4s5c +adie6s5el +ad3inf +1adj +2admu +a2dob +ado3c +ado4n3a +adon3e +a2dop +ad4o3s +adot2 +2adp +2adq +adrat5s4 +a2dre +3adres +ad4ret +4adro +ad3rot +ad3rue +ad3run +ad3rü +2ads2 +3adsor +ad5stu +2ad2t1 +adte2 +ad4tel +adto2 +adt3re +a2düb +1adv +2ae. +aea2 +aeap2 +ae2bl +3aebte +aech4sa +ae3chu +2aeck +aeck5sp +ae2da +ae4ders +aede3s4 +ae4d3ia +aedi4e +ae2dr +2ae1e +aee2k3 +ae2f3l +ae2fr +aefts3 +2aeg +ae2ga +aeg3an +ae4gent +ae2gl +ae2gr +aeg3ri +aeg5sta +aeg5s4tr +3aegy +ae2ha +4aehe +ae4hei +ae4hel +ae4h3in +aeh6lein +4aehm +3aehnl +2aehr +aeh4rei +2aehs +2aeht +2aei +ae3im +a3ein +ae5isch. +ae5ischl +ae5ischs +ae5i6sch5w +ae3isk +a3eiw +ae2kr +a3el. +ael4bl +a3elh +ae4lia +aelk3s4 +a5ella. +ael4lak +a3ello +ael4pl +a3els. +a3el4sa +a3elsb +ael6schl +a3elsf +a3elsg +a3elsh +a3elsk +a3elsl +a3elso +ael4st +a5elsta +a3elsv +ael3te +aelte5i +4aemm +aemte4 +2aen. +2aene +aene4n3 +aen2f3 +4aenge +2aeni +aen3k4e +aen4kl +aenne4 +aenner5 +2aens +aen4s3a +aen4s3c +aen4sp +ae3nu +2aenz +ae2o3p2 +5aepfel +a3e2pi +1ae2q +2aer. +aer3a2 +3aera. +ae3r4af +ae4rap +ae4rat +aerauto6 +aerb3l +aer4ble +2aerd +ae4r3ei +ae4r3e4l +aere4m +ae4r5emi +ae4r5erf +ae4r5erh +aerer4k +aer5ersa +ae4rese +aeri4e +ae4r5ing +4aerk +aer4mau +aer4m5in +aer6munt +ae3ro +3aerod +ae4rop +ae4ror +aero3s +2aers +a3ersa +aer4seh +aers5er +4aert +aert6er +aert4s3 +ae4rue +2aerw +a3erwä +aer6z5en6t +aer6z5err +2aes4e +ae3se3i +aese5rei +aese5ri +ae3s4kr +ae2sp +2aess +aes4s3c +aess5erwe +aes4so +ae4st +aes3te +aestein6 +3aesth +aeß5erk +aeß5erw +ae4t3a +aeta4b +aet4eb +aete3e +aeteo4 +aet5erbl +ae2th +4aeto +ae4tob +ae4t3op +ae4t3ra +ae4t3ri +ae4t3ro +aet4sen +aet4ste +aet4sti +aet4str +aett3s4 +ae2u +aeu4eri +2aeuf +3aeugi +5aeugle +4aeul +2aeum +4aeur +aeu5re +4aeus. +aeu6scha +aeu6schr +aeu6schw +aeu4sp +ae2vi +a1e2x +2afa +a4f3aen +a2f3a2n +a2fap +a2f3au +2afä +a2f3än +2afe +a2f3ec +a2f3ef +a4f5einh +a4f5einr +afel3e +a4fentl +a2f3e2p +afe4re +afe2s +a4f3ese +a2fex +aff4a +3affär +af4fei +3affek +af4flu +aff2r +2afi +afi2e +afie3i +afi6kanz +afi4kat +afi4k3l +a4f3ins +afi2t +2afl +af3lae +af3lau +af3lä +2afo +a4fober +a2f3oc +a2fop +a4ford +a4f3ort +afo3s +a2f3o2v +2afra +af3rae +a3frag +af3rat +af3rä +af3rie +a3fris +2af2ro +af3roe +af3rö +af3run +af1s +af3sa +af2si +afs4ke +af4soc +afs2t +af4t3ak +aft5anz +af4t3au +af4tei +af4t3e4l +af4t5ent +aft5erfa +aft5int +af4toe +af4t3op +af2tö +aft5rad +af4t5res +af4t5ric +aft4sei +aft4sel +aft4spi +aft6stic +af4tur +2afu +a2f3um +a2f3ur +2afü +2aga +ag3a2d +a2g3au +ag3ärm +agd3an +ag4d3ar +ag4del +ag4det +ag4din +ag4dre +ag4dro +4age. +ageb2 +age3id +age5ind +age5inf +age5inh +age5in4s +2agel +agel5d +agelei6s +a4gelem +age4len +age4ler +4a5gen. +age4nas +agend4 +age4n5e4b +a6generg +agens4p +5a4gent. +agenta6bl +5a6genten +a4gentf +age4rad +age4r5af +age4ral +age4r5ap +age6rauf +agerei6g +ager6eign +agerer6f +age4s5am +age4s5an +age4sau +ages5ka +age4s5oe +age4sö +ages6sen +age3u +3aggr +a2g3i2d +ag3ill +a2g3im +ag3inn +ag3ins +agi3oe +agi4os +2agl +ag4lan +ag3lat +a2gli +a2glö +a2glu +ag2n2 +a2gnä +3agog +ag3opf +agori4 +ago2s3 +a4g3rad +a2gre +a2gri +ag3rie +ag3rin +a4g3rum +ag2sa +ag3s4ah +ag2se +agsen4t5 +ag4s3in +ag5span +ag5s6port. +ag5s6porta +ags5taet +ag4stät +ag5stel +ags4top +ag5stras +2agt +aguet4 +a4gund +2a1h2a +ah3ang +a1hä +ahdi3 +a1he +a4hein +ahein6se +a4h3er4h +aher4ku +a4h3er4w +ahe3s +a1hi +ahin5d +ah2l3a2 +ahl3än +ah4l3e4b +ahl3ec +ah4l3ei +ah4lel +ahlenk4 +ah5lenz +ah4l5erd +ahl5erf +ahl5erg +ah4l5er4h +ah4l5erz +ahli4n3 +ah2l3o2 +ah2lö +ahl3sa +ahl4sal +ahl5se +ahls4pa +ahl3sz +ah4m3ae +ah4m3ar +ah2m3ä +ahme3e +ahme3i +ahmen5e +ahmen7sc +ahme3s4 +ah3mu +ah2n3a2 +ah4nas +ah2n3ä +ah3nee +ah4nef +ahner6e +ahn5er4h +ahn5erk +ah6nerle +ahner4n +ah4n5erp +ah6n5ersa +ah6n5er6sc +ah4n3eu +ah4nin +ah2no +ahns2 +ahn3sa +a3hom +a3hop +3a2hor +ah3ost +a3hot +a1hö +ahr3a +ahra4b +ahra4g +ah3rai +ahr5eig +ahren6se +ahren6s5o +ahr6ere +ahrer6lau +ahre4s +ah3ri +ahr4tae +ahr4t5ei +ahr4t3h +ahr4tin +ahr4t5ri +ahr4tro +ah2ta +ah4t3e4l +aht5ero +ahte4s +ah4t5ese +ah4t3ex +ah4tis +aht3re +ah4t3ri +aht3s2 +ah4t3um +a1hu +ah3w +a1hy +ai1a2 +a3ia. +aian3 +aidi2 +ai3dr +aid2s +ai1e2 +aien3 +aien4de +ai3er3 +aif2 +ai3fr +aig2 +ai5gel +a3ik. +ai4k3er +ai4lar +ai4l5auf +ai4lei +ail5er4l +ailge3 +a3im. +a3imp +2ain +ai3nac +ai3nal +aind2 +a3indu +ai4neb +ainen4 +ai5ner +a3info +ains4p +a4inv +ai1o +3airb +3airl +airo3 +air2s +ai2sa +ais3ac +a5isch. +ai4schi +a5ischs +aiser5a +ais4o +ai3spr +aiss2 +ais4se. +ais4sem +ais4ses +ais3t +ai3s4tr +ai3ti +ait4r +a3iv. +a3ivl +a3ivs +a2jat +2ak. +2a3ka. +aka3b4r +a4kade +2akae +a3kal +2akam +2akan +3a4kanl +2akar +aka4tak +aka4tan +aka4t5er +aka4t3r +akat3s +3a2kaz +2akä +2akb +2akc +1akd +2a1ke +a2kef +ak3eis +3a2k3em +ake2n +aken3e +a2keo +2akf +2akh +2a1ki +ak3ins +aki3p +1akk +4akko. +2akl +a3klec +ak4lin +2akm +3a2kne3 +2ako +ako3c +akoni4e +3akox +2akp +2akra +2akre +2akri +ak2ro3 +a2k3ru +a2k3rü +2aks +ak3sh +ak3sta +ak3ste +ak4t3a4b +ak4tag +ak4t3ar +ak4t3au +2aktb +akter5a +2aktg +ak2th +4aktik +aktionsche6 +4aktis +2aktm +ak4t3ob +ak4toe +ak2tö +ak4t3re +ak4t3ro +ak4tru +4aktsi +4aktsp +4aktst +4aktun +2aktw +2akul +2akun +2akur +aku2s3 +2akw +1akz +2a3la. +al3abb +4alabo +a4l3abt +a3lad. +al3add +al3adm +al3aen +al3af +2alai +ala3kl +al3akr +al3ame +alami3 +al3ana +a3lanc +4al4and +a4l5ande +al5angel +al5angr +alang5s +ala3ni +al3ank +al3anm +al3ann +al3ans +al3anz +al3app +a3lar. +al3arb +al3arg +a3lari +al3arr +al3art +a3las. +alaska5 +al3asp +a3lat. +ala4tan +al5attr +alat3z +al3au +a3l4au. +alauto6r +a3lay +a1lä +a2l3än +al3ärm +al3äu +alb5aer +al4bär +3albb +4albe. +alb5eink +al4b5erh +al6b5er6sti +al4b5er4w +al4b5eta +alb5ins +al4ble +al4bon +alb5rae +al4b3rä +alb5rin +alb5ros +alb4spe +3album +al1c +alda3c +al2dä +5aldehy +ald5enkl +al4d5ere +al4d5erh +al4d5erl +al4d5ern +al4d5erw +al4d5ese +ald5inn +al4d3ot +al4drä +al4dro +ald3s4t +al4duf +2ale +al3e4be +alebran6 +a2l3ef +al3eie +a4l3eig +a4l3ein +a4l5eise +al3ele +al3elf +3a4lema +a4lemb +ale4mis +3a4l3emo +al3emp +alende6r5 +a4l5endr +a4l5ends +a4l5endu +a4l5engl +alen5in +alent6ei +al5enth +alen6z5ei +al3e4po +a4l5erfo +al4erg +alergene6 +a4l5erhe +a4l5er4hö +aler4kl +aler4kr +a4l5erlä +a4l5er4mi +aler5oe +a4l3erö +3alerr +a4l5erse +a4l5er4tr +5alerwa +5a4l5erwä +a4l5erze +a4l3e4s4k +a4less +a4l3e4th +a4l3eur +al3exi +al3ext +a1lé +al5gas +algene6r +3algor +alg2r +alig4h +al3imm +al3imp +a4l3in4q +alin6stin +al3int +al3inv +ali3o +al3ira +al3irr +ali3sp +ali5str +a4l5ital +al4k5enz +alk5erd +al4k3lä +al4klö +al4klu +al4kne +al4kof +3alkoh +al4k3rü +alk3sa +alk3se +alks4i +alk3sp +alk3st +al5lad +all5ank +all5ano +all5ans +all5anz +al4l3a4r +alla4s +al4lass +al4laus +3allee +all5eim +all5erfa +all5er6mu +all5ermü +al6lersa +al4l5er4z +al3les +al4lev +3allgä +3allia +al4l3id +alli5er. +alli5ers. +all5ink +al4l5oef +alloe6se +all5ora +al4l3öf +al4l5ö4se +all6s5amt +all4sin +all4sph +al4lum +alm5aus +almo6de. +al3n2 +2alo. +alo2b +al3obe +al3obl +al3obr +al3obs +al3oe4d +al3oer +a2lof +2a3log +alo4g3a +alo4gn +alo4gr +alo5is. +a3lop +al3ope +al3opf +a4l3orc +al3ort +2alos +al2ov +alo2w +a2lö +al3ö2d +al3ör +3alpe. +6alphas +alp3st +als5aer +alsau4g +alsch5s +al3s2e +als4pac +al5s6temp +al3sti +alt5alg +al4t5ana +al4t4an4b +al4t5ans +al4tap +alt5auf +alt5eic +al4t5eig +alt5ein +al4t5eis +alt5elt +alter6fol +al4t5eti +al4teu +al4tid +al4tir +alt5ita +al4toe +alt5ort +al2tö +al4t5rat +alt5ric +alt5rit +alt5roc +alt5ros +al4trus +a1lu +a2lum +al3umb +3alumi +a2lur +a4l3use +alu3ta +a3lüg +2aly +al4z3ap +al4z3ar +al4zet +2am. +ama3a +am3a4ba +ama3d +4amae +am3aep +ama5ers +ama5ge +3a4malg +am4an +am5ang +aman6the +aman4tr +am3apf +amas5z +a2maz +2amä +3ambiv +2amc +2amd +4ame. +am3ein +ame5len +a4memo4 +ame4n +amen3e +amen3i +amen4s5e +amen4su +amen6tarb +am2er +amer4a +amera5u +a4m3erf +ame3r4u +a4m3erz +a4mesh +ames4p +a4me3st +2a3met +a2mex +2amf +2amg +ami4kan +ami4kl +ami4kn +ami4n5an +amine4 +ami3o +amis4tr +4amix +2amk +2aml +4amm. +am4maa +amm5aba +am4m3ac +am4mal +amm5ang +am4map +am4mar +amm5ass +am4m3au +amm3ei +amme4la +amme4n +am6m5es6sen +am4m3id +4amml +am4m3oe +am2m3ö +4amms +am4munt +ammu4t3 +a2m3oc +amo2k +amotte5s +am2pf +amp4f3a4 +amp6ferr +ampf5la +ampf3o +ampf3r +amps2 +3ampul +2am2s +am3sau +am5sche +am3se +ams3en +am3so +am3spa +am3spi +am3s4tr +am3su +am2t3a +amta4b +am4tän +am4t5eig +am4t3el +4amtem +amter4 +am4t5erb +am6t5erei +am4t5erf +am4t5erg +am4t5erh +am4t5eri +am4t5erk +am4t5erl +am4t5ern +am4t5err +am4t5ers +am4t5ert +am4t5erz +am4t3ex +am2th +am5tig +amt5int +am4tis +am4tit +amto2 +amt3ob +am4toe +am4t3op +am4tou +am2tö +am4t5rat +amt5rei +amt5ric +am4tro +3amts. +3amtsd +am2tu +amt3ur +2amtv +2amu +a2m3uf +3a2mul +a1mü +2amv +2amw +a1na +a3na. +ana2a +a2nab +4anac +ana5che +anadi5er. +an3aes +an3aeu +ana4l5in +an5al4ph +anal5sk +3analy +ana3m +an4and +an3a4no +an3ant +2anar +a4narb +an4are +an4arr +a3nas +ana4se +a3nat. +ana4ta +ana4t3r +a3nats +3anäm +an3är +3a2n3äs +an3äu +1anb +4anban +4anbas +4anbib +2anbo +2anbu +2anbü +anche4n5 +an3cht +2and. +4andaa +4andab +3an3d4ac +and5achs +4andal +and5amm +4andan +and5anz +an4d5arm +an4d5ass +4andat +an4d5att +4andaw +2andd +4ande. +an4d3ei +and5elfe +an6den6ga +and5ente. +ander5ei +anderer6le +and5erhö +ande4sa +ande6spo +andes5ta +an4d3ex +2andf +2andg +andi4e +ando3s +and5ott +and5rai +an4drau +an4drec +an4d5res +2ands +and4sam +and4seh +and6s5ein +and6spar +and6spas +and6spat +and4sph +an4düb +2andw +andy3s4 +an3e2c +a3n4ei. +3aneig +a3neip +an3e2k +ane2l +an3elf +an3e4mi +3a4nemo +ane2n +anen3e +anen4ze +anen4zy +aner4nä +a4n3eru +a4n5erz. +an3eth +a2n3e2v +1anf +2anf. +4anfab +an4f3ak +an5fes +2anfi +anfi4n +anfin5i +anf5rau +2anfs +an3f2u +4anfun +4ang. +4anga. +3angab +ang5art +4angas +ang2ä +an3gäh +an4gäs +4ange. +5angebo +5angeho +5angehö +ang5eig +ang5eis +3angek +an4g5erf +an4gerg +ang5er6he +an4g5erk +an4g5er4p +ang5ertr +an6g5erwa +an4g5erz +3angez +4angie +ang4lok +angmi4 +ang3n +3angol +ang5rad +ang5ras +an4grau +5angrif +4angs. +ang5sat +ang6st. +ang5sti +ang3t2 +1anh +4anhau +4anher +4anhex +2anhu +2anhü +2ani +4ani. +4anie +aniel4l +ani5els +ani5ers. +anie3s +ani4ka +ani4ki +ani4kr +3a2nim +an3i4on +a4niso +ani5sta +4ank. +an4kag +ank5alt +ank5anz +4anken +ank5erfa +4anki +an4kid +an4k5las +an4klö +an4k3ob +4ankon +ank5ori +an4k3öf +an4k5ras +an4kro +ank5rue +an4krü +4anks +ank3s4k +ank3s4p +ankt2 +an4küb +3ankün +1anl +4anlad +4anlan +4anman +3anmel +3anmie +3anmu +4anmun +anna4ge +3annah +3annä +an4nef +annens4 +anne4s +3annex +ann4spa +2annt +3anod +an3ode +anoe4d +an3ofe +2anog +a3nol +ano4l3a +an3oma +ano4m5en +an3opf +an3opi +an3or +ano5rak +3a4nord +a4no4ri +anor3t4 +a3nos +an4ou +a3nov +a1nö +an3ö2d +1anp +4anpaa +4anpag +2anpo +1anr +4anrad +2anrö +an3s4ab +an4s5aem +an4s3al +ansa4m +an4s5ame +an4s5amt +an5s4au +3ansäs +ansch4l +an4seu +3ansic +ansicht6 +an4sim +an4spo +ans5pon +3anspr +5anstal +an4stem +an4s5ura +ans2z +an3tac +an4t5a4go +ant5ang +ant5ans +an4t3ar +anta4re +3antá +3antei +anteri4 +antes3 +ant5e4ta +5anthro +3antib +5antilo +ant5inst +3antiq +5antise +3antr +ant5räu +2ants +3antw +a1nu +anu5ell +a3nup +a3nur +a1nü +1anw +2anwi +any3l4 +an4z5aep +an4z3an +an4z5art +an4zau +an4zäp +2anzb +an4z3e4d +5anzeig +anz5elf +anze4n +4anzes +anze4t +anz5eta +2anzg +2anzh +an4z3i4d +anzi4n +an4z5inf +an4z5ins +anz5int +2anzk +2anzm +an4zol +2anzr +2anzs +2anzt +5anzuen +3anzün +an4zwä +an4zwi +2ao +a1oe2 +a1of +ao2fe +ao1i +a3okt +a2om +a1op +ao2pe +a1or +a3orm +aor5te +a1os +ao2sk +aos2p +aos3t +a3ot. +ao3t2s +2a1ö2 +2ap. +ap2a +apa3b2 +a2ped +a3pel +apen3 +ape4ra +a2pev +2apf. +ap2fa +4apfe. +3apfel +4apfen +2apfs +2aph +aph5aer +aph3är +aph5ers +a2ph3t +2a1pi +api2e +apie5l +apie3s +2apl +a3pla +a3plä +ap3li +ap2n +ap2o +2apol +apo3p2 +a5posi +apo5sta +apos5te +apo5str +a2pot +3appar +ap4pei +ap2p3f +ap4pre +ap4p3ru +a1pr +ap4san +ap4sin +ap2s3p +apst3o +apst3r +apt2 +a4que. +2ar. +a1ra +a3ra. +ar4a3a +ar3abb +ar3abt +ar3adr +arad4s +2arae +ar3aer +ar3aes +araffi4 +a5rafo +arafor4 +ar3agg +arag4l +ar4ah +ara3i +4aral +ara3le +ar3all +ar3ang +a4ranr +ar3ans +a4ranw +a4r3anz +ar5apos +a4r3app +2arat +ar3atl +ar3atm +ar3au +arauto6r +ar3ax +2a1rä +ar3äs +ar4bab +4arbe. +4arbef +3arbei +ar4b5eig +ar4b5eim +ar4b5ein +6arbeitr +4arben +arb5erl +4arbi +ar4b3le +2arbs2 +arb3sk +2arbt2 +2arbu +1ar1c +4archl +4archr +ar4d5anz +ard5arm +ar4d3ed +arde4n +ar4dob +ar4dop +ar4dro +ards2 +2are. +a2rea +are5aler +a2reb +are5ba +2arec +a2re3e2 +a2ref +ar3eff +areg2 +ar3ehr +a4r3eid +ar3eig +areim5 +a4rein +2arem +ar3emb +a4remi +4aren. +a4renah +arens4 +aren6sem +a4r5entn +aren4zy +a2reo +4a5rer. +are3r4a +arerau5 +a4r5erei +a4rer4fo +a6rergeb +a4reri +a4r3erk +a4r3erl +a4r3ert +a4r3erz +arer4zi +2ares +a4r3es4k +ares4p +a4r3es4s +are5sta +are5str +2aret +are5the +a2reu +a2rev +arfs3a +arf4s3p +ar3fu +2arg. +ar4g3e4c +arge6l5er +ar2g3n +4argo +ar5gre +a1r2i +2ari. +2aria +arie4n3 +ari5er. +ari5erbu +ari5erd +ari5erg +ari5erh +ari5erp +ari5ers. +ari5ersc +4arif +arif3t +ar3imp +2arin +arin5it +arin3s4 +a4r3int +ar3inw +2a3rio +a3rir +4arisc +ari4su +a4rith +2a3riu +3arkad +ar4k3ak +ar4kal +ark5aue +ar4käh +ar4k5erh +ar4k5er4l +arker4n +ar6k5erne +ar6k5er6sc +ar4k5erw +ar4kid +ar4k5inn +ark4lö +ar4kne +ar4koa +ark5rin +ark3sa +ark5she +ark5spe +arks4t +ark5stu +ark4ta +arkt5a4g +ark4tel +ark4t5ex +ark4top +ark4tre +arku2 +ar4k3um +ar4küb +arlang5 +ar4les +arlichter6 +arlich6ters +arl2s +arma3l +ar4m5ans +ar4m5anz +arm5art +5armatu +arm3au +ar4meb +arme4e +ar4m3e4g +arm5ein +5armeni +ar4meo +ar4mep +ar5mes +ar4m3op +2armö +arms2 +ar4mum +3armut +2arn +ar4nan +ar4nap +arn4au. +arnte4 +a1ro +ar3ob. +4aroc +aro6ckeng +ar3o4di +2aroe +ar3oem +2arof +ar3ofe +ar3off +aro2i +2arok +2arol +ar3o4no +ar3o4pe +ar3opf +2a2r3or +aros5t +2arot +arot5an +aro4to +a4roun +aro2w +a2rox +2arö +ar3öm +2arr. +arr5ach +5array +2arrä +ar4r3e4b +ar3rei +ar6r5ei6sen +arre4n +arren5e +4arri +ar4r3or +2arrs +2arrt +2ar3s2a +ar3sä +ar6schab +ar6sch5ac +ar5sche +arsch5erl +arsch5lä +arseri4 +arsex3 +ar3s4ha +ar3si +2arsk +ar4ska +ar3sp +ar3sta +ar3stä +ars5tät +ar5stel +ar5stern +ars6t5ers +ar3str +ar3su +ars4ue3 +artal4s5 +ar4t3am +ar4tang +art5att +art5auf +art5elf +arter6fo +arter5k +arter6la +3arthr +4artie +3artig +art5int +ar4t5iso +ar4t3ob +ar4t5ram +art5ren +art5rie +ar4t5rol +art5ske +a1ru +arude4 +aruder5 +a2r3uh +a3rumm +ar3ums +2arü +a3rüc +a3rüm +arwa4s +2ary +ar4z3ae +ar3zan +ar4z3au +ar2zä +ar3zäh +arze2 +arz5elf +arz5ente +arzer4 +ar4z5erl +2arzi +3arzn +3arzt +arz4th +arz4ti +arzt5ra +arz4tri +ar2zw +asa5chi +as3aeh +as3aet +4asaf +as4ai +asal4t5e +as3amp +asa5ne +a2sap +asa4ro +asa2s +as3asc +a5sau. +asau4f +as3aug +as3ät +a2sca +as5cham +a5scher. +ascher6g +a3schi +asch5mi +asch5ru +4ascht +a4schwu +a4schwü +a2sco +2ase +a2sec +a2s3ef +as3eie +as5elef +a3sen. +ase4na4 +asene4s +ase6n5esc +asenk4 +ase4n3o +a6sen6sem +asens4p +aser3a +aser5eig +ase4ren +as4erh +as4es +as3eva +a2sex +asex3p +2asf +a3s4hen +a3s4hig +3asiat +a2s3i2k +as3imp +as3ind +asing3 +as3inn +as3int +asi4s3a +a2sk +as5kla +aslan4 +a3slip +a2so2b +as3oe +aso2f +as3ofe +a3sol +a3som +a2s3o2p +as3orc +as3org +as3ori +as3pan +aspek6t5o +aspi5en +as3pik +as3pio +a4spir +as4ple +as4por +as4pul +as5s4abl +as4s5amt +as4scr +ass5eins +assens6k +assens6p +as5s4er +asser5a +asse6r5ec +asse6reis +5assess +3assim +as3ski +as4soe +as4s5ora +3assoz +as2sö +as4spo +as2st +ass4tie +ass4top +ass4tue +as4sur +a4st. +a3s4tad +ast4af +ast5ans +ast5ant +as3tap +asta3s +as5tatt +ast5auf +ast5einl +a4ste4n +ast5ents +as3ter +as6t5er6ho +as6t5er6hö +aster5oe +aste4s +as4t5ese +as4tex +as2th +3asthm +asti4e +asti4ka +a2sto +as3toc +ast5orc +as4t5rap +as4trau +ast5raum +as4tre +as5tren +as6t5ritt +ast5roll +as4tru +ast6ruk +2astu +as4tud +as2ty +asu2s3 +aswa4s +as2y +2asy. +a2syl +3a4symm +aße2 +aßen3 +2at. +2ata +a2t3aa +at5a4ben +a4t3abl +at3abr +at3aer +ata3la +at3apf +ata3ra +at3arb +at3art +ata3s2 +at3atl +at3att +a4t3auf +ata3un +a4t3aus +at3är +2atb +2ate. +ate4au +a2teb +a3teh +ateien6d +atei3f +atei3s +3a4teli +4atell +atel6l5erk +3a4temg +a4teml +a5tempe +a4tems +2aten +aten6t5ank +aten6t5ri +ate5ral +aterer5 +ate4ro +2ates +ates4sa +at4eur +ath4a +a4t3hee +at5herd +a4thin +ath5in. +at3hir +3athl +a4t3hoc +at3hof +a2th3r +2at2i +at3idi +ati4kab +ati6k5er6w +ati4kl +ati4s3a +ati4s3e +at4isl +a4tiso +ativ3a +ati4vat +ativen6d +ati6v5er6f +ati6v5er6s +4atle +4at5mus +at2o +a2t3o2b +at3oef +at3oel +a4t3oer +at3o2f +a2tom +ato4man +ato4min +3atomk +a3tomo +3atoms +at5opfe +a4tops +2ator +ato4ra +a4t3orc +ato4rei +atorgene6 +ato3s +ato2w3 +a2t3ör +at5raet +at5raeu +a4t3rau +a4t3rav +at3rän +a4t3rät +a4t3räu +at3rec +a4trep +at5ring +at3roh +at3rol +a4t3rom +4atron +a4troo +3a4trop +a4t3rot +at3rue +at3ruh +at3rut +a2t3rü +2ats +at6schma +at6schme +ats3e4h +atse4l +at4set +at4s3in +at2so +ats3pr +at4stoe +at4stop +at4stö +at4stur +3attac +at4t3ad +at4tah +att5akt +atta3s4 +at4t3au +at4t3ec +4attel +attens4 +4atter +at6t5ertr +at4thu +at3ti +at4t3o4b +att3o4f +5attrak +at4t5ras +att5rat +att5räu +att5ros +att3s4 +2atu +atu3et +atu2n +atu4rei +atu4rer +atur3z +atus3e +atwa3r +2atz +at2za +at2zä +at4z3e4c +at4z3e4d +at4z3eh +atz5eig +atz5ela +atz5elt +at4z3em +atzen5e +atz5erb +atz5erf +atz5erg +atz5erh +atz5erke +atz5erkl +atz5erkr +atz5erl +atz5ero +atz5erp +atz5ersc +atz5ert +atz5erw +at4z3e4t +at4z3in +at2zo +atz3op +at2zw +a2u +2au3a2 +au1ä +2aub +au4b3ab +aube4e +aub5ele +aube4n +auben6se +auberin6s +au4b5eul +au4b5ins +au4b5lae +aub5las +au4blä +aub5rin +aub4spo +aub4spr +au4b3um +2auc +auch5e4c +au6ch5en6t +au6ch5erke +auch5sti +2aud +au1e +4aueb +aue2d +au3eg +auene4 +auens4t +auere4 +au5erel +au5erent +aue4r3o +aue2s +aufa4c +auf5akt +au4f3ar +auf3ei +aufen4s +5aufent +auf5erf +auf5erh +au4f5er5i +auf5er6kr +auf5erla +auf5ind +3auflö +3aufn +4aufo +auf3re +5aufruf +auf3t2 +4auft. +5aufträ +au4fum +3aufzü +4augeh +augene4 +4auger +5auges. +2augl +4augr +augs4t +aug5str +3augus +au1h +auh4a +2a3uhr +2au1i +aui2n +2auj +au4kle +auk5te +au4l3at +aul3e4c +aule4s +aul5ese +aul4les +au3lü +2aum +au4mad +au4maeh +au4mäh +aume4n +aum5ents +aume4r +au4m5er5i +au4m5ern +au4m5ers +au4m5erz +aumgene6 +au4m3id +au4mind +au4m5ins +au4mis +au4m3o4r +au4m5ott +aum3p2 +au4ms2 +au4mum +au4mun +2aun +aun4ac +aun5alg +au3nas +aun4aus +aun3d +aun2e +au4n5er4d +aun5erze +a4unf +au4nio +aunk2 +au2no +au3nu +au1o +2aup2 +au3pf +aupt5er +au1r +aur4a +auran4 +aurant5 +aur2h +au3ru +au4s3ah +ausan6ne. +au4sap +au4sas +au4s3au +4ausc +au6schan +ausche6f +au6sch5lag +au6schli +au6schmi +au6sch5wi +au4scr +3ausd +4ause. +ause4e3 +aus5ehr +aus5ele +aus5elf +au4s5erb +au4s5erf +aus5er5in +au4s5erk +au4s5erl +aus5erpr +au4s5erw +3ausgl +au4s3in +au4sis +aus5kr +2auso +au4s3or +au4spic +3ausrü +auss2 +5aussag +aus4se. +aus3sz +au5stae +au5stal +au5stat +aus5tauf +4auste. +au5stein +au5stel +4austem +4austen +4austes +5austrag +au5s6tras +au4s5tür +3auswu +3auszü +auße4 +außer3 +aut5aeu +au4t3äu +aut5ein +au4t3e4l +au4t5ero +au4ters +au4tev +aut3ex +au4thy +2auti +3auto +auto6renk +auto6renp +auto6rens +auto6rent +au4torg +auto6rinn +aut5rau +aut5rin +aut5rue +au4trü +auts2 +aut3sk +aut3z +2auu +auve4 +2auw +2aux +2auz +a1ü +av2an +avari4e +a2va3t4 +a2v3ex +2avi +avie6rer +a2vl +2avo +a2vr +2avy +2aw +awi3e +a2wr +a1x +ax2am +ax2at +ax3emp +ax3erw +3a2xio +2a1ya +a2y3ak +ay4ere +ay2es +a3yeu +ayma2 +ay3of +ayou2 +ays2 +aysi3e +ay3t +ay2u +az2a +a3zad +3a2zal +aza3s2 +az2o +2a3zu +az4ur +2az2w +azz3a2 +az4z3en4 +az4z3in +az2z3w +ä1a2 +äap2 +ä2bl +äbni2 +äb2s +äbte3 +ä1ce1 +ä3che +äche3e +äche4n3 +ä3chi +ä2chr +äch4s3a +äch4s3o +äch4sp +äch4st +ä3chu +ä1ck +ä3cke. +ä3ckes +äck3sp +ä2da +ädel3a +ädel3e +ädel3i +äde3s2 +ä2d3ia +ädi2e +ä2d1r +äd2s +äd3te +ädte3e +ädte3i +ädtein4 +ädte3s +2ä1e +äe2j +äe2k3 +ä2eri +äe2x +äfermi4 +3äffc +äf2fl +äfig3s +äf1l +äf1r +äf2ro +äf3te +äft2s3 +ä2g1a +1ä2gä +ägd2 +ägdi3 +ä4g3ent +äge4ra +äger5as +äge6rend +ägeren6g +ägerer6k +äge6rers +ägerin6d +ä2g1l +ä2g3ne +äg2ni +ä2g1r +ägs2c +äg3sta +äg5stel +äg3s4tr +1ä2gy +2äh. +äh1a +2ä1he +ä2h3ei +ä2h3e2l +ähe2n +äher6gebn +ä1hi +äh3in +äh4l3a +äh4l5e4be +ähl5ein +ähl5ins +2ähm +äh3na +äh3ne +3ähnl +ähr3a +ähre2 +äh4r3ei +äh4r3el +ähr5sa +2ähs2 +äh3stu +2äht +ä1hu +äh3w +2äi +ä1im +ä3is. +ä5isch. +ä4ische +ä5i4schl +ä5ischs +ä5i4sch5w +ä3isk +ä1k +ä2kle +äk2li +ä2kr +ä1la +äl3an +äl2bl +äl1c +äl2l3a +älle3s +äl2pl +äl4schl +äl3te +älte3i +ältein4 +ä1lu +2äma +äma3t +2äml +ämon2 +ämoni5e +ämte2 +äm2th +2ämtl +2än. +ä1na +ändes4 +än2dr +2äne +äne2n3 +äne3p +äne3s +än2f3 +änge4ra +ängerin6s +än2gl +än2gr +2äni +2änk +än3k4e +änke3i +än2kl +än2kr +änne4 +änner3 +2äns +än2s3a +än2s3c +än2sp +ä1nu +2änz +ä1on +äo3s +äos4e +2äpfc +3äpfel +äp2pl +äp2pr +äp2s3c +1ä2q +ä2r1a2 +3ära. +är2af +ärana3 +ärauto4 +är1ä +är4b3le +är1c +2ärd +ärde4n +ärde4s +2äre +ä2r3ei +ä2r3e2l +äre2m +ä4r3emi +äre2n +ären3a +ä4r5ener +ä4r3erf +ä4r3erh +ä4r3er4k +ä4r3erz +ä4rese +är2gl +ä1ri +äri2e +ä4r3ind +ä4r3ing +är3ins +ä4r3int +ärm5arm +är4m3au +ärme3e4 +är4m5ent +är4m5erz +ärme5sz +är4m3in +är4mof +är3o2b +är3of +är3o2p +är3or +ä1rö +ärp2 +är4seb +är4s3eh +ärs3er +är2sz +ärt2s3 +ä1ru +ärz3au +ärzen4t +ärzer3 +ärz3te +äsche5i +äsche5s4 +2ä3se +äse3e +äse3g +äs4e3i2 +äsein4 +äse3la +äsen4s +äse3re +äse4ren +äse5r4i +äse3u2 +ä3s2kr +äs1p +2äss +äs2s3c +äss5erkr +äss5erwe +äs2so2 +äs4spr +äs4ste +äs3te +ästein4 +3ästh +äs2tr +ä4ßent +äß3erk +äß3ers +äß3erw +ä2t3a2 +ätab3o +2äte +ät4eb +äte5be +äte3e2 +ätein4n +ät3eis +ät3emp +äte2n +äteo2 +ät3e4pi +äter4bl +ätergene6 +ätes4p +ä2th +ät3id +ät3ob +ät3op +ät1r +ät2sa +ät2sä +ät4schl +äts3en +ätsi2 +äts3p +ät2st +äts3tä +äts3te +ät2su +ät2t3a +ät4tei +ätte4n3 +ät2tr +ätt3s2 +ät3v +ät2za +ät2zw +äu2b3l +äu2br +äu1c +äude3 +äuder4 +äu1e +äuel3 +äu4eri +2ä2uf +1äug +äu2gl +2äul +2äum +äu2ma +äum3p +äum2s +äum3sc +ä2un +äun2e +äu3nu +2äu3r +äu5re3i +äurein4 +äure3s +2äus. +äu4scha +äu4schä +äu6sch5ei +äu6schel +äusch5erz +äu4schi +äu4schl +äu4schm +äu4schn +äu4sch5o +äu4schr +äu4schü +äu4schw +ä3usd +ä5useff +äu3s4e3i +ä3usg +ä3usk +ä3usn +äus4s3c +ä3usst +1äuß +äu2tr +ä2vi +1äx +âte2 +á1 +1b +ba2b3a +ba4b3er4 +2babg +2b3a2br +2babs +5bach. +bach5en +5bachh +bach3s +b5achse +bach7ter +ba4ck5er4 +backs4 +bad3a +ba4dab +ba4dan +ba4dar +4b3adel +ba4dent +2b3adl +2b3adm +ba2dr +ba2du +b2ae +2b3aeh +4b3aerz +4b3aeug +bae5us. +2b1a2f +2b3ahl +bah4n5e4b +bah6nene +bah6n5ent +bai3d +ba2k3a +ba4k3er +ba2k3i +bak3l +ba2k3n +ba4k3ra +ba4kre +ba3la +ba4lab +ba4l3ak +ba4lar +ba4las +ba4lau +bale4a +ba4lei +baler4 +ba4l5erk +bal5ins +balk4a +balke4 +bal4lan +bal4l5au +bal4l5eh +bal4l5ei +bal6lerg +balo3 +bal4tr +2b1am +bama2 +bam3az +3b2amb +ba2me +ban2a +ba4n3ae +b2and +ban4dab +ban4d5al +ban4d5an +ban4dar +ban4dec +ban6delf +ban6d5en6de. +ban6d5en6den +ban6d5en6des +ban6d5erf +ban6d5erk +ban6d5erz +ban4dob +ban4dr +ban4du +ban4k3a +ban6kerf +ban6k5erw +ban4kl +ban4k3n +ban4kop +ban4kor +ban4k3r +ban4ku +2banl +b3anna +2b3ans +2banw +2b3app +ba4r3ab +ba4rad +bar5akt +ba4rar +bar5ast +ba4r3at +ba4rau +bar5ein +bar4en +barer5ei +barer4t +bari3e +bar5in4s +bark4l +bar2s +bar4ta +bar4tel +b3arti +ba4rue +bar3zw +bas2a +ba4seb +ba4seli4 +bas4i +b3asia +bas4sa +bas4s5ei +bassin4 +bas4sp +bas4st +bas5te +bas3ti +bas4tr +ba4t5ent +2b3atl +batro4 +ba4tru +bat4tak +baub2 +bauchar6m +bau6ch5el +bau6ch5er +bau3eb +bauen4s +bauer6fo +bauer4g +bauer4l +bauer4n +bauer6tr +bauer4w +bau3fa +b3aufg +bau3f4r +4b3aufs +4b3auft +4b3aufw +baug2 +bau5kl +bau3l +bau4m5ac +bau6m5ant +bau6m5ast +bau6m5ent +ba4u3n +b3ausb +4b3ausf +bau3s4k +b3ausl +bau3s4p +b3ausr +4b3auss +bau3s4t +b1a2x +ba3yo +b2ä1c +b2äd +2b1äh +b2äl +b2är +bären5 +bärk2 +2b3ärz +b2äs +2bäug +bä3us. +2bb +b3ba +bbau3 +bbauer4 +b3be +bbens4 +bbe3ra +bbin4g +bb2lo +bb2lö +bblu2 +bb2r +bbran4 +b3bru +bbru4c +b3brü +bb2s +2b1c +b3ch2 +2bd2 +b3da +bdän3 +b3de +bde3s +bdi2e +bdome4 +3be. +bea2n +bea4ten +beat4m +b3ebb +beben3 +bebens4 +beb2l +3be1c +be2ce +becher5e +4b3echt +be3cke +4b5e4cke. +bed2 +4b3e4del +bedi2 +2b3edl +be3e2h +bee2l +be3ela +be3en +be3erd +bee4r5ei +bee6r5es +bee4r3i +be5ertr +be5erzi +bee4ta +be3eti +bee4t3r +be3eu +bef2 +be3fe +befen4 +2b3eff +befi4l +be3g2 +3beh +be4he. +beh3ri +beh2u +b2ei +beib2 +bei5bl +bei3d4r +4b3eier +bei3f2 +bei3g2 +beige4o +be3imm +be3imp +bei6ner6k +be5in4fo +bein6fra +4b3eing +be5in6hal +be5in4hi +bein6spe +bei3s4a +bei5sc +beisch4 +bei3sp +bei3s4t +beit4s5e +beit4s5o +beit4s5t +beiz4w +3bek +be3ke +b2el +bel5achs +bela4n +be4l5ano +be4l5ant +be4lanz +bel5bi +4b3el4bl +bel3d4 +be5le. +be3lec +3belei +be5lei. +be5leie +be4l5ein +be4l3en +bel5er5e +bel5erh +bel5erk +bel5ern +be4l3e4t +bel3f +3belgr +beli6ef +bel5inn +belle4n5 +bel5oef +be4l3om +be4l3or +be4l3öf +bel5öse +3belp2 +bel3sk +bels4p +bel5stern +bel3sz +belti4 +2b3emp +4b3emul +b2en +3bena +be5nabe +be4nat +ben3au +be2nä +3benb +benden4 +ben6dens +ben6d5es6s +4bendl +4bendm +4bendp +be3ne. +ben3ec +benei4l +be3nen +be4n5end +be4n5ent +be3ner +be4nerf +6benergi +be5nergr +be4n5erl +be4n5ers +ben5ert +be4nerw +be4ness +ben3f +beng2 +be4nid +be4nik +benim4 +ben5inn +be4n3is +3benk2 +ben3n +be4nob +be4n3or +ben4san +ben4s5au +ben4sek +ben4s5el +bens5end +ben6sere +ben6spar +bens5trae +bens5trau +bens5trä +5bensz +4b3entb +4b3entd +4benteu +4b3entf +4b3ents +4bentw +ben3un +3benv +3benz +be1o2 +be1p2 +be4pit +4b3epoc +b2er +be3ra. +ber3aa +ber5abe +ber5abl +ber3a4c +ber5adm +be5raeu +ber3af +ber3a4k +ber5all +ber5amm +ber5anz +be4rar +bera4s +ber5asc +ber5ass +be4rato +berbeste5cke +berd4 +be4r5eck +ber5eig +ber6eini +be4r5eiw +ber3el +bere4m +ber5emi +be3ren +ber5end +be4r5en5e +be4reng +ber5ens +berer6fa +ber5ersa +ber5e4sc +be4r3e4t +berf4 +ber4g5af +berg5an. +berg5ass. +ber6g5en6t +berger4 +ber6g5erf +ber4g5li +ber4gn +bergs4t +ber4hab +ber3i4d +berin6sp +berin6st +berin4v +be4ri4o +ber5iss +berk2 +ber4ken +ber5n4a +ber3n4e +ber3ni +b3ernt +ber3o4b +ber3oe +ber3of +be4r3oh +ber5ost +ber3ö +berr2 +bers4k +ber5stau +b2e1s +be3s4an +besch4 +be4s3ep +be4s3er +bes5esk +be4s3id +be3s4la +bes4pe +bes4po +bes3sa +bess4t +b5esst. +bes3sz +be6stang +beste4 +be4ster4 +bes6t5erh +bes6t5erk +bes6t5ern +be4stol +bes4t5o4r +bes5tos +be4s5trä +be4s5tur +be4sum +be4sur +be3s4ze +be4t5ant +4b3etap +beta5sp +bet5auf +bete4s3 +bet4o +bet4tis +bett3r +be3uh +be3um +be3un +be3ur +b2ew +2b1e2x1 +3bez +2b1f2 +b3fa +bfal4li +bfang5s +b3fä +b3fe +bfin4s +b3fr +b3fu +b3fü +2bg2 +b3ga +bga6send +bge1 +bge3ne +bges4 +b3gl +2b1h +b3ha +b3hä +b2i +bi3ab +bi3ak +bi2ar +bi3as +bib2 +bi3ba +bibe4 +bibel3 +biber3 +bi2c +bid2 +bi3dj +biens4 +bie4r3a4 +bieres4 +bie4st +bie4t3a4 +4bieti +bie4tr +biet4s3 +bi3f +bi4ke. +bi4kes +bi3k2l +bi4kre +bi3ku +3bil +bi4l5ang +bi4lant +bi4lau +bil4da +bil4deb +bil4del +bil4d5in +bil4d3r +bild3s +bi4lei +4b3illu +bi2lu +2b3imb +2b3imp +bi3na +4b3indi +bing3a +bi4n3ok +4b5insas +2b3int +2b3inv +bio1 +biob2 +bi2o3d +bi3oef +bi3re +biri3e +2b3irr +bisa2 +bi4s3ei +bi3si +bi2s3p +bis5sau +bis4s3c +bi2st +bi3sta +b4i3tr +bit3s2 +bi3z +2bj +bjek4t5o4 +2b1k2 +bk4an +b3ko +b3kr +bl2 +2bl. +bla3b2 +2b3lac +blade5s +4b3laed +4b3laen +blaes5c +3b4laet +2b3lak +2b3lam +b4lanc +b3lang +bla4s3p +6blassens +blas6serk +4blassu +bl4at +b3late +3b4latt +blat4t5r +bl4au +blau4c +4b5laus +blaus4t +b3laut +2b3law +b3lay +2b3läd +2b3län +bläs3c +3b2lät +b2le2a +2b3leh +ble4i +b4lei. +b4leib +bleich6a +4b3leid +blei3f +4bleih +ble5imp +blein4 +ble5int +blei5sc +blei7sch +4bleit +blei5ta +b2lem +ble4mei +ble6m5ent +ble4mer +blem3p4 +ble2n +b4len. +b4lend +4blenk +b2leo +b4les. +4blese +4blesu +ble3sz +b2let +b3li. +4b3lich +4blief +b4lier +2blig +bl4in +b4lind +4bling +blings5 +blin4k5a +b4linz +4blis. +b4liss +b2lit +b3lite +3b2lo. +b2loc +4b3loch +bloch5s +blo6ckent +blo2g +3b2loi +blo4id +b3lok +b2lon +2blos +blues5s +2b3lun +blu4t3a4 +blu4th +blu4tin +blut3o +blut5sc +2b1m +b3me +bmen4s +2b1n2 +b3na +bnas2 +b3ne +b3nu +bo2a2 +bo3ab3 +boabi4 +bo3as +b1ob +bo2b3l +bo2br +bobs2 +bo3ch4 +bo3d2s +body3 +bo2e +boe4bl +boe3i +boei4g +bo3en +b1of +boh4r5ei +bo3is +bo4l3an +bo4l3au +bol3c +bol5erl +boli4e +bo4l3in +bo4lit +2bolu +bol3za +bombe4 +3bon. +bo3nat +bon4d3e +bo4n3er +3bons +3boo +boo2l +boo4t3i +boo4to +b1op +bo2pe +3bor. +bor3ae +bor3al +bo5rant +bo4r3as +bor4d5an +bor4dr +bo4rei +bo4rig +bo4r3in +bor2s +b3ort. +bor4ter +bor6t5rat +bose3s +bos3t +3b2ot +bot4e +bote4r3 +bo3th +bot4se +bot4so +bots3p +bot4s3t +bot3t +bo4xel +boy1 +bö2bl +2b1öf +bö2is +b1öl +2b1p2 +bpa2g +b3po +2bq +br2 +2br. +b2ra. +bra3a +2b3rad +4b3raed +b2rah +bra5i4sc +b2rak +b2ram4 +bran6dent +bran6derk +bran4d5r +4b3rat. +brater4 +bra6terd +bra6terg +4b3ratg +bra4t3h +bra4t3r +5b4raun +bra5unte +2b3räd +2b3re. +6brecherc +brech5s +6b5rechte +6brechtl +2bred +2bref +2b3reg +b3reic +4breif +brei6t5ei +brei4t5r +b3reiz +2brek +breli3 +b2rem +brem4sc +brem6sei +brem4s5t +b4ren. +4b3rent +2breo +2b3rep +b2re3r +b4res. +b3rese +4b3rest +b2ret +bret6t5en +2b3reu +b2rev +bri4da +4brieb +brie4fa +brie6f5er +4b3riem +b4rien +b4riga +b4rigi +4brigk +bri6kans +bri4ker +bri4ku +b2ril +b4rina +4b3rind +b2rio +b4risc +4briss +4b3ritt +b2roe +2b3roh +2b3rol +bro4m3a +3b2ron +b2ros +4b3rost +bro4ta +brot5ei +bro4ter +bro4t3h +bro4t3o +bro4t3r +brotz4 +2b3rou +b2rö +b2ruc +bru4chu +4b3rueb +2bruf +br4un +4b3rund +bru3n4e +brus4 +brust3 +bru4ter +bru4t3h +2b3rüb +3brüc +2b1s +b2s3ad +b4s3aer +b2sak +b4s3all +bs3amb +b4s3amt +b4sanf +b4s3ant +b4s3anz +b2sap +b2sar +b3sau +bsau4m +bsau4r +b2s3är +bsch2 +b4s5chec +b4schef +b5schu +b2sco +bs2cu +bsde3s4 +b2s3e2b +b4s3ehr +b4s3ein +bse2n +bsen3e +bs3erd +bs3er3e +b4s3erf +b4s3erg +bs5e4r5in +bs3erk +b4s3erl +bs3err +b4s3ers +bs3ert +b5serv +bs3erw +bs3erz +bse2t +bs3eth +bset6z5er6 +b2s3eu +b2sex2 +b4s3ide +b2s3im +bs3ind +bs3inf +bs5inte +b2sk +b2so2b +bs3off +b2s3op +bso2r +bs3orc +b4s3ori +bs5ort. +bs5orts +bs2öf +bs2pe +b4speri +bs2ph +b3spit +bsprun4 +bs5rei +bs3s2 +bs2t +bst5a4be +b4stade +bst3ak +b4st3al +bstands5 +bst5ank +bst5ann +bst5art +bs4t3as +b4s5tauc +bst5au4f5 +bst5aus +bs5teil +bst5einb +bst5eis +bst3e4k +bste4m +bst5ema +bst5emi +bst5en5e +bst5ent +bst5enz +bst5er5e +bst5erf +bst5erh +bst5erk +bst5ernt +bst5er5o +bst5erö +bst5erz +bs3tes4 +bs4t5ess +b3stet +b5steue +bst3h +bst3i4d +b4stin +bst3io +b4s3tip +b4st3ob +b4stoch +bs3tod +b4stor +b4strac +b4s5trae +b4strau +b4s3trä +bst5rep +bst5ret +bst5ros +bst5rue +b4st3rü +b4stun +b4stüb +b2sty +bs3umb +bs3ums +b2s3un +bs3url +b2sz +b3s4zep +2bt +btas4t5r +b3te +bt1h +bti2s +bti5schen +btisch5l +bto2n3 +btran4 +bts2t +btu3en +btü1 +bub2 +bu5chare +buch5art +buche4 +bu4ch5ec +buch5ere +buch5erf +buch5er6h +buch5er6l +bu6ch5er6s +buch5er6w +buch5er6z +bu4chet +buchs4p +bu2e +buegel5e +buer4o +2b3u2fe +bu3f2r +bu2gl +bu4g3ra +bul4l3a +2b3umf +2bumk +2bumr +bu3n2a +3bund +bun4dä +b4undd +bun6d5ent +bun4d5er +bunde4s +bun4din +b4undn +b4undz +2b3unf +b3un3gn +2b3unh +2b3unl +bun6t5er6z +bun4tr +2bunw +bur3c +bur4gan +bur4gar +bur4g5au +bur4gin +bur4gl +bur4gr +bu3ri +2b3urn +b3ursa +b2us +bu2s3a +bu4scha +busch5ar +bus5chau +bu6schei +bu6sch5el +bu6scheu +bu4schl +bu4sch5m +bu4schn +bu4sch5o +bu4schr +bu4schw +busch5wa +bus5ers +bus5erw +bu4sin +bu2so +bu2s3p +buss4pa +bus3ta +bu2su +bus3un +bu4ßer +but2a +bu2t3r +bü1c +bügel5e +bühre4 +bür2s +2bv +2bw +b3wa +bwasse4 +b3wä +b3we +b3wi +b2y1 +byauto4 +byo2 +by3p +byp4i +bys2 +byte3s +2bz2 +b3ze +b3zu +bzug2 +bz4u3n +1ca +cab2 +ca3bl +3cac +ca4ch +cae3 +caes2 +ca3g2 +cago3 +ca1h +ca3lab +ca3lam +cam4p3l +c3ang +ca2pe +car2a +car3n2 +carri3 +car2s +ca3s4a3 +cas3te +ca3t2h +ca3ur +caus2 +caust3 +ca1y2 +cä1 +cäs2 +2cc +c1ce +c1ch +cch4i +2c2d2 +c3do +2ceae +2cear +2ceä +2ceb +2cec +1ced +ce2dr +2cee +2cef +2cegr +1ceh +ce1i +2cej +2ceka +ce3l2a +cele3r +3cell +3cels +cen1 +cene2 +ceng2 +ce2n3i +ce2no +cens2 +3cent +cent3o +cen4tos +cen3un +ce1o2 +2ceop +1cer +cere3u +ce3ri +ce4ris +ce3ro +ce3s2h +2cesp +ce3sti +2cet +3cet. +ceta2 +ce4t3am +3cett +ce1u +2cez +1cé +c1f +cg2 +2ch. +4chab +ch3a4b3i4 +2cha2c +ch3ack +ch3aep +ch5aerm +ch3aes +2chaf +5chafei +ch3a4ge +ch3akt +5chalam +chal5b +chal6lein +cha4mer +ch5ampe +3chamy +3chanc +ch5andr +ch5ange +ch5angr +ch3ano +ch5ansa +ch5anst +4chanz +3chao +ch3app +4char. +3chara +ch5a4rab +3chard +4chare +5charta +cha4sc +ch3asp +ch3atm +3chato +4chatt +4chatu +4chatz +4chauk +ch5ausf +chau5spo +ch5ausr +ch5ausst +ch5austr +ch5ausw +ch5ausz +chau5t +ch3äp +ch3ärm +ch3äs +1châ +2chb +2chc +2chd +che3b +4ch5e4ben +ch5echt +che5ers +3chef. +che4fei +4cheff +5chefin +che4fr +3chefs +2cheh +4chehe +4chehr +4chei +ch3eil +ch3eim +ch5einla +ch5eintö +ch5einze +ch5eis. +ch5eise +che4las +ch5elek +4chelem +che4len +che4ler +che4me. +ch4emi +5chemie +5chemik +4chemp +che4neb +ch5enge. +che4nid +che4no +chens4p +4chentc +chen6ten +4chentf +4chents +4chentw +cher3a +che4rat +cher6bes. +4ch5erbs +cher6de. +6chergebn +4ch5ernt +6cherregu +cher5sta +ch5ertrag +cher6trage +6cherzeu +4ch3ess +4cheta +ch5e4tag +ch5e4tik +4ch3e2x +1ché +2chf +2chg +ch5gau +2chh +3chia +chilf5s +chi5li +4chind +4ch3inf +ch3inh +ch5inko +ch5inkr +ch3ins +4ch3int +4ch3inv +3chip. +3chips +3chiru +chi5sta +3chit +2chj +2chk +2chl +chl4a +ch5lade +ch5laend +ch5lanz +ch3lat +ch5lauf. +ch5läd +ch5länd +ch5lein +ch4len +chl4o +ch4lun +2chm +ch2me +2chn2 +chn4e +ch4nep +chner6ei. +chn4i +4chob +cho3ch +ch3oel +4choen +3choer +4chof +ch3off +chof4s3 +3chog +cho5l4a +3chole3 +ch3orc +4chord +cho4rei +5chores +5chors. +ch5orts +3chorw +3chosl +ch3öl +2chön +3chör +2chp +chr2 +2chra +ch3rab +ch3rad +ch3ral +chrap4 +ch3ras +ch3rat +2chrä +2ch2re +ch3reh +ch3reu +ch3rev +ch3rh +ch3rin +5christ +4chrit +4chroe +ch3rol +5chromo +3chron +ch3ros +2chrö +2chru +2chs +ch4sm +ch6s5pani +ch6spart +chstro4 +2cht +ch4tang +cht4ank +cht6en. +ch4t5ruh +4chue +4chuf +4chuh +chu2n +4ch3unf +ch3uni +4chunt +4chunv +ch3urs +ch3urt +4chut +chut4t3 +2chü +2chv +2chw +2chz +ci2a +ci1c +ci3es +cill4 +c3ind +cins2 +c3int +ci2s1 +1cit +c2k +2ck. +ck1a +3cka. +cka2b +ck3a2c +2ckae +2ckal +cka2m +cka2n +cka2r +3ckard +3ckau. +3ckaue +2ck1ä +2ckb +2ckc +2ckd +1cke +2cke2c +4ckeff +2ckeh +ck3ehe +2ck3ei +cke4la +ckel3c +4ckelek +4ckelem +ckelti4 +4ckemb +4ckemp +ckener6ze +6ckensem +cken4s5o +cken6s5tur +6ckente. +6ck5enten +4ckentf +cken6trop +4ckentw +cke4rec +6ckeremi +cker6fül +6ckergeb +6ck5erken +cker6ker +6cker6lau +6ckernie +6ck5ernst +4ck3ero +ck6ers. +4ck5ertu +4ckertü +4ckerz. +cker6zeugn +4ck3ese +2ck3eu +2ckex +2ckf +2ckg +2ckh +1cki +2ck3i2d +4ck3ind +4ck3inh +4ckink +4ck3inn +4ck3ins +4ckint +3ckis +2ckj +2ckk +2ck3l +2ckm +2ck3n +ck1o2 +ckoe2 +ck1ö2 +2ckp +2ck3r +2cks +ck5sac +ck4sar +ck4sint +ck5stae +cks5tate +cks4tec +cks4to +cksto4r +ck3str +2ckt +ck4tar +1cku2 +2ckue +2ck3uh +ck3umb +4ck3ums +4ckumw +4ck3una +ck3unf +4ckuni +4ckunt +2ck3up3 +2ck3ur +2ckw +1cky +2ckz +c2l2 +clet2 +cle4vel +clien4 +clin4g +cli2p +clip3a +1clo +clo3ch +clo5fe +1clu2 +club3 +cm2 +c2mi +1co +coca3 +co3ch +coche5 +co2de +code3s +co3g +cog4n +coi2 +co3it +co2ke +cola3s +co4leu +colle6ge. +co3l4o +com4te. +comtes4 +continuati6 +co2o +2cope +co4pe. +co3ra +2corb +cor4da +core3s +cor5ti +corvet4 +cos3t +2cotc +co2te +co2u +cou4c +cou3si +2cout +1cô2 +2c1p +2cq +c2r +c3rae +c3rah +c3rau +c3rä +1cre +2cree +cre4me +cres2 +cros2 +cross3 +1cru +cry2 +2cs +cs2a +cs2f +c2si +c3s2ti +c3s2tr +2ct +cte2a +cti2o2 +ction3 +ctur2 +1c2u +2cua +cul4u +cup1 +cu2pe +cup3p +cu2p3r +cussi4o +cus3t +c2v +c3ve +2cx +1cy +cy3an. +cybe2 +1ç +1d +da1a +2d3a2al +d1ab +d4abab +4da4bak +2dabb +2dabd +d2abe +4d3a4ben +2dabf +2dabg +2dabh +dabi2 +4da4b3it +da4blo +2dabn +2da2bo +dab4ra +4da4bre +d4ab4ru +d4ab4rü +2dabs +2dabt +2dabw +2dabz +d1ac +dach3a +dachau6t +d4ache4 +da4chec +da4ch5er +dach4in +dach3o +dach5stu +4dachtz +da5chung +3dack +d1ad +da4del. +da4delg +da4dels +da2do +dad2r +da2e +3daec +2d3aeg +2d3aeh +4d3aemt +dae4n3a +d3aend +2d3aep +2daeq +da3erf +d5aerzt +2d3aeu +dae3us +2d3aex +d1af +2daff +da3f2l +dafo4n +da3fra +d1ag +dagi4 +dag2o +da1h +dah3l +da3in +2d3air +da3ism +da3ist +da4kad +da2kn +da4kro +dal3a4d +dal5aer +da3lag +da4lär +da4lop +da4lor +da3lö +4d3alpe +4d3alte +da3lü +2d3amb +dam4e +d3a4mei +damen5s4 +d5ammae +d5amman +4d3ammä +4d3ampe +d3ampu +2d3amt +dana3e +4d3anal +dan5alg +da4nan +d3a4na3t +dan4ce. +dan3d2 +4d3anda +4d3anei +4d3a4n3eu +2danf +d4anfu +4d3anga +d5angeb +d3angs +2danh +dani5er +4danim +dan4kal +dan4kan +d5anker +dan6k5erf +dan4kl +dan5kla +dan4k3o +dan4kr +4danle +4d3anna +2d3a2no +2dans2 +d4ans. +d4anse +danta4 +4dantr +4dantw +4d3anzu +4d3anzü +dan4zw +2d1ap +d2apa +3dapol +da4por +da2pu +da2ra +dar3an +dar3as +darb2 +5darbi +2darc +dar4dar +dar4de +dard5ei +dard5er +dar4do +dar4dr +dardwa5re +4d3area +dar3ei +dare4m +dar5emi +da4r5en5e +d5a4rist +dar4kr +dar4mar +4d5arres +4d3art. +4d3artd +4d3artg +4d3ar4th +4d3arti +dar4t5in +4d3artl +da2ru +dar5um. +4darzt +da4sche +da5sei +das6ein +da3s2h +da2s2p +da3spa +4d5aspek +4d3assi +da3st +4d3aste +das4tra +das4tu +da3ta +da3tei +4d3a4tel +date4n +da2th +dati4v3 +2d3atl +2d3atm +dat4st +4d3atta +datum4 +2d3aud +dauer5e +2d3au2f3 +2d3aug +dau2i +2d3auk +da3unt +dauri3 +2d3aus +2d3au2ß +4dauto +d1ax +d1az +da2zo +3däc +2d1äf +2d1äg +2d1äh +2d3ämt +dä2n3a +2d3änd +2d1äp +2däq +därm2 +4d3ärzt +2d1ä2u +dä3us +2däx +2db2 +dbe4e +dbi2e +2dc +d3ch2 +dchengene6 +dco2r +d1cr +2dd2 +d3da +dd4ae +ddar4m3 +d3dä +dde2a +ddel5e4c +dde3ra +d3dh +ddha3 +ddi2e +ddien4 +dding5s +d3do +dea2d +de4adl +de3ae +de3a4t +deb2 +4d5e4bene +3debü +de4ca. +de5chor +de3chr +2d3ecu +ded2 +4d3e4del +dede3s +dedi4e +de4dit +dee2 +de3eb +3de3ef +de3eg +3de3em +de3ep +de3er +de3es +de3et +3de3eu +3deex +de4fa. +2d3eff +def2l +deg2 +2d3egg +deh2a +de5hop +2d3ehr +4dehyds +de4ic +dei6ch5er +de5i4den +2d3eie +2d3eif +2d3eig +de3im +4d3einb +de3in4d +4d3eine +de5in4fo +de5in4fr +d3eing +dein6gen +4deinh +4d3eink +4d3einl +4d3einm +4d3eino +4d3einr +4deins. +4d5einsa +4d5einsä +de5in4se +dein6sta +dein6str +dein6ter +5d4e3in4v +4d3einw +de3io +de3i4si +d4e3ism +dei4sp +de3ist +2dekz +del5ach +de4l3am +dela4n +del5ano +de4l5ant +dela4t +del5ate +del5auft +de4l5aug +deld2 +de5le. +de4lef +de4l5ei4g +4d3elek +4deleme +del5end +del5erk +del5ern +del5er4r +del5erz +4d3elf. +4d5elfe. +delfen6s +4d3elfm +deli4ne +del5inn +delk2 +del4l5an +del4lar +delle4 +del4l5eb +del4lei +dell5eic +del4l5er +delmi4 +del4n3e +de4l3ob +de4l3op +del6s5ach +del4san +del4sas +del6sein +del6s5int +del6s5pan +del6spar +del6spat +del4spr +de4manh +dem4ar +dement4 +de6m5entg +demi4e +4d3empf +d4en. +dena4b +den4ade +de3n4am +de4nan +de5n4arr +de5n4au. +de5naue +dende4n +4d3endh +4d3endk +den3d4r +dend4s +dend4u +den3e4b +de4n3e4d +de4n5end +de4n5eng +de4nep +den5erb +den5erei +4d5energ +de4n5ero +de4n5erö +den5ers +de4n5ert +de4nerw +den3f2 +4d5en4ge. +6d5engeln +6d5en6gels +den6gen. +de4nid +de4n3im +den4k5ak +den6kerl +den4k5ob +de4nob +d4ens +den4sal +den6stie +den6stop +dens5tor +den4sur +4d3entd +dentengene6 +4d3entf +4d3entg +den3th +4d3entk +4dentla +4d3entn +den6trop +4dentw +4dentz +den6z5erf +den4zym +2deol +de3p2f +dep2l +depo4t +de3ra. +der3ab +de5rabi +der5a4de +der5aen +de5raet +de5raeu +der3af +der3a4g +der3ak +der5alb +der5alt +de5rampe +dera4n +de5ran. +der5ane +der5ant +der5anz +de4r3ap +dera4s +der5asi +der5asp +de4r3än +4d3erbs +4derdb +de4r3e4b +de4r5eck +der5edi +6dereigni +der6eini +der5eis. +de4rele +der5ell +de4remi +der5ende +de4r5ene +der5enge +de4r5ens +der5ente +der5entg +der6er. +derer6de +derer5e +der4erf +de4r5erh +der5erre +de6rersa +dere4t +der5eta +de4r3eu +derf4 +der5fl +derg2 +6dergebn +de4r3id +de4r3i4m +der5inb +der5inner +de6r5innu +derin6teg +de4r5iss +derk2 +der4nal +der5o6ber +deroe4 +der5oef +der5onk +de4ror +de3rot +derö2 +derr2 +ders4k +der5stau +dert5ende. +der6ters +6dertragen +de4ruh +der4ump +der6weck +4d3erz. +4d3erzv +de4s3a4g +de4s3ah +de4sak +des5amt +des5ang +des5ant +des5anz +de4sap +de4s3au +de6sche6f +de4s3eb +de4s3e4h +de4s5eil +de4s5ein +des5elt +de4s5en5e +des5eng +des5er5e +des5erf +des5erg +des5erh +des5erk +des5erl +des5erm +des5ern +des5ers +des5erz +dese4t +des5eta +de3shu +desi5er. +de4s3in +de4sos +de4spa +des6park +de4spot +des4pu +dess2 +dess4e +des6sens +des6ser. +des6seri +des6sern +des6sers +de5staf +de5stall +dest5alt +de5stec +dest5eige +dest5einsa +de5stell +des6temp +des6t5erg +de5stern +des6tert +des6tina +de5stras +des4tum +de2su +des3un +de3ta +deta4c +4d3e4tat +deten4t +4d3e4thi +de4tik +det2s +2d3etw +de3u2f +de3uh +2d3eul +de3um +de3u2n3 +de3url +de3us +d4eut +4d3e4vid +dex3em +dexen4 +de4x5end +de4x3er +de4xis +4dexpe +4dexpo +2d1f2 +dfang3 +dfen2 +dfin4s +df4la +2dg2 +dga2t +d2ge. +dger4e +d4ge3sh +dge6tang +dge4tau +dge4t3e +d3gl +2d1h2 +dhau6eri +dhek2 +d3hu +dh4un +dia5l4ei +dialer4 +di3arr +dia3s2 +dib2 +di3ce +di3chl +di3cho +dich7ter +dichts4 +di4ck5el +2d3i2co +4d3i4dea +4d3idee +di4den +2di2dy +di3e2d +di2e3i +di5elek +die4n5e4b +dien5e4v +die4ni +die4no +di5ens. +dienst5art +dienst5rä +dienter6mi +die2p +die4rei +diese4 +die4th +dige4s +dig2n +dig4ta +dig4t3r +di4lar +di4l3au +dilf2 +4d3illu +dil4s3t +2d3ilt +2d3imb +2d3imp +di3na +2d3ind +2dinf +d3infe +d3infl +d3info +d3infr +3ding +din4g3a4 +2d3inh +2d3inj +4d3insc +4d3inse +4d3insp +4d3inst +4d3insu +4d3insz +2d3int +2d3inv +di4odi +dion5in +dion3s4 +di3ora +dios2 +di1p2 +di3pt +di4rak +di4ran +di3re +4d3i4re. +4d3i4ren +4d3i4rin +4d3i4ris +2d3irl +2d3irr +di3s3a2 +di4sag +di4sas +di4sch5r +diskan4 +2diso +di4som +di5s4per +2d3isr +diss2 +dist4 +di3s4ta +dis3te +di4t3ak +di4tal +di4t3an +di4t3au +dite3c +di4t5er5e +di4t5erl +di4t5erm +di4t5ero +di4t5erö +di4t5ers +di4t5erw +dite3s +dit5ins +di4tob +di2t3r +dit3s +di2t3u +di3ve +di3vi +di2vr +2dj +2d1k2 +dkab2 +dkraf4 +dkraft5 +2d1l +dl2a +dlan4dr +dlang5s +d3le +dl4er +dle4r3a +dlerin6t +dli2f +d3lo +d3lö +dlungs5 +dl2v +2d1m +2d1n +dn2a +d3n2e +dne3p +dnet4s +dn2j +dnus2 +dnuss3 +do3a2 +d1ob +3d2oba +do4bera +do4berh +do4beri +do4bern +do4berr +2dobj +2dobm +2dob2s +3doc +do3chi +doch4t5r +do3e2b +2d3oe2d +2d3oe2f +2d3oek +2d3oel +doen4 +doe5ne +doe2r +doer3i +doe4s3c +4d3o4fen +2d3off +2d3ohr3 +3dok +3doli +do4mal +do4man +do4mar +do4m5ing +2domn +do2mu +3don. +3don2a +don4au3 +donaus4 +doni3e +3donn +2dony +doo3g +do2or +2d3opf +do3ra +4d3o4rak +4d3orbi +2d3orc +2d3ord +3dorf +dor4f3a +dor4f3ä +dor4fei +dor4f3i +dor4f3l +dor4fo +dor4fr +dor4f3u +2d3org +4d3orna +4d3orni +2d3ort +dor4t3a +dor4t5ei +dor4ter +dor4t5he +dor4tr +3dosi +dos2k +2d3osm +do3sp +dos5sa +d3os2t +dosta4s +doste4c +dosten4 +do4stu +2d3ouv +dow2s +d2o1x2 +2d1ö2d +2d1ö2f +2d1ök +2d1öl1 +dör4fl +dö2s3c +2d1p2 +2dq +dr2 +3dra. +2drab +2d3rad +d3raed +4d3raet +d3raeu +3drag +d2rah +4drahm +2d3rak +3dral +d3ramm +d3ramp +dr4an +4d3rand +4d5rangl +dran5k +2d3rap +dra4pu +d3rase +d3rass +4d3rast +dra4t3a +dra4th +dra4tin +d3raub +4d3rauc +4d3raum +4d3raup +4d3raus +4d3raut +2dräd +d4räng +2d3rät +2d3räu +3dräue +2d3re. +d4rea. +4dreal +d4reas +d4reck +2dred +2dref +2dreg +3dreh +dre4h3a +dre4hi +dre4h3o +4dreic +d4rei3e +d4reik +4dreine +4dreins +d4reir +4dreise +drei5s6tr +d4reiv +d2rej +2drek +2drel +dreli3 +2drem +2dren +dre3na +d2reo +2drep +2drer +d2res +4d3res. +dre6schm +dre6schw +4d3rese +4d3resi +dres6san +dres6s5ei +dres6sel +dres6s5er6 +dres4st +4d3rest +d3resu +2dret +2drev +d2rew +2drez +d2ré +2d1rh +2d3ric +4d3rieg +drie4n3 +3drif +4d3riff +d4rift +4d3rind +4d5ring. +2d3rip +4d3risi +4d3riss +d2rit +drit6terf +4d3ritu +d2ro +2drob +4d3rock +d3roec +droe4s5c +4drogg +3droh +4drohr +5drohu +dro4id. +4d3roll +3dros +d3rose +2drot +2d3rou +2d3rov +d3row +2d3röc +drö4s3c +2d3rub +3d2ruc +2d3rud +4d3rue3b +drue7cke +d3rueh +2druf +2d3ruh +4drund +d4runt +2d3rut +2d3rü3b +drü5cke +2d3rüh +2d1s +dsa5cha +d2s3ad +d4s3aem +d2sak +d4s3alk +d4s3all +d4s3ane +d4s3ang +d4sanm +d4s3ant +d4s3anz +d2sar +ds5assi +dsau2 +d4s3auf +d4s3aus +d4saut +ds2äu +ds2ca +dsch2 +d4schin +dsch4r +5d4schun +d2scr +d2s3e2b +dse2e +dsee3i +d2s3ef +ds3ehr +ds4eign +ds5einl +d4s5eint +d4s3ele +d4s3emb +d4s5e4mis +ds5ende. +ds5enden +dsen5er +d4s3eng +dsen3s +d4s3ent +d4s3er3e +d4s3erf +d4s3erh +d4s3erk +d4s3erl +d4s3erm +ds3err +d4s3ers +d4s3ert +d4s3erz +dse4t +d4s3eta +d4s3eth +d2s3ev +dsex2 +d4sexa +d4sexe +d3s4ha4k +dshi2 +d2sid +d4s3ind +ds3ini +d4s3int +d2ske +d3s4kel +d4skir +d4skis +d5sklav +d2sko +d3slaw +d4sli +d2so2b +d2s3oe +d2s3op +dso2r +d4s3ori +d4s5ort. +d2sö +ds4ped +ds4pen +ds4perb +d4speri +d4sphi +d2spl +ds4por +d6sporto +d4sprae +d4sprä +d4sprei +d4spro +ds2pu +dss2 +ds3sa +dst2 +d6s5taeti +ds5tauf +d4s5täti +d2ste +d3stec +d3steg +d3stei +d4steil +d5stell +d3s4tep +d5s4terb +d5s4tern +d3steu +d4sth +ds2ti +ds4til +d4stoch +ds4tol +ds4top +ds2tr +ds5trad +d5strei +ds5treu. +ds2tu +d2sty +d4suml +d4s3ums +d4sumz +d2s3un +d4s3url +2dt +dta2d +dta2n +dtan3e +d3t2as +dtas3t +d3t2ax +dtä2s +d3tea +d3tec +dteer3 +d3tem +dte3na +d3tep +dte3ra +d3tes +dte5sta +dti2d +dto2b +d3töp +dt3ral +dt3rec +dtre3s +dt1s2 +dt3sa +d3tur +d3ty +du3al. +du3all +du3alm +du3alp +du3alv +du3alz +du3ar +du4b3li +du3ce +due5bel +due3br +du3el +du3en. +du3ena +du3enb +due3p +duese4 +du3ett +duf4ter +duf4to +duf4t3r +dug2 +du2ga +2d1uh +du3ist +d3u2kr +du4l5ar4r +du4l5art +du4l3au +du4l3ei +dul3s +du2lu +2d3umb +2d3umd +2d3u2m3e +2dumf +2dumg +2d3umh +2dumk +2duml +d4ump +2dumr +4d3umsc +2d3umt +2d3umv +du2n +2d3una +2d3und +dun4de +2d3une +2d3unf +d3unga +4d3ungl +dungs3 +2d3uni +dun4kl +2dunr +4d3unsc +4dunsi +dun4st +dunst5r +2dunt +2dunw +2d3unz +du3os +dur4c +durch3 +2d3urk +2d3url +2d3urn +dur2s +4d3ursa +2d3urt +dusche4 +du6sch5ec +du6sch5ei +du4schl +du4schn +du4sch5w +dü3bel +d3über +düns3 +düse2 +2dv +dv4en +2dw +dwa2 +dwa5sche +dwasse4 +dwert5a +dwesten6 +dwes6tend +2dyac +dy3an +dydi2 +dygene4 +dy3la +dy3lä +dy2le +dy1o2 +dy2sa +dy4s3l +2dz2 +2e1a +ea2be +e2abi +ea2bl +ea2br +ea2c +ea3chi +eadli4 +ea2dr +eae2 +eaga2 +ea2ge +ea2gi +ea3g2l +ea2ka +e2akd +eak3e +e2akg +e2akr +eal3ac +eal3ak +e3a4lei +e5a4l5ent +e5alen4z +e5alerb +e5a4l5er4g +e5a4l5ert +e3alex +e2ali +e3a4l3in +eal4ler +ealmo4 +ea4lob +ea4l3on +ea4lop +eals4p +ealti4 +ea2l3u2 +eam1 +e2ame2 +eam3ev +ea4mid +eamm4 +eam3s2 +eam3tu +eanden4 +e2ane +eanet4 +e3anf +eanla6gente +eannet4 +ea4n3ob +e4ant. +ean4t3a4 +ea2q +ea2ra +ear3ac +ear3ak +ear3am +ea3rat +ea4r3ei +ea4r5ene +e4arer +e2ari +ear3n4 +ea2ro +ea2r3u +e3a2sc +e3asf +easin4g +ea2sp +eass2 +e2at +eate2 +eat5ens +eater3 +e3atm +eat3s2 +e3at3t2 +e2au. +eau4fe +e3auff +eau5f4re +eaug2 +ea4u3n +eaussen6dung. +eau5ste +ea2va +e2az +e1ä2 +2eb. +eba2g +ebah6ner +eb3anz +eba2p3 +e3bas +2ebc +e3bef +ebein4s +e4b5eise +ebe4lan +ebe4lau +ebe4ler +ebel5in +e3bem +ebe4na +ebe5n6au. +ebenen5e +e5benu +e5benz +ebe5rad +eber5as +ebe4rel +eberg5s +e3bes +ebe4s5e4h +ebe4ses +ebe4sor +ebet4s3 +e4b3inb +eb3lag +e4b3le. +eb3lem +eb4len +eb5les +eb4leu +eb4lie +e3bloc +eb4loec +e3blöc +ebni2 +e2bo2b +ebo3ef +ebö2s +2ebr +eb2re +e3bru +e3brü +eb4s3ac +eb4s5ang +eb4s3as +eb4s3au +eb4s5ele +ebs5end +ebs5ene +eb4sep +eb4ser +ebsi2 +eb4sid +eb4sis +eb2sp +ebs5panne +ebs3pe +eb6s5taet +eb4s5tät +ebs5tem +ebs5ten +ebs3t4h +ebs5tic +ebs5tie +ebs5treu +eb2su +e4bueb +e4bunt +ebu2t3 +e2büb +eby2t +e1ce +ech3ab +ech3ae +ech5ang +ech5aus +ech3ä +e3che +eche4c +eche3e +ech3ei +eche6reib +echer6fah +echer5o +echer6zie +e3chi +echi3e +ech3la +e3chlo +ech3lu +ech3m +e4ch3n +e2cho +3echo. +ech5obe +echoe4f +ech5org +ech3ö4f +e2chr +ech3ra +ech3rä +ech3re +echsau4 +ech6s5en6d +ech4s3o +ech4spa +ech4spo +echst5ri +echs4tu +echt5er6ha +ech7ters +echt6s5ac +echt6s5ag +echt6s5eid +echt6sen +echt6stie +ech3uh +ech3w +e1ci +e4ck. +e3ckag +e4ckef +ecke4n +e4ckep +eck5erfo +ecker6ken +eck5ermi +e4ck5erw +eck5erze +e4cks +eck5spo +3eckzä +e1cl +e3co +e3cr +ecre2 +ec3s +e3cy +2ed. +e2da2b +ed2ac +eda5chu +ed2ad +ed2dr +ede4al +ede3e +edel5au +e3dens +eden4sa +eden4s5e +eden4sp +eden6s5tau +eden6stem +eden6s5tu +ede3o2 +edere4 +ede6r5eic +eder5er4 +ede6rinn +eder3o4 +eder3ö +ede3sk +ede5stei +edes4ti +ede5str +edia3l +edi3p +editen4 +edi4tin +ed2o +ed4oc +edost3 +edpol4n +e3d4rei +ed3rik +3edris +ed4s3al +ed4sco +ed4seh +ed4s3es +edsi2 +ed2sk +ed2s3o +ed4s3ti +ed4sto +ed4s3um +ed3te +edte3e +edte5in4 +edt2h +edue3b +edu2f +e2d3ug +e2dum +ed2ü +edü3b +edy1 +edys2 +2ee +ee3a2 +e3e4ben +eeb2l +ee3bs +ee1c +ee2ce +e3e2ck +eede3s4 +eedi4e +ee2dr +eed3s2 +ee1e2 +eees3 +e3eff +eef2l +ee3fo +eef3s +eeg2 +ee4hep +e3ehr +e1ei +ee3ig. +ee3imp +ee5in4se +ee3i4sc +eei4sp +e3e4lek +eele4n +eele4s +e3elf +eem4a +ee5man +e3emb +e3emp +ee2na +ee3n4ac +ee3n4ad +ee3nae +eena4g +ee3nas +een3er +een4es +een3e4v +e3en4ga +een4o +ee5nor +een3sh +ee1o2 +ee1p +eer3ad +eer3al +ee5ram +eer3as +e5erben +e3erbt +eer5eis. +eer5eisa +ee4r5en4g +ee5r4e4s3 +eere4t +eer5eti +e3er4fi +ee3rhi +e3erhö +e2eri +e3erlo +e3erlö +e3ermä +eero4be +ee4r5oef +ee4r3oh +e3eröf +ee3rös +eer3sh +eeru2 +ee4r3uf +eer4und +eerz2 +e5erzeu +ee1s2 +ee4seb +ee3sh +ees3ka +ee3sl +ee3sp +e3es3s +e3e4tat +eet5ein +ee2th +ee4tik +ee3tr +ee4t3ro +ee1u +eeu2f +eeun2 +eewa4r +e1e2x1 +e1f +2efa +e2f3ad +e4f3aeu +e2f3a2g +ef5akti +ef3ana +e2fap +e4farc +e2fat +ef3auf +ef3aus +e4fähn +e2fäu +2efe +ef3ebe +efecht4 +e4f3edi +e4f5eing +ef5eink +efe4l5ei +efel7ler +efe2n +efen3e +e4f5ener +e4f3enk +e4f3ent +efere4 +efer5eg +efe4rei +efer6gebn +eferin6d +e4f3eta +e2fex +ef4f3as +3effek +ef4fle +ef4fli +efig3s +efi4le. +e4f3ins +ef3int +e2fj +ef2le +ef4lue +e3f4lü +2efo +e2fob +e2f3o2f +e4f3ora +ef2r +ef3rat +ef3red +ef3reg +e5fris +ef3rol +ef3rom +efs2c +efs2p +efste4h +ef3str +eft5anz +eft5erkr +ef4top +ef4t3ri +ef4t3ro +eft2s +ef3ums +2eg +egab4r +ega4la +ega4l5in +ega2m +ega5sta +eg2äh +egd2 +ege5in4s +ege5in4t +ege4l5ac +ege4l5au +ege4lec +ege6l5ei6er +ege4ler +ege4l5in +ege4lo +egel6s5tur +egena4 +ege4nag +egen5ec +ege6nero +ege4rak +ege4ral +ege4ran +ege4r5as +ege6reck +ege6rend +egeren6g +egerer6k +ege6rers +egerin6d +ege6rin6g +egerin6t +ege6s5tro +ege3u +egg2l +eg3ins +e4g3lac +egler5e +eg3nei +egori4e +eg3rän +eg4sal +egsauto6 +egs2c +egse4r +egsgene6rat +eg3spe +eg4stä +egus3t +e2gy +e1ha +eh3aka +eha3kl +ehalt4s +e4h3ans +e4h3ant +eh3anz +e4h3app +eha4sc +eh3aug +eh5ausw +eh5ausz +e1hä +e1he +eh3eff +e4h3ein +eh3e4la +eh5elek +ehelf4 +eh3elt +3e4hema +eh3emb +e4h3e4mi +e3hen +ehen4se +ehens5ei +3ehepa +eh3epi +eher4an +eher4fa +eh5erfah +eher4fo +eher6gebn +eher4ke +eher4le +e6h5ersta +eher4zä +ehe5str +e1hi +e2h3i2d +eh3ill +eh3imb +eh3inf +e4h3inh +e4h3ins +ehi2r +ehi2t +ehla4c +eh3lae +eh3lam +ehl3ap +eh4l3as +ehl3au +eh3lä +eh4l5e4be +ehl5einh +eh4lel +ehle4n +eh3leu +ehlkop4 +ehl3o4r +eh4l3öf +ehl6s5ein +ehl4sin +2ehm +eh4mab +eh4mant +ehm6ens +ehmen6sp +ehmest5 +ehm5estr +eh3mu +eh3nad +ehn3e4c +eh3nel +eh3no +ehn6s5eid +e1ho +eho2l +eh3oly +eh3ope +eh3org +eh3osm +e1hö +eh3öff +ehö4r5er +ehr3ak +ehr3al +ehr3an +ehr3ap +ehr3än +ehre4c +ehr5eck +ehr5ei. +ehr5eic +ehr5eie +ehr5ein +eh4r5eis +eh3ren +ehre4n5e +ehrens6t +ehrer6fa +ehrer6la +ehr5er6tr +eh3reu +eh3rie +eh3roc +eh3roe +ehr3of +eh3rom +eh3rot +eh3rö +ehr4s5a4g +ehr6sein +ehr4ser +ehr6stras +ehrungsgene6 +ehs2 +eh3sh +eh3sk +ehst2 +eh3sta +eh5stei +eh3str +eh3stu +2eht +eh3t2a +eh5ther +e1hu +eh3unf +e4huni +ehu3s4a +e1hü +eh3w +e1hy +2ei1a2 +2eiä +ei4b5aer +ei4b3ak +ei4bär +eibe3e +ei4b5ele +ei6b5erla +ei4bid +ei4b5las +ei4bli +ei4b5rau +ei4brea +ei4broc +eibu4t +ei4b5ute +ei3ce +eiche4n +eich3r +ei3cl +e2id +ei4dar +ei4dau +5eidech +ei4d5eis +eid5erfue +ei6d5erfü +eides4t +eide5ste +eidi4e +4eidn +eid4str +e2i1e +eie4be +eie2d +eie3f4 +eie2l +eie2m +2eie2n +eienge4 +eie4r3a +eierei6s +ei5ereu +5eiersu +eie2t +ei3f4lo +eif5lut +e2ig +2eig. +ei4g3ak +ei4g5ans +4eige. +eige5in4 +e5igel. +5eigene. +ei4glo +ei4glö +ei2g3n +3eigne +3eignu +ei2go2 +ei4g5rad +ei4g5rat +2eigs +eigs4p +eig5str +2eigt +2eigu +4eigw +ei4kak +ei4k5ank +ei4kli +ei4klo +ei4kni +ei4l3ab +eila4n +ei4l5ane +ei4l5ang +ei4lant +ei4l5anz +ei4lar +4eil3c +eild2 +ei4lein +eiler6geb +eiler6kl +eilf2 +ei4l5inf +ei4l5ins +eil4ip +ei4lob +ei4l3op +ei4m3aa +ei4mab +ei4m3a4g +eim5all +eim5alp +ei4m5ans +ei4map +eima4ta +eima6ter +eima4to +eim5aus +e4ime +ei4m5ent +ei4m5erl +ei4m3i4d +ei4moe +eim5org +2eimp +eim3sa +ei4m3um +ei4mur +eina4b +ein3a4d +ein3ae +4einam +ei4n3an +ein3a4s +4einat +ei2n3ä +einde5ra +ein6d5erk +ein4duk +ein4dus +4eineb +eine5in6f +eine5inn +einen6zy +ein6er. +eine5ras +ein5erbe +ein5erkl +ein5erkr +ein5er6la +ei6n5er6sc +ein5ertr +ei4n5ess +ei4n5e4ti +ei4n3eu +ein6fant +ein4fek +ein4fiz +5einflus +4einfo +ein4fo. +eing2 +e3ing. +e3ingt +4einhab +ei4n3i4d +4einie +e3i4nit +eink2 +ein6karn +ein3n2 +ein4nen +ein6nere +ei2n3o2 +5einord +ein4seb +ein6seln +ein4sid +ein6stab +ein6stal +ein6star +ein4sul +ein4sz +ein4teg +ein6terf +ein6terp +5eintrag +ein6trig +5eintrit +eint4s +ein5tsc +2einu +e4inver +ein4ves +ein3z4s +ei1o +eio2b +eio2p +eio2s +2ei1p +eip2f +2eir +eir2c +e3irr +ei3ru +ei4s5aer +eis5ala +eis5alt +eis5ane +ei4s5ant +eis5anz +eisch5er6k +ei6schertr +ei6scher6z +ei6schin +eisch5war +ei6s5elas +ei6s5en6te. +eise5ra +ei4s5erf +ei4s5erg +ei4s5erh +ei4s5er4l +ei4serm +ei6s5er6sa +eis5ersp +ei6s5erst +ei4s5erw +ei4s5erz +eises4s +ei4sex +eis3id +ei3s4ky +ei4soe +ei4som +ei2sö +ei3spe +ei3spr +eis6s5chen +ei5stei +ei5stel +eisterin6f +eis4tol +eis4tri +ei5stue +ei4s3um +e2it +2eita +ei4t3ab +eit5aer +eit5alb +eit5alk +ei4t3an +ei4tap +ei4t3ar +ei4tas +ei4tat +2eitä +ei4t3är +4eite. +eit3e4h +ei4t5emi +4eiten +eitende6m +ei5t6er. +eit6err +ei4tess +ei4t5eti +ei2t3h +4eiti +ei4t3in +eito4r +ei4trau +eit5rin +4eits +eit4s5a4g +eit4sal +eitt4 +eit5tr +ei4t3um +ei4t3ur +eit3z2 +2eiv +ei4zar +ei4z5eig +ei4z5inf +ei4z5ins +ei4z5int +e2jak +2e3ju +2eka +ekaden6z5 +eka4l5oe +ek4ar +ek2ä +1ekd +e1ke +eke2n +e1ki +e1k2l +ek2n +eko4man +ek4or +ekor4da +2ek2r +ek2sa +ek4s5te +2ekt +ek4t3ae +ekt5akt +ek4tan +ekt5ank +ek5tante +ek4t3at +ek4tau +ek2t3ä +ek4t3eb +ekt5eig +ek4t3e4l +ekt5ende +ek4tep +ekt5erf +ekt5erg +ekt5erk +ek4t5erl +ekt5ero +ek4t5err +ek4t5erz +ek2th +ek4t5ins +ekt5int +ek4t3ob +ekt5ret +2ek2u +ekue3b +ek2ü +ekü3b +e3k2w +1ekz +e1la +e2l3aa +el3abb +ela4ben +e3labi +e4labk +el3abl +ela4br +el3abu +e4lada +el3add +el3aef +e2l3af +e4laho +e2l3ak +e3lake +ela2l +e4l3ama +el3amb +e4l3ame +el3amm +e4l3ana +e4lanb +e4landa +e4lanf +e4lanh +ela3n4i +e4l3ank +e4lanm +el3ann +e4lanp +el3ans +el3anz +e2l3ap +e2l3ar +ela4ri +el3a4si +el3a4sp +el3atl +e4l3atm +el3atr +e4l3att +el3au. +el5aufg +el5aufl +el3auk +e4l5ausb +el5ausl +el5ausr +e1lä +e3läd +el3äf +3el4bis +elb4lei +elb2r +elb3sl +elb6star +elb4ste +elb6stim +elb4st5o +elb4stu +el1c +elda4r +el4d5ari +el5d4eic +el4d5er4f +eld5erhe +eld5erho +eld5erhö +el6d5er6pr +el6d5er6sa +eld5ersp +eld5erst +eld5ertr +el6d5er6we +el4do4b +elds2 +eld3st +2ele. +4elea +elea4r +e6l5e6bene +el5echt +e3lecl +el3edi +ele4g5er4 +2eleh +el5ehe. +4elei +e6l5ei6ern +el3ei4f +el3ei4l +e4l5einb +e4l5einf +e4l5eing +e4l5einh +e4l5eink +e4l5einl +el5einn +e4l5einr +e4l5eins +el5einto +e4l5einz +el5eisf +el5eisz +el3eiw +e5lekti +5elektr +e2lel +el3elb +el3ele +el3eli +el3ell +el3els +el3elt +e2lem +e3lem. +e3lema +el3emb +3eleme +el3e4mi +e3lemm +el3emp +4elen. +elena4 +elen6klo +e4l5ense +e4l5entf +e4l5ents +e4lentw +el5enzi +el3epe +4eler +e5ler. +eler4e +e6l5er6fah +e6lerfin +e6l5erfol +e4l5erga +e4l5ergä +e6lergeb +elergene6 +eler5h +elerin6f +elerin6t +e4lerke +e4l5er4lä +e4l5ernä +e4l5eroe +e4l3erö +el5erre +el5ersa +el3erw +e6lerwei +e6lerzae +e4lerzä +eles4p +el3ess +ele5ste +el5e4tag +ele4tik +elet4ta +el3eur +e5leut +ele2x +e3lex. +el3ex3e +el3exk +el3ext +elf2a +3elfd +elf2l +elf3st +elgi5er. +elgi5ers +el3g2l +elg2r +e2lid +e3lie +eli5ef. +eli5eff +eli5efs +elif3 +e4l3i4ma +e4l3imb +el3imp +e4linfe +el3inj +eli5no +el5inso +e4l3inv +el3i4on +e4l3iso +eli4t3h +elk4a +el4k5anl +elke4n +elk2r +elk5sta +ell5akt +el4larb +ellar4c +el4l5arm +ellar6ma +el4l5art +ell5aufb +el6l5auft +4ellä +el3le. +ell5einf +ell5eing +ell5einh +ell5einr +ell5eins +ell5einz +el4l3el +el3len +ellens4 +ell4er +eller7ge +eller6tra +eller6zeu +elle3s +el4lid +el4l5inv +3ellip +el4l3or +ell3s4k +elm4stu +2elo +e2l3oa +e4loel +eloe6se. +e4lonk +el3opf +el3orb +el3orc +el3o4ri +e4l3orn +el3ort +e4l3osz +e3lot +e2lov +elö4se. +el4sb +els5eins +elsgene6 +el4sob +el4strü +el4sun +el4tans +elt5art +el4t3e4k +elt5eli +elt5entr +elt5er6be +elt5erd +elt5er4f +elt5erhe +elt5erk +elter6ke +elt5er6le +elt5erlo +elter4n +elt5ero +elt5er6sc +elt5er6tr +elter6zeu +el3thü +el4t3i4b +elt3se +elt5s4po +elts4tr +e1lu +e2lum +el3umb +el3umm +e2l3u2r +el3use +elu2t +el3ute +e4l3uti +el3uto +e1lü +e2lya +el4z5aep +el4z3ar +el4zäp +elz5eig +el4z5ene +el4zoe +e1m +e4mago +e4m5alar +e4m3alk +e4manb +e4manf +e4m3anm +e4manor +e4manp +e4manr +e4m3ans +e4manw +e4manz +e4m3asp +ema3to +em3aus +em2ä +3embol +3embry +em4dae +em2dä +em2dr +em4due +em4dum +em2dü +e2meb +e2mef +e2meg +eme4n3e +emen6gel +e4mengt +emen6t5ak +emen6tan +emen4t5h +e2meo +e2meö +e2mep +em3e4po +eme4r3a +emerin6t +emer4le +emes3t +e2m3ex +emi4e3i +emie3s +4emil +2emin +emin4a +e4m3inf +e4m3inh +e4m3inn +e4m3ins +e4m3int +e4m3inv +3e2mir +emi6scha +e5mitg +emi3tr +em4m3ap +emma3u +em4m3ei +e4moel +emo3ra +emo3s +5e4motio +3empfa +3emp3fä +em3pfl +3emphy +empi4re +em4p3le +emp5lin +empo3s +em4san +em4s3au +ems5ele +em4spa +ems4por +em4stal +em4s5tau +em3stu +3emuls +em3una +em3urs +e3mus +en1a +e3na. +e2naa +en3ab3i +ena2c +en5ach. +e3naep +ena5er. +e4naho +en3a4ke +e3nal. +e3nale +ena3li +4ename +e4namp +en3ams +ena2n +e4nant +ena4pa +en3ark +ena4sc +e4natl +en5atom +enat4s3 +e4natt +e4nau4f3 +e6nausta +enauto6re +ena2x +ena4zi +en1ä +e2näl +e2när +4enärm +4enbom +en4ce. +en3dac +enda4d +end3ak +en4d3al +en4d5ang +en4dark +end5eins +ende5int +ende4l5ä +end6er. +endes4p +ende5sta +ende5sti +ende5str +en4d3ex +en4do4b +3endog +en5d4ort +en4dox +en4dral +end5rom +end5s4au +end3s4l +ends4p +end2ü +2ene. +e5nece +ene4ck +e4n3eff +ene2h +en3ehe +e5nehm +e5neien +4eneigu +en3elb +en3elc +en3e4le +en3elf +e4n3eli +e4n3elt +4enem. +en3e4mi +4enen. +enens4 +e4n3epe +en3epi +en3epo +e2n3e2q +4e5ner. +ene3ra +e4n3erd +ener5ei +ener6ei. +en5ereig +e4n5erek +en3erf +ener4fi +e4nerfo +en4erfr +e4nerg +en5erge +5energi +en5ergu +e4n3erh +e4n5erla +e4n5ermi +e4n5ermo +en5ermu +4enern. +e4n5erna +ener5ob +ener5oef +ene5roes +ene5rös +e4n3err +4eners. +e4n5ersa +e4n5ersp +en3erw +e4nerwi +e4n3erz +4enes. +en3ess +e4n3eth +en3eul +4eneut +e4n3e4va +e4ne2x +enf2a +enf5einh +en5fes +3engad +3engag +5engelh +engel6st +enge6ne. +enge6nen +enge6net +engerin6s +5englis +en3gna +en3gno +eng5oel +3engp +eng3s4c +eng5stä +enhof5s +2enic +eni2d +en3ide +e3nie +eni5er. +eni5era +eni5erp +eni5ers. +eni5erv +en3i4ko +en3ill +eni2m +en3ima +en3imi +enin3a +eni2o +e4n3ion +2enis +eni4sa +eni5sta +e3niv +enizi5e +en4k5aus +en6kenla +en4k5er4f +en6k5erha +en6k5erkr +enker4l +en6k5ersa +en4k5ort +enk5spa +enk5spo +enk4su +enk2ü +4enlic +en4nef +en6n5er6fa +3enniu +enn5sa +e2n3oa +en3o4be +e4nober +en3obo +e4n3o4br +e4n3oe4d +e4n3oel +e2n3of +e2n3oh +en3oli +en3olm +en3oly +eno2n3 +en3opf +e4n3opi +eno2r +en5oran +en3orc +e4n3ord +e4n3ori +en3ork +e4n3ost +en3osz +e5noti +eno2w +e1nö +en3ö2d +enre4gu +en5rei +ensa5es +ens5amte +en4sanf +en4sang +en3sat +ensau4g +ensau4s +ensche6f +en6schlic +en4s5ehr +5ensemb +en4s5en5e +en4s5enz +ens5erei +ense4t +en4s5eta +en5s4law +en4s5off +en4s5ont +en5spen +ens4pu +4enst. +enst5alt +en6starb +4enste. +4ensten +ens7teru +4enstes +4enstl +4enstm +4enstn +en4stob +4ensts +4enstv +en4sumf +ens5umv +ens5umw +4ensym +en4tanm +ent5anst +entar6tik +en4tela +en4telä +en4tema +ent5emo +en6t5en6de. +enten6den. +en6t5en6des. +ente4n5e +5entera +ent5erben +en4t5erf +5entero +en6terre +ent5er6sc +ente4t +ent5eta +3entfl +4entfo +3entgi +5entheb +en4t3id +3entla +5entloh +3entlu +3entlü +5entnah +en4t3ob +ent5oel +en5tré +5entrieg +5entropi +5entsag +3entso +3en4tw +6entwert. +6entwerte +6entwerts +4entwet +3entz +4entzuw +en1u +e3nu. +e3nueg +enu5ett +enu4ma +enu2r +2enu2t +e4n3uto +e3nüf +e3nüg +e3nül +e3nüp +enü3st +e1ny +enz2ä +enze4ne +enz5erfo +en4z5erh +en4z5erk +enzer6ku +enz5erla +enz5ermi +en4z5ero +enz5erte +enz5erwe +en4z5erz +en4zf +en4z3i4d +enzo4l +4enzuf +4enzyl +3enzym +4enzys +2eo +e1ob1 +eo4ber +e2obo +e2ob2r +eo1c +e3ochs +eod2 +e1oe2 +eo3edi +eo3em +eo3en +eo3ers +e3off +e3okk +eo3la +e3o2ly +e1on +e3o4n3ae +e3o4n3an +e5onatom +e3o2nä +e2ono +eo1o +e3o2pe +e3opf +e3ops +e1or +eo3rau +e3orb +e3ord +eor5eis +e3org +eort2 +eo1s2 +e3os. +eo3s4e +e3oste +eoti3e +eo3ul. +eo3uls +eou2t +e1ö2 +ep2a +epa2g +epan6zerf +epas6s5er +ep3ega +ep3ent +ep3ex +2epf +eph3el +eph3em +e1pi +3e4pid +3e2pig +3e2pik +3e4pile +epin3g4 +e3pio +3episo +3epit +ep2n +e2poc +epoli3 +3e4pos. +ep2p3a +ep4pei +ep2p3f +ep2p3l +ep2pr +2e1pr +ep2s3c +ep3sh +ep4t3ae +ep4tal +ep4tau +ep2tä +epter4s +ep6t5erst +e3pu +epu2s +e1pü +2eque +3equip +er3ab. +4e3raba +e4r3abb +era4ben +er3abr +er3abt +er3ach +e3rad. +er3add +e5radi +e4radj +er3adr +erae4b +era5ebe +e3raed +er3aef +er3aeh +er3ae4m +er3aer +era5er. +er3aes +er5aeuß +era5fra +era4gen +er3a4gi +e3rago +er3ahl +era4ho +er3aic +e3rake +e3rakl +era4kro +e4r3akt +e3ral. +e3rald +e3rale +eral4eb +era6lent +e3rali +eralin6d +era6ling +eralin6sp +er5alke +er3amb +er3amn +e4ramp +e4r3a4na +erana5t +e5rand. +e6rander +er5andr +e4ranf +er3ang +e4ranga +e4rangr +e4ranh +er3ank +e4ranl +e4r3a4no +e4r3ans +e6rantei +e3ranu +e4ranw +er5anzu +er5anzü +e3rao +e3rap +er3apf +er3apo +era4rie +e4rarr +e4rars +e4rart +e3ras. +er3as4b +er3ask +er5ass. +er5assi +er3a2ß +e3rat +e4ratel +e4r3atl +e4r3atm +er3au +e3raub +e3rauc +erau4f3 +e4raufs +e3raum +e3raup +erau4se +e4rauss +e4raust +er3a4va +e3raw +e4r3ax +e1rä +er3äb +e3räd +er3äf +er3äh +er3ä2m +er3äp +e2r3äs +eräte3 +er3ätz +erbe5ers +5erbeti +3erbig +erbin4f +erb4lo +er5brec +5erbreg +er1c +er5ch4e +erche5i +er3chl +5erdaep +erd5ant +erd5anz +3erdäp +3erdb +erdens4 +erdes4t +3erdg +er4d5inn +er4dob +erd4os +3erdöl +erd3st +2e3re. +er5e4ben +er2e2c +er5echs +ere4del +er5e4dit +ere3en +er3e4he +e3rehi +e3reho +er3ehr +4e3rei. +4e3reia +4e3reib +erei6be. +erei4de +e3reie +e5reiga +e5reigeh +5ereigni +4ereih +4ereik +4ereil +er3ein +e4r5einh +e4reinl +e4reins +e4reint +er5eisar +e4r5eisb +er5eisd +er5eisf +er5eisr +erei5str +er5eisv +er5eisw +4ereit +erei6t5erk +4ereiu +e3reiv +4ereiz +er3ele +er5e4lit +4e3rem. +er3emb +er5e4mig +ere4mis +er3emp +e4r3ems +e4r3emu +4e3ren. +e3rena +er5endg +erengene6 +eren5sk +e4r5entn +e3renz +eren6z5en6d +eren6z5er +er3epi +e4repos +4e3rer. +4ererb +e4r5erde +erergene6 +e4rergo +e4r5erho +erer4hö +e4r3erk +e4r3erl +erer4lo +erer4lö +4erern. +e4r5erna +e4r5erne +e4r3ero +4er6ers. +er5ersat +e6r5ersche +e4r5ersp +e4r3ert +e4r3eru +4ererv +e4r5erwa +e4r3erz +2eres +e3res. +e4r3ess4 +er5essen +ere5star +ere4tab +e4r3eth +eretri5 +e4retu +er3eul +er3e4va +e4r5e4vid +erf2a +5erfahru +erf4e +erfen4s +5erfolg. +5erfolgr +erf4ta +erg5ant +er4g5are +5ergebn +er4g5ein +er4g5elf +6ergenau +5ergenb +6ergeni +5ergenp +3ergom +er4g3op +erg3s4o +erg3s4p +erg5sta +erg5sti +erg5sto +erg5str +erg5stu +5erhaltb +er3ho +e1ri +e4r3iat +e3rib +2eric +erich6t5er6s +e4r3ico +4erie +erie3b4 +erief4 +erie5fl +erie4ne +erien5en +eri5er. +eri5ers. +e3rif +er3ima +er3imb +er3imm +er3imp +4erin. +e4r3ind +e5rind. +e3ring +er3inh +e4r3i4ni +e4r3in5k +er3inl +6erinnen +5erinneru +5erinnr +er3ins +e5rins. +e4r3int +e4r3inz +e3rio +e4r3ion +e3ris +4erisc +er3isl +er3iso +e4r3isr +3eri3tr +4erkaj +er5kat +erk5ends +5erkranku +erk4sen +erk4ser +5erkundu +5erläut +5erlebn +5erledi +er5leg +4erlöh +5erlös. +erls2 +er3ma +er4m5alg +er5man +erme5sz +er3mo +erm2u +ernab6sa +erna5che +er4n5alg +er4n5all +er4n5alt +ern5erbe +ern5erei +ern5erst +4ernet +erne4te +er4n3ob +erno4r +er4n5ori +4ero. +e3roa +e3roba +e3robb +e3robi +er3o4bl +er3obm +e3robo +er3o4br +er3obs +er3oe4d +e3roeh +e3roen +er3oer +e5roes. +eroe4s5c +er3off +e3rofr +e3rog +e3rohl +er3ohm +e3roi +e3rol +e4r3oli +e4r3oly +e3rom +ero3ma +er3omb +e3ron +ero3n4a +ero4nan +er5opal +er3o4pe +er3opf +e3ropo +er3o2r +eror4a +eror4e +e3roro +4e3ros. +e3rosa +e3rose +5erosio +e5rosit +ero5sto +e3rotr +er5otter +e3row +er3ox +e3rozy +e3röc +er3ö2d +er4öh +e3rös. +erö4s3c +erp2 +er5pen +5erprobt +5erprobu +er3rae +er4rant +er3rä +5erregt +erri5er +5erringu +5ersatzo +5erscheinu +ersch4l +ersch4m +ersch4r +ers2e +er3si +er4sos +5ersparn +er7stati +5erstatt +er7statu +erst3h +er3sto +er4t3a4d +er6t5akts +er4tax +erte4c +ert5eck +er4t5er4f +er4t5er4h +er4t5er4k +er7tersc +ert5erst +er4t3ho +ert5spr +2e1ru +eruch4s5 +e4rueb +eruf4s3 +e4r3uhr +er3u2m +erum3a +erums4 +er3und +e4rundu +er3unf +er5unga +er5ungl +erungs5 +e4r3unk +er3up. +3e4rupt +eru2r +er3u4se +er3uto +er3uz +e1rü +ervenen6 +5erwerb. +e2rya +er4z5erk +erz3te +erz4wi +es3abl +e2s3a2d +e4s3aem +e4s5aerz +e4s3all +e4s3alt +e4s3ami +esam4t5r +es5andr +es4apa +es3art +e4s3ato +e2s3a2v +e2s3ax +es3b +esbi5er. +e3scen +e4s5cham +esche6mi +e6schent +e6sch5er6f +e6sch5er6l +esch5op +e2sco +es2cr +es3da +ese2c +ese3er +es3ehr +ese3i4m +e3s4ek +3eselc +ese4len +ese4ler +esen4ac +ese4neu +esen5th +ese5ref +e5seri +eserin6t +es5erke +es3erö +esers4 +eser3u4 +e5serv +ese5str +eseu2 +ese2x2 +es3ext +es3f +e3sham +es3han +es4har +es3he +eshi2 +es3him +es3hor +es4hut +2esi +esi5ers. +e2s3i2k +e4simp +e4s3inf +es3ini +es3int +e2sk +es3ke +es3kl +e4sky +es3lo +es3m +e2s3o2b +e2s3od +eso4de +es3ofe +eso3pa +e4s3orc +e4s3ori +es3ost +e1sö +2e1sp +es4pec +es4poe +es6portp +es6portv +es5rei +4ess. +es4s3ad +ess5alt +essau4s +3essay +4essä +4essc +es3sch +esse5er +esse3g +esse6l5ers +es3ser +es6serfah +ess5er6ho +esser6hö +esser6sae +esser6sat +es6ser6sä +4essk +4esso +es4sof +es4sph +es4spu +es4stec +es4stop +essur4k +e4st. +estab4b +e4stabg +e5stael +e3stah +est5anst +es5tant. +est5anza +esta4s +est5asc +e4s5tat. +e5statu +est5auf +e4staum +estauto6r +es4tec +e5stein. +e5steine +e6st5eing +est5eink +est5einl +est5einr +est5einsc +est5eint +estell5a +es4t5emi +est5empo +e4s3ten +esten6ga +est5engl +es6ten6se +est5entr +est5ents +ester6ken +est5ernä +ester6n5o +est5eroe +es4t5er4ö +est5er6tr +ester6weit +es4t5es4s +es4thy +es4t3id +es5tij +e3stil +est5ing +es4tins +e4s5tipp +estmo6de. +es4tob +e3s4toc +e3stol +es4top +es4t5org +es4t5ort +es2tr +es5trac +es5trag +e5strang +est5rate. +e5streb +e5streck +e5strick +e5strik +es5tros +e3strö +e4strun +e3s4tub +e3s4tue +e4stueb +est5ums +es4tu4r +est5uri +est5urk +e4stüb +es4tüm +e3sub +e3suh +e4sumg +e4s3ums +e4s3umw +es4und +es3unt +e3sup +es3urb +es3url +es3urs +es3urt +esu2s +es3w +e3sy +e2sz +es3ze +eß3ele +eße3re +e4ß3er4l +et5a4ben +et2a3c +eta3e4b +et3aeh +et3agi +etal4l5o +et3a4mi +eta3na +e4tanh +e5tanj +et3ano +e4tanp +e4t3arb +et3art +eta4t3r +et5aufs +et3auk +et3äh +2ete +et3eff +et5e4he. +e4t3eim +et5eins +e4t3eis +etel4n3 +etend4 +eteng4 +ete4n3i +e4t5ents +e4tentw +eten6z5er +e4t3e4pi +ete4r3ä +ete6r5eis +etergene6 +e4t5erhö +eterich6 +eter6loe +e4t5er4lö +eter5oe +et3erö +e5t6erta +e3tes +eth4a +3e4thik +3ethn +3ethol +et3hon +e2t3i2d +eti4kar +eti4kl +eti4kr +eti2m +eti3na +et3ind +e4t3inf +etin4g +e4t3inh +et3ini +eti4ta +eti4th +et3obs +et3o2f +eto4n5al +eto4nar +eto4ner +etons4 +e4t3orc +e4t3ord +e4t3org +et5rade +etra5e4d +et3rec +et2ro +et2sa +et3sac +ets3ae +etscher5e +etsch5wu +ets5eng +et4sim +et3sö +ets3pr +ets4toe +ets4tu +et2su +et4t3a4b +et4tad +et4tae +et4t3ak +et4t3a4m +et4t5ang +et4tanm +et4tans +et4tap +et4t3at +ett5auf +et2tä +et4temb +ette4n +et4tesp +et4t5ess +etto5en +ett5ope +et2tö +et2tr +ett5rei +ett3ru +et4t3um +2etu +3e2tui +e4t3ums +et3ups +et3use +ety2l3 +3e2tym +2etz +et2za +et2zä +et4z3eb +et4zed +et4z5eig +et4z5ene +et4z5erw +etzes3 +etze4t +et4z5eti +et4zoe +et2zw +eu1a2 +eu4b3le +eu4b3li +euch4ta +euchter6s +euch4to +euch4t5r +euder4e +eudi4e +eu1e +eue2d +eue3fa +eue2m +eue3p +eue4rei +euer5eif +euer5eig +euer5eis +euerer6r +euerer6s +euerer6t +eue4ret +eue4ro4 +euer5of +eu4esc +e3u4ess +eu3f2r +eu2ga +eug3an +eu6g5endm +eu6gense +eu4gid +eugin4f +eu4g5in4g +eu2gl +eu4gre +eugs2 +eug3sp +eug5sta +eug5sti +eug5stu +eu3hu +eui2 +eu3id +euil6le. +eu3in3 +eu3ir +3eukal +e2ul +e3um. +e4ume +eum3p +eum4s3p +eu3nas +eun2e +eune4b +eung2 +eu4ni. +eu4nio +eu4nis +eu1o2 +e3up. +eu3p2f +e3upg +3euph +eu4r3an +eurasi5e +eu4r5ast +eura3t +eur3c +e4u3re +eure5in4 +eur5eis +eurin4f +eur5in4s +e4uro +euro3s +eur4sau +eur4so +eur6s5tae +eur4stä +2eus +e3usar +eu6sch5ei +eu6schel +eu6sch5erz +eu4schl +eu4schm +eu6schue +e3usd +e5useff +eu3s4ei +e5usevan +e3usg +e3usk +e3usn +eu3spr +eus4s3c +e3usst +eu3sta +eu3sto +eu3s4t4r +eu3su +eu3tei +eute5in4 +eute5r4a +eut2h +3eu3tha +eut4o +euts4 +eut6scha +eut6schm +eut6schn +eut6schr +eut6schw +euve4 +2eux +eu1y +eu2za +eu4zec +eu2zo +eu2zw +e1ü +2ev +3e2vak +eval4s3 +evan4z3 +ev4en +eve3r4i +e2vl +ewal6t5er +e2w3au +ewebe5i +eweb4s +ewei4sc +ewer4g +ewer5te +ewi2e +e3win +ewinde5s +e3wir +e3wit +ew4st +ewuerzes6 +ewürzes6 +2ex. +3exam +2exan +ex4ans +3exek +e2x2el +ex3ele +2exes +2exf +e1xi +3e2xil +e2x3in +ex3l +ex3or +1exp +2expu +2exs +2exta +4extet +ex4tin +ext5rei +2extv +2exu +e2xum +ex3v +2e1xy +ey1 +eya2 +ey2dr +ey4ere +ey2ne +eyo2 +eys2 +2ez +e3za +e4z3enn +eze3re +ez3erk +eze3s4a +ez2o +e3zoh +ezu2e +ez2w +é1 +é2es +égi2 +élu2 +ém2 +ér2 +éu2 +è1 +ène1 +2ët +ê1 +ê2t +1fa +fab2 +2f3abb +fa4ben +2fabf +2fabg +2f3a2b3l +2fabn +f3a4bre +2fabs +2f3abt +2fabw +2fabz +fa2ce +fach3a +fa4cheb +fa4ch5ec +fa4chel +fa6cheng +fa6chent +fa6ch5en6z +fa6ch5erf +fa6cherk +fach3i +fach3o +fach5sk +fachs4p +fa5chung +fad3a +fa4dan +fa4del +f3ader +fa2di +f3adl +2f3a2dr +fad3t +fa2e +fae2c +fa3eck +f3aem +fa4er +f3aerm +4f3aerz +faesser6 +faes6s5ers +faes6serw +fae4ßer +4f3aetz +f3aeug +2f3aex +fah6l5ent +fahler6z +faib2 +fa4kad +fa2ke +fa3la +fal4k3l +fal4l3a +fal4l5ei +faller4 +fal6l5ere +fal6lerf +fal6lerg +fal6l5erk +fal6l5erm +fal6l5err +fal4lo +fal6scha +fal6schei +fal6sch5l +fal6schm +fal6sch5w +fal4t3r +fal4zw +fa4mei +2f3amp +f3amt +3famy +f4an. +fa4nar +fan4d5er +fan4dre +fan4d5ru +fan4drü +fan4gei +fan6gert +fan4gra +fani2 +fa4n3in +2f3ank2 +2fanl +2fann +f3a2no +2fanr +2fanw +2f3anz +f1ap +fa2po +fa3r4a +far4b3a +far4b3ä +far4bel +far4b5er +far4b5in +far4b3l +far4b3o +far4b3r +far4b3u +far4m5an +4f3armi +2f3a2ro +far4r3a +far4rh +2f3art +2f3arz +fa3s4a +faser5e +fa3sh +4f3assi +4f3astr +fa2ß +f1at +fa2to +2fatt +2faue +2fauf +f3aug +f4aul +f3ausb +f3ausl +3fav +1fä +fä1c +2fäf +fäh4r3u +2f1äm +2fäq +2f3ärm +2f3ärz +fässer4 +fäs6serk +fäs6s5ers +fäs6serw +fä4ßer +2f3ätz +2fäug +2fäx +2fb2 +fbau3 +fbilder6 +fbil6derh +2f1c +f3ch2 +2fd2 +fdi2e +f3ds +1fe +featu4 +2f3ebb +fe4ben +fe2c +fecht6e +fechts5e +2f3eck +fe4del +federen5 +fede4ro +fedi4e +fe4dit +fe2dr +feein5 +2feff +fef4fr +fefi2 +fef2l +2f3e2he +fehl3a +fehle4 +feh4l5ei +feh6lerinn +feh4l3o +f3eib +2f3eic +fei4l3a +fein6d5ei +fei6n5erz +4f3einl +4f3einn +4f3einq +fe5ins. +fe5inta +fe5into +fei4nu +4f3einw +4f3einz +f3eis +4feise +3fek +fek4ta +fek4t3r +3fel +fe2la2 +fel3ae +fel3at +fel3au +fe2l3ä +f4elb +fel5bi +felda4 +fel4dac +fel4d5ar +fel4d5e4m +fel6d5end +fel6d5er6k +fel4dra +fel3e4c +4f3e4lef +fe4l3eg +fel5ein +4f3e4lek +fe4l3en +fe4l3er +fe4les +feli4n3 +fel5ins +fell5ans +fel6lein +fel6l5ers +fel4lin +fe2l3o +fel6spir +fel5sti +6f5el6tern +2f3emb +fem2m +2f3emp +f4en. +fe4na4g +fe2nä +fend2 +fende4f +fen5end +fe4n5ent +4f5energ +fen5er5o +fen5erö +fene4t +fen3f2 +4fengp +fe4nid +fen3in +fen3i4t +fenk2 +fenn2 +fe2n3o2 +fen4sal +fen7schl +fen3sh +fen5skl +fens4po +5fenst +4f5ente. +4f5en4ten +4f3entf +4fentla +4f3ents +4fentw +4fentz +fe2n3u +f4er. +fer3ab +fer3a4d +fer3ae +fer3al +fe5rant +fer5anz +fer3as +fe2r3ä +4ferd. +ferde3 +4ferde. +4ferden +ferdes4 +4ferds +fe4r3e4b +fer5eck +3ferei +fe4rein +fe4r5eis +fer5eiw +fe4rek +fer5ell +fe4r5erd +fe4r5er4g +fe4r5erw +fe4r3e4v +fer6fahru +fer6fuel +4f5er4fül +6fergebn +fe4r3i4d +ferien5 +ferin4f +ferin4t +feri4on +fe4riso +fer6lebn +fernab6s +fern6au. +ferner6k +fer3oe +fer3of +fer3ö +ferr2 +fer6regt +fer3s4m +fers4p +4f3e4rup +fest5a4b +fest5ak +fest5al +fest5an +fest5ar +fest5ei +fes6t5end +fes6tero +fes4th +fes4tin +fe4s4t3o4 +fest5ru +fe4tag +fet5ero +4f3e4tik +fet4t3a +fet4t3h +fetti5s +fett3r +fett3s +2feu. +4f5eu5cha +feuer5o +2f3eul +2f3eut +f1ex +2fexp +1fé +2f1f +ffa5chu +ff3a2d +ffang5s +ff3ans +ff3ark +ff3arm +ffa2t +ff3au +f2f3e2b +ffe2e3 +f2f3ef +f4f3eig +ffei4n +ffe5in. +ff5einb +ffe5ini +ff5eink +ff5eins +ffel3a +f4felem +ffels4 +ffe2m +f4f3emi +ffe4rec +f4f3erh +f4f5erze +ffe2t +ffet3r +f2fex +fff2 +fffen4 +f2f3im +f2fj +ff3län +ff3lät +ff3lec +f2f3o2b +ffo2r +ff3ori +ff3ott +f2f3ox +ff3rak +ff4ren +ff3roa +ff3rol +ffsa4 +ffs3al +ff3sam +ffs3au +ffs5end +ff4stau +ffs5tie +ffs5tut +ffs5tür +ff3ti +f2f3um +ff3urs +f2fy +2fg2 +fga4s5er +fgas5te +fgeb2 +fge3na +fger4 +f3gh +fglim4 +2f1h +fh4au +fho4l3a +1fi +3fi. +fi2ar +fi3ask +fi3at +fici3 +fi2do +fid2s +2fi2dy +fie3b4l +fie4br +fi3er4f +fi4gec +fi2g3r +fi4kanf +fi4kanr +fi4k5ans +fi4kanw +fi4k3as +fi4k3el +fiker4s +fi6k5erst +fi4kin +fi4kle +fi4klo +fi4klö +fi2kn +fiko2 +fi4k3ob +fi4k3op +fi4kor +fi2k3r +fi4l3an +fi4lau +fi5laufb +filauf6n +filb2 +fi4l3ei +filen4 +fi4l5end +fi4lent +fi4lep +fi4les +fi4l3in +fil4ip +fil4ma +film5as +fil4mä +fil4m5ed +fil4mei +filo2 +fi4lob +fil3st +fi2mi +2f3imp +f4i3na +fi4nant +fi4nar +fin4dia +fi4nel +fi4net +finge6ro +fingst5r +2f3inh +2f3inj +4f3inse +fin4sp +fin4ste +fin5sti +fi3ol +2f3i2ri +fi4sch5a +fi4schä +fi6sch5ei +fi4schl +fi4schn +fi4sch5o +fi4schr +fi4sch5w +fi3s2h +2f3i2so +fi3s2p +fis3t +fiste4 +fite2 +fit3el +fi3ti +fi4tin +fiti3s +fito2 +fi4t3or +fi4t5rat +fi4xel +3fiz +fi2za +f2jor +2f1k2 +fkaken4 +fka4t3i +2fl. +fl2a +f3lab +3f4laec +f3laed +4flaen +f3laeu +f3lamp +flan6sch5l +flanzer6 +f3lap +f4lasc +f3lats +f3lauf +flauma4 +3f4läc +2f3läd +2flän +f3läu +fl2e +2f3le. +2f3leb +f2lee +4f3lein +flei6sch5r +4f3leit +flek5t +2f3ler +2f3li. +f5lich. +f5liche +2f3lig +2f3lil +f2lim +f3lind +fli4ne +4f3ling +4f3lini +4f3lins +f2lit +f3loef +f3lok +2f3lon +3f2lop +3f2lor +flo3ra +5floss. +3floß +3f2lot +flo2w3 +2f3löf +f2lög +f2luc +f4lueg +f3luem +f3luf +f4lug +flu4g3a +flu4ger +3flugz +f3lup +fl4ur +flur3i +3f4luss +flut3o +f2lüg +f3lüm +3flüs +f2ly1 +2f1m2 +fma2d +fmarschach6 +f3mu +2f1n2 +f3ne +1fo +foa2 +fodi2 +2f3oe2f +2f3oek +f3oele +fo3eli +foer4s5 +f3oe2s +2f3o2ly +fo4nad +fo4n3al +fo4nam +fo4n3an +fo4nap +fo4nar +fonat4 +fo4nau +foni4en +fo4n3in +fo2nu +f1op +2fo2pe +fo3rat +4fordn +ford3s +2f3org +3form +for4mae +for4m5a4g +for4mas +for4m5au +for4mä +for4m5ei +for4min +for4mor +forni5er. +for6schl +for4st +forst5ei6 +for6st5r +for4t5ei +for4t5er +for4t3r +fort3s4 +fo3r2u +fo3sta +fo3to3 +fo5toe +1fö +föde3 +2f1ö2f +2f1ök +2f1öl +för2s +förs5t +f1ö2s +2f1p2 +2fq +fr2 +f3ra. +frach6ta +frach6t5r +2f3rad +fra4dei +4f3raed +f3raet +f3raf +frage5i +2f3rah +2f3ral +fr4a2m +fram3a +f2ran +f3rand +f3rann +2f3rap +f3rase +fra3st +fras5ta +3fraß +f3rat. +f3rats +3frau. +f3raum +fraus4 +frau5se +2f3räd +f2rän +2f3rät +f3re. +f4rean +f5recht +fre4e +2f3ref +f3rege +f3regi +f3reh +f4rei. +freib4 +4f3reic +f4reie +frei3f4 +frei3k4 +frei5str +2f3rek +2f3rel +3f2rem +f3ren +2f3rep +3f4req +f4resc +4f3resi +fres4st +f3rest +f4rete +f2reu +2f3rez +f1rh +2f3ric +fri3d +2f3rig +2f3rin +frin6g5er +f3rip +3f4risc +fri6scha +f4rist +2f3roc +2frol +3f2ron +fro4n3a +fro2s +f3rose +frost5r +3f2ruc +f3rud +f5ruehr +f3rues +fru6fere +f3ruh +f3rum +2frun +f3rut +f3rühr +früh3t +f3rüs +2fs +f3sac +f2sad +fs3aer +f2sak +f3sakr +f4s3all +f4s3alt +fs3amp +f2san +fs3ana +fs3ane +fs3ang +fs3anz +f2sar +f2s3as +fs3a4te +f4s3auf +fsau4n +f4saut +f2sär +fs3äus +f2sca +f2sce +fsch2 +f5schal +f4schan +fscha4o +f5schau +f4schef +f5schra +f5schri +f2scr +f2s3e2b +fse2e +fsee3i +fs3ehr +fs3ein +f5seit +f2s3e2l +fs5endes +fs3e4n3e +f4s3ent +f3seq +f2s3er +fse3r4u +f5serv +fse4t +fs3eth +f2sex2 +f2si2d +fs5iden +fs3imp +fs3inf +fs3ing +fs3int +f2ska +f2sko +f3s2ky +fs1o +f2sob +fs3och +f3soh +f2sop +fso2r +f1sp +f5spann +f4spas +f4spel +f2sph +fsport5e +f4spot +fs5prie +fs4pul +fs3s2 +f3sta +fs5taeti +f4stag +f4s3tak +f4stale +f4s5tank +f4stas +f4s5täti +f3steg +f3stei +f4steil +f3stel +f5s4tern +fs3tes +f3steu +f3s2ti +f4s3tip +f4stit +f3s2to +f4ston +fs4tras +fs4tre +fs5tren +f3s4tri +f5strom +f3strö +f5s4tuec +f3stüc +f4s5tüte +fs3ums +f2s3un +f2s3ur +f2s3ut +f5szene +2ft +f2ta. +f4t3aeu +ft3ala +f5talg +ft3all +fta2n +f4t3ana +ft3and +ft3ang +f4t3ano +f4t3ans +ft3apf +f2t3a2r +ft3att +ft3auf +f2t3äu +fte2c +ft3eck +ft3e4he +ft3eig +ft3ein +f4t3eis +f4t5elek +f4t3eli +ft3emb +fter3a +f4t5erke +fter3o +ftersau6 +ftes4p +f4t3es4s +fte5stei +ft3eth +f4t3e4ti +fte3u +ftgu4t3 +f2t1h +f3t4hem +f2t3id +f3tik +f2t3im +f2tin +ft3ind +ft3inf +f3ting +ft3inh +ft3inn +ft3ins +ft3iso +ft3le +f2t3o2b +ft3oef +ft3of +f2tom +fto2p +ft3ope +ft3ora +ft3org +f2t3ot +ft5raed +f5t4ran +ft3rau +ft3ril +ftritt4 +ft3ruh +fts1 +ft2sa +ftsa4g +ftsau4 +ft4sän +ft3sch4 +ft4se4h +ftse4l +ft4sem +ft4send +ft4seng +ft4sep +ft4ses +ft4seu +ft2si +ft2so +ft4staf +ft3sti +fts5tick +ft6stier +ft4stor +ft6streu +ft6strop +ft4stur +ftu3en +ft3ums +ft3unb +ft3und +f4tunf +ft3urk +ft3url +ft3urt +ftwa4r +ftz2 +1fu +fuch4s5a +fuchs5t +f2ue +2f3ueb +fuen4f5a +fuen6fei +fue4r +f2uh +fuku3 +2ful +fuld4 +f3ult +2f3umb +fu4mei +fu4m5erz +fu2m3i +f2umm +2fums +fun6derg +fun6d5erh +fund3r +fund5st +2f3unf +4f3ungl +2f3u2ni +3funk +fun4ka +fun6kerk +fun4k3l +fun4k3o4 +fun4k3r +fun4ku +2f3unl +2funm +2funr +2f3unt +2funw +f2ur +fu4re. +2f3url +f2us +2fusc +fu4schw +fus4sa +fus4sei +fus4s5er4 +fus4sp +fu3s2t +fu4ß3er +3fut +1fü +2füb +fühl4s5c +fühl4s5e +fühl4st +fü2r +2fv +2fw +f1y +2fz2 +fzeiten6 +fzei6t5end +fz3t +fzu4gin +fzug4le +1g +3ga. +2g3aal +2g3abd +gabe3e +gabe3i +3gabel +gabe3r +2gabf +2gabg +gabi4t +gab5itu +2g3a2bl +gab5le +gab4ri +gab4ru +gab4rü +4gabsc +5gabst. +4gabstu +4gabstü +4g3abtr +2gabw +gaby2 +2gabz +ga1c +gade3s +g4a3di +2g3adl +gae5is. +ga3emp +4g3aend +2g3aep +ga3erh +g3aerz +gaes3 +gaeste5 +ga3eta +ga3fl +3gai +ga3il. +ga3in +ga1k +ga4kad +2g3akt +ga4kus +ga3la +gala3e +g3a4lar +4g3alau +ga4l5en4d +g3algo +4g3allt +4g3alta +4g3altd +g3a2lu +ga4m3al4 +4g3amaz +ga3me. +ga4meb +ga4mec +ga4mep +ga4m3in +gam5ma +gamo2 +gam3sc +gam3t +ga2mu +4g3anal +ga4nan +gan5ato +ga4nau +g4anbr +gan3d2 +gan5erh +gan5erk +gan5ers +2ganf +gan4gar +gan6g5e6be +gan4g5el +gan6g5er6b +gan4gr +gan4g3u +gan5inn +gank2 +4g3anke +g5ankun +2ganl +4ganmu +ga4n3ob +ga4nor +2ganp +2ganr +4g3ansi +g5ansti +2ganw +ga3ny +gan4zw +2g3app +2garc +garde3 +4g3area +ga4rena +g3arg +ga3rin +2g3ark +2g3arm +ga3r4o +gar2s +4g3arti +2g3arz +ga2s3a +ga2sä +gasche4 +ga3se. +ga4s3ei +ga4s3el +ga4s3e4m +gas5endr +ga4s5ene +ga4s5ent +ga4serz +gas3e4t +ga2si +ga2so +gass2 +6gassess +gas5tank +gas4tel +gast5elt +gas6tens +gas4tin +gas6t5rae +gas4t5rä +gas6trol +ga4sum +gat2h +ga3tha +2g3atm +gau4eri +2gau2f +ga3ufe +2g3aug +g4auk +ga3unt +4g3ausb +4gausd +4g3ausf +4gausg +4gausr +4g3auss +4g3ausw +g3ausz +2g3aut +g1äh +2g1äm +2g3änd +2g1äp +2g3ärz +gä4st +gäste3 +2g1ät +2gb2 +g3ba +gb4au +gbau3f +g3bä +gbes2 +gbi2e +g3bl +g3bo +g3bu +g3bü +gby2t +2g1c +2gd +g2dab +gd2ad +g2d3ak +gd3all +g2dan +gd3anz +gd3art +gdä1 +g2d3ei +gd3els +g4d3ent +g2d3er +gd3eth +gdi2e +gd3ins +g2do2b +g2dop +gd3roc +gd3roe +g2d3rö +gd3s2 +gea2d +gea2n +gea2p +ge3at +ge4at. +ge4ate +4g3ebbe +geb4le +geb4lin +gebot4 +ge1c +ged2 +gedi2 +4gedig +ge1e2 +ge3eg +geer3e +ge2es +geest3 +ge3ev +gef2 +ge5far +2g3eff +gegen5s4 +ge3g2l +ge2gn +geh5aben +ge4h5erl +geh5ste +ge3im +gei2n +4g3einb +gein4di +ge5in4fo +4g3eing +gein4ha +ge3ini +4g3einl +ge3in4n +4g3einr +gein6ten +gein6ter +ge3in4v +4g3einw +ge3ir +geise4 +gei3sh +ge3iso +geis4s5c +geist5r +2gek. +gela4c +gela4n +ge4l5ano +ge4l5ant +ge4lanz +g4elb +gel5bi +gelb5s +gel4del +gel4dem +gelder4 +gel6d5erh +gel6derp +gel6ders +gel6dert +gel6derw +gel6drue +gel4drü +ge3lec +ge4lef +4g3e4lek +ge4l5end +ge4l5en5e +ge4l5ent +gel5erd +gel5er5e +gel5erf +gel5erg +ge4l5erk +ge4l5erl +ge6l5erna +gel5er5o +ge4l5ers +gel5erz +ge4less +gel3f +gel4fe. +gelfen4 +gelgene6 +gel5int +gelk2 +gel3la +ge4l3om +ge5lot +gel4sal +gel6s5chor +gels4t +gel5ste +gel5sti +gel5sze +gel6tent +gel6terh +gelz2 +gem4a +ge4ma. +2g3emb +2g3emp +gem2u +gen4aes +ge5n4au. +ge5naue +ge5naust +gen6auste +4genda. +gend5in5 +ge4n3e4g +gen4eh +ge4n3el +ge4nemi +ge6n5ende +ge4nep +ge4n5er4b +ge6n5erei +ge4nerf +gen5er5in +ge4n5erk +ge4n5erl +ge4n5ern +gen5ersc +gen5erst +ge4n5ert +gener6wa +gener6wä +ge4n5esk +ge4ness +gen3f2 +geng2 +gen4ge. +gen6geln +gen6gels +genger4 +ge4nim +ge4n3in +genk2 +gen3n2 +ge4noc +gens5amt +gensch4 +gen6semb +gens5ert +gen5ska +g3entf +4gentfa +4gentfe +gen5t4he +4g5entst +4gentw +ge4nun +gen6z5art +gen6z5eig +ge3oh +ge3oo +geo4ri +ge3ou +ge3p2 +g4er. +ge3ra +ger3ab +gerabdru6 +ger5adm +ger5akr +ger5ala +ger5alb +ger5all +ger5alp +ger5alt +ger5amp +ge4r5ant +4gerdg +ger5ecke +ge4r5eid +ger5eig +6g5ereign +4g5e4remi +ger5ende +ge4r5en5e +ge4reng +ge4r5en4s +ge4r5ent +ge4rerf +ge4r5erh +gerer6ku +ger5ersa +ge4r5er4w +gerer4z +ger4hoe +4g3er4hö +gerin6de +gerin6di +gerin4f +4g5erklä +germi4 +ger4nau +ger3ne +4g3ernt +ger5obe +ge3roc +ge3rod +ge3roe +ge3ros +ge3rot +ger3o4v +ge3rö +ger6regu +4g5er4seh +gers4p +4g5er4wac +ger6wart +ger6weit +6g5erwerb +ge2ry +gerz2 +ge1s2 +g4es. +ges5anz +ges5auf +gesch4 +ge4s3eb +ge4s3ec +ge3sei +ge4s5ein +4g5e4sel. +ge4s5elt +ge3sen +ge4s5er5e +ge4s5erf +ge4s5erz +ge4sex +ge4s3ou +ge4s3ph +ge3spu +ges3sa +ges3se +ges3sp +ges3s4t +ges4tan +gest6ei +ge4s5ter +ges3th +ge4s5tur +get5ange +ge4tant +ge4thi +4getik +getin5 +ge3to +4getow +get2r +ge4t5res +get3sa +get3s4p +gets4t +get3t +4getui +2g3eul +ge3unk +ge3urt +ge3u2t +2g1e2x1 +2g1f2 +gfa5chu +g3fe +gfen2 +2gg +gga2t +g3ge +ggeb2 +gge4r5er5 +ggi3en +gg2lo +g2gn +g2gof +gg2r +ggs2 +2g1h +gh2a +gh4ae +ghafe4 +3ghale +gham3 +gh4äh +3g2het +gh1l +g3ho +3gh2r +ghs2 +ght1 +gh2ti +gh2tr +gh3w +gi1a +gib2l +gich2 +gicht3 +gi2e +gie3b +gie3d +gie3f4 +gie3g2 +gie3i +gi5enab +gie5nac +gie3n4e +gienein6 +gi3enk +gi3enr +gi3ens +gie6r5eis +gie5res +gi5ergi +gie5sp +gie3st +gif4t3r +gift3s4 +3gig +4g3i4gel +gi2gu +gik3a +gi4kan +gi4k3el +gi4me. +gi4mef +gi4mes +gi4met +4g3impf +gi3na +2g3ind +gin4di +2g3inf +gin4ga +6gingeni +gin4g3r +2g3inh +gi4nit +gin6nenb +4g5inner +gin4sa +4g3inse +4g3insf +4g3insp +gin6standh +gin4ste +2g3int +2g3inv +2giok +3gis +gi4sch5w +4g3isel +gi3sta +giste4 +3git +gi3tu +giu3s +2gj +2g1k2 +g3ka +g3kä +g3ko +gko3ef +gkraf4 +gkue3b +gkü3b +2gl. +gl2a +2g3lab +g3lach +4gladu +g5laend +glaes5c +2g3lag +2glah +4gland +glan4dr +glan4za +glan6zen +glan6z5er +2g3lap +g4las. +gla4sa +gla4s5el +4g3late +4g3lauc +g3laue +4g3lauf +4glaun +4glaus +g3laut +2g3lav +2glaw +4g3länd +g2läs +gläs3c +4gläuf +2glb +2gle. +g3leb +4glebi +g4lebo +g4lebö +gle3c +g3led +gleer4f +g2lef +2g3leg +g4leha +4glehr +4gleib +g4leic +4g3leid +4glein +g4leisi +glei6terk +glei6t5er6sc +glei4t5r +g2lek +g2lel +g2lem +g5lemmi +2glen +g2lep +2gler +glerei6g +g4les. +4glese +g2let +4gleuc +g4leur +g2lew +2glh +2gli. +3g2lia +2glib +2glic +3g2lid +3g2lie +4g3lieb +4g3lief +g3lieg +2glif +g2lik +2glil +g2lim +2glin +glings5 +g2lio +gli3on +gli3o4s +2g3lis +4glite +g4litt +g2liz +4g3lize +gl2o +g2loa +g2lob +g2loc +4g3loch +4gloes +4gloew +g2lof +4gloks +g2lom +g2lon +g2lop +4glorb +2glos +g2lot +2gloy +4glöch +2glöh +2glös +2glöw +2gls +g2luc +g3luch +g4luec +4gluef +2gluf +2glun +2g3lus +3g2lüc +2glüf +g3lüg +2glw +3g2ly +2g1m +gm4a3b2 +g3me +gm4en +gmen4tr +gmen4tu +gmi1 +gm4un +2gn. +2g3nac +g3naep +2gnag +2g3nah +gna2k +gna4l5er5 +gna4l5in +2g3nam +gn3ana +2gnaq +2gnar +2gnau +g3näp +2gnb +2gnc +2gnd +gn2e +g3neh +2gnei +2gnel +gne2n +gnen3e +4gn3ent +gner6geb +gne4ta +gne4t3r +2gneu3 +2gnf +2gng +2gnh +g2nie +g2nif +g2nin +4gninf +4gn3ing +4gnint +2g3ni2s3 +gnise4 +gniser5 +2gnk2 +2gnl +2gnm +g2no +2gnob +2gnop +gno3r +4gnori +2g3not +g3nov +2gnp +2gnq +2gnr +2gns2 +2gnt +3gnu. +3gnum. +gn4umf +4gnumg +4gn3ums +2g3nun +2gnut +2gnv +2gnw +g2ny +2gnz +go2al +goa3li +g1ob +2g3obj +g2ob4l +gob2s +go1c +2g3o2e2f +2g3oeh +4goel. +go3enz +g1of +go2fe +2gog +2g3ohr +go1i +go3k +gol2a +gol4d3a +gol6d5eng +gol4d3o +gol4d3r +gol4fa +gol4fel +gol6ferl +gol4f3l +gol4f3r +golf3s4 +g3o4liv +3gon +gon2a +go4n3an +gon5ato +2gope +go3pfl +go4pos +2gopt +go3rap +2g3ord +2g3org +g4oro +2g3ort +go2si +go3s2l +go3ste +2g3osz +go5thae +got6t5erb +got6t5erg +got6t5erl +got6t5erw +got4th +go1y +2g1ö2f +2g1öh +2g1öl +2g1p2 +gp4la +g3pu +2gq +g3qi +gr2 +gr4ab +gra4ba +gra4bi +gra4bl +gra4dei +gra6dend +4g3radl +4g3raed +graes5c +2g3rah +2g3rak +grammen6 +gram6m5end +gram6m5er +g3ramp +gra3na +g5rand. +4g3rann +2gr4ar +gra4ra +gra4r3e +gra4ru +gra4s3a +gras5st +g2rat +gr4au +4g3raub +grau5f +4g3raum +grau5sk +2gräd +gräs3c +4g3räub +2g3re. +2grea +g2reb +2g3rec +4grede +4gredu +g2ree +2g3ref +gre4fr +4grege +4gregi +4gregl +4greic +4g3reih +4greim +4grein +g2rem +g2ren +4g3renn +5grenz +gren4za +gren6z5ei +gren6zer6f +gren6zer6t +gren4z5o +g2rer +g2res +4g3rese +gres6ser6 +gres4st +2grev +2grez +2g3ric +gri2e +4g3rieg +4g3riem +g5riese +2grig +gril4l5a +4g3ring +4g3rinn +grit2 +g2ro +gro4b3a +gro4b5ei +gro4bl +gro4b3r +2groc +4g3roec +4g3roeh +groe5le +2g3roh +2g3rol +gron2 +4g3rose +gros4s5a +gros4s5c +gros6s5el +gros6senk +4grout +2g3röc +2gröh +grö3le +grue5be +4g3ruec +4gruf. +4grufe +g4ruft +2g3ruh +gr4un +grun6dere +grun4g +2g3rut +grü3be +2g3rüc +2gs1 +gsa2b +g3sack +g2sa2d +g4saeh +g2sa2k +g4sall +g4salt +g4sama +g4samt +g3sani +g4sant +g2s3a2p +g2sa2r +gs3ars +g2sas +g4sato +g3sau. +gsau4e +g4sauf +gsau4r +g4saut +g2sa2v +g2säc +g2säh +g2sän +g3sät +gsby2 +gsch4 +g5schal +g5schat +g3schä +g4sche4f +g5schei +gs4chen +g3schl +g3schm +g3schn +g3schr +g3schu +g3schü +g3schw +g2sco +g2se2b +gse2c +gse2d +g3sege +gs2eh +gs3ehr +g3seil +gse4kl +gse2l +g3sel. +gse4me +gse4n3e +g4sent +gsen5th +g2se2p +g2ser +gser3e +gser5in +gser5oe +g2ses +gse4t +g2seu3 +gs3e2v +g2se2x2 +gsgene6t +gsi2d +g3sie +g3sig +gsi2k +gsi2n +g4s3ita +g3s4ki3e +gs3li +g2so2 +gso3c +g3s2ol +g3soz +g3s4pac +gs4pate +g3spek +g3spit +g5sporn +g5s4pule +gsrue4c +gsrü4c +gs3s2 +gssau4g +gst2a +gs5tabl +g5stabs +g3stad +g5staer +g3stah +gs3tak +gs5tank +g5stark +g5start +gs3tas +gs5tat. +gs5tate +g5stati +g5statu +gs5tau. +gs5tauc +g3stäb +gs3tät +g5steig +g5stein. +g5steine +g5stell +gst5ent +gster5e +gs6t5erei +gs4terz +g3s4tew +gs5the +gs4thy +g3stic +g3stir +gs3tit +g5stock +gs4toer +g3stör +gs4tras +g5s4trat +g5streb +gst4ri +gs4t5rit +g5stroem +g5strom. +g5stroms +g5s4trop +g5ström +g3stüc +g2su2 +g3sub +gs3un +gs3ut +g5szene +2gt +gt3auf +g3te +g3ti +gti2m +gt3inh +g3to +gt2st +gt3stu +gtu3en +gtu5ers +gu3al. +gu3am +gu3ant +gu3ar. +gu3ark +gu3ars +gu2d3r +2gueb +2gued +2g1u2f +2g1uh +gu3in. +gu3ino +gu3ins +gu3is +2g3ulm +2g3umb +gum4e +2gumf +2gumg +2gumk +2guml +2gumr +2g3ums +2g3umw +2g3una +4g3unem +2g3unf +4gunge4w +4g3ungl +2g3u2ni +2g3unk +2gunr +gun4st +2gunt +2g3unz +gure2 +guren3 +2g3url +gur4t3h +gur4t3r +gurt3s +3gus1 +gu2sa +gu2sä +guschi5 +gus4s5er +gus4s3o +gus4sp +gus4st +gu4st +gust5a4b +gust5ein +gus6t5en6de +gus6terl +gus4t3h +gus3to +gus4t3r +gu2t3a2 +gutan5e +gu4ted +gut3ei +gu4t5er4h +gu2t3h +gu2t3i +gu2t3r +gut4sp +gu2tu +gutwa3 +2güb +gün2 +3gür1 +2gv +g3ve +2gw +g3wr +3gym +2gz2 +gzeu4gi +gzeugin6 +g3zh +ha2a +ha3ak +ha4b3ei +h3abla +ha4blä +h3a4blu +2habn +h3a2b3o +h3a2br +2habw +hach3a +ha4chec +ha4ch5en +hacher6be. +hacher6ben +hach5eri +ha3chi +hacks4 +ha4cku +2hada +ha4del +hade4n +haden6s5o +ha4d3in +h3adle +hado2 +h3a2dr +ha3dri +had3st +haede4 +h3aeff +h3aehr +ha3elb +ha3e4n3e +4ha3er. +h3aerg +4h3aerz +hae6s5chen +4h3aest +4h3aeug +hae5us. +haeu4s5c +hae5usp +h5affae +h3affä +haff4l +haf3r +haf4ta +haf6t5erl +haf4t3o +haf4t3r +haft4se +haft6stem +2hagi +ha2gl +h3ahl +h3ahm +2hail +2haka +ha4kad +ha4kes +ha2kr +h3akro +2hal. +hal3ab +ha3l4al +ha3lam +halan4c +hal5ang +h5alarm +ha3lat +hal4ba +hal6bein +hal4bel +hal6b5ern +hal4bin +hal4bl +hal4bo +hal4b5ra +hal4bri +hal4bro +hal4b5ru +hal4b5rü +halb5s +2hale +hale4n +ha6l5ents +2halh +2halk +h5alkal +hal4lan +hal4l5au +hal6l5ere +hal6lerf +hal6lerg +hal6l5err +haller6s +hal6l5ersc +hal6l5er6w +hal4loe +2halp +hal4sei +hal4sp +hal4sta +hal4stu +hal4tal +hal4t5an +hal4tau +hal6t5erfo +hal4th +hal4t3r +haltungscha6o +h3a4mei +h5ameri +hamot4 +h3ampf +2h3amt +h4an. +2hana +hana3e +ha4nal +ha4nan +2hanb +h4anbe +2hanc +han4d3a4 +h4ande +han4d5er +han4dob +han4dr +hand5ru +hand5rü +hand3s4 +2hane +ha4nem +han4f3i +han4f3l +han6g5end +han4gro +2hani +han6kerl +han4ko +han4kr +2h3anl +2hano +ha4nor +2hanr +4h3ansc +h5anten +h3anth +2hanz +h5anzei +h5anzug +h3apf +ha2pl +h3a2po +h3a2pr +har2a +hara3d +2h3arb +2harc +h4ard +har4da +h3area +har4fa +har4f5ei +har4fr +2harg +h3argu +ha3rh +ha3ri +h3arm. +h5armem +h5armen +h5armer +h3arms +har4ne +h3a4rom +4h3arra +2hars +har4tal +har4th +4h3arti +har4tri +har4za +har4zo +4harzt +2hasa +ha4schm +has2h3 +hasi5er +ha3spo +has4sa +hasser4 +has6s5erf +has4sp +hass3t +h1a2ß +ha2t3a +hat5ein +ha2t3h +h3atl +ha4t3ra +ha4trä +ha4t3re +ha4trou +2hats +h3attr +hat4zer +haub2 +h3audi +h3aufb +4h3aufd +h3aufk +h5aufla +hau5f6lie +hau5flu +4h3aufm +4haufn +h3aufs +4h3auft +4h3aufz +2haug2 +h5auge. +h3aukt +hau4men +hau4mer +hau4sa +hau4s3c +hau4seh +hau4sel +hau6s5en6t +hause4r +hau6seri +hau6serp +hau4so +haussen6 +haus6sen. +haus5sk +haus5tau +hau4s5ur +hau4ta +hauten4 +hau6teng +hau6terh +hau6terk +hau6tern +hau6terw +hau4tin +hau4tr +haut5s +ha4ve. +havil4 +hä2c +häde2 +h3äff +2h3ähr +2häi +hä2kl +hä2la +h3ärg +härm4s +hä3ro +2h3ärz +hä6s5chen +2h3äst +2häug +4hä3us. +häu4s3c +4häusg +4häusk +4hä3usp +2hb2 +hb4ar +hba4ras +hbar5sc +hber4e +2h1c +hchar4 +hcre2 +2hd2 +hdan2 +hdi2e +h3ds +he2ad +hebe5in +he2bl +3hebs +3hebt +he3cha +he5ch4e +h3echs +heck5sp +hed2g +he4dit +hee4la +he3e2m +hee2n +he3eng +hee4rei +hee2t +he3eti +he4fan +he4fau +he4f5erm +2heff +he4f3id +he4find +he4f5in4g +he4f3le +he4flo +hef3s2 +3heft +hef4tan +hef4tau +heft3s +he2fu +he4hef +h3ehr +hei2a +hei4da +hei6d5ert +h3eie +h3eif +h3eig +hei4kl +hei4lau +he4im +hei4mal +hei4man +hei4mar +hei4mau +hei4mei +heimer6f +hei4min +hei4m3o +heim3p +hei4mu +4hein. +hei4n3a +hein6dik +hei4n5e4b +hein5e4c +hei4neh +hei6nene +heinen6g +hei4n5er +h5eingab +h5einkä +h5einmi +hein5sk +4heio +4he3ism +heis4s5t +he3ist +h5eitei +he3i4ti +heit4s3 +h3eiw +hei4za +hei4zel +hei6zene +hei4zw +he5kar +hek2s3 +hek4t3a +he4l3an +hel5ass +he4l3au +hel3e4c +4helek +h3elem +hel5end +hel5ent +hel5ers +he4less +4heleu +4helic +he4l5ing +hel4l5au +hel3le +hel4m5an +hel4mei +3helms +he4l3oe +he4lop +he3lor +he2lö +helsau4 +2helt +helt4s +2hemb +hem4br +3hemd +he4mer +h5e4miss +2h3emp +he2na +hena4g +hen3au +he2nä +hend4r +hen5e4be +hen3ec +he4nel +he4n5end +he4n5en5e +hen5ens +hen5ent +he4nenz +he4nep +hen5erei +he4nerf +he4n5erk +he4n5ero +he4n5erö +hen5ersc +hen5erst +he4n5ert +he4ness +he4n5eta +he4n5e4ti +he4neu +hen3f2 +h3eng. +hen4gag +h5enger. +heng4le +heng4r +he4nil +he4n3im +he4n3in +henke6l5i +henk6lei +henk4lo +henk4r +hen6k5rin +henmen6s +henmi4s +hen3ne +hensch4 +hen6semb +hen5sla +h3entc +h5ente. +h3entf +4h3entg +h3entk +h3ents +4hentw +he4n3u +hen3z2 +he2o +he3on +heori4e +he3pf +2hepi +he3ra. +her3a4b +her3an +he5rane +4herap +he4r3än +her4big +herb4st +her3c +3herd. +3herds +he4r3e4b +he4r5eck +her3ef +4hereig +h5ereign +he5reigr +her6eini +he6r5eis. +her5eises +he4rel +he4r5en5e +he4r5ent +he3rer +he4rerf +he4r5erh +he6r5ersc +he4r5erw +her4fas +4herfo +h5er6folg +herg2 +6hergebn +h5erheb +h5erhit +h5erhoe +h3erhö +he4r3id +heri4e3 +4herif +he4r3il +herin4d +herin4f +he6r5in6nu +herin6st +herk2 +4herke +h5erlaub +her6lebn +her6lös. +her6löse +h3ernä +h5erneu +her5no +her3o4b +he3rod +her3of +he4r3oh +he3rok +heros3 +h5er6regu +hers4k +hert2 +her3th +her3tr +heru4t +he4r5ute +4herzae +her6zaep +her4zap +4h3erzä +her4zäp +her6z5eng +herzes4 +4herzeu +h5erzeugu +heseen4 +he3s2h +hesi4e +he3s2k +hes4ser +he3sta +he5s6temp +he5stro +he3stu +h3e4tap +heter4z +he3t2s +het3t +3hetz +he3u4fe +heu3g +he3ums +he3unf +heu3r4a +3heusc +he3xan +hex3em +2hexp +1hè +2h1f2 +hfel4la +hfen2 +hfi4le. +hfolgegene6 +2hg2 +hgeb2 +hgene6ri +hgin4s +h3glo +2h1h +hh4au +hhaustuer6 +hhaustür6 +hhoh2 +hhohl3 +2hi. +hialer4 +hi5a6lerw +hi3a2m +hi2ar +hi3a2s +h3iat +hi3ce +hich6t5er +hi4dea +hi4dio +2hido +hi3dr +hi2e +hied4s3 +3hiera +hie4rei +hier3i +hie4rin +hiesser6 +hiff4r +h3i4gel +hi2k3r +hi5la. +hi4l3a4d +hi4l3an +hil3c +hil4dau +hil4dr +hil4f3a +hil4fä +hilfe5s +hil4fl +hil4fr +hili4e +hi4lip +2hill +2hima +2hime +h3i4mit +h3imma +2himp +h3impe +h3impl +h3impo +hi3nak +hi3nal +hi3nam +hi3nap +hi3nas +hinau4 +hin4dac +hin4en3 +hing4a +hing4l +hing4r +2hi3n4i +hin3n2 +2hino +hins2 +hin4t5an +3hinw +2hi2o +hi3ob +hi3os +hip1 +hi2pa +hi4pei +hi4pen +hi4phe +hi2p3l +hi2po2 +hi2p3r +hips2 +hipt4 +hi2pu +2hire +hire3s +hir4m3a +hir4m3i +hir4ner +hirs2 +hir6scha +2his. +hi4schl +hi4schr +hi2se +h3i2so +his2p +hi3spo +hi3spr +his4st +hi5s4tie +hit2a +hi3tac +hi4t3an +hi4tel +hi4tro +hit3z4e +hi4van +hivar4t +hi2v3o +hi2vr +hi2vu +2h1k2 +hke3na +h3ku +hk4uh +2hl +h3labo +h5lache +hlach6t5r +h5laeche +h5laed +hlaf3l +hla4gä +hla4gei +hla4g3r +h3lake +hl3akt +hlammer6 +hla2n +hlan4d5a +hlan4dr +hl3ano +hl3ans +hl3arb +hl3art +h3las +hl3asp +h3late +hl3ato +h5laus. +h3laut +hl3ax +h3l4ay +h5läche +hl3är +h3läs +hlb2 +hlbe3n +hl1c +hl3d2 +hld4a +hle3e2 +h4legel +h4leic +h4leie +hlei4fa +h4leime +hl5einb +hl5eing +hle5in6ha +hl5eink +h4l5einl +h4l5einr +hl5eise +hl3ele +hl3emp +h5len. +hl5en6dun +hlenen5e +hlen5sk +hlens4t +h4l5entd +h4l5ents +h4lentw +hl4enz +h2lep +hler3a +hler5eig +h6l5er6nae +h4l5er4nä +hler6ze. +h4lesi +h4l3ess +hles4t +hle5sta +hle5sto +hle5str +h2leu +h3leuc +hlf2 +hlg2 +hlge3n +h2lie +h3lieb +h3lied +h3lieg +h2lif +h2lim +hl3imp +h4l3inf +hling4s5 +h5lings. +h4lingt +h4l5inst +hl3int +h4l3inv +h2lip +h4liss +h2lit +h3lite +hlk2 +hlo2b +hl3obe +h4loess +h2lo2f +h2loh +h2l3o2p +h4lor +hlor3a +hlo4re +hlos4s5t +h2lot +hl4samb +hl4sang +hl4sau +hlsch4 +hls2e +hl4s3el +hl4ser +hls5int +hl4sph +hl5s4tec +hl5s6tern +hls5tie +hl4sun +h3luk +h5lumpe +h3lurc +hl3urn +hlus6s5en +hluss5t +h3lüf +hlz2 +2hm +hm3abl +h4maec +h4maeh +h4mael +h4malw +h4malz +h4mand. +hmani4e +h4marc +h4maro +h4mause +h2mäc +h2mäh +h2mäl +h3me. +hmee2 +hme3eb +hmeer4s +h3mein4 +h5meise +hme5l4a +hme3le +h3men +hme5nad +hmen4sa +hmen6schl +hmen4se +hmens5em +hmen6spo +hme4rad +hme4ran +hmer5ei +hme4r5id +hmerin6t +h3mes +h3mex +h2mid +h2mie +h4milz +h4mink +h4miss +h2m3o2f +h2mol +h4morb +h4more +h4mors +h4mort +hm1p2 +hm2s3p +hm2st +hm3s4tr +hm2th +h2mud +hmuss4 +h2mut +hmut4s +2hn +hnabe4 +hna2c +hnacht6s5 +hn3amb +hn3a2n +h3nark +h3nas +hn3a4te +h3n4au. +hn3äh +h2näp +hn3är +hn3d2 +hn2e +h2nee +h4n3eff +hnei7der +hn3eig +hn3ein +h2nel +hne2n +hnen6sem +hn3epo +h5ner. +hne4r3a +h4n5er4de +h4n5er4du +hner5ei +hnergene6 +h5nerlö +h4n3ero +h4n3erö +hnet4s +h4netu +hn3ex +hn3f2 +hnflei4 +hng2 +h2nib +h2n3id +h2nie +hnik3a +hni4kan +hni4kar +hni4kl +hni4kn +h2n3im +hn3ing +hn3inn +h2nip +hni4sa +h2nit +hnit4tr +hnk2 +hn3o2b +h2nod +h2noe +h2no2r +hno3s +hns2p +hnst4 +hnsuch4 +hn4t5auf +hn4tho +h1nu +h2nuc +h2nue +h2nul +hn3ums +h4n3unf +h4nute +hnz2 +ho2ar +hobel5e +h5oberf +ho2b3l +hoch3 +ho4cha +hochen5e +hochs4 +ho6ck5ere +ho6ckerl +hoe2d +ho3edu +2hoee +hoe2s +hoes3c +h3oese +h3oest +ho2ex +ho2f3a2 +ho2fä +ho4f3ed +ho4f3ei +ho4fet +ho4feu +hof3f4a +hoffen6s6 +hof5fi +hof3fl +hof3fr +ho4f3in +ho2f3l +ho4for +ho2f3r +hogene6r +hoh4lei +h3ohm +ho3hö +h3ohr +2hoi +hok2l +hoko3 +hokos4 +h3okt +ho4lab +ho4l3ak +ho4l3an +ho4l3au +ho4lav +ho4l3ei +ho4lem +ho4l5ent +hol5gr +hol5int +hol2o +ho3lof +3holst +2holy +h3o4lym +3holz +hol6zene +hom4e +ho4me. +ho4mec +ho4med +ho4mel +ho4meo +2hon. +ho4nar +ho4nau +2hone +2hong +2honh +honi4e +2honk +2hons +ho3on +hoo2r +2ho2pe +ho4p3r +4hora +ho3rae +ho5ral. +ho5rala +ho5ralg +hor5alt +2hore +ho4rem +ho4rer +2horg +ho4rol +ho4rop +hor4ta +hor5tan +hor4ter +hor4t5rä +hor3um +hose4 +ho4s3ei +2hosm +ho2sp +ho3spr +hos4sa +host3r +h3osz +hoße2 +ho4ß5ene +2hot. +3hotl +hotli4 +hoto5p +2hot3s2 +hou1 +hou2s +2how +h1ox +2hö. +hö2c +hö3ck +h1ö2d +2höe +2höff +1höh +hö2s1 +h3öse +h3öst +2h1p2 +h3pu +2hr +hr3a2c +h3rad. +hr3adr +h5raeum +h3rago +hr4ai +hr3ala +h3ramp +h3r4ane +hran4kl +hra4no +hr3ans +hr3atl +hrau4ba +hraube4 +h3rauc +hr3auf +h3raum +hra2x +hr3ämt +h3räu +hrb2 +hr1c +h3re. +h3rea +hre4bl +h3rech +hre6ck5er6 +h3red +hr3eff +h3reg +h3reha +hr3ehr +h4rei. +hrei4ba +hrei4bl +hrei4b5r +h3reic +h4reie +h3reif +h5reihe +hr5eing +h4r5einl +h4reins +hrei5th +h3rel +hr3emb +hr3emp +hrende6s +hrenk4 +hren4zy +h3rep +h5rere4 +h4rerfa +hrer6fol +hr6erfor +hrer6gebn +hr5er4ke +h6r5er6leb +hrer5oe +hrer3ö +hrer6zae +hrer4zä +h3re2s3 +hresgene6 +hres5k +hress4 +hre2t +h4r3eta +h2reu +hr3eul +h2rev +hr3eva +h3rez +hrg2 +hrge3r +hrgu2 +hrgut3 +h3ric +h4rick +hri2e +h5riesl +hr3imk +h3ring +hr3inh +hr3ins +hr3int +h3risc +h3risi +hr3iso +hrit6tel +h5ritter +h3riu +h3robo +h3roch +h3roeh +h3roe4s3 +hro2f +hr3ofe +h3rog +h3roh +hro2l +hr3oli +hr3oly +h4romat +h4ron +hro4n5a +h3ropa +hr3o2r +hr3osm +h3rote +h3roto +hrot4t5a +h3rou +h3rö2s3 +hrp2 +hrr2 +hr4s3ac +hr4s5amp +hr4s5and +hr4s5ang +hr4s3au +hrs5eins +hr4sele +hr4sent +hrs5erf +hrs5erl +hrs5ers +hrse4t +hr4s5eta +hr4s3in +hr2sk +hr4s3of +hr4stri +hr4sum +hr4s3un +hr2sz +hrt5aen +hr4tak +hr4tall +hr4t3an +hr4t3än +hr4t3e4l +hr6t5en6de +hrten6gag +hrtengene6 +hr4t5erf +hr4t5erl +hr4t5erz +hr4t3ho +hrt5ing +hrt5ins +hrto2 +hr4top +hr4t5ram +hrt5rol +hrt6s5ein +hrt4sin +hrt4sto +hrt4stu +hr4tung +h1ru +h2rub +h4rueb +h3ruf +h2rul +h3rum. +hr3ums +hr3unf +h1rü +h2ry +hrz2 +hrzah4 +hrzahl5 +hrzeu6ger +2hs +hsa2b +h4s5achs +h4s5acht +h2s3a2d +h4s3aeb +h4s3aeh +h4s3aem +h2sak +h4s5alar +h4s3alk +h4s3all +h4s5alte +h4s3amm +h4sanb +h4sanf +hs3ang +h4sange +h4sanh +h4sanm +h4s5anna +h4sanr +h4s3ant +h4s3anz +h2sa2r +h2s3as +h4sath +h4s3ato +h4s3aud +hsau6erl +h4s3auf +h4saut +h2s3äb +h2säh +h4säug +hs2ca +hsch2 +h4schan +hsco2 +hs2cr +h2s3e2b +h2s3e2c +hse2e +hsee3i +h4s3ehr +h4s3eie +h4s5eind +h4s5einh +h4s5einl +h4seinn +h6s5einst +hsela4 +h4selad +hse4l5ag +hse4lan +hse4l5ei4 +hse4l5en +hse4ler +hse4l5in +hs5e4lit +hsels4 +hs5e4mis +h4s5endw +hsene4 +hse4n5eb +hsen5erg +h6sensem +h6s5entar +h4s3epi +h4s3erb +h4s3erf +h4s3erh +h4s5erkl +h4s3erl +h4s3erm +h4s5erna +h4s5ernä +h4s3er3o +h4s3erö +h4s5erpr +h4s3ers +hse3r4u +h4s5erwe +h4s5erwi +h4s3erz +hse2t +h4s3eth +hsex2 +h4sexe +hs4ext +hs4ha4k +hs4her +h2s3i2d +h4s3ind +h4s3int +h4s3ita +h5skala +h5skand +hs4nac +h2so2b +h4s3ofe +h4s3off +h2s3op +hs5ort. +hs6orti +hs5orts +h1sö +h1sp +hs4pach +h4sparl +h4sparz +h4spat +hs4pec +h4spel +hs4perb +h4speri +h2sph +h2spl +h4spro +hs3s2 +h4st. +h3stab +hs5tabl +h4stabs +h3stac +h3stad +hs5taeu +h3stah4 +hs5talb +hst5alt +hs5tank +h5stans +hst5anza +h3stap +h5stark +h5start +hs5tata +h5stati +h5statu +h5stau. +h4s3täu +h2ste +h3stec +h5stein +hst5einsa +h3stel +h4stele +hs3ten +hs4t5en4g +h3s4tep +hs3ter +h5s4terb +h5stere +h5s4tern +hs3tes +h5steth +hstge3 +h3stic +hs4tieg +h3stil +h3stim +hs4tin +h3s2to +h4stole +hsto4n +hs2tr +h5straen +h5straf +h5strah +h5stras +h5strat +h5strec +hs5treu. +hs5treue +h3stro +hstro6ma +h3strö +hs4tub +h3s4tue +h3stun +h2s1u2 +h3sub +h3sul +hs4ung +h3sup +hs3ut +h3s4wim +2ht +ht1a +h2ta2d +h4taer +ht5a6gent +h4taka +ht4akt. +ht4akte +h4tall +h4ta4lo +h4talp +h4talt +h2ta2m +hta2n +h4tan. +h4tanb +h4tan3e +h4tanh +h4tanna +h4tans +h4tanw +h4tapo +h2tar +hta4rie +ht5a4ris +h4tasi +h4tasy +h4tath +h4tato +h2tau +h3taub +h3taum +h2tax +ht1ä +h2tär +hte2c +hte5cha +ht5echs +ht5echt +ht3eck +h4t3ede +hte3e +ht3eff +ht3e4he +h4t3eif +h4t5eilz +h4t3eim +ht3ein +hte5in6fo +h4t3eis +h4t3e4ke +h4t5e4las +hte6l5ei. +h4t5elek +h4telem +h4t5elfe +h4t5e4lit +h4t5e4mis +h4t5e4mit +h5ten. +h4t5endo +hte6nere +hten6gag +htens4p +ht5entar +h4t5entd +ht5ente. +h4t5entf +h4t5entg +h4tentl +h4t5ents +h4tentw +hte4r3a +ht5erbe. +h6t5erben +hterer6sc +h6t5erfah +h6t5erfol +h6t5erfuel +hter6fuellt +h6t5erfül +hter6füllt +h6tergeb +hter6gri +hter6hael +h6t5er6häl +ht5erheb +hter6hiel +hter6höhu +h6t5erklae +h6t5erklä +h6terleb +h6t5erleu +h6t5erneu +h4t5ernt +h4t5er5ob +h6ter6prob +h6ter6spar +hters4t +h6ter6stat +ht5er6trä +ht5erwae +h4t5erwä +h6t5erzie +h4t3ess +hte5sta +hte5str +h4t3eta +hte4th +ht5etho +h4t3eur +h4t3eva +h4t5extr +h2t1h +h5therm +h3thr +h2t3i2d +hti6g5er6k +h2t3im +hti2n3 +h4t3ina +h4t3ind +h4t3ine +h4t3inf +h4t3ini +h4t3ins +ht3iso +h4t3isr +hti3t2 +h4t3ita +ht3l +htni2 +h4t3obs +hto2d3 +h4t3oef +ht5oele +h4t3oes +ht3o2f +h4t3oly +h4t3ont +h4t3ope +ht5opfer +h4t3opi +h4topt +h4t3ora +h4tord +h4torga +h2t3ot +h2t3ös +ht4ran +ht5rand +h4t3ras +h4t5rati +h4t5ratt +ht3rea +h5treck +ht5reic +ht5reif +h4t5reis +ht5reit +ht3rem +h4t3res +ht2ri +h4t5rieg +h5trik +ht5ring +h5trio +h5t4rit +h4t3riv +h6trocke +h4t3ros +ht3röm +ht3rus +h4t3rut +ht4s3ah +htsa4n +hts5ana +htsch4 +hts5chro +ht4scr +hts5endu +ht4s5eng +ht4sep +htse4r3 +hts5eri +hts5kr +ht5slaw +ht4soe +ht4sof +ht5s4pen +ht5spru +ht4staf +ht6s5tate +ht4stip +hts4tol +ht6streu. +ht6streue +ht6strip +ht6stuer +ht4s5tür +htu3en +ht3uh +h4t5umse +ht3und +h4tunem +h4tunf +h4t5ungl +h4t3urk +h4t3urs +htwa5re +htz2 +hu2b3a +hu4b3ei +hu4b3el +hu4b3en4 +hu4b5er4z +hu2bi +hu2b3l +hu2b3o +hu2b3r +hu2bu +hu1c +hue5ben +huef6ter +huehne4 +hue4r5ei +huer4fr +hu2f3a2 +hu2f3ä +huf5erk +hu2f3o +hu2h3a +hu4h3ei +hu2h3i +huh3l +hu2h3o2 +huh3r +h3uhr. +h3uhre +h3uhu +hu2k3a +hu2kä +hu4k3er +hu4k3in +hu2k3l +huko3s +huk3t2 +hu2l3a +hu2lä +hu4l3e4b +hu4l3ei +hu4lel +hu4lem +hu4l5eng +hu4lent +hu4l3er +huler4f +huler4w +hu4l5ess +hu4let +hu4l3in +hu2l3o +hu2l3ö +huls2 +hul5se +hul5ste +hul5ta +hu4lum +hum4a +hu5man +h3umb +2humf +h3umh +hum4pl +2h3ums +humu2 +2humz +2hun. +h3una +hunde5i +hunde5n +hunde5s4 +hund3r +hun2e +2hunf +h3ungl +hungs3 +hungsa4 +huni3 +2hunt +hup3fl +hu2p3h +hu2ra +hurg4 +h3urku +h3url +hur4ter +hur4t3h +hu4s3en +hu4so +hus4s3a +hus4s5er +hus4s3o +hus4sp +hus4sta +hus4ste +hu4t3ab +hu4tau +hut3ei +hut3ex +hu2th +hut3he +hu2t3i +hu2t3o2 +hu2t3r +hut5sc +hut4ta +hut4zeh +hut4zei +hut4z5en +hut4z5er +hü3bel +hü3ben +h3über +h2übs +hüf4t5er +hühne4 +hür4f3r +2hüri +hüs3 +2hv +2h2w2 +h3wack +hw4ar +h3weck +h3weib +h3weid +h3weih +hwei5n4e +hwein6s5a +hwen4ka +h3wick +h3wies +hwi2s +h3wisc +1hyd +hy2da +hy2de +hy3dr +1hyg +hy4lor +3hymn +h1yo +hy3os +1hyp +hy4pe. +hysteri6e +2hy2t +2hz +hzug2 +hz2w +ia1a2 +ia4ben +iab2l +i3ad. +iad2a +i3adn +ia3do +ia3ei +ia3el +i3ae2m +i3aer. +ia3erf +i3aeri +ia3err +i3aers +i3aerz +i3aet. +i3aeta +i3aetb +i5aetem +i3aetf +i3aetg +i3aeti +i3aetk +i3aetp +i3aetr +i3aet3s +i3aett +i3aetw +i3aetz +i3aev +ia3fl +i3agg +ia1h +iah4o +i1ai +ia3in +i3ak. +i3a4kei +i3akg +i3akh +i3akm +i3a2k3n +ia4kre +i3aks +i3akt +i3akv +i3akw +i1al +ial3a2 +ia4lar +ia2lä +ialb2 +ial5bl +ial3d +i3a4leb +i3alef +ia5lek +i3a4lel +i5alemp +i5alengp +i5a4l5en4s +i5a4l5ent +i5alen4z +i3a4lep +i5a4l5er4b +i5a4l5erf +i5a4l5erh +i5a4l5e4ri +i5a4l5erk +i5a4l5erl +i5a4l5erm +i5a4l5ero +i5alerö +i5a4l5erp +i5a4l5ers +ial5erwa +i5a4l5erz +i3a4l3e4t +i3alex +i2ali +i3a4lia +i3a4l3id +i3a4l3in +ialk2 +i3all +ial4ler +ia4lob +ia4l3on +ia4lop +ia4l3o4r +ialp2 +ialsch4 +ia2l3u2 +ialz2 +i3amp +iampe4 +ia3mu +i3an. +i3anab +ia4n3ae +i3a4nal +ian5alt +ia4nar +i3a4nau +i3a2nä +i3anc +ian4ce +i3and2 +i2ane +i3a4n3e4b +i3anei +i3anex +i3ank2 +i3ann +ia4nod +i3ans +ian4s3p +ian4st +i3ant +i3anz +ian4z3a4 +ia1o +ia2op +i1ar +i3ar. +ia4rab +iar5ein +i2ari +iarr2 +i3as. +i3asb +ia3sh +i2asi +ias2p +ias3pr +i3as3s +ia3sta +ia3s4tr +i3asz +i3at. +i2ate +i3atl +ia4tol +i3atom +i3ats +i1au +ia3un +i1ä +iä2m +iä2r3i +i3ä4tem +iä2ti +iä2tr +iät3s +i4b5achs +i4b3arm +i4b3art +i4barz +i4b3auf +i4b3aus +i4baut +i3bä +ib3ärm +ib4bli +ibeer4 +i4b3eig +i4b5einh +i4b3eis +ibel3i +ibend4 +i4b5ener +ibe3ra +ibe4rab +ibe4rae +ibere4 +ibe4reb +ibe4ren +i4ber4fa +ib5erfah +i6ber6geb +iber6lau +i6b5er6sche +ibe4tau +ib3ide +i4b3ins +i4b3lab +i4blad +i4bläh +ib4len +i4bleu +i4bluf +ib2o +i4boel +i2b3op +i4b3ort +i2böl +ib4rec +ib2ri +ib5roec +i4b3röc +ib4ser +ib3s4kl +i4bueb +i4b3unk +i4bunt +ibus3c +i2büb +2ic +ica2l3 +ic1c +ice1 +icein4 +i4ch. +icha2 +ich3a4c +ich3ag +ich3al +ich3an +ich3ar +ich3au +ich3ä +i3che +i4ch3e4b +ich3ei +i6chensem +i6ch5erde +icher5eig +icher6gebn +i4ch3eu +i3chi +i4chins +ich3la +ich3lä +ich3li +i5chlor +ich5lot +ich3m +ich5ma +i4ch3n +ich3oe +i3chon +ich5ort +ich3ö +i2chr +ich3ra +ich3re +ich6s5amt +ich6s5ern +ich4s5or +ich4spo +ich5stri +ich4tab +ich4tan +ich4tar +ich4tas +ich4teb +ich6t5eig +ich7ten. +ich4t5in +ich4toe +ich4t3r +i3chu2 +ich3uf +i4chur +ich3w +i1ci +i4ck5erf +i2cko +ic3k4os4 +ick3sa +ick3sk +ick3s4p +ick5sta +ick5sti +icktu3 +ic3la +ic3le +id2ac +id3aer +ida2n +id3ana +i4d3ani +i4danl +i4dante +i4danw +id3arm +id3art +id2ax +i2daz +idä1 +i2d3är +i2dee +i3deei +ide5inst +i4demul +ide5nad +idener4 +ide4n3i +ide4n3o4 +iden4zi +i4denzy +ide2o +ide5rae +ide5ran +ide4rel +ider6fül +iderin6f +iderin6nu +ide3ro +ide3rö +ide4ser +ides4p +ide5sta +ide5str +2idf +id4gea +idi3ae +idi2o +id5ion. +id5ione +i4diot +i2dol +3idol. +i3dor +id4o3s +i2d1r +id4ro +id4san +id4set +ids3pr +ids5tra +idue4 +idu3en +idwa3r +1idy +ie3a2 +ie4bab +ieb5ach +ie2bl +ie3bla +ieb4sen +ieb4s3i +ieb4so +ieb4ste +ieb4sti +ieb6stre +ie4bum +iech3o +iech3t +ie2ck +ieck4s +ie4d3an +ieden6sc +iede4r5a +iedi4e +ie2dr +ied4si +ied4spa +ie1e2 +ie3ev +i2ef +ie4f3an +ie4fau +ie4feb +iefe4m +ief5emi +ieff2 +ief5fl +ie2f3l +ie3flu +ie4fonk +ief5rea +ie4f3ro +ie4g3li +ieg2n +ie4gre +ieg4s3c +ieg4se +ieg4si +ieg4sp +ieg4s3t +ie4hap +ieh5eis +ie4h3in +ieh3la +ieh3r +i1ei +ie3im +ie3in5d +iela4r +ie4lau +ie4läs +ield2 +iel5ech +iel5e4ck +ie4l5eic +iel5eid +ie4l5eig +ie6l5eint +iel5eis. +iel5eise +ie4leng +ielen4s +ielen4z +ieler6fin +ieler6fol +ieler6ga +ieler6gä +ieler6geb +ie6l5erkl +ie6l5er6la +ieler6lebn +ieler4ö +ie6l5erru +ieler6wei +ieler6zae +ieler6zä +ieles4 +ie4less +ie4l5eta +ielgen6a +ielgene6 +iel3i4d +ie4l5inf +ie4l5ins +iel5int +ielo4b +ie4l3op +ie4lor +iel3sk +iels4p +iels4t +iel5ste +iel3sz +i5e4meti +i1en +iena4b +ie4na4g +i4enah +iende5s4 +ien3ec +ien5ekl +ie4nel +ien6erfu +iener6la +iene5sk +ien3f2 +ieng2 +ienge4z +ie4nid +ie4n3im +ie4n3in +ienk2 +i3enn +ieno2 +ien5sa +iensch4 +i5en5se +iens6er +ien3s4k +iens4p +ienst5aen +ienst5än +ienst5ein +iensten6 +iens6t5end +iens6ter +ienst5erl +ienst5erw +ienst5op +ienst5or +ienst5rän +ien6t5ero +i5enti +ien3tr +ie1o2 +ie4peg +ie3p2f +ie3ra. +ier3a4b +ier5aet +ie5raeu +ier3af +ier3al +ier3an +ier3as +i2ere +ie4r5eck +ier5eier +ier6eini +ier5eis. +ier5e4mi +ie5r4ert +i2eri +ie4r3id +ie4r3il +ie4r5ing +ierin5i +ierk4 +ier3l +iermi4s +i3ern. +iern4a +ier3ne +ier3ni +ier2o +ier3ob +ier3oe +ier3of +ie3roh +iero3n +ier3ö +ier4re. +iersau4 +ier4s5eh +ier4sko +ier5sta +ierts4 +ieru2 +ie4rund +ie4r3ut +ie4s3au +i3esd +ies5ein +iesend4 +iesen5s4 +ie3s4ka +ieso2 +ie4s3oe +ie2sö +ies2p +ies4sel +ies6s5ent +ies6s5er6l +ies4sti +iest4a +ie5stel +ie3str +ie3stu +i3esü +ie4ßel +ie4ß5ent +ie4t3ak +ie4t5ang +ie4tans +ie4t5anz +ie4t3ap +ie4tat +iet5ein +ie4t5ent +ie6t5er6ho +ieter6hö +ieterin6t +ie6t5erlo +ie4t5er4t +iet5hel +iet5her +ie4t3ho +ie4thu +i3e4thy +ie4t3ob +ietoe4s +ie4t3ö4s +iet5ram +ie4tren +ie4t3ri +iet5rud +i3ett +iet3z4a +iet3zw +ieu2e +ie3uh +i4e3um +ie3un +ieu4r3a +ieu3s +ie2w3r +i1e2x1 +iezo3 +2if +if3aen +i2f3af +if5ange +i3fari +i4f3arm +i2f3au +if3än +i2fec +if3eff +i4f5einh +i4felem +ifel4s5o +ife2n +ifen3e +ifen6te. +if3erh +ifes5tat +if4far +if2fl +iff3la +if3flu +if4f3ru +if4f3rü +iff2s +iffs5et +iff4sti +iff4stu +if3inf +i4f3ins +if3lac +if3lad +i3flae +i3flag +if3lau +i3flä +i3flec +i3f4lie +i3flue +i3flug +i3flü +if1r +i3f4rag +i3freu +i3f4rev +if3sa +if2t3a +if4t3ed +if4t3ef +if4t3ei +if4tent +if4t3ep +ift5erl +ift5erz +ifte4s +if4t5esc +if4t3ex +ift5int +if4t3op +if4t3ra +ift5rei +if4t3ri +if4t3ro +ifts4c +ifts6tem +ifts4tr +if4tum +if4tur +i2f3um +i1fy +i4ganb +i4g3ano +ig3anz +i4g3arb +ig3auf +i4g3aus +igd2 +igeb2 +i6gebrau +ig3e4ck +ige4dre +i4gefar +ige4füg +ige4hef +i4g3eig +ige5inf +ige5inh +i4g5eise +i4gel. +3i4geln +ige4mes +ige4mis +igena4b +ige4nag +ige4n5e4b +ige6nene +igener6f +ige6ners +igener6z +ige4nid +ige4pac +ige4ral +ige4rar +ige4rau +i4g5er4fa +igerin6se +iger6klä +ige3sk +i4g3ess +ige4woh +igg2 +iggas5t +igh3s +2igi +i2g3im +ig3ins +ig5laen +i4g3lau +i4g3län +ig3lec +i4gled +i6gleitu +i2gli +ig3lim +ig3lu +ig2na +i4gnae +ign3a4g +i4gnak +i4gnan +i2gnä +ign3ei +i6gnerge +i3g4neu +ig3nu +igo3p +i4grams +i4greg +i4grei +i4g3res +i4g3rin +ig3rom +igs4ae +igs4ag +ig3sar +igsau4 +ig4scha +ig4set +igs2i +ig3s4or +ig3s4pe +ig3spr +ig5stei +igs4top +ig4stö +ig4s5tür +ig2sy +ig4tin +igtu5er. +i1h +ih2a +i4h3ans +i4h3auf +ihe3e +ihe3in4 +ih3elt +ihe2n +ihenk4 +ihens4 +ihe3o +ihe3s +ihe3u +i2h3in +ih1l +ih3m +ih1n +i2h3op +ih1r +ihrau4 +ihs2 +ih3sta +ih3ste +ih3sti +ih3str +i4h3um. +ih3w +i1ia +i3i2c +i1i2d +i1ie +i1i2g +i1im +i1in +i1i2r +i1i2s +i3it. +i3ja +1i2js +i1k +ik5abla +ik5ablä +i4kada +i3kae +i4k3aeh +i4k3aer +ika4gen +ika3in +i4k3akt +ikalk4 +ikal5t +ik5alta +i4k3ang +i4kanl +i6kantei +ikanten6n +ik5anzu +ik5anzü +ika3pf +ik3arm +ika4tan +ik3att +i2k3au +i2käh +i2k3är +ikbu2 +4ike. +i2keb +ik3ebe +ike2c +i2k3e2d +i2kef +ik3eim +ikei4n3 +ike2l +4ike2n +iken3e +i4k3en4z +i2kep +i6k5er6be. +ike4ren +i6k5er6fah +iker6folgs +iker6hal +iker6ken +i6ker6leb +i4k3ero +i4keru +ik5erze +iker4zi +ike4t +i4k3eta +ik3eth +i4ketu +i2k3e2v +i3ki. +ik3imm +ik3inh +i4k3ins +ik3lab +ik3lad +i4k3lag +iklan4 +ik3lem +ik4leri +i3kles +i4klos +ik3nar +ik2o +ik3obs +i4k3oef +iko3nu +ikop2 +ik3o4ri +ik3öf +i2k3öl +i4kres +ik3rin +ik2ro +ikro3s +i2krö +ik3ru +ik1s +iks2p +iks2t +iks2z +ik4t3eb +ikt5erk +ikt5erz +iktes4 +ik4t5esk +ik4t3o4b +ik4t5res +i4kueb +i4kumf +ik3unf +i5kuns +i2kup +ik3urh +i3kus +iku2t +ik3ute +i2küb +i2ky +i1la +il3abb +il3abl +il3a4br +i4l3abt +i2l3ac +i4l3aer +ila3f +i2l3ak +il3a4ma +il3ana +i4lanb +ilan5er +i4lanf +i4lanh +i4l3anm +i4l3ano +il3ans +ilan6z5er +i4l3arb +il3art +i4l3asp +il5aufl +ilauf6ru +il5auft +il5aufw +il5aufz +il3aus +i1lä1 +i2l3är +ilch5au +ilch5ec +ilch3o +ilch3r +il4dae +ild5au. +il2dä +ild5ebe +il4d3ed +ild5ele +il4d5ene +ilden4s +il6dense +il4d5en4t +ild5erhal +il4d5ese +il4d5es4s +il4did +il4dil +il4dop +il4dor +il4drü +il5ebene +il3e2c +i2led +ile3gl +i4l3eig +il3ein +il5eise +il3ele +i4l3emb +i4l3e4mi +il3emp +i4l5entb +i4l5ente +i4l5entf +i4l5entl +i4l5ents +i4lentw +iler6be. +iler6ben +i6l5erbli +i4l5er4de +iler4ei4 +i6l5ereig +i4l5er4fo +i4l5er4fu +i4l5er4fü +i6lergeb +i4l5erhe +i4l5erkl +iler6las +i4l5er4lo +i4l5er4lö +iler6öff +iler6stat +i6l5er6wae +i4l5er4wä +iler6weit +iles2 +i4lety +i4l3eur +i4leva +il3ex +ilfe3i +ilf2s3 +ilf4sta +ili3as +i2l3i2d +ilie4n3 +ili4g5a4b +il3imp +i4l5init +i4l3inv +ili3o +il3ip. +ili3pf +il3ipp +il3ips +ilk2 +ill4ant +il4l5ein +il5lem +il3li +ills4p +3illum +il4mad +ilm5a4ge +il4mak +il4mam +ilm5ans +il4m3ap +il4meh +ilm5ein +il4m5ent +il4m5enz +il4m5erk +il4m5erl +il4mex +il4mik +il4m3in +il4mop +il4m3or +il4mou +ilms2 +iln2 +il3obe +ilob4r +il4oh +i2l3o2r +il3ox +il4san +ilsch4l +il4s5ent +il5stec +il5stemp +il5stern +il3sti +ils4to +ilsto4r +i1lu2 +i2lue +i2lum +i2l3ur +ilv2 +il4z3an +il4z3ar +il4zau +ilz5erk +ilzes4 +il4z5ess +il4zwa +im3abl +im3abr +im2ad2 +ima4get +ima3i +ima3kl +imaler4 +iman3d +i4manh +i4manw +im3apf +i4marc +im5a4ren +i4m3arm +i4m3art +ima3sz +i3mat +ima4t5in +ima4tra +ima4tur +i4maut +i2mäp +im3b +i2meg +im5eins +im5ein4w +i2mej +i2mek +i4m3ele +i4m3elf +i2m3e2p +imer4la +i4m3er4z +i4me3sh +imess4 +i4meti +i2mew +i2m3ex +imhau4 +i2m3im +i4m3inf +i4m3ins +im4mak +im4mant +im4m3au +im4mei +imme4n +im6m5enth +imm5erkr +im4m3id +im4mop +im4mor +5immuni +i2m3o2p +imo4re. +4impa +im5paa +imp4fa +imp4f5er4 +imp4f3i +impf5li +impf5lu +im4ple. +im4p5ler +imp2s +im3pse +3impu +ims2p +im2th +imt3s2 +2imu +imult4 +im2um +im3urk +i1mü +4ina. +i3nabo +ina4br +in3abu +ina2c +in5ach. +in3ack +in3aeh +in3ae4s +in3aeu +in4a3fr +ina5ge +inahe3 +4inal +i3nald +i3nale +inalen4 +inaler4 +ina6lere +ina6l5ins +in3amb +in3ana +i4nang +in3ann +ina4no +i4nanzü +4inap +in3ape +in3app +in4a4ra +i3n4are +in4arr +in4ars +in3as4b +inat3h +in3atm +in5atom +i4nau +in3aue +inauf3 +i3naz +i2n3äh +in3ä2s +inbun4 +4ind. +in4da4b +inda4c +in4d3al +in4d3an +4inde. +indei4g +ind5eis +4inden +4inder +inderei6s +inderer6t +in6der6sat +inde5spa +inde5sto +5index. +5indexe +3india +5indika +3indiv +ind2o +in4drec +in5drue +in5drü +ind4spe +ind4ue +3induk +3indus +ind2ü +2ine. +in5ebene +ine3ch +in5eck. +in5e6cker +ine3el +i4n3eff +in5e4hen +i3neim +i4n3elb +ine5nae +ine3nä +inen6gag +inen6ge. +inen3s4 +ineo2 +ine3p2 +i5ner. +i4n5erbi +i6n5er6hal +i4n5er4he +i4n5erke +iner6läu +i6ner6leb +i4n5er4lo +iner4lö +i4n5ersa +i4ner4tr +i4n5erz. +iner6ze. +iner6zen +iner6zes +iner6zeu +i4n5erzi +inesi4e +i4ne3s4k +ine5str +2in2et +ine3u4f +ine3un +ine2x +in3f2 +3infek +3infiz +3info. +4infre +in4g3af +in4g3a4g +in4gak +in4g3am +ing5ans +5ingeni +in5g4ler +in4g3n +in4gop +ing3or +in4g5rat +4ings +ing5sat +ingsauto6 +ing5s6por +3inhab +6in7haltu +4ini. +4i3nie +ini5erk +i4n3inn +ini3o2 +ini3s4e +ini3s4k +ini3sl +i5nitr +i5nitz +3injur +ink5aet +5inkarn +in4k3ät +inkels6t +ink5erd +in5kom +ink4sin +ink4ste +ink2u +in4kut +ink2ü +2inn. +4inne. +inne4n +inn5erfu +inn5erho +5innerns +inn5erwa +in4n5erz +inne4s5t +2innl +2inns +3innta +inoe4d +ino3et +in3old +i3nomi +in3opf +in3ord +i3n4ot +ino5ta +ino5te +i1nö +in3ö2d +ins5aen +in6samt. +in4s3än +insch4 +ins5ebe +in4s5eke +in4s5emb +in4s5erf +ins5erg +ins5erm +ins5ertr +3insid +in3sky +ins6port +instab4 +ins5tak +5instal +ins4tec +in6steil +in6st5einst +5instin +ins4tip +5instit +inst5rol +3insuf +3insul +in4s5umw +in4sur +4insy +int5an. +int5ans +4inte. +3integ +5interp +inter5z +int2h +inth3a +intons4 +4intö +5intrig +int3s +in1u +inue4b +i4nuh +i3nuk +3inumg +in4umt +in3unz +3inurs +inü4b +in5ver +3inves +4invo +i1ny2 +inz4el +inzel5a4 +inzel5e +inze4li +inzel6ler +inz5int +in3z2u +inzup4 +inz2w +io3a +io1c +i1od +i2oda +io2de +iodi4e +i2odu +io3e2d +ioe2f +i3oe2k +io3e2m +ioe2n3 +io3er +i3oes. +i3oest +ioe2t +iof2l +io1h +ioi2 +io3k4r +iok3s +i3ol. +i3olp +i3ols +i3om. +i3oms +i1on2 +ional5a +ion3an +i3o4n3as +i3o4n3au +i2onc +ion3d2 +ionen3 +io4nor +ions3 +ionsa4 +i2ony +io1o2 +i3opf +i3or. +ior3ak +i3oral +i3orb +i3orc +ior4e +io5re4n3 +ior6ens +i3org +io3r2h +i3orm +i3orp +i3orr +i3ors +i3ort +io1s +i3os. +io3sh +i3osm +ios2p +i2ot +i3ot. +iot4e +iote3l +io3t2o +i3ots +i3ox +i3oz. +i1ö2f +i1ö2k +i1öl +i1ön +i3ös. +i3öst +2ip +ipa2n3 +ip3ein +ipe2n +ipendi5e +i2pes +ip3fab +i3pfan +ip2hi +i1pi +ipi3el +ipie4n3 +ip2l +ip4pan +ip4pap +ip2p3f +ip2p3l +ip4pru +i1pr +ip4sei +ip2s3i +ip2sp +ips3tu +ip4s3tü +ip4ter +ipt5ers +ip4tro +i3p2ty +i1ra +i3r2ad +irae5us +ir2ak +3iraki +iral3a +iral5s4t +iral6stue +ir2an +i2rar +irat2 +i1rä +ir3äh +irä3us +irbe4l5a +irbel5i +ir4ble +ir1c +ir4ch3a +ir4ch3i +ir4ch3o +ir4en +i2rew +irgen4 +irg2s3 +irg6ste +i1ri +i4r3in4t +ir2k3l +ir4k3or +irks3c +irk2u +ir4k3ur +ir4m3ae +ir4m3a4g +ir4mak +ir4m3au +ir2m3ä +irme2 +ir4m3ed +ir4m3ei +ir4m3er +ir2m3o2 +ir4mum +ir4munt +irmwa4 +ir2na +irn3al +irn3an +irn3au +irn5erf +irn5erk +irn5ers +ir2no +i1ro +iroe4s3 +i1rö +irö2s3 +irpla4 +ir4rar +irr3au +irre4l +irre4p +3irri +irsch5ar +irsch5au +ir6schei +ir4schl +ir4schm +ir4schn +ir4sch5r +ir4sch5w +ir3s2h +ir4st3r +ir3su +ir4t3er4 +irtra4 +irt4s3e +irt4st +i1ru +ir2ue +irue3b +iru2m +iru2s +irus3e4 +iru5sta +i3r2ü +irü3b +isa2c +i4s3aen +isage4 +is3air +i2sak +i4s3alg +i3sam +i4s3amp +i4s3amt +i4sanb +i4sanf +i4s3ang +i4sanh +i4s3ann +i4sanp +i4sanr +isa2r +i4sare +is4arg +i4saro +is5assi +is3att +i2s3au +is4aue +i2s3än +i2sca +i2sce +i4schab +i4scham +ischa4o +i4schäh +ische4c +i4sch5e4h +isch5einh +isch5eint +ische4m +ische4n +i4schep +ische5re +i6scherkra +i5scheru +isches6s +i4sch5eu +i4sch5im +i6schind +i4schm +i4schna +isch5obs +i4schos +i6sch5rip +isch5ru +i6schueb +i4schum +i4schüb +i4sch5wo +i4sch5wö +isch5wun +i4schwü +is3chy +i2sco +i2scr +2ise +ise3a +i4s5eben +ise3c +ise3e4r +iseer5i +is5einsa +i4selem +isel5er +isel4l +i6s5el6ter +i4s3emb +ise4ne +ise5neb +isen5en +isen3f +isenk4 +ise6r5eic +iserin6t +is3erm +i4s5ernt +ise5r4ue5 +ise5rum +ise3r4ü3 +i4s3ess +ise5str +i4s5e4tat +is4eur +is4exi +isex5po +i2sid +isi4de +i2sim +is3imp +i4s3ind +i4s3inf +isin3g +ising4l +i4s3int +isi3st +is4kis +3islam +4isli +ismu4t +i2sob +is3oef +i2s3of +i2sol +i4somo +iso4nar +isonen4 +iso6n5end +isonet4 +iso4nor +i2sop +is5ort. +3i2sot +4i3sott +i2sou +isour4 +i2s3ov +i1sö +i3spal +is3pan +i2spe +i4s3pic +i4s3pir +is4ple +i3s4por +i3spra +i4spro +i3sprü +is3pü +iss3ac +is4sau +iss5aus +is4sed +issen6sa +issen6sp +issen6s5tu +issen6s5tü +iss5er4f +isseri6e +iss5er6ke +iss5erz +issi4l +is5s4kat +iss3l +isslich6t +iss4par +iss4por +is4stec +is6steil +iss4tu +i4st. +ist3a4c +is3tal +ist3ap +i3stat +is3täu +i2ste +ist5einh +ist5einl +is5t4e4n +isterin6t +ister6ras +iste4s +ist4hal +i3stil +i3stim +istin4f +i3stoc +i3stoe +i4stöp +is3tör +i5s4tram +istri5en +i5strik +ist5rit +istu5er. +istum4 +istungsex5po +is3tüm +isuch3 +isu6cher6 +isu2m +isum3p +i3s4up +i4s3urk +is3url +i4s3urt +i2sü +isvil4 +i2sya +isz4t3 +i4ß3erl +i4ß5erse +ißlich6t +it3ab. +it3aef +i4t3aes +itaet4s5 +ita3h +itai2 +i4t3aka +ita3kl +i6t5akte. +i6t5akten +i4takts +ital3a +italent6 +italer4 +ita6l5erh +ita6lers +ita6l5erw +ita4l5in +ita4lo +ital3t +it3a4mi +itan5at +it3ang +it3a4po +it3app +i4t3arb +it3a4re +it3art +it3asp +i4t3att +i5tauc +i4t3auf +it5aus. +i4taut +itauto6r +i2t3äf +it3änd +i2t3äs +ität2 +itäts3 +2ite +i4teben +it5echs +it3e4ck +ite3er +it3eff +it3eig +i4teige +it3ein +i4t3eis +itel5ae +itel5an +itel3ä +it5elef +itel5eh +itel5ei +i4t5elek +i4telem +i4t5e4lit +i4t3emb +ite4mis +ite2n +iteng4 +iten6gag +itens4 +i6ten6sem +i4tentl +i4t5ents +i4tentw +i6t5ereig +ite4rel +iterer4 +iteres4 +i6ter6fol +itergene6 +iterin6d +iterin6s +iterin6t +iter6klae +i6t5er6klä +i6ter6led +iter3o4 +i5terr +i4t3esk +ites4ka +ite5sta +ite4tik +i4t3eva +i3t4hag +it3hem +itho3s +i3t4hr +i2thu +i2thy +i2tia +i3tic +i2t3id +3i2tii +itik3a4 +iti4kan +iti4kl +iti4kr +it3imm +iti2n3 +i4t3ina +it3ind +it3ini +it3int +itionsex5p +i4tiso +iti4v3a +itiven4 +it3o4be +i3to3c +it3of +it3opu +itor3a +it3orc +i3tra. +it3rab +it5rade +it5raeu +it3raf +it5rand +it5rang +it3ras +it3rau +it5räts +i4t3räu +it3reb +it3rec +it5reic +it5reif +it3ren +it3res +it5rieg +it3ris +i4t3rob +it3roc +i4t3rol +it3rom +it3rö +it3ruc +i4t3run +i5trup +i4t3rut +its5alb +its3an +itsau5d +itsch5ro +it4s3e4h +it4s5eif +it4s5ein +it4s5ela +it4s5ele +it4s3es +it4set +it4staf +it4stec +its4tue +it4stur +it4t3ac +it4tän +it4t3eb +itt5eige +it4t3il +it4tl +it4t3o4b +it4t3op +itt5rad +itt5rän +itt5rei +it4tri +itt5rol +itt4s5e4h +itt6s5ein +itt4sti +itt4str +i3tud +it3uh +i4tunf +i4t3unk +i4tuns +ituran4 +itu4rer +it3urg +i4turp +itut4 +ity1 +it2za +it4z3e4c +itzen5e +itzer6fo +it6z5ergr +itz5er6ho +itzer6hö +it6z5erla +it4zoe +it4z3op +it2zö +it2zw +2i1u2 +i2u3l +ium1 +iuma2 +ium4s5en +iun2 +i3ut +i1ü +2iv +i2v3ab +i2v3ad +i2v3ae +i2v3af +i2v3a2g +i2v3ak +iva4lo +iv3ang +iva6tent +ivater4 +iva6t5erf +iva6term +iva6t5erw +i2v1ä +i2veb +i2vec +i2v3ef +i2vek +ive2l +iv3ele +iv3elt +i4vemi +i4vemu +ive4no +i2vep +iver4ha +iver4he +iver4lo +iver4lö +iver4oe +i4v3er4ö +ive3s +i4vesc +i4vese +i4vesh +i4vest +i4veti +i2veu +i2veü +i2v3e2x3 +i2v3im +i4v3ind +iv3ins +i4v3int +i2vl +i3vol +i2v3op +ivo3re +iv3ro +i2vue +i2vun +i2v3ur +i2vü +i3wo +iwur2 +i2x3a2 +ix3ele +i3xi +ixt2 +ixta2 +ix2u +i1y +2iz +iza2b +i4z3aen +i2z3ag +iz5anke +iz3anz +i2z3ap +iz3art +iz2as +i2zat +i2z3au +i2z3än +izein4t +izeit5s4 +i3ze2n +i4zentz +izen6zer +izen4zw +i4z5erle +i4z5er4sc +i4z3er4z +izi3na +iz3irr +izo2b +i2z3oe +i2zo2f +i2z3or +i2zö +i4z3una +i2zwe +i3zwec +iz3wic +i2zwo +i2zwö +iz3za +í1 +ï1 +1j +j2a +ja1a +jaal2 +jab2 +ja1c +jacht5s4 +ja3ei +ja3er +ja1f +jaf2l +jag4da +jah4r5ei +jahr4s3 +ja1i +ja3l2a +ja3le +ja3ne +jani3t4 +ja1p +ja3ru +ja3st +2jd +je2a +jean4s +jee2p +je2g +jek4ta +jek4tei +jekten4 +jek6tend +jek4ter +jek4t5in +jektor4 +jek6t5ort +jek4t3r +jek4t3u +je3na3e +je2p +jeri3 +je2s3t +je2t3a +je2t3ä +je2t3e +je2t3h +je4t3in +je4tor +je2t3r +jet3s2 +jet3t +je2t3u2 +ji2v +2j1m +joa1 +jo2b1 +joba2 +job3e +joch3a +jo3che +jo3chu +jo3ck4 +jo2d3a +jo3el +jo3gh +jo2i +jona3t +jong4 +jo2p3 +jo3ra +jor2z +jo2sc +jos3t +josta3 +jou2l +2jt +jty1 +j2u +ju2b3l +jude2 +jugen4 +jugend5 +ju2k +jun4g3a +jungens6 +jun6g5ente +jun4gl +jun4g3r +jung5s4c +jung5s4p +jung5s4t +ju3ni +jun3t +jus3t +ju3te3 +2jv +1ka +ka4ans +ka3ar +2k3abb +kab4bl +2kabd +kabel5a +kabel5i +kabel5o +4k3a4ben +2kabf +2kabg +2kabh +2kabn +2kabs +2k3abt +2kabw +2kabz +ka1c +2k3ace +4k3achs +2k3add +k2ade +kade3i +kadein4 +kade4n3 +2k3adm +ka2dr +2kadv +ka3e2d +k3ae4mi4 +4k3aend +ka3ent +k3aepf +4k3aerg +k3aerz +kae4s3c +ka3esk +4k3affi +ka3fl +ka3fr +kaft4 +k2age +4kagen +k3agg +2k3aho +kaim2 +kai3s4 +kai5se +ka3k2l +4k3akt. +4k3akti +4k3akts +4kala. +kal3ac +kal3a4d +kal3an +kala4s +ka3lat +ka4leb +ka4l3eh +kal5eis +kale4n +kalen6du +kalen6se +kal5erei +ka4l5erf +ka4l5er5i +kal5ins +kal4kan +kal4kle +kal4klo +kalk3r +kal4kri +kal4kru +4k3allt +ka4l3on +ka4lop +kalori6e +ka4l3os +4kalp. +k3altä +kal4t5ex +kal4th +kal4tre +kalts4 +ka2lu +ka4mat +kamp6f5en +kamp6f5er6f +kamp6ferp +4k3amte +ka4n3an +ka4n3a4s +ka4nau +4kanda +kandal5a +4kandä +4kandin +4k3andr +ka4nel +ka4n5ent +4kanfä +k4anfu +k4anfü +kani5er. +4kanim +kank2 +4k3a4nom +4k3a4nor +k2anp +kan3s2 +4k3ansä +4k3ansi +6k5antenn +ka3num +kanu3s4 +ka2o1 +ka4p3ho +k3appa +kap3st +4k3arbe +k4arbr +2karc +k2ard +kard4a +4k3area +ka4r5ent +k2arg +k2arh +ka3ri +kari5es +k2ark +2karm +k3arm. +k3arme +k3arms +ka3rö +k2arp +kar4pf +k2ars +k3arsc +k4arta +k3arti +4kartig +4kartik +k4arto +kart3s4 +k2aru +kar3u4f +ka3rum +ka3rü +k2arw +3karz +4karzt +ka4schm +ka5s4e3i +kasi5er. +kas3s4a +kassen5 +k5assis +kasso3 +4k3ast. +kas3ta +kas3te +4k5aster +kas5til +kas6tras +ka3sz +ka4t3a4b +kat5akt +kat5ans +k2ate +ka4tea +ka4tent +ka4tep +ka4t5erk +kat2h +ka3tha +k2ato +ka4t3ri +kat3t4 +k4atz +k2au. +k4aue +kau4eri +kau4f3a +kau4f5er +k5auffa +k5aufga +4k5aufla +k5aufli +kau4f3o +kauf6s5ag +kauf6s5en +kauf4sp +kau4fu +3k4aum +ka3umr +k4aus. +4k3ausf +4k3ausg +k3ausk +4k3auss +kaussen6 +4k3ausw +kau3t2 +4kauto +kauto4r +1kä +3käf +k1äh +2k3ä2mi2 +2k3änd +2k3äpf +2k3ärg +2k3ärz +kä2s3c +2kb2 +k3ba +k3be +kbo2n +kby2 +2k1c +k3ch2 +kcre2 +2kd2 +kdar4m +kdi2e +keb2 +ke4ben +2k1ec +2kedi +ke4dit +ke3er +2k3eff +kefi2 +kege4la +kegel5e +k3egg +ke2gl +ke4he. +ke4hen +kehl3s4 +2keho +kehr3a +keh6rert +keh6r5er6z +kehr4s5o +kehr6s5po +kehr6s5tur +2kehu +2k3eic +2k3eig +kei4l3i +keils4 +keil5ste +kei4ma +keim3p +2k3ein +keinen4 +4k3eise +keit2 +keits5c +ke4l3ab +ke4l3a4c +kel3ae +ke4laen +ke4l3am +ke4l3an +ke4l3as +ke4l3at +ke4l3au +ke2lä +kel3än +keld2 +ke4l5eim +ke4l5ein +kel5eis +4k3e4lek +ke4l3en +kelen5e +ke4l3er +kelgene6 +ke4l5inf +kel5ins +kel5int +ke4lio +ke2l3o2 +ke2lö +kel3öf +kelsch4 +kel3s4k +kel3sl +kel5s6tern +k3emb +2k3e2mi +2k3emp +ke2na +kena4b +kena4g +ken3au +ke2nä +4k3endg +kend4r +kend4s +ken5dsc +4kendst +ke4n3e4b +ke4nel +ken5end +ke4n5en5e +ken5eng +ke4n5ens +ken5ent +ke4n5enz +ke4nep +ken5erk +ken5er4n +ke4n5ero +ken5erö +ken5ers +ke4n5est +ken3e4t +ken3f2 +keng2 +4ken4gag +k5en6gel. +k5en6gels +ke4nim +ken3in +kenk2 +4kenlad +4kenläd +ken3ne +ke2no2 +ken3om +kensch4 +4k5en4sem +kens6tei +kens6temp +4k5en4te. +ken6ten. +ken6tene +ken6tenp +4k3entf +4k3entg +ken3t4h +4k3entl +4k3ents +4kentw +4kentz +ke1o2 +ke3pf +ke2pl +k3e2po +ker3ae +ker3ak +ker5all +ker5alt +ke4rap +ker3as +ke2r3ä +k5erbit +4kerde +ke4r3e4b +ker5eck +ker3ef +k5ereign +ke4rein +k5e4remi +ke4r5end +ker5ens +ke4r5ent +ke4r5erd +ke4r5ers +kere4z +ker4fas +4kerfol +ker6folg. +ker6folgen +4k5erfül +ker6gebn +kergene6 +4kergu +ker6hoehu +k5er6höhu +ke6r5in6nu +kerin6st +kerin6te +kerk2 +k5erlang +ker6langu +k5erlau +ker6lebn +ker5nab +ker4nar +kerner5 +ker6nere +ker6ners +ker6n5eur +k5er4nie +ker3o4b +ke5rode +4k5erodi +ker3oe +k4eror +2k3erö +kersau4 +ker6schü +kert2 +ker4tue +k3er4tü +ke4ruh +4k3erz. +4k5erzes +k5erzeugu +4kerzie +ker6zieh +ke4san +ke4sel +2kesh +ke3sha +ke4spa +kes4pe +4k3es4sa +kess4p +ke3sta +ke2s3u +ke4t3a4b +ke4t3an +ke4t3au +kete2 +ke4t3eb +ket3ei +ke4t3el +keten3 +ke4t3er +2ke2th +ke2to +ke4t3re +kets2 +ket4tau +ket6t5erz +ket4t3h +ket4to +2keud +2keup +2k3e2va +2k1e2x1 +2k1f2 +kfen2 +k3fi +kfi4le +kfin4s +k3fu +2kg2 +kgas5ta +kgeb2 +kgespen6 +k3gl +k3go +2k1h2 +khau6sen +k3he +ki1a +ki2ad +ki2ag +kia2k +kia2r +ki1c +2kicl +ki3dr +2ki2dy +kiefe4 +kiel3a +kie4lei +kie4l3i +kie4l3o +kierer6geb +4ki3ern +kie4sei +kie4so +kie4s3p +kiess4 +kie4ze +ki3f2l +ki3f2r +kik2 +2kila +kilan4 +2kilä +2kile +2kili +4k3illu +3kil4o3 +ki3m4o +2k3imp +ki3nad +kin3al +ki2nä +4k5index +4k3indi +4k3indo +4k3indu +2k3inf +kin4g3a +kin6g5ent +kin4g3r +2kinh +kini3k4 +kini3s +k3ink +kino3 +kinos4 +4k3inse +kin3sh +4k3insp +4k3inst +2k3int +ki1o +2kior +kio2s +kio2v +kip4pr +3kirc +kir6scha +ki3s2h +2k3i2so +2ki3s2p +kissens6 +ki3sta +ki3s4to +kit6schl +kit6sch5w +2kiv +2kiz +2kj +k3jo +2k1k2 +kkab2 +kka3la +kka3lö +kking3 +k3kn +kko3ef +kk4or +kkue3b +kku3s +kkü3b +kl2 +2kl. +2k3la. +2k3laa +2k3lac +kl4ae +4k3laed +4k3laeh +k3laeu +2k3lal +kla5me +kl4an +4kland +klan4ga +klan6ger +klan4g5r +4klat. +klaue4 +k3lauf +4k3laug +5klavie +2kläd +2k3läh +kl4är +2k3lät +2k3läu +2k3le. +kle5ar. +kle5ari +k2leb +kle4ba +kle4b3r +2kled +k2lee +4kleer +k3leg +2k3leh +4k3leib +k4leid +klei4er +kleinen6 +4k3leit +k3lek +k2lem +k3lem. +kle2o +kl4ep +4klero +kles3t +k2let +kle3us +2k3lex +4klich +k2lid +k4lien +k2lif +2k3lig +k2lim +4klin. +k4li3na +4klind +k4lino +k2lip +k2lir +k4lisc +4klist +2k3liz +k2lo. +k2lob +2kloc +4kloec +4kloef +k4loest +k4loet +2klog +2kloh +2k3lok +k2lom +k2lop +klor4 +4klose. +4klosem +4klosen +4kloser +4kloses +klos4s5t +k4lost +4klot. +4klote +4klots +2klöc +2klöf +2klöh +k4löst +k2löt +k1lu +3klu2b3 +k2lud +k2lue +4k3luec +k2lug +k2lum +klungs5 +klus5s4 +2klux +2k3lüc +2kly +2k1m +k3ma +kma4li +kmes4st +kmi2t +1kn2 +4k3nach +2k3nad +k3naep +2knah +2k3nam +k3napf +kn4ar +2knav +k3naz +2knäp +kne4bl +kne3e +knen4a +2knes +kne4ta +kne4t3r +4knetz +2kneu +2kney +k2ni +kni2e +4k3nier +knies4 +4k3nis. +4kniss +2k3niv +k2no +kno4b3l +4knorm +kno2s +2k3nov +2k3num +1ko +ko3ad +kobal4 +kobalt5 +ko4beli +kober4e +2k3obj +kob3la +kob4sk +ko3che +koch3i +ko3edu +k5oeffn +ko3eli +ko3erh +ko3erk +ko3erm +k3oert +ko3ev +ko4fen +koff6e5i +4k3offi +koh3lu +ko3hö +2k3ohr +ko1i +koka3s4 +kol4a +ko5lau +kol3c +kol2k3 +kol4l5e4b +kol4lei +kol6lerw +ko3lo +2k3o2ly +ko4m3er +2komn +ko4mp +ko4mun +ko4nau +kone2 +ko4nel +ko4nem +ko4nod +kons4 +kon5su +kon4tae +kon4tä +kon4t3h +ko2nu +koo2 +ko3on +2kop. +ko4pal +ko4p3an +2kopä +ko4pf +kopf3a4 +kop4f5en +kopf5erk +kop6f5err +kopf3l +2kopg +ko4p3he +kopi4e +2kopk +ko4pli +2kopm +3kopp +ko4p3re +2kop3s2 +ko3pte4 +2kopv +2kopz +ko3r2a +kor4an +kor4bar +kor4b5er +kor4bla +kor4b3r +2k3orc +kor4dar +kor6d5erfo +kor6der6g +kor6d5erw +kor4dr +ko4r3el +2k3org +kor4k3a +kor4ker +kor4k3r +kormo3 +kor4nac +kor4n5ae +kor4n3ä +kor4n5e4b +kor4n3o4 +ko4rob +4korpi +k3ortu +ko3r4u +k2os. +ko4scr +kos4pr +ko2st +ko5stei +kos6t5er6z +kot4a +ko4t5ent +kot5erb +ko3t4i +koti4n3 +kot3s2 +kot4tak +kot4tan +kot4th +3kow +2k1ox +1kö +kö4res +2k3ört +2k1p2 +k3pa +kpi2t3 +k3pl +2kq +k3qu +1kr2 +2k3rad +kra4da +kra4dei +4k3raed +k5raeum +2k3rah +k5raket +kr4am +kr4an +4k3rand +krang4 +5krankh +kran5s +k3rats +kr4au +k3raub +4k3rauc +4k3raum +2kräd +k3räte +k3räub +4k3räum +2k3re. +4kreak +4k3real +k4reat +k2reb +kreb4sc +kreb4s5o +2k3rec +4kred. +4krede +5kredi +4kredn +4kredu +2kref +2k3reg +4kreib +4kreic +kre3i4e4 +kreier4 +4k3reif +4k3reih +4k3reim +4krein +krei6s5ei +krei6s5in +kreis5t +k2rem +krem4l3 +k2reo +4krese +4krest +4kresu +k2reu +2k1rh +2k3ric +4k3rieb +5kriec +4k3ries +k3rind +4k5rings +4k3risi +kro3a4d +krob4 +krobe4w +5kroch +kro3e4b +4k3roeh +kro3el +4k3rohs +2k3röh +2k3ruc +krue3b +2kruf +kru4fer +krum4ma +4k3rump +2k3run +krü3b +2k3rüs +k2ry +2ks +ksa2b +ksab3a +k4s3aeh +ks3aem +k3saen +ksa2f +k2sa2k +k4s3amt +k2san +ks3ang +ks5anka +ks5ankä +k5sanke +ks3ant +k2sap +ksa2r +k2s3a2s +ksau4e +k4s3au4f3 +ksau4g +ksau4re +k4saut +k2s3a2v +k2säh +ks2än +k2sce +ks2ch2 +ksche4f +k2s3cr +k2s3e2b +k2s3e2c +k3s2ed +ks3eie +ks4eif +k4s5eind +k4s5einf +k4s3e4le +ks3elf +ks4end +k4s3eng +ks4enk +ks5ense +k4s3en4t +k4s3erf +k4s3erg +k4s3erh +k4s3erk +k4s3erl +k4s3erp +k4s3err +k4s3ers +k4s3ert +k4s3erw +k4s3erz +kse2t +ks3eta +k2s3e2v +k2sex4 +kshi2 +k2s3i2d +ksi2n +k5sinf +ks3ing +ks3int +k2s3is +kso2b +ks3oe +ks3ofe +k3son +k2s3op +kso2r +ks3org +ks3ori +k4s5ort. +k3spaz +k3s4pec +k3spek +k3s4pel +ks4pes +k4sprä +k3spri +k4spro +k3spru +k3s4pul +ks3s2 +k4st. +k3stab +k3stac +k3stad +k4stag +k5stall +ks5tanz +k3s4tar +kstat4 +ks5tat. +kstatt5 +k3stau +k3stec +k3stei +k4steil +k3stel +k3s4tep +k5stere +ks4tern +k3steu +k3s4tew +k3stic +k5s4tieg +ks4tiel +k5s4ties +k3stil +k3sto +k4stod +ks4toe +ks4tol +k4ston +ks4top +k4stot +ks2t2r +k6straen +k4strän +k4s5tres +ks5troc +kstro4m5 +k4strop +k3s4tub +k5s4tuec +k5stuer +k4s3tüt +k4s3um. +ks3umb +k4sumf +k4s3ums +k2s3un +k2s3ur +2kt +kt3abr +k2t3ad +kt3akr +k4t3alg +kt3al4l +ktal3s4 +kta2m +k4t3amo +kta2n +kt3ana +k4t3ang +kt3ano +kt3ans +kt3a4re +kt3arm +kt3ars +kt3art +kt3arw +k5tasc +kt3asp +kt3atl +k4t3auf +kt5aust +ktauto4 +k3te. +kt3eff +kt3ein +k4t3e4la +k4telem +k3te2n +kten3e +ktens4 +k4t5entf +k4tentl +k4t5ents +k4tentw +kten3z +kt3e4pi +kte5r4an +kter5ei +k4t3erh +kte4rik +kter5oe +k4t3erö +k4t3e4ti +ktgan4 +ktge3n4 +kt1h +k4thel +k2thi +k2tho +k2thy +k2t3i2d +kti4me +k4t3ind +kt3ini +k4t3ink +kt5insa +kt5inse +kti4s3e +k4t3iso +kt5i4ter +ktiven6d +k4t3oef +kt3of +kto4n3a +ktop3a +kto4rei +k4torga +ktorgene6 +kt3ott +k4t3ram +k4t3ras +k4t3rau +kt3räu +kt3rec +kt5reic +kt2ro +ktro3e +ktro3s +kt3rue +k4t3run +k2t3rü +kt1s2 +kt3s4a +kt5sae +kt3sä +kt3se +ktsen3 +kt4s5ene +kt2sk +kts5org +kttei4 +k4tunf +k4t5ungl +k4tuns +k2tup +ktu4ran +ktu4ras +ktu4rei +ktu4rer +ktur3z +ktz2 +1ku +ku4al +ku3an +ku1c +kuck2 +kue5bel +ku3en. +kuen4s +kuer4s +ku3fa +kuf3s +kuge4l5a +kugel5e +ku2gl +ku2h1 +kuhle4 +2k3uhr +ku3in +kul2a +kulan4z +ku3li +2k3ulm +2kulp +kul4t3o +kul4t3r +k3umba +2k3umd +ku3m4e +2kumg +k2umk +2kuml +kum4san +k3umsc +kum4sei +kum4si +kum4sp +k3umw +2kumz +kun5d4a +kunde5i +kundein6 +k3une +k5ungeb +k3unk +2kunm +kun4s4 +kunst3 +2kunt +2kunw +2k3up. +ku3pfl +kup3s2 +2kupu +kur5ans +kur4bl +kur5ein +2k3url +ku2r3o +kurr2 +kurs5an +kur4sau +kur4s5ei +kur4ser +kur4sin +kur6s5or6te +kur4s3p +kur4ste +kur4str +kur5tsc +kur4zel +kurzer6k +ku4sak +kusa4r +ku4schl +ku4s3el +ku4s3er3 +kus3ev +ku2sp +kus3pi +kuss4t +ku2su +2kut. +kut4a +kut3ei +kut6schw +1kü +kü3bel +2kübl +kü1c +kü2li +kür2s +2kv +k3ve +2kw +k1ya +ky3la +2kz2 +kze5le +kz4en +kzeu4g5i +k3zu +kzue4r3 +1la. +1laa +la3ar +la3at +la3ba +la4bad +3labä +2labb +lab4bri +2labd +4l3aben +la4ber4z +2labf +2labg +2labh +l3a4b3i4t +2la2bl +2labn +l3a4bo. +l3abon +l4abor +2l3abp +la3b4ra +lab4res +4labruf +2labs +3labs. +4labtei +la4bus +2labw +2labz +la3ceb +la3cel +lacher4 +la6ch5erf +lachste6 +lachter6f +lach6terp +lach4t5o +la5chun +3lack. +lacks4 +l3ada +2ladd +3ladem +la4des. +4ladj +2l3adl +l3adop +2l3a2dr +3ladu +2ladv +la2e +l4aed +2laef +3laehm +4l3aehn +3laemp +l3aemt +4l3aepf +2laeq +l3aerz +lae4sc +3laeuf +4l3aeug +lae3um +2l3aex +la4f3ae +la2f3ä +la4f3ei +la4f3er +2laff +l4afi +laf3ra +laf3s2 +2laf3t4 +la2g3a2 +lag5eis +lagen5d +lag6ers +2lagg +la4gil +la4gin +4l3a4gio +la2g3l +la2g3n +lago2 +la4gob +la4gop +lag5s6eid +lag5stan +lag5stei +lag5sto +1lah +1lai +1laj +4l3a4kad +la4k3ar +l4ake +la4kes +la4kin +3l4akka +l4ako +la4kro +l3akt. +l3akte +l3akts +2l1al +3lala. +lal3ab +3l2ali +lam2a +3l4amä +2lamb +la4mei +lam4en +lament4 +la4mer +lam4m3a +3lammf +2l3amn +2lamo +la4mor +l4ampe +4l3ampl +l3ampu +lam3s +2l3amt +lamt4s +3lamu +la4mun +la4nae +4l3anal +4l3a4na3t +la4nau +2la2nä +3land +landa4d +lan4dam +lan4dan +lan4d5au +landei6g +lan6d5en6t +lan6d5erh +lan6d5ers +lan6d5erw +lan6d5erz +lan6ding +lan6d5inn +lan4dob +lan4dra +lan4d5ru +lan4d5rü +4l3anek +lanel4 +l3anem +lan5ente +lan5erd +la4n5er4f +la6n5erst +4langa +lan4gae +lan4gan +lan4gar +lan4gat +lan4gä +4langeb +lan4gei +6l5angel. +6l5angeln +lan6gerl +lan6g5ers +lan4g3i +4langri +langs4 +lang5sa +lang5sc +lang5se +lang5si +lang5sp +lang5ste +4lanhä +4lanim +4lanke +lan4k3l +2lanl +2lann +4la4nod +la4nof +la4nor +l4anpu +l4anro +2lans +3lanse +3lansw +l4ant. +lan4tan +l5antig +4lantw +lan3um +2lanw +l4anwar +l4anwer +3lanx +lan4zel +lan6z5erf +lan6zerg +lan6z5erh +lan4zin +lan4zw +2l3apf +la2ph +2l3a2po +4lappa +lap4pl +l4ar. +la4r3ab +la4r3ac +la4r3a4g +la4r3an +la4rau +4larbei +4l3area +lar3e4b +la4r3ei +la4r3el +lar3g +lar5ini +lar5ins +l3arkt +4larm. +4larmat +lar4mau +lar4mei +l5armem +l5armer +4larms +4l3a4rom +3larp +lar6schl +lar4ter +l3arti +lar3tu +3l4aru +3l4arv +lar5za +4larzn +lar3zw +la4s3aa +la4sae +la4sam +la4san +la4sas +la2sä +lasche4 +la4s3ei +la5ser +la6s5er6ke +2lasi +la4sin +la4sis +la4sor +lass5er6kl +5lassern +5lassers +las5sto +lass4tr +3lassu +la2st +last5ae +last5an +last5ar +last3ä +last5ein +4lastiz +las3to +la4stu +3lastw +3lastz +l4at. +l4ata +lat5ang +la4tar +3l4atä +la3t4e +3late. +l3a4tel +late4n +3later +late5ra +3latè +2lath +la4ther +la3t4i +2latl +l3atla +l3atmo +la4toe +la4tol +4l3atom +la2tö +la4t3ra4 +la4tro +lat6schn +lat6schw +4latta +lat4tak +lat4t5an +lat4teb +lat6t5end +lat4tex +lat4t5in +4lattr +lat4t5ro +lat4zei +lat4zer4 +3laub. +lauben6s5 +lau4bla +lau4bra +lau4bre +3laubs +laub4se +lauch5a +l4auer +lau4f3a +lau4fel +lau4fer +lau5fer. +lauf5ere +lau4fin +lau4fo +lauf5s4t +laug4l +l4aui +2lauk +3l4aun +4laun. +l4aur +l4aus. +lau6scha +lau6schl +4lausd +4l3ausf +4l3ausg +4lausr +4l3auss +laust4 +4lausw +4l3ausz +lau4tae +4lauto +lauto6ri +lave2 +laven3 +1law +lawa2 +2lawe +1lax +la4xel +lay3s +lä1c +1läd +2läf +2lägy +3lähm +2l3ähn +3lämp +2l3ämt +lände5s +2l3äpf +2läq +2lära +lär4mar +lär4mat +lär4mei +lär4m5em +lär4mun +lär4st +2l3ärz +lä2sc +4läser +3läuf +2läug +2läx +1là +2lb +l4babw +lb3alp +lb3ana +lb3ang +l4b3ant +lb3arb +lb3a4ri +lb3ärm +lbb2 +lbby2 +lbei6ge. +l4b5eise +l4b3elf +lbengene6 +lber4ei +lbe4rem +lbe4ren +lbe4ris +lbes2 +lbe4tag +lbe4tap +l2b3i2d +lb3lag +lb3las +l4bled +lb4len +lb2lö +lb3oef +lb3oel +l2bof +l4b3ora +l2b3ov +l2böl +l4brea +lb5ries +lb4s3au +lbs4t +lbsta4 +lb4stab +lb4st5ac +lb6staende +lb4stam +lb4stan +lb4stau +lb6stände +lbst5er +lb4str +lb2u +lbuch5s +lbu2n +l4b3une +lby2t +2lc +l1ce +l4ch. +lch3al4 +lch5arm +lch3ei +lche4n +l5chen. +lch5erh +lch5ern +lch5ersa +lch5ert +lch5erz +lch3eu +l3chi +lch3la +lch3lä +lch3li +lch3n +lch3ob +lch3re +lch3sh +lch3sn +lch3s4t +l2ch3u +lch3w +l1ci +l2ck +l1cl +l1cr +2ld +l2da2b3 +l2da2d +ld3aef +ld3aer +ld3aes +ld3aet +l2daf +l2da2g +l2d3ah +l2d3ak +l2d3al +ld3ams +l2d3a2n +ld3arb +ld3arr +ld3art +l4d3ass +ldas4t +ld3asy +ld3ato +ld3att +ldau5sta +l3däm +ld3är +ld3äs +ld3ät +ld1c +lde4ben +ld5eck. +ldefen4 +l2d3e4i +ld5elef +l4d3elf +ld5e4lit +l4d3emb +ld5e4mis +l4d3emp +l4d3ems +lde4ni +l4dentl +l4d5ents +l4d3enz +lde5rae +l6d5er6fah +lder6gebn +l6d5erlas +lder3r +lde4sel +lde5skr +ldes4sa +lde5sta +lde5stel +lde5str +ld3eur +l2d3ex +ldhof5s +ldi2a +ldi3ae +l4d3ide +ldi2e +ldien4 +ld3ill +ld3i4mi +ld3inf +l4d3ink +l4d3ins +l4d3ion +ld3iso +ldo4br +l2d3oh +l4donk +ldo2p +l4d3ope +l4dopt +ldo2r +l4d3ori +ld4os +ldos3t +ld3rab +ld5raen +l4draeu +ld3rag +ld3ral +ld3ras +ld3rat +ld3rau +l4d3rän +l4drec +ld3rie +ld3rob +ld3rod +ld3roe +l4d3rol +ld3rom +l2drö +ld3run +ld3rüc +lds5amt +lds4kl +ld3sko +lds2p +ld5stel +lds4to +ld3tu +ldu2 +l4dueb +ld3ufe +ldu3i +l2d3ul +l2d3um +l2düb +ldwa3r +1le +le2ad +le4an. +le4anm +le2as +le3atl +leauto4 +le2av +leben4s +lebens5e +le5ber +2lec +lech5au +le4ch5ec +le6chens +le4chi +lech5oe +lech5o4f +leck5se +3leco +ledi4e +le4dit +le2dr +3leer +lee4ret +le3f4a +2l3eff +lef2l +lef4o +le4gabt +le4g3ae +le4gang +le4g3ar +leg5ast +le4gau +le2gä +lege3s +le4gesl +4l5egge. +4l5eggen +legi5en +le2gl +le2g3n +leg2r +legs2 +leg5sta +leg5ste +leg5str +4lehe. +3lehm +leh6mest +3lehn +3lehr +lehr3a +leh4rei +lehr3o4 +le4id +lei3dr +lei5eri +2leif +lei4fan +lei4fei +leifen6s +lei6fer6g +4l5eigen +3leih +lei4hau +leil4 +le4im +lei4mau +4leimb +4leimh +leim3p +le5impo +l4ein. +4leindr +lei5nec +lei6nerb +lei6nerk +lein5erz +6l5einfal +4leinn +4leinsa +4leinto +4leintö +4leinwe +2leir +lei6sch5a +lei6schei +lei6sch5l +lei6schn +lei6scho +lei6schw +6leisens +lei6s5ern +4leisf +4leiss +leis6s5er6 +lei5stro +4leisz +2leiß +lei4ß5er4 +lei4tae +lei4t5a4g +lei4t5ak +lei4tal +lei4tei +leit5er6kr +lei4t3o +leit5sk +leit5s4p +leits4t +2leiw +2l3ek3s +4lektr +2lekz +3lela +le4l3ab +le4l3ac +le4l3au +le4lef +lel5ein +4l3e4lek +4leleme +leler4 +lel5erd +lel5erk +lel5ers +lel5erz +le4lev +lel3ex +4l3elf. +4l3elfe +le4lim +le4l3in +le4lor +lem2a +lem5anz +le4mau +lem5ein +lem4en +lem5erf +lem5erk +3lemes +3lemet +le4m3id +le5mil +4lemmt +le4m3o4r +l3e4mot +2lemp +lemt2 +le2mu +l3emul +l4en. +lena4b +lena4d +le4nan +le4n3a4t +le2nä +lende4s +4lendl +4len4dog +l3endp +4lendun +le4n3e4b +len3ec +le4n3ed +le4nel +lenen5erg +le4n5eng +4l5energ +le4n5erk +len5erl +len5ero +len5ersc +len5ert +le4nerw +le4net +le4n5eur +4leneuv +len3f2 +4l3eng. +4len4gag +4l5enge. +len6gem. +len6ges. +4lengp +leng4r +len6k5lag +len6k5lei +len6klin +len4kob +4lenniu +le4n3on +le4n3or +le4n5ott +len6s5ein +4lensem +lens4po +6l5enteig +l5entfe +4lentla +4lentlu +4lentlü +4l3entn +6l5en6trop +4l5entrü +4lent4wa +4lent4wä +4lentwi +4lentz +lent6zug +5lentzuw +le4nz +len5z6er +len4zin +len4zun +len3zw +lenz6wei +lenz4wo +4l3enzy +le3o2f +le3o2s +le4pen +3le3p2f +2l3e2pi +4l3epoc +l4era +le3ra. +ler5abi +ler5aeu +ler3a4g +ler3al +ler3a4s +lerauto6r +ler3az +le2r3ä +4l5erbe. +4l5erben +ler6blin +4l3erbs +4l3erbt +le4r3e4b +lere3c +ler3ef +ler5eigen. +6l5ereign +le4r5ei4m +ler5eis. +le4r3el +le4remi +le4r5eng +le4r5ens +le4r5en4t +ler5er5e +le4rerf +le4r5erg +le4r5ers +le5res +le4r3e4t +7lergenau +7lergeni +l3ergi +4l5ergol +4l5er4heb +l4eri +le4r3i4d +leri4e +lerin4d +lerin4g +lerin4s +lerk2 +4l5erlas +6l5erlaub +6lerlebn +4l5er4leu +ler4mäs +ler4mäß +ler4nal +ler4nar +ler4nau +lerner4 +ler3o4b +ler3of +le3ros +4l3erot +4l5eröff +ler6regu +4l5er4ric +ler6rung +ler4set +lers4k +ler6s5tur +ler3sw +ler3t +6l5ertrag +leru2 +le4rup +4lerus +le4r3ut +l4erwec +6l5erweit +4l3erz. +4l5erzeu +4l3erzg +4l3er4zo +le4s3al +lese5in4 +lese5ras +lese5s4k +les5ess +l4e3s4h +lesi3e +le3s4ki +le3sko +les4kop +4lessi +lester6i +4l3estl +lestra4 +le3stu +2lesw +2lesy +4l3e4tap +4l3e4tat +4lethi +4lethn +lets2 +letsche6 +let4tau +let4tel +let4top +lett3r +lett3s4 +2leue +le3u2f +leu4ka +2l3eul +le3umb +le3u4m3i +le3umo +le3ums +3leunt +le3url +4l3euro +3leut +4l3e4val +4leve. +l3e4vol +2lex +3lexik +le4xis +lext2 +2l1f +lfa5chu +lf3aka +lfang3 +lfan4gr +lf2at +lf3aus +l2fec +lfe3e +lfein4f +lf5einh +l4feis +l4f5erei +lfe4rel +lferin6d +lfe5sta +lfe5str +l2f3im +l2fj +l3f2jo +lf2la +lf3län +lf2lö +lfpi2 +lf2r +lf3ram +lf3run +lf2sa +lfs3au +lf2sä +lf4spe +lf4s3ti +lf2su +2lg +l3ga +lgär4s +lge3in4 +lgene6ri +lge6ners +lgens4 +lge4rab +lge3u +lgge3 +l3g2h +lgi3en +lgin4s +l4g3lam +l3g2le +lg2lö +l3go +lg4san +lg4sau +lg2se +2l1h +lhauser6 +lhe3x +3lhi. +lhu3s2 +1li +li3abe +li3ac +li3ak +li3an4g +li3ato +liär2 +3lib2 +libi3e +li3ce +4lichet +lich3r +lich4ta +lich4to +lichts4 +licht5sp +2lick +l2id +lid4a +li4dea +4l3idee +lider4 +li4d5ers +lidi4e +l3i4dio +li3dri +3lie. +lie4ba +liebe4s5 +lie4br +li3e4f3a +5liefer +li3e4fi +li3efk +li3efl +li3efm +li3efp +lie4fr +li3em +lie4na4 +liene4b +liene4v +lie4no +lie4r5ei +li5erstu +lie4s3c +lie4s5t +li3f2l +li3f2r +3lift +lif4tu +lig5ang +liga3s +li4gatt +4l3i4gel +lige4n +li4g5er4s +li4gin +li2g3l +lign4 +lig4ne +li4g5rad +li4gre +li4gro +ligs4c +ligs4p +lig5str +li3ker +lik3l +lik4li +lik4spr +lik4tau +lik4ter +lik4th +lik4t3i +likto4 +lik4t5or +lik2u2 +li3la +li3li +2l3ill +li3m4a +lima3c +lima3e +lima3s +2limm +limma4 +3limo +2limp +lin5all +lin3a4m +lin3an +lin4d5ei +lin4del +4lindex +4l3indi +4l3indu +li4nef +li4neh +li4nep +line3s +lin4gl +2l3inh +3l4inhe +4linik +lin5iti +2linj +lin4kar +lin4k5au +link3l +lin4kla +4l5inner +li4n3ol +lins2 +4l5insel +4linspe +4l3insz +2lint +3lintu +3lintü +li3n4um +li2o +li3oe +li3ons +li4onse +lio3r +lip3he +3lipt4 +li4rak +li4ran +2lirr +lir4t3r +3lis. +li3s2a +li4sau +li4schl +li5schm +li4schn +li4sch5r +li4schu +lis2h +li3shi +2l3isl +4l3isol +4lisot +liss2 +lis3sa +li2st +lis4tra +l2it +lit2a +li4t3a4d +li4t3ak +li4tal +lit5all +li3t4e +lite3i +litein4 +lite5rar +lit2h +3lito +li4tri +li4tro +lit3s2 +li2tu +li3tus +lit4za +lit4zi +lit4zu +li4vea +live4n +2livg +livi3e +2lixi +li4z3ae +li4z3an +li2z3ä +lizei5t +li2zo +2lj +2l1k +lk2a +l4k3abl +l4k3aeh +l4k3alg +l4k3alp +lka4n5er +lka4n5in +lk3arm +l2käh +l4k3eif +lkens4 +lke3re +l3kla +l4k3lad +l3kle +lklore5 +l2knu +lko2f +lk3ofe +lkraf4 +l4k3rob +lk3roc +lks1 +lk2sa +lk3sän +lk2se +lk3s4ke +lk2su +lkue3b +lk2ü +lkü3b +2l1l +ll3abb +lla4ben +l4labk +ll3abl +l4l5abri +l4l3abt +ll3aff +ll3aft +lla3ga +l6l5a6gent +lla3gl +l4l3a4ma +ll3amb +ll3amo +ll4anb +lla6nent +lla4ner +ll3ann +lla4no +l5lans. +ll5ansä +ll5anzü +l4l5appl +l4larc +lla3rö +ll3atl +l4l3atm +ll5attr +l4l5auff +l4l5aufg +l6l5aufko +ll5aufl +ll5aufr +ll5aufsi +ll5aufst +ll3auk +llau4m +ll3aus +l4lausk +lla2x +ll3axt +llb2 +ll3c +ll3d2 +lld4e +ll5ebene +ll3ech +lle3er +lle6gest +ll3eif +l4l3eig +ll5einsa +l4l5eise +lle4la +lle4lei +lle4ler +l4l3e4mi +ll3emp +lle4na +ll5endl +ll5en6dun +lle6nens +lleng4 +llen6ge. +lle4n5in +llenk4 +llen6sem +l4l5entf +l4l5entg +l4l5entk +l4l5ents +l4lentw +llen4zy +lle4r3a +ller5eis +l6l5er6fas +ller6geb +l6l5ergen +l4lergi +llerin6f +llerin6t +ller6kla +ller6klä +ller6kun +ller6laub +ller6müd +l4l5ernt +l4l5eroe +l4l3er4ö +ll6ers. +lle4ses +lle4spo +lle4th +llet5he +llet6terf +ll3ex3e +ll3ext +llf2 +llg2 +ll3ide +ll3ido +lli4gan +lli4go +l4l3imb +ll3imp +l4l3inf +llings5 +ll3inj +l4l3inl +l4l5inst +ll3int +lli5on. +llk2 +lll4a +ll3m2 +lln2 +llnes4 +l4l3obe +ll3obs +lloe6sen +llo4ner +l4l3ope +ll3opf +l4l3orb +l4l3ord +l4l3o4ri +ll3ort +l4l3osz +l2l3ou +ll3ox +llö4sen +llrue4c +llrü4c +lls5amte +llsau4e +ll4s5en5e +ll4s3es +llsgene6ra +lls5int +ll3sla +ll3sli +ll5s6temp +ll3sti +ll4stor +ll5szen +llti4m +ll3umm +ll3urg +ll3urn +ll3urs +llus3e +llus5t4r +l2lüb +llz2 +2l1m +l2m3a2b +l4mada +l4m3aep +lm5aerz +lm5aest +lm3aka +lm3alm +lma2m +lm3ama +l4m3apf +lm3arr +l4m3art +lm5attr +lm5auss +lm5ausw +l2mäp +lm3ärz +lm3äst +lm1c +lmd2 +l2m3e2b +lmeeres6 +l2m3ef +lme2h +lm3ehe +lm5eins +l4m3emu +l4m5ends +lme4no +l6mensem +lmen4zy +l2m3e2p +lmer2 +l4m3er4b +l6m5ereig +l4m3erf +l4m3er4r +l4m3erz +l4messa +l2m3i2d +lm3iko +lm3inh +lm3ins +lm3int +l2m3of +lmo2p +lm3ope +lm3orc +lm1p2 +lm3s2k +lm3s2n +lms2t +lms5tur +lms2z +lmt2 +l2m3uf +l4m3ums +l4munt +l2m3ur +2ln +l1na +ln2ab +lna2r +l4nare +lnd2 +ln2e +l3nei +l2n3in +l1nö +l1nu +lnus2 +l1nü +l1ny +1lo +3lob. +lo3ba +4l3o4ber +lobe4s +2lobj +lo2bl +2l3obm +lo3che +lo4chel +loch3o +lochs4 +lod2g +lodi4e +loe2b +2loed +loe3du +loe2f +l3oefe +l3oehr +2loek +l3oel +2loep +lo3er. +loe4t3a +2loé +l1of +2lofe +lo4fen +2loff +lo3fj +lof2l +loga2 +lo4gar +lo4gei +lo4g3el +lo4g5ers +lo2g3l +lo4gor +lo4gre +logs2 +log3sp +loh4e +lo5her +loh4ner +lohn5erh +lohn5erw +2l3ohr +loh4rer +loi2r +2l3okk +lo2k3r +2l3okt +4l3oliv +2l3o2ly +lo4mel +lom5ing +2lomn +lo4n3al +lo4nau +lon3er +lon4gu +lon4sae +lons4p +4l3onto +lo3ob +lo2op +lo4pal +lo4per +lo3pi +lop4p3a +lop4pr +2lopt +lo4r3am +lo5rand +lo2rä +lor3do +lo3re +lorein4 +lo5r4en +2l3or3g2 +lori4di +lori6en. +lori6eng +lori6enm +2lort2 +l4orti +lo2s3a +lo4ske +2loss +los4su +lo5stei +loste4r5 +4l3osth +lo3sti +lo4stö +los3tr +lo3s4tu +l3oszi +2loß +lot5ein +lo4thr +3loti +loti4o +lotte4n +lot4tr +2lotz +lour4s +2l3ouv +l1ov +2lo2ve +l2ovi +2lox +1lö +lö2b +3löch +2löck +2löd +lö2f +2l3öfe +l3öffn +2l3öhr +2lök +2l1öl +2löp +2löss +l3östl +3lösu +2löß +lö2th +2löz +2l1p +lp2a +lpeng4 +lpete4 +lp2f +lpf4e +l5phas +l4p3hir +l4p3hoe +l4p3hor +l2p3hö +l2p3hu +lpi2e +lpi2p +l4ple. +l3p2n +lp3sta +lp3sti +lptu5r4 +l3pu +2lq +2l1r2 +l3ra +l3re +lre3st +lroe4s3 +lro2h +lrö2s3 +l3ru +lrue3b +lruecker6 +l3rü +lrü3b +lrücker6 +2ls +lsa4b3a +l2s3a2d +l4s5aeus +l4saka +ls3alg +ls3alt +l4s3amp +l4s3ana +l4sanb +l4s3ane +l4sanf +ls3ang +l5sani +l4s3ank +l4sanm +ls3ann +l4sanr +l4sant +l4s3anz +ls4apa +l4sa4ri +ls4ark +ls5assi +l4s3au. +l4s3auf +lsau6ger +lsau4m +lsau6rer +l4s3aus +l4saut +ls4äug +l4s3äus +lsby2 +ls2ca +l6sch5eid +lsch5eint +lsch4le +lsch4lo +l4schmü +l6schore +lschs4 +l4sch5wo +l4sch5wö +l2sco +l2s3e2b +l4s3eie +l4s5eind +ls5einf +l4s5eint +l4s3e4li +l4s3emb +l4s5en4ge +l4s5enzy +l4s3epi +l4s3er3e +l4s3erf +l4s3erg +l4s3erh +l4s3erk +l4s3erl +l4s3ern +ls3err +l4s3ers +lse3r4u +l4s3erw +l4s3erz +lse2t +l4s3eta +l4s3eth +lsex4t4 +l2s3id +l4s3imp +l4s3ind +l5skand +l5s4kele +ls2ky +l3s4law +l3s4nac +l4s3oef +l4s3off +l2s3op +l4s5ort. +l4s5orts +l1sö +l2söf +l3s2öl +l1sp +lspani5 +ls4pare +ls4park +l4sparl +l4späs +ls2pe +l4spiz +l4spoe +ls4pon +ls4por +l2spö +ls5prae +l4s3prä +ls4pul +l4spun +ls3s2 +l3st2a +lstab4 +ls5tabl +l6s5taeti +l4stag +lstahl5 +l4s3tak +l4stalb +l4s5tanz +l4stas +l4s5tat. +l4s5tate +ls5tauc +l4s5täti +l3stea +l5steck +l3steg +l3stei +l4steil +l3stel +l6ster. +l5s6ternb +l5s6terns +ls3tes +l3steu +l3stic +ls4tie +l3stil +l3stim +l3s4tip +l3sto +l4stoch +ls4toe +ls4toi +ls4tol +ls4topf +l3s2tr +l4s5trac +ls5trad +ls5trit +lstro6ma +l3s2tu +l4sumf +l4sumk +l4suml +l4s3ums +ls3umv +ls3unk +2lt +lt3abr +lt5ach. +l3t4a3d +lt3aeh +l4t5aerz +lta3f2 +ltag2 +lt3agr +lt3aka +lt3all +lt3alt +l4t3alu +ltampe4 +lt4anda +ltaner4 +lta6nerf +ltan6gab +ltan6ges +ltan4sa +ltan4sä +lt3a4po +lt3app +l6tarbei +ltar6beitn +ltare4a +lta5rhe +l4t5arma +ltar6me. +ltar6men. +ltar6meni +lt5art. +l5tarta +l5tartei +ltar6tik +l5tartis +l5tartr +l5tartu +lt3asp +lt3atl +l4t3ato +lt5aufb +lt5aufk +lt5auge +l4taut +lt3äh +l4t3ärz +ltbau5c +lt3e4ck +lte3e +l3tehu +lt5eink +lte5iso +l4telem +ltem4pu +l4temul +ltende6s +lten6gag +lten6gel +lten6spo +l4tentl +lten6t5ri +l4t5ents +l4tentw +lte4rat +lter6be. +lter6bel +l5terg +lter6kun +lter6loe +lt5er4lö +lt5er4nä +lter5oe +l4t3esk +lte5sta +lte5stra +lte5stre +lte5stro +lt5etho +lte4tik +lt3eur +l3thas +lt3heb +lt5heri +l2t3hi +l2t3ho +lt2hu +lt3hun +l2t3hy +lt3ide +lti6g5er6k +l4t3ind +lt3inf +l4t3ins +lt3i4ra +lt3i4ri +l4t3iso +l5tist +lti5va +l4t3o4be +l4t3obo +lt3oef +lt3oel +lt3oes +lt3o2f +l3top +l4topt +l4tord +l4t3org +l4t3o4ri +lto2w +l2töl +lt3ös +ltra3l +lt5räuc +lt3reb +lt3rec +lt5reic +lt5reif +l4trig +l4t3ris +l4t3roh +lt3ruc +lt3rum +ltse4l +lt4s5ele +lt5skal +lt4s5ort +lt3s4ph +lt4staf +lt4stie +lts4tre +lt4s5tüt +lt5szen +lt3t +ltu4el +ltu3en +lt3uh +l2tum +lt3ums +ltu4rab +ltu4r5an4 +ltu4ras +ltu4rei +ltu6r5ent +ltu4r5er +ltu4r3i +ltwa3r +ltz2a +1lu. +lu3an +2lub1 +lu2ba2 +lu2bo +lub3s2 +luch4s5a +luchs5t +luch4t5o +luch4t5r +lucht6s5o +lu4cku +lu4de. +lu2dr +2lueb +3luebd +luecke4 +luecker5 +2lueh +lu3ent +lu3enz +lue6scha +lue6schw +1luf +2l3u2fe +2luff +lu3f2r +luf2t +luf4t3a4 +luft3e +luft3r +lu4g3af +lu4gas +lu5ge. +lu4g3e4b +lu4gei +lugen5e +lug5erl +lug5erp +lu2g3i2 +lu2g3l +lu2go +lu2g3r +lug5spo +lug5sta +lug5ste +lug5str +lu2gu +2l1uh +l2u3hu +lu3id. +lu3is. +luka2 +3luke +l3ult +lumbi5e +2l3umd +lume2 +lumen3 +2lumf +2lumg +2l3umh +l3umj +2lumk +2luml +2lumm +lum4p3h +2lumr +l3ums +2l3umw +2lumz +1lun +2l3una +lu3nap +2l3unb +lund3r +2l3unf +l4ung. +5lunge. +4l3ungl +lungsgene6 +2l3u2ni +4lunkr +2l3unl +2lunr +2l3uns +2lunt +2lunv +2lunw +3lupe +lupf3 +lu4r3an +lu4r3at +lu4rau +lurch3 +lu4r3ei +2lurg +lu4ris +l3urk +2l3url +2lurn +luro2 +lu4rof +lu4ror +2lurs +2l3urt +lur3z2 +lu4ser +lu3si +lu3spr +lus4s3a +lus4s3c +lus6sein +lus4s5el +lus4s5er4 +lus4si +lus4s3o +lus4s3p +lus4st +luss5ti +lus4sur +lu2st +lust5ae +lust5ak +lust5ei +lus6t5erf +lus6t5erk +lus6terl +lus4t3o4 +lus4t3u +lu2s3u +lu4t3ae +lu4tal +lu4t3an +lu4t3au +lu2tä +lu4t3eb +lu4t3e4g +lu4t3ei +lu4t3el +lu4t5en4g +lu4t5ent +luter4 +lu4t5erb +lu4t5ere +lu4t5erg +lu6t5ersa +lu4t5erz +lut3ex +lu4thy +lut5ins +2luto +lu4t3ob +lu4toe +lu4t3o4f +lu4top +lu4t3or +lu2t3r +lut3s4a +lut5schl +lut3s4k +lu4t3ur +lut3z +2lübe +2lübu +lücke4 +lücker5 +lügel5e +2lüh +lü4h3e4l +lü4hem +lüh3l +lü2s +lü4scha +2lv +l2veo +lv2l +2lw +2lx +1ly +ly1a2 +ly3c +ly3en +lye2r +2l1yo +ly3on. +ly3onn +lyra3 +lyri3e +ly5sen +ly3sp +ly3th +ly4t3re +2lz +l4z3aer +l2z3ag +l2zak +lz3al +lza2m +l4z3ant +l4z3app +lza2r +lz3aro +l2za2s +l2zat +l4z3aus +l2z3är +l4z3eim +l4z3eis +lze2l +l4z3ele +l4z5enth +lzen6tro +l4zents +l4zentw +l4z3enz +lze2p +l4z3er4h +l4z3er4m +lze3ro +lz5ersa +lz5er4tr +l4z3er4z +lze2t +lz3eta +lzeugin6 +l2z3id +lz3iko +lzi2m +lz3imi +l4z3ind +lz3inf +lz3ing +l4z5inst +lz3int +lz3l +lzo2b +lz3oef +l2zo2f +l2z3or +l2z3oz +lz5rei +lzt2 +lz3tha +l4z3u4fe +lzug4s +lzvol4 +l4zwar +l2zwo +l2zwö +l2zwu +1ma +ma3ala +maa2n +ma3ari +ma3atl +2m3abb +2mabd +2mabf +2mabg +2mabh +2mabk +m3abla +4m3a4blä +2m3abm +2mabn +ma3bo +mab4ra +ma4bri +2mabs +2m3abt +2mabw +2mabz +ma4c3ar +ma5chan +ma3chi +ma3cho +m5achse +mach4ta +mach6terf +mach6t5erg +mach6t5erh +mach6terr +mach6t5ers +mach6t5ert +mach6t5erw +mach4t5r +ma3chu +ma2ci +2mack +mack4s +2m3act +mada4m +2m3add +ma4del +ma4dem +ma3d2j +2m3adm +2m3ad2r +mad2s +2madv +4m3aend +4m3aepf +ma3epo +2maeq +maerer4 +ma3erh +ma3erw +maer6z5en6 +maes3t +ma5estr +ma3eta +mae5us. +maeu4s5c +ma1f +2m3aff +maf2r +2m3aft +ma4ge. +ma4geb +ma4geg +ma4gek +mage4n3 +ma4gep +ma4ges. +ma4gev +ma4gew +2m3agg +magi5er. +magi5ers +m3a4git +ma3g4n +ma3gri +mah4ler +ma3ho +ma3hü +mai4ler +mai4n3e +mai4s3e +ma4kad +ma4ke. +ma4kes +ma3kla +ma3kli +mak2r +2m3akt +ma4kus +malad4a +ma5lade +mal5aer +ma3lag +mal3ak +mal3an +ma3las +ma4lär +malb2 +6mal6dehy +malen4s +ma4lent +mali5er. +ma4l5ins +ma4linv +malk2 +4mallt +ma4l3on +ma4lop +ma4l3o4r +mal5ska +malster6 +mal5s6terb +mal5s6tern +malu2 +ma4l3ut +mal4zäp +2m3amb +ma4mid +ma4moe +2m3a2mö +2m3amp +2m3amt +m3anae +5manag +4m3a4nal +man3an +ma4nar +4m3a4na3t +ma4nau +2m3anä +4manbie +man4ce. +man4cek +mand4 +man3di +man5erf +ma4n5er4n +ma4net +man3g4a +4mangab +4m5angeb +m3angs +4manhä +manin4t +5manip +4manke +4manla +m4anle +4ma4nod +man4o5m +4manpf +m4anpl +m5ansatz +4m3ansä +m5anschl +4m3ansi +4m3ansp +4m5ansti +man4tag +man4tan +man3um +manu3s +4manwen +4m3anza +manze4 +m3anzu +m3anzü +ma3o2b +ma2or +ma3pfl +ma4phr +4m3appa +4m3appl +ma3pr +4marag +mar4an +2m3arb +4m3area +mar3g4 +m3argu +ma3ri +3mark +mar4klo +mar4kr +m3armb +4m5arme. +mar4o +maro5d +ma3rö +2marr +mar6schei +mar6scher +mar6sch5l +mar6schm +mar6schn +mar6scho +mar6schr +mar6sch5w +4m3arse +mar4sp +mar4su +4mart. +4m3arti +ma3ru +ma3rü +masch4 +ma3sco +maser4n5 +ma2s2p +ma3spa +ma5spen +ma3spr +4m3as4so +mast5an +ma5stel +masten4 +mas6t5ent +mas4ti +mas4to +ma3s4tr +ma4t3ae +ma4t3an +ma4t3ar +ma4tat +ma2t3ä +mat3ei +m3a4tel +mate4n +ma4t5erd +mat5er5in +ma4t5erz +ma4tho +mati6kan +4m3atmo +ma4toa +ma5toma +ma4t5opf +ma4t5ort +mat3s4p +mats4t +mat3th +matt4ha +matt4hä5 +mat4the +mat5url +2matz +2mauc +2m3au2f3 +2m3aug +mau4les +mau4l3o +mau4lö +ma3um +m4aun +ma3una +ma3ung +ma3unt +m4aus. +mau4sa +4mausd +maus3p +maus3t +2m3auß +mau4ta +mau6t5erh +mauto4r +ma2xa +m2ay +ma3yo +1mä +mäh3r +2m3änd +m1äp +2mäq +märer4 +mär4kl +mär2z +märzen4 +mä3us. +mäu4s3c +2mb2 +mbe4er +mben5er +mbe4ren +mber5er5 +mbi4e3i +mbi3o4f +mble3s +mbo4ler +mb4re +mb3s +mby2t +2mc +m3ch2 +m1cr +2md +m2dab +md3aer +m2d3an +md3arb +md3art +m2d3a2s +md3ato +md3är +mde2a +m2d3ei +m4d3e4mi +mde2n +m4d3ent +mder2 +m4d3erf +m4d3erk +m4d3erl +m4d3err +m4d3ert +m4d3erw +mdi2a +mdi2e +mdien4 +m2dob +md3ras +m3dru +md2s3a +md2sä +mds2p +md3ums +1me +mea2n +me3at +meb2 +me4ben +me1c +4m3echo +3med +medie4 +me4dito +3mee +mee5ing +mee5ins +me5e4mis +mee2n +me3ene +meep2 +mee4rei +meer5eic +meer3u +2m3eff +3meh +meh4l5er +mehls4 +mehr3a4 +mehrer4 +meh6rert +2meic +2m3eif +2m3eig +mein4da +meinde5s +mei5nec +mei6nenz +mei6ner6k +mei6nerl +mein6hal +mein6sel +meins4p +m2eis +4m3eis. +m3eisb +me3iso +5meist +meist5a +me4l3au +3mel3d4 +melde5i +me3lec +me4l5ein +4m3e4lek +4m3elem +me4l3en +me4l3er +melet4 +4m3elf. +mel3id +4m3e4lim +meli4n +mel4k5ei +3melo +me4l3ob +me4lor +mel3p +mel4s5a4c +mel4san +mel4sph +mel4s5ta +6m5el6tern +mel4zei +mel6z5ent +mel4zi +4m5e4miss +2m3emp +m2en. +me4na4b +men4ade +men4ag +me4nan +men3au +me2nä +mende4c +4m3endl +mend4u +men3e4b +men3ed +men5end +menen5e +men5ent +men5erb +men5er5o +men5erö +men5ers +mene4s +me4n3e4v +men3f +4men4gag +me4nid +me4nil +me4n3im +menk2 +men4s5au +men4se. +men4seb +men6semb +mens5ert +men4ses +men4sex +mensex5p +men4s5i4d +mens6temp +mens5trun +men4sum +men6t5anz +mentar6ten +men6t5eig +men6t5enden +men6t5ers +men6t5erw +men4tet +men4thi +men4thu +men6t5ins +men6tres +men6t5rol +men3um +menz4w +me1o2 +2meou +me1p2 +me3ra +mera3s +merauto6 +mer3b +4m5erde. +4m5er4den +mer4dun +me4r3e4b +mer5eck +mere4f +me4r5eis +me4r5end +me4r5ens +me4r5ent +me4r5erd +me4r5erf +merer6kl +4m5erfol +merg4 +4m5ergän +4m5er4gus +meri4e +merin4d +4m5erklä +4m3erlö +merns4 +mer3of +mersau4 +mer4siv +mer3sm +mer3sn +5mer5te +merti4 +4merzä +mer4zer +4m5erzeu +mer4zwe +m4es. +mes2a +mes3ae +me2sä +mes3e4l +mes5er5o +mes3or +mes4pe +mes3pr +mes4s5ac +mes4sal +mes4s5an +mes4say +mes6sersa +mes4s5or +mes4ste +mes4sti +mes4stu +mest2 +me3sto +3me3ta +meta5st +mete4r3 +me3t2h +2metö +met5res +me4trig +metro5s +mett5en6de +met4t3i +meu1 +me3um +meute5r6e +2m3e2x3e +2mexp +1mé +2m1f2 +mfahr6ta +mfen2 +mfi4le. +2mg2 +mgeb2 +mglim4 +mgus2 +2m1h2 +mh4au +mhaut5o +1mi +mi1a +mia2b +mi3ak +mia2m +2m3i2at +mibi3e +mic3e +mi3ch +mi2ci +mi3cr +m2id. +mi4dea +mi4di. +mi3d2r +2mi2dy +mie3dr +mi4eno +mierer4 +mie4tau +mie4t3i +mie4t5rä +mie4tru +mi1f +mi4k3ar +mi4k3el +miken4 +mi4k5ens +mik6erl +mi4k5er4z +mi4k3in +mi4k3ob +mi4k3of +mi4kon +mi4kre +mi4kum +mi3l4a +mil4i +4m3illu +mi3l4u +2m3imm +2m3imp +mi3na +mi4na4b +min5eck +mi4nef +mi4n5e4r5i +4minfo +m2ing +min4gan +min4gl +ming3s4 +4m3init +min4kr +mi4n3of +mi3nop +mino3s +6minterp +mi3nu +min4z3o +mi2o +mi3o2b +mioni3 +mi3os +mi1p +mi3ra +2mirg +mi3sa +mis4au4 +mi6schef +mi6scherb +mi6sch5er6z +mi4schi +mise3r +mi3s2n +mi4som +mi3s4pa +mi3s4ph +mi3spo +mis3sa +mis4sac +mis4ser +mis4s5in +mis4st +mis3te +mit3ak +mi4tar +mit3as +mit3au +mite2 +mit3ed +mit3em +mit3er +mit3es +mi2t3h +mi3tia +mit5iss +mito2 +mit3op +mit3or +mi2t3r +mit3s4 +mit5sa +mi2tu4 +mit3ur +2mitz +mit3za +mi2zw +2mj +mjäge4 +2m1k2 +2m1l +ml1c +m3le +mlein4g +mlerin6t +ml2v +2m1m +mm3aal +mma4bar +m4m3aeh +m4m3aeu +mm3a4kr +mm3all +m4manb +m4manf +m4m3ank +m4m3ans +mmanu3 +m4m3anz +mma2p +mm3apo +m5mar. +mm3art +mma3sp +m2m3äh +m2m3äu +mmd2 +m2m3e2b +mm5e4dit +m2m3ef +m4m3eid +mm3ein +mm5eise +mmela4 +mme4l5ad +mme4l5in +mmen4er +mme4no +mmens6pa +mmen6stru +mmen5ta +mmen6te. +mmen6ten +m4m5entg +m4mentl +m4m5ents +m4mentw +mmenzu6c +mme4r3a4 +mme4rec +mmer6fol +mmerk4 +mmer6sio +mme2s +mmes3a +mme3sc +mmes3e +mmes6sens +mmes3t +m2m3eu +m2mex +mmi3e4l +mmi3k2 +m4m3inb +m4m3inf +m4m3inh +mm3inn +m4m3ins +m4m3int +mmisch4 +mmi3s4t +mmi3tu +mmi3tw +mmmi2 +mm3ope +mm3org +m2m3ou +mm1p2 +mms2k +mms4p +mms4tr +mm3te +mm2th +mm5ther +mmuel4 +m2mum +m3mun +mmut3s4 +mmül2 +mmüll3 +2m1n2 +mne4bl +m4nesi4 +mnes3t +mni3er +1mo +m4obb +2m3o2be +2mobj +mo3che +mode5st +mode5sz +mo2dr +moeb2 +2m3oe2f +moe2h +mo3ehe +2moek +mo3ela +mo3e2m +mo3ero +mo4fen +2m3off +mo4g3al +moh2a +mok3al +mo2k2l +mok3la +mo3kle +3molc +molch5a +mol3d4 +mo3lo +2molt +mom4e +mo4n3ae +mon3al +mo4n3an +mon5ato +mo2nä +mon4das +mon4d5el +mon6d5erk +mon4din +mon4dre +mo4n3er +moni4en +moni5er. +mon4s3t +mon3s4u +mont4a +2mony +mo2o +mo4per +2m3opf +2mopt +mo3rae +mo3ral +mo3rat +mor4d3a +mor6d5ere +mor6d5erl +mor6d5erm +mor4dr +mo4rei +mor5enge +morgens6 +mor5int +moro2 +mo4rob +mo4rop +mor3t4 +mosa6ische +mo4ska +mo3s2p +mo2st +mo3s4ti +mo2s3u +mo3t4h +mo2u +mous2 +2m1ox +moy1 +1mö +möb2 +mö2c +2m1ö2f +2mök +2m1öl +2mp +mpa3ne +mp2ar +mp3arb +mpat4 +mpe4la +mpe4ler +mpe4lin +mpe4ne +m4p5ener +mpens4 +mpf3au +mpf3ef +mp4f3el +mp4f5enz +mp6f5er6fo +mp6f5erfu +mp4f5erz +mpf5lae +mpf3lä +mpf3lo +mpf5lüc +mpf3ra +mp3haf +mph3au +mphe2 +m4phoe +mph5org +m2phö +m3pi +mpi2a +mpin4 +mp2l +mp3le. +m4p5lem. +m4p3len +m4p3les +m3po +mpo3l4i +mporta4 +mpor6tag +mpor6ter6 +mpp2 +m1pr +mp3sh +mp3sta +mp3s4tr +2mq +2m1r2 +m3ra +mrich7te +m3ru +2m1s +m2sak +ms3all +m4s3alt +ms3amt +ms5andr +m4s3ang +m4sanm +m4sanr +m4s3ant +m4s3anz +m2s3a2s +m4s3auf +msau4g +msau4n +ms3b +m4schan +msch4l +m3schu +m2sco +m2s3ef +ms5einsa +m3s4ek +mse5l4a +mse4le +msel5ei +mse2n +msende4 +m4s3ene +m4sentl +ms3erf +ms3erk +ms3ert +m5serv +mse2t +ms3eti +mse2x2 +mshi2 +m4s3imp +msi2n +ms3ini +ms3int +m2skl +m2s3o2d +mso2r +ms3orc +m4s3ori +m2s3ou +m4sped +m2spl +ms4pon +m4spot +m4spro +ms2pu +ms3s2 +mss4a +m3stät +ms5täti +m3stei +m4steil +m4stem +ms2ti +m4stit +ms2to +m5s4tras +mstro4 +ms2tu +m2sty +ms3ums +m2s3ur +m3sy +m2szi +2mt +mta2m +mt4asc +mt3änd +mt3c +mte2a +m2t3e2b +m2t3e2d +m2t3ef +mt3ein +m4t3eis +m4t3eiw +mt3elt +mte2m +m4t3emi +m4ten4ga +m4t5engl +mte4no +m4t5en4se +m4t5entf +m4t5entg +m4tentl +m4t5en4tr +m4t5ents +m4tentw +mte4ri +mter4n +m4t3eta +m2t3eu +m2t3e2v +mt1h +m2t3i2d +m2t3im +mt3inb +mt3ind +mt3inf +mt3inh +mt3ins +m2t3i2r +mti6schw +mt3isr +mt3ita +mtmen4s +m2tob +mt3oes +mt3ord +mt3org +m2töl +mt3ös +m4trah +mt5rang +mt5rauc +mt3rec +m4t3res +m4t3ris +mt3rob +mt3rot +mt2sa +mts2c +mt3se +mt4s3e4l +mt4sen +mt5s4kel +mts5kr +mt5spru +mts4tro +mtt2 +mtu3en +mt3um +mtz2 +1mu +mu1a +2muc +mu4ckel +mucker4 +mu6ck5erk +mu6ck5ers +mu6ck5erz +muck5sp +muck5st +mu4cku +2m3ueb +2muec +mue4her +muehl5a +mu3el. +muell5a +mu3els +muen4s +mu3ez +mu2f +2mug +2m1uh +mu3la +mul4lau +mul4l3e +2muls +mult2 +2m3umf +2muml +2mumr +mum4s3p +m3umst +2mumt +mundan4 +mun6dant +mun4dar +mun6d5erf +mund3r +muner4 +mu4nerk +4m5ungeb +4muniv +m2unm +2munw +2munz +mur3au +3mure +2m3url +murn2 +mu3r2u +mu4r3u4f +m2us +mu2s3a +mus5erf +mu4sin +mu2s3o +mus4s5e4h +muss4p +mu2st +4must. +mus3ta +mu2su +muße3 +mu4t3au +mu4t3er +2m3u2to +mut4str +2müb +1müd +1müh +mü4her +mühl3a +1mül +1mün +1müt +2mv +2mw2 +mwel2 +mwelt3 +mwelter5 +mwetterer5 +1my +my1a +my3la +my3on. +my3s2p +my4s3t +2mz2 +1na. +1naa +n1ab +3naba +na4b3ar +3nabä +4nabb +4nabd +3nabec +na4bei +nabens4 +nab5erk +nab5er4r +2nabf +nab4fr +2nabg +n3abh +n2abi +na4bi4t +na2bl +na4b3or +na4bos +3nabö +na4bra +na4bre +na4bri +nab4rue +n4ab4rü +4nabs +4nabt +na4bus +2nabz +3nac. +3nachb +na4che +nachen4 +na5chen. +nach5ent5 +nach5erb +nach5ers +3nachf +3nachh +nach3i +3nachl +3nachm +3nachr +nach3s +5nachsc +n5achse +5nach5s4p +3nacht +n4acht. +nachter6f +nach6terk +nach6t5ersc +nach4to +nach6traum +nach6t5ren +3nachw +4nadap +n3add +nade4l +nadel5a +nadel5e +na4der +n5ader. +nadet4 +n2adi +nadi5ers. +n3adl +4n3adm +4n3a2dr +n3ads +3n2aec +3naee +3naehe +3naehm +4n3aehn +3naeht +3naei +2n3ae2m +2n3aen +n2aep +2naeq +3naer. +na3erg +nae5us. +3naex +2n1af +3na3fl +na3fra +1nag +nage3i +nagein5 +nagel5e +nage6lei +na4gem +na4gent +2n3agg +na4gio +na4git +na3g2r +1n2ah +na4h5erk +3nahm +n3ahn +nah3oe +na2hö +4n3ahr. +na2hu +1n2ai +na3i2d +nai2e +na3im +2n3air +nai4re +n1ak +na4kad +3nakä +2na2kn +3nako +na4kro +4nakt +na4kus +n2al. +nala2 +nal3ab +nal3ad +nal3ae +nal3ak +nal3am +nal5ang +nal5are +nal3as +nal3at +na2lä +4nalbb +4n3albk +nalb4r +nal3da +na4leb +na4l3ed +na4l3ei +na4lel +na4l5end +nalen6se +na4lent +nal5ents +na4l3ep +na4l5erb +na6l5erei +na4l5erf +na4l5er4g +na4l5er4h +na4l5erm +na4l5er4s +na4l5erw +na4l5er4z +nales4 +nal3ex3 +nalf2 +nal3g +n2ali +na4l3i4d +nalk2 +nal6l5en6g +nal4lor +4nallt +n3almt +na4lob +na4loe +nal5oef +4nalog +na4lop +na4l3or +nal3ö +n4als. +nal5s4ka +nal5skr +n3alt. +n3altd +nal5to +n4alty +na2lu +nal3uk +2naly +n3a4mal +n3a4mat +3namä +3name. +na4mei +3n4amen +namen6s5p +4n3a4mer +n2ami +n3a4mid +na4min +2n3amm +2na3m2n +3namo +n3amp +namt4s +3namy +4nana +na4nae +na4nal +na4na3t +n3a2nä +2nanb +n3and2 +n4and. +nanden4 +n4ands +na4nem +nan3er +nan3eu +4nanf +2n3ang2 +2nanh +2nani +4n3ank +4nanl +4n3anm +3nann +4n3anna +2nanr +4n3ans2 +nanta4 +nan4tag +4nantr +3nanu +2nanw +nanzen4 +nan6zene +nan6zeng +nan6z5er6f +nan6zer6g +nan6z5er6m +nan6z5ers +nan4zin +nan4zw +1na1o +1naö +na4per +3napf. +na3pfa +4napfel +3napfs +3napl +na4pos +na2pr +n1a2q +n1ar +3n4ar. +3nara +na4rab +na4r3ak +na4ran +3narä +n4arbo +2narc +3n2ard +nar5ein +nare3s +n2arh +3n2ari +n2ark +n4arle +4narm. +4narme +na4rom +na3rö +n2arp +nar4rh +n4ars. +narse3 +nar4s5ta +nar4stä +2nart +n4arta +n2aru +na3rum +n2arv +n4arze +3n2as. +3nasa +na4san +na4sar +nas3au +3nasä +na4schm +na4schw +3nase +3nasf +n2asi +na2sp +nas4ph +4n3assi +na3sta +naster4 +nas6t5erf +na2su +na2sy +nasy5la +1nat +natal5s4 +na4tat +na4t3au +nat3ei +na2th +n3atla +2natm +n3atmu +n2ato +4natom +nats5ac +nat6scheng +nats5en +4n3atta +n5atten +n1au +n2aue +nau3eb +3nauem +3nauen +3naues +3naui +3n2aul +na3um. +4nausb +4nausd +4nauss +naussen6 +n4aut. +n4aute +1nav +2nava +nave2 +navel3 +navi5er. +navi5ers +1naw +3nax. +na2xi +3naxp +n3axt +3nazu +1nä +2näb +3n2äc +2näd +2n1äf +3näg +n4ähe +nä2hi +3n4ähm +2n3ähn +n2ähr +näh3ri +nä2hu +3näi +2n1ä2m +2n1än +n2äp +4näpfel +2näq +närm4s +n4ärr +när4s3t +n2äsc +3näss +2näst +2näu +3nä3um +nä3us. +2nb2 +n3ba +nbahner6sa +nbais4 +nbau3f +nbau3s +n3bä +n3be +nbe3n4a +nbe3ra +nber4e +nber4ga +nber6g5ei +nber4gr +nbes2 +nbi2e +n3bl +nboge4 +nbogen5 +nb4or +nbruchs5tr +n3bs +n3bu +nb4un +nby2t +2n1c +n2cab +n2cei +n2cem +n3ce4n +n2cev +ncha3l +nchar6me +n3che +nchial5 +nchi5en +n3chl +n3cu +2nd +n4dab. +n2dac +n2dad +nda3ef +nd3aet +nd2ag +n2dak +nd3akt +n4d3all +nd5ange +n4danl +nd3ans +n4danw +n4d3arb +ndar4da +ndar4t5r +n4daut +n2dax +nd3äng +n2d3ät +nd1c +nd5dre +nde4al. +nde3e +n4d3ei. +nd3eil +nd5ein. +nd5einh +n4d5einn +n4deis +nde4lat +nde5lec +nde4len +nde4ler +ndel5l +ndel6s5am +ndel6s5en +ndel6s5or +ndel5ster +nde4m5ac +nde4mau +n6d5en6gel. +nden6geln +n6dengli +nde4n5in +ndenk4r +nden5s4k +ndens6tem +n4dentl +n4d5ents +nde3o2 +nd5epoc +nde6r5eis +nde6r5eiz +nde6reng +nde6rent +nder6läs +nde4ron +n4d5eros +nder6tragen +nde4sam +nde4san +nde4sel +nde4ser +nde4set +nde6stät +nde5strae +nde5stran +nde5stri +ndi5en. +ndi4ene +ndi4ens +ndi5ensy +nd3imm +nd5inan +nd3iso +nd5kuc +ndni2 +ndo4n5au +ndoo2 +n4dopt +n4d3orb +n4d3o4ri +n4d3out +n4d3rat +n4dreih +nd3rob +n4d3rod +n4d3roe +n4drose +nd5rost +nd3rot +n2d3rö +n5druc +n4drui +nd3run +nds5amt +nds5chin +nd4s5ene +nds5erb +nd4skl +nd4spae +nd4spä +nd4szi +ndwa5re +nd5wil +1ne +nea2b +nea2n +ne2ap +ne3ar. +nebel5e +nebe6lei +nebels4 +2n3ebn +neb2r +2nec +3neca +3nece +ne3chr +n3echs +ne3ck4a +3necl +3neco +nedi4e +ne4dit +ne4e3i4g +neel4 +neen2 +nee3ra +ne3erk +nee3ro +ne3er4z +nee3t +ne3f4a +nef2l +neflei4 +2n3egg +neg2l +n3e2go +neg2r +4n3e4he. +4neheg +nehen4 +4n3e4hep +nehe4re +3nehm +neh4min +ne3ho +2n3ehr +neh4r5er +2n1ei +3neia +n3eic +nei6derf +neid5erfu +3neigt +5neigu +ne3imp +neins4 +ne5in6sel +n4einsu +nei6senho +neit4h +n4ek. +2nekd +2n3ek3s +nek3t4 +2nekz +n3e4las +4n3e4lek +4n3elem +3neli +ne3lid +ne4lim +ne4lit +3nelk +n2ell +nell5an +nel4l5au +nel4l5ei +nel6l5erf +nel6l5erk +nel4l5in +3n2eln +3nels +3nelt +n2em. +4n3e4mai +2n3emb +nem4e +n2emi +4ne4mig +n4emo +4n3emp +2n3ems +4n3e4mul +n4en. +n2ena +ne5nack +ne4nan +ne2nä +n2enb +n2enc +4n3endb +4n3endd +4n3endf +n3endg +4n3endh +nen4dig +4n3endk +n3endl +4n3endp +4n3endt +4n3endw +n2ene +ne4n3e4b +nen3ec +ne4n3ed +ne4n3el +nen5end +ne4n5en5e +nen5ens +nen5ent +nen5erb +ne4n5erk +ne4n5ers +ne4neru +ne4nerw +ne4n3eu +n2en3f2 +4n3engb +4n5enge. +nen6gen. +nen6gend +nen6gens +4n3engs +4n3engt +4n5engun +n2enh +ne2ni +nen3in +n2enj +n2enk2 +n2enm +nen4nar +nen5nec +ne2n3o2 +n2enr +n2ens +6nensemb +nen3sk +nen3sl +nen3sn +nens4p +nens6tern +4n3entb +4n3entd +4n3entf +4n3entg +4n3entl +4n3entn +4n3entp +nen6t5ric +4n3ents +4n3entt +4nentw +4nentz +ne4n3u +n2env +n5envir +n2enw +n2enz +4n5enzia +nenz4w +n3enzy +neob2 +ne4pen +ne3pfl +ne2pi +2nepo +n3e4pos +nep3t2 +n4er. +ne3ra. +ner3a4b +ner3af +ner3a4g +ner3ak +ne3ral +neral5a +ner3a4m +ner3an +ne4r3ap +ner3ar +nera4s +n4erat +4n5erbe. +4n3erbg +n5erbie +ner4big +4n3erbs +n3erbt +4nerdb +ner4dig +n2ere +ne4r3e4b +ne4r5eck +ner3ef +ne4r5eic +6n5ereign +ne4rein +ne4rek +ner5ema +ne4r5ere +ne4rerf +ne4r5erh +ner5ers +4n5erfol +n4erfüh +ner3g4 +n6erges +n5erheb +5n4erhin +n5erhöh +n2eri +neri4e +ner5inner +nerin6st +nerin4t +n2erj +nerk2 +4n5erklä +6n5erlaub +4n3erlö +ner6löses +4n5ermit +4n3ermü +n2ern +4n3ernä +4n5erneu +4n3erni +4n3ernt +4n3erod +6nerosio +ner4pre +nersau4 +ner4sk +4n5ersts +ne4rup +n2erv +nerve4 +4nerwae +4nerwä +n4erwo +4nerze. +4n5erzes +4nerzeu +n4erzim +n2es. +ne4sal +nesch4 +4n3e4sel +ne4s3ev +nesi5er. +ne4s3of +ne4spa +nes4pe +nes6sige +nes6sigs +ne3sta +nesti4e +nest5rae +nest5ran +nest5rau +nest5rä +nest5ri +4nesyn +ne4tab +ne4tad +n5e4tage +ne4t3an +4n3e4tap +4n3e4tat +ne4t3ei +ne4t3e4l +nete4n +net5erh +net5ers +ne2th +ne4t3in +ne4t3ob +net3sp +n4ett +net3tr +4netui +ne4tus +3netz +net4z3i +ne2u +neub2 +neu3c +ne3ueb +neu4ere +neuer4f +neuer4k +neuer4ö +neuer4r +neuer4s +neuer4w +n4euf +neu3g2 +4n3eule +ne3umf +ne3ums +2n3eup +neur2 +neur3a +4n3eury +neu5sc +neu3sp +neu3t4 +4neuzt +ne4van +ne4vid +ne4volu +n2ew +new2s3 +2n1ex +nex3em +1né +2n1f +nfa5chu +nfah4l +nf5akti +nfall6s5o +nfalt4 +n4fanb +nfangs5 +n4faut +n3fä +n3fe. +n4f3eim +nfen2 +n3fen. +n4f3end +n3fer +nf5erze +n2fex +n3fi +nfi4le. +nfin4s +n2fj +nf2l +nf5laen +nf3län +n3f2o +nfo3st +nf2r +nf3s2 +nf4tan +nf2t3r +nft4sen +nft4s3p +nft4st +nf1u +n2fun +2ng +n2g3a2c +ng3adr +n4g3ael +ng5aerm +nga3h +ng3aka +n4g3alm +ng3a4me +ng3amp +nga2n +ng5anda +ng5angl +ng5ansc +n4g3ant +n4g3anz +n5garn +ngas5ta +ng3ato +ng5attr +ng3auf +n2g3a2v +n2gäh +n2g3äl +ng3ärm +ng3d2 +n4g3eif +n4g3ein +n4g5eise +n4gelem +ngel6s5tu +nge4nat +ngen5ec +n6generg +ngen6erw +n6gensem +ngen5sk +n4gentl +nge4ral +ng5ereig +nge6rend +nge6rers +n4g5erho +ngerin6st +n6g5erwei +nger6zae +nger4zä +n3ges +n4g3ess +ng5e4tat +ngg2 +ngg3s +ng3hu +n2g3i2d +n3glä +ng4lea +ng4led +ng4le3e +ng3ler +ng4lev +n4glig +n4glog +ng5lot. +n4gluk +ng3m +n2gn2 +ng3nar +n3gnä +ng3ne +ng3ni +ng2ob +n2gof +ng3o4pe +ng3opf +ng3p +ng5raen +ng5raeu +n4g3rai +ng5ram. +ng5rand +n4g3rän +n4g3räu +ngres6s5o +n5grif +ng3ris +ng3rob +ng3roc +ng5rost +ng2s +ngs3e4h +ngse4m +ng4sf +ngs5tec +ngs5türm +ng3sy +2n1h2 +nhab4 +nhabe6r5e +n5halb +nh4au +n3he +nhe4re +nhe3x +nhor4s +1n2i +ni3a2b +ni3akl +ni3akr +ni5aleng +ni5alim +ni2ar +ni3as +ni3ato +nibb2 +ni3be +nib2l +nib2r +nibu2 +ni3cho +ni3chr +nich3s +nich4ta +nich7te. +nichter5 +nicht5sp +ni3cr +ni3da +n4i3de. +4nidea +4nidee +ni5den. +4nideo +nidi4e +n3i4dio +ni3d2r +2n3i2dy +nie3b +3nied +ni2e3i +ni3el. +ni3elh +ni3ell +ni3elr +ni3elw +ni3elz +nie2n +nien3e4 +niens4 +nien6z5er +niere4 +nie4r5ei +nie6rerg +nie5sti +nie4t3o +niets4 +ni3fl +ni3f2r +niga2 +ni4gab +ni4g3am +ni4g3an +4n3i4gel +nig5erz +4niget +ni4g3li +nig2n +ni4gre +nig4sal +nig4san +nig4spr +4ni4k3el +ni4k5er4f +2nikh +ni4k5ing +ni4klo +ni4k3of +ni4kor +niko3s +ni4k3ra +ni4k3rä +nikt2 +nil3l +nille3 +ni3l2o +3nimmt +2n3imp +ni3mu +ni3nac +ni3nae +ni3nap +ni3nä +2n3ind +2n3inf +3n4ing. +3nings +2n3inh +4n3i4n3it +2n3inj +2n3ink2 +4n3inno +ni4nor +2n3ins +n4ins. +2n3int +ni3n2u +2n3inv +ni3o2k +ni3ora +ni3os +ni4ron +2n3irr +ni4sai +ni4scha +ni5s4che +ni2s3e +ni3se. +nise4l +ni3seu +nisi2 +nis3id +ni4s3ik +nis3in +ni4s3oe +4n3isol +ni4som +nis3or +4nisot +ni2sö +3niss2 +nis3sk +nis3sz +nis5tha +ni2s3u +3nit. +nita4le +nit4an +4nitia +ni4top +3nitr +3nit3s2 +nit4t3a +nit6teig +nit6tele +nit6ter6g +nit6t5er6k +nit4t5ra +nitt5ri +ni3v +2nj +n3jo +2n1k +n4k3a4bl +n4k3abo +nk3a4br +n4k3adr +n4k3aeh +nk3a4ge +n4k3alg +nk3alp +nk3ama +nk3ang +n4kanh +n4kanl +n4kanna +n4kanr +n4k3ans +nk3apf +nk5art. +n4karth +n4k3atm +n4k3ato +n2käh +nke2c +n4k3ei. +nke4lei +n4kelem +nken5sk +nke6r5eis +nker6fah +nk5ergu +n4k5er4zi +nke2t +nk3eti +n4ketu +nki2d +n4k3ide +nkier4s +nkin4g3 +nk3inh +n4k3ins +n4k5lade +n4k3laf +n4k5lamp +n4klen +n4kleu +nk4link +nk4neb +n4knit +nk5ober +n4koel +n4kopt +nk5o4rie +nko2w +n2köl +nkraf4 +n4krel +n4kren +n4krez +nk3rob +n4kroh +n4k3rol +n5kron +n4k3ros +n4k3rot +nk3rüc +nk4s3ac +nk4s5alt +nk4s3el +nk2so +nk5spat +nk3s4pe +nks4por +nk3spr +nk3sta +nk3s4ti +nk3str +nk3stu +nk3s2z +nk4t3ak +nk4t3an +nk4t3au +nk4t5eig +nk4t5ent +nkte5ra +nk4t5erf +nk4t5erg +nk4t5ern +nk4t5ers +nk4t5ert +nkte5sk +nk4t3in +nk4t5ort +nk4t5rae +nk4trä +nkt5rol +n4kumf +n4k3ums +nku2n +nku3na +nk3uni +nk3urh +n4k3urk +nk3urt +nku2t +nk3ute +2n1l2 +n3la +nlage5i +nlage4n5 +nlan4dr +nlan4gl +nl4au +n3le +nle4gas +n3li +nli4ne. +nli4nem +nlite3 +n3lo +nlus4t5e +2n1m2 +n3ma +nma6l5erl +nm4en +nmen5sche +nmi6schw +nmi2t +n3mo +n3mu +2n1n +nnae4s +nna5esc +nna5le +n4n3alg +n4n3all +n4n3amb +n2n3an +nn4an. +n4n3app +nn3a4sc +nna3s4t +nn3atm +nn3ato +n2nau +nn3d2 +nndar4 +nn3eff +n3n4eh +nn4ei. +nne3lu +nnend4 +nneng4 +nne3pf +nne4r3a +nner6fol +n4n5er4fü +n6ner6geb +nner6hoe +n4n5er4hö +nner6laub +nner6mit +nner3o +nner3ö +nner6war +nner4zi +nne4s3e +nnesi5e +n3n2ex +nng2 +nngas3 +n2n3i2d +nni4sa +nnk2 +nno5be +nn3obl +n2n3of +n3nom +n2n3op +nno2r +n4n3ori +n3nos +nno4t3h +nn4s3am +nn4s3as +nn4sau +nns4po +nn4stec +nn4stoc +nn4s5tol +nnt4a +nn2th +n2n3uf +n4n3unf +nn3ur +nnvoll6 +n2nya +1no +3no. +no3at +2nob. +2n3obd +n4o5bel +nobe4la +nobe6lin +2n3obh +2nobj +no4bla +2n3obm +2n3obs +2nobt +n4oby +no1c +no2de +no3der +n3ods +2n3oe2f +2n3oek +n4oet +no2fe +2n3off +nogene6r +no3hö +n3ohr +n2ok +no4kel +2n3okk +n2ol +no4lam +nol3c +no4leu +no4liv +no4lym +4no4mal +n4omas +no3m4au +no4mel +n4o3mi +non2a +4nonke +2n3ont +2nony +no2o +no3oh +no3os +2n3o2pe +2nopf +nopf3a +no4pie +3nopp +2n3ops +2nopt +3no3p4te +n4or. +nor2a +no4rad +no3ral +4noran +no5rar +n3orbi +2norc +nor4d3a +nor4del +nor6d5en6d +nor6deng +n5ordens +nor6d5er6w +nor4des +nord3o +nor4d3r +nords4 +nor4du +nor2e +no3res +2n3org +n5o4rien +2nork +3n4o4rm +nor4m3o +2n3orn +n4oro +n4orö +2n3ort +no3ru +n2os +3n4os. +nose5l4 +no3sh +no3s2k +noste4 +nos4tei +noster5e +4n3ostl +nost5ra +4n3ostv +n2ot +no4tam +no4t3an +no4tap +no3tar +no4tau +not3ei +no4t3em +note4n +no4ter +not5erb +noter6b5r +no4tex +no3the +no4t3ho +no4t3in +not5ope +not5opf +notor4 +no4t5org +no2tr +3not3s2 +no4t3um +3notv +2n3out +no3va +2n1o2x +n2oz +2nöd +2n1ö2f +2n1ök +2n1öl +2n1p2 +npa4ge +npar4ka +npa4s +npe3l +npi2p +n3pn +n3pr +npro3 +n3ps +n3pu +2nq +2n1r2 +n3ra +nra4dei +nra4du +n3rä +n3re +nrebe4z +nre3sz +n3ri +nroe4s3 +nro2h +nrö2s3 +n3ru +nrue3b +nruecker6 +nr4un +n3rü +nrü3b +nrücker6 +2n1s +ns4aa +n3sabo +ns4ack +n2s3a2d +ns5aemt +n3saeu +n4s3a4gi +ns4ai +n2sak +nsa4kr +n4s3alg +n4s3all +n4s3alm +ns4alp +ns5alta +n4s5alte +n4sanb +n4s3ane +ns3ang +n4sanga +n4sange +n4sanh +ns5anka +n4sanm +n4s3ann +n4sanp +n4sanr +n4s3ant +n4s3anz +n4sapo +nsa4re +ns4ark +ns4atm +nsau4e +n4s3auf +nsau4n +nsau4r +n4saut +n4säugl +n5sche. +n6schefi +nsch5erf +n5sches +nsch5eul +n4schl. +nsch6mau +ns5chor. +nsch4ue +nsch5werd +n4scor +ns2cr +ns5eben +nse4ein +nseha4 +nse4hab +nse6hent +nseher4 +nseh5ere +nseh5erz +nse4hin +nse4hue +nsei4ne +n4s5einf +ns5einh +n4s5einl +n4seinn +ns5einsa +ns5einst +n4s5eint +n5seit +ns5e4kel +nse4la +n4selem +nse4len +nse4ler +nse4lim +nse4l5in +nse4lit +n3s4ell +n4sem. +n4s3epo +n3s4eq +n4s5erar +nse5ras +ns5erbi +nser5eig +n4s5erfa +n4s5erfo +ns5ergi +ns5ergu +ns5ergü +n4s3erh +n3seri +n4s5erke +n4s5erkl +n4s5erle +n4s5erne +nser3o +n4s5eroe +n4s3erö +ns5ersa +nse3r4u +n4s3erw +n4s3erz +n4s3eth +n4sety +ns3eur +nsex2 +n4se4xi +nsex4t +n3s4ha4k +ns4har +nshi2 +ns4hir +ns4ic +n4s3ima +n4s3imp +n4s3ind +n4s3ini +nsinn4s5 +n4s3int +n4siso +nsi4t3e +nsi4tr +n2sk +n5skala +n5s4kali +n3s4kel +ns3kr +n5skrip +n3s4ky. +ns4kys +n3slip +n5smara +nsmaragdei6 +n2so2b +n2s3o2d +ns2om +n2sop +nsor3a +n4s5ort. +n4s5orts +ns4pac +ns4pari +n4spark +ns6part. +n6sparti +ns4pas +ns4päs +ns2pe +n3spek +n4speri +n2sph +ns4pie +n2spl +n4s5poe +n6sporti +n5sprac +n4sprei +n4s5prie +nsrue4 +nsrü2 +ns3s2 +nst5abr +n6s5taeti +ns6tagent +ns5talb +n4stale +nsta4n +n4st5ane +n4s5tat. +n6s5taten +n4stats +n4staus +n4staut +n5stäb +n4s5täti +nst5eifer +n6steil. +n6steile +nst5einhe +nst5einsa +nstein6ste +ns4temm +ns4tent +ns4tep +nst5erbie +nster5ei +nst5ergeb +n6sterin +nst5erke +ns6terne +ns6terns +nster5o +ns4teu +n5steue +ns4tew +n3s4tic +nst3id +ns4tie +n4stilg +ns4tin +ns2to +n4stole +nst5ord +n4store +nst5org +ns2tr +n4s5trac +n4strik +ns5tros +ns2tu +ns5tung +n2sty +nsu4m3o4 +ns5umsa +n4s3urt +n2s3ut +2nt +n4t5a4ben +nta5chl +nt4ack +nt3aem +nt5aend +n4t5aerz +nta3f +nta4gn +ntak4t5a +ntal3a +ntalent6 +ntal5ins +ntalk4 +n4t5allt +nt5alter +nta3ne +n5t4anfr +n4tanna +n4tantr +nt6anwer +ntar5ak +ntar3c +ntar6chi +nt5art. +n5tarta +n5tartei +nt5arten +nt5arter +n5tartex +n5tarth +nt5arti +nt5artu +nta4ru +ntasi4 +n4tath +nt3äm +nt3änd +n4t3ärz +nte5ame +n4teben +nteg4 +n3tehe +nt3ein +n4t3eis +nt4el +nt5elek +nte4let +nte4lin +nte5ma +nte4mot +nte4nem +ntene4r5 +n4tenpä +ntens4 +nten6te. +n4t5ents +n4tentw +n4tepi +n3ter +nter3a4 +nt5erbe. +nt5erbt +nterdru6 +nter5eic +nte6r5eis +nte4rel +nteren6de +nter6loe +nter4lö +nter5n4e +nter5obe +nter5oe +nter3z4 +n4t5erz. +nter6ze. +nte3sa +n4t3ess +nte5stei +ntest5r +n6t5estri +nteu6eri +nt4hals +nthan5a +n4t3heb +n4t3hel +n3ther +nt5herd +nt5herst +nth2o +n4t3hof +n4t3hot +n3thr +n4thue +n4thum +n3ti +nti3an +nti3c +nti3f +nti6k5erh +nti4lin +nt4im +ntim3p +n4t3ind +ntin4e4 +nti5nen +ntiner6s +n4t3inf +nting4 +n4t3inh +ntini5er. +ntini5ers +n4t5inse +nt4is +nti5sta +ntlan4 +n3t4lem +ntmen4s +ntni2 +ntnis3 +n3tof +nton4st +nto5pfa +n4topt +n4t3org +n4t5o4rie +ntos2 +nto3st +nt2r +nt5raed +nt5raend +nt4ral +nt5rand +nt5rang +nt5ränd +nt5räng +n4t5räts +nt3rea +n5treco +n5t4ree +nt5reic +nt5reif +n4t5reis +n5t4rem +nt4ré +n4t5rieg +n4t5ries +nt5ring +n4t3roh +nt1s +nt2sk +nts2t +nt3sti +nt6stief +ntt4a +n3t2ue +ntu3en +n4tumsc +ntu4r3a +ntu4re. +ntu4rei +ntu4rer +n3t2ü +ntz2 +1nu1a +nu3a2r3 +nubi3 +1nu1c +1nud +nudel5e +3nue. +3nuea +2nueb +3nuec +3nue3e +3nuek +nu3el. +nu3els +3nuem +3nuen +nu3en. +nue3na +nu3end +3nueo +3nues +3nuet +3nuev +3nuew +3nuez +nu2fe +1nug +2n1uh +3nuhi +1nui +nu3in. +n2uk +n3u2kr +nul4l3a +nulle4 +nul4l5eb +nul4lei +n2um. +2n3umb +n2ume +4numf +4numg +2numl +3n2umm +4n3umma +4numr +2nums2 +2n3umv +2n3umw +2n3umz +nu2n +2n3una +2n3und +2n3une2 +4n3unfa +4nunfä +nunft3 +3n2ung +4n3ungl +nungs3 +nungscha6o +2n3uni +n3unk +2nunr +nuns2 +2nunt +nunte4 +3nunti +2nunw +3n2unz +4n3unzu +1nuo +2nup +2nur +nu2ra +nu2ri +nur2z3 +1n2us +nu2sa +nus3au +nu2sä +nu2s3i +nus5pe +nus6serl +nus3t +1nut +nu2ta +nuti2 +4n3u4top +3nutt +3nutz +nut4zei +nutzer5e +1nuu +1nu2x1 +1nuz +1nü. +1nüa +2nüb +3nüba +1nüc +1nü1e +1nük +nü3la +1nüm +1nün2 +nü3na +1nüo +nür3c +nürs2 +nür5ste +1nüs +1nüt +1nüv +1nüw +1nüz +2nv2 +n3ve +n3vi +n3vo +nvoran4 +2nw +n3wa +n3we +nweg3s +nwehr3 +nwin6d5er5 +n1x +1ny. +n1ya +n2ya. +1nyf +1nyh +1nyl +n1yo +1nyr +1ny1s2 +1nyw +2nz +nza2b +n4z3aer +n2z3a2g +n2za2k +nz3and +n4z3a4n3e +n5zani +n4z3ant +n4z3anz +nz3app +n4z3a4ri +n2zas +n2zat +n4z3aus +n2zän +n2z3är +n4z3eim +nze4la +nze4lei +n4z3emb +nze5nad +nzene4 +n6zenerg +nzen5ev +n6zensem +nz5ente. +n4z5entg +n4zentl +n4z5ents +n4zentw +n4zentz +nzer3a +nzer5eig +n4z5erlo +n4z5erlö +nzer5oe +n4z3erö +nzer6tau +nzer6trae +n6z5er6trä +nze3sk +nze5sta +nze5s4te +nze5sti +nze5str +nze3u4t +nzin3a +nz5info +n4z3inj +n4z3inn +n4z5inte +nz3l +nz4ln +nzo2b +n4z3oef +n2zof +nz3oli +n2zor +nzue4c +nzu5eck +nzu5eng +n4zwet +n5z4will +n2zwo +n2zwö +n2zwu +1ñ +ño1 +o1a +oa2be +o2abi +oa2br +o2ac +oa3chi +o2ad +oa2dr +oad3ra +oad3t +oa2g +oa3in +oak3l +oa2ku +oa2l +o2ala +oa3lab +oa3las +oa2m +o2a3mi +oampe4 +oa2n +oana3 +oan3e +o2aq +oa2r +oar4dr +o3a2sc +3oase +oa2si +o3ass +o2at +oa2tr +o3au +oauf3 +oa2v +oa2x +oa2z +o1ä +ob3ach +ob2al +oba4lin +ob2am +2ob2ar +2ob2as +2obau +ob3auf +2ob2ä +obb2l +2obe. +2obea +4obec +obe3e +2obef +ob5eins +4obek +obe6leis +obe3li +2oben +obend4 +obera4 +ober5ad +ober5al +o4beram +obe4r5ap +o4berb +obe4reb +obe6r5eis +obe4rel +obe6rend +o4berf +3o4berg +obe6rin6g +oberin6t +obe4ris +o4berk +2obet +2obev +2obez +2obi +obi2e +obi4l5in +1obj +ob2l +ob3lau +ob3leb +ob3lei +ob3li +ob4lit +3o4boe. +2oboh +ob3ort +2obot +ob2ra +2obru +2obrü +ob4s3ac +ob3sc +ob3s2h +ob3sku +ob4ster +3obsts +2obu +o4bueb +o4bunt +obu2s +2obü +o2büb +2oby +oby2t +ocas2 +ocau2 +oc1c +3occl +o1ce +och3a4b +o5chani +ocha4r +och3au +ochdru4 +oche4b +o4ch3e4c +och3eh +och3ei +oche4l +och5ele +o4ch5er4g +ocher6ke +o4ch5er4l +och3id +ochi3e +och3l +och3m +och3n +ochoe4f +ocho4f +och3ö4f +och3re +3ochse +och3sh +ochs4p +och5sta +och5stem +och5stu +ochtergene6 +ochu4f +o3chun +och3u4t +och3w +o1ci +o3ckad +ocke4l +ock5ersa +o6ck5er6sc +o4ck5er4w +ock5le +o2cko +ock3s4p +ock5sta +ock3sz +o4ckum +o1cl +o1cr +ocre2 +od2a +odab2 +od3ak +od2d3r +o3dec +ode3e +odegene6 +ode3i2 +odein4 +o5dein. +o5deins +ode4l5a4g +ode6lauf +odel6l5ae +odel6lau +odel4l5ä +ode4lo +ode2n +oden3e +oden3i +odenk4 +o3der. +o3dern +o3ders +odes5ae +ode4sä +odes4po +o6d5esser +o3dex +2odia +2odie +2odif +2odir +2odo +o3dom +3o2don +o2d3op +o4dora +odo4si +2odö +2odr +ods2 +2ody +ody2m3 +oe3ba +oebels4 +oe4ble +oe2br +oeb2s3 +oe1c +oech6ste +oe2du +oe1e +o3effi +oe2f3l +oegen4s +oe2gl +oe2gr +2oeh +oehe4n3 +oeh3ri +2o1ei +oe4is +oe5isch. +3oe2ko3 +oe2kr +oe2l +3oel. +oel3an +oel3au +oelb5ste +oel5ein +4o3elek +oel5ent +oe3ler +oeles4 +oel4fei +oelfen4 +oel6f5end +oel3in +oel4kle +oel4kli +oel3le +3oelm +oelo2 +3oelr +3oels. +oel3sz +3oelu +3oelv +3oelw +2oem +o3em. +oe4mie +2oen +oen3ec +oen5erg +o3entf +o3entl +oentu3 +oe3nu +oe3ota +2oep +oepf3l +o3e2pi +oe4r3a2 +oer4b3l +oe4r3ec +oe4r3ei +oe4rel +oe4r3e4m +oe4r5ene +oerer6fa +oerer6fol +oerer6gebn +oerer4l +oe6r5ermu +oe5r4erz +4oerf +o3erfi +o3ergä +oe4r3il +oerner6v +oer3oe +o3erö +oers4k +oert6er +o2es +oe2sa +oe4scha +oe6schef +oe6sch5ei +oe6schene +oe6sch5eri +oe4schl +oe4schm +oe4schn +oe4scho +oe4schr +oe4schw +oes2p +oes6s5che +oes4st +oest3a +oes3te +oes3tr +4oesu +oeße4n3 +o3et. +oe4tag +oet3ei +oete4n3 +oet2h +oe3t4he +oe4tho +oe4thy +oe2t3r +o1eu +oe2vo +o1e2x +o1ë +2of2a +ofa2c +of3a2d +of3a2g +of3är +o2f3e2b +o2f3ec +o4fena +o4feng +o4fenk +o4fent +o4fenw +o4fenz +o4f3erb +o4f3err +o4f5er4sc +o3fes +of3eti +of3eun +of2fa +off3al +off3an +off5eie +offe4l +offel5s +of4f3in +3offiz +of2fl +of2fr +off3ru +off3rü +off3sh +off3si +offs4o +off3t2 +of4f3uh +of4fur +ofgas3 +2ofi +of3ins +ofi3s4 +ofi5sc +2o1fl +of2la +of2lo +of2lö +2ofo +o2fop +of3orc +2o1f2r +of3rä +of4s3am +of4sei +of4s3en +of4sin +of4sof +of4spa +of4spe +of4staf +of4sto +of2s3u +of3th +of4t3ri +oft3s +2ofu +o2f3uh +o2fum +o2f3ur +2og +o2gab +o2g3ac +og3ang +og3arb +og3ari +og3aus +og3d +og3ein +ogel3i4 +ogen5ec +ogener6n +ogen4id +ogens4p +o4g3erf +ogeri4 +o4g3eth +ogfi2 +ogge4n +ogging5 +og2hu +ogin3 +o4g3ini +o4g3ins +ogle3e +ogleer4 +og2lo +og2n2 +og4nas +o2g3nu +ogo3e +o3gog +og3o4ri +o2g3ou +og3rau +ogs2c +og3spr +og2st +og3ste +og3sti +ogu2 +o1ha +oh3alk +o2h3ar +o1hä +o1he +o2h3e2d +o4h3ein +o4h3eis +ohen5s +o4h3er4t +ohe3x2 +o1hi +ohkos4 +ohkost5 +ohl3a +ohla4d +oh3lec +ohl5eint +ohl5eis +oh3lep +ohler4 +oh4l5erg +oh4l5erh +oh4l5erw +oh3les +ohl3o4r +ohl5sa +ohlste4 +oh2lu +oh3mi +oh3mu +ohn3ac +ohn3ap +oh4n3e4b +oh3nee +oh3nel +oh6nense +ohn5ersa +oh4n3e4t +oh4nin +3ohnm +oh2n3o +ohn3s4k +o1ho +oh3oel +oho4l3a +oho4l3o +o2h3o2p +oh3öl +ohr3a +ohre2 +oh4r3el +oh4rem +oh4r5er4g +ohr5erh +ohr5erk +oh4r5ero +oh6rer6sa +oh6rersä +oh3rie +ohr3im +oh4r3in +ohrs2 +ohr3sk +oh1s +ohs2p +oht2 +o1hu +o2h3um +oh3w +o1hy +o1i2d +oid3an4 +o2ik +o1i2m +oimm2 +o1in1 +oi4nen +oi2n3i +o1i2o +oi2ra +o5isch. +o4ische +o3ism +o3isr +oiss4 +ois5sa +oi4ta +oi2tu +ojek6tori +o1k +oka3b2 +okabe4 +o2kac +oka3i +okale4 +oka4lei +oka6lere +oka4lin +okal5th +oka3pf +okas3 +oker4be +oke3re +okka3s4 +ok2la +ok2li +ok2lo +ok2lu +ok2o +oko4pt +ok2r +ok2sa +ok2s3o2 +oks3p +okt4o +okto5b +oktobe4 +ok5tor +o4kula +1okw +2o1la +ol3a4br +ol3abu +o4l3ach +olade4n +ola3ei +o4l3aet +ola3fl +ol2al +ol3a4mi +ol3ana +olars4 +ol3a4ve +o1lä +o2l3ät +olb2 +olbe4n3 +ol3che +olch3r +ol5da. +ol4dae +ol2dä +ol4d3ed +old5ersa +ol4d5erz +olde4s +ol4d5ese +ol4deu +ol4dim +ol4dop +oldo4r +ol4d3re +ol4d3ri +ol4d3ro +ol4drü +old3sa +old4sam +old4sk +old3st +old6stro +ol3eie +ol3emb +ol3emp +ole2n +olen3e +ole3ra +o4l3erk +o4l3ert +o4l3erz +o4l3ess +oles4te5 +ole3um +ol3ext +ol3exz +o1lé +ol4fak +ol4fau +ol4f3ei +ol4fem +olfer4l +ol4fle +olft4 +ol2gl +ol4gre +o3li. +oli3a +olie4n3 +oli4kan +ol3imp +ol3inj +oli4tor +oli5tur +oli3ze +ol2kl +ol4kre +ol4kro +olk2s +olks3c +olks3t +oll5ach +ol4l3ak +ol4lanh +oll5anz +oll5ass +ol4l3au +oll3e4c +oll5ein +ol4l3el +ollende6f +ol4l5er4d +oll5erfo +ol4l5erh +ol4l5er4m +oll5ertr +oll5erwe +ol4l5erz +ol4l5ess +ol4l5ins +ollla6der +ol4l3op +oll5s4a +olls4p +ol4lum +2olo +oloni4e +olo5pe +olo3ra +ol3ort +ol2ov +ol3s2k +oltai4 +olta5is +ol4t3am +o1lu +olu4ta +olu4th +o1lü +olve4 +ol2y +4olys +ol2za +olz3a4c +olz3ar +ol4z5eig +ol4z5en4g +ol4zep +olzer4 +ol4z5ern +ol4z5ers +ol4z5ert +ol4zik +ol4zim +ol4z3in +ol2z3o +ol2zö +ol2zw +ol2zy +2om. +o4m3a4bl +o4m3a4br +o4mada +o4m3a4ge +oma3la +o4m3alg +omali4e +om3all +o2m3a2m +4oman +oma4n5er +om4anh +omani4e +om5anke +om4anr +o6mantei +o4m3arm +omar4s +o4m3art +omar4te +o4m3a4sy +4omat +o4m3atl +o6m5atom. +o6m5atome +o2m3au +ombi4e +ombu3c +2ome +o2meb +om3ebe +o2m3ef +om3ein +o4meis +om5eise +o4mele +ome3na +om5ener +o2mep +o5mer. +om3er4h +omer4ke +omer4sp +om5er4tr +o4meru +om3erz +o4mesh +o2m3eu +o2m3ex +omi4c3 +omi2e +omie3s +o4m3inf +o4m3inj +o4m3inn +omi3n4o +o4m3ins +o4m3int +o4m3inv +o4miso +omi3tr +2omm +omme4la +1omn +o2m3oa +om3ope +o4m3orb +o4mord +o4m3org +om3o4ri +ompani4 +om3pf +2oms2 +omt2 +o2m3um +o4m3unf +o4munt +o2m3ur +o3mus +o1mü +2o1na +o2nab +ona4be +on2ac +onaer4s5 +ona5ge +o2nak +onaler4 +ona4l5in +on3amb +on5amei +on3amt +on3ann +on3a4no +on3apo +on3app +on4are +o4narm +on4arr +on4ars +on3art +ona2s +on3asp +on5atom +onat4s +onats5c +2onä +onär4s3 +on4d3an +onda4s +ond5ast +on4d3ei +onder5ei +onde6r5ers +on4d3ex +ondi4e +ond5inn +on4dob +on4d5ril +on4d5rin +on4d3ro +ond5ruec +on4d5rüc +ond4sa +ond4sei +ond3st +ond4ue +2one +o2neb +on3e4ck +o2n3ef +on3ein +one2l +on3ela +on3ele +on3ema +3onemp +one2n +o4n5ends +on4eng +onens4 +on3ep +o4n3erd +o4n3ere +o4n3erf +oner4fa +on3erg +o4n3erh +o4n5erke +o4n3er3o4 +on3erö +on5ersa +o4n3erz +o4n3est3 +o4netu +on3f2 +onf4l +ong2 +on3ge +on3gha +on3gla +on3gle +on3go +ongru5e +ong3s2 +on3gu +2oni +oni5ake +onialer6 +oni5a6lerw +on3ido +oni4enk +oni4enm +onie3s +oni4g3a +oni4ger +oni4gl +onig3s4 +o4ni4kan +o4nikba +o4nikbe +o4nikbr +o4nikei +o4nikf +o4nikk +o4ni4kl +o4nikm +o4nikp +o4ni4kr +o4nikse +onis4o +2onj +on3k2 +3onke +onli2 +onlo4c +onloch5 +on3na +on4na4b +onn4an +on3n4e +onne3s +ono1 +on3o4be +on3ode +on3ofe +o4n3oke +on3orc +onos2 +onot4h +ons3a +onsch4 +on4s3eb +onsen5e +onser5e +onse4t +onsi4d +ons5ing +ons5kl +on4spar +onst4a +on6stapel. +onste4r5 +ont5arm +on4t5auf +ont5end. +ont5erfa +on4t5er4h +ont5erke +on4t5erl +on4tho +onto3e +on4t5rat +on3tu +o1nu +o2nue +2onuk +on3ums +on3url +2onut +1ony +on3z +onz4e +onze5in4 +onze5la +onze5r4a +onzer6t5ak +2oo +oo2bo +oo1c +oo2dr +o1oe2 +oo3en +oo3ge +oo2gl +ooki2 +oo2k3l +oo2kn +ool3c +oomgene6 +oomo2 +oo4mop +o1op +o1or +oo4r3a +oor3d4 +oor5de +oo4r3ei +ooren4 +oo4r5ent +oo4rer +oo2s3a +oo2sk +o3osm +oos2p +oo2s3u +oo2t3a +oot3ei +oo2th +oot3he +oo2t3r +oot4s3a +oot4s5en +oo4tur +o1ö2 +2op. +op3adr +op3aeh +opa3i +op3akt +opa4let +op3ant +opar4a +opa6rade +opa4rit +op3arm +op3äh +4o3ped +o2p3ef +op3eig +o3pek +4o3pel +o3pen +ope4ne +openty4 +4opep +3o4pera +op3erh +3opern +o5pers +op3ey +op2fa +opf3ac +opf3af +op3fah +opf3al +opf3ar +opf3au +op2fä +op4fem +opfe4n +opf5ent +opf5eroe +op4f5erö +op4fin +4opfl +opf3lo +op3flü +op2fo +opf3r +opf5ste +oph2 +op3him +op3hit +o4phol +oph3t +o1pi +opi3a +opia4t +op3id +opi5er. +opi5ers. +opie4r5u +op3i2k +opin2 +oping3 +op3lag +op3lic +op3lis +op2ne +opo4la +op4pan +oppe6l5in +oppin4 +op2p3l +3oppo +2o1pr +op3rec +op3roe +op3rö +3opsi +op3sta +op3sto +op3stu +op3sz +1opt2 +2opt4e +op3th +2opu +2opy +o3ra. +o3raa +ora2b +or3abb +or3abr +or3abt +or3ace +or3ach +o3rad +o4r3add +or3adr +4orae +or3aeh +or3aet +o3raeu +o3raf +o3rag +ora4ge. +4o3rak. +or5akad +3orake +o3rakr +4o3raks +or3akt +or3a4ku +o5ralba +oral6ben +oral6bum +o3rali +o4r3alm +o3rals +oral4ta +oral4tä +ora3ma +or3a4mi +oran6fra +oran4ku +oran4kü +oran4me +ora4no +or3app +or3aps +ora4r3a +ora4rer +2o3ras +o3rat. +o3rate +o3rati +or3atm +o3rats +o4r3att +4orau +o3raub +o3rauc +or3auf +o3raum +o3ra3un +or3aus +oraus6gewaehl +oraus6gewähl +oraus6sche +oraus6wa +or3auß +o3rav +o3raw +o3rax +o3ray +o3raz +o1rä +or3äh +or3änd +or3ät +or4bana +orb5art +or4b5inn +3orbit +orb2l +orb5lad +or1c +4orca +or2ce +4orda +orda4m +or4darb +or4das +or4dau +or4d3e4b +or4d3ei +ord5els +ord5engl +4ordes +or4d3eu +or4d3it +3ordn +or2do +or4dös +ord3st +ord3t2 +or4duf +or4dum +2ore +orea3s4 +ore4ast +o2reb +or3e4be +o4r3e4ck +or3e4di +or3eff +o3reg +or3eig +or3ein +or3e4la +or3emp +or3emu +o4renbo +oren4er +ore4n3i +o4renki +oren6nen. +o6rennet +o4renno +ore4no +o4renpl +o6renpor +orens4 +o6rensen +or3erd +o4r3erf +ore3ri +o4r3erk +ore3ro +or3erp +or3erw +or3erz +or3et4h +o2r3eu +o2rew +orfa4c +or4fend +or4feng +orf3s2 +or3g4a +orge3i +orgein4 +4orget +or3ghi +4orgia +orgi5en +orgi5er +or2gn +orhu2 +o1ri +ori4ena +ori6enbe +ori4enl +ori6enme +ori4enn +ori4enr +5o6rienti +ori4env +ori6enwe +o5rier +ori5er. +or3ima +ori4mi +ori2n +o4r3ind +orin3i +or3ins +o4riso +4o3rit +ork2a +ork5erz +ork2l +ork3sh +orkt4 +ork5ta +2orm +or4mad +or4mam +or4m5ans +orm5asp +or4m3eb +or6m5erfa +or6merge +orm5inh +orm5ins +orm5int +or4m3op +orm5ord +orm5org +orm5ori +or4mum +ormu4n +or4m5uni +or4munt +ormvol4 +orna4c +orn5ack +or4n3al +or4nan +or4nar +or4nau +orn5erf +orno3s +2o1ro +or3o4be +or3oef +or3oel +o2r3ok +or3oly +oro5n4a +or3o4pe +or3opf +o2r3o2r +o2r3ox +o1rö +or3öl +orp2f +orrai4 +or3r4e +or5ren +orr2h +or3s2a +orsau4 +or3sä +orsch4m +orse3s +or3sh +ors2k +orspor4 +ors4tin +orst5ob +ors2z +ort5aer +or4tan +ort5an. +or4t3a4r +or4t5aus +or4tär +or4t3eb +ort5eiw +or4t5eli +orte4n +or4tent +or4tenz +ort5erb +or4t5er5e +ort5erfi +or4t5erk +or4t5erl +orter6lo +orter6lö +ort5ersa +ort5er6sc +or4t5erw +or4t5erz +orter6ze +or4t3e4v +or4thol +2orti +orti5en +or4t3in +or4t5obs +or4t5off +or4t5ori +or4t5ort +or2tö +or4t5res +ort5rin +ort5rol +ort5spe +orts4ti +or4t3um +4ortü +or3uhr +orum4s +or3un +oru2r +o3rus +or3u2t +o3rüc +ory1 +o2rya +orzel5 +os2a +osa3ar +o3sab +osa3er +osa3i +o4s3ami +os3art +o3s4a3s2 +3o2s3ca +2osch +osch5ar +osch5aug +o3sche +osch5lei +os2co +os2e +o4s3eie +ose3in4 +o3sen +o4s3er4k +o3s2hi +o3s2hu +o1sk +os4kal +o2ski +os4kle +os2ko +os3kol +o2sky +o3s4mog +o2s3oe +os1p +os4pac +o4spae +os4pas +o4spä +os4per +o4spf +o3sphä +os2po +os4pra +o3s2pu +oss4abb +os4s3a4c +os4s3ae +os4s5ala +os4sall +os4san +oss5and +os2sä +os4s5en4z +os4s3ep +os4s5er4b +os4s5er4f +os4s5er4r +os4s5er4z +oss5esse +os4sik +os4sim +os4s3io +os4sof +os4son +os4s3or +oss3o4v +os4spar +os4spe +os4spo +os4s3ta +os4sto +os4str +osstu5er. +os4stur +o4st. +ost5abl +ost3ac +ost5aer +o3stah +ost3ak +ost5ang +ost5ans +ost5apo +os5tarr +ost5auf +ost3a4v +4ostä +ost5einb +ost5eing +ost5einl +ost5einr +ost5einw +ost5eis +os5telz +o4ste4n +ost5engl +os6t5er6weit +ost6es. +o4s3tet +ost5eur +os2th +ost5her +os4t3i4d +os5tina +ost5ink +os4tins +ost3ir +os4t3o4b +os3toc +os4tom +os2tr +ost5rac +ost5räu +ost5rie +ost5rot +o3s4tuc +os4tug +os4tums +os4tune +ost5urk +os3tüm +osu2 +osyri3 +o3s2ze +os4zin +2oß +o2ß3el +o4ß3en4z +oßer4 +o4ß3erb +o4ß3ere +o4ß3erf +o4ß3erh +o4ß3err +o4ß5ersc +oß3is +o4t3abi +ota2c +o2t3ad +o4t3aer +o4t3aeu +o4tagg +ot3a4go +o2t3ah +ot3amb +ot3ano +ot3app +otar3a +o4t3arb +o4t3arm +o4tarz +otas3t +ot5astr +o4tatem +o5tati +ot3att +ot3auf +ot5auge +ot3aus +ot3äm +o2t3är +o2teb +o4t3eib +o4t3eic +otei4n +ote5in. +ote5ina +ote5ind +ote5ine +ote5ini +ote5ink +ote5inp +ote5inu +ote5inv +ote5inw +ote5inz +o4t3eis +ote4l3a +ote4lei +ote4ler +ote4l5in +otel3s4 +ote2m +o4t3emi +o5ten. +ote4na +o4t5ends +oten3s4 +o4t5entb +o4tentl +o3ter +otergene6 +ot3erl +oter4le +o4t3erw +o4t3es4s +ot4est +otes5tant +otfall5 +ot4h3a4c +ot4har +o3t4has +o2thi +ot3hin +ot3hir +o4t3hof +ot3hol +o2til +ot3ill +o2t3i2m +otima4 +oto3c +oto3e2 +ot3off +otop3f +oto4r5in +oto3sz +o2tö +otra5c +o3tran +ot3rec +ot3ret +ot2ri +ot3ric +ot3rin +ot3rut +ot4s3at +ot4s3el +otso2 +ot4s3ol +ot3s4pe +ot4stei +ots4tra +ot4stri +ot4stru +ot4strü +ott5akt +ot4taut +ot4t3eb +ott5er6kl +ott5er6lo +otter6lö +ott5erwä +otte4s +ot3to +ott5rei +otts4t +o3tund +ot3url +ot4zae +ot2zä +otze4d +ot4z5er4k +ot4z5er4r +oub2 +ouch3e4 +ou3chi +ou3et +ouf3f4 +ou3f2l +oug2 +ou3g4a +ou2ge +o1uh +ou3is. +ou4le. +ou4lel +ou4les +ou2li +oul3s +o1um +ou2n +o4und +oun4d5ei +o5undso +o3unf +oun2g +oure2 +ou4r3et +ourie4 +ourme4 +ourmet5 +our4ne. +ou2sc +ous4i +ousi4e +ou2st +out3el +3outf +outli4 +3outp +out3s4 +outu4 +o1ü +3o2val +ove3s2 +oviso5 +2ovo +2ow +o3wec +ow3ef +o4wer. +ow3eru +o2wh +owse2 +ox2al +2oxe +ox3ele +ox4erz +o3xi. +3o2xid +ox3l +ox2od +o2x1u +ox3v +1oxy +2oxy. +o1yo +oy3s2 +o3z2a +3o2zea +oz4en +ozen4ta +ozes4s5c +ozes4s5t +o3zi +ozialer6 +ozon3a +órd2 +ö3ba +öbe4la +öbe4li +öbe4lo +öbels4 +ö2b3le +ö2br +öb2s3 +ö1c +öch3l +ö2chr +öch6ste +öchst5ei +öchst5r +öchs4tu +ödi1 +öd2st +1ödu +ö1e +öf2fl +öf1l +ögen4s3 +ö2gl +ö2gr +ö1he +öhe2n3 +öhle2 +öh4nef +öhre2 +öh3ri +ö1hu +ö1i +öi2g +ö4ische +ö1ke +1ö2ko3 +ökos2 +ö2kr +ö2l +3öl. +öl1a2 +ölas2 +ölb2r +öl3ei +öl3em +öl3ent +ö3ler +öles4 +öl3ess +öl4f3ei +ölfen4 +öl4f5end +öl4fli +ölf3s +1öli +öl3im +öl3in +öl4kle +öl4kli +öl3le +ölle4n3 +1ölm +öl4nar +ölo2 +1ölp +2ölpe +öl4ple +1öl1s +öls2z +öl3tu +1ölu +2ölz +ölz2w +ö2mie +öm2s +ön3ec +önizi5e +ön2st +ön3sta +öntu3 +ö1nu +ö3o2ta +öoti3e +2öp +öpf3l +ör1a2 +ör2b3l +ör1c +ördens4 +ö2r3ec +ö2r3ei +ö2r3e2l +ö2r3e2m +öre2n +ö4r3ene +ö4r3ent +öre4rer +ö4rer4fa +örer6fol +ör6erfor +örer6gebn +örerin6t +ö4r3er4l +ö4r5ermü +ör3ess +ör4fli +ö1ri +ö2r3il +ö2r3im +örne2 +örner4v +ör1o2 +örs2k +ö1ru2 +ö4r3une +örungs5 +ö2sa +ö4sch3a +ö4schä +ö4schef +ö4sch5ei +ö6schene +ö6schent +ösche4r +ö6sch5er6f +ö6sch5eri +ö6sch5er6l +ö4schi +ö4sch3l +ö4sch3m +ö4schn +ö4sch3o +ö4sch3r +ö4schum +ö4sch3w +ös2e +öse3e +ö4s3ein +öse5str +ös4s5che +össe4n3 +ös2st +öst3a2 +ös3te +ös2th +öße2n3 +öß2ti +2öt +ö2t1a +ö2t3ei +ötel4n3 +öte2n3 +öt3ho +ö2t1o2 +ö2t1r +öts2 +öt2sc +öt2tr +öwe2 +öze3 +özes4 +1pa +3paa +pab2ä +pab4lu +pab4ru +pab4rü +2pabw +pach6t5er6 +pa3e2b +pae6cker +pae4d5er +2paeh +pa3ell +2paen +pa3erf +p3aerm +2paeß +2paet +pae6terb +pae6t5er6k +paet3h +pae4ti +pae4tr +paet3t +pa3fl +pa3fr +pag2 +2pa3gh +pa3ho +pa3ke +paki3 +pa3kl +pak4to +3pa3la +pa4lar +pala3t4 +3palä +3pale +pa3l4i +pal4mae +pal6m5ast +pal4mä +pal4m3o +2palt +pal4tal +pal4teb +pal4tei +pal4to +pal4t3r +pa3mi +pam3s +pa4nar +pa4n3at +pa4nau +pan3d4a +pan5de +pan5enz +pa4n3eu +p2anf +3panik +pa4nisl +pank2 +2panl +pan4nar +pan4n5e4b +pan6nene +3pa2no +pans2 +pan3sl +3pan3t4h +3panto +4pantr +3pap +papieren6 +papie6r5end +pap4pr +paps2 +papst3 +para3b +3parad +pa4rae +pa4r3af +par5akt +3param +pa4rant +3paras +pa2rä +2parb +4pare. +pa3ree +4paref +par5ein +4paren. +4parer +parer6geb +pa3reu +2parg +pargel6d +pa4r3id +par5ins +par4kae +par4k5am +par4k5au +par4kop +par4kr +park5ra +par4kue +2parm +par3ne +pa4rod +pa4roe +pa4rof +3parol +pa4ron +pa4rö +2parp2 +2parr +4parsa +4parst +4parsu +4parta +par3t4e +partei5 +parter4 +3party +2paru +pa4rue +2parv +2parw +par3z2 +pa4scha +pas5chaf +3pasi +pa3spo +pa3spr +5passa +pas6sein +passer4 +pas6serg +pass5ersa +pass5ersä +pas6s5ert +pas4sp +pas3t +pa5s4ter +pa3s4tr +2paß +pat2c +pat4e2 +4patel +pa5ten +3pati +pat3sa +4patz. +2p3auf +pa3uni +4p3ausl +4p3ausr +4p3ausz +3pazif +1pä +pä2c +päck5er +pä4d3er +2päh +2päl +2pän +2p3ärm +2päß +2pät +pä4ta +pät3e4h +pä4t3ei +pä4t5ent +pä4tep +pä4t5erb +pä4t5er4k +pät3h +pä2t3i +pä2to +pä2t3r +pät3s +pät3t +2pb +2p1c +p3ch +2pd2 +pda2 +pdi2e +1pe +pe2a +pea2r +pea2s +2p1e2b +pech3e +pech3s +4pecht +2peck +ped4a +pe5dan +pe4dri +3peel +peene3 +2p3eff +2pefr +pe4gal +pege4l3 +pe2gn +pei1 +2peic +2peie +2peig +pei4l3a +pe3im +4peis +peise5i +2pek +p2el +pe4l3ab +pe4l3ac +pe4l3ae +pe4lai +pe4l3au +pe4l3ax +pe2l3ä +pelb2 +pel3d +pe4l3e4h +pe4l5ei4c +pe4l5ein +pe4l5ei4s +4p3elem +pel5end +pel3er +peler5e +peler6sa +pe4l3et +pe4leu +pel3ex +pel3i4d +peli4n +pel4inh +pe4l5ink +pel5inn +pel5int +pe4lit +pelk2 +pe4l3ob +pe4l3oe4 +pe4l3or +pe2l3ö2 +pels2 +pel3sk +pel5ster +pe4lut +pel4zap +pena4b +pena4r +pe2nä +pend4a +pende4c +pende6l5a +pend4o +pe4n3en +penen5e +pen3f +pen5inn +peni4t +pen3k2 +penn4i +pe2n3o +pens4e +3pensi +pen3sl +pen3sm +pen5s4o5 +pen5s4p +3pensu +pen3ta +4pentw +penu2 +penz2 +3pep +pept4 +pe3ra +4perad +pe4r3ae +pe4r3ak +pe4r3as +pe4rau +pe2r3ä +pe4r3e4b +pe4r3ec +per5eig +pe4re4l +peren5e +pere3s +3perga +perik4 +pe5r4io +pe3rit +per6l5a6sc +per6l5aug +3perle +pern5en +per3o4b +per3oe +pe3ros +per4r5an +perr5eig +4persio +perwa4r +per4zeu +pes2a +pese4n3 +pes3s2 +pest5op +peten6z5a +pet2r +2pe2x +2pey +1pé +2pér +2pf. +p2fab +pf3abe +p2fad +pf3aer +pf3aeu +p2faf +pf3ai +p2f3a2k +pf3ame +pf3ans +pf3ant +p2fa2r +pf3are +p2f3as +p2fau +p2fäh +p2f3är +p2f3äu +pfdru4 +2pfe. +p2f3e2b +p2fef +p2fei +pf3eig +pfeil5s4 +pf3eim +pf3ein +pfe4l3i +pfels4 +pfe2m +pf3emi +p3fens +p2f3e2p +pfe4r3a +pfe6ran +p6f5ereig +pfer6gebn +p6ferleb +pf5erpro +pfer6prob +pfer6zeu +p3fes +pfes5ta +pfe5str +p2f3e2t +p2f3e2v +p2fex +pff2 +2pfh +p2f3i2d +pf3inf +pf3inn +p4f3ins +p4f3int +2pfk +pf2l +pf3lad +pf3lag +pf3lam +3pfleg +pf3lei +pf3les +pf3lie +pf3lin +pf3lis +pf3los +pf3lös +pf3luc +4pfluem +pf3lus +4pflüm +pf3ly +2pfm +p2f3o2b +p2foe2 +pf3oed +p2f3of +p2fom +p2fo2r +pf3orc +pf3ori +pf3orn +p2fot +pf3ott +p2fö2 +pf3öd +pf3ran +pf3rän +pf3ris +pf3rob +pf3roe +pf3ros +pf3rö +2pfs2 +pfsau4 +pf3s4e +pf3sk +pf3sl +pfspor4 +pf3str +pf3stu +pf3sz +2pft +pft4r +p2fuh +pf3uhr +p2f3um +3pfusc +2pg2 +p3gl +pgra4d +1ph +2ph. +phack3 +4p3haeh +pha3el +4phaer +pha3fl +2p3hah +pha3k +phal4t5a +phal6t5es +phaltest5 +p3hand +pha3s +pha4tr +2phau +2p3häh +3phän +2phb +2phd +phebe4 +phebel5 +2p3hei +phen3d4 +phe4n3e +phens4 +2phf +2phg +phik3a +phi4kan +phi4la +phir3 +phi4st +phit3e +2phk +ph2l +2phm +2phn +p2ho. +ph3oed +phori4e +ph2r +2phs +pht2 +4ph3the +phu2s3 +phu3t +2p1hü +3phys +2phz +1pia1 +piab2 +pia5ka +pi4a5li +pi4a3n +piap2 +pi2ar +pias2 +4pi3ce +pich3o +pich3s +p2id2 +pie3b2 +pie4gl +pi2e3i +pie4l3a4 +pie6leis +pieler6ke +pieler6kl +3piep +3pier +pie5rae +pie4reb +pie4rei +pie4rem +pieren6z +pies4st +1pig +1pil +pil4zer +pil4zi +pil4zw +pi3nal +pi3nas +pi3n4at +2pind +piner4 +pi4n5erf +2p3inf +3ping +pin4ga +pingen4 +ping5er +ping3s +3pinse +pin3sk +pins4p +pi3n4um +2pioi +pi5o4n5al +pi3os +1pip +pi4pe. +pi4pel +pi3ra +5pirate +pi3ri +3piro +pir3u +1p2is +2pi3s2k +2piso +pita6l5ent +pite2 +pitel3 +pites3 +2pi3t2h +pit2s +2pitz +pit4za +pitz4e +pi4z3in +3pizz +2pj +2p1k2 +pkur3a +1pl2 +2pl. +p3lab +2p3lad +pl4an +p3lanz +pla2q +p2lat +p2lau +pla2y +p3lea +ple3c +p2leg +p2lem +ple3n2 +4p3lere +p2leu +p2li1 +2p3lig +pli3n4a +p2lo +2p3lok +plo4min +plo3r +p2lö +2pluf +2p3lun +plu3r +plu2s3 +plus5s +2p1m2 +p3ma +p3mö +2p1n +pnoe3 +1po +2pobj +po1c +3pock +pocke4 +3pod +po5e4ben +po3ems +po3erb +po3erh +po3ets +2p3off +2poil +po2in +pok2 +3pol +po6lacke +po4l3an +po4l3au +pol4d3a +pol5ein +pol5eis +po4lep +polo3p4 +polo3s +po4lum +pol3z2 +pom4ph +p2on +2pond +poni2 +pont2 +3poo +po3o2b +poo4len +popa2 +po4p3ak +po4p3ar +po4phi +po2pi +po4p3li +pop3s2 +2popt +po3ral +po3rat +po3rau +por2ä +po3rär +pori4e +4porn. +pors2 +por4tae +por4t5ak +por4t5ei +por4tem +porter6l +por4t3h +por4t5is +5porto. +5portos +5portra +por6t5raeu +por6t5rat +por6t5rau +5porträ +por6t5räu +port5ren +por6t5ric +por4tro +port5sz +3po3ru +po3s2p +po2st +po4sta +pos4t5a4g +postasi6 +post5au +post5ein +pos4tem +pos6t5er6h +post3h +pos5til +pos4t5or +post5ra +post5re +post5ri +3pota +po4t3ar +3potä +3pote +poti4e +pot3sp +pot4t3a4 +pot4t3r +po4t3um +po3un +povi2 +po2w1 +po3wa +powe2 +1pö +3pöb +pö2bl +pö2c +pöl2s +2p1p +p2p3a2b +p2p3ac +pp3alt +p4p3ank +pp3ans +pp3app +pp3arb +p2p3at +ppe3e +pp3ein +pp3eis +pp3eit +p3pek +ppe4l3a4 +ppe4len +ppe4ler +ppeli5ne +ppe4lo +ppe2n3 +ppens4 +p4p5erze +pp3fa +ppfen4 +pp3fr +p2pfu +p2p1h +pp1l +p4plac +p2plä +p4plet +p5plik +pp3lis +pp3lo +p2p3oe2 +p2p3oh +p2p3o2p +pporter6 +p2p1ö2 +ppp2 +p2p3ra +p2p3rä +pp3rec +p4p3reg +pp3ren +p2p3ri +pp3roe +pp3rol +pp3rot +pp3rö +pp3run +pp3rüc +p2ps +pps2a +pps2p +pp3sta +pp3sto +pp3str +pp3stu +pp3sy +ppt4a +ppt4e +pp3to +p3puc +p2p3um +p2r2 +3praef +5praeg +3prael +3praem +3praep +3praes +3praev +3praez +pra3f +3prak +3pral +pra3r +3prax +3präf +3präg +3präl +3präm +3präp +prä3q +3präs +3prät +3präv +3präz +2p3re. +pre6cher6l +3pred +2pre2e +pree3i +3prei +p3reih +prei4s5c +prei6s5ei +preis5s4 +prei6st +4preiz +3prem +pr4en +pren4ga +pren4gl +3pres +pres4st +p3resu +pri2e +2p3rig +pri2l3 +3prim +pri4met +4pring +prings4 +3prinz +3prio3 +3pris +pri2t +prit3a +prit5er4 +prit5t +4pritz +3priv +1pro +3prob +probe5i +probein6 +2proc +3prod +2proe +3prof +3prog +3proj +3prop +4pross +pro3st +3prot +pro4ta +pro3te +prote5i +4prott +pro3x +3proz +2prö +2p3rub +2pruc +4pruec +3pruef +3prueg +4prueh +4prung +prun4gl +prung5sc +prun4kr +2p3rut +2prüc +3prüf +3prüg +2prüh +2prün +2ps. +p2s3ad +2psai +2psan +ps3ang +p2s3au +2psb +2psc +psch4l +2pse +ps3ein +p2seu +2psf +2psg +2psh +ps2hi2 +2psi +psi2e +psie3n4 +psie3r +ps3ind +psis3 +2psk +2psm +2pso +p2s3oe +2p1sp +ps2pe +2pss2 +pss4a +ps5sau +2pst +ps3taf +pst3au +p3stea +p3stel +ps3tes +ps2t2h +ps3tha +ps2to +p2s2tu +2psv +2psw +1p2sy +2psys +ps2ze +2pt +pt3ak +pt3alb +pt3am +p4t3ana +p2t3ar +p2t3a2t +pt3au +pt3ax +pt1ä +ptbau6mes +p3te. +p2t3e2b +pt3ec +pt3e2d +pt3ef +pt3ei +pt3ele +pte2m +pt3emi +pt3emp +4pten +p3tend +pt3eng +pten4ge +p3tens +pt3ent +pt3ep +pt3erb +pter5ei +pt5ernt +pte3ro +pt3erw +pt3erz +p3tes +p3tet. +pt3eta +pt3e4ti +pt3eun +pt3e2v +ptgene4 +p2t1h +pt3ide +pti2n3 +pt3ind +pt3inh +pt3ini +pt3int +pt3ir +pto3e +pto4mo +pto4n3a +pto4p3 +ptor3a +pto2w +pt3rol +pt3ru +pt1s2 +pt4soe +pt3uh +pt3ums +p3tung +pt3urh +pt3urk +pt3url +pt3urs +pt3urt +1pty +ptz2 +1pu +pua1 +3pub +2puc +pu2dr +2pue +pueb2 +3puef +pue4lei +pue4l3o +puf4fa +puf4fr +2p1uh +2puk +pu4ker +pu2kl +pu2k3o +4pule. +pu4l3in +pul4s5ac +pul4sp +pul4s3t +pult3a +3pulv +2pulw +2pumg +pum4pa +pumpe4 +pump3f +pum4p3h +pum4p3l +pum4p3r +2pund +pun2e +3punk +punk4tr +pun2s +2punt +pu2r3a +pure2 +pu4r3ei +pur5erk +pur5erw +purg2 +pu3ri +pu2r3o2 +3purpu +pu2se +pus2h +pu3she +pus3t +puter5s +3puts +put6schm +put6schn +3putz +1püf +2pül +pü4lei +pül3l +pü2l3ö +2pv +2pw +pwa2r +1p2y1 +py3s2 +py3t +2pz +1q +2qs +qu2 +3qual +qu4ar +que4rei +que4ri +quer3n +que4te. +que4tek +2quew +2quip +3quo +ra2ab +r3aal +ra3arb +ra4b3ar +rab4ble +4rabd +ra4b3ei +rabe4l +ra4bep +rab4er +rab5erd +rabe4s5e +2rabf +4rabg +4rabh +ra4b5itu +2rabk +4rabla +rab3lo +2rabn +ra2br +4rabre +r4abro +rab4rü +4rabs +2rabt +4rabw +3raby2 +4rabz +4r3acet +ra6cher5ei +ra6ch5erf +ra6ch5erh +ra6cherk +rach5in +4rachs +rach5sk +rach6t5er6h +rachts4 +racht5sp +rach3u +3r2acr +r2ada +ra4dac +ra4d3a4m +ra4d3an +4radap +3radar +rad5ein +4r5a4del. +rad5ende +rad5enz +rad5erf +rad5erk +ra4dern +rad5ers +rad5erz +rade3s +3radf +3radh +r4adi +3radio +3radiu +3radmu +3radp +ra2dr +rad3ri +rad3s +rad4sor +3rad3t4 +3radw +ra2e +raech4s +r2aed +2raeg +2raeh +ra3elb +ra3elf +ra3elk +ra3elr +ra3els +4raem +4raenk +r3aepf +2raer +r4aer. +ra3erk +r3aerm +3raers +rae4sc +raes5sc +rae3te +raete5i +3raetl +4raeue +4raeul +4r3aex +rafa5els +raf5ahn +rafe2 +ra4f3er +raf3la +raf3lü +r4afo +raf6t5erz +raft3s4 +rag2a +4ragg +2ragm +ra2g3n +3ragou +rag4stä +3rah. +rahle4n +3r4ahm +r3ahn +ra3ho +rah4ter +rah4t3r +rai4c +rail4l +ra3ims +ra3ind +1raj +4rakad +3rakan +3rakat +2rakk +ra3k2l +ra4kre +4rakti +rak4tin +3rakue +3rakü +2rakz +r4al. +rala2 +ral3ab +ral3ae +ral3ag +ral3ak +ra5lamp +ral5ant +ral3as +ra5laue +ra2lä +ral3b2 +5ralbea +r5alben +ral5bl +5ralbuc +ra4lel +ral5ents +ra4l5erb +ra4l5erl +ra4l5erm +ra4l5ern +ra4l5erz +2ralg +ra4l3id +ralie4n +rali5er. +rali5ers +ra4l5ind +ralin6de +ra4l5inf +ralin6ge +ral5insp +ra6l5inst +ralin6str +ralin4t +ra4lin4v +ra4lir +ralk2 +r3alk. +2rall +ral3la +r5alleg +r6allin +4rallt +r3allu +r3allü +3rally +4r3alm. +ra2lo +ral3os +3ralö +r3alp. +4ralpe +3ralq +ral3s4k +ral5s6tern +rals5tuer +ral6s5turm +ral6s5tür +ral4t5ak +r3altä +ral4tem +r4al3th +2ra2lu +ral3un +3ralü +3ralz +r3a4mei +3rami +ram4mab +ram4man +ram4mas +ram4med +ram4mei +ram6mens +rammer5 +ram6m5ere +ram6m5erk +ram6m5erw +ram4mor +ram4m3u +ram4p5le +3ramsc +4r3amt +ramt4s +ran5ade +ra4nae +r3a4nal +ra4nan +ra4n3a4r +ra4nau +r3a2nä +4ranb +ran4ce. +r4anda. +ran6d5ala +r4andas +ran6dau. +r4anden +ran4dep +ran6d5ere +ran6d5erh +rand5erke +ran6d5erm +ran6d5err +ran6d5ers +ran4dob +ran4dre +rand3s +rands4t +4ranei +r3anek +ra4nenz +r4aner +ran5erk +ra4n5erz +ra4n3eu +3ranfi +ran4gan +4rangeb +ran6gerh +rang5sk +rang5s4p +rangs4t +rang5ste +rang5str +ran4i +rani3e +r3a4nil +ra3nin +ran4kr +2ranl +2r3anm +r4anmi +r3anna +ran3ne +ra4nod +ra4n3oe +4r3a4nom +2ranp +2rans +r4ans. +ran5skr +ran4spa +r5anthr +ranti4e +ran3um +4r5anzah +ran4zar +ran4z5er +ran4zin +4ranzu +4ranzü +ran4zw +3rap. +ra4par +4rapf +ra4pher +ra4pok +ra4pos +4rappa +rap4ple +rap4pr +4r3a4pri +2r1a2q +r1ar +r2ara +ra4rab +ra4r3ak +ra4r3an +4rarb +2rarc +r2ard +4rarea +3r4arei +raren5e +rar5erh +rar3e4v +r2arf +ra4rid +rari3e +ra4r3in +ra4rist +4rarm +rar4man +ra4rom +raro4t +rarot5i +r2arp +r2aru +r2arv +4rarz +rar3zw +r2as. +ra4sae +ra2sä +ra4schl +ra4s3eb +ra4s3el +4r3as4ph +4rass. +ra2st +ras5taf +rast5ei +ras4t3o +ra5stra +3rasur +4r3asyl +2raß +r4ata +ra4tae +ra4t3ar +ra2tä +ra4t3ei +4ratemg +ra6t5en6te +4rathl +ra3ti +ra4tid +rati4e +rat5inh +r3atmo +ra4toe +r3atom +rat4or +6ratorge +4ra4tro +ra4tru +ratsche6f +rat4se +rat4stä +4ratta +4r3atti +4r3attr +ratu4r5a +2rau. +r4aub +3raub. +rau4ban +rau4bel +rau4bri +r4auc +rauch5a +rau5chig +ra3ueb +raue4n +rau4ere +raue4r5i +2rauf +4raufb +4r3auff +4raufg +4rauf3l +raufs4 +4raug2 +2rauk +r4aum +5raumab +rau4m5a4g +rau4man +rau5me. +rau6ment +rau5mes +rau4m3i +r4aun +rau4nal +rauner4 +rau6nerz +r4aup +4raur +4raus. +4rausb +3rausc +rau6scher +rau6schn +rau6sch5r +4rausd +4r3ausg +4raush +4rausl +4rausr +raus5se +4rausv +4r3ausw +r4aut. +r4aute +rau6t5ent +4rauto +rau4tra +rau4tro +rau4tru +raut5s +2rax +ra2xo +r3axt +ray3o +3ra2za +ra4zub +2räb +räch4s3 +1r2äd +2räf +rä3fr +2räg +rä4g3ei +2räh +2räm +3rän. +3räni +3räns +2räp +2räq +2r1är +r2är. +rä3ra +rärm4s +rä3ro +rä2sa +rä2sc +räs3sc +rä3te +räte3i +2rätz +rä2u +4räue +2räul +räu6scher +4räuss +2räuß +2räx +2rb +rb3abb +rb3abe +rba4del +rba4lin +rba2m +r4banf +r4b3ang +r4b3ant +r4barz +rbb2 +rbby2 +rbe5erf +rbe5eri +rbei3d4 +rb5einh +rbein6te +rbe6lach +r6belaeh +rbe4lau +r4beläh +rbe4ler +rben4ac +rbengene6 +rbe5rae +rber4ei +rber6en +rberin6t +rber5k +rb5erke +rbe5rum +rbe3sl +r4beti +r2b3im +r4b3inf +r4blan +r6b5lasser +r4b5last +rb2le +r4b3le. +r4b3leg +rb5lein +r4b3ler +r4bleu +rb4lie +rb4lin +rb4log +rb2lö +rb2o +r4bonk +rboten6e +rb2ö +rb2r +rb3ras +rb3rea +r6brechts +rb4sam +rb4s3an +rb4set +rb4stau +rbs4teu +rb4sum +rbun4da +r2büb +rby2t +2rc +r1ce +rce2n +r3che. +rch3ei +rchein6s +rche4l +r3che4n +rchi3d +r3chil +rch3l +rch5mi +rch3n +rch3ra +rch3re +rch3s2 +rch5sp +rch3t4a +rch6t5er6f +rch6t5erg +rch6t5er6r +rch6t5erw +r2ch3u +rch3w +r1ci +r2ck +r3cki +rck3s +r1cl +r1cr +2rd +r2dab +rd2ac +rda5chu +r2da2d +rdaer4 +r2daf +r2d3ak +r2d3al +rd3ans +rd5anti +rd5anza +r2d3a2q +rd3ara +r4d3arg +rd3ark +r4d3art +r4darz +rd3a4si +rd3asp +rdär2 +rde4al +r4d3ech +rdein6fl +r4d3eis +r4d3elb +r4d3elf +r4d3emp +r3den +rden3d +rd5endu +rden4es +r6d5engen +rdenk4r +rden4s5a +rd5enth +r4d5ents +rde5ono +rde4r5ei +rde6rend +rde5r4er +rde6rer6g +rder6folg +rderin6du +rderin6s +r4derit +r4d5ernt +rder5oe +rder3ö +rdes4k +rde5stel +rd5estn +r5dete +rd3eti +rdeu4ri +r2d3e2x3 +rdi2e +rdie5ne +rdien4s +rdiensta6 +rdienst5ad +rdin4a +rd3inf +r4d3ins +r4d3ira +rdo4be +rdoe2 +r2d3o2f +rdo2l +rd3oly +r3dom +rd3oss +rdo4st3 +rdö2 +rd3öst +rd3rat +rd3rau +r4drea +rd6reig +rd4reis +rd3ris +rd3rob +rd3roc +rd3rol +rd3rom +r4drose +rd3rot +rd2rö +rdrucker6 +rd3rum +rd3run +rd4s5aen +rd4s3än +rds2p +rds4to +rds2z +rdt4e +rdte3l +rdt2s +r4dueb +rdu2f +rd3ufe +r2d3ug +r2d3uk +r4d3ums +rduns3 +r2düb +1re +re4ade +rea6l5erw +re2am +rea4men +rea2s +re3as3e +re3at. +re3ats +2reä +re4ban +re4bau +3rebel +reb4si +reb4sti +rech5ar +5recherc +re6cherfa +re6ch5erzi +4rechs +rech5tei +rech6t5erh +5rechtl +5rechts +re3chu +4reckg +4recki +4reckz +4recot +4recô +3recy +4redd +4r3edel +rede3r +2redi +r3e4dik +3redip +3redu +re3el +re3eng +re3er +re4es +3refe +2reff +r3effi +3refl +3refro +rege4l5a +rege4l5ä +regelei6g +2r3egg +re3gi +re2gn +2reh +re4h3ac +re4h3ar +re4h3ei +re4hel +re4h5ene +re4h5ent +re4hep +re4herf +re4h5er4l +rehgene6 +reh3l +reh5le +reh3n +reho2 +re4h3or +3rehö +reh3ra +reh3ro +re4hue +re4h3ur +re2hü +r4ei. +3reiä +r2eib +rei4bar +reich6si +rei3de +4reidec +4r3eidg +r4eie +4reier. +4reiern +4reiers +r2eif +4reifel +2r2eig +reig4a +5reigel +r5eigene +5reigeno +r5eigensc +5reiger +5reiges +5reigew +5reigru +r4eih +rei4kab +reil4a +4reils +4r3eilt +4r5eimer +rei4mor +re3im4p +rei5nalt +4reinan +4reinb +rei5necke. +4reinf +4reing +4reink +4r3einn +4reino +4reinp +4reinr +re5in6sel +r5einsp +re3in4v +4reinw +4reinz +rei4sal +6reisant +4reisar +6r5eisenb +rei6s5ent +rei6s5erk +rei6sers +reises4 +4reisf +4reish +4reisk +4reisl +rei4so +reis5or +4reisr +rei5s6temp +4reisu +4reisv +4reisw +reit3s4 +rei4zan +rei6zell +rei6zene +reizer4 +rei6z5err +rei4zwi +4rekk +rekter6z +rek4tin +rek4top +rek4tum +rekturin6 +2rekz +2r3elb +rel4e +relea4 +4re4lef +re5lei +4r3e4lek +4r3elem +re4lend +r4elev +2r3elf +re4lim +4relit +4r3elix +rel4lar +rel4l5ei +4r3elsa +4r3elst +2r3elt +relu4d +r2em +r4em. +re4m3a4b +re4mau +2remb +rem4da +re4m3ei +re5men +re6m5erei +re4m5er4f +re4m5erl +4remig +r4empe +4r3empf +4rempo +rem4sel +rem4str +re4mul +r4en. +r4ena +rena4be +re3nad +re4nan +ren5aue +4r5endab +ren4d5an +rende4b +rende4k +ren6d5erk +ren6d5erm +4rendg +3rendi +re4n3e4b +ren3ec +ren5ende +re4nep +4r5energ +ren5erk +re4n5erl +re4n5erm +4renexe +3ren3f2 +4ren4gad +4rengag +ren4goe +4rengp +4rengs +4renhis +re4nid +re4n3im +ren5inn +4renju +renk4lo +renk4r +3renl +3renm +ren4nar +ren4n5e4b +renn5e4c +ren6nene +6rennen6n +4renng +ren4n5in +renns4 +3reno +re4nos +renrue4 +renrü4 +rensch4 +4r5ensem +ren6serg +ren6sing +ren6s5pan +ren6spat +6renstip +ren6s5tri +4r3entd +6rentera +4r3entf +5rentfo +4rentgi +4r5enthä +ren4thi +4r5enthu +4r3entl +rentmo4 +ren6tric +ren4trü +4r3ents +5rentss +4rentw +4rentz +4renue +4renumg +2renü +r2enz +ren4z5ar +ren6zerg +ren6zer6l +ren6zerm +renz5ertr +ren6zer6w +ren4z5in +ren4zut +ren4zw +4r3enzy +re3ou +3repe +4r3e4pen +re3pf +2repi +re4pis +4r3epoc +2repp +3repra +3reprä +4repren +3repu +r4er. +rer3a4b +re3rad +4r5er4bie +6rerbreg +4r3erbs +4rerdb +4r3er4dr +rere4d +re4r5eid +4r5ereig +4r3e4rek +re4r5ent +re4r3ep +re4r5ers +re4r5erw +re5res +r3erfa +4rerfah +r3erfo +4rerfol +r6erform +rer4fue +4r3er4fü +4r3ergä +6rergebn +rer4gom +rer4gri +4r3erhö +r2eri +re4r3id +re4rim +rerin4f +rerin4s +rerin6ter +rerk2 +rer4kan +5r4erki +5r4erko +5r4erkre +5r6erlaen +rer4lan +5r6erland +4r5erlau +5r4erlän +5r4erli +4r5erloe +4r3erlö +4r5ermae +4r3ermä +4r5ermit +rer6mued +rer4müd +4r3ernä +rer5ne +4r3er4ni +4r3erns +4r3ernt +rero4b +4r3erod +4r3eroe +rer3o4v +2r3er2ö +rer5reg +5rers. +rer6sätz +rer4seh +rer4sto +4r3er4su +rer4tei +re2ru +rer4wac +4r3erwä +6r5erwerb +4rerzae +4rerzä +rer5ze. +5r4erzei +rer5zen +rer5zes +5r4erzu +5r4erzü +5r4erzy +r2es +r4es. +re4s3an +re4s3au +rese4h +3reser +reser5e +3rese4t +re4s3in +re4s3of +3resol +3reson +6ressen +res6s5erf +res6s5er6m +res6s5erp +res6s5er6w +ressgene6 +res4spa +3rest. +re5start +res4tas +5reste. +rest5ein +re5stel +5resten4 +res6tent +res4tex +re4stö +rest5rau +rest5ric +3rests +res4tu +4r3etab +re4t3ak +4r3e4tap +4retat +retex4 +re2th +4rethi +re4t5rol +rett3a +ret4tan +ret4th +rett3s4 +reub2 +reu4eri +reu3g2 +2reul +re3ums +re3ung +4r3eunu +2reup +2r3eur +reu4z5ei +2reü +2reva +re4val +revil4 +rewa4r +re2wi +rewin4 +2rewo +2r1e2x1 +2rey +1ré +2r1f +rfa5chu +r2fap +r5fast +rf3aue +rf4äh +r4fense +rfen6ste +r4f3erd +r4f5er4la +rfer4le +rf4es +rff2l +rff2r +rfi4le. +rf3inf +rfin4s +r2fj +rf2l +r4fland +r4f3lic +r2f3o2b +r2f3o2f +rfolg4s +rf2r +r4f3ras +r4f5rauc +rf2sa +rf4säu +rfs3id +rf4sor +rf2su +rf2u +2rg +rg2ab +r2g3a2d +r4g3aeh +r4g3aem +r2g3ah +r2g3a2k +r4g3alb +r4g3alp +rga4me. +rg5amei +rga4mes +r5gamo +r4g3amt +rga4ner +rga4nin +r2g3ap +rg5art. +r4g3ask +r5gasse. +rga4st +rgast5a +rgast5ei +rg3auf +rg3aus +r2gäh +rgd2 +rge4ant +r4g3ech +r4g3eid +r4g5eise +rge4l5ac +r4g5elef +rgel5ein +rgel5in +rgene6rals +rgen6gel +rgens4p +r4g5entr +rgen4zw +rge4ral +rge6rang +rge4rau +rgerin6t +r4g5e4tap +rgg2 +rgi5er. +rgi4sel +r4gl. +rg2le +rg3le. +r4gled +rg3leh +rg3ler +r4gleu +r4glig +rg4loec +rg4log +rgmi2 +rgn2 +rg3nar +r2g3ni +r2gno +rg3nom +r2g3ny +r2g3oa +r2go2b +rgoe2 +r4g3oed +r3gog +rg3ora +rgö2 +r2g3öd +rg2r +rg5raen +rg3rai +r4g3ral +rg5rand +r4g3rän +r4g3reg +r4g3res +r4g3ret +rg3rüs +rgs4al +rgs2c +rg4sel +rgs4en +rgs2i +rg3s4pe +rgs4pi +rg3spr +rg5stei +rgs4tol +rgs4tra +rgs4tub +rgti2 +r1h2 +2rh. +r2ha. +2rhaa +2rhae +2rhah +2rhak +2rhal +rhal4b3 +2rhan +rhang5s +2rhar +2rhau +2rhä +r2he. +2rheb +2rhef +4rheit +2rher +rhe4r3e +2rhi +r2hod +4rhoel +2rhof +rhof3s +rho2i3 +2rhol +2rhot +2rhöl +2rhs +2rhue +rh4un +2rhü +3rhyt +ri5alei +ri5a6l5erw +ri5alim +ri3am. +ri3ams +ri3ang +ria3no +ria3p +ri4ara +ria3s4t +rib4bl +rib2l +ri3ce +rich3a +ri5chlo +rich5ska +rich4sp +richtgene6 +5richtl +richt6s5e +richt6s5o +ri4cki +ri4con +ri4dar +ri4dau +4r3i4dea +4r3idee +ri4d3e4l +ri4dent +4ridol +2ri2dy +r2ie +rie4bac +rie5blo +rie4b5ru +rie4b5rü +rie4fem +4riefm +rie4fr +rie5g4r +rieg4s +ri2e3i2 +riein3 +ri3el. +rie5la +ri3ell +ri3els +rie5n6ade +riene4 +riener5i +ri5enro +rie5rad +rier5ei. +riere4n +rie5res +ri3ern +ri4ernu +rie3sa +rie5sta +ri3eur +ri2fa +rif3an +ri2fä +ri4f3ei +ri4f3er +rif6f5en6d +rif4f5er +rif4fr +ri4fin +ri2f3o +rif3s2 +rif4t5er +1r2ig +riga3e +4r3i4gel +ri4g5ene +ri2gl +rig3la +rig3lä +ri3g2o +ri2g3r +ri1i2 +rika3s4 +ri2kä +ri4kin +ri2k3l +ri2kn +ri4kone +ri4kor +ri2k3r +ri4kun +rila4b +rilf2 +rille4n5 +2rima +ri4mag +ri4m3au +ri4me. +2rimm +2rimp +rim2s3 +r2in. +ri3na +4r3inan +4rinbe +3rinde +rin4dia +4r3indu +ri3n2e +rine3i +rinein4 +rine3s4 +2r3inf +rin5fr +5rinfre +r2ing +rin4g3a +rin4gei +6ringeni +rin4gle +rin4gor +rin4gr +ring5sc +ring5s6en +ring5sti +ring5str +2rinh +5ri5ni. +4rinit +2r3inj +rin4kal +rin6k5ent +rin4kl +rin4kn +rin4ko +rin4kri +2rinl +6r5innenm +4rinner +r5innere +r3inno +4r3innr +4rinnta +rino3 +2r3in2q +2rins +r4ins. +rin4sc +r4insen +rin4sig +rin4sol +5r4inspi +r5insti +3rinsy +rin4t5an +4rinte +rin6tent +rin6ter6g +r4intr +rin4t5re +2r3inv +rio2d +2r1ir +r2is +ri6schei +ri4schl +ri4sch5o +rischoe6 +ri4sch5ö4 +ri4schw +risen3 +3risik +3risk +ri3s4ko +4risl +rismu4 +4risol +4risom +ri2sp +ri4spi +ris3pr +ris4ser +ris4sp +ris4ste +ri4st +rista4 +rist5ab +ris6t5erf +ris6t5ers +ris3to +ris4t3r +r2it +4r3i4tal +rit5ant +2ritr +rit4ra +ritre3 +rit3s2 +ritt3a +rit4tan +rit4tau +rit4t3h +rit4to +rit4tr +ritt4s5o +3ritua +r3i4tum +rix1 +rizi4s +1rí +2rj +2r1k +rka4b5l +rk3adr +r2k3a2g +rk3all +r4kanl +rk3are +r4k3att +rkauf6st +rk3edi +r4kelem +rke3lo +rke2n +rkens4t +r3ker +rker4he +rker4le +rker4we +rk3ide +r2k3im +rk3inh +r4k3i4n3i +rk2le +rk2lu +r4knut +r4k3o4be +rko3ef +r4kord +r4k3o4ri +r2k3ou +rk2ö +rk2r +rk3ren +r4k3rom +r4krou +rk4s3al +rks3au +rk4s5cho +rk4s5ein +rk4sel +rks5ern +rk4sin +rks5inn +rk2so2 +rks3or +rks4pen +rk3sta +rkstati6 +rk4stoc +rk4stor +rk3str +rk5s4tue +rk4sum +rk4t3ak +rk4t3an +rk4t5eng +rk4t5ent +rk4t5erf +rk4t5erg +rk4t5erk +rk4t5erl +rk4t5ero +rk6t5ersc +rk4t5erw +rk4t5erz +rk4t5eta +rk4t3in +rk4t3o4b +rkt5ope +rk4torg +rk4t5o4ri +rk4t5rad +rk4t5rat +rkt5rei +rk4t3ri +rkt4sor +rk4t3um +rku2n +rk3una +rk3uni +rku4ra +rku4sa +rkus3t +2r1l +rl2a +rlage4n5 +rlags5t +r3lan +r5land +rlan4d5i +r2l3ar +rlas4t5r +r4l3au. +rl1c +rle2a +r3lec +r5lei. +r3lex +r3li +rli4ne. +rli4nee +rli4nem +rli4nes +rli2s +rloes5s +rlog5in +r3lor +rlos3t +rlös3s +rl3skr +rl3ste +rl4sto +r3l2u +rlust5a +rlus4t5r +rlz2 +2r1m +rm3ade +rma3le +rma4leb +rm3ami +r4m3amo +rma5n4e +r4m3ank +r4manl +r4manp +r4mantr +r4m3aph +r4m3a4po3 +r4marc +r4marz +r4matta +rm2ä +rm3d2 +rmd4e +r4m3e4ck +rme5erh +rme5erz +r4m3ei. +r4m5einb +r4m5einf +rme5inha +rme5inse +rme5in6to +r4m5einw +rme4ner +rmens4 +r4m5entf +r4m5ents +r4mentw +r4mentz +r4m3erb +rmere4 +rmer6fah +r4m5er4fo +r4m5erfu +r4m5erfü +rmer6geb +r4m3erh +rmer4ha +rm5erkr +r4m3erl +r4m5er4na +r4m5er4nä +r4m3erp +r4m5erse +r6m5erwei +r4m5er4ze +rm4es +rme3s4a +rme5sta +rme5str +r2mex +rmid2 +r4m3i4de +rmilch5 +rmi6nanz +rmi4nar +rmi6neng +rmin5it +rmin6terp +r2m3i2r +rmi6schl +rmi6schw +r4m3iso +rmiti4 +rmm4a +rm5man +r4m3obs +rmo3el +r4m3ofe +rmo4nau +rmon5s4 +rmo6reng +rmo4ri +rmo3st +rm1p2 +rms2k +rms2p +rms4tr +rm3ums +rm3umt +r4m3una +r4m3unf +2rn +r1na +r3na. +r2nab +rna4ben +rn3abi +rnab4l +rnab4st +r3nac +r4nacc +r4n3aes +r4naho +r2nak +r4n3ala +r4n3amt +rna2n +rn3an. +rn3ana +rn4and +rn3ani +r4n3ano +r4n3anz +rn3app +rna2r +r4nare +r4narg +r4n3ari +r4narm +rn4arr +rn5arra +r4n3asp +r4n3ast +r4n3atl +rnat4s3 +r4nauf +r4naug +r4naus +r2n3äs +rnd2 +rn3da +rndi4e +rne4bl +r4n3eff +r3n4eid +r4n5elfe +r4n3e4ma +rne2n +r5nen. +r4n5ends +r4n5ener +rneng4 +r2n3e2p +r4n5erbi +r4n5er4fa +r4n5erfo +r4n3erg +r4n5erke +r4n5er4ku +r4n5er4le +r4n3ert +r4n3er4w +r4n3erz +rne4t3a +rne4ter +rne4to +rne4t3r +rne3u4f +rn3f2 +rng2 +r2n3i2d +r2n3in +rn4i3na +r3n4ing +rnk2 +rnk4a +rnn2 +r2n3oc +r2n3oe +rnoe4d +r3noet +rn3oly +r4n3ome +rn3opf +rn3orc +rn5ost. +rn5osti +r4n3ott +rn1ö +rnö2d +r3nöt +rn3sä +rnsch4 +rnse4h5a +rn3s2k +rn3s2l +rns2p +rns2z +rn3te +rnte3e +rn3tr +r1nu +rn3ums +rn3ur +rnus3 +r1nü +r1ny +rn3z2 +1ro. +ro3adr +r5oberf +ro4berl +2robj +r4obo +ro4b3o4r +2robs +ro1c +3rock. +ro4ckel +5rockes +3rock3s +ro4cku +rodi4e +rod2r +roebel5 +roe4bl +roe2f +r3oefe +r4oeh +ro4eis +4r3oek +4roel. +ro3ela +r3oelf +r3oeli +roel4la +2roem +3roemi +ro3en. +ro3end +ro3enf +ro3enk +ro3enz +ro3erf +ro3erh +ro3erz +ro3eta +3roetu +ro3ev +ro3fa +ro4fen +4roff +3rogg +ro4h3in +r4oh3l4 +roh5nau +ro4hoe +3rohr +roh4rer +ro3ir +ro3iso +rok2l +r3okt +ro3le +ro4liv +rol4lac +rol4l5a4g +rol4lan +rol4las +rolle4 +rol4l5eb +rol4lei +rol6lerg +rol6ler6w +rollor4 +rolls4 +roll5ste +r4olo +rol3s +ro4lym +5roman. +5romane +rom4anf +4romb +ro4mei +romen5e +rom5ent +ro4mer +rom5erk +rom5ers +2romn +r4on. +ron5ant +ro3nar +ron4au +rond4a +ro4n5erb +roni4e +ro4nikg +ro4niki +ro4niku +rons4 +ron4t5an +ron4tar +ron4tel +ron6tend +ron6terf +ron6terk +ron4th +ron4t3r +ron4t3u +2rony +ro3o2f +r3oog +3room +4ropf +ro3pn +ro3p4r +rops5t +4ropt +ror3ab +ro3rae +ror3al +ror3an +ro3rau +r4o3re +4r3orga +r4oro +ro3rol +ro3rom +ro4rop +rort4st +r4oru +ro3r4ue +ror2ü +rosai4 +r4ose +ro3se. +ros5elt +3rosh +ro3s4i +3rosin +ro3sp +ros4pe +ros4s5ei +ross5enke +rosseri6 +ros6sess +rossmut6 +ros4so +ros4spa +ros4st +ros4t5er +3rosto +r3oszi +ro4ß5en4k +ro2ßi +ro2ßu +ro4t3ak +ro4t3al +ro4t5ars +rot5art +ro2tä +ro4t5en4d +ro4terl +rot5ers +ro4thu +ro4toe +rot3sa +rots4te +rot6terk +rot6terl +ro4t3um +3roule +roulet4 +3rout +r3ouv +3rowd +r4ox. +ro3xa +r4oxk +röbel3 +rö2bl +rö2du +2r1ö2f +1röh +2r1ök +r3ölf +röl4l3a +3römi +r1ör +3rösl +3rötu +2r1p +r3p2a +rpe4r3a +rpe4r3e +rpe5re. +rpe4r5in +rpe2s3 +rp2fa +rp2fä +rp2fo +rp2l +r2p3li +rp2sa +rp2s3i +rps3t +rp3t +2rq +2r1r +rra3b4r +rr3abt +rr2ac +rr3a4ck +rr2ad +rrade4 +rra4deb +rra4den +rra4der +rr3aem +r4r3akt +r3ral +rrat4st +rr5aufg +rr5aufl +rr3äm +rr1c +r3reh +rr5einr +rreise6k +rrena4 +rre4nob +rren3s4 +rren6z5er +rrere4 +r4r3er4h +r4r3er4l +r4r3esk +rre4ve +r4rezi +rrg2 +rr4hen +rr4hos +r3ri +rrid4 +rri5er. +rrie5r4e +rri5erm +rrik4 +rri4tan +rrk2 +rr4nau +rr3obs +rro3el +rroe4s3 +rron3 +rro4re +r4r3osz +rrö2s3 +rr2th +r3ru +rr2ue +rrue3b +rruecker6 +r4rur +r3r2ü +rrü3b +rrücker6 +rry1 +rrz2 +2r1s +r3sabo +r3sac +rsa5chu +r2s3a2d +r4s3aem +r2sak +rsa4lin +r4s3amt +rs3ana +r4sanb +r4sanf +r4sanga +r4sanp +r4s3ant +r4s3anz +r2sap +rs4ark +r5s4au. +rsau6erl +rsau4g +rsau4m +rsau4n +rsau5se +rs2är +rscha4o +r3sche +r4sch5e4b +rsch5einh +rscher5ei +r3schi +r3schl +r3scho +rsch5ort +r3schr +rsch4ue +r4scor +r4s3ebe +rse2e +r3seg +r4s5e4mis +r3sen +rse5n4al +rse4ne +rsenk4r +rsens4 +rser5ei +r4s3erh +r4s3er3o +r4s3erö +r4s5ersc +r4s3erw +r4s3erz +r3set +rsex4t +rs4ha4k +rs4har +r3s4ha4v +rshi2 +r3shir +r3s4hor +rsi3el +rs4il +rs4kal +rs4kel +rs4kie +rs4kis +rs4kle +r3skul +r2sky +r3slip +r3s2lu +r3s4mog +r3snob +r2so2b +rs2om +r4s5ort. +r4s5orts +rs3ost +rs2p +rs5para +r4s3phi +rs3s2 +rst5abl +r3stad +rst5ala +r4stale +r4stanp +r4st5ans +r4stant +rst3as +rs4tat +r4staus +rstauto6 +rs5täti +rs4tei +r4s5teil +rst5eindr +rst5einga +rst5emi +rs4temp +rs4terb +rst5erba +rst5er4f +rs4t5er4h +r4steri +rst5er4l +rs4tern +r4st5ero +rst5er4ö +rst5er4r +rst5er6st +rst5er4t +rst5er4w +rs4t5erz +rs3tes +rs4t5ess +rs4t5eta +r3steu +r3s4tev +rs4tew +rs2th +rst5her +r3s4tie +r4stin +rst5ing +r4stit +rs2to +r4s3tot +rs2tr +r6st5rat. +rst5rät +r4strea +r4s5tren +rst5ret +rst5rin +r4strun +rs2tu +r6stuebe +rst5ums +rs5tung +r4stübe +r2sty +rsui2 +r4sumf +rs3umw +rs4win +r2sya +rs4zin +2rt +r4t5a4ben +r4t3abo +rta4ck +rt3aeh +r5taet +rt3aff +rt3aga +rt5a6gent +rt3aka +rt3akr +rt5aktes. +r4talle +rt5allt +rt3alp +rt3ama +rt3ana +rt3ang +r4tange +r4t5anku +rt3ann +rt3ans +r4tanw +rt5anza +rt3arb +r4t3arg +rt5a4rie +r4t3arm +rt3art +rt3asp +r5tat. +rt3atl +r4t3atm +rta4to +r4t5auft +r4taut +rt3äh +rt3ärm +rt3ärz +rtät2 +r3te. +r4teben +r3ted +rte3eh +rt3eff +r3teh +rtei3f +rt3ein +r5t4e5in4d +rtei3s4 +rte4lae +r4t3el4b +rte4l5ei +r4telem +rte4l5en +rte4ler +rte4lit +rtel6lau +rtels4t +rtel5ste +r4t3emb +rtem4e +rte4nem +rte4ner +rte4net +rtenge6bi +rten3s4 +r4t5entf +rt5entl +r4t5ents +r4tentw +rteo2 +rte5rae +r6t5ereig +r4t5erfo +rt5ergu +rt5ergü +r4t5erla +rter6mit +r4t5erna +r4t5ernä +r4t5ernt +rter6öff +rter6sparn +r7t6erstel +rter4wa +r3tes +rte3s4k +rte5stat +r4t5etab +r4t3e4ti +r4teuro +rtex4tr +r3t4hag +r4t3heb +r4t3hel +rt4herr +rt3heu +r2t3hi +r4thony +rt4hum +r2t3i2d +rti2e +rti4gel +rti4g3r +rti4kl +rt5illu +r4t3i4ma +r4t3ind +r4t3inf +r3tit +r3to +r4t3oef +rto4nam +rt3ope +rto5pfl +r4topt +r4t3orc +r4t3org +rto4rie +rto3s +rt2ö +r4t3ral +rt5rams +rt5rand +rt5rati +rt3rea +rt3reb +rt5reic +rt5reif +r4t5reis +rt5reit +rtreu4 +rt5ring +r4t3ris +r4t3riv +r4t3rob +r4troh +rt5roma +r4t3ron +r4trou +rtrue4c +rtrü4c +rt5sand +rtsch4 +rts5ing +rt4sis +rtski4l +rt4s3or +rt5s4pas +rt5s4por +rts4tru +rts4tue +rtt2 +rtu5end +rt3uh +rt3umb +rt3und +r4tunf +rt3ups +rtu4rei +r4t3url +r4t3urs +rtus3e +rtu2t +r4t3ute +rtwa5re +rt2y1 +rtz2 +ru1a +ru3a2r3 +rube4 +rubens6t +ruch3a +ru4ch5er5 +ruchter6 +ruch6top +rucht5s +ruck3a +ru6ck5erho +ru6ckerhö +ruck5er6kr +ru6ckerzeu +ru4cku +rude4a +rude4l5i +ru2dr +rueb3r +5rueckf +5rueckg +5rueckz +5ruempe +ru5enz. +ru5enzs +1r2uf +3ruf. +ru2f3a +ruf5erei +ruf5erk +ru2fo +ruf4se +ruf4ter +ru4fum +ru4gae +ru2gä +ru2gr +rug4sa +ruhe3i +3ruhm +3ruin +ru3in. +ru3ins +ru3ist +2r3ulk +2r3ulm +2rum +ruma2 +ru4maa +ru4m5aer +ru4mal +ru4mar +ru4mär +r3umd +rum3ei +4rumf +ru2mi +rum4mei +r4umpe +rum4ple +r4ums. +r3umt +r3umw +2r3una +2r3unb +4run4d3a +r4unde +run4d5eb +run4d5ei +run6demo +runden5e +run4dep +run6d5erf +run6d5erk +run6d5erl +run6d5ern +run6dero +run6d5ers +run6d5ert +run6d5erw +run6d5erz +run4do +rund3r +rund3s4 +run4due +4rundv +4rundz +runei4 +4r3unem +run4en +2runf +runft3 +4run4ga +4r5ungeh +run4gei +run4gel +run6gerg +4rungl +run4gla +run4gr +rungsauto6 +2r3u2ni +run4k5er4 +2r3unl +2runm +2runn +2runr +4runse +4runsi +2runt +runte4 +2runw +ru2p3a +2rupd +ru2p3h +rupt4o +2r1ur +ru2ra +ru2r3e +r2us +ru4sam +rus4s5er +3russi +3russl +rus4spa +rus4ste +rus6t5erf +rust3o +ru4ß3er +ru4t3ab +ru4tak +ru4t3an +ru4t3ap +ru4tau +rute2 +ru4tei +ru4t3el +rut5erf +2ru4to +rut3o4f +ru2t3r +rut6scha +2ruz +ru2zw +2rüb +rü4bc +rü4bk +rü4bs +rü4ckel +3rückg +rücks4 +rück5sa +rü4fet +rü2fo +rü4h3e4l +rüher4 +rüh3la +3rümpe +rün3f +rün3z2 +rüse2 +2rv +rve3a +rven3e +r4ventz +rve3s +rv2o +2rw +rwe4gel +r5weis +r2wo. +rwolf3 +2r1x +1ry +ry2ac +ry2c +ryl3e +ry3sth +2rz +rza2b +rz4aeh +r2z3af +r2zak +r2z3al +rz3am +rza2n +rz3and +rz3ano +r4z3ant +rz3anz +rz3app +r2z3ar +r2zas +r2zat +rz3aud +rzba4c +rz3ega +r3zel +r4zelem +rze4li +rzell4a +rzen3e +rzen6gen +rzen6ten +r4zentg +r4z5entl +r4z5ents +r4zentw +r4zentz +rz3epe +r4z3er4d +r4z5er5ei +r4z5erfo +r4z5erfr +rz5erfu +rz5erfü +r4z3erg +rzer4la +r6z5erreg +rz5er4sa +r4z5ersc +r5zert +r4z3erw +r4z3er4z +r3zes +r4z3ess +rzeu4g5i +rzeugin6n +r2z3id +r4z3ind +r4z3inf +rz3inh +r4z3inj +r4z3int +rz3inv +r5zirk +rzkop4 +rz5oele +rz2of +r2z3ot +rzten4g +rzt5ric +rzu4gl +rzum2 +r4zwar +r3zwec +r4zweis +r4zwet +r4z5wir +rz4wis +r2zwo +1sa +3s2aa +saat3s +2s1ab +sab2ä +5s4abbat +3sabel +sa4ben +sab5erk +3sabet +s3abh +sa4b3i4t +sa2bl +sa4bo. +sa4b3or +sa2br +sa3b4ru +sab4rü +s3abs +2sacc +sa5chal +3s4ache +sach3o4 +sachs4 +sach3t +3sachv +s2ack +sa4cki +sa3cr +2s3ada +sa4der +2s3adm +2s3a2dr +s2aeb +3saec +sa3e2d +3saeg +4s3aehn +4s3aelt +4s5aemte +4s3aend +4s5aengs +2s3aep +2saeq +s3aerm +3s2aet +4saeugi +3saeul +4s5aeuss +4s3aeuß +sa4fe. +2s3aff +sa3f2r +s2aft +saf4to +saf4t3r +saft3s4 +3sage. +sage3i +5sagen. +4s5a4gent +2s3agg +sa4gio +2s3a2gr +sah3l +sah3n4a +s2ahs +3s2ai +sai2b +sa3ik +sai4ka +sail2 +sai2r +s1ak +sa2ka +3sa3kar +3s4aki +2sakk +3sa3kl +3sako +2s3akt +sa4kus +2sakz +3s2al. +sa3la +sa4l3a4g +sa4lal +sa4lan +sa4lar +s4alat +sal4bo +3sald +3sale +sa4leb +sa4l5erb +sa4l5erf +sa4l5erk +3s2ali +sa4l3id +sali5er. +sal5ins +sal4le. +4s3alli +s3allt +sa4lor +3s2alp +sal4s3o +sal4tau +s3altä +s4alto +3s2alz +sal4zer +sal4zw +3sam. +sa4mae +sa4map +4s3a4mat +sa2mä +4s3ambu +3s2ame +4s3a4mei +samen5s +4s5a4meri +sa4m5ess +3samf +sa4min +3samk +s2amm +5samml +2s3amn +sa4m3oe +samp4 +4s3amph +3sams +sam3sc +sam4ti +sam4to +sam4tre +sam4tri +samt5s4k +samts4t +3samu +sa4nae +4s3anal +sa4nan +s4ana3t +sa2nä +4s3anbr +san2c +5s4and. +san4dab +san4dan +san4d5ar +san6d5ers +4s5andeu +san4dob +san4dot +san4dre +san4d5ri +3s4and5s4 +sa4n3er +4sanfa +4sanfä +3sang. +3sanie +4s5anker +sank4l +4s3anku +4s3ankü +2sanl +s3anm +s4anmu +2s3a2no +2s3ans2 +s4ans. +san5sp +4santr +4santw +2sanw +s1ap +sa2pe +2sapf +sapi3 +sa4pos +2sap3p +s2apr +2s1a2q +s1ar +3sar. +s2ara +2s3arb +2sarc +3s2ard +4sarea +3sarg. +s4arge +sar4gl +s4args +4sargu +4sa4rit +2sarl +2sarm +2sarr +s2ars +2sart +sarts4 +s2a2r3u2 +s2arv +s2arw +2sarz +2s3a2s2p +4s3asso +sa3s4tu +2s3a2sy +satan4 +sa4t5ant +sat3ei +4s3a4tem +2s3atl +2s3atm +sa4tol +sa2tr +sats2 +sa3t4su +4s3atta +4s3attr +3s4atz +sat4zei +sat4zel +sat4z5en +sat4zer5 +satz5ere +5satzk +s2au. +3s4auc +3s2aue +sau4ere +sau6erste +4s3aufb +sau5fra +4s3aufs +s3aufz +s5auges +sau4gla +sau4gle +sau4glo +3sau4g3r +sau2i +2s3auk +3s4aum +3s4aun +sau3n4a3 +s2aur +4s3aura +sauri5 +saurie4 +4s3ausb +4sausd +s5ausei +4s3ausf +4s3ausg +4s3ausl +4s3ausm +4s3ausn +s3ausp +4s3ausr +4s3auss +saussen6d +s5austau +4s5austr +s3ausu +s3ausü +4s3ausw +4s3ausz +2s3auß +sauto6rin +2s3a2xi +s4axo +sa2y1 +1sä +s2äb +3s2äck +3s2äg +säge3i +s1äh +2sähn +2s3ält +2s1äm +3säma +3säml +2s3änd +4s3ängs +2s1äp +2säq +s1är +s2är. +2sära +s2ärg +2särm +2särz +s2ät +s4äuge +4säugi +3säul +3s4äur +4s3äuss +2säuß +2sb2 +s3ba +sba4ne +sb4au +sbau6m5en +sbe3n4a +sbe3ra +sber4e +sbest5op +sbi2e +s3bl +sb4li +s3bo +s3br +sbue5be +sbü3be +1sc +2sc. +2scab +2scac +2scaf +2scal +2scam +s2can +2scar +2scas +2scat +s1ce +2scei +2s4ch. +4schabf +scha5cher. +s4chad +s4cha4e +4schaep +4schaeq +3schaf +s4chal +4schame +sch5ana +4schanc +4schang +4schanl +4schans +4schant +4schanw +4schao +4schap +5schar. +4schara +4scharc +sch5arme +4scharo +s4chauf +6schaufk +6schaufn +6schausf +6schauss +s2chä +4schäp +4schäq +2schb +2schc +2schd +s4che. +4schech +4schef. +4schefs +4sch5ei. +4scheim +6scheinm +schei4s +s4chel +4schemi +4schemp +6scheng. +6schenge. +6schentk +s4cher +scherin6s +scherin6t +sch5er6kl +scher6kran +6schernt +scher6trag +5scherü +4schess +4schex +2schf +2schg +2schh +s2chi +4sch3i4d +3schie +5schil +4schimi +4schinf +4schins +4schint +4schiru +2schj +2schk +4schlad +6schlaed +4schläd +4schle. +4schleb +4schleh +6schlein +4schlek +4schloc +4schlog +sch5lor +4schlöc +4schluf +6schmaes +6schmaeß +4schmas +4schmaß +4schmäs +4schmäß +4schmed +4schmes +4schmex +4schmod +4schmoh +4schmüh +4schmy +4schn. +4schnas +4schneb +4schneg +sch4nic +4schniv +4schnum +sc4ho +4schobj +scho4e +4schoer +scho4f +sch5ofe +4schoh +4schorc +sch5ord +sch5org +sch5orte +sch5ortu +4schox +4schök +4schöl +3schön +4schör +2schp +2schq +4schrad +4schrag +4schrap +4schras +4schräu +4schre. +4schrea +4schref +4schreg +4schreh +6schreif +4schrep +4schres +4schrin +4schris +4schrol +4schron +6schrote +4schrou +4schrus +4schrut +2schs +sch3s4k +schs4p +sch5sta +sch5s4ti +sch5str +sch5stu +2scht +sch3t4a +s4chu +4schuhu +4schumg +4schunf +4schunt +3schur +sch2ü +2schv +s4chw +4schwaa +4schwah +4schwas +4schwäl +4schwec +4schweg +4schwet +4schwid +2schz +2scj +2s1cl +2scoa +2scoc +2scod +2scom +2scon +4scoup +4scous +scre2 +s2cri +2scs +2scu +2scy +2sd2 +s2daq +sdaue4 +s3de +sde2a +s3di +sdi2a +sdi2e +sdien4s +s3do +s3dr +s3ds +s3du +sdue3b +sdü3b +1se +sea2n +se3at. +seauto6r +se4ben +seb2l +4s3echo +sechst5r +4s3echt +2s3eck +sed2 +sede3 +sedes4 +sedi4e +se4dik +se4dit +3see +se4e3i4g +see3na +se3end +see5n4e +4seeng +se3ent +se3enz +see3p2 +see3ra +se3erb +se3erd +se3erf +seer4i +se3erk +se3ern +see3ro +se3err +se3ers +se3ert +se3erz +see3sk +sefen4 +2s3eff +sef2l +4s3e4gal +segelei6 +3se2gl +se3gla +se3glä +se3glo +se2gn +s3e2go +seg2r +seh5abe +se4h3a4g +se4h3ak +se4h5ang +se4hap +se4heng +se6hense +sehen4t +seh5ente +se4hep +se4h5erf +se4h5er4k +se4h5erl +seh5ing +seh5int +seh3l +seh3na +seh3ra +seh3ri +seh3ro +seh3s +seh3t +se4h3ur +se4hüb +2s3ei. +2s3eic +4s3eid. +4s5eidec +4s5eides +4s3eids +4s5eifer +2s3eig +s4eih +s2eil +sei4l3a +sei4lei +4s3eime +se3imp +5s4ein. +4s3einb +4s3ein4e +seine5i +seinen4 +4s5einfl +se5in4fo +4s5einfu +4s5einfü +4s3eing +sein4ha +4s5einhe +4s3eini +4s3eink +4s3einm +s3einn +se5inne +4s3einr +s5einsat +s5einsät +4s5einsc +s5einspa +sein6stit +sein6str +s5einstu +se5in6ten +sein6ter +4s5eintr +4s3einw +4s3einz +2s3eis +3s4eism +3s2eit +2sekz +s2el. +se4l3ac +sela4n +se4l5ano +se4lanz +se4l3at +se4l3au +s2elb +4selbis +selbst5e +selbst5r +sel3d4 +sel3e4c +se4lef +4s3e4leg +se4l5eig +6selektr +4seleme +selen5e +se4l5ent +sel5erd +se4l5ere +seler5ei +se6l5erfi +se4l5erk +se4l5erl +sel5erm +4s3elf. +4s3elfm +sel5ind +sel5inf +sel5inn +se6l5inst +4s3elix +selk2 +3s2eln +se4l3ob +3s2els +sel5skl +sel5sko +sel5stem +sel5sti +sel3sz +seltex4 +se2lü +selz2 +4s3e4mig +2s3emp +s4en. +sena4b +se4na4g +se4nah +sena4r +sen3au +se5n4au. +se2nä +5sendel +senderin6f +sen5eck +se5neg +senei4l +se4nel +sen5ende +senen5e +se4nep +se6n5erei +se4nerf +4senerg +se4n5erk +se4n5ers +se4nerw +se4ness +se4n3e4v +s2enf +sen5fun +seng4le +4sengp +senhaus6 +3s2eni +se4n3im +se4n3in +6s5en6keli +sen6kero +sen4k5ni +senks4 +sen3ob +se4noe +se4n5ott +se4noz +s4ensa +sensch4 +sen4s5e4h +sen6s5ers +sen4s5i4d +sen4s5in +sen5s6lan +3s4enso +senst4 +sens6temp +4s3entd +sen6tenr +4s3entf +4s3entg +4sentla +4s3entn +sen6t5ric +4s3ents +4sentw +4sentz +se4n3un +sen4zun +sen4zym +seo2b +se3of +se3oh +seo2r +se4pen +se3p2f +s3epis +se4pit +s2er. +se3ra. +ser5adl +ser3af +ser5age +se3rai +ser3a4k +ser3al +ser3am +se5rand +ser5anz +5serat +ser5äus +serb2 +s5erbe. +se4r3e4b +ser5eck +serei4f +6s5ereign +se4rein +serei5s6t +ser5eli +sere4m +ser5emi +3s4eren +ser5end +se4r5en5e +se4r5enk +se4r5ens +ser5enth +se4r5erb +serer5e +se4r5erh +ser5ersc +se4r5erw +4s3ergä +6sergebn +ser5ha +4s3er4hi +4s3erhö +se4r3id +3serie +ser5inner +serk2 +6s5erkran +4s5erläs +4s5er4le. +4s5er4len +4s5ermah +ser3na +4s5ernte +ser3o4b +ser3oc +se3rod +4s5eroef +se5roel +4s3eros +4s3erot +4s3eröf +se3röl +s4ers. +sersau4 +4s5erseh +ser6sehn +sers4k +ser4um. +se5rumm +3s4erv +se2ry +3ses. +sesch4 +4s3e4sel +se5skan +4s5eskap +se3so +ses2p +4s3essa +s4essel +sest2 +se3sta +se5stec +se5stei +se5stel +se5stemp +se3sti +se3sto +se3stu +3s2et. +4s3e4tap +4s3e4thi +4s3e4tho +4s3e4tik +set3sp +se3tun +3setz +set4zei +seu2f +se3ufe +se3uh +2s3eul +se3um2 +seums3 +se3uni +2s3eup +seu2t +se3ute +se3uto +4s3e4van +s1ex +3s4ex. +4sex4am +se4xen +2s3exk +s2exo +2sex2p +sext4an +sex5ti +4sextr +sext6ra +3s4exu +2sex2z +1sé +2s1f2 +s3fa +sfal6l5er +s3fe +sfen2 +sfi4le. +sfin4s +s3fl +sf4la +s3fo +s3fö +2sg2 +s3ga +sgangs5 +sga2t +s3gä +s3ge +sgeha4 +sge6habe +sge3lu +sge3n4a +s3gi +s3gm +s3go +s3gö +s3gr +2s1h +3s2ha. +s3hae +s4ha4ke. +shal4li +shalt4s5 +3s4hamp +s4has. +s3hä +3shi. +sh4ia +3s2hib +3s2hik +3s4hip +3s4hirt +sh1l +sh1n +sh2o +s3hoc +s3hoe +s4hoe. +s3hof +3s4hoo +3s2ho2p3 +sho4re +s4horts +3s4ho2w3 +s3hö +sh2r +sh4re +sh2u +sh2ü +1si +si5ach. +si3am. +si3as +sia4s3e +2siat +sib2 +3sic +sichts5te +sicker5e +4s3i4dea +4s3idee +4s3ideo +si3der +2si2dy +s2ie +sie4b3a +siebens6 +sie4b3r +sie4bu +si2ed +sie4gl +sie4g3r +sie5r4e +sies2 +si1f2 +s2ig +si2g3a2 +si2gä +si4g3ei +s3i4gel +si2gl +sig2n +sig4nu +si2g3r +sigs4e +sigs4p +sig5sto +si4k3ab +si4kad +si4k3ae +si4k3a4g +si4k3ak +si4kalb +si4kana +si4kanb +si4k5ans +si6kanth +si4kanw +si4k3ar +sik5asp +sik5ass +si2k3ä +si4k3el +si4k5ens +si4k5ent +sikerbe6 +si6k5erei +sik5erfo +si6k5erke +si4k5er4z +si2k3i2 +sikin3 +si2k3l +sik5le +si2k3n +siko3n +siko3s +si2kö +si2k3r +sik3s +sikt2 +si2ku +si3kum +si2kü +s2il +3silb +silber5e +sil4br +sil3c +3sili +2s3ill +si4mag +2s3imb +2s3imm +4s3impf +4simpu +sim4st +3s4imu +si3n2a +si5nau +4s3indu +sines4 +4s3infe +4s5infor +4s3infr +sin4gae +sin4gan +sin4gar +sin5g6le. +sin4glo +sin4g3r +sings4 +sing5sa +sing5sc +sing5so +2s3inh +sini5er. +4s3i4n3it +s2ink +3sinn. +s5innenm +4s5inner4 +sin6n5erf +3sinnl +4s3inno +3sinns +2s3inq +2s3ins +3s4ins. +s4into +2s3inv +2s3inz +si4onm +si3os +3sip +s2ir +2s3irr +si4sam +3sisc +si4scha +si4schl +si4schn +si4schu +si4schw +si2s3e2 +sisen3 +sis3i +si4sin +si4sis +sismu4 +s3i2so +sis3or +siss2 +3s4i2st +sis3te +si2su +3s2it +si4tal +si4tau +si4tra +si4t3re +sit3s +sit4zel +sit6z5erm +siv3a +si4val +si4van +sive3 +si4v5er4f +si4v5er4t +si4vin +siv3o2 +si4vor +si2vr +1sí +2sj +s3jo +s1k2 +2sk. +2skab +2skae +ska3la +4skalk +2skam +5skanda +2skap +2s3kar +2s3kas +ska4s3p +4skata +ska4te. +4skateg +s4kater +ska4tes +ska4to +ska4t3r +2skau +2skä +2skb +2skei +ske4l3i +2ske2n +sken3e +3s2kep +ske3ra +s4ketc +s3kh +3s2ki. +s2kia +3s2kif +s2kig +3s2kik +2skin +s2kip +4skirc +3s4kire +3s4kis. +3skiv +3skiz +2skk +s3klam +4sklas +s4klav +5sklave +2sklu +2skn +4skoe +sko3ef +2skoh +2skol +3s4koli +4s3kom +4s3kon +3skop. +s4kopa +sko4par +s4kope +s4ko4pl +s4ko4pr +2skow +2skö +2skr +s3kra +skraf4 +s3kre +3s4krib +s4krip +s3kro +2sk3s +sk3t +skto2 +2sku +s3kue +3skulp +s3kun +s3kur +2s3kü +2skv +2s1l2 +s3lab +s3lae +s3lag +3slal +sla4ma +sland5a +sland6s5o +slap3 +4s3lar +3s4lash +sla3sp +s3l4au +sla4ve +s3lä +sl3b +s3le +sle4gas +slers4 +s3lic +s2lip +sl4ob +slo5be +s3loc +3s4loga +3s2low +s3lö +sl4um +2s1m2 +s3ma +smab2 +sma3la +s4mall +sm4an +sm4ar +smar4k5n +sma3sp +smen4t5i +s3mi +smi2e +smi2t3 +smitte4 +3s2mok +s3mu +smusiker6f +smut4ti +s3mü +2s1n +sn2a +snab2 +s4nack +snae4s3 +snä2s3 +s3n2e +s3ni +sni5er. +sni5ers +sn4ob +snus2 +1so +2s3oas +s1ob +s2oba +so4bel +4so4ber +2sobj +so2b3l +2sobm +4so4boe +so2br +3s2o1c +s4oda +sodi2 +so2do +so3e2b +2s3oe2d +soe2f +2s3oek +2s3oes +so3et +so4fen +s4off. +4s3offi +3s2oft +sog2l +s2oh +2s3o2he +3s4ohl +sohle4 +sohlen5 +4s3ohng +2s3ohr +2s3okt +3sol. +so3la +3solan +so4l3au +sol3c +3sold +sol4d5er +3sole +so4l3ei +4s3o4lig +sol4l3a +sol4l5er +3solm +so3lo +solo3p +solo3s +3sols +so3lu +2s3o2ly +2s3omb +some4n3 +3somm +s2on +son6anz +sonan6z5ei +so4nau +3sond +sone2 +so4n3eh +so4ner +son4ga +3sonn +son5ori +sons4a +son4s3o +so1o +2s3o2pe +2s3opf +3s4opr +sop3s2 +2sopt +4s3orat +2s3ord +sore2 +so4rei +so4r3el +so4rem +so4rer +4s3orga +sorgi3 +4s3o4rie +so2r3o2 +sor4t5ae +sor4tau +sor4t3ä +4s5ortes +4s3orth +5sorti +sort4st +sose4 +2s3osm +so3s2p +3s2oss +so3sta +s3osth +2s3osz +3so3ß +soun4d3 +3sour +2s3out +2s1ox +s2oz +2s3o2ze +sö2c +2s1ö2d +s1ö2f +s3öffn +1söh +2s1ök +2s1öl +2s1ös +sp2 +2sp. +4spaa +3s4pace +4spack +3spaen +4spaer +3spaet +s4paga +4spage +spa4ge. +4spago +2spak +4spala +4spalä +4spale +s4pali +4spalm +5spalt +spal4t5a +spa2m +s2p4an +5spannu +4spano +5spant. +4spanth +4s3panz +2spap +sp4ar +4spara +spa4rap +s4pard +3sparr +5s6parten +4spartn +4sparty +4spassi +3spaß +3s4pat. +5spatel +4spati +4spatr +3s4pats +2spau +s2paz +5spazie +2späd +3spän +2spär +3spät +2spc +2spe. +s2pea +3s4pedi +s2pee +2speg +3speic +4spein +3speis +s4pek +4spensi +spen6ster +4s3perf +3sperg +s3peri +4sperle +s3pern +s4perr +sper6r5ei +4s3pers +4speru +4sperü +3s4pese +2s3pet +3spex +3s2pez +2s3pf +5sphaer +s4phal +s3phan +4s3phas +s2phä +4sphän +3sphär +3s4phin +2sphy +1spi +2spia +3s2pi4e +4spiep +4spier +spier4r +2spig +spi2k +2s3pil +4sping +3s4pinn +2spip +s2pir +4spiro +2spis +2s3pla +2s3plä +4sple +sp3le. +3s2pli +2s3plu +2s3pn +2spod +2spog +3s2poh +3spoi +2s3pok +4spol +3s2pom +3s4pons +2spoo +2spop +spor6tag +spor4tu +4sporz +2s3pos +4spote +4spoti +s4pots +s2pö +4spr. +3sprac +4spraeg +4sprael +4spraem +s4praen +4spraep +4spraes +4spraev +4spraez +4sprak +3s4pran +4sprax +3spray +s3präd +4spräg +4spräl +4spräm +s4prän +4spräp +4spräs +4spräv +4spräz +3s4prec +4spred +4spreis +5spreiz +3s4pren +4spres +s4pric +5spring +5sprint +4sprinz +4spriv +4sprob +4sprod +4sprof +4sprog +4sproj +4sprom +4sprop +5spross +4sprot +4sprov +4sproz +3spruc +5spruec +4spruef +3sprüc +4sprüf +3sprüh +3sprün +2s3ps +s4ps. +sp3t +4spub +2spuf +3spuk +4spulv +3spund +4spunk +2spup +3s2pur +spu4rer +4spurpu +2sput +1spü +s2pür +2s3py +2sq +s3qu +2s1r2 +s3ra +srat4s5t +s3rä +s3re +sre5cha +s3rh +sroe4s +sroes5c +sro3tu +srö2s +srös3c +s3ru +sruecker6 +s3rü +srücker6 +2s1s +s3s4aba +ssa5ble +ssa3bo +s4sach. +s2s3aj +ss5alar +s4s3amb +s5samm +s4s3amp +s5san. +s4sanf +s4s3ang +s4sanh +s4sanp +s4s3anz +ssa4ro +ssa2s +s4s3asi +s4s3ass +ssa3s4t +ss3att +ss3aud +s4s3au4f +ssau4n +ssau4r +ssau5ri +s3sät +s3s2äu +ss2ca +s2sce +ssch2 +s6scheid. +sscheiner6 +ss4chem +s2sco +sse3a +s4s5eben +sse4ck +sse3ec +s3seh +ss3eib +sse5in4d +sse5inha +ss5einl +sse5inte +s4sekä +s4selem +sse6l5end +sse4l5oe +sse4lö +s3sen +s4s5endl +sse4n5e4b +sse6nend +sse6nene +sse6n5ent +sse6n5er5i +sseng4 +ssengene6 +sse4nid +ssenk4 +ssen6kel. +ssen6keln +ssen6kels +sse4no +s6sen6sem +ssen6te. +sser5a4d +sser5as +sse6r5att +sse6r5ent +sse4rep +sser6fahru +sser6gebn +s6s5erker +ss5ernt +sser4öf +sses4sa +sses6sen +s4s5estr +ssex2 +s4sexa +ssgene6ri +s3s4hut +ssicht6s +s3sie +ssi3es +ssi4ken +ss3i4ko +s4s3imp +s4s3ind +ssing5s +s4s3int +s4s3isr +s2sk +s5skala +ss3li +s4s3oef +ss3oel +ss3ofe +ss3off +ssoi2 +s4sonk +sso5ral +s5sorde +s4s5orgi +s2söf +ss4pec +s2spl +s4spro +ssquet4 +ss3s2 +sssau4 +ss5spr +s4st. +sst2a +s3stab +s5stad +s3stae +s3stah +ss3tak +s3stan +ss5tank +s5s4tap +s3stau +ss5tauc +s3stä +ss5täti +s5stein +ss4teng +s3s4tep +ss4tern +ss3tet +s3steu +ss5teur +ss3tho +s3stie +ss4tin +ss4toer +s3stop +s3stöp +s5stör +s3str +ss4tras +ss4trat +s4strie +sstro6ma +s3stu +s3stü +s2sty +s4sumf +s4sumg +s4sumk +s4sumr +ss5umsc +s4s5umse +s4suns +s4sunw +s3sup +s4s3urk +s2sz +2st. +s2ta +s3ta. +3staa +3stab. +4stabb +4stabd +4stabel +5stabes +4stabfl +4stabh +3stabi +4stabit +4stabl +sta4bli +st3abo +4stabr +4stabt +4stabu +4stabz +st2ac +4stada +3stadi +4stadj +4stadm +4stadr +3stadt +3s4taeb +4staebt +3staed +4staeg +4staehn +3staem +6staenz +4staep +st5aerm +5staett +4staeus +4staeuß +4stafel +5stafet +st3afr +2stag +s4tagg +s4tagl +3s4tagr +stai2 +2stak +st3aka +3staks +s3tal. +4stala +sta5lak +st3alg +stalle4 +stal6leb +stall6ta +st3alm +st3alp +st3alr +st3alv +4stame +st3a4mi +3stamm +stam4ma +3stan. +4st3ana +4stanb +3stand +3stane +4stanf +5stange. +6stangeb +6stangek +st5angel +st5anges +4stangr +4stanl +4st3anm +4stann +st3ano +3stanp +st5ansa +st5ansä +4stanw +st3a2o +3stapl +stapo3 +stapos6 +4st3app +3s4tar. +6starbei +4stari +4starn +3s4tars +star4ta +4starti +4starz +2stas +3stase +stas4tr +4statb +5statis +st3atl +st3ato +4staubh +4stauf +st5aufb +st5aufk +st5aufs +st5auft +4s3taug +3staup +5s4taur +4stausc +stau6scha +4stausg +4stauss +staussen6 +4stauto +3stauu +2stav +2s3tax +1s2tä +s4täb +3städ +2s3täf +2stäg +4stähn +4stäle +4stält +s4tänd +4stänz +2stäp +st3ärm +st3ärz +4stäte +3stätt +4stäus +4stäuß +2stb +2stc +st3ch +2std +4s3te. +3ste4ak +4steam +4stean +5s4teche +ste4chi +4stechn +ste2d +st3edi +5stedt +2stee +s2teg +3steg. +3stege +ste4gra +ste4gre +3stegs +3steh +4stehr +4s3teic +4s4t3eid +3s4teif +st5eifr +s4teig +5steiga +stei4gr +5steigu +steil5a +4steilc +4steils +4steilu +6steindr +ste5i4sc +s4t5eise +s4t3eiw +2stek +s2tel +4stel. +4stela +4stelb +4stelef +4steleg +s5telei +st5elek +4stelem +4steleo +4stelep +ste4ler4 +st5elev +s3telf +stel6laen +stel6l5än +4stelm +4steln +stel5oe +ste4lö +4stels +4stem. +4stema +ste4m5ac +ste4mei +ste6m5ent +ste4mer +4stemi +ste4min +6stemper +4stempf +ste4mu +2sten +stende6ta +s4t5ends +stenei6l +stens4 +st5entd +s4t5entf +s4t5entg +s4t5entl +s4tents +s4tentw +sten6zerf +s4tepi +4stepr +4s5ter. +4stera +ster5al +4sterba +s6terben +st5erbrin +4sterd +4stere. +ste4rec +4sterei +ster5eif +ste4rel +4sterem +4s5teren +5stereo +ste4rep +4sterer +4steres +4sterf +st5erfin +4sterg +ster6gie +4sterh +s4terit +4ster5k +4sterl +4sterm +st6ern. +4sterna +4sterö +4sterp +4sterr +4sters +4stert +4steru +4sterw +4sterz +ster4za +ster4zä +s4t5er4zo +2stes +stese4 +stes5er +stes6se. +ste4st +stes5ta +s4testn +stes5tr +2stet +ste4tab +ste4tag +3s4teti +3s4tett +s2teu +3steue +4steul +st5eura +2stev +st3eva +st3e4vi +2stex +s4texa +s4t5extr +2stf +2stg +2sth +st3heb +st3hee +st3hel +st5herd +s5therm +s4theu +st3hex +s2t3hi +s2t3ho +s2thu +2stia +2stib +s2tic +3stich +6sticket +2stid +4stie. +4stief. +4stiefl +s4tieh +sti4el +stie6lei +4stien +6stiere. +3stieß +3s2tif +2stig +st5i4gel +2s3tik +sti4kam +sti4kan +sti4kar +sti4kas +sti6k5ere +sti4kl +sti4kr +s2til +3stil. +sti4l3a +s3tild +3stile +3stils +s2tim +3stimm +4stimp +sti3na +st3inb +s4t3ind +4s4t3inf +s4t3inh +s4tink +st5inka +5stinke +st5inko +st3ins +s4tinsz +4s4t3int +2stio +4stip. +s4tipe +4stipps +4stips +s2ti2r +st3iri +3stirn +4st3iro +4stis +sti6scha +st3iso +st3isr +st3ita +4stite +4stiti +5stitui +2stiv +stiva4 +stiz3a +2stj +2stk +st5kan +2stl +2stm +2stn +4sto. +4st3o4bl +4stocht +2stod +4stoech +4s4t3oef +4stoen +4stoepf +3stoer +s4toeru +s4toes +3stoeß +4stoet +3stof +6stoffiz +s2toh +4stoil +s4tois +3stoll +4ston. +sto4ne. +4stonn +4stons +4stoo +s4t3ope +4stopf. +4s5topo +3stopp +4stor. +s4torc +4stord +4stores +4storf +4storg +4stori +s4torn +4storp +4stors +4stort +4storw +s4tory +s2tos +3stoss +stost5r +st3osz +3stoß +4stota +4s3tote +s4tott +4stou +1s2tö +4stöch +3stöck +2stöf +2s3t2öl +2stön +4stöpf +2stöt +2stp +2stq +4strad +4s5traec +4straeg +6straeum +stra4f5a +s4trafe +s4traft +4strag +4strahi +5st6rahl +4strai +4strak +4stral +stra5la +6strangi +5strangs +s5trank +4strans +st5rast +3straß +4straum +4s3träc +4sträg +3strän +3sträs +3sträß +4sträum +4stre. +4strech +4stref +4streg +4s5treib +5streif +s4t5reis +4streno +4strep +4streso +s4tress +st5rest +4stret +6streuem +6streues +4streuh +5s4treuu +4strez +4strib +4strieb +strien4 +strie5s4 +4strig +4strim +4strink +4s5trio +5s4tripp +4stris +4striu +st2ro +4stroc +4stroep +3s4troh +4stroll +s4troma +5s4trome +5stromk +4stropf +4stros +s4t5rose +4strou +4ströp +s3trös +4strua +3s4trud +6struemm +4struh +3struk +4strup +4strümm +2st3s2 +sts4a +stsau4 +st4sb +stsch4 +sts4i +2st3t2 +4s5tuch +3stud +4s5tuech +5s4tueck +3stueh +stu4el +3s4tuen +4stuer. +4stuere +5stuetz +3stuf +st3uga +3stuh +s2t3uk +4stumf +4stumo +4stums +stum4st +4stun. +st3unb +3stund +4stune +4s4tunf +4stung +4stunn +4stuns +4stunt +4s3turb +stu5re +sturi3 +s4turk +st3url +stur6mei +4s5turn +4sturs +4st3urt +3s4turz +2s3tus +3s2tut +1s2tü +4stübu +4stüch +3stüh +4stür. +4stürc +4stüre +4stürg +4stürs +4stürw +3stürz +3stüt +4stütc +2stv +2stw +stwa5re +s2tya +3s2tyl +2stz2 +1su +su3al. +su3an +su2b3 +suba2 +4subi +subs2 +3suc +such3o +such6s5ende +such6spe +such6ters +sucht4s +3sud +2sueb +3suec +sue2d3 +sueden4 +3suef +su3ell +3suen +3suep +3sues +3sueß +su3ez +su2f +2s3ufe +su3fi +sugge4s +2s1uh +3suhl +su3is +su3it. +su2k +s2ul +su3la +su3li +3sulta +su4m3an +su4mar +su4mau +su4mei +su4m3el +sument4 +su6m5ents +su6mentw +su4m3et +4sumfa +4sumfe +su4m3id +su4m3in +4sumla +3s4umm +3s2ump +4s3ums4t +su2n +2s3una +sunder4 +sun6d5erh +sun6d5ess +sunds4 +2s3une +2s3unf +6s5ungena +4s3ungl +2s3uni +2s3unl +2sunm +4sunsi +2sunt +2sunv +s4unwa +4sunwe +2supd +3sup3p +su4ran +su4rau +su4r3ei +su4r3er +3s4urf +surfs4 +sur5ins +4s3urku +su2r3o2 +s5urspr +s3urw +sus3e +su3shi +sus3i +s1ut +su2te +su2to +1sü +2süb +sü4be +3sü2d1 +süden4 +3sün +s2üs +2sv2 +s3ve +svie2 +s3vo +svoran6s +2sw +s3we +swe6gers +sweg3s +sweh2 +s3wi +4s5wink +s4wist +1sy +s1ya2 +sy3ab +syl1 +3syli +sy2lo +sy2lu +sym3 +sy2n3 +3synd +2syo +3sys +sy3sto +sz2 +2s3za +2s3zä +2sz4ei +szeits4 +2szel +3s4zena +s4zend +s4ze3n4e +s4zeni +4szent +2szer +sze3ra +2szet +2szeu +3s2zew +2szie +4szins +s4zint +3s2zis +2szo +s3zs +sz3ta +2s3zu +sz4un +szu3ni +2s3zü +2szw +2szy +1ß +2ß1a2 +ß3ac +2ß1ä +2ßb2 +ßbal4li +ßbau4c +ßbrau4 +2ß1c +2ßd2 +ße2be +2ß1ec +2ß1ef +2ß3e2g +2ß1ei +2ß1ek +ße2l3a2 +4ß3e4lek +ßels2 +ßel3sk +2ß3emp +ß4en. +ße2na2 +ßene2 +4ß5energ +ßeng2 +ße2ni +ßenk2 +ße2n3o2 +ßen3s4k +ßens4l +ßens4p +ßens4t +ßen4te. +4ß3entf +4ß3entl +4ß3ents +4ßentw +4ßentz +2ß1e2p +ßer3a +ßerei5s4 +4ß5er4geb +ßerk2 +4ß3er4ke +ßer4le +ßermen4 +4ß3ersa +ßer4seh +ßers4k +2ß3er2z +2ß3es4s +4ß3estr +2ß1ex +2ß1f2 +2ßg2 +ß3gm +2ß1h +ßi2g3a2 +ßigs4e +2ß1i2k +2ß1il +2ß1im +2ß1in +2ß1i2o +2ßj +2ß1k2 +ß3kh +2ß1l +ßlers4 +ßli1 +2ß1m +ßmau2 +ßmut2 +2ß1n +2ß1o2 +ßoe2 +ßos2 +2ß1ö2 +2ß1p2 +2ßq +2ß1r +ßroe4s3 +ßrö2s3 +ß3ru +2ß1s2 +ßsau2 +ßsch2 +ßst2 +2ßt +ß2t3in +ßt2ö +ßtu5er. +ßtu5ers +2ß1ue +2ß1um +ßums2 +2ß3unf +2ßunt +2ßunv +2ß1ü +2ßv +ßvie2 +2ßw +2ßz2 +ß3zs +1t +3taan +ta3ar +4taat +t2aba +ta4b3an +2t3abb +2tabd +3tabel +2tabf +2tabg +2tabh +4t3a4b3i4t +2t3abk +4t3abla +4t3a4blä +5tablet +4t3ablö +2tabm +t3abme +2tabn +tab5ort +2ta2br +2tabs +2t3abt +3tabu +ta4bue +tabu5s +ta2bü +2tabw +taby2 +2tabz +2t1ac +t2a3ce +t4ache +ta3chi +ta3cho +tach4r +4tachs +ta3chu +3tacl +3tacu +2t3add +4t3a4der +tadi3o4 +t3adm +t3a4dol +2t3a2dr +ta3d2s +4tadt +tad4t3a4 +2tadv +ta2e +t3aebt +tae2d +ta3ede +ta3edr +3t2aef +t3aega +3taegi +3taegl +4taegy +2taeh +t3aehn +ta3ei +3taele +4t3aelt +2tae2m +t3aemt +t5aenderu +4taeng +ta3ent +3taenz +2t3aep +2taeq +tae6rers +tae4rob +t2aet +3taete +ta3eth +taet4s5t +t3aetz +4t3aeug +5taeusc +t5aeuss +4t3aeuß +2t3aex +3taf. +3tafa +3tafel +2taff +t3afg +ta3flu +ta3fra +3t2ag +t4ag. +ta2g3a2 +ta4g3ei +6tagent. +tage4si +t3agg +ta4git +ta5gla +ta4glo +4tagnat +tagse4 +tags5ta +2tahl +tah4ler +tah4li +tahls4t +tahl5ste +tah4l3u +3t2ai +ta3i2k +tai2l3 +tai4ne. +ta3ins +ta3ir. +ta2ka +2takk +ta3kla +2t3akq +ta4kro +tak4tel +tak6t5erf +tak6t5er6z +5taktik +tak4t5in +4t5aktiv +tak4t3o4 +tak4t3r +tak4tum +2takz +t2al. +tal3a4b +tal3ac +ta3lad +ta3lag +tal3a4s +t2alb +t3alb. +4t5alben +t3albk +3talbr +ta4leb +talem4 +tal5emb +ta4l5emi +3talen +ta4l5en4d +ta4l5eng +ta4l5en4s +talent6n +3taler +ta4l5er4f +ta4l5erg +tal5erha +tal5ersa +ta4l5ert +ta4let +ta4leu +ta4lev +tal4ga +t3algo +3talia +tali6ene +3talis +3talke +tal4l5ac +tal4lan +tal6l5auf +tal6l5ein +tal6l5erh +tal6lerm +tall3s4 +t5allta +4t3alm. +talmi4 +3talo +ta4lob +ta4lop +ta4l3o4r +ta3lö +talp2 +3t2als +tal4sae +4talter +talt4s +3t4am. +t4amar +t3a4mei +3tamen +4t3a4mer +5ta5mix +tam4mei +tam6mense +tam4m5er +tam4mi +tam4m3o +2t3a2mö +t3ampl +t3ampu +3t4ams +2t3amt +3tamu +ta2na +4t3anal +ta2nä +t4anbe +4tanbl +3tanbu +3tan2c +tan4dan +tan6d5erk +tan4dre +tan4d5ri +tand4s5a +tand6sen +tand4sk +tand4st +ta5neie +tan5erfa +2tanf +4tangab +tan4gar +5tango. +5tangos +t3ang4r +tan4gra +4tanhä +t4ani +t4anj +tan4kan +tan6k5er6k +tan4klo +3tanks +4t3ankü +2tanl +3t4anne +tano2 +ta4nod +4t3a4nom +t3anon +ta4nop +ta4nor +4t5ansam +4t5ansat +t3ansc +4tanspr +4tanstr +t4ant. +tan4tan +4tantei +5tantenf +5tantenp +t5antig +5tantis +4tanwae +4tanwal +4tanwä +t4anz. +tan4zar +tan6z5erh +tan6z5erm +tan6z5erz +4t3anzu +tan4z5un +4t3anzü +tan4zw +3ta4pe. +ta4pes +2tapf +ta2pl +ta4poka +t3app. +4tappa +t3appr +ta2ra +4t5arabi +3tar3al +4t3aram +ta5rant +tar3ap +ta3ras +tarb2 +2tarc +5tarchl +5tarchr +3tard +4t3area +tar5ein +ta4r3el +ta4r5en4d +ta4rer +tar5ere +tar5ers +tar4hei +3tari +tar5ins +tar4kn +4t3arm. +t3armb +4t5arme. +4t5armee +6t5armen. +6tarmeni +4t3arms +3tarn +t3a4rom +ta4rop +4tarot +ta3rö +2tart +tar4tau +tart5e4c +tar6t5eig +tar6t5ere +t5artik +tar4t5in +tar4t3o +tar6t5rau +tar4tre +tar6t5ric +tart3s +3tartü +3tarty +ta3rum +4tarzt +3tas. +3t2asc +tas4k3l +ta3spr +ta4s3s +4t3assi +4tasso +3t2ast +ta4ste +tast5ei +tas4tin +tas4to +2t3as2z +2t1aß +t4at. +ta4t3an +ta4t5arm +3tatb +t2ate +ta4te3c +tat5ein +ta4tem +ta4t5ent +ta4t3er +ta4t3hi +4t3atom +tat5ort +tat5ric +ta4tro +3tatsa +4tatta +tatt5an +4tatti +3tatur +tat3zw +tau4b3a +tau4bei +3taubh +tau4b3r +t4auc +tauchs4 +t3audi +tau3e +tauer4l +tau4fak +t5aufar +4t5aufba +t3aufd +taufe4 +5t6aufe. +4t3auff +4t3aufg +t3aufh +4t5aufko +4t3aufl +tau5f6lie +4t3aufm +4taufn +4t3aufp +4t3aufr +taufs4 +4t5aufsi +4t3aufw +4t3aufz +3taugl +t3augm +2tauk +t3aukt +t3aul +3taume +4t3ausb +3tausc +tau6schei +tau6schm +tau6schn +tau6schr +tau6sch5w +4tausd +4t5ausei +5tausen +4t3ausf +4t3ausg +4t3aush +t3ausk +4t3ausl +t3ausn +4t3aus3p +4t3ausr +4t3auss +4t5austa +4t3ausu +4t3ausü +4t3ausw +4t3ausz +3tav. +3tavs +taxi3s +2t3axt +3taz +t3äbt +tä1c +t2äf +3tägi +3tägl +2tägy +2täh +t3ähn +3täle +2t3ält +2tä2m +t3ämt +t5änderu +2täng +t3ängs +3tänz +2t1äp +2täq +tä4reng +tä4r5ers +tärm2 +tä2ru +t2ät +3täte +5tätigk +4tätt +2t3ätz +2täug +3täusc +t3äuss +2täuß +2täx +2tb2 +tbahner5 +tbauer4 +tbau3f +tbau6m5en +tber4e +t3bh +tblocken6 +tbote4 +tb4ra +t3bs +tby2t +2t1c +tchar4m +tche4n +tch4i +tch3s +tch3w +t2ck +tcor2 +tcre2 +tcu2 +2td2 +tdar4m3 +tde2a +tde3s4t +tdi2e +t3ds +tea2 +2teak3 +3te2am +tean3 +3teba +t3e4ben +t5e4ber. +t2ec +4techd +4techk +4techm +5techn +4t3echo +4tech3s +4techt +2teck +tecke4 +te4cki +tecks4 +2t3ecu +te4del +tedi4e +te4dit +ted2r +2tedt +tee3id +te3ele +te3end +te4er. +tee3r4a +te3erl +te3erz +te4es +teesen4 +tee3t +4tefeu +2teff +te5ge. +tegen4 +te5gen. +te4g5end +teger4n +2t3egg +teg2l +3te3gr +teg5rae +teg5ran +te4grä +2teh +3teha +te4h3ac +te4hap +3tehä +te4hef +te4hep +3tehi +te4him +teh3la +3teho +3tehö +t3ehr +teh3ra +3tei. +t5eifers +tei4gab +teigerin6s +tei4gla +tei4gle +4t3eign +teik2 +t2eil +tei4lau +teilen6s +tei6l5ent +tei6l5er6h +tei6l5ero +tei6lerst +5teiln +2tein +tein5al +tein3d +tei4n5e4b +tein5e4c +tei6nen6s +tei6n5erf +tei6nerk +tein6hab +tein6sel +tein6spo +tein6tol +4t3eis. +t3eisb +4t3eish +4t3eisk +4teiss +tei5su +2teiß +tei3t +teiz2 +2tekz +3tel. +3tela +te4l3ab +te4l3ac +tel5aer +te4lam +tela4n +tel5an5e +te4lant +te4lat +te4l3au +te4l3är +3telbe +3telbr +3telbu +3telc +3tel3d4 +te3le. +tel5ech +tel5e4ck +3telef +3teleg +te4l5ei4c +te6l5eier +te4l5eig +tel5eint +5telema +tel5en5e +tel5eng +te4l5ent +3teleo +3telep +te4l5erb +te4l5erd +te4l5erf +te4l5erg +te4l5erh +te4l5erk +te4l5ern +te4l5ers +3teles +te4l5eti +te4leu +3telex +4t3elf. +4telfd +4t3elft +3telg +3telh +tel3id +te4lim +tel5ind +tel5inf +tel5inn +tel5ita +3telk2 +tel6lanf +tel6lant +tel6lein +tel5ler +tellerei6g +3tellö +3telm +3teln +3telo +telo4g3 +te4l3or +3telp2 +3telq +3telr +3tels +tel3s4k +3telt2 +3telu +te4lut +3telv +3telw +3telzy +t2em +3tem. +te4mak +3temal +te4mant +te4marc +tem5art +te4m5att +te4mau +4temäc +4t3embl +tem3eb +tem5ein +tem5er5e +tem5erf +tem5erg +tem5erh +tem5ern +tem5ers +tem5erw +te4m3i4d +te4m3i4m +tem5ing +te4miss +4temms +4temmt +te4mop +te4m3o4r +6tempelt +5temper +4t3empf +4temph +tem4pli +tempo3 +tems2 +temt2 +te4mun +te4m3ur +3t4en. +te4na4b +tena4d +ten5aeh +te4na4g +te4nan +te4n3a4r +ten3au +te2nä +ten3äh +4t5endal +4t5endap +4t3endf +4t3endh +4t3endk +4t3endl +4t3endp +tend4r +4t3endt +te3ne. +te4n3e4b +ten3ec +te4n3ed +te4nef +te5n4ei. +tenei4d +te4n3el +te4n5end +te4n5en5e +te4n5ens +te4n5ent +te4nep +ten5erei +te4nerf +4t5energ +te4n5erk +te4n5erl +ten5erm +te4n5ern +te4n5ero +te4n5erö +te4n5erp +te4n5ers +te4n5ert +te4neru +ten5e4sc +te4ness +tene4t +ten5eta +ten5eti +te4neu +te4n3ev +ten3f2 +4t3eng. +4tengag +4tenge. +6tenglis +teng4r +te4nid +te4nil +te4n3in +tenk2 +ten3n4 +4tenog +te4nol +te4nop +te4nos +te4n5ott +te4n3o4v +3tenr +3t4ens. +tensch4 +ten4seb +ten6serg +ten6stip +ten4sur +tent5art +ten6ters +ten6t5er6t +ten6t5er6w +6tentheb +t5enthu +t5enthü +ten6ting +4t3entk +4t5entrü +4tentz +te2ny +ten6z5erw +tenz6wei +ten4zym +teo2b +te3o2f +te5o4rei +te3ou +t2ep +2tep. +te3p2f +4tepit +4t3epoc +4teppe +4teppu +t4er. +t4era +te3ra. +ter5abe +ter3a4c +tera4d +4terad. +ter5ade +ter5aff +ter3a4g +ter3ak +ter5alar +te5ralk +ter5all +te6ralm +ter5alp +te5rals +ter5alt +te5ramp +te5ran. +te5rane +ter5anz +te4r3ap +ter3a4s +te4rato +ter3a4v +te4r5änd +ter6bauu +4terbe. +6terben. +6terbend +ter6blin +4t3erbs +4terbt +terb4u +4terdäp +4t5erde. +4terdg +terd4r +ter3d4s +3tere. +te4r3e4b +te4r5eck +ter5eier +ter5eige +6t5ereign +ter5eil +te4rein +ter5ell +3tere4m +te4r5ema +te4r5emi +te4r5end +te4r5en5e +te4r5ens +te4r5ent +teren5th +te4r5en4z +4tereo +3terer +te4r5erb +te4r5erd +te4r5erf +te4r5erg +ter5erh +terer6ku +terer4n +terer5o +terer5ö +te4r5erp +te4r5err +te4r5ers +te4r5erw +3teres +te4r3e4t +ter6folgr +terg2 +6tergebn +tergene6s +4t5erhab +ter7hall +ter6haltb +6t5erhöhu +te4r3id +ter5iko +4teril +6t5erinner +terin4v +teri4on +te4riso +te4rit +ter5i4ta +terk2 +t5erklär +6terkranku +ter6löse +3termi +ter4nas +4ternc +terngene6 +4ternwa +tero4be +ter5o4de +ter5oel +ter5ofe +te3roh +te3rok +4terons +ter3ö +ter6probt +terr2 +ter3ra +ter4re. +ter6regt +tersau6er +ter4sep +6tersparn +ter5stag +ter5t4a +tert4s +teru2 +te4r5ufe +terungsex5 +ter5uns +4t3erzb +terz4e +4t5erze. +ter4zes +4t3erzg +t2es +t4es. +te4sa4k +te4s3au +tes5eli +te4s3ep +tes5erb +tes5er5e +tes5erl +te4sex +tes4par +tes4pen +te4sph +tes4por +tes6senz. +tes3si +tess4t +test5ak +te5stal +test5art +tes5tät +tes4teb +te5stec +te5steig +te5s4tel +test5elt +te5step +tes6t5er5e +tes6t5er6f +tes6ter6g +tes6t5erh +tes6terk +test5erkl +tes4tin +t3estn +tes5to5s4 +te4sur +te4tabl +4t3e4tap +4t3e4tat +3tete +4t3e4thi +4tetho +2tetl +tet2s +3t4euf +teu4f3a +2t3euk +2t3eul +3te3um +te3una +te3uni +t3eunu +2t3eup +3teur. +teuren4 +t3euro +teu4r5ob +3teus +te4val +te4van +tewa4s +tex1 +4texam +2t3e2xe +2t3e2xi +2t3exk +2t3exo +2texp +tex4ta +tex4th +tex6trau +2t3exz +tè2 +2t1f2 +tf4ae +tfahr6ta +tfal4l5i +tf4äh +tfen2 +tf4la +2tg2 +tga4s5er +tgas5te +tgas4tr +tga4su +tga2t +tgeb2 +tgel4ta +tgende4 +tge4n5en5 +tge4nid +tgens4 +tger4a +t3gl +tgol6der +2th. +2t1h2a +3t4ha. +t2had +t4haei +3thal. +thal5ba +3thale +3thalh +t4hali +t4han. +3thani +2t1hä +3thäi +2thb +2thc +3the. +3t2hea +2t3hef +2t3hei +t4hein4 +the5ins +3t4hek +the4kau +3t4hema +4themd +3t4heme +t3hemi +4t3hemm +the3na +t3heng +t3henn +3t4heo +5therap +4therd +t3herl +t4herm +4t3herr +t3herz +3t4hese +t3hess +t2heu +t3heu. +2thf +2thg +3thi. +3thic +thic3k +t3hiel +thi5er. +2t3hil +2t3him +th4in +thi3nu +4thinw +2thir +4t3hist +2thk +2th1l +2th3m +thmu2 +thmus3 +2th3n +thn4o +2t3hob +tho5chr +2t3hoe +2t3hoh +4t3hole +4t3holt +4tholz +4thops +4thopt +tho3ra +4t3horn +4t3hose +tho3st +4t3hote +2t3hou +t3hov +2t1hö +2thp +4th3rau +thr4i +th3rin +2ths +2tht2 +t1hu +2thub +t3huet +2thuh +2thup +t2hur +t2hus +2thut +2t1hü +2thv +thvil4 +2thyd +ti3ac +4t3iago +ti5alei +ti3a2m +ti3a2p +ti3as +3tib2 +ti3ce +5ticket +tico2 +ti4dea +4tidee +ti4d5en4d +ti4dio +5tief. +5tiefe. +4tiefel +5tiefen +5tiefg +3tiefl +tieg2 +4tieg. +4tiegen +4tiegs +4tiegt +ti4e3in +4tiel. +tie4l3a +ti3ell +4tiels +tie2n +tien3e +5tiera +tie4r5ei +tieren6z +tie4reu +tie3s4t +2tieß +ti3eu +3tif. +ti3f2r +tifter6ke +tif4tr +3tig +ti4g3an +tige4ra +ti4g5er4z +tiges4 +ti4git +ti4g3la +ti4g3lä +ti4gle +ti4g3li +4tigmat +ti2gn +tigs4e +tigs4p +tig5s4tr +tik5age +tik5amt +ti4kanw +tik5a4sc +ti4kei +tikel3 +ti4k5ent +ti6k5erfa +ti4kim +ti4k3in +ti4k3la +ti6klini +ti4klu +ti2k3n +ti4kob +ti4k3op +ti4k3ra +ti4k3rä +ti4krei +ti4k3ro +tik3s +ti4kun +ti4kut +ti4lant +ti4lar +ti4lau +ti4l3e4b +ti4lei +ti4lel +ti4lent +ti4l3ep +3tilg +3tilie +3tilin +til5ing +til5ins +3tillo +4tillu +ti4l3oe +ti2l3ö +ti4meli +2timm +t5immat +timmer4 +tim6merg +tim6m5erk +3timo +2t3imp +ti3naf +tin5alb +tin3an +ti3nap +3tin2e +ti3nec +ti3ne3i +tinein4 +tinen4z5 +ti4n5erf +tine3t +4t3infe +4t3info +tinga4 +tin4g5ab +ting5as +tin4g3i +tin4gl +tingo4 +tings4p +ting5st +ti3n4in +t3i4n3it +2t3inj +tink3l +tin4kla +tin4kn +4t3inno +ti3no +ti4n3oe4 +ti4nor +ti2n3ö2 +t4ins. +4t5insel +tin4s5es +tin4spa +tin4sum +5tinte. +4t5inter +ti3n4um +2t3inv +3tio +ti4onv +3tip. +tip4pr +3tips +ti4que. +3tirad +t3iran +2t3ird +ti3re +ti3r2h +tirn3a +3tisc +ti5sche. +tisch5ec +tisch5ei +ti5schen. +ti5sches +ti4schl +tisch5lam +ti4schn +ti4schr +ti4schu +tisch5wa +tis5erk +tisex5p +2t3isl +tis4tal +ti3s4th +tis3ti +ti3s4tr +ti4sur +ti3ta +ti4tal +3tite2 +ti3ti +2ti3tu +3tiu +tium4s +3tiv +ti4van +tiv5ant +ti4v5atm +ti4v5att +ti4vel +ti4v5ent +ti4v5erg +ti4v5erh +ti4v5er4k +ti4v5er4l +ti4v5ero +ti4vet +ti4v3id +ti2vo +tiv3or +ti2v3r +ti2za +ti4z3in +ti4zir +ti2z3o +ti2zw +2tj +2t1k2 +tkege4 +tkraf4 +tkue3b +tkü3b +2t1l +tlang5s +tlei4bl +tlei6d5er6 +t2lep +tl4er +tle4r3a +tlergene6 +tle6ring +t3li +tlings5 +t3lö +tlungs5 +2t1m +tm4en +tmen6schl +tmen4t3 +tments4 +tmetaller5 +tmin4s +tmi2t +tmo4des +tm4or +2t1n2 +t3na +tn4am +tn4an +t3ne +tne5reig +tne4rer +tnergene6 +tnis3t +to3al +to2as +to3a2t +4tobj +to4boe +tob4re +to3cha +to3che +4t3ochs +3tocht +2tock +to6ck5ent +to6ckers +to6ck5er6z +t2od +3tod. +toder4 +tod5ern +todi4e +to4d3un +2t3o2dy +3toech +4toeck +2t3oe2d +toe2f +2t3oe2k +4to4el. +4t3oels +4toelu +to3e2m +3t4oen +3toepf +4toeps +to3erh +to3ero +t3oest +2toeß +t3oett +2tof +3tof. +toff3a +tof4f5ei +t5offens +tof6f5ent +tof6f5ertr +4toffi +toff3l +tof4fo +toff3r +toff3s4 +tof4f3u +3tofi +3tofo +3tofr +to3fu +togas3 +2t3ohr3 +2toi. +3toil +toi4ne. +toi2r +2toiz +3toj +tok2l +2t3okt +3tole +to4lym +2tolz +4toma. +tom5ans +tomar4b +to4m5ene +3tomie +3tomis +2tomn +to2mu +tom3un +3ton. +to4nad +tona5er +to4n3an +to4nap +to4nau +ton3d2 +ton3e4b +to4neh +tonend4 +toner6ke +to4nid +ton5ing +3tonn +tonne4 +to4nob +to4nof +3tons +ton4sin +2tony +3too +to4pad +to4pak +4t3o4pal +top5ang +to4pat +to4p3el +to4per +t2opf +3topf. +to3pfä +topf5erd +5topfes +top5flu +to4ph3a +to4p3hi +topi4e +to4ple +to4pli +2topp +tops2 +5topsy +3tor. +tor5aen +to3ral +to3ram +to5rand +tor5ans +tor5ant +tor5anz +to3rat +to4rän +tor6ch5el +4tordn +3tore +tor5eic +to4rein +to4r3el +to4rem +to6renna +tor4fan +t3orga +4t5orgel +to4r3i4d +torin4s +tor5int +tor3n +to4rob +to4roe +to4rop +to2rö +3tors +4t3ort. +tor5tan +4t3orth +4t3ortn +4t3orts +tort4st +tor3um +3torw +torz2 +3tos. +3tosa +tos2h +to4ska +to3s2m +3to3s2p +tosser4 +tos6s5ersc +to5stat +tos4to +to3s4tu +2toß +to4tae +to2tä +to5t4em3 +totemp4 +tote4n +to4tho +to4t3ri +tot3s4 +3t2ou +touil4 +to3un +to1x +4toxid +3töch +2t1ö2d +2t3ö2f +2t1ö2k +t1öl +2töl. +2töle +2töls +2tölu +3tön +3töpf +3törtc +2t3öst +3töt +4t3ött +2t1p2 +tpar4k +tpho2 +2tq +tr2 +2tr. +t4ra. +tra3a +tr4ab +tra4bu +tra5cha +tra5chl +3tract +4t3rad. +tra4dem +tra4der +4t5rades +t5radie +4tradio +5tradit +4tradiu +4t3radl +4tradp +4t3rads +3traeg +4traeh +4traend +5traene +4traeng +4traes +4traeß +tra4fah +4traff +4trafg +traf3l +4trafr +tra4g3l +3tragö +3trahi +4trahl +4trahm +3trai +tra5inh +3trak +4t3rake +3tral. +3trale +tra4leb +3tralg +tra6lins +4tramm +5t4ran. +4trand +tran4d5r +4trang +t5rangi +trang5s +t5ranki +tran4k5o +t3rann +5t4rans +tra5po +tras6terf +tras4t5r +4traß +4trateg +tra4t3h +t5ratin +tra4tr +3trau. +4traub. +4t5raubv +4trauc +4t3raup +4trauß +2t3räd +3träg +2träh +4tränd +3träne +4träng +2träs +2träß +4träuc +4träus +4träuß +t2re +2t3re. +2trea +t3reak +t3real +2treb +tre4bl +tre4b3r +2trec +t3rech +5treck. +5trecô +2t3red +4trefe +3t4reff +4trefl +4t3refo +2t3reg +2t3reh +trei4bl +4treic +4treif +4t3reig +4t3reih +4treik +4t3reim +4t3rein +trei4sc +t3reiß +4treit +trei6terp +t3reiz +2t3rek +2t3rel +t3rena +3trend +4trendi +5trennu +4t3rent +t3repa +4trepe +4t3repo +5trepp +4trepr +t5resek +5tresen +4t3resi +t5resso +tres4st +4trest. +tre5str +4trests +t3resu +tre4ta +3t4rete +tret3h +4t3retr +tre4tra +tre4t5ri +4trett +tr4e2u +treu3c +5treuem +5treues +3treuh +4treun +2t3rev +tre3x +2t3rez +t2ré +2t1rh +3tri. +3trib +4trich. +4triche +trichs4 +trid2 +tri2e +3trieb +trie5fr +tri5ens +tri5ent +3trier +tri3gl +3trigo +t4rik +tri4ke. +tri4kes +5triko +4trikt +3trim +4t3rind +4tring +4t3rinn +3t4rio +t2rip +3trisc +t3riss +tris5ta +4t3ritu +tri2x +trizi3 +3tro. +4trock. +4t3roec +4t3roeh +4troem +3troep +4t5roess +3troj +tro4kes +4trom. +tro6manp +4tromb +tro4men +tromer4 +4tromg +tro4min +4tromk +4troml +5trompe +4trompu +4tromr +4troms +4tromt +4tromv +4t3rond +tro6niks +t3ronn +tro3ny +t4r4op +3tropf +5trophä +3tross +tro5sta +4t3rot. +4trout +3trow +3troy +2t3röc +2t3röh +2tröm +2t3rön +3tröp +4t3röss +3trua +3truc +true5be +true5bu +4t3ruec +truecker6 +5truemm +t3rues +2truf +2truk +trum2 +3trum. +t3rumä +4t3rumm +trums3 +4t3run. +4t3rund +t3rung +5trunke +3t4rup +tru4th +2truw +trü3be +trü3bu +2t3rüc +trücker6 +3trümm +2t3rüs +t2ry1 +2ts +tsa2b +t3sack +t2s3a2d +t4s3aem +ts3aer +t3saeu +ts5aeus +t2sak +t3s4akk +t3sakr +t4s3all +t4s3alt +t4s3amb +t5samm +ts3amp +t4s3amt +t2san +ts3ang +ts3ank +ts3ant +ts3anz +t2sa2p +t2sa2r +t3s4ark +t2s3as +t2s3au +t3s4au. +tsau4d +ts4au4e +tsau4m +tsau4n +tsauto6r +ts2äc +t2sär +t2säs +t4s3äus +ts3b +ts4cap +t2sce +t6schart +t4s5chef +t6s5chine +t3schl +tsch6lic +tsch4lo +t6schmue +t6schmun +t4schmü +t6schor. +t6schore +t6schors +t4schro +t4sch5wo +t4sch5wö +tsco2 +t2s3e2b +tse2c +t2s3e2d +tse2e +tsee3i +t2sef +ts6ein. +t4s5eind +ts5einf +t4s5einh +t4s5einl +t4s5eint +t5seit +ts3e4kl +tse4la +t4s5e4mis +tsen5er +t4senq +t4s3ent +t4s3e4pi +ts3epo +t2ser +ts3er3e +ts3erf +ts3erg +ts3erh +ts5e4r5in +ts3erk +ts3erl +ts3erm +ts3ern +ts3er3o +ts3erö +ts3err +ts3ers +ts3ert +tse3r4u +ts3erw +ts3erz +t6s5essen +tse2t +t4s3e4ta +t4s3eth +t4s3eti +t2s3e2v +t2sex2 +t3s4exi +ts3ha +t2s3i2d +t3sig +t2si2k +ts3iko +ts3ima +t4s3imp +tsi2n +t4s3ind +tsing4 +tsin5gl +t4s3ini +t4s3int +t5skala +ts4kele +t5sklav +ts3ko +t3slip +t3s4mog +t2so2b +ts3oef +t4s3off +t3son +t2s3op +tso2r +t4s3ora +t4s3orc +t4s3ori +t4s3orn +t4s5ort. +t4s5orts +t3sos +t2s3o2v +t2sö +ts3öf +ts2öl +t3span +t5s4pare +t3spek +t4speri +t5sperr +t4s3pic +t4spins +t2spl +t5s6port. +t5s6portl +ts6ports +ts4prac +t4sprei +t5sprun +t3spue +ts4pul +ts5rei +ts3s2 +tsse5ru +tss4t +t1st2 +t6s5taeti +t4stafe +ts3tak +t4stale +ts5tank +ts5tant +t4s5tanz +t4s5tat. +t4s5tauc +ts3tav +t4s5täti +t4steil +t5stein +t4step +ts4terb +ts4tern +ts4tero +ts3tex +ts3th +ts4tig +t4stit +t4stoch +t4ston +t4stört +t4strac +ts4trap +ts4tras +t4stren +t4strie +ts4trom +tstro6ma +t4strun +t5s4tub +ts4tuem +tstu5er. +t2sty +t2su. +3tsubi +t2s3ul +t4s3um. +t4s3umb +t4s3umd +t4sumf +t4sumg +t4sumk +t4suml +t4sumr +t4s3ums +t4s3umv +t4s3umw +t4sumz +t2s3un +t2s3ur +t2s3ut +tswa4s +t5szene +2tt +tt3abm +t3t2ac +tt5achs +tt3aeh +tt3aer +ttafe4 +tta6g5ess +tt3ahl +tta4ke +t4t3alb +t4t3ana +t4tanb +t4t3and +tt5anka +tt5ankä +ttan4k5l +tt5anku +t4tanr +t4tanw +t4t3app +t4t3arb +tt3arm +tt3art +tt3äh +tt3änd +tt3är +tte2c +tt3eck +t2t3ef +t4t3eib +t4t3eif +tteil5a +tteile4 +ttei6leb +t4t3eim +tt3ein +t4t3eis +tte4l3a4 +tte4l5e4b +ttel5ein +tte4l5en +tte4l5in +tte4lit +tte4l3o +tte4lö +ttels4t +ttel5ste +ttem4bo +t4temo +t4temö +t4t3emu +t3ten +tte4na +tte4net +tte4nob +t6ten6sem +ttens4p +t4t5entb +t4t5entf +t4tents +t2teo +tte4rat +tte4r5ec +tte6r5ei6s +tteri4e +tte4rik +tte4ris +tter6klä +t6terneu +t4t5ernt +tter5oe +tte4r5oh +ttesa4 +ttes5ac +ttes5ae4 +tte4s3ä4 +tte4s3o +ttes5pr +ttes6ser +tte5stri +t4teti +tt5extr +t4t3heb +tt3hel +tt3hen +t4t3hin +t2t3ho +t2ti2d +tti3es +t4t3igi +tti5le +tt3imm +t4t3ind +t4t3inf +t4t3ins +tt5isol +tti3z +t4t3la +t4tli +tto3ef +tt3oel +tto5ent +tto3er +tt3ord +t4t3org +t4t3orn +tto3s +tt5rand +tt3rea +tt3rec +tt2ro +tt5roch +t3troe +tt5rose +t4trou +t3trö +tt3rud +tts5and +ttsau4 +ttschi4 +tt4sele +tt4s5end +tts5erb +tts3pr +tts5taet +tt4s5tät +tt4stem +tts4tu +tt4sum +ttu3en +ttu2f +tt3ufe +tt3uh +ttur4m3 +ttü1 +ttwa3r +ttz2 +tu3al. +tu3alm +tu3alp +tu3alv +tu3ant +tu3ar. +tu3ars +tuba3b4 +3tuch +tu4chel +tu3cho +tuch3s4 +tu3cka +2tud +tudie4n5 +3tudo +2tueb +tue5ber. +3tuech +2tueh +tu3ell +tu3en. +tu3enb +tu3eng +tu3enk +tu3ens +tu3ent +3tuer. +3tuerc +5tuere +tu3ern +tue4r3o +tu5ers. +4tuerz +tu3es +tue4th +4tuetz +2tuf +tuf4e +tu5fen +t3u4fer +3tuff +tu4gan +2tuh +tuhl3a +tuh4lei +tu3ist +tu2kr +2t3ulm +tul4pa +tul4pf +tul4ph +3tum. +tum4b3l +tume4 +2tumf +2tumg +2t3umh +2tumk +2tuml +3t4umo +2tump +2tumr +4t5umsat +t3umsc +tum4si +tum4s3o +tum4spe +2t3umt +2t3umw +2tumz +2t3una +3tuna. +3tunas +2tund +tund4e +tun4d5ei +tun3dr +3tune +tune4b +tun4en +t3unf +3tung. +4t3unga +4t5ungeh +4t5unger +4tunget +tungs5 +4tunif +4t3u4nio +4tuniv +2t3unl +2tunm +3t4unn +2t3u2no +2tunr +t3unsc +2tunt +3tunt. +2tunv +t3unz +2t3up. +2tupd +2t3upg +2tups +t2ur +tur5abe +tu4r3ac +tu4rad +tu4rae +tu4r3a4g +tur5aka +tu4r5alm +tu4ralt +tu4ram +tu4r5ang +tur5ank +tur5ans +tu4r5anz +tur5asp +tura5st +tu4rau +tur3c +tu4r3e4b +tur5eic +tur5ein +tur5eis +tu4rep +tur5erb +tur5erg +tur5erh +tur5erk +tur5erl +tur5ern +tur5ers +tur5ert +tur5erw +tu4res +tur5es5t +tu4r3e4t +tu4r3e4v +tu4rid +tu4ril +tur5ini +tur5ins +tur5int +tu2r3o +turo4p +tur5sa +tur3sk +tur3sn +tu2ru2 +tur3uf +tur3un +tu2s3a +tu4schl +tu4schm +tu2se +tu2s3i +tu2so +tu3spr +tu3t4a +tuts3c +tuts5p +2tüb +tü5ber. +3tüch +tück4se +3tüf +3tüll +3tüm. +3tüma +3tümb +3tümc +3tümf +3tümi +3tüml +3tüms +3tümt +3tümu +3tümv +3tümw +3tür. +3tür3c +3türe +3türg +3türk +3tür3s +3türw +3tütc +2tv +tvie2 +tvoran4 +2tw2 +twa3i2 +twa2r +twa2s +twegs4 +twe4n3e +twetterer5 +twi2e +3t4wist +2t1x +tya2 +3typ +ty2p3a +ty2pf +ty1s2 +2tz +tz1a2 +t2zat +t2z3än +t2zäp +t2z3är +tz3e4di +t4z3eie +t4z3eim +tze5ina +tze5ind +t4z3eis +tzei6t5end +t4zelek +tze2n +tzene4 +tzen5eb +t6zensem +t4z5entg +t4zentl +t4z5ents +t4zentw +t4zentz +t5zer. +tzer6bre +tze4reb +tze4rer +tz6erfe +tz6erfr +tzer6gre +t4z5erhö +tzerin6s +tz6erl. +tz5ermi +t5zern +tzero4 +t4z3erö +tzer4öf +t6z5ertra +t6z5erträ +tzer6werb +tz5erzi +tze2s +t2z3i2d +tz3imi +t4z3ind +tz3inf +t4z3inh +tz5insa +tz3int +tz3inv +tz3iso +tzo2b +tz3oef +t2z3ok +tz3om +t2z3or +tz3ti +tz4tin +tzu4gu +t4z3unf +t4z3uni +tzwan4d5 +t3zwie +2ua +u1a2b +u1a2c +ua4ch +uad3a +ua4dan +uad2r +uae2s +ua2g +u3al3a2 +u3a2l3ä +u3alb +u3ald +uale2 +u3a4leb +u3a4lei +u5alemp +u5a4l5ent +ualer4 +u5a4l5erf +u5a4l5erg +u5a4l5erk +u5a4l5erl +ua4l5err +u5a4l5erz +u3a4let +u3alf +u3alg +u3alh +u2ali +u3a4l3id +u3a4l3in +u3alk +u3aln +ua2l3o2 +ualp2 +u3alr +u3als +u3alt +ual5to +u3a2lu +u3alw +ualz2 +u1am +u3an. +u3anar +u2ane +u3ans +uante4 +uanten5 +uara4b +uarer3 +uar4k3a +uar4k3e +uark3s +uar4t5a4n5 +uart3s +u3arw +u3as. +uas2a +uasi3 +uas2p +ua2th +uati6ons. +u1au +uauf3 +u1ay +u1äm +uä2s +u1äu +2ub +ub5anke +ub3ant +u4b3arb +uba2s +ub3asc +ubb2l +ube4be +u4b3ehe +u4b3eig +u4b5eins +ub5eint +ub5ein4w +u4b3e4mi +uben3o +u4b5erde +uberin6t +ube4woe +ube4wö +u3b4is +u3b4it +ub2l +ub3läu +ub3los +u4b3lus +ubmi2 +u2bo2b +u2b3ok +u2bop +u2b3oz +ub5rank +ub2ri +ub2ru +ub2rü +ub4s5ang +ubsau4 +ub4s5auf +ub4s5aus +ub4sin +ub4s3or +ubst4 +ubs5trau +ub4s5tri +ubt2h +u4bueb +u2büb +2uc +uc1c +u3cha. +uch3ab +uch3ad +uch3ae +uch3al +uch3am +uch5ank +uch5ans +uch5ant +uch5anz +uch5arte +uch3as +uch3au +uch3ä +u3che +u4ch3e4b +u4ch3ed +uch3ei +u4ch5elf +uch5ell +uchens4 +u4ch3ep +ucherin6s +ucherin6t +ucher6ken +u6cher6lau +uch5erlö +uch4erz +uch5er6zeu +u5ches +uche4t +uch5eti +uch3i4d +u5chig. +uch3il +uch3im +uch3in +uch3l +uch3m +uch5ma +uch5mi +uch3n +u3cho. +uch3o4b +uch5oef +uch3op +uch5org +uch5ort +u3chos +uch3öf +uch3r +uch4san +uch6s5ein +uch4s5el +uch4sem +uchser6z +uch4s5in +uch4s5or +uch5stran +uch5stub +uch4tak +uch6t5eig +uch6t5erf +ucht5erl +uch6t5ersc +uch6t5er6t +ucht5erw +uch6t5erz +uch4t5in +ucht5sk +u3chu2 +uch3uh +u4chum +u4chur +uch3ut +uch3w +u1ci +ucin3 +u4ckar +uck5elf +u4ck5ene +u4ckent +u4ck5en4z +u5ckero +u2cki +uck3in +uck5spe +uck5stu +u1cl +u1cr +2ud +u3d2a +uda3d +ude3e +ude3i2 +udein4 +ude4n3e +udens4 +ude6rent +ude6rerr +uderna5 +uder3o +uder3ö +ude4str +udiener6 +udi2o3 +uditi4 +ud2ob +udo3c +u3d4o3e +u2don +ud4o3s +u4d3rat +2ue. +2uea +1u2e2b +2ueba +ueb3ae +2ue4bc +2uebd +uebe2 +ueber3 +ue3bi +ue4bk +ueb5le +ue3blo +ue3bo +ue4bs +2u2ec +uech4s5c +ueck5erb +ue6ckerf +ueck5erh +ue6ck5ers +ue6ckerz +ue2d3a2 +uede4c +ue5den. +ue2d3o4 +ue2d3r +ued3s2 +ued3t2 +2uee2 +ue3er +2uef +ue2fa +uef3ae +ue4f3ei +ue4f3in +ue2f3l +ue2f3r +uefs2 +ue4fum +2ueg +uege4l5a4 +uege6lei6s +ue2gl +ue2gn +u2eh +ue2h3a +uehan5 +ue4h3ei +ue4h5eng +ue4hent +ue4h5erf +ueh5erk +ue4h5er4z +ue2h3i +ueh4lei +ueh4l3o +uehn4s +ueho2 +ue4h3ob +ue4h3of +u1ei +2uek +2uel +ue3lak +ue4l3am +ue4lau +uel5eim +uel5ein +ue4lek +uel4er +ueler5ei +ueli2 +ue4lio +uella4 +uel4l5ad +uel4lar +uel4l5au +uell5e4c +uel4l5ei +uelle4n +uel4lo +ue4lor +2uem +ue4ment +ue4m5erz +ue4m3id +ue4m3in +uemm4s +uem2s +ue2mu +2uen +ue2na +uena4g +uen3ah +uen3ar +ue2nä +uen4d5ei +uendergene6 +uen4due +uendungsgene6 +uen5ema +uen5ens +uen5erk +ue4n5er4l +uen5esk +uen4fak +uen4gag +uenge6bu +uengene6 +uenge6schl +uenge6wan +u3enh +ueni2 +ue4n3in +uenk4l +uenk4r +ue2no +uen4seb +uen4ser +uen5ska +uens6por +u3env +uen6z5eig +uen4z5in +uen4zw +2ue1o +2uep +uepfel5 +ue2po +uer3a +uera4de +ue4ra4t +ue2r3ä +uer3d2 +ue5re. +ue4r3ec +uer3ed +ue3rei +ue5rei. +uerei5s4 +uer5ela +uere4m +uer5emi +ue4r5eng +ue4r5ent5 +ue4rerf +ue4r5er4g +ue4r5er4h +uerer4k +uerer4l +ue4r5er4m +ue4r5err +ue6r5er6sa +ue6r5ersc +uerer6sp +ue6r5er6st +uerer6za +uerer6zä +u5er5esk +u5eress +ueres6si +uere4t +uer5eta +u5ereu +uer6fahru +uer4fas +uer6g5eng +ue4r3i4d +ue4r3i4m +uerin6nu +uerin4t +uerk4l +uer4le. +uerme4c +uer4nan +uer4nau +uer4neu +uerns4t +ue3ro +uer5ode +uero3s +uer4rh +uerr4s +uers4k +uers4ta +uer5stag +uer5stau +uer6trag +u3e4rue +u3e4ruh +u3erum +u5erunf +u5erunt +u3erur +uer4zoe +2ues +uesch5af +ue4schl +uesen5e +uese3s +ue4sin +ues3l +uesli3 +ues2p +ues4sau +ues4s3c +ues4so +ues4st +ues5tel +ues4th +2ueß +2uet +ue4t3al +ue4tap +ue4tau +ue3te +ue4tik +ue4t3ro +uet6schw +uet4st +uety2 +2uev +2uew +u1e2x1 +2uez +u2fab +uf3abe +u2f3ad +uf3aen +ufae4ß +ufall4 +ufa2n +uf3ana +u4f3an3e +ufa2r +uf3are +uf3arm +u2f3aß +u2fa2t +uf3au +u2f3än +u2f3ä2ß +u2fei +uf3eig +uf3ein +ufel4s5a +ufel4s5i +u4f3ent +ufen4te +ufer4fo +uferin6s +uf5erke +ufer6lau +u4f5erne +ufe2t +u4f3eta +u4f3eth +u4f3eur +u2fex +uffer5e +ufferin6h +uff2l +uf4fot +uff2r +uf4fro +u2f3id +u2f3im +u4f3ins +u4f3int +u4f3inv +uf1l +uf3lim +uf3lit +uflos3 +u3fluc +u3flug +u3flüg +u2fob +u2f3of +u2fop +ufo2r +uf3ori +uf5orte +uf1r +uf4rass +u3frän +ufs3an +uf4s5eta +uf4s3in +uf5skie +uf2so +ufs4por +uf4spre +uf4spro +uf5stras +ufta4g +uft3eb +ufte4m +uft5erd +uft5er4g +uft5er4l +uft3in +uft3s4 +ufuen4 +uf3ums +uf3urk +ufus2 +2ug +ug3a4br +u4gabs +u4gabte +u2g3a2d +ug3aeh +ug3aku +u4g3ana +u4ganb +u6gangeb +u4gani +u4g3ank +u4g3ant +u4g3anz +u2g3ap +ug3ars +uga2s +ug3asc +ug3au +u4gaus +ug3d2 +ugd4e +ugeb2 +u2g3ec +u4g3eig +u4g3ein +ug5eise +u4gelem +ugena4b +ugen6dre +ugen6d5ri +uge4n5en +ugens4 +u4gentf +ugenz4 +u4g5erke +u4g5ersa +uges4 +u5ges. +u4g3esk +u4g3ess +ugge4l5a +ug3i4de +ugi3en +u2g3im +ugin4h +ugin6ner +u4g3ins +ugin4sp +ug1l +u4gläu +u6gleist +u6gleitb +u6gleits +u6gleitu +u2gli +ug3liz +u4glok +u2g3n +ugo1 +u2go2b +u2g3oc +u2g3oe +u2gof +u2gom +ug3o2p +u2g3o2r +u2gö +u4g3ram +u4g3rat +u4g3reg +u4g5reis +u4g3res +u4g5rind +u4grou +ug3rum +ug3rüs +ugs4alb +ugs4and +ugs2c +ug4sein +ug4sel +ug4sin +ug5span +ugs4por +ug5stan +ug5s4tie +ugs4tol +ug5stre +ug4s5tür +u1ha +u1hä +2u1he +uhe3a2 +uhe3b +uhe3e2 +uhein4t +uhe5rae +uhe3s2 +2u1hi +2uhl +uh4l3as +uh3lä +uhl5ein +uh4l5ent +uh4l5erb +uh4lob +uhmes3 +u1ho +u1hö +uhr3an +uhr3au +uh3rei +uhrei4s +uh4r3er +uhr3o +uh2ru +uh1s2 +uht2 +2u1hü +uh3w +2ui +ui2a +ui4che +ui3cho +ui4cker +u3idd +u3idm +u3ids +u3idt +u1ie +ui3em +u1ig +ui2ge +uik2 +uil4les +u1im +uin3it +u3inl +u3inw +u4ische +uisi4n +u3ism +uis3ta +u3isv +u2it +ui4te. +ui2t3r +ui1x +uji3 +u1ke +uke2n +uken3e +uk3er3e +u1ki +u1k2l +ukle3i +uk3ler +uk2n +u2k3o2b +uko3c +uko4ma +uk2r +u4krai +uk2t3a +uk2t3ä +uk4t3eb +uk4t3el +uk4t5ent +uk4t3er +uk2th +uk4t3i4m +uk4t3in +uk4top +uk4t5o4ri +uk2t3r +uk4t3um +uk2u +ukue3b +uku2s +uk2ü +ukü3b +u1la +u3la. +u2l3af +u3l4ag +ulan4di +u4lanf +ulap3 +ula4ra +ularen4 +ula6r5end +ularz4 +ula2s +ulasch5 +ul3aus +u1lä +ul3äm +ulb2 +ul1c +ulda3e +uldan3 +ul4dar +ulde4n +ul4d5erf +ul4d5erk +ul4d5erl +ul4d5ers +ul4d5rue +ul4drü +ul3eig +u2lek +ul3elf +ul3elt +ul3emb +ule2n +ulen3e +ulen3i +ulenk4 +u2lep +ul3epe +ul3e4po +ul3er4h +ul3er4r +u4leru +ule4s3a +ules3t +ule2t +ul3eta +ul3ext +ulf2 +ulg2 +uli2k +ulik5an +ulik3o +uli3pf +ulk4a +ulk2l +ulk2r +3ulkt +ul3le +ull5eic +ull5ein +ul4lem +ull5endu +ull5erkl +ul4lid +ul4l3o4d +ul4lop +ull3s2 +ulni2 +ulo2i +ulom2 +ulomb3 +u2l3o2p +u2l3or +ulps2 +ul4sal +ul4sam +ul4s3ec +ul4ser +ul4s3in +uls6t5erk +uls4t3h +ul4sum +4ulta +ult3ar +ul4tau +ultauto6 +ult5ein +ulter6min +ulti5t +ult3s2 +u1lu +ul3umb +u2l3ur +ul2vr +ulz2w +u4m5a4ben +uma2c +u4m5aest +u4m3aku +um3alb +u4m3all +u4m3alt +u5mann +u4m3app +u4marc +u4m3arg +u4m3arm +um3aro +u4m3art +u4m5asts +u4m3atl +u4m3atm +u4m3aus +u4maut +um3ärg +u4m3äst +umd2 +u2m3e2b +u4m3e4ck +u2m3ef +um3eic +u4m3ei4n3 +u4m3elf +umens4 +u2m3e2p +umer4a +u4merd +u4m3erf +u4m3erg +u4m3erh +u4m5erku +u4m3erl +u4m3ero +u4m3erw +1umf +1umg +2umgü +um3ill +um3inh +um3ir +um3iso +umi2t +um3ite +1umk +1uml +um3me +4ummel +ummi5erz +um3n +u2m3ob +u4moel +3u2m3o2f +u2m3o2p +3umöf +ump4fa +ump4fin +4umpfs +ump5ler +1umr +umsch4 +umsche6f +um4s3ed +ums5ein +um4ser +ums5erw +um2sk +um3s4kl +ums4ter +um4sum +umt2 +um3to +umu2r +u1mü +u2m1y +1umz +2umzw +un1 +4una. +u2nab +un4abe +4u3n4abr +u3nac +u3nae +un2ag +u3n2al +un4apa +un4ara +4un2as +una3st +u3n4au. +u3naue +u3naus +u3naz +un2är +2und. +un4da4b +und3ak +und5art +un4d3as +un4dän +3undd +2unde +un4d3ec +undein6f +und5eis +undemi4 +unde5ras +under5ei +underer6 +und5eroe +un4d5erö +underten6 +under6tend +un4d3ex +4undg +4undi +un4d3id +un4dim +und5in5i +3undn +2undo +un4do4b +un4d3op +4un2dr +und3rä +und3re +und3ri +und3rü +4undsc +undue6b +un4d3um +undü4 +un4düb +3undz +un3ec +une2d +une2h +un4ei. +un3eid +un3ein +un3eis +une2l +u4n3elf +3unemp +une2n +unen3e +uner5o4b +u4n5erz. +un2es2 +uneu3 +u4n3eul +unf2 +un3fa +3unfä +4unfe +3unfr +un4g3ab +un4gad +un4g3a4g +un4gak +un4gam +ung5art +ung5ass +un4gat +un4g3au +ung5eig +un3gen +ungen5erg +ungen6te. +ungen6ten +unger4e +3unget +3ungew +3ungez +un4g3ig +un4g3in +ung5lat +3unglu +3unglü +un2go +un4g3rä +un4g3ri +un4gro +ungs5pa +ungs5tae +ung5stang +un3i4de +3u2nif +unik2 +un3iro +u3nisc +un3isl +3u2niv +2unk +un4k3ak +un4k3an +un4k3au +unker6ke +un4ket +unkgene6 +un4kne +un4kno +un4knu +un4k3ro +un4k3ru +unk3s2 +unk4tit +unk4top +unk4t5ri +unk4tro +un4kum +un4kur +un4kut +un4küb +unloe4 +unlö2 +1unm +unmen4s +unmut4 +un4n3ad +unne2 +unnel3 +unn6en5e +u2no2b +uno2r +1unr +uns2a +unsch5el +un5schen +unsch5wi +3un3si +uns2k +un3ska +uns2p +unsta4g +unste4c +uns6t5erw +unstu4 +un2sz +1unt +2unt. +unt2a +unt3ar +5unterd +unteren6 +unte4ri +unt6ers +2unth +2unti +unti4m +2unto +unt2ö +unt3s2 +un3t4u +unuss4 +1unv +2unvo +unvol4 +unvoll5 +1unw +2unwä +u3ny +unz2w +2uo +u1o2b +u1oc +uoe2d +u1of +u3or. +u3or3a +u3or3c +u3o4ret +u3org +u3orh +u3o4ris +u3ork +u3orm +u3ors +u3ort +uo3ru +u3orv +u3orw +uos2 +u3os. +uote2 +u1ox +u1ö +uö2d +u2ös +1upd +upe2r +upe4r3a4 +uper3e +uperer4 +upf3au +u4pfen +upf6erp +u2pfi +u4pf5lau +u2p2fo +u1pi +up2l +2u3po +2upp +up2pf +up2pl +uppor4 +upport5 +u1pr +up3sl +up4t3a2 +upt5en5e +upt5erf +upt5erg +upt5erk +upt5err +upt5ers +upti4d +upt3il +upti4m +upt3in +upt3o +upt3r +2ur. +u1ra +u4r3abb +ura4ben +ur3abt +ur3adr +ur3ael +ur3ae4m +ur3aen +u4r3akt +ural6leg +u5rallei +u5rallo +ur3alt +ural4ta +ural4te +ur3alu +ur3amp +3u4ran. +u4ra4na +uran5at +uran4ba +uran4bi +u4ranf +uran6fän +ur5angs +u4rank +u4rann +ur4anr +uran5s +u4r3apo +u2rar +ura4ren +ura4ri +u4r3atl +u4r3att +ur3auf +ur3auk +ur3aus +u2r3a2v +ur3äl +ur3ä2m +ur3än +ur3b4a +urchi4 +ure3am +ure4be. +ure3e +ur3eff +ur3ehr +ur3eig +u4r3ele +ure4ne +u4r5ener +uren6gag +urens4 +uren6sem +u4r5entg +uren6thu +u4r5entn +uren4zy +ur3epo +u2r3e2q +urer5eig +ure3un +u4rex +urfar4m5 +urf5eig +urf5erg +urf5erk +ur4f5erl +ur4f3li +ur4f5rec +ur4fro +urf5str +ur4f3uh +ur6gense +urg5inn +ur4g3ri +urgros4 +urg3s2 +u1ri +uri4c +ur3ide +ur3ill +ur3imp +u4r3ind +u4rini +urin5it +ur6ins. +ur5in6sek +urin6stin +u4rint +u4r3i4so +urk2l +urk2o +urk3sp +ur4m3ab +ur4maeh +ur4m5ang +ur4m5art +ur4matt +ur4mäh +ur5me. +urm5eie +urm5einh +ur4m5ere +ur5mes +urms2 +ur4mum +ur4m3ur +ur4n5ere +ur4nol +u1ro +ur3ofe +ur3off +u4r3orc +ur3org +u1rö +urp2 +urr2a +3ursac +ur4san +urs5ang +ursau4e +urs5auf +ur4seb +urs5ere +urs5erf +urs5erk +urs5ert +ur4sex +urs5inf +urs5int +ur2sk +ur4staf +urs5tak +urs5tele +urster4 +ur4s3um +ur4s3un +ur4tai +urt5erz +urt2s +urts4c +u1ru +urue5cken +urueck5er5 +uruf2 +uru3fl +ur3uhr +ur3umb +ur3umh +ur3ums +uru3s +u1rü +urück5er5 +urve4n +ur2za2 +urz3ac +ur4z3ec +ur4zeg +ur4z3ep +urzer3 +ur4z5ere +ur4z5erk +ur4z5erl +ur4zin +ur4z3op +ur2zw +us3aar +usa2b +us5achs +us3aeh +us3aer +us3aes +usa4gi +usa2k +usa4la +us5alar +u5sali +us5alta +u5salz +us3amb +us5amma +us3amt +u4s3ang +us3ant +us3anz +u2sar +usa4ro +us3ast +usauto4 +u2säh +u2sär +u2s3äs +us5bli +usby2 +u2sce +us2ch +u4schab +u6schaer +u4schak +u4schar +u4schas +u4schär +u4schef +usch5eic +u6sch5eind +usch5einh +u6schene +u6schent +usch5eul +us4chi +u6schimp +u6schlan +usch5lap +u6schlauf +u6schlem +usch5mis +u4schmo +u4schmö +u6schmus +u4schof +u4schok +u4schor +u4schur +u2sco +usde3s4 +use5be +u2s3e2c +use3en +u2s3ei +u3seid +us5e4kel +us2en +usen6dac +usen6dere +usenk4 +u6sen6sem +u4sentl +us3erd +use4rec +u4s5er4pe +u2se2x +us3g +u3shid +u2sid +usi5er. +usi5ers. +usi4kas +usi4kat +usiken4 +us3ind +us3inf +us3int +u2sk +us5lob +us3oc +u2s3oh +u2s3op +us3orb +us3orc +us3ost +us3ot +us3ou +us4pate +us4patz +u3s4pec +u3spek +us3pic +u3s4piz +us6port. +us6ports +u4sprae +u4s5prä +us4s3ad +us4s3eb +us4s3ec +usse4g +uss5einf +uss5eins +us4seka +usse4kr +us4sema +us4semä +usse4mi +usse4n +ussen5e +us4sep +us5s6er. +usser6fa +usser5kr +us6s5erla +uss5er6le +usser5w +uss5erz +us4sesp +us4sete +us4sez +us4sph +uss5tal +us4stor +ussu2 +us4s3um +uss5urt +u4st. +u3stad +ust5arm +usta4v +ust5ave +u6stele +ustell5a +ust6er. +uster6fü +uster6tr +us4t5ess +us5thal +u3stir +u3s4tol +u4stöp +u5strah +u5streck +u5s4tuec +u2sue4 +u2s3uf +u4s3umb +u4s3umd +u4sumg +u4sumz +u2sü +u2sya +u4s3zi +2uß +u2ß3eb +uße2n +ußen3e +u2ß1u +2ut +ut5ablo +ut3abr +ut3adl +ut3aeh +ut3aer +u5tafe +ut3aff +u2tah +ut3akt +ut3alg +ut3alk +ut3al4l +u4t3alt +u2t3a2m +ut3ana +ut3and +u2t3ar +ut3ass +ut3auf +u4taut +ut3äh +u2t3är +u3tea +ut3ede +ute3e +uteg2 +u3teh +ut3ei. +ut3eie +u4t3eig +ut3eiw +ute4la +uten4ac +ute4n3e +utens4 +u4t5entf +uter3a4 +utergene6 +u4t5erhö +uteri4e5 +ute6ring +u4t5ernt +u4terwä +ut4es +ut4et +ut4ev +u2tex +utfi2 +ut3hel +u2t3hi +u2t3ho +u2t3hu +u2t3i2d +u4t5igel +3u4tili +ut3ind +u4t3inf +u2t3ir +u4tiso +uti4vi +uto3c +ut3oef +uto5erf +uto5ers +uto5erz +u3tof +u3tom +u4tops +u4tord +uto6rena +uto6renb +uto6renc +uto6rend +uto6rene +uto6renf +uto6reng +uto6renh +uto6reni +uto6renl +uto6renm +uto6renr +uto6renv +uto6renw +uto6renz +ut3ort +uto3ru +uto3s2 +u2töl +ut5raeu +u4t3räu +ut3rea +ut3res +ut3ril +u4t3rou +ut3röt +u5trup +ut3rü +utsau4 +ut4schl +ut4scho +ut4schö +ut3ser +ut4sin +u4tsk +ut3sky +ut3sp +uts4pe +uts4to +uts4tu +ut3t2l +ut3to +utto3e +ut4t3ro +utt3s2 +utu3en +u2t3um +utu2n +u4t3une +utu4re +utu3ro +utu3ru +ut2za +ut4zed +utz5ehr +utz5eig +ut4z5ene +utzer6tra +utzer6trä +ut4z3e4t +ut4z3in +ut4zis +ut2zo +ut2zö +ut2zw +2u1u2 +uum1 +uume2 +uumen5e +uungsex5 +u1ü +u2ve. +uve3na +uve3ra +uve3rä +uvoran4 +uv2r +2u1x +ux2a +ux1o +ux2or +ux3t2 +u1ya +2uz +uz3al +uz3ar +uz3eck +u4z3ela +uze2n +u4z3ene +u6z5en6tro +u4z3en4z +u6z5er6hoe +u4z5er4hö +uzer4l +u4z5erla +u2z3i2k +u4z3inf +uzi3o +uzo2f +uz3or +uz3ot +uzu4pf +uz3z2 +uz4zb +ü1a +1ü2b +2ü3ba +üb1ä +2übc +2übd +übe2 +2ü3bef +übe3le +ü3bem +übe5n4au +über3 +ü3bes +ü3bi +üb3l +üb5len +üb5les +üb1r +üb4s3te +2üc +ü3che +ü3chi +üch4s3c +ücke4n +ü5cken. +ück5erb +ü4ck5erf +ück5erh +ück5er6ke +ück5erla +ü4ck5ers +ück5erw +ü4ck5erz +ü4ckin +ück4sor +ü4ckum +ü2d1a2 +ü2dä +üde2c +üde2l2 +ü3den. +üden4g +ü3dens +üdi2r +üdi2t +ü2d1o2 +üdo4s +üdost3 +ü2d1ö +üdpol4n +ü2d3r +üd3s2 +üd3t2 +üdu2 +üd3ug +üe2 +üeb3l +ü1ei +2üf +ü2f1a +ü2f1ä +ü2f3ei +ü4f3ent +ü6fer6geb +üfe2t +üf3eti +ü2f1i +üf1l +üf1s2 +üf4tan +üf2t3o +üf4t3ri +üf4t3ro +üft3s +ü2fum +üge4l3a4 +üge4l3ä +ügel3d4 +üge4lec +üge6lei6s +üge4len +üge4ler +üge4lo +ügen3s +ü2g1l +ü2gn +ügs2 +ügs3t +üh1a2 +ühan3 +ühan5d +ü1he +ü2h3ei +ühe2m2 +üh3emb +üh3emi +ü4h3eng +ü4h3ent +ü4h3erf +ü4h3er4k +ü4h3er4z +ü2hex +üh1i2 +ühla4c +üh3lam +ühl3au +üh3lä +üh4l3ei +üh4lel +üh4l5ene +üh2lo +üh2l3ö +ühl4sin +üh3ne +ühn2s +üh1o2 +üh3rad +üh3reh +ühr5ei. +ühr5eie +üh4r5eis +ühren5e +ührens4 +üh3ro +ührungsgene6 +üh1s +ühs2p +üh5stei +üh3t2a +ü1hu +üh3w +ü1k +ül1a +ül2ad +ü2l3ef +ül3eim +ül3ein +üler3a4 +üler5ei +ülergene6 +ülerin6f +ülerin6t +ül3io +ül2la2 +üll3ad +üll3an +üll3au +üll3e4c +ül4l3ei +ül4lent +üller4s +üller6zeu +ül4lid +ül2l3o2 +ül2lö +ülls2 +üll3sk +ü2l1o +ü1lu +ü2m1a +ü4ment +ü2m3e2p +üme4r3a +ümerin6t +ü4m3erz +ümi2 +ü2m3id +ü2m3in +ümm2s +ü2m1u +2ün +ün1a +üna2c +ün2da +ün4d3ei +ünde4l5a +ündergene6 +ün2dr +ünd3s +ündungsgene6 +ün2dü +ünen3 +ü4n3erd +ü4n3er4l +ün2f +ünf3ac +ünf3ak +ünf3ei +ünf3le +ünf3li +ün2gl +ün3gla +ünkoh4 +ünkohl5 +ünn4st +ü2n1o +ün2st +ün3sta +ün5stei +ünste4r +ün3str +ün3t +ü1nu +ün2za +ünze4n +ün2z3i +ünzu2 +ün4z3un +ün2zw +ü1o +2üp +üpfel3 +üpf3l +ü1pi +üp2p3a +üp2p3l +ür1a +ü2r3ei +ü2r3e2l +ü4r3en4t3 +ür4f3er4 +ür4g5en4g +ürge4ra +ü1ri +ürk2l +ürme4c +ü1ro3 +üroe4 +üro5fe +ürom4 +üror4 +ürp2 +ür2rh +ürr2s +ür2st +ürte4l3 +ü1ru +ür4z3au +ür4z3in +ür2zo +ür2zö +ürzt4 +ür2zw +üsch5af +ü3schi +ü4schl +üse3e +üsen3e +üse3r4 +üse3s +üs3l +üsli3 +ü1s2p +üs2s3a +üs2s3c +üsse6l5ers +üsse4l5i +üs2s3o +üs2st +üst3a +üs4tei +üste4n +üs2t3h +üs4tl +2üß +2üt +ü2t3al +ü2t3au +ü3te +üte2n +ütens4 +üter3a +üte6r5eis +üte4rer +üterich6 +ü2t1h +ü2t1r +üt2sa +üt2se +üt2st +ütte4n3 +üt2tr +üt2za +üt2zw +1v +vab2r +2vabs +va1c +va3e2m +va3er +va3fl +v2ak +va3lam +val4en +val5ero +val5erp +val4lan +val4lo +vall3s4 +val4sa +val4sp +val4su +2v3amt +v3ansa +v3ansc +2v3arb +var4d3r +2v3arm +2v3art +va1s +va4s3ek +2v3ass +vas2t +va4s3ta +va2t3a2 +va4tag +va2tä +va4t3ei +vatem4 +va4t5emp +va4ten4g +va4t5enz +vat5ermi +va4tess +va2t3h +va4t3id +vatik4 +va4t3in +vati6ons. +va4t3op +va4t5ora +va4t5ord +va4torg +va2t3r +vat3sa +vat3s4p +vats4t +va2t3u +va2ty +vat3z +2v1au +2v1ax +2vb2 +2v1c +2vd2 +vdi2e +ve2a +ve3ak +2ve3al +ve3an +ve3ar +veb2 +ve1c +ve1e2 +ve4ff +veg2 +2v3eig +2v3ein +veit2 +veits3 +ve3l4ar +ve4l3au +velo5p +velo3s +vembe4 +2v3emp +vendi4 +4v3endu +vene4n +4v5energ +vener6sa +ve4n3in +venk2 +ven4t5a4g +4v3entd +vent4st +venu2 +venzer5 +ven6z5ere +ve3of +ve1p +ve3pn +ve2r1 +ve3ra. +ve5rada +ve5rade +ve5rand +ve5rane +vera4s +ver6b5art +ver5blei +ver5b4li +ver3d4 +vere2 +ve3rec +ve3reg +ve5reif +ve5reig +vere6in +verein6s5tr +ve3rem +ve3res +ver5est +ve4rf4 +ver5fl +ve4r3g2 +ve3rit +verk2 +vermen6s +vern4a +ver3n4e +ver4sep +ver5sta +ver3sw +ve4rt +vert6er +verue4 +ve3rus +verü4 +verz2 +ve2sp +ves3ti +vesti4e +vete3r +veto3 +ve1u2 +ve2ur +2v1f +vf4gh +2vg2 +2v1h +vi3ana +vi3a2r3 +via3t2 +vi2c +vid3s4t +vie4h3a +vieh3s +vie4l3a4 +viele4 +vie6l5er6z +vi4ene +vier5ad +vie4rec +vie4r5ei +vie5r4er +viers4 +vie2w3 +vig2 +vi2l3a +vi2lä +vi4l3e4h +vi4lei +viler4 +vi4l5ers +vi4l3in +vil4le. +vi2l3o +vi2ma2 +vi2n3a +2v3inv +vin4zin +vire4n3 +vi3s2a3 +vise2 +visen3 +vi3si +vis4o +vi5sor +vis2u +vit2 +vi4tam +vi3z +viz4e +vize3i +vize3s +2vj +2v1k2 +v1l2 +v3le +2vlic +v2lie +2v1m +vmwa2 +2v1n +v1ob +vo2be +vob4l +voge2 +vogel3 +vo2gu +vol4l3a +vol4lei +vollen4 +vol6l5end +voller4 +vol6lerf +vol6lert +vol4li +von3s4 +vo2pe +vor1 +vo4ra +voran6schl +voran6ze +vor5de +vo2re2 +vor3es +vo4rie +vo4rim +vo4rin +vo2ro2 +vo2rö +vors2 +vorsch4 +vor5ta +voy1 +vö2c +2v1p2 +v3ps +2vq +2v1ra +v1rä +v2ree +v3ris +2vs +vsau2 +vs2c +vson2 +v1s2p +v3sta +v3stel +v3steu +v3s2tr +vsu1 +v1s2z +2vt2 +vue1 +vu3en. +2vumf +2vumg +2vumk +2vv +2vw +2vz +1w +3waa +wab4bl +w2ac +wa3cho +3wacht +wach6tra +wach6trä +wa3chu +3waeld +4waeng +3waes +w3aeu +waffe4 +waffel5 +wa3ge +3wage4n3 +wa2g3n +3wah +wahlen4 +wah6l5ent +wah4ler +wah4l3i +wahl3s4 +wai2b +wak3l +wa3la5c +3wald +wal4d3a4 +wal4dec +wal4del +wal6denk +wal4din +wal4d3r +wa3le. +wa4les +walf2 +wa3li +wal2m3 +wa2lo +wal4t3a +walter5e +wal6t5erf +wal6terl +wal4t3h +wal4t5in +wal4t3o +wal4tri +walt6sta +wal4tur +3walz +walze4 +walzen5 +walzens6 +wal4zw +wam4mei +wa3na +wa4nau +wan4dab +wan4dan +wan4d5ar +wan4d5as +wan4d5eb +wan4dec +wandels6 +5wander +wand5in +wan4dob +wan4dr +wan4due +wan5ge +wangs3 +3wann +wan4z3a +wanze4 +wan6z5en6d +wan4z5er +wa2p +3ware +wa4ref +ware3i +warein4 +wa3ren +war4ma +war4m3i +3warn +war4th +wart4st +war3u +war4za +war4z5ei +war4zel +war6zent +3was +wa3s2a +wa4scha +wa5sche. +wasch5ei +wa5schen +wa4sch5l +wa4schm +wa5schun +wa4schw +wa3se +wa3s2h +wasser5e +wa3su +3watt +watte5s +3wäh +wäh4lei +wäh3ri +3wäld +wände5s +2wäng +wärme5i +3wäs +2w1äu +2wb2 +wbu2 +2w1c +2wd +we2a +we2b3a +webeat5 +webe3e +we2b3i +we2b3l +we2bo +we2b3r +web3sp +w2ec +we3cha +we5cke. +we5cken. +we5ckes +wecks4 +wee2 +weed3 +2wef +we2fl +3weg +we2g3a +we4g3ei +we4g5en4g +we4g5erk +weg5ersc +we4gim +we4gin +we2g3l +we2g3n +we2g3o2 +we2g3r +weg5s4a +wegs4t +3weh +wehr3a4 +weh4rei +weh4r5er4 +wehr3o4 +wei5be +wei4ble +weib4s +wei3de +weide5r +wei3dr +2weie +2weif +weifel6d +weifs4 +wei4gra +weigs4 +2weik2 +3weil +wei4na +wei6nenz +wein5erz +weinsau6 +wei4san +wei4sas +weiser6g +weiser6h +weisex5 +3weish +weis6sel +weis6spi +wei5s4tr +wei6takt. +wei4tal +wei4t5ra +weit3s +wele4n3 +wel4kl +welle4 +wellen5 +wellende6f +wel6schl +wel6schr +wel4t3a4 +welte4 +wel4teb +welt5ei +wel4tel +wel6t5en6de +wel6tent +wel4ter +welt5ere +welt5ern +welt5ers +welt5erw +welt5erz +wel6t5esc +wel4th +wel4t3i +wel4to +wel4t3r +wel4t3u +wena2 +wendegene6 +wendes4 +wen3f +wen4kau +wen4k5la +wen4k5ri +we2no +wens2 +we4r3a2 +werbe5e +werbe5i +werbes4 +werb4s3 +3werbu +2werc +werde5i +5werdens +3werdu +werer4 +werga4 +wer4g5am +wer4gan +wergel6 +wer6g5els5 +wer6g5en6t +wer4g3l +wer4gn +wer4g3o +wer4g3r +wergs4 +w4e3ri +werin4d +we4ri4o +3werk. +wer4k3a +wer4kä +3werke +werk5erz +wer4k3i +wer4k3l +wer4kne +wer4kop +wer4k5or +wer4k5re +3werks +wer4ku +wer4kü +wer3o +wer3ö +wer3s4h +wer6star +wer4ta +wert5ab +wert5ak +wert5an +wer4t5än +wer6t5eig +wer6ter6g +wer6t5erm +wer6t5ero +wer6t5erö +wer4t3h +3werti +wer4t5in +wer4t3o4 +wer4tre +wer4t5ri +wert5sk +werts4t +wer4t5um +wer4t5ur +3wese +wesen4s +we2sp +we4st +west3a +weste4 +west5ec +west5ei +wes6temp +wes6t5end. +west5ende +wes6teng +wes6t5erwei +wes4tex +wes4th +wes4ti +west3o4 +wes4tor +west3r +wes4tu +2wets +3wett +wet4ta +wett3r +wett3s +we1u +2w1ey +2w1f +2wg +whi2 +whi4t +w3ho +wicht6se +wi3cka +3wid +2wieb +wi3ec3 +wiede4 +wie5la +wien4e +wi2gl +wigs4c +wi3k +wik4i +3wild +wil4d3a4 +wil4del +wil4dr +wim4ma +wim6m5ent +wim4m3u +wi3na +win4d3a +win4d5e4c +win4d5ei +win6d5erz +3win4dr +wind5ri +wine2 +winen3 +win4g3l +win4g3r +wings4 +wing5se +win4kl +win6nerh +win6n5er6sc +win6nerw +win4no +win3sk +wi2r +3wirk +wir6kene +2wirn +3wirt +wi6schef +wi4sch5l +wi4scho +wi4schr +wis4e +wi5sen +3wiss +wiss4z +wis4t3r +wi3th +3witzi +wit4zin +3witzl +w2j +2w1k +2w1l +2w1m +2wn +wn2s3p +3wo1c +wo4cha +woche4 +3woh +woh4lei +woh4l5e4t +woh4na +woh3r +3wolf +wol4fr +wol4l3a +wol4lei +wolls4 +wor3a +wo2r3i +work3a +wort3a +wort3ä +3worte +wort5ei +wor4tel +wor6t5erf +wor6t5erh +wort3h +wor4to +wor4t3r +3wort3s4 +wor3u +wor3ü +wöl4fa +wöl4fo +4wölft +wör2g +2w1p +2wq +2w1ro +2w1s +wser3e +w3s2h +w2ska +ws2ki +ws2ky +ws2t +w2s1u +2wt2 +wti2 +3wuc +wuch4sa +wuch4sc +wuch4sp +3wuec +wue3le +3wuerf +3wuerg +wuer4s +5wuerst +2w1u2f +wun4d3a +wun4dä +wund3r +2wung +wun4g3r +wung5sc +wun2s +wunsch5ei +wunsch5l +wur4f3a +wur4f5ei +wur4fel +wur4fer +wur4f3l +wur4f3o +3wurm +wur4m3a +wur4mei +wur4mer +wurmes4 +wur6m5ess +wur4m3i +wur2s +3wurst +wurst5a +wus2 +wus3te +wu2t3 +wutz2 +3wüc +3wüh +3würf +3würg +3würst +2ww +2wz +x1a +1xa. +2xab +xa2be +xa2br +xaby2 +1x2ac +1x2ad +1x2a1e2 +xa3fl +1x2ag2 +xal3a +xal2l3 +xa2m +xa3mer +2xan +x2an. +xa2na +x3anb +3x2ane +xant4h +3x4anti +3x4anzi +xa2r +1x2as +2xau +xa2z +2xb2 +2xc +xcal2 +x1ce +x1ch +x1cl +2xd +xda2 +xdi2e +1xe +2x1ef +2x3e2g +2x1ei +2xek +xe2l +3xel. +xel3a +xe3lei +2xemp +x2en +xenk2 +xen3s2 +2x1e2p +2x3erl +xer4la +xer3s2 +x3erz +2x1eu +2x1ex +2x1f2 +x3fi +2xg2 +2x1h +x2i +2xia +xib2 +xi1c +xich4 +xi3dan +xide2 +xi4d3ei +xi4d3em +xidi2 +x3i2do +3xi2e +3xik2 +xi4lar +xi4l3au +xi4l3er +xi2lo +xi2l3u +1xim +x3imp +xin3s2 +1xip +xi2s3a +3xisc +xi2s3e +xi4s3or +xiss2 +xi2su +x3i2tu +2x1k2 +xkal2 +2x2l2 +x3la +x3lä +x3le +x3lo +2x1m +xmon2 +2x1n +x1od +xo2du +2xo1e2 +x1o2l +xonen4 +xo4n5end +x1o2r +xos2 +x2ot +2x1ö2 +2x1p2 +x3par +x3pau +x3ph +x3plor +xpor4ta +xpor6t5er6 +xpor4t5r +x3pu +2x1r +2x1s2 +x3sh +xst2 +2xt +xt1a +x2ta2b +x2tak +xt4ane +x2tau +xt1ä +x2tän +x2täs +xtblo4 +xtblock5 +x2t3e2b +x2t3e2d +x4t3eig +xt3ein +x2t3el +x4t3ent +x4t3er4f +x4t3er4g +x4t3er4k +x4t3er4l +xter4s +x4t5ersc +x4t5erse +x4t3erw +x4t3er4z +x2t3e2v +x2t3ex +xtfi4l +xt1h +x2thi +x2t3id +xti4la +xti4lin +xt5il4lu +x2t3im +xt3ind +xt3inf +xt3inh +xt3ins +xt1o2 +x2tob +x2tor +xtra3b4 +xtra3c +xtra3l +xt5rand +xt5raum +xt3rec +x4t3rep +x4t3res +x4t3rol +xt3s2 +xtu3e +x2t3um +x2t3un +x2tup +xtu5ral +1xu +xu1a +xu1e +x1um +xu2n +2xunt +xu2s1 +xusa2 +xuss2 +xus3t +2x2v +x3ve +xvil2 +x3vo +2xw +1xy. +1yac +y2ach +yach4t5r +yacht5s4 +ya1h +ya1i2 +y3al. +ya3la +y3alb +yan2g +y3ank +y3anz +ya2ro +ya3s2p +y1au +y1ät +yb2r +y1c +ycer4 +y4chia +ych3n +yd3al +ydri2 +ydrid3 +ydro3 +y1e +y2ec +ye2d +y2ef +y2el +ye2ne +yen2n +3y2eno +yer3a2 +y2eri +yer2n3 +y2es. +yes2p +ye2th +y1f2 +yfen2 +yg2l +ygro3s +y1h +yhr2 +2y1i2 +y1k2 +yke3n +yk3s +yk3t +yl3ac +yl3aet +y3lak +y2l3a2m +yla2n +ylan3e +y5lant. +y5l4ante +y5l4anti +yl3ar +y3lat +yl3ät +yl3c +y2le. +yle3is +y2l3es +y2l3e2t +ylinde4 +yli4ne. +yli4nes +ylk2 +yl4l3ak +yloid3 +yloni5e +yl3ora +yl1s2 +yltri3 +y1lu +2ym +ym4ae +ym4an +ym4är +ym4el +ym1p2 +ym4pha +ym4phor +ympi3e +2yn +y1na +ynae4r +y2n3an +ynä2r +yndro6ma +y3ne. +yne4c +ynk2 +yn1o +yno2d +yno2t +yno2v +yn3t2 +ynta2 +y1nu +y2od +yof2 +yoff3 +1yo3g +yog4a3 +yonal5s4 +yon4ne +y3ont +y3o2pe +yo3ru +yo1s +y3os. +y3ot. +y1ou +yout3a +2y1p +yp3a2b +ypa2n +yp3än +y2pec +ype4r +y4pesc +y2p3i2d +ypi2e +y2p3in +y2p3lo +ypo3t2 +y2p1s +yp3th +y2p3um +y2pü +y1r +yrae3i +yras4t +yrä3i +y2rh +yri5er. +yri5ers +yro1 +yro4s5te +yrr2 +2ys +ysau2 +ys2c +yse1 +ysein4 +yse2n3 +yse3s2 +y3sho +y3s2n +ys2po +yst4e +ys5tem +yste4n +ys4the +ysti5er +ys4tol +ysto4le +ys4tri +y3s2ty +ysu2 +y2s3ur +y1s2z +yte2n3 +y2tes +yt2h +yt4he +ythi3e +y3to +yto3s2 +y1u +yu2r +yure3 +yvet2 +yvon4 +ywurs4 +y1x +y1y +yze5r4i +1z +2z1aa +2z1ab +2zacc +2z3ach +za3cha +za3chä +2z1a2d +za2e +z3ae2c +z2aeh +2z3aem +4z3aend +4z5aengs +z3aerm +za3ess +2z3aet +4z3aeus +4z3aeuß +za3f4a +2z3aff +za3fl +za2ga +za3gr +3z2ah +zah4n5er4 +za3hol +z2ai +za3im +z1ak +2z3akk +za3kl +2z3alb +2z3ald +zal4e +2z3all +2z3alp +2z3alt +2z1am +2z3a2na3 +2zanb +zan3d2 +2zanf +4z3anga +4z5angeb +z5angel +z3angl +4z3angs +2zanh +zan4ka +zan4kl +2zanl +2z3anm +2zano2 +2zanp +2zanr +2z3ans2 +z4ans. +zanti3 +2zanw +zao2 +zapf3l +2z3a2po +zap4ple +2z1a2q +3zar. +2z3arb +2zarc +zare3s +2z3arg +2z3arm +4z3arti +zar4t3r +zarts4 +2z3arz +z1as +zas2b +zas2p +z1at +3z4aub +2z3au2f3 +2z3aug +2z3auk +3zaun +z3aur +z3ausb +z3ausf +4z3ausg +4z3ausl +4z3auss +4z3ausw +2z3auß +2z3aut +2z1ä2c +zäh4lei +zäh4lin +4zähnl +zäh3r +2z1äm +2z3änd +z3ängs +z1äp +z3ärg +2z3ärm +2z1ät +2zäug +2z3äus +2zäuß +2zäx +2zb2 +z3be +zber4e +z3bo +zbue3b +zbü3b +zby2 +2z1c +2zd2 +zdan2 +zdä1 +zdi2e +zdi5str +z3ds +zdue3b +zdü3b +4z3e4ben +zebra3 +4z3echo +zech3s +zed2 +ze4dit +ze1e2 +2z3eff +zef2l +zeg2 +ze4gal +ze4hem +zehe4n3 +zeh2l +zehn3a +zeh4n5en +zehns4 +zei5cho +zei3f2 +zeik2 +zei5le4 +zeilen5 +2z3ein +ze3in. +z4ein4a +zein4de +zei4n3e +zeinen4 +z4eino +ze5in4se +zeis2 +zei3sk +zei3sp +zei3st +zei4t3a +zei4tä +zeite4 +zei4teb +zei4tei +zei4tel +zei4t5er5 +zei4t3o +zei4tr +zeit5re +zeit5ri +zeit6sto +ze2la +zel5abr +zel3a4d +zel3ae +zel3am +zela4n +zel5an5e +zel5ang +zel3au +ze2l3ä +zel3d4 +ze4led +zelei6er +ze4l5eig +ze5leis +z3e4lek +4zelekt +4zeleme +ze4l3en +ze4l3er +zeler5e +ze4les +zel3ex +zel3id +zel3in +ze4linf +4z3e4lit +zel4lab +zel4l5ac +zellar6t +zel6lein +zeller6z +zell3s4 +ze2lo +zel3ob +zelp2 +zels4t +zel5ste +zel3sz +zel4tau +zel4t5in +zelu2 +zel5unk +ze4lus +zembe4 +zem4e +ze4mis +2z3emp +zen4ade +zena4g +ze5natt +zen3au +ze2nä +zende4c +zende4k +zende4s +ze3nec +zen5en5e +zen5erke +zen5ern +zen5ertr +ze4nerw +zen3f2 +4z5en4ge. +zenge6ne +zengene6r +4zengp +zeng4r +ze4nid +zen5inn +zenk2 +zen3n +ze2n3o +zen4san +zensch4 +zen4sem +zen4ser +zens4po +4z5entna +zent5rae +zent3s +zen4z5ar +zen6z5ers +zen6zert +zen6zerw +zen4zin +zen4zy +zeo2r +ze4pen +ze3p2f +zeppe3 +zer3ad +zer3ae +zer3a4g +zer3an +ze4r3at +ze2r3ä +6z5ereign +ze4rein +ze4remi +zer5end +zer5erw +ze4r3e4t +4z5erfol +4z5erfül +4z5ergae +4z3ergä +zer4gon +4z3er4gu +ze4r3i4d +zerin6di +zerin4f +zerin4h +zerin6te +5zerl. +zer6lang +zer6laub +zer4läu +4z5erleb +zer4lei +zerlo4 +zer6maec +4z5er4mäc +5zerme +zer4nan +zer4n5e4b +zer6nerb +zer5obe +ze3ros +ze4rou +ze3rös +2z3erq +zers2 +z5ersae +4z5ersat +zersau4 +4z3ersä +zer3sk +6z5er6strec +zer4t5a4g +zer4tar +zerte4 +zer6t5emp +zerten4 +zerter5 +zer6t5ere +zer6terl +zer4t5in +zer4ton +zer6t5rau +zert3s4 +6z5erwerb +zer4zä +z4es. +zes2a +zes3au +ze3sch +zes3er +zes4ph +zes4sa4 +zess5ag +zess5an +zes4seb +zessen4 +zes6s5end +zes4ser4 +zess5erf +zess5erz +zes6s5ort. +zes6s5or6te. +zes6s5or6ten +zes6s5orts +zes4spa +zes4spo +zes4sum +zes4s5ur4 +ze5stau +zes4tie +ze5stra +4z3e4tap +4zetat +2z3e2th +4zetts +zeug3a +zeu4gei +zeu4gel +zeu4g5e4m +zeuge4n +zeu6g5ersc +zeu4g3r +2z3eul +ze3um +ze3ur +2z1e2x1 +2z1f2 +zfen2 +2zg2 +zge3n4a +zger4a +zge6r5in6nu +zger4s5e +2z1h2 +zhau4t3 +zi3af +zi5alei +zi5aleng +zi5alim +zi3ang +zi2ar +zib2 +zi3cho +zich6t5er6 +zi4d3ak +zi4d3ei +3zie +zie4hei +zie4l3a +ziele4 +zie4l5eb +zie4lei +zie4l5er +zie4let +zie4l3i +zi3ell +zie2n +zierde4 +zie4r5ei +zi3es. +zi3ess +zi3est +zi3esu +zi3esz +zif4fr +zi3fl +zi4gec +4z3i4gel +zig3sp +zi4kon +zil4e +zi4mit +2z3imp +zim2t3 +4z3inde +zi4n5er4z +zi4n3et +zi4n3in +4z3i4n3it +zin4kae +zin6k5erz +zin4k3l +zinks4 +zin4na +zin4sa +zin5sas +4z5insel +zin4ser +4zinsuf +zi2o +zio3d +zi3on. +zi3ons +zip4a +zirk2 +zirks3 +4zirri +zi3s2z +3zit +zi4t3an +zite2 +zithe4 +zi2t3o2 +2zj +2z1k2 +z3ko +zko3ef +z3kr +zkue3b +zkü3b +z3ky +2z1l +zlan6d5er +zlei5ti +zle3s +z3lo +2z1m2 +zmau4s +2z1n2 +zna5che +z3ne +znebe4 +znei3 +2z3o4as +2z1ob +2z3oe2d +zoe2f +3z4oeg +2z3oek +4zoel. +4z3oels +z1of +zo2gl +2z1oh +zo4lig +zol4l3a4 +zol4l5ei +zol6l5en6t5 +zoller4 +zol6lerf +zol6l5erk +zol6l5erl +zol6lert +zol4lin +zol4lo +zolls4 +zoll5ste +z3o2ly +3zon +zon3s2 +zon4teb +zon4t5er +3zo2o +zoo3s +2z3o2pe +zop4f5en +2zopt +2z3ora +z3orc +z3ord +z3org +zo2ri +zor4n3a +zor4ner +2z3ort +2z3osz +zo2t3a +2z1ou +zo2w +2z1ox +zo2ze +2z1ö2d +2z1ö2f +3zög +2z1ök +z1öl +2zöl. +2zöle +z2öli +z2öll +2zöls +2z1p2 +z3pn +2zq +2z1r2 +z3ra +z3ru +2z1s2 +z3sa +zsau2 +zsch2 +zse3r4u +z3sh +zsi1 +z3sk +z3sl +z3sp +zst2 +zsu1 +zsui2 +2zt +zt3abr +zta2n +z4t3an3e +z4t5anze +z2t3ar +ztee2 +zte3eh +zt3ehe +zt5eins +z4t3ent +z4t3erz +z3t4es +zte3s4k +zte5str +zt3hel +z2t3hi +z2t3ho +z2thu +zt3ind +zt3inf +zt3ins +zt4or +zt4ram +zt3rec +zt3s2 +z4t3urs +ztz2 +zu1 +zub2 +zucht5o +zucht5r +zuchts4 +zud2 +zudi4e +2zueb +zu3ebb +zu3ebn +zue2k +zu2el +zu2en +zu3ent +zu4erc +zu4ern +zue4r3o4 +zu4ers. +zue2t +zu3f2 +zu4g5ans +zu4g3ar +zu4g3at +zug5eis +zu4g5ent +zugg2 +zug5inn +zu4gla +zu4glä +zu4glö +zug2r +zug5rau +zug4san +2z3uhr +zui2 +zu3le +2z3ult +zu3lu +2z3um. +2z3umb +zumen4s +2zumf +2zumg +2z3umh +2zumk +2zuml +zu3mo +2zumr +2z3ums +2z3um4t +zu3m2u +2z3umw +4z3unab +zun4am +zu3n2e +4z3unem +4z3unfa +4zunfä +zunge4n5 +4zunget +4z3ungl +zungs3 +4z3u4nio +2zunr +2z3uns +2zunt +2zunw +zuo2 +zup4fi +zu3r4a +2z3urk +2z3url +2z3urn +2z3urs +2z3urt +zu3s2 +zu5sch4 +zus4e +zus4p +zu3t4a +zu3te +zut4r +zuve4 +zuz2 +2züb +zür3c +2zv +4zwaer +4zwaes +2zwaf +2zwag +2zwah +2zwal +4zwand +z4wang +2z3wap +2zwa2s +zwasse4 +2zwäc +2zwär +2zwäs +2zweb +z2wec +4z3wech +2zwed +2zweg +zweg3s +2zweh +5z4weig +4zweil +4zwein +zwei5sc +zwei6t5er6k +2zwel +2zwen +z4werg +4zwerk +4zwert +2zwes +z2wic +2zwid +zwi2e +4z5wied +4zwild +5z4wing +4z5wint +4zwirr +4zwirt +z4wisc +4zwiss +z4wist +2zwur +zwur4m3 +2zwü +zy3an. +5zyklu +zy2le +zy2m3i +2zz +zz2a +zza3t +z2z3id +z2z3i2k +zzi2n +zz3ini +zzi3s2 +zz2o +zzol2 +z2z3or +zz2ö +zzug4s +zz2w +} + +\endinput + +%%% Local Variables: +%%% mode: tex +%%% coding: utf-8 +%%% End: diff --git a/crates/proof/src/hyphen.rs b/crates/proof/src/hyphen.rs index 9fdae033f6..2b262a92fd 100644 --- a/crates/proof/src/hyphen.rs +++ b/crates/proof/src/hyphen.rs @@ -15,6 +15,9 @@ //! //! **Bulgarian**: the Bulgarian syllable rules ([`crate::bg`]); no pattern file or word list. //! +//! **German**: the TeX German patterns for the reformed spelling ([`Patterns::de`]), also for +//! Swiss text, whose `ss` the patterns split like any double consonant (`Stras-se`). +//! //! Then the paragraph's limits ([`Limits`]) are applied. pub use crate::dict; @@ -36,6 +39,9 @@ static PT_PAT: &str = include_str!("../../../assets/hyphenation/pt.pat"); /// hyph-utf8's `hyph-it.tex`, unmodified (offered under MIT or LPPL; WordCraft uses MIT, whose /// notice is in the file's header). static IT_PAT: &str = include_str!("../../../assets/hyphenation/it.pat"); +/// hyph-utf8's `hyph-de-1996.tex` (reformed spelling), unmodified (MIT; the notice is in the +/// file's header). +static DE_PAT: &str = include_str!("../../../assets/hyphenation/de.pat"); impl Dictionary { /// The bundled US English dictionary (decoded on first use). @@ -77,6 +83,12 @@ impl Patterns { static P: OnceLock = OnceLock::new(); P.get_or_init(|| Patterns::parse(IT_PAT)) } + + /// The bundled German patterns (parsed on first use; the file has no exception words). + pub fn de() -> &'static Patterns { + static P: OnceLock = OnceLock::new(); + P.get_or_init(|| Patterns::parse(DE_PAT)) + } } /// Hyphenation limits from the paragraph's Hyphenation settings. @@ -130,11 +142,11 @@ fn core_breaks(core: &[char], lang: ProofLang) -> (Vec, Source) { if lang == ProofLang::Tr && core.iter().all(|&c| crate::tr::hyphen::is_letter(c)) { return (crate::tr::hyphen::points(core), Source::Rules); } - if lang == ProofLang::Pt { - return core_breaks_pt(core); - } - if lang == ProofLang::It { - return core_breaks_it(core); + match lang { + ProofLang::Pt => return core_breaks_patterns(Patterns::pt(), core), + ProofLang::It => return core_breaks_patterns(Patterns::it(), core), + ProofLang::De => return core_breaks_patterns(Patterns::de(), core), + _ => {} } if lang == ProofLang::Ru && core.iter().all(|&c| crate::ru::is_cyrillic(c)) { return (crate::ru::hyphen::points(core), Source::Heuristic); @@ -178,25 +190,9 @@ fn core_breaks_pl(core: &[char]) -> (Vec, Source) { (heuristic_points_pl(core), Source::Heuristic) } -/// [`core_breaks_pl`] for Portuguese: the bundled patterns, then the generic heuristic. -fn core_breaks_pt(core: &[char]) -> (Vec, Source) { - let pats = Patterns::pt(); - let s: String = core.iter().collect(); - if let Some(p) = pats.exception(&s) { - return (p.to_vec(), Source::Dictionary); - } - if !pats.is_empty() - && let Some(p) = pats.points(core) - { - return (p, Source::Patterns); - } - (heuristic_points(core), Source::Heuristic) -} - -/// [`core_breaks_pt`] for Italian: the bundled patterns (the file has no exception words), -/// then the generic heuristic. -fn core_breaks_it(core: &[char]) -> (Vec, Source) { - let pats = Patterns::it(); +/// [`core_breaks_pl`] for a language with bundled patterns (Portuguese, Italian, German): its +/// exception words, the patterns, then the generic heuristic. +fn core_breaks_patterns(pats: &Patterns, core: &[char]) -> (Vec, Source) { let s: String = core.iter().collect(); if let Some(p) = pats.exception(&s) { return (p.to_vec(), Source::Dictionary); @@ -862,6 +858,19 @@ mod tests { } } + #[test] + fn german_words_follow_the_german_patterns() { + let de = |w: &str| hyphenate_word_in(w, &Limits::default(), ProofLang::De); + assert_eq!(de("Silbentrennung"), "Sil-ben-tren-nung"); + assert_eq!(de("unterliegende"), "un-ter-lie-gen-de"); + assert_eq!(de("öffentlich-rechtlicher"), "öf-fent-lich-recht-li-cher"); + assert_eq!(de("Begründung"), "Be-grün-dung"); + assert_eq!(de("Zucker"), "Zu-cker"); + // Swiss spelling: `ss` splits like any double consonant. + assert_eq!(de("Strasse"), "Stras-se"); + assert_eq!(word_breaks_in("Unterlagen", ProofLang::De).1, Source::Patterns); + } + #[test] fn bulgarian_words_follow_the_bulgarian_rules() { let bg = |w: &str| hyphenate_word_in(w, &Limits::default(), ProofLang::Bg); diff --git a/crates/proof/src/lang.rs b/crates/proof/src/lang.rs index 6129d03ab4..4726ca77b8 100644 --- a/crates/proof/src/lang.rs +++ b/crates/proof/src/lang.rs @@ -39,6 +39,10 @@ pub enum ProofLang { /// `id_ID` dictionary, dictionary-driven suggestions and generic checks with Indonesian /// messages. Hyphenation keeps the fallback English patterns. Id, + /// German (`de`, `de-DE`, `de-AT`, `de-CH`…): hyphenation by the German patterns (reformed + /// spelling). There is no German dictionary or grammar yet, so, as for [`ProofLang::Other`], + /// no spelling or grammar marks. + De, /// A language without proofing tools here (`fr-FR`, `ja-JP`, `x-none`…). Like Word without /// the language's proofing tools: no spelling or grammar marks, so its words are never /// flagged as English misspellings. Hyphenation keeps using the English patterns, as before @@ -76,12 +80,14 @@ impl ProofLang { ProofLang::Cs } else if primary.eq_ignore_ascii_case("id") || primary.eq_ignore_ascii_case("ind") || primary.eq_ignore_ascii_case("in") { ProofLang::Id + } else if ["de", "deu", "ger"].iter().any(|c| primary.eq_ignore_ascii_case(c)) { + ProofLang::De } else { ProofLang::Other } } - /// The primary language subtag (`en`, `pl`, `bg`, `tr`, `ru`, `cs`, `id`); empty for [`ProofLang::Other`]. + /// The primary language subtag (`en`, `pl`, `bg`, `tr`, `ru`, `cs`, `id`, `de`); empty for [`ProofLang::Other`]. pub fn code(self) -> &'static str { match self { ProofLang::En => "en", @@ -94,6 +100,7 @@ impl ProofLang { ProofLang::Ru => "ru", ProofLang::Cs => "cs", ProofLang::Id => "id", + ProofLang::De => "de", ProofLang::Other => "", } } @@ -192,7 +199,11 @@ mod tests { for t in ["tr", "tr-TR", "tr-CY", "TR_tr", "tur"] { assert_eq!(ProofLang::from_tag(Some(t)), ProofLang::Tr, "{t}"); } - for t in ["fr-FR", "de", "x-none", "ja-JP", "plx", "bgx", "trx", "csx", "idx", "inx", "e", "-"] { + for t in ["de", "de-DE", "de-CH", "DE_at", "deu", "ger"] { + assert_eq!(ProofLang::from_tag(Some(t)), ProofLang::De, "{t}"); + } + assert!(!ProofLang::De.is_proofed()); + for t in ["fr-FR", "dex", "x-none", "ja-JP", "plx", "bgx", "trx", "csx", "idx", "inx", "e", "-"] { assert_eq!(ProofLang::from_tag(Some(t)), ProofLang::Other, "{t}"); } assert_eq!(ProofLang::from_tag(None), ProofLang::En); diff --git a/crates/proof/src/lib.rs b/crates/proof/src/lib.rs index 9d345ab6b2..7c140cd4e2 100644 --- a/crates/proof/src/lib.rs +++ b/crates/proof/src/lib.rs @@ -34,10 +34,12 @@ //! - **Indonesian** ([`ProofLang::Id`], module [`id`]): the installed Hunspell `id_ID` //! dictionary (not bundled), dictionary-driven suggestions and generic checks with Indonesian //! messages. Hyphenation uses the existing English fallback. +//! - **German** ([`ProofLang::De`]): hyphenation by `hyph-utf8`'s German patterns (reformed +//! spelling, MIT); no spelling or grammar marks yet. //! - **Other languages** ([`ProofLang::Other`]): no spelling or grammar marks, like Word without //! the language's proofing tools. -//! - **Hyphenation** ([`hyphen`]): dictionary, Liang patterns (English, Polish, Portuguese), -//! Bulgarian syllable rules, Russian word-division rules, heuristic. +//! - **Hyphenation** ([`hyphen`]): dictionary, Liang patterns (English, Polish, Portuguese, +//! Italian, German), Bulgarian syllable rules, Russian word-division rules, heuristic. //! //! [`check_text`] checks a paragraph whose runs are in different languages. #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] @@ -627,7 +629,7 @@ pub fn is_correct_in_with(word: &str, lang: ProofLang, opts: &Options) -> bool { ProofLang::Ru => accepted_by_options(word, opts) || ru::is_correct(word), ProofLang::Cs => accepted_by_options(word, opts) || cs::is_correct(word), ProofLang::Id => accepted_by_options(word, opts) || id::is_correct(word), - ProofLang::Bg | ProofLang::Other => true, + ProofLang::Bg | ProofLang::De | ProofLang::Other => true, } } @@ -643,7 +645,7 @@ pub fn suggest_in(word: &str, max: usize, lang: ProofLang) -> Vec { ProofLang::Ru => ru::suggest(word, max), ProofLang::Cs => cs::suggest(word, max), ProofLang::Id => id::suggest(word, max), - ProofLang::Bg | ProofLang::Other => Vec::new(), + ProofLang::Bg | ProofLang::De | ProofLang::Other => Vec::new(), } } @@ -661,7 +663,7 @@ pub fn check_grammar_in(text: &str, lang: ProofLang) -> Vec { ProofLang::Ru => ru::grammar::check(text), ProofLang::Cs => cs::grammar::check(text), ProofLang::Id => id::grammar::check(text), - ProofLang::Bg | ProofLang::Other => Vec::new(), + ProofLang::Bg | ProofLang::De | ProofLang::Other => Vec::new(), } } @@ -840,7 +842,7 @@ mod tests { assert_eq!(dictionary_needed(ProofLang::Pt, false), Some("pt_BR")); assert_eq!(dictionary_needed(ProofLang::Pl, true), None); assert_eq!(dictionary_needed(ProofLang::Pt, true), None); - for l in [ProofLang::En, ProofLang::Bg, ProofLang::Other] { + for l in [ProofLang::En, ProofLang::Bg, ProofLang::De, ProofLang::Other] { assert_eq!(dictionary_needed(l, false), None); } // Installed dictionaries and the built-in English list: no hint. @@ -1310,7 +1312,7 @@ mod tests { let _ = has_dictionary(Some(&s)); let _ = is_correct_for(&s, Some(&s)); let _ = hyphen::hyphen_points(&s, &hyphen::Limits::default()); - for lang in [ProofLang::En, ProofLang::Pl, ProofLang::Pt, ProofLang::It, ProofLang::Hr, ProofLang::Tr, ProofLang::Ru, ProofLang::Cs, ProofLang::Id, ProofLang::Other] { + for lang in [ProofLang::En, ProofLang::Pl, ProofLang::Pt, ProofLang::It, ProofLang::Hr, ProofLang::Tr, ProofLang::Ru, ProofLang::Cs, ProofLang::Id, ProofLang::De, ProofLang::Other] { let _ = check_spelling_in(&s, lang); let _ = check_grammar_in(&s, lang); let _ = is_correct_in(&s, lang); diff --git a/crates/ui-egui/src/options.rs b/crates/ui-egui/src/options.rs index 00fcb5a873..203ed31242 100644 --- a/crates/ui-egui/src/options.rs +++ b/crates/ui-egui/src/options.rs @@ -346,8 +346,8 @@ fn language_name(lang: wordcraft_engine::proof::ProofLang) -> &'static str { ProofLang::Ru => tl!("Russian"), ProofLang::Cs => tl!("Czech"), ProofLang::Id => tl!("Indonesian"), - // English uses its bundled word list; Bulgarian has no dictionary yet. - ProofLang::En | ProofLang::Bg | ProofLang::Other => "", + // English uses its bundled word list; Bulgarian and German have no dictionary yet. + ProofLang::En | ProofLang::Bg | ProofLang::De | ProofLang::Other => "", } } diff --git a/docs/gaps.md b/docs/gaps.md index 34366c865c..7734439d59 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -1,6 +1,6 @@ # Where WordCraft falls short of Word -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** trivial (#8 narrowed: Indonesian proofing from the installed Hunspell dictionary, #498; previously trivial: glossary/building blocks closed; previously trivial: Split Cells and Convert Text to Table follow the Word check, #2592; previously trivial: Language dialog keeps inherited proofing language and no-proof choices (#2717); previously trivial: #29: a copied equation also goes in the MathML clipboard formats, #1297; previously trivial: VML freeforms read and kept on save; previously trivial: 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: floating-table overlap ignores empty line-break lines; previously trivial: copies keep their formatting on the clipboard as HTML, #1523; previously trivial: sample-text formulas on Enter, #1067; previously trivial: #29: copied equations as MathML, OMML open as #1297, #816; previously trivial: the Font list no longer keeps every previewed font in memory, #848; 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 Table and Eraser landed, #303; previously minor: beta distance restated: ~15 points, ~150–230 h; previously: first version; every known shortfall from the 2026-10-10 re-measure, ranked) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** trivial (German hyphenation, gap 8); previously trivial (#8 narrowed: Indonesian proofing from the installed Hunspell dictionary, #498; previously trivial: glossary/building blocks closed; previously trivial: Split Cells and Convert Text to Table follow the Word check, #2592; previously trivial: Language dialog keeps inherited proofing language and no-proof choices (#2717); previously trivial: #29: a copied equation also goes in the MathML clipboard formats, #1297; previously trivial: VML freeforms read and kept on save; previously trivial: 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: floating-table overlap ignores empty line-break lines; previously trivial: copies keep their formatting on the clipboard as HTML, #1523; previously trivial: sample-text formulas on Enter, #1067; previously trivial: #29: copied equations as MathML, OMML open as #1297, #816; previously trivial: the Font list no longer keeps every previewed font in memory, #848; 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 Table and Eraser landed, #303; previously minor: beta distance restated: ~15 points, ~150–230 h; previously: first version; every known shortfall from the 2026-10-10 re-measure, ranked) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 > **Printing update:** 2026-10-12 · direct desktop printing through craft-print; details below. @@ -32,7 +32,7 @@ Document renaming: desktop renaming requires a filesystem that supports hard lin | 6 | **Print dialog depth**: desktop printer choice, copies, collation, duplex and grayscale submit through craft-print; File › Print retains preview, page ranges and odd/even (#641); driver tray/options and pages per sheet remain; web prints through the browser (#209) | B | `ui.print` → `apps/wordcraft/src/print.rs`; issue #15 | Everyone who prints | 5–8 | [hardware-parity.md](hardware-parity.md) | | 7 | **Dialog depth**: 58 modal dialogs vs Word's ~100 (Split Cells and Convert Text to Table match Word's observed limits and defaults) | B | `Dialog` enum in `crates/ui-egui/src/dialogs.rs` | Power users reach for dialogs the ribbon alone doesn't cover | 11–19 | [ui-parity.md](ui-parity.md) | | 9 | **Password-protected `.doc` files** (AES) can't be opened; password-protected `.docx` opens and saves (agile encryption, #55 closed by #281) | | `crates/docx/src/crypt.rs`; `crates/docbin/src/lib.rs` opens XOR/RC4/RC4 CryptoAPI `.doc` with a password (#1079, untested on Word-written files; AES `.doc` refused) | Corporate and legal users with old files | 3–5 | [file-format-parity.md](file-format-parity.md) | -| 8 | **Proofing covers English, Polish, Portuguese, Italian, Croatian, Turkish, Russian, Czech and Indonesian only**; no dictionaries, grammar or hyphenation for the other ~42 languages Word proofs (text in them is left unmarked) | | `crates/proof` `ProofLang::PROOFED`: English, 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) and Indonesian (the installed `id_ID` dictionary, #498, 2026-10-16), a language per run; Bulgarian has hyphenation only (#826); 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 and Indonesian | 20–40 | [localization-parity.md](localization-parity.md) | +| 8 | **Proofing covers English, Polish, Portuguese, Italian, Croatian, Turkish, Russian, Czech and Indonesian only**; no dictionaries or grammar for the other ~42 languages Word proofs (text in them is left unmarked), and no hyphenation for them except Bulgarian and German | | `crates/proof` `ProofLang::PROOFED`: English, 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) and Indonesian (the installed `id_ID` dictionary, #498, 2026-10-16), 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 and Indonesian | 20–40 | [localization-parity.md](localization-parity.md) | | 10 | **Objects**: text in a freely rotated text box (free rotation and flips landed, #332; 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); missing: Ink to Math, two-stroke arrows and arrows with a closed triangle head, 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) | @@ -90,6 +90,7 @@ The Language dialog reads effective character formatting, including document def | Date | Change | Summary | |---|---|---| +| 2026-10-18 | trivial | Gap 8 narrowed: German text hyphenates by the TeX German patterns (reformed spelling); German spelling and grammar remain | | 2026-10-17 | trivial | Glossary/building blocks closed (DOCX glossary read, kept and written); `w:altChunk` and sub-documents remain | | 2026-10-17 | trivial | Language dialog keeps inherited proofing language and no-proof choices (#2717); readiness estimates unchanged | | 2026-10-17 | trivial | Explicit DrawingML preset/custom outline dashes survive import/save and paint in raster/PDF, scaled to the effective stroke width even for zero-width paths. Synthetic controls; theme-inherited dashes and line caps remain unverified. | diff --git a/docs/localization-parity.md b/docs/localization-parity.md index 02682e2a1c..774fa4b7ba 100644 --- a/docs/localization-parity.md +++ b/docs/localization-parity.md @@ -1,6 +1,6 @@ # Localization parity with Microsoft Word -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-16 · **Change:** minor (Indonesian proofing: id-ID spelling from the installed Hunspell dictionary, dictionary-driven suggestions, generic checks with Indonesian messages, #498; previously trivial: print-area strings translated in every catalog; previously trivial: Italian: the interface audit checks the Italian catalog, `Privacy Settings` wording; previously trivial: Italian grammar: `po'`, `qual è`, `È`, `un`/`un'` rules; more elisions and truncations in spelling; previously trivial: Czech interface catalog completed, 2,447 entries; previously trivial: interface audit runs in CI again: size labels translated, 20 strings in every catalog, Read Aloud error race fixed, #1128; previously trivial: Review › Language reaches every language again — Set Proofing Language… and Set As Default, #860; previously minor: Italian proofing: it-IT spelling from the installed Hunspell dictionary, accent-first suggestions, conservative grammar with Italian messages; previously minor: Portuguese proofing; previously trivial: Turkish interface catalog completed, 2,325 entries, #774; previously minor: Hungarian interface added; previously trivial: sorting and table numbers follow the text's language, #836, #837; previously minor: Polish interface; Polish proofing — spelling, grammar, hyphenation — and a proofing language per run; previously minor: Turkish interface added; previously major: first version; catalogs measured from `crates/ui-egui/src/i18n`, Word's languages from its bundle listing) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** trivial (German hyphenation by the TeX German patterns, reformed spelling, for every region); previously minor (Indonesian proofing: id-ID spelling from the installed Hunspell dictionary, dictionary-driven suggestions, generic checks with Indonesian messages, #498; previously trivial: print-area strings translated in every catalog; previously trivial: Italian: the interface audit checks the Italian catalog, `Privacy Settings` wording; previously trivial: Italian grammar: `po'`, `qual è`, `È`, `un`/`un'` rules; more elisions and truncations in spelling; previously trivial: Czech interface catalog completed, 2,447 entries; previously trivial: interface audit runs in CI again: size labels translated, 20 strings in every catalog, Read Aloud error race fixed, #1128; previously trivial: Review › Language reaches every language again — Set Proofing Language… and Set As Default, #860; previously minor: Italian proofing: it-IT spelling from the installed Hunspell dictionary, accent-first suggestions, conservative grammar with Italian messages; previously minor: Portuguese proofing; previously trivial: Turkish interface catalog completed, 2,325 entries, #774; previously minor: Hungarian interface added; previously trivial: sorting and table numbers follow the text's language, #836, #837; previously minor: Polish interface; Polish proofing — spelling, grammar, hyphenation — and a proofing language per run; previously minor: Turkish interface added; previously major: first version; catalogs measured from `crates/ui-egui/src/i18n`, Word's languages from its bundle listing) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 Interface languages, script support and proofing per language. How translation works (catalog format, adding a language, clean-room rule): `crates/ui-egui/src/i18n/mod.rs`. @@ -110,6 +110,7 @@ other than envelopes (Letter, Legal, A4, B5 (JIS)) stay as they are (#1128). | Czech (`cs`, `cs-CZ`, `ces`, `cze`) | The system Hunspell `cs_CZ` dictionary, read at runtime (Debian/Ubuntu: `hunspell-cs`; the same system, LibreOffice and `WORDCRAFT_DICTIONARIES` search paths as Polish). No dictionary data is bundled; without an installed dictionary spelling stays off | suggestions from the loaded dictionary's MAP/REP/TRY/KEY tables, missing spaces and a stem scan; short-word splits rank after deleting an extra letter; NOSUGGEST stems and their derived forms are accepted when typed but excluded from suggestions | Czech messages (including Czech quotation marks) for repeated words, space before punctuation and double spaces only; digit-space-colon-space-digit ratios keep their spaces; no Czech-specific grammar rules | existing English fallback, as for `Other`; no Czech patterns bundled (the TeX/LibreOffice Czech patterns are GPL-2) | | Italian (`it`, any region) | The Hunspell `it_IT` dictionary installed on the system (same places as Polish and Portuguese; not bundled — the upstream file is GPL-licensed), read at runtime by the same affix engine; elided forms check the word after the apostrophe (`l'amico`, `un'idea`, `c'è`, `dov'è`, `senz'altro`), truncations keep their apostrophe (`po'`, `mo'`), hyphenated compounds part by part | missing accents first (`caffe` → `caffè`, `perche` → `perché`), the dictionary's confusion table, missing space (`perme` → `per me`), one typo, then an accent-folding stem scan | Italian messages: repeated word (emphatic doubles like `piano piano` are allowed), space before punctuation, double space, sentence capital (knows `ecc.`, `dott.`, `prof.`, initials, ordinals), doubled punctuation, `un pò` → `un po'`, `qual'è` → `qual è`, `E'` → `È`, `un'altro` → `un altro` and `un altra` → `un'altra` (gender from the ending, masculine `-ista`/`-ma`/`-cida`… left alone); these work without a dictionary; no comma rule | TeX Italian patterns (Beccari; MIT option), no exception words | | Turkish (`tr`, any region) | The Hunspell `tr_TR` dictionary installed on the system (same places as Polish; not bundled), read at runtime; proper nouns capitalized, apostrophe suffixes (`Ankara'da`, `WordCraft'ta`) and hyphenated compounds | missing diacritics / ASCII restoration first (`turkce` → `Türkçe`, `isik` → `ışık`), common confusion table (`yalnış` → `yanlış`, `herşey` → `her şey`), detached suffixes (`bende` → `ben de`, `geldinmi` → `geldi mi`), one typo | Turkish messages: repeated word (idiomatic reduplications allowed: `yavaş yavaş`), space before punctuation, double space, sentence capital (knows `vb.`, `vs.`, `prof.`, `dr.`), no comma before/after conjunctions (`ve`, `veya`, `de`), doubled punctuation | Turkish syllable rules (TDK rules); no external pattern file or word list needed | +| German (`de`, any region) | — (no marks yet, #1564) | — | — | TeX German patterns for the reformed spelling (Trennmustermannschaft; MIT), no exception words; Swiss text uses them too | | Indonesian (`id`, `id-ID`, `ind`, legacy `in`) | The system Hunspell `id_ID` dictionary, read at runtime (Debian/Ubuntu: `hunspell-id`; LibreOffice's `dict-id`; the same system, LibreOffice, Dictionaries folder and `WORDCRAFT_DICTIONARIES` search paths as Polish). No dictionary data is bundled; without an installed dictionary spelling stays off. Hyphenated reduplication is accepted whole (`kupu-kupu`) or by parts (`anak-anak`); hyphenated clitics after capitals and abbreviations (`KTP-nya`, `se-Indonesia`) are accepted | the same dictionary-driven ranking as Czech: MAP/REP/TRY/KEY tables of the loaded dictionary, missing spaces and a stem scan; nothing Indonesian hard-coded | Indonesian messages for repeated words (offering the hyphenated reduplication `anak-anak` first, then the single word), space before punctuation and double spaces only; no Indonesian-specific grammar rules | existing English fallback, as for `Other`; no Indonesian patterns bundled | | Any other | — (no marks) | — | — | the English patterns, as before | @@ -193,6 +194,7 @@ WordCraft keeps 2,54 cm). | Date | Change | Summary | |---|---|---| +| 2026-10-18 | trivial | German hyphenation: runs in `de` (any region) use the TeX German patterns for the reformed spelling (MIT) instead of the English ones; still no German spelling or grammar | | 2026-10-16 | minor | Indonesian proofing (#498): id-ID spelling from the installed Hunspell `id_ID` dictionary (not bundled), Czech's dictionary-driven suggestions, repeated-word (hyphenated reduplication offered) and spacing checks with Indonesian messages; Review › Language, the status bar, Set Proofing Language and File › Options › Proofing list Indonesian; estimates unchanged | | 2026-10-12 | minor | Czech proofing review: short-word splits rank after deletion, NOSUGGEST excludes suggestions while accepting typed forms, affix comments apply only to headers, ratios keep spaces and repeated-word messages use Czech quotes; synthetic engine end-to-end and latency checks | | 2026-10-12 | minor | Czech spelling from the installed Hunspell dictionary, runtime-table suggestions and generic checks; Czech interface writes cs-CZ documents; no Czech hyphenation patterns bundled; estimates unchanged | From de3db3f02cf6d8c235f2dcc9dbfc955fb37a12c9 Mon Sep 17 00:00:00 2001 From: Naxvog Date: Wed, 14 Oct 2026 21:26:11 +0200 Subject: [PATCH 073/336] Hyphenation: German patterns in NOTICE; pattern files skip \message Review follow-ups: the German patterns' MIT notice goes into NOTICE with the other pattern sets; the pattern reader skips other commands' arguments, so hyph-de-1996.tex's \message{} line no longer adds junk patterns and exception words; a load test for the German set; the status bar names a bare "de" tag; the docs list the languages with their own hyphenation. Co-Authored-By: Claude Opus 5.5 --- NOTICE | 22 ++++++++++++++++++++++ ROADMAP.md | 2 +- crates/proof/src/hyphen.rs | 8 ++++++++ crates/proof/src/patterns.rs | 16 ++++++++++++---- crates/ui-egui/src/ribbon.rs | 1 + docs/layout-parity.md | 5 +++-- docs/localization-parity.md | 2 +- 7 files changed, 48 insertions(+), 8 deletions(-) diff --git a/NOTICE b/NOTICE index 3cd8077ebc..8e7c3d5117 100644 --- a/NOTICE +++ b/NOTICE @@ -52,6 +52,28 @@ list (author, source, license) is in ATTRIBUTION.md. ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +- German hyphenation patterns (assets/hyphenation/de.pat) — hyph-de-1996.tex (reformed spelling) + from the hyph-utf8 package, bundled unmodified and used under the MIT License; the copyright + and permission notice is in the file: + + Copyright (c) 2013-2024 Stephan Hennig, Werner Lemberg, Günter Milde, Sander van Geloven, + Georg Pfeiffer, Gisbert W. Selke, Tobias Wendorf, Keno Wehr + + Permission is hereby granted, free of charge, to any person obtaining a copy of this + software and associated documentation files (the "Software"), to deal in the Software + without restriction, including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or + substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. - Italian hyphenation patterns (assets/hyphenation/it.pat) — hyph-it.tex from the hyph-utf8 package, Copyright (C) 2008-2011 Claudio Beccari, bundled unmodified and used under the MIT License (offered under LPPL 1.3+ or MIT); the copyright and permission notice is in the file: diff --git a/ROADMAP.md b/ROADMAP.md index 6591c241ab..aa6ff7eded 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -117,7 +117,7 @@ Also shipped: Traditional Chinese (`zh-hant`), Ukrainian (`uk`), Serbian (`sr`, system-locale selection and a saved language choice (Italian also has 51 strings none of the others had yet); Estonian, Turkish and Italian proofing are not included. Hungarian (`hu`) has a complete 2,419-entry catalog, also without proofing. Vietnamese (`vi`, #1434) has a complete 2,448-entry catalog, also without proofing. -Proofing: English, Polish, Portuguese and Russian (spelling, grammar, hyphenation), plus Czech and Indonesian (system Hunspell spelling, generic checks), chosen per run from its language; German hyphenation only. Word ships +Proofing: English, Polish, Portuguese, Italian, Croatian, Turkish and Russian (spelling, grammar; hyphenation except Croatian), plus Czech and Indonesian (system Hunspell spelling, generic checks), chosen per run from its language; Bulgarian and German hyphenation only. Word ships 30 interface languages and proofing for ~50. Detail: [docs/localization-parity.md](docs/localization-parity.md). ## Upcoming diff --git a/crates/proof/src/hyphen.rs b/crates/proof/src/hyphen.rs index 2b262a92fd..1db67eb02e 100644 --- a/crates/proof/src/hyphen.rs +++ b/crates/proof/src/hyphen.rs @@ -858,6 +858,14 @@ mod tests { } } + #[test] + fn german_patterns_load() { + let p = Patterns::de(); + assert!(p.len() >= 36_709, "{}", p.len()); + // The file has no exception words, and its `\message{…}` line adds none. + assert_eq!(p.exception_count(), 0); + } + #[test] fn german_words_follow_the_german_patterns() { let de = |w: &str| hyphenate_word_in(w, &Limits::default(), ProofLang::De); diff --git a/crates/proof/src/patterns.rs b/crates/proof/src/patterns.rs index 50dd74444f..08d54a9470 100644 --- a/crates/proof/src/patterns.rs +++ b/crates/proof/src/patterns.rs @@ -116,13 +116,16 @@ pub struct Patterns { enum Block { Patterns, Exceptions, + /// Another command's argument (`\message{…}`), ignored. + Skip, } impl Patterns { /// Parse patterns: whitespace-separated, `%` comments to the end of the line (`#` too at the /// start of a line). A whole TeX pattern file works as is: `\patterns{…}` holds patterns and - /// `\hyphenation{…}` exception words written with their hyphens (`ni-gdy`); outside any - /// block, a token with a hyphen is an exception and anything else a pattern. + /// `\hyphenation{…}` exception words written with their hyphens (`ni-gdy`), and other + /// commands' arguments (`\message{…}`) are skipped; outside any block, a token with a hyphen + /// is an exception and anything else a pattern. pub fn parse(text: &str) -> Patterns { let mut p = Patterns::default(); let mut block = Block::Patterns; @@ -136,12 +139,17 @@ impl Patterns { let mut tok = tok; if let Some(cmd) = tok.strip_prefix('\\') { let (name, rest) = cmd.split_once('{').unwrap_or((cmd, "")); - block = if name == "hyphenation" { Block::Exceptions } else { Block::Patterns }; + block = match name { + "hyphenation" => Block::Exceptions, + "patterns" => Block::Patterns, + _ if cmd.contains('{') => Block::Skip, + _ => Block::Patterns, + }; tok = rest; } let closes = tok.ends_with('}'); let tok = tok.trim_end_matches('}'); - if !tok.is_empty() { + if !tok.is_empty() && block != Block::Skip { if block == Block::Exceptions || tok.contains('-') { p.insert_exception(tok); } else { diff --git a/crates/ui-egui/src/ribbon.rs b/crates/ui-egui/src/ribbon.rs index 5c48d81e91..7bcd5d7850 100644 --- a/crates/ui-egui/src/ribbon.rs +++ b/crates/ui-egui/src/ribbon.rs @@ -552,6 +552,7 @@ pub(crate) fn language_name(tag: Option<&str>) -> String { wordcraft_engine::proof::ProofLang::Ru => "Russian".into(), wordcraft_engine::proof::ProofLang::Cs => "Czech".into(), wordcraft_engine::proof::ProofLang::Id => "Indonesian".into(), + wordcraft_engine::proof::ProofLang::De => "German (Germany)".into(), wordcraft_engine::proof::ProofLang::En if tag.eq_ignore_ascii_case("en") => "English (United States)".into(), _ => tag.to_string(), }, diff --git a/docs/layout-parity.md b/docs/layout-parity.md index 946816a4a8..31e4cd95eb 100644 --- a/docs/layout-parity.md +++ b/docs/layout-parity.md @@ -1,6 +1,6 @@ # Layout and pagination parity with Microsoft Word -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-17 · **Change:** 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 proportional fallbacks; previously trivial: DrawingML outline dashes, including zero-width paths; previously trivial: consistent cell widths override a stale table grid; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: page-break-only list paragraphs hide their labels; previously trivial: floating-table overlap ignores empty line-break lines; previously trivial: paragraph spacing adds up when the document turns HTML auto spacing off; previously trivial: an inherited footer sizes its percentage tables by its own section; previously trivial: an empty section mark stays on a full page; previously trivial: a fitting row with less room than its minimum height moves on whole; oversized rows still split; previously trivial: text past an exact-height row is cut off; previously trivial: a list number stays with text after a leading page break; previously trivial: a bookmark after a page break takes no line; previously trivial: a page break followed by a page break before or a next-page section starts one page; previously trivial: All Caps and Small Caps draw ß as SS, #1581; previously trivial: justified vertical page alignment, #1320; previously trivial: page numbers restart at 0; previously trivial: a list label moves with its first line when the paragraph is centred or end-aligned (#1159); previously trivial: PAGE fields in a paragraph split across pages show the page they are on, #1352; previously trivial: a dragged floating object anchors in the column it lands in, #884; previously trivial: a paragraph ending on a page with footnotes keeps its space after above the separator, #1116; previously trivial: symbol-font text drawn as Unicode symbols, #776; previously trivial: large-table scrolling measured and fixed, #811; previously trivial: text boxes use their own margins; Resize shape to fit text never cuts text, #743; previously trivial: complex-script shaping and Myanmar line breaking, #604; previously trivial: body page totals, table-cell footnotes and bookmark link pages fixed, #450 #451 #455; previously major: first version; line breaking, pagination and object placement checked against Word's behaviour from the layout source on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** trivial (hyphenation row: languages with their own rules listed, German patterns added); 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 proportional fallbacks; previously trivial: DrawingML outline dashes, including zero-width paths; previously trivial: consistent cell widths override a stale table grid; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: page-break-only list paragraphs hide their labels; previously trivial: floating-table overlap ignores empty line-break lines; previously trivial: paragraph spacing adds up when the document turns HTML auto spacing off; previously trivial: an inherited footer sizes its percentage tables by its own section; previously trivial: an empty section mark stays on a full page; previously trivial: a fitting row with less room than its minimum height moves on whole; oversized rows still split; previously trivial: text past an exact-height row is cut off; previously trivial: a list number stays with text after a leading page break; previously trivial: a bookmark after a page break takes no line; previously trivial: a page break followed by a page break before or a next-page section starts one page; previously trivial: All Caps and Small Caps draw ß as SS, #1581; previously trivial: justified vertical page alignment, #1320; previously trivial: page numbers restart at 0; previously trivial: a list label moves with its first line when the paragraph is centred or end-aligned (#1159); previously trivial: PAGE fields in a paragraph split across pages show the page they are on, #1352; previously trivial: a dragged floating object anchors in the column it lands in, #884; previously trivial: a paragraph ending on a page with footnotes keeps its space after above the separator, #1116; previously trivial: symbol-font text drawn as Unicode symbols, #776; previously trivial: large-table scrolling measured and fixed, #811; previously trivial: text boxes use their own margins; Resize shape to fit text never cuts text, #743; previously trivial: complex-script shaping and Myanmar line breaking, #604; previously trivial: body page totals, table-cell footnotes and bookmark link pages fixed, #450 #451 #455; previously major: first version; line breaking, pagination and object placement checked against Word's behaviour from the layout source on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 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, @@ -35,7 +35,7 @@ font can't match Word's metrics. | Paragraph boundaries inside field instructions | ✅ | Synthetic DOCX observed in Word: instruction-only paragraphs add no height; the empty result keeps a line. `docx::fixtures::field_code_spans_paragraphs`, `layout::tests::hidden_marks_take_no_space`. Other field-code display modes remain unverified | — | | Widow/orphan control, keep with next, keep lines together, page break before | ✅ | `lib.rs` keep_next/keep_lines; a line holding only a page break stays at the bottom of a full page instead of leaving a blank page (#534); a paragraph ending with a page break keeps its space after on the old page, so the next page starts at the top (compatibility modes 12 to 15, `layout::tests::space_before_at_the_top_of_a_page_like_word`); a three-line paragraph with room for two moves whole (neither a widow nor an orphan is allowed), as in Word | — | | Tabs (left, center, right, decimal, bar), leaders | 🟡 | Bar tabs untested | 1–2 | -| Hyphenation (automatic, zone from the document, words in capitals, soft hyphens, consecutive limit, Manual Hyphenation, #407) | 🟡 | English patterns only; other languages hyphenate wrongly or not at all | in localization | +| Hyphenation (automatic, zone from the document, words in capitals, soft hyphens, consecutive limit, Manual Hyphenation, #407) | 🟡 | Per the text's language: English, Polish, Portuguese, Italian and German patterns, Bulgarian and Turkish syllable rules, Russian word-division rules; other languages use the English patterns | in localization | | Drop caps | ✅ | 2026-10-06 | — | | All Caps and Small Caps: the full capital of each letter, so ß draws as SS (ff as FF); the capitals drawn for one character are one cluster, so the caret, selection, hit testing and line breaking treat ß as one character | ✅ | `layout::tests::caps_draw_sharp_s_as_two_capitals_in_one_cluster` (#1581). The PDF text layer still reads the document's lowercase letters until #1473 | — | | Columns, column breaks, separators, balancing | ✅ | Columns balance on a section's last page when the next section starts continuous (also at the document end, which Word only balances when the document ends with a continuous break); not after next-page/even/odd breaks or on a page with a column break; keep-together, keep-with-next, widow/orphan control and unsplittable rows can leave them slightly uneven (#350) | — | @@ -179,6 +179,7 @@ Theme-inherited dash styles, line caps, ink and open freeform strokes are not co | Date | Change | Summary | |---|---|---| +| 2026-10-18 | trivial | Hyphenation row: lists the languages with their own patterns or rules (English, Polish, Portuguese, Italian, German, Bulgarian, Turkish, Russian) | | 2026-10-17 | trivial | Explicit DrawingML preset/custom outline dashes survive import/save and paint in raster/PDF, scaled to the effective stroke width even for zero-width paths. Synthetic controls; theme-inherited dashes and line caps remain unverified. | | 2026-10-17 | trivial | Arial Unicode MS uses Arial and its proportional substitutes; glyph coverage and exact Word line height remain font-dependent. | | 2026-10-16 | trivial | Exact OpenType full names (name ID4) select installed static faces after family names; variable-instance aliases remain unsupported | diff --git a/docs/localization-parity.md b/docs/localization-parity.md index 774fa4b7ba..a7d1f9196c 100644 --- a/docs/localization-parity.md +++ b/docs/localization-parity.md @@ -110,7 +110,7 @@ other than envelopes (Letter, Legal, A4, B5 (JIS)) stay as they are (#1128). | Czech (`cs`, `cs-CZ`, `ces`, `cze`) | The system Hunspell `cs_CZ` dictionary, read at runtime (Debian/Ubuntu: `hunspell-cs`; the same system, LibreOffice and `WORDCRAFT_DICTIONARIES` search paths as Polish). No dictionary data is bundled; without an installed dictionary spelling stays off | suggestions from the loaded dictionary's MAP/REP/TRY/KEY tables, missing spaces and a stem scan; short-word splits rank after deleting an extra letter; NOSUGGEST stems and their derived forms are accepted when typed but excluded from suggestions | Czech messages (including Czech quotation marks) for repeated words, space before punctuation and double spaces only; digit-space-colon-space-digit ratios keep their spaces; no Czech-specific grammar rules | existing English fallback, as for `Other`; no Czech patterns bundled (the TeX/LibreOffice Czech patterns are GPL-2) | | Italian (`it`, any region) | The Hunspell `it_IT` dictionary installed on the system (same places as Polish and Portuguese; not bundled — the upstream file is GPL-licensed), read at runtime by the same affix engine; elided forms check the word after the apostrophe (`l'amico`, `un'idea`, `c'è`, `dov'è`, `senz'altro`), truncations keep their apostrophe (`po'`, `mo'`), hyphenated compounds part by part | missing accents first (`caffe` → `caffè`, `perche` → `perché`), the dictionary's confusion table, missing space (`perme` → `per me`), one typo, then an accent-folding stem scan | Italian messages: repeated word (emphatic doubles like `piano piano` are allowed), space before punctuation, double space, sentence capital (knows `ecc.`, `dott.`, `prof.`, initials, ordinals), doubled punctuation, `un pò` → `un po'`, `qual'è` → `qual è`, `E'` → `È`, `un'altro` → `un altro` and `un altra` → `un'altra` (gender from the ending, masculine `-ista`/`-ma`/`-cida`… left alone); these work without a dictionary; no comma rule | TeX Italian patterns (Beccari; MIT option), no exception words | | Turkish (`tr`, any region) | The Hunspell `tr_TR` dictionary installed on the system (same places as Polish; not bundled), read at runtime; proper nouns capitalized, apostrophe suffixes (`Ankara'da`, `WordCraft'ta`) and hyphenated compounds | missing diacritics / ASCII restoration first (`turkce` → `Türkçe`, `isik` → `ışık`), common confusion table (`yalnış` → `yanlış`, `herşey` → `her şey`), detached suffixes (`bende` → `ben de`, `geldinmi` → `geldi mi`), one typo | Turkish messages: repeated word (idiomatic reduplications allowed: `yavaş yavaş`), space before punctuation, double space, sentence capital (knows `vb.`, `vs.`, `prof.`, `dr.`), no comma before/after conjunctions (`ve`, `veya`, `de`), doubled punctuation | Turkish syllable rules (TDK rules); no external pattern file or word list needed | -| German (`de`, any region) | — (no marks yet, #1564) | — | — | TeX German patterns for the reformed spelling (Trennmustermannschaft; MIT), no exception words; Swiss text uses them too | +| German (`de`, any region) | — (no marks yet, #1564) | — | — | TeX German patterns for the reformed spelling (Trennmustermannschaft; MIT), no exception words; Swiss text uses them too. The patterns leave ambiguous compound joints unbroken on purpose (`Staubecken`, `Urinstinkt`), so German text can hyphenate a little less than in Word | | Indonesian (`id`, `id-ID`, `ind`, legacy `in`) | The system Hunspell `id_ID` dictionary, read at runtime (Debian/Ubuntu: `hunspell-id`; LibreOffice's `dict-id`; the same system, LibreOffice, Dictionaries folder and `WORDCRAFT_DICTIONARIES` search paths as Polish). No dictionary data is bundled; without an installed dictionary spelling stays off. Hyphenated reduplication is accepted whole (`kupu-kupu`) or by parts (`anak-anak`); hyphenated clitics after capitals and abbreviations (`KTP-nya`, `se-Indonesia`) are accepted | the same dictionary-driven ranking as Czech: MAP/REP/TRY/KEY tables of the loaded dictionary, missing spaces and a stem scan; nothing Indonesian hard-coded | Indonesian messages for repeated words (offering the hyphenated reduplication `anak-anak` first, then the single word), space before punctuation and double spaces only; no Indonesian-specific grammar rules | existing English fallback, as for `Other`; no Indonesian patterns bundled | | Any other | — (no marks) | — | — | the English patterns, as before | From ffb3a2189f1c73c2f64503805c6d6cdeb348e3bf Mon Sep 17 00:00:00 2001 From: Naxvog Date: Wed, 14 Oct 2026 21:27:17 +0200 Subject: [PATCH 074/336] Hyphenation: exact German pattern count, a parser test for skipped commands Co-Authored-By: Claude Opus 5.5 --- ROADMAP.md | 2 +- crates/proof/src/hyphen.rs | 2 +- crates/proof/src/patterns.rs | 7 +++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index aa6ff7eded..7547737b17 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -114,7 +114,7 @@ Weights and evidence per area: [docs/target-app-parity.md](docs/target-app-parit | Vietnamese | vi | partial (#1434) | ~95% | Also shipped: Traditional Chinese (`zh-hant`), Ukrainian (`uk`), Serbian (`sr`, `sr-latn`, #250), Russian (`ru`) and Polish (`pl`), all partial (~95%). Bulgarian (`bg`) covers everything the interface audit finds (2,419 entries); Swedish (`sv`) covers that too, plus 44 newer strings no other catalog has yet (2,504 entries). Estonian (`et`), Italian (`it`, #594) and Czech (`cs`) now have complete interface catalogs, and Turkish (`tr`) a complete 2,325-entry one (every key of the Russian catalog), all with -system-locale selection and a saved language choice (Italian also has 51 strings none of the others had yet); Estonian, Turkish and Italian proofing are not included. Hungarian (`hu`) has a +system-locale selection and a saved language choice (Italian also has 51 strings none of the others had yet); Estonian proofing is not included. Hungarian (`hu`) has a complete 2,419-entry catalog, also without proofing. Vietnamese (`vi`, #1434) has a complete 2,448-entry catalog, also without proofing. Proofing: English, Polish, Portuguese, Italian, Croatian, Turkish and Russian (spelling, grammar; hyphenation except Croatian), plus Czech and Indonesian (system Hunspell spelling, generic checks), chosen per run from its language; Bulgarian and German hyphenation only. Word ships diff --git a/crates/proof/src/hyphen.rs b/crates/proof/src/hyphen.rs index 1db67eb02e..00297331e6 100644 --- a/crates/proof/src/hyphen.rs +++ b/crates/proof/src/hyphen.rs @@ -861,7 +861,7 @@ mod tests { #[test] fn german_patterns_load() { let p = Patterns::de(); - assert!(p.len() >= 36_709, "{}", p.len()); + assert_eq!(p.len(), 36_709); // The file has no exception words, and its `\message{…}` line adds none. assert_eq!(p.exception_count(), 0); } diff --git a/crates/proof/src/patterns.rs b/crates/proof/src/patterns.rs index 08d54a9470..a91235e48f 100644 --- a/crates/proof/src/patterns.rs +++ b/crates/proof/src/patterns.rs @@ -142,6 +142,7 @@ impl Patterns { block = match name { "hyphenation" => Block::Exceptions, "patterns" => Block::Patterns, + // shortcut: the argument ends at the first `}`, so nested braces would leak; no bundled file has them. _ if cmd.contains('{') => Block::Skip, _ => Block::Patterns, }; @@ -345,6 +346,12 @@ mod tests { assert!(p.to_tex().contains(&".ab4cdefghijklm5n".to_string())); } + #[test] + fn other_commands_arguments_are_skipped() { + let p = Patterns::parse("\\message{Some Patterns `x-y' 2024-02-28}\n\\patterns{%\na1b\n}\n\\endinput\n"); + assert_eq!((p.len(), p.exception_count()), (1, 0)); + } + #[test] fn apply_tolerates_short_value_buffers() { let p = Patterns::parse("a1b"); From d342135bc763fce3a50785510e0e7712194b7303 Mon Sep 17 00:00:00 2001 From: Nghiathan13 <201675788+Nghiathan13@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:21:25 +0700 Subject: [PATCH 075/336] #2738: parity checklist and catalog count after merging main Co-Authored-By: Claude Opus 5.5 --- ROADMAP.md | 2 +- docs/parity-checklist.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index d9951fa53e..2f115a2fdb 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -16,7 +16,7 @@ agent control. This page is the summary; the assessment is | Number | Value | Kind | | --- | --- | --- | -| Ribbon/menu catalog coverage | **490 / 505 (97.0%)** | measured (`cargo xtask parity` → [`docs/parity-checklist.md`](docs/parity-checklist.md)) | +| Ribbon/menu catalog coverage | **491 / 506 (97.0%)** | measured (`cargo xtask parity` → [`docs/parity-checklist.md`](docs/parity-checklist.md)) | | **Feature breadth** (weighted, incl. dialog options, styles, languages beyond the ribbon) | **~80%** | estimated | | **Ready for real work** (full target) | **~60%** (55–63%) | estimated, additive weighted sum over the dimensions | | **Mainstream practitioner** | **~55%** | estimated: weekly-work depth × discounts for interaction (×0.93), stability (×0.90), file exchange (×0.90) | diff --git a/docs/parity-checklist.md b/docs/parity-checklist.md index 8a9375bcc3..02f07af5ce 100644 --- a/docs/parity-checklist.md +++ b/docs/parity-checklist.md @@ -2,14 +2,14 @@ Generated by `cargo xtask parity` (`wordcraft-cli parity --markdown`): the word-processor feature catalog (`crates/engine/src/catalog.rs`) compared with the live command registry. -**490 of 505 catalog features have commands (97.0%).** +**491 of 506 catalog features have commands (97.0%).** | Tab | Live | Total | |---|---|---| | File | 22 | 22 | | Quick Access | 5 | 5 | | Title Bar | 1 | 1 | -| Home | 103 | 104 | +| Home | 104 | 105 | | Insert | 47 | 53 | | Draw | 10 | 11 | | Design | 10 | 10 | From 5450419a39f3cb6538f56dd103bd2d0650d3977f Mon Sep 17 00:00:00 2001 From: ofdistress2 <240779139+ofdistress2@users.noreply.github.com> Date: Sun, 18 Oct 2026 12:29:53 +0300 Subject: [PATCH 076/336] file.recover: keep explicitly requested documents visible --- ROADMAP.md | 4 +- crates/ui-egui/src/backstage.rs | 81 ++++++++++++++++++++++----------- crates/ui-egui/src/lib.rs | 3 ++ docs/gaps.md | 7 ++- docs/recovery.md | 6 ++- docs/ui-parity.md | 7 ++- 6 files changed, 75 insertions(+), 33 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 579117238a..02ef99953b 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **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 (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 (explicit documents keep the editor when recovery discovery finishes; readable recovery controls); previous change: 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-17 · **Change:** 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. @@ -137,6 +137,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm - 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 | Explicit New/Open keeps the requested document visible when startup recovery discovery finishes; older copies stay available. Recover uses the regular theme. See docs/recovery.md. | | 2026-10-16 | The Spike, as in Word: Ctrl+F3 (⌘F3 on macOS) cuts the selection and adds it to the end of the Spike, each piece followed by a paragraph mark; Ctrl+Shift+F3 (⌘⇧F3) inserts everything collected, in order and with its formatting, and empties the Spike. The clipboard is left alone; each is one undo step. The Spike is the AutoText entry "Spike", so Insert › Quick Parts inserts it without emptying it. It lasts for the session (Word keeps it in Normal.dotm) | | 2026-10-16 | DOCX two lines in one and horizontal in vertical (`w:eastAsianLayout`: id, combine, brackets, vert, vertCompress) are read and kept on save, in runs and character styles, instead of being dropped. Layout doesn't apply them yet. | | 2026-10-16 | Read Aloud speaks Russian, Ukrainian, Greek, Arabic, Hebrew, CJK and Thai text with a voice for its language instead of spelling out letters (#1843): the sentence's language comes from its run's tag (`w:bidi` for right-to-left text) or, when that disagrees with the script, from its letters, even when English letters outnumber them; Windows reads the text as UTF-8 and picks an installed voice of that language, macOS `say -v` and Linux `espeak -v` get the same language. The user's own voice stays whenever it already speaks the language (English documents read exactly as before), and a voice espeak lacks falls back to the default voice instead of silence. Not run on Windows or macOS here. | @@ -447,6 +448,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | Date | Change | Summary | |---|---|---| +| 2026-10-18 | trivial | Explicit New/Open is not interrupted by startup recovery discovery; older copies stay available and Recover follows the interface theme. | | 2026-10-16 | trivial | Progress log: the Spike | | 2026-10-16 | trivial | Progress log: `w:eastAsianLayout` kept on save | | 2026-10-15 | trivial | Progress log: undrawable private-use list bullets show the level's default bullet (#1683) | diff --git a/crates/ui-egui/src/backstage.rs b/crates/ui-egui/src/backstage.rs index b89665ad3b..e716d6acec 100644 --- a/crates/ui-egui/src/backstage.rs +++ b/crates/ui-egui/src/backstage.rs @@ -22,7 +22,6 @@ const PAGES: [(&str, &str); 11] = [ pub fn show(app: &mut WordApp, ui: &mut Ui) { let t = Tokens::get(ui.ctx()); - let recovery = app.ui.backstage_page == "recover"; // No document open (#1129): only the pages that open or create one, and Options. let has_document = app.has_document(); if !has_document && !works_without_document(&app.ui.backstage_page) { @@ -33,12 +32,7 @@ pub fn show(app: &mut WordApp, ui: &mut Ui) { .exact_size(200.0) // Fixed width, as in Word: without this egui shows a resize handle that does nothing (#502). .resizable(false) - .frame(egui::Frame::NONE.fill(if recovery { egui::Color32::BLACK } else { APP_COLOR }).inner_margin(egui::Margin { - left: 0, - right: 0, - top: if mac { 0 } else { 12 }, - bottom: 12, - })) + .frame(egui::Frame::NONE.fill(APP_COLOR).inner_margin(egui::Margin { left: 0, right: 0, top: if mac { 0 } else { 12 }, bottom: 12 })) .show(ui, |ui| { let back = if mac { // macOS (#222): the Backstage covers the title bar, so its top band is the title @@ -135,31 +129,28 @@ pub fn show(app: &mut WordApp, ui: &mut Ui) { } }); }); - egui::CentralPanel::default() - .frame(egui::Frame::NONE.fill(if recovery { egui::Color32::BLACK } else { t.ribbon }).inner_margin(egui::Margin::symmetric(40, 30))) - .show(ui, |ui| { - // The Print page fills the pane and scrolls its settings and its preview itself (#1156). - if app.ui.backstage_page == "print" { - export_page(app, ui); - return; - } - egui::ScrollArea::vertical().show(ui, |ui| match app.ui.backstage_page.as_str() { - "home" => home_page(app, ui), - "new" => new_page(app, ui), - "open" => open_page(app, ui), - "saveAs" => save_as_page(app, ui), - "info" => info_page(app, ui), - "recover" => recovery_page(app, ui), - "print" | "export" => export_page(app, ui), - "options" => crate::options::page(app, ui), - _ => home_page(app, ui), - }); + egui::CentralPanel::default().frame(egui::Frame::NONE.fill(t.ribbon).inner_margin(egui::Margin::symmetric(40, 30))).show(ui, |ui| { + // The Print page fills the pane and scrolls its settings and its preview itself (#1156). + if app.ui.backstage_page == "print" { + export_page(app, ui); + return; + } + egui::ScrollArea::vertical().show(ui, |ui| match app.ui.backstage_page.as_str() { + "home" => home_page(app, ui), + "new" => new_page(app, ui), + "open" => open_page(app, ui), + "saveAs" => save_as_page(app, ui), + "info" => info_page(app, ui), + "recover" => recovery_page(app, ui), + "print" | "export" => export_page(app, ui), + "options" => crate::options::page(app, ui), + _ => home_page(app, ui), }); + }); } /// Recovery is a separate copy. Leaving this page never discards it. fn recovery_page(app: &mut WordApp, ui: &mut Ui) { - ui.visuals_mut().override_text_color = Some(egui::Color32::WHITE); heading(ui, "Recover documents"); ui.label(tl!("Restore opens an unsaved copy. Your original file stays unchanged.")); ui.add_space(20.0); @@ -539,6 +530,42 @@ mod tests { h.run_steps(4); } + #[test] + #[cfg(not(target_arch = "wasm32"))] + fn recovery_does_not_replace_an_explicit_new_document() { + for template in ["blank", "report"] { + let (mut app, root) = recovery_app(); + app.run("file.new", json!({"template": template})).unwrap(); + let document = app.session.document_id(); + let mut h = recovery_harness(app); + settle_recovery(&mut h); + assert!(!h.state().ui.backstage, "the requested template must stay visible"); + assert_eq!(h.state().session.document_id(), document); + assert_eq!(h.state().session.recovery_documents().len(), 1, "older copies remain available"); + h.state_mut().run("file.recover", json!({})).unwrap(); + h.run_steps(4); + assert!(h.state().ui.backstage && h.state().ui.backstage_page == "recover"); + drop(h); + let _ = std::fs::remove_dir_all(root); + } + } + + #[test] + #[cfg(not(target_arch = "wasm32"))] + fn recovery_does_not_replace_an_explicitly_opened_file() { + let (mut app, root) = recovery_app(); + let path = root.join("Requested.txt"); + std::fs::write(&path, "Requested synthetic document").unwrap(); + app.run("file.open", json!({"path": path})).unwrap(); + let mut h = recovery_harness(app); + settle_recovery(&mut h); + assert!(!h.state().ui.backstage); + assert_eq!(h.state().session.doc.plain_text(wordcraft_doc::StoryRef::Body), "Requested synthetic document"); + assert_eq!(h.state().session.recovery_documents().len(), 1); + drop(h); + let _ = std::fs::remove_dir_all(root); + } + #[test] #[cfg(not(target_arch = "wasm32"))] fn recovery_page_keeps_dismissed_copies_and_confirms_discard() { diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 3ff2348c3e..58a84eb1a9 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -993,6 +993,9 @@ impl WordApp { // When the document has been replaced (not when Open only showed its picker), a pending // "Save changes?" and AutoSave's go-ahead were both about the old one. if self.session.document_id() != document { + // A requested New/Open wins over the asynchronous startup recovery scan. + // Older copies remain available through File > Recover. + self.recovery_seen = true; self.autosave_path = None; if matches!(self.dialog, Some(dialogs::Dialog::SaveChanges { .. })) { self.dialog = None; diff --git a/docs/gaps.md b/docs/gaps.md index bc16973039..b930074720 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -1,6 +1,8 @@ -# Where WordCraft falls short of Word +#Recovery discovery no longer interrupts an explicitly created or opened document. Older copies remain in File › Recover, whose buttons use the interface palette. See [local recovery](recovery.md). -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** 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 and Convert Text to Table follow the Word check, #2592; previously trivial: Language dialog keeps inherited proofing language and no-proof choices (#2717); previously trivial: #29: a copied equation also goes in the MathML clipboard formats, #1297; previously trivial: #24: the Spike, Ctrl+F3 and Ctrl+Shift+F3; previously trivial: VML freeforms read and kept on save; previously trivial: 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: floating-table overlap ignores empty line-break lines; previously trivial: copies keep their formatting on the clipboard as HTML, #1523; previously trivial: sample-text formulas on Enter, #1067; previously trivial: #29: copied equations as MathML, OMML open as #1297, #816; previously trivial: the Font list no longer keeps every previewed font in memory, #848; 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 Table and Eraser landed, #303; previously minor: beta distance restated: ~15 points, ~150–230 h; previously: first version; every known shortfall from the 2026-10-10 re-measure, ranked) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 + Where WordCraft falls short of Word + +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** trivial (explicit documents keep the editor when recovery discovery finishes; readable recovery controls); previous change: 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 and Convert Text to Table follow the Word check, #2592; previously trivial: Language dialog keeps inherited proofing language and no-proof choices (#2717); previously trivial: #29: a copied equation also goes in the MathML clipboard formats, #1297; previously trivial: #24: the Spike, Ctrl+F3 and Ctrl+Shift+F3; previously trivial: VML freeforms read and kept on save; previously trivial: 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: floating-table overlap ignores empty line-break lines; previously trivial: copies keep their formatting on the clipboard as HTML, #1523; previously trivial: sample-text formulas on Enter, #1067; previously trivial: #29: copied equations as MathML, OMML open as #1297, #816; previously trivial: the Font list no longer keeps every previewed font in memory, #848; 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 Table and Eraser landed, #303; previously minor: beta distance restated: ~15 points, ~150–230 h; previously: first version; every known shortfall from the 2026-10-10 re-measure, ranked) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 > **Printing update:** 2026-10-12 · direct desktop printing through craft-print; details below. @@ -90,6 +92,7 @@ The Language dialog reads effective character formatting, including document def | Date | Change | Summary | |---|---|---| +| 2026-10-18 | trivial | Explicit New/Open is not interrupted by startup recovery discovery; older copies stay available and Recover follows the interface theme. | | 2026-10-16 | trivial | #24: the Spike (Ctrl+F3, Ctrl+Shift+F3) | | 2026-10-17 | trivial | Glossary/building blocks closed (DOCX glossary read, kept and written); `w:altChunk` and sub-documents remain | | 2026-10-15 | trivial | #5 narrowed: Track Changes Options › Moves (Moved from / Moved to marks and colours) and Track moves (`w:doNotTrackMoves`) landed; drag-and-drop moves remain | diff --git a/docs/recovery.md b/docs/recovery.md index 00ce845061..347f312096 100644 --- a/docs/recovery.md +++ b/docs/recovery.md @@ -1,7 +1,11 @@ # Local document recovery Desktop WordCraft periodically keeps a local copy of unsaved work, including new drafts. -File › Recover lists available copies. The page opens at startup when copies are found. +File › Recover lists available copies. The page opens at startup when copies are found, +unless a document was explicitly created or opened before the startup scan completed. +In particular, a window launched for New/Open keeps the requested document visible; +older copies remain available through File › Recover. Recovery uses the regular interface +palette so its labels and buttons stay readable in light and dark themes. Restore opens an unsaved document with no file path. Save As chooses where to keep it. Recovery never writes to the original document. AutoSave's permission to write to an earlier file is cleared on restore. diff --git a/docs/ui-parity.md b/docs/ui-parity.md index b13ec51ac5..aac6e3c648 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -1,6 +1,8 @@ -# UI parity with Microsoft Word +#Recovery discovery no longer interrupts an explicitly created or opened document. Older copies remain in File › Recover, whose buttons use the interface palette. See [local recovery](recovery.md). -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** 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 + UI parity with Microsoft Word + +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** trivial (explicit documents keep the editor when recovery discovery finishes; readable recovery controls); previous change: 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 @@ -65,6 +67,7 @@ Find selects the Results tab, including when it prefills selected text. Editing | Date | Change | Summary | |---|---|---| +| 2026-10-18 | trivial | Explicit New/Open is not interrupted by startup recovery discovery; older copies stay available and Recover follows the interface theme. | | 2026-10-15 | trivial | Dialogs, the Read Aloud player and the file browser: the 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 ✕ while the pointer is over it, darker red pressed, in both themes; Escape as before (#1885) | | 2026-10-15 | trivial | Font box (ribbon and mini toolbar): a Recently Used Fonts section at the top of the list holds the fonts applied from the box, the mini toolbar or the Font dialog, most recent first, once each, ten at most, kept between runs in `ui.json`; All Fonts follows; the sections show while nothing is typed and a query lists its matches only; Up/Down walk the recent entries then the list, Enter applies the highlighted one; a recent font no longer installed stays listed (#2068) | | 2026-10-16 | trivial | Web Layout text fills the window width and re-wraps once a resize settles or at once on a zoom, instead of a fixed 800 pt column; Draft and Outline keep their 800 pt column (#1876) | From 300164dde0809e35f5cdf60cf857da5e790f51c9 Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Thu, 15 Oct 2026 04:52:06 +0200 Subject: [PATCH 077/336] Double-click switches between header and footer editing Fixes #1026 Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Sonnet 5.5 Co-Authored-By: Claude Opus 5.5 Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Sonnet 5.5 --- crates/ui-egui/src/canvas.rs | 79 +++++++++++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 6 deletions(-) diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index bfc463d1c3..6b15c80719 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -2301,11 +2301,18 @@ fn mouse(app: &mut WordApp, ui: &Ui, resp: &egui::Response, rects: &[Rect], layo } else { None }; + // Entering: from the body, or from another header/footer: the other one of the two + // (switching, #1026), or with Different First Page on, page 1's header is not the one being + // edited on page 2 (#2826). + let hf_enter = hf_zone.is_some_and(|(shown, header)| match part_kind(app, story) { + _ if story == StoryRef::Body => true, + Some(PartKind::Header) => !header || shown != Some(story), + Some(PartKind::Footer) => header || shown != Some(story), + _ => false, + }); if resp.double_clicked() { - // Also from another header/footer: with Different First Page on, page 1's header is not - // the one being edited on page 2 (#2826). - if let Some((shown, header)) = hf_zone - && (story == StoryRef::Body || (shown != Some(story) && matches!(part_kind(app, story), Some(PartKind::Header | PartKind::Footer)))) + if let Some((_, header)) = hf_zone + && hf_enter { let id = if header { "insert.editHeader" } else { "insert.editFooter" }; if app.run(id, json!({ "page": page })).is_ok() @@ -2345,8 +2352,7 @@ fn mouse(app: &mut WordApp, ui: &Ui, resp: &egui::Response, rects: &[Rect], layo app.canvas.last_press = Some((now, p, clicks)); app.canvas.unit_drag = None; // A double-click that enters or leaves a header/footer is handled on release. - let hf_switch = - (hf_zone.is_some() && story == StoryRef::Body) || (matches!(story, StoryRef::Part(_)) && !in_text_box(app) && hf_zone.is_none()); + let hf_switch = hf_enter || (matches!(story, StoryRef::Part(_)) && !in_text_box(app) && hf_zone.is_none()); // Clicking into a footnote/endnote or a text box edits it; clicking outside a text box goes // back to where it is. While editing a header/footer, only its own text boxes are in reach. let story = { @@ -4054,6 +4060,67 @@ mod tests { assert_eq!(h.state_mut().session.layout().pages[0].header_story.map(StoryRef::Part), Some(first)); } + /// #1026: while editing the header, double-clicking the bottom margin switches to the + /// footer (and back), as in Word, instead of selecting a word in the header. + #[test] + fn double_click_switches_between_header_and_footer() { + use egui::{Event, PointerButton}; + let mut a = WordApp::new(wordcraft_engine::Session::new(wordcraft_doc::Document::new()), Default::default()); + a.run("text.insert", json!({"text": "Body text"})).unwrap(); + a.run("view.commentsPane", json!({"value": false})).unwrap(); + let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(1440.0, 2200.0)).with_step_dt(1.0 / 60.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 double_click = |h: &mut egui_kittest::Harness<'_, WordApp>, at: Pos2| { + h.event(Event::PointerMoved(at)); + for _ in 0..2 { + h.event(Event::PointerButton { pos: at, button: PointerButton::Primary, pressed: true, modifiers: Default::default() }); + h.step(); + h.event(Event::PointerButton { pos: at, button: PointerButton::Primary, pressed: false, modifiers: Default::default() }); + h.step(); + } + for _ in 0..40 { + h.step(); + } + }; + let kind = |h: &egui_kittest::Harness<'_, WordApp>| part_kind(h.state(), h.state().session.sel.focus.story); + let page = h.state_mut().session.layout().pages[0].clone(); + let (x, top, bottom) = (page.body.x + 20.0, page.body.y / 2.0, (page.body.bottom() + page.h) / 2.0); + let head_at = page_to_screen(h.state_mut(), 0, x, top).expect("top margin on screen"); + let foot_at = page_to_screen(h.state_mut(), 0, x, bottom).expect("bottom margin on screen"); + double_click(&mut h, head_at); + assert_eq!(kind(&h), Some(PartKind::Header), "editing a new header"); + let header = h.state().session.sel.focus.story; + h.event(Event::Text("Head".into())); + for _ in 0..3 { + h.step(); + } + // Header -> footer. + double_click(&mut h, foot_at); + assert_eq!(kind(&h), Some(PartKind::Footer), "header -> footer band double-click switches"); + assert!(h.state().session.sel.is_collapsed(), "no word selected"); + assert_eq!(h.state().session.doc.plain_text(header), "Head"); + h.event(Event::Text("Foot".into())); + for _ in 0..3 { + h.step(); + } + let footer = h.state().session.sel.focus.story; + // Footer -> header. + double_click(&mut h, head_at); + assert_eq!(h.state().session.sel.focus.story, header, "footer -> header band double-click switches"); + assert!(h.state().session.sel.is_collapsed(), "no word selected"); + assert_eq!(h.state().session.doc.plain_text(footer), "Foot"); + assert_eq!(h.state().session.doc.plain_text(header), "Head"); + } + /// #513: right-clicking a picture selects it and offers Wrap Text, as in Word; choosing /// Square there wraps text around it. #[test] From ac76375d741103ba016bfabeba1c547af8963cbb Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Sun, 18 Oct 2026 11:01:34 -0400 Subject: [PATCH 078/336] docs: add SonarCloud and CodeRabbit badges to README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 41b097cbd2..56da639ead 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ Runs on macOS, Windows, Linux, BSD and the web License: MIT OR Apache-2.0 Agent-drivable over MCP + Scanned by SonarCloud + Reviewed by CodeRabbit

From da99deb3aa1d8d1e10211fc1c53371baa0fec4c8 Mon Sep 17 00:00:00 2001 From: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com> Date: Fri, 16 Oct 2026 05:18:11 +0200 Subject: [PATCH 079/336] gap: single selected cell's border drags in its row only Co-Authored-By: Claude Sonnet 5.5 --- crates/engine/src/cmd/caret.rs | 22 ++++++++- crates/engine/src/cmd/table_resize.rs | 70 +++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/cmd/caret.rs b/crates/engine/src/cmd/caret.rs index c4e38a2ba2..ccc9990a3a 100644 --- a/crates/engine/src/cmd/caret.rs +++ b/crates/engine/src/cmd/caret.rs @@ -378,8 +378,28 @@ fn select_sentence(s: &mut Session, _: &Value) -> CmdResult { select_unit(s, Unit::Sentence) } +/// A triple-click on the only paragraph of a table cell selects the cell (Word takes its end +/// mark too, which shades it), so its borders then move on their own (`table.resizeDrag`). fn select_paragraph(s: &mut Session, _: &Value) -> CmdResult { - select_unit(s, Unit::Paragraph) + let r = select_unit(s, Unit::Paragraph); + let (a, b) = (s.sel.anchor.clone(), s.sel.focus.clone()); + let n = a.path.0.len(); + if a.story == b.story + && a.path.0.len() == b.path.0.len() + && let Some(tp) = a.path.0.get(..n.saturating_sub(3)) + && !tp.is_empty() + && a.path.0.get(..n.saturating_sub(1)) == b.path.0.get(..n.saturating_sub(1)) + && let (Some(&row), Some(&cell)) = (a.path.0.get(n.saturating_sub(3)), a.path.0.get(n.saturating_sub(2))) + { + let tp = wordcraft_doc::Path(tp.to_vec()); + let (row, cell) = (row as usize, cell as usize); + let only = s.doc.table(a.story, &tp).and_then(|t| t.rows.get(row)?.cells.get(cell)).is_some_and(|c| c.blocks.len() == 1); + let grid = s.doc.table(a.story, &tp).map(|t| super::table::grid_cols(t, row, cell)); + if let (true, Some(cols)) = (only, grid) { + return super::table::select_range(s, a.story, &tp, (row, row), cols); + } + } + r } /// A click in the selection bar (#856): the whole line at `pos` (default: the caret's), its diff --git a/crates/engine/src/cmd/table_resize.rs b/crates/engine/src/cmd/table_resize.rs index 70f04f0c40..8abadaffa2 100644 --- a/crates/engine/src/cmd/table_resize.rs +++ b/crates/engine/src/cmd/table_resize.rs @@ -1016,4 +1016,74 @@ mod tests { assert_eq!(first_table(&s).grid.len(), 3, "no new grid line"); assert!((cell_rect(&mut s, 0, 2).x - (c.x + 20.0)).abs() < 1.0, "the first row follows"); } + + /// Selects the cell at (row, cell) one of the ways Word does: Select Cell, the left-edge + /// arrow, or a triple-click on its text. + fn select_one_cell(s: &mut Session, how: &str, r: usize, c: usize) { + let b = cell_rect(s, r, c); + let y = b.y + b.h / 2.0; + match how { + "arrow" => { + s.run("table.selectAt", &json!({"page": 0, "x": b.x + 4.5, "y": y})).unwrap(); + } + "triple" => { + s.run("caret.set", &json!({"page": 0, "x": b.x + b.w / 2.0, "y": y})).unwrap(); + s.run("select.paragraph", &json!({})).unwrap(); + } + _ => { + s.run("caret.set", &json!({"page": 0, "x": b.x + b.w / 2.0, "y": y})).unwrap(); + s.run("table.selectCell", &json!({})).unwrap(); + } + } + } + + /// Each way of selecting one cell makes its right and its left border move in its row + /// only; the neighbour on that side gives up or gains the room and the row keeps its width. + #[test] + fn a_single_selected_cells_borders_move_only_in_its_row() { + for how in ["cell", "arrow", "triple"] { + for left in [false, true] { + let mut s = table_session(3, 3); + let (a, b, c) = (cell_rect(&mut s, 1, 0), cell_rect(&mut s, 1, 1), cell_rect(&mut s, 1, 2)); + let y = b.y + b.h / 2.0; + select_one_cell(&mut s, how, 1, 1); + let start = first_table(&s); + let x = if left { b.x } else { b.right() }; + s.run("table.resizeDrag", &json!({"page": 0, "x": x, "y": y, "toX": x + 30.0, "toY": y})).unwrap(); + let (a1, b1, c1) = (cell_rect(&mut s, 1, 0), cell_rect(&mut s, 1, 1), cell_rect(&mut s, 1, 2)); + if left { + assert!((b1.w - (b.w - 30.0)).abs() < 1.0 && (a1.w - (a.w + 30.0)).abs() < 1.0, "{how} left: {a1:?} {b1:?}"); + assert!((c1.w - c.w).abs() < 0.5, "{how} left"); + } else { + assert!((b1.w - (b.w + 30.0)).abs() < 1.0 && (c1.w - (c.w - 30.0)).abs() < 1.0, "{how} right: {b1:?} {c1:?}"); + assert!((a1.w - a.w).abs() < 0.5, "{how} right"); + } + assert!((c1.right() - c.right()).abs() < 0.5, "{how}: the row keeps its width"); + for r in [0, 2] { + assert!( + (cell_rect(&mut s, r, 1).x - b.x).abs() < 0.5 && (cell_rect(&mut s, r, 1).right() - b.right()).abs() < 0.5, + "{how}: row {r} unchanged" + ); + } + s.run("edit.undo", &json!({})).unwrap(); + assert_eq!(first_table(&s), start, "{how}: one undo step"); + } + } + } + + /// A caret, a part of a cell's text, or another cell's border with a cell selected: the + /// whole column moves. + #[test] + fn without_that_cell_selected_the_whole_column_moves() { + let mut s = table_session(3, 3); + let b = cell_rect(&mut s, 1, 1); + let y = b.y + b.h / 2.0; + s.run("caret.set", &json!({"page": 0, "x": b.x + b.w / 2.0, "y": y})).unwrap(); + s.run("table.resizeDrag", &json!({"page": 0, "x": b.right(), "y": y, "toX": b.right() + 30.0, "toY": y})).unwrap(); + assert!((cell_rect(&mut s, 0, 1).w - (b.w + 30.0)).abs() < 1.0, "caret only"); + s.run("edit.undo", &json!({})).unwrap(); + select_one_cell(&mut s, "arrow", 2, 2); + s.run("table.resizeDrag", &json!({"page": 0, "x": b.right(), "y": y, "toX": b.right() + 30.0, "toY": y})).unwrap(); + assert!((cell_rect(&mut s, 0, 1).w - (b.w + 30.0)).abs() < 1.0, "another cell selected"); + } } From d9467df1c0d991ef821a23f53f283fdedd90c2f3 Mon Sep 17 00:00:00 2001 From: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com> Date: Fri, 16 Oct 2026 05:47:03 +0200 Subject: [PATCH 080/336] gap: single cell border: review round 1 Co-Authored-By: Claude Sonnet 5.5 --- crates/engine/src/cmd/caret.rs | 5 ++- crates/engine/src/cmd/table_resize.rs | 53 +++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/crates/engine/src/cmd/caret.rs b/crates/engine/src/cmd/caret.rs index ccc9990a3a..4ab47171fa 100644 --- a/crates/engine/src/cmd/caret.rs +++ b/crates/engine/src/cmd/caret.rs @@ -378,7 +378,7 @@ fn select_sentence(s: &mut Session, _: &Value) -> CmdResult { select_unit(s, Unit::Sentence) } -/// A triple-click on the only paragraph of a table cell selects the cell (Word takes its end +/// A triple-click on the last paragraph of a table cell selects the cell (Word takes its end /// mark too, which shades it), so its borders then move on their own (`table.resizeDrag`). fn select_paragraph(s: &mut Session, _: &Value) -> CmdResult { let r = select_unit(s, Unit::Paragraph); @@ -393,7 +393,8 @@ fn select_paragraph(s: &mut Session, _: &Value) -> CmdResult { { let tp = wordcraft_doc::Path(tp.to_vec()); let (row, cell) = (row as usize, cell as usize); - let only = s.doc.table(a.story, &tp).and_then(|t| t.rows.get(row)?.cells.get(cell)).is_some_and(|c| c.blocks.len() == 1); + let last = s.doc.table(a.story, &tp).and_then(|t| t.rows.get(row)?.cells.get(cell)).map(|c| c.blocks.len().saturating_sub(1)); + let only = last.is_some_and(|l| a.path.0.last().is_some_and(|&i| i as usize == l)); let grid = s.doc.table(a.story, &tp).map(|t| super::table::grid_cols(t, row, cell)); if let (true, Some(cols)) = (only, grid) { return super::table::select_range(s, a.story, &tp, (row, row), cols); diff --git a/crates/engine/src/cmd/table_resize.rs b/crates/engine/src/cmd/table_resize.rs index 8abadaffa2..20af69360f 100644 --- a/crates/engine/src/cmd/table_resize.rs +++ b/crates/engine/src/cmd/table_resize.rs @@ -1085,5 +1085,58 @@ mod tests { select_one_cell(&mut s, "arrow", 2, 2); s.run("table.resizeDrag", &json!({"page": 0, "x": b.right(), "y": y, "toX": b.right() + 30.0, "toY": y})).unwrap(); assert!((cell_rect(&mut s, 0, 1).w - (b.w + 30.0)).abs() < 1.0, "another cell selected"); + s.run("edit.undo", &json!({})).unwrap(); + // All of a cell's text, picked as a range (no cell mark): not a cell selection. + s.run("caret.set", &json!({"page": 0, "x": b.x + b.w / 2.0, "y": y})).unwrap(); + s.run("text.insert", &json!({"text": "Wide"})).unwrap(); + s.run("select.text", &json!({"text": "Wide"})).unwrap(); + let (anchor, focus) = (serde_json::to_value(&s.sel.anchor).unwrap(), serde_json::to_value(&s.sel.focus).unwrap()); + s.run("select.range", &json!({"anchor": anchor, "focus": focus})).unwrap(); + assert!(s.cell_range().is_none()); + let b = cell_rect(&mut s, 1, 1); + s.run("table.resizeDrag", &json!({"page": 0, "x": b.right(), "y": y, "toX": b.right() + 30.0, "toY": y})).unwrap(); + assert!((cell_rect(&mut s, 0, 1).w - (b.w + 30.0)).abs() < 1.0, "all of a cell's text selected"); + } + + /// The jagged row a single cell's drag leaves is saved to .docx and read back the same. + #[test] + fn a_jagged_row_survives_a_docx_round_trip() { + let mut s = table_session(3, 3); + let b = cell_rect(&mut s, 1, 1); + let y = b.y + b.h / 2.0; + select_one_cell(&mut s, "arrow", 1, 1); + s.run("table.resizeDrag", &json!({"page": 0, "x": b.right(), "y": y, "toX": b.right() + 30.0, "toY": y})).unwrap(); + let t = first_table(&s); + let bytes = crate::io::save_bytes("jagged.docx", &s.doc).unwrap(); + let doc = crate::io::open_bytes("jagged.docx", &bytes).unwrap(); + let back = doc.body.iter().find_map(|b| b.as_table()).cloned().unwrap(); + let spans = |t: &Table| t.rows.iter().map(|r| r.cells.iter().map(Cell::span).collect::>()).collect::>(); + assert_eq!(spans(&back), spans(&t)); + let mut s2 = Session::new(doc); + for c in 0..spans(&t)[1].len() { + assert!((cell_rect(&mut s2, 1, c).w - cell_rect(&mut s, 1, c).w).abs() < 1.0, "cell {c}"); + } + } + + /// In a cell of two paragraphs, a triple-click on the last one selects the cell (its border + /// then moves in its row only); on the first one it selects text, and the whole column moves. + #[test] + fn triple_click_selects_a_cell_only_on_its_last_paragraph() { + for last in [true, false] { + let mut s = table_session(3, 3); + let b = cell_rect(&mut s, 1, 1); + s.run("caret.set", &json!({"page": 0, "x": b.x + b.w / 2.0, "y": b.y + b.h / 2.0})).unwrap(); + s.run("text.insert", &json!({"text": "One"})).unwrap(); + s.run("text.newParagraph", &json!({})).unwrap(); + s.run("text.insert", &json!({"text": "Two"})).unwrap(); + s.run("select.text", &json!({"text": if last { "Two" } else { "One" }})).unwrap(); + s.run("select.paragraph", &json!({})).unwrap(); + assert_eq!(s.cell_range().is_some(), last); + let b = cell_rect(&mut s, 1, 1); + let y = b.y + b.h / 2.0; + s.run("table.resizeDrag", &json!({"page": 0, "x": b.right(), "y": y, "toX": b.right() + 30.0, "toY": y})).unwrap(); + let moved = (cell_rect(&mut s, 0, 1).w - (b.w + 30.0)).abs() < 1.0; + assert_eq!(moved, !last, "last paragraph {last}: the column moves only without a cell selection"); + } } } From 5aa87f27f3573549fdada6927411ea56f14ad49c Mon Sep 17 00:00:00 2001 From: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com> Date: Sun, 18 Oct 2026 17:43:19 +0200 Subject: [PATCH 081/336] gap: single cell border: only a triple-click selects the cell, not the ribbon's Select Paragraph Co-Authored-By: Claude Sonnet 5.5 --- crates/engine/src/cmd/caret.rs | 10 +++++++--- crates/engine/src/cmd/table_resize.rs | 5 ++++- crates/ui-egui/src/canvas.rs | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/crates/engine/src/cmd/caret.rs b/crates/engine/src/cmd/caret.rs index 4ab47171fa..12a65494f1 100644 --- a/crates/engine/src/cmd/caret.rs +++ b/crates/engine/src/cmd/caret.rs @@ -378,10 +378,14 @@ fn select_sentence(s: &mut Session, _: &Value) -> CmdResult { select_unit(s, Unit::Sentence) } -/// A triple-click on the last paragraph of a table cell selects the cell (Word takes its end -/// mark too, which shades it), so its borders then move on their own (`table.resizeDrag`). -fn select_paragraph(s: &mut Session, _: &Value) -> CmdResult { +/// A triple-click (`cell: true`) on the last paragraph of a table cell selects the cell (Word +/// takes its end mark too, which shades it), so its borders then move on their own +/// (`table.resizeDrag`). Without `cell` (the ribbon's Select Paragraph) only the paragraph is selected. +fn select_paragraph(s: &mut Session, v: &Value) -> CmdResult { let r = select_unit(s, Unit::Paragraph); + if v.get("cell").and_then(Value::as_bool) != Some(true) { + return r; + } let (a, b) = (s.sel.anchor.clone(), s.sel.focus.clone()); let n = a.path.0.len(); if a.story == b.story diff --git a/crates/engine/src/cmd/table_resize.rs b/crates/engine/src/cmd/table_resize.rs index 20af69360f..af51c22fdd 100644 --- a/crates/engine/src/cmd/table_resize.rs +++ b/crates/engine/src/cmd/table_resize.rs @@ -1028,7 +1028,7 @@ mod tests { } "triple" => { s.run("caret.set", &json!({"page": 0, "x": b.x + b.w / 2.0, "y": y})).unwrap(); - s.run("select.paragraph", &json!({})).unwrap(); + s.run("select.paragraph", &json!({"cell": true})).unwrap(); } _ => { s.run("caret.set", &json!({"page": 0, "x": b.x + b.w / 2.0, "y": y})).unwrap(); @@ -1131,6 +1131,9 @@ mod tests { s.run("text.insert", &json!({"text": "Two"})).unwrap(); s.run("select.text", &json!({"text": if last { "Two" } else { "One" }})).unwrap(); s.run("select.paragraph", &json!({})).unwrap(); + assert!(s.cell_range().is_none(), "the ribbon's Select Paragraph never selects the cell"); + s.run("select.text", &json!({"text": if last { "Two" } else { "One" }})).unwrap(); + s.run("select.paragraph", &json!({"cell": true})).unwrap(); assert_eq!(s.cell_range().is_some(), last); let b = cell_rect(&mut s, 1, 1); let y = b.y + b.h / 2.0; diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 38f8d1d214..88e645c558 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -2372,7 +2372,7 @@ fn mouse(app: &mut WordApp, ui: &Ui, resp: &egui::Response, rects: &[Rect], layo return; } if resp.triple_clicked() { - let _ = app.run("select.paragraph", json!({})); + let _ = app.run("select.paragraph", json!({"cell": true})); return; } let pressed = ui.input(|i| i.pointer.primary_pressed()) && resp.contains_pointer() && !over_balloon(app, p); From 635137b76906a5f7d80e8f8fab609817372237f1 Mon Sep 17 00:00:00 2001 From: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com> Date: Sun, 18 Oct 2026 17:43:27 +0200 Subject: [PATCH 082/336] gap: single cell border: progress docs after rebase Co-Authored-By: Claude Sonnet 5.5 --- ROADMAP.md | 4 +++- docs/ui-parity.md | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 2d9807f841..a7dca6f65e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **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 (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 (a single selected cell's border drags in its row only; triple-click on a cell's last paragraph selects the cell; 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-17 · **Change:** 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. @@ -140,6 +140,7 @@ Imported Word 97–2003 `.doc`/`.dot` files now ask for a supported DOCX target | Date | What landed | | 2026-10-18 | Symbols keep their font (#2036): a `w:sym` or Insert › Symbol character in Wingdings, Symbol or Webdings remembers its symbol font apart from the text's font, so selecting all and changing the font (or Clear Formatting) no longer turns the symbols into boxes; saved back to DOCX as `w:sym`, so Word shows them too. Typed text and the Format Painter don't pick up the symbol font. | +| 2026-10-18 | Dragging a single selected cell's border (Select Cell, left-edge arrow, triple-click on its last paragraph) moves it in its row only; the neighbour takes up the difference. Checked against Word for Mac 16.113.4. | | 2026-10-16 | `wordcraft-cli run --script FILE.json` (`-` for stdin) runs a JSON list of commands, then saves to one or more outputs (docx and pdf in one run); errors name the command number and id and nothing is saved, #1963 | | 2026-10-14 | RTF export writes every section (gap 19): `\sect` ends each section and `\sectd` carries its break type, page size and orientation, margins, columns, page numbering and title page, so multi-section documents survive an RTF round trip | | 2026-10-14 | `ASK` and `SET` fields no longer show their stored result on the page or in PDFs (#1765): like Word, they only assign text to a bookmark and draw nothing; the stored result is kept and saved | @@ -481,6 +482,7 @@ Imported Word 97–2003 `.doc`/`.dot` files now ask for a supported DOCX target | Date | Change | Summary | |---|---|---| +| 2026-10-18 | trivial | Progress log: a single selected cell's border drags in its row only | | 2026-10-18 | trivial | Progress log: symbols keep their font when the text font changes (#2036) | | 2026-10-14 | trivial | Progress log: a list item's first word is not flagged for a missing capital (#1741) | | 2026-10-18 | trivial | #2445: pending Save As answers keep their originating document identity, so a replacement document is never written to an old picker destination; real Save-button and focused-editor shortcut controls verified | diff --git a/docs/ui-parity.md b/docs/ui-parity.md index 55801b0e72..c3200d801d 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -1,6 +1,6 @@ # UI parity with Microsoft Word -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** 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 (a single selected cell's border drags in its row only; triple-click on a cell's last paragraph selects the cell; 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 @@ -70,6 +70,7 @@ Footnote and endnote editing keeps body and note text at normal opacity, on ligh | Date | Change | Summary | |---|---|---| +| 2026-10-18 | trivial | Tables: a single selected cell's border drags in its row only (Select Cell, left-edge arrow, triple-click); checked against Word for Mac 16.113.4 | | 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-15 | trivial | File › Close (Mod+W, Mod+F4) closes the document and keeps the window, showing no page, as Word does (it quit the whole app); the window's close button still asks to save and quits (#1712) | | 2026-10-16 | trivial | Save Changes exposes an alert-dialog role, the document-specific question and loss/failure context to accessibility; full-app focus/ancestry regression. Native screen-reader verification remains pending. | From 487a7ccb86b3db818fb3b417d4a8c4bf6897e119 Mon Sep 17 00:00:00 2001 From: luisvaca Date: Thu, 15 Oct 2026 17:58:19 -0600 Subject: [PATCH 083/336] #2289: comments in headers, footers, notes and text boxes are listed, get balloons and are reached by Next/Previous comment_list scanned the body only, so a comment anchored anywhere else was listed with no anchor: no balloon beside its text, Go to did nothing, and Next/Previous Comment never reached it. It now scans every story except comment text, body first and then each part in Pos order, which is the order nav_comment relies on. Reuses tools::all_stories. Co-Authored-By: Claude Opus 5.5 --- ROADMAP.md | 2 + crates/engine/src/cmd/review.rs | 100 ++++++++++++++++++++++++++++++-- crates/engine/src/cmd/tools.rs | 2 +- 3 files changed, 97 insertions(+), 7 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 39675b4fe2..894ba0aa4a 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -234,6 +234,7 @@ Imported Word 97–2003 `.doc`/`.dot` files now ask for a supported DOCX target | 2026-10-14 | Page-break-only list paragraphs no longer print stray bullets or numbers. The next item keeps its number, matching the reference export. | | 2026-10-12 | Backspace at the start of the paragraph after a table nested in a cell moves into the nested table's last cell, no longer into the last cell of the table around it | | 2026-10-14 | Grouped `flowChartOr` shapes draw their circle and crossing spokes, as in Word | +| 2026-10-15 | Comments in headers, footers, notes and text boxes are listed with their place in the document, get a balloon beside their text, and Next, Previous and Go to reach them, after the body's comments (#2289) | | 2026-10-14 | Deleting a comment removes its anchors from headers, footers, notes and text boxes too, not just the body, and deletes the whole thread below it, replies to replies included; no anchor or reply is left pointing at a deleted comment (#1345) | | 2026-10-14 | Legacy DOC DIB conversion checks the BMP header and copy before allocation, uses the caller's decode allowance, and caps PNG output while encoding; a BMP fallback is kept when it fits. A generated 1-pixel regression covers 57/58/512-byte limits. Resource-exhaustion consequences remain unmeasured. Related: #1334 and #1554 | | 2026-10-15 | Draw: Ink to Shape makes two-stroke arrows and arrows with a closed triangle head. A line plus a separate head stroke (an open V or a small closed triangle, drawn after the line or before it) whose apex sits at one of the line's ends becomes one straight arrow connector, and the head stroke goes, in one undo step; this works while drawing with Ink to Shape on and on lasso-selected strokes with Convert to Shapes. A shaft with a small triangle drawn round its tip in the same stroke is an arrow too; a triangle without a shaft stays a triangle. Our own design (Word for Mac has no Ink to Shape). | @@ -583,6 +584,7 @@ Footer page-table height and body wrapping are corrected. Other pagination gaps | 2026-10-14 | trivial | Field instructions spanning paragraphs no longer add blank lines during pagination; empty result paragraphs still take a line. | | 2026-10-14 | trivial | A list paragraph containing only a page break has no visible label; it still advances the list counter. Ordinary empty list items keep their label. | | 2026-10-14 | trivial | Floating-table overlap checks use visible line content; empty line-break lines no longer move a table | +| 2026-10-15 | trivial | Progress log: comments outside the body in the list, balloons and Next/Previous (#2289) | | 2026-10-14 | trivial | Progress log: deleting a comment leaves no dangling anchors or replies (#1345) | | 2026-10-14 | minor | Legacy DOC DIB conversion byte limits only; related #1334/#1554, stage and readiness estimates unchanged | | 2026-10-15 | trivial | Progress log: Ink to Shape two-stroke and closed-head arrows | diff --git a/crates/engine/src/cmd/review.rs b/crates/engine/src/cmd/review.rs index 904bd8ddc7..d54a1be849 100644 --- a/crates/engine/src/cmd/review.rs +++ b/crates/engine/src/cmd/review.rs @@ -285,14 +285,22 @@ fn delete_comment(s: &mut Session, v: &Value) -> CmdResult { Ok(json!({"deleted": ids})) } -/// Comments in document order with their anchors. +/// Comments in document order with their anchors: the body's, then those in headers, footers, +/// notes and text boxes, story by story (#2289). That is `Pos` order, which Next and Previous +/// rely on. Comment text itself isn't scanned. pub fn comment_list(s: &Session) -> Vec<(u32, Option)> { let mut found: Vec<(u32, Option)> = Vec::new(); - for path in s.doc.para_paths(StoryRef::Body) { - let Some(p) = s.doc.para(StoryRef::Body, &path) else { continue }; - for o in p.object_offsets() { - if let Some(InlineObject::CommentStart { id }) = p.object_at(o) { - found.push((*id, Some(Pos { story: StoryRef::Body, path: path.clone(), off: o }))); + let stories = super::tools::all_stories(&s.doc).into_iter().filter(|st| match st { + StoryRef::Body => true, + StoryRef::Part(id) => s.doc.parts.get(id).is_some_and(|p| p.kind != wordcraft_doc::PartKind::Comment), + }); + for story in stories { + for path in s.doc.para_paths(story) { + let Some(p) = s.doc.para(story, &path) else { continue }; + for o in p.object_offsets() { + if let Some(InlineObject::CommentStart { id }) = p.object_at(o) { + found.push((*id, Some(Pos { story, path: path.clone(), off: o }))); + } } } } @@ -1747,6 +1755,86 @@ mod tests { assert!(started.elapsed() < std::time::Duration::from_secs(5), "took {:?}", started.elapsed()); } + /// A body comment, then comments in a header, a footer and a text box, each on its story's + /// whole text. Returns their ids and stories in that order. + fn comments_in_every_story(s: &mut Session) -> Vec<(u64, wordcraft_doc::StoryRef)> { + use wordcraft_doc::StoryRef; + s.view.proofing = false; + s.run("document.setText", &json!({"text": "Body text with a comment on it."})).unwrap(); + s.run("select.text", &json!({"text": "comment"})).unwrap(); + let mut out = vec![(s.run("review.newComment", &json!({"text": "body"})).unwrap()["id"].as_u64().unwrap(), StoryRef::Body)]; + for (cmd, text) in [("insert.header", "header"), ("insert.footer", "footer")] { + s.run("insert.closeHeader", &json!({})).unwrap(); + let story = StoryRef::Part(s.run(cmd, &json!({"text": text})).unwrap()["story"].as_u64().unwrap() as u32); + s.run("select.all", &json!({})).unwrap(); + out.push((s.run("review.newComment", &json!({"text": text})).unwrap()["id"].as_u64().unwrap(), story)); + } + s.run("insert.closeHeader", &json!({})).unwrap(); + s.run("caret.docEnd", &json!({})).unwrap(); + s.run("insert.textBox", &json!({"text": "boxed"})).unwrap(); + let story = s.sel.focus.story; + s.run("select.all", &json!({})).unwrap(); + out.push((s.run("review.newComment", &json!({"text": "text box"})).unwrap()["id"].as_u64().unwrap(), story)); + s.run("caret.docStart", &json!({})).unwrap(); + out + } + + /// #2289: comments in headers, footers and text boxes are listed with their anchors, after + /// the body's, rather than with no anchor. + #[test] + fn comments_outside_the_body_are_listed_with_their_anchors() { + let mut s = Session::new(Document::new()); + let want = comments_in_every_story(&mut s); + let listed = s.run("review.comments", &json!({})).unwrap(); + let got: Vec<(u64, wordcraft_doc::StoryRef)> = listed + .as_array() + .unwrap() + .iter() + .map(|c| (c["id"].as_u64().unwrap(), serde_json::from_value(c["anchor"]["story"].clone()).expect("every comment has an anchor"))) + .collect(); + assert_eq!(got, want); + } + + /// #2289: Next and Previous Comment go through every story's comments and wrap around. + #[test] + fn next_and_previous_comment_reach_comments_outside_the_body() { + let mut s = Session::new(Document::new()); + let want: Vec = comments_in_every_story(&mut s).into_iter().map(|(_, st)| st).collect(); + let mut forward = Vec::new(); + for _ in 0..=want.len() { + s.run("review.nextComment", &json!({})).unwrap(); + forward.push(s.sel.focus.story); + } + let mut wrapped = want.clone(); + wrapped.push(want[0]); + assert_eq!(forward, wrapped, "Next visits each story's comment, then wraps to the first"); + let mut back = Vec::new(); + for _ in 0..want.len() { + s.run("review.previousComment", &json!({})).unwrap(); + back.push(s.sel.focus.story); + } + let mut rev = want.clone(); + rev.reverse(); + assert_eq!(back, rev, "Previous from the first comment wraps to the last"); + } + + /// Text inside a comment isn't an anchor: a comment marker in a comment's own story is not + /// listed as that comment's place in the document. + #[test] + fn comment_list_skips_comment_text() { + let mut s = Session::new(Document::new()); + s.view.proofing = false; + s.run("document.setText", &json!({"text": "hello"})).unwrap(); + s.run("select.all", &json!({})).unwrap(); + let id = s.run("review.newComment", &json!({"text": "note"})).unwrap()["id"].as_u64().unwrap() as u32; + let part = s.doc.comments.get(&id).unwrap().part; + let at = wordcraft_doc::Pos { story: wordcraft_doc::StoryRef::Part(part), path: wordcraft_doc::Path::top(0), off: 0 }; + s.doc.insert_object(&at, wordcraft_doc::InlineObject::CommentStart { id }, &Default::default()).unwrap(); + let list = super::comment_list(&s); + assert_eq!(list.iter().filter(|(x, _)| *x == id).count(), 1); + assert_eq!(list.first().and_then(|(_, p)| p.as_ref()).map(|p| p.story), Some(wordcraft_doc::StoryRef::Body)); + } + #[test] fn rtf_tracked_changes_accept_and_reject_after_opening_and_round_trip() { // Gap 19: an RTF file's tracked insertions and deletions open as tracked changes, also diff --git a/crates/engine/src/cmd/tools.rs b/crates/engine/src/cmd/tools.rs index b02408dd21..76243df5b7 100644 --- a/crates/engine/src/cmd/tools.rs +++ b/crates/engine/src/cmd/tools.rs @@ -1586,7 +1586,7 @@ impl Hidden { } /// Every story of the document: the body, then headers, footers, notes, comments and text boxes. -fn all_stories(doc: &Document) -> Vec { +pub(crate) fn all_stories(doc: &Document) -> Vec { std::iter::once(StoryRef::Body).chain(doc.parts.keys().map(|k| StoryRef::Part(*k))).collect() } From 652a1596aa77025f17a6b673018b72b9fa520a0c Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Sun, 18 Oct 2026 14:47:49 -0400 Subject: [PATCH 084/336] docs: put SonarCloud and CodeRabbit badges on their own line --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 56da639ead..2dc6acdf03 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Runs on macOS, Windows, Linux, BSD and the web License: MIT OR Apache-2.0 Agent-drivable over MCP +
Scanned by SonarCloud Reviewed by CodeRabbit

From 80f6ba6a25f8565a7e2f5380bef1dd1bd4df5e8f Mon Sep 17 00:00:00 2001 From: luisvaca Date: Thu, 15 Oct 2026 19:36:18 -0600 Subject: [PATCH 085/336] #2289: comment navigation follows the stories a reader sees, in reading order Review of the first commit found, and ui_shot runs confirmed, three bugs in it: - Next Comment moved the caret into a removed header or a deleted footnote's text: a story nothing shows, so typing went nowhere visible. - Next visited headers and footers by part id (creation order), so a footer made first came before the header. - Comments in footnotes were claimed but untested. Document::shown_stories lists the stories a reader sees, in reading order: the body with the notes and text boxes it shows, then the headers and footers the sections use with theirs. It reuses the reachable walk that counted_stories uses. comment_list scans those stories (and returns at once when the document has no comments: the balloons ask every frame), and nav_comment orders by a story's place in that list instead of by Pos. tools::all_stories is private again. Co-Authored-By: Claude Opus 5.5 --- ROADMAP.md | 2 +- crates/doc/src/lib.rs | 39 +++++++- crates/engine/src/cmd/review.rs | 158 ++++++++++++++++++++++---------- crates/engine/src/cmd/tools.rs | 2 +- 4 files changed, 146 insertions(+), 55 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 894ba0aa4a..c721920a10 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -234,7 +234,7 @@ Imported Word 97–2003 `.doc`/`.dot` files now ask for a supported DOCX target | 2026-10-14 | Page-break-only list paragraphs no longer print stray bullets or numbers. The next item keeps its number, matching the reference export. | | 2026-10-12 | Backspace at the start of the paragraph after a table nested in a cell moves into the nested table's last cell, no longer into the last cell of the table around it | | 2026-10-14 | Grouped `flowChartOr` shapes draw their circle and crossing spokes, as in Word | -| 2026-10-15 | Comments in headers, footers, notes and text boxes are listed with their place in the document, get a balloon beside their text, and Next, Previous and Go to reach them, after the body's comments (#2289) | +| 2026-10-15 | Comments in headers, footers, notes and text boxes are listed with their place in the document, get a balloon beside their text, and Next, Previous and Go to reach them in reading order: the body with its notes and text boxes, then headers and footers. A comment left in a removed header or a deleted note's text isn't navigated to (#2289) | | 2026-10-14 | Deleting a comment removes its anchors from headers, footers, notes and text boxes too, not just the body, and deletes the whole thread below it, replies to replies included; no anchor or reply is left pointing at a deleted comment (#1345) | | 2026-10-14 | Legacy DOC DIB conversion checks the BMP header and copy before allocation, uses the caller's decode allowance, and caps PNG output while encoding; a BMP fallback is kept when it fits. A generated 1-pixel regression covers 57/58/512-byte limits. Resource-exhaustion consequences remain unmeasured. Related: #1334 and #1554 | | 2026-10-15 | Draw: Ink to Shape makes two-stroke arrows and arrows with a closed triangle head. A line plus a separate head stroke (an open V or a small closed triangle, drawn after the line or before it) whose apex sits at one of the line's ends becomes one straight arrow connector, and the head stroke goes, in one undo step; this works while drawing with Ink to Shape on and on lasso-selected strokes with Convert to Shapes. A shaft with a small triangle drawn round its tip in the same stroke is an arrow too; a triangle without a shaft stays a triangle. Our own design (Word for Mac has no Ink to Shape). | diff --git a/crates/doc/src/lib.rs b/crates/doc/src/lib.rs index c41a14b2b6..a82a15b284 100644 --- a/crates/doc/src/lib.rs +++ b/crates/doc/src/lib.rs @@ -1079,10 +1079,33 @@ impl Document { /// The body, then the text boxes, footnotes and endnotes it shows (nested ones too), in /// document order. Headers, footers, comments and stories nothing points at aren't included. pub fn counted_stories(&self) -> Vec { - self.reachable(vec![StoryRef::Body], &|o| match o { - InlineObject::NoteRef { id, .. } => vec![(*id, &[PartKind::Footnote, PartKind::Endnote][..])], - o => o.text_boxes().into_iter().map(|id| (id, &[PartKind::TextBox][..])).collect(), - }) + self.reachable(vec![StoryRef::Body], ¬es_and_boxes) + } + + /// The stories a reader sees, in reading order: `counted_stories`, then the headers and + /// footers the sections use, each followed by the notes and text boxes it shows. Stories + /// nothing points at any more (a removed header, a deleted note's text) and comment text + /// aren't included. + pub fn shown_stories(&self) -> Vec { + let mut out = self.counted_stories(); + let mut seen: std::collections::BTreeSet = out.iter().copied().collect(); + let mut roots = Vec::new(); + let mut root_seen = std::collections::BTreeSet::new(); + for (_, sec) in self.sections() { + for set in [&sec.headers, &sec.footers] { + for id in [set.default, set.first, set.even].into_iter().flatten() { + if self.parts.get(&id).is_some_and(|p| matches!(p.kind, PartKind::Header | PartKind::Footer)) && root_seen.insert(id) { + roots.push(StoryRef::Part(id)); + } + } + } + } + for st in self.reachable(roots, ¬es_and_boxes) { + if seen.insert(st) { + out.push(st); + } + } + out } /// Drop the text box stories no box shows any more (a deleted or cut box's text), nested @@ -1452,6 +1475,14 @@ pub fn count_words(s: &str) -> usize { /// How deep in nested tables [`Document::para_paths`] goes: cells below this are skipped. pub(crate) const PARA_WALK_DEPTH: usize = 16; +/// The note and text box stories an object shows, for `Document::reachable`. +fn notes_and_boxes(o: &InlineObject) -> Vec<(u32, &'static [PartKind])> { + match o { + InlineObject::NoteRef { id, .. } => vec![(*id, &[PartKind::Footnote, PartKind::Endnote][..])], + o => o.text_boxes().into_iter().map(|id| (id, &[PartKind::TextBox][..])).collect(), + } +} + fn walk(bl: &Blocks, prefix: &mut Vec, out: &mut Vec, depth: usize) { if depth > PARA_WALK_DEPTH { return; diff --git a/crates/engine/src/cmd/review.rs b/crates/engine/src/cmd/review.rs index d54a1be849..ed92382ef8 100644 --- a/crates/engine/src/cmd/review.rs +++ b/crates/engine/src/cmd/review.rs @@ -285,16 +285,16 @@ fn delete_comment(s: &mut Session, v: &Value) -> CmdResult { Ok(json!({"deleted": ids})) } -/// Comments in document order with their anchors: the body's, then those in headers, footers, -/// notes and text boxes, story by story (#2289). That is `Pos` order, which Next and Previous -/// rely on. Comment text itself isn't scanned. +/// Comments in reading order with their anchors: every story the reader sees, in +/// `Document::shown_stories` order (#2289). A comment anchored only in a story nothing shows +/// (a removed header) has no anchor. pub fn comment_list(s: &Session) -> Vec<(u32, Option)> { let mut found: Vec<(u32, Option)> = Vec::new(); - let stories = super::tools::all_stories(&s.doc).into_iter().filter(|st| match st { - StoryRef::Body => true, - StoryRef::Part(id) => s.doc.parts.get(id).is_some_and(|p| p.kind != wordcraft_doc::PartKind::Comment), - }); - for story in stories { + // The balloons ask every frame; most documents have no comments. + if s.doc.comments.is_empty() { + return found; + } + for story in s.doc.shown_stories() { for path in s.doc.para_paths(story) { let Some(p) = s.doc.para(story, &path) else { continue }; for o in p.object_offsets() { @@ -330,8 +330,16 @@ fn list_comments(s: &mut Session, _: &Value) -> CmdResult { fn nav_comment(s: &mut Session, dir: i32) -> CmdResult { let list: Vec = comment_list(s).into_iter().filter_map(|(_, p)| p).collect(); - let caret = s.sel.focus.clone(); - let t = if dir > 0 { list.iter().find(|p| **p > caret).or(list.first()) } else { list.iter().rev().find(|p| **p < caret).or(list.last()) }; + // Reading order: the story's place in `shown_stories`, then the place in the story. Part ids + // follow creation, not reading, so `Pos` order won't do across stories. + let rank: BTreeMap = s.doc.shown_stories().into_iter().enumerate().map(|(i, st)| (st, i)).collect(); + let key = |p: &Pos| rank.get(&p.story).map(|i| (*i, p.path.clone(), p.off)); + let caret = key(&s.sel.focus); + let t = if dir > 0 { + list.iter().find(|p| caret.is_some() && key(p) > caret).or(list.first()) + } else { + list.iter().rev().find(|p| caret.is_some() && key(p) < caret).or(list.last()) + }; if let Some(p) = t { s.sel = Selection::caret(p.clone()); } @@ -1755,69 +1763,121 @@ mod tests { assert!(started.elapsed() < std::time::Duration::from_secs(5), "took {:?}", started.elapsed()); } - /// A body comment, then comments in a header, a footer and a text box, each on its story's - /// whole text. Returns their ids and stories in that order. + /// A comment on the whole of the story the caret is in; its id and that story. + fn comment_here(s: &mut Session, text: &str) -> (u64, wordcraft_doc::StoryRef) { + let story = s.sel.focus.story; + s.run("select.all", &json!({})).unwrap(); + (s.run("review.newComment", &json!({"text": text})).unwrap()["id"].as_u64().unwrap(), story) + } + + /// Comments in a header, a footer, a text box and a footnote, made in that order, then one + /// on a word of the body. Returns each comment's id and story in reading order, which isn't + /// the order they were made: the body, the text box and footnote it shows, then the header + /// and the footer. fn comments_in_every_story(s: &mut Session) -> Vec<(u64, wordcraft_doc::StoryRef)> { - use wordcraft_doc::StoryRef; s.view.proofing = false; s.run("document.setText", &json!({"text": "Body text with a comment on it."})).unwrap(); - s.run("select.text", &json!({"text": "comment"})).unwrap(); - let mut out = vec![(s.run("review.newComment", &json!({"text": "body"})).unwrap()["id"].as_u64().unwrap(), StoryRef::Body)]; - for (cmd, text) in [("insert.header", "header"), ("insert.footer", "footer")] { - s.run("insert.closeHeader", &json!({})).unwrap(); - let story = StoryRef::Part(s.run(cmd, &json!({"text": text})).unwrap()["story"].as_u64().unwrap() as u32); - s.run("select.all", &json!({})).unwrap(); - out.push((s.run("review.newComment", &json!({"text": text})).unwrap()["id"].as_u64().unwrap(), story)); - } + s.run("insert.header", &json!({"text": "header"})).unwrap(); + let header = comment_here(s, "header"); + s.run("insert.closeHeader", &json!({})).unwrap(); + s.run("insert.footer", &json!({"text": "footer"})).unwrap(); + let footer = comment_here(s, "footer"); s.run("insert.closeHeader", &json!({})).unwrap(); s.run("caret.docEnd", &json!({})).unwrap(); s.run("insert.textBox", &json!({"text": "boxed"})).unwrap(); - let story = s.sel.focus.story; - s.run("select.all", &json!({})).unwrap(); - out.push((s.run("review.newComment", &json!({"text": "text box"})).unwrap()["id"].as_u64().unwrap(), story)); + let text_box = comment_here(s, "text box"); + s.run("insert.closeHeader", &json!({})).unwrap(); + s.run("caret.docEnd", &json!({})).unwrap(); + s.run("references.footnote", &json!({"text": "noted"})).unwrap(); + let note = comment_here(s, "footnote"); + s.run("insert.closeHeader", &json!({})).unwrap(); + s.run("select.text", &json!({"text": "comment"})).unwrap(); + let body = (s.run("review.newComment", &json!({"text": "body"})).unwrap()["id"].as_u64().unwrap(), wordcraft_doc::StoryRef::Body); s.run("caret.docStart", &json!({})).unwrap(); - out + vec![body, text_box, note, header, footer] } - /// #2289: comments in headers, footers and text boxes are listed with their anchors, after - /// the body's, rather than with no anchor. + /// `review.comments` as (id, the anchor's story). + fn listed(s: &mut Session) -> Vec<(u64, Option)> { + let list = s.run("review.comments", &json!({})).unwrap(); + list.as_array().unwrap().iter().map(|c| (c["id"].as_u64().unwrap(), serde_json::from_value(c["anchor"]["story"].clone()).ok())).collect() + } + + /// The stories Next Comment visits, `n` presses from the caret. + fn next_stories(s: &mut Session, n: usize) -> Vec { + (0..n) + .map(|_| { + s.run("review.nextComment", &json!({})).unwrap(); + s.sel.focus.story + }) + .collect() + } + + /// #2289: comments in headers, footers, text boxes and footnotes are listed with their + /// anchors, in reading order rather than the order their stories were made. #[test] fn comments_outside_the_body_are_listed_with_their_anchors() { let mut s = Session::new(Document::new()); - let want = comments_in_every_story(&mut s); - let listed = s.run("review.comments", &json!({})).unwrap(); - let got: Vec<(u64, wordcraft_doc::StoryRef)> = listed - .as_array() - .unwrap() - .iter() - .map(|c| (c["id"].as_u64().unwrap(), serde_json::from_value(c["anchor"]["story"].clone()).expect("every comment has an anchor"))) - .collect(); - assert_eq!(got, want); + let want: Vec<_> = comments_in_every_story(&mut s).into_iter().map(|(id, st)| (id, Some(st))).collect(); + assert_eq!(listed(&mut s), want); } - /// #2289: Next and Previous Comment go through every story's comments and wrap around. + /// #2289: Next and Previous Comment go through every story's comments in reading order and + /// wrap around. #[test] fn next_and_previous_comment_reach_comments_outside_the_body() { let mut s = Session::new(Document::new()); - let want: Vec = comments_in_every_story(&mut s).into_iter().map(|(_, st)| st).collect(); - let mut forward = Vec::new(); - for _ in 0..=want.len() { - s.run("review.nextComment", &json!({})).unwrap(); - forward.push(s.sel.focus.story); - } + let want: Vec<_> = comments_in_every_story(&mut s).into_iter().map(|(_, st)| st).collect(); let mut wrapped = want.clone(); wrapped.push(want[0]); - assert_eq!(forward, wrapped, "Next visits each story's comment, then wraps to the first"); - let mut back = Vec::new(); - for _ in 0..want.len() { - s.run("review.previousComment", &json!({})).unwrap(); - back.push(s.sel.focus.story); - } + assert_eq!(next_stories(&mut s, want.len() + 1), wrapped, "Next visits each comment, then wraps to the first"); + let back: Vec<_> = (0..want.len()) + .map(|_| { + s.run("review.previousComment", &json!({})).unwrap(); + s.sel.focus.story + }) + .collect(); let mut rev = want.clone(); rev.reverse(); assert_eq!(back, rev, "Previous from the first comment wraps to the last"); } + /// #2289: a header comes before the footer whichever was made first. + #[test] + fn next_comment_reaches_the_header_before_the_footer() { + let mut s = Session::new(Document::new()); + s.view.proofing = false; + s.run("document.setText", &json!({"text": "Body"})).unwrap(); + s.run("insert.footer", &json!({"text": "footer"})).unwrap(); + let footer = comment_here(&mut s, "footer").1; + s.run("insert.closeHeader", &json!({})).unwrap(); + s.run("insert.header", &json!({"text": "header"})).unwrap(); + let header = comment_here(&mut s, "header").1; + s.run("insert.closeHeader", &json!({})).unwrap(); + assert_eq!(next_stories(&mut s, 2), vec![header, footer]); + } + + /// #2289: a comment left in a story nothing shows any more (a removed header, a deleted + /// footnote's text) has no anchor, and Next never takes the caret into that hidden story. + #[test] + fn comments_in_hidden_stories_are_not_navigated_to() { + let mut s = Session::new(Document::new()); + let all = comments_in_every_story(&mut s); + let (note, header) = (all[2], all[3]); + s.run("insert.removeHeader", &json!({})).unwrap(); + s.run("caret.docEnd", &json!({})).unwrap(); + s.run("text.backspace", &json!({})).unwrap(); + assert!(!s.doc.shown_stories().contains(¬e.1), "the footnote's reference is gone"); + let list = listed(&mut s); + for hidden in [note, header] { + assert!(list.contains(&(hidden.0, None)), "comment {} sits in a hidden story: {list:?}", hidden.0); + } + let shown = [all[0].1, all[1].1, all[4].1]; + for st in next_stories(&mut s, all.len()) { + assert!(shown.contains(&st), "Next went to hidden {st:?}"); + } + } + /// Text inside a comment isn't an anchor: a comment marker in a comment's own story is not /// listed as that comment's place in the document. #[test] diff --git a/crates/engine/src/cmd/tools.rs b/crates/engine/src/cmd/tools.rs index 76243df5b7..b02408dd21 100644 --- a/crates/engine/src/cmd/tools.rs +++ b/crates/engine/src/cmd/tools.rs @@ -1586,7 +1586,7 @@ impl Hidden { } /// Every story of the document: the body, then headers, footers, notes, comments and text boxes. -pub(crate) fn all_stories(doc: &Document) -> Vec { +fn all_stories(doc: &Document) -> Vec { std::iter::once(StoryRef::Body).chain(doc.parts.keys().map(|k| StoryRef::Part(*k))).collect() } From 54d89d563b139434f942cd76282d98dcdacdef5a Mon Sep 17 00:00:00 2001 From: Nghiathan13 Date: Mon, 19 Oct 2026 02:36:39 +0700 Subject: [PATCH 086/336] #2964: align list labels with text after leading page breaks --- ROADMAP.md | 4 ++- crates/layout/src/para.rs | 26 ++++++++++-------- crates/layout/src/tests_bidi.rs | 48 +++++++++++++++++++++++++++++++++ docs/gaps.md | 5 ++-- docs/layout-parity.md | 4 ++- 5 files changed, 72 insertions(+), 15 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 39675b4fe2..4c02797002 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **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 (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-19 · **Change:** trivial (centred list labels after leading page breaks, #2964; 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-17 · **Change:** 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. @@ -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-19 | Centred numbered headings after a leading page break keep their number before the text (#2964). The label uses the alignment of the line it is drawn on; heading text positions and page count stay the same. | | 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. | | 2026-10-14 | Tab stops edited on the ruler: the corner selector picks Left/Center/Right/Decimal/Bar, a click sets a stop (Alt skips the snap), dragging moves it with a guide line and position tooltip, dragging off the ruler clears it; markers draw per kind and mirror for RTL; one `para.tabs` call and one undo step per gesture | @@ -500,6 +501,7 @@ Footer page-table height and body wrapping are corrected. Other pagination gaps | Date | Change | Summary | |---|---|---| +| 2026-10-19 | trivial | #2964: a centred or end-aligned list label after a leading page break uses its text line's alignment. Synthetic LTR/RTL numbered/bulleted controls and the issue's DOCX export; broader pagination gaps and readiness estimates unchanged | | 2026-10-17 | trivial | Progress log: recipient list search and merged-only view (#1576) | | 2026-10-18 | trivial | Progress log: symbols keep their font when the text font changes (#2036) | | 2026-10-17 | trivial | Progress log: Restrict Editing guards tables, Paste Formatting and merge fields (#1327) | diff --git a/crates/layout/src/para.rs b/crates/layout/src/para.rs index 7c904a96cb..f80ce34ff0 100644 --- a/crates/layout/src/para.rs +++ b/crates/layout/src/para.rs @@ -286,7 +286,7 @@ impl Line { } } -/// The list label drawn on the first line. +/// The list label drawn on the first text line (after an opening page break, if any). #[derive(Clone, Debug)] pub struct Label { pub text: String, @@ -1934,6 +1934,9 @@ fn break_lines(pl: &mut ParaLayout, env: &ParaEnv, mark_style: u32, level: Optio let mut first_x0: Option = None; let mut hcache: Vec<(u32, u32, f32)> = Vec::new(); let mut lines: Vec = Vec::new(); + // The label is drawn on line 0 or, after an opening page break, line 1. Apply that + // line's alignment once both lines are known, before mirroring right-to-left labels. + let mut label_shifts = [0.0; 2]; let mut i = 0usize; let mut top = 0.0f32; let mut first = true; @@ -2362,14 +2365,11 @@ fn break_lines(pl: &mut ParaLayout, env: &ParaEnv, mark_style: u32, level: Optio for v in xs.iter_mut().skip(align_from - c0) { *v += shift; } - // The list label belongs to the start of the line: it moves with the text (Word keeps - // the bullet attached when a list item is centred or aligned to the end). After a tab - // stop in the line the label stays, as the text before the tab does. - if first - && last_tab.is_none() - && let Some(lab) = pl.label.as_mut() + // After a tab the label stays, as the text before the tab does. + if last_tab.is_none() + && let Some(label_shift) = label_shifts.get_mut(lines.len()) { - lab.x += shift; + *label_shift = shift; } } let justify = display.is_none() && ((rp.align == Align::Justify && end == LineEnd::Wrap) || rp.align == Align::Distribute); @@ -2509,11 +2509,15 @@ fn break_lines(pl: &mut ParaLayout, env: &ParaEnv, mark_style: u32, level: Optio } } } - if rtl_para && let Some(lab) = pl.label.as_mut() { - lab.x = width - lab.x - lab.width; - } pl.height = top; pl.lines = lines; + let label_shift = label_shifts.get(pl.label_line()).copied().unwrap_or(0.0); + if let Some(lab) = pl.label.as_mut() { + lab.x += label_shift; + if rtl_para { + lab.x = width - lab.x - lab.width; + } + } } /// Make a row's lines as tall as its tallest, and return that height. diff --git a/crates/layout/src/tests_bidi.rs b/crates/layout/src/tests_bidi.rs index 5d20141f6f..7e2dfceadb 100644 --- a/crates/layout/src/tests_bidi.rs +++ b/crates/layout/src/tests_bidi.rs @@ -561,3 +561,51 @@ fn list_label_moves_with_its_text_when_the_paragraph_is_aligned() { } } } + +#[test] +fn list_label_after_leading_page_break_uses_its_text_lines_alignment() { + // #2964: the number moved to page 2, but kept the centring of the empty break line. + // Adding the break must leave both the label and text at the same horizontal positions. + for (rtl, heading) in [(false, "SUBJECT OF THE AGREEMENT"), (true, "عنوان القسم")] { + for kind in [wordcraft_doc::ListKind::Numbered, wordcraft_doc::ListKind::Bullet] { + for align in [Align::Left, Align::Center, Align::Right, Align::Justify] { + for tab in [false, true] { + let text = if tab { format!("{heading}\tTail") } else { heading.into() }; + let make = |page_break: bool| { + let contents = if page_break { format!("\u{000C}{text}") } else { text.clone() }; + let mut d = doc_of(&[("Before", false), (&contents, rtl)]); + let id = d.numbering.add_list(kind); + for a in &mut d.numbering.abstracts { + let level = a.levels.first_mut().unwrap(); + level.start = 2; + level.indent = 14.2; + level.hanging = 14.2; + } + let at = Pos::body(1, 0); + d.format_paragraphs(&at, &at, &|p| { + p.numbering = Some(wordcraft_doc::props::NumRef { num: id, level: 0 }); + p.align = Some(align); + }) + .unwrap(); + lay(&d) + }; + let without = make(false); + let with = make(true); + let (plain, plain_x) = first_line(&without, 1); + let (broken, broken_x) = first_line(&with, 1); + let plain_label = plain.label.as_ref().unwrap(); + let broken_label = broken.label.as_ref().unwrap(); + assert_eq!(broken.label_line(), 1); + assert_eq!(with.pages.len(), 2); + assert!(with.pages[1].items.iter().any(|item| matches!(item, + Placed::Lines { path, l0, l1, .. } if path.0 == vec![1] && *l0 <= 1 && *l1 > 1 + ))); + let label_delta = broken_x + broken_label.x - plain_x - plain_label.x; + assert!(label_delta.abs() < 0.01, "rtl={rtl}, kind={kind:?}, align={align:?}, tab={tab}: label moved {label_delta}"); + let text_delta = caret_x(&with, 1, 1) - caret_x(&without, 1, 0); + assert!(text_delta.abs() < 0.01, "rtl={rtl}, kind={kind:?}, align={align:?}, tab={tab}: text moved {text_delta}"); + } + } + } + } +} diff --git a/docs/gaps.md b/docs/gaps.md index 88f0cbeee1..3abca8943e 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -1,6 +1,6 @@ # Where WordCraft falls short of Word -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** 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 and Convert Text to Table follow the Word check, #2592; previously trivial: Language dialog keeps inherited proofing language and no-proof choices (#2717); previously trivial: #29: a copied equation also goes in the MathML clipboard formats, #1297; previously trivial: #24: the Spike, Ctrl+F3 and Ctrl+Shift+F3; previously trivial: VML freeforms read and kept on save; previously trivial: 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: floating-table overlap ignores empty line-break lines; previously trivial: copies keep their formatting on the clipboard as HTML, #1523; previously trivial: sample-text formulas on Enter, #1067; previously trivial: #29: copied equations as MathML, OMML open as #1297, #816; previously trivial: the Font list no longer keeps every previewed font in memory, #848; 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 Table and Eraser landed, #303; previously minor: beta distance restated: ~15 points, ~150–230 h; previously: first version; every known shortfall from the 2026-10-10 re-measure, ranked) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-19 · **Change:** trivial (centred list labels after leading page breaks, #2964; 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 and Convert Text to Table follow the Word check, #2592; previously trivial: Language dialog keeps inherited proofing language and no-proof choices (#2717); previously trivial: #29: a copied equation also goes in the MathML clipboard formats, #1297; previously trivial: #24: the Spike, Ctrl+F3 and Ctrl+Shift+F3; previously trivial: VML freeforms read and kept on save; previously trivial: 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: floating-table overlap ignores empty line-break lines; previously trivial: copies keep their formatting on the clipboard as HTML, #1523; previously trivial: sample-text formulas on Enter, #1067; previously trivial: #29: copied equations as MathML, OMML open as #1297, #816; previously trivial: the Font list no longer keeps every previewed font in memory, #848; 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 Table and Eraser landed, #303; previously minor: beta distance restated: ~15 points, ~150–230 h; previously: first version; every known shortfall from the 2026-10-10 re-measure, ranked) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 > **Printing update:** 2026-10-12 · direct desktop printing through craft-print; details below. @@ -36,7 +36,7 @@ Active footnote and endnote text stays readable while editing (#2545). This fixe |---|---|---|---|---|---|---| | 1 | **DOCX never tested on real-world files.** No corpus of files from Word, Google Docs, LibreOffice and Pages has been opened, rendered and round-tripped and compared with Word page by page | B | No corpus in the repo; `crates/docx/tests` are synthetic fixtures (including omitted/empty paragraph defaults and imported TOC range preservation); "DOCX fidelity corpus: not started" since 2026-10-06. Field reports fixed one by one: a cover page's theme-coloured shapes (#611), freeforms inside groups and text-box margins and Resize to fit (#743), cover-page positions relative to the page and freeforms drawn back to their start (#743), Word's text watermark in headers, read and written (#1290), VML freeform rule lines in headers, and `flowChartOr` drawn with its spokes (#1644); explicit DrawingML outline dashes preserved | Everyone: the files people receive are the files that break | 30–50 | [file-format-parity.md](file-format-parity.md) | | 3 | **Crashes and freezes in the field**: startup crash on Intel UHD Graphics (#170, open), a crash on macOS 12.7.6 Intel (#979, open) (lag on GPUs without a DirectX 12 driver fixed, #316; 32-bit Windows exited at launch because DirectX 12 graphics pipeline creation failed, now OpenGL), freezes (#59 Thai fonts and #31 font selector, both open; #77 closed), broken shape tool (#142, open); the Windows 11 no-write-without-admin (#218) and unmovable images (#82) reports are closed (pictures and shapes in Behind Text drag through the text over them, #1313); memory growth on every font change fixed (#848: the font list no longer loads each family it previews) | B | 197 open issues (2026-10-12), among them the crash and freeze reports #170, #979, #59 and #31 | Anyone hit loses work or trust; the never-crash standard covers panics, not hangs or GPU driver faults | 20–30 | [target-app-parity.md](target-app-parity.md) (Stability) | -| 4 | **Pagination differs from Word**: Aptos has no metric-matched substitute; legacy compatibility options only partially covered; no page-by-page comparison harness | B | SECTIONPAGES now counts shared continuous-section pages in body and header/footer fields; legacy same-style cell spacing preserved for measured modes 11/12/14, with mode 15 and non-cell controls; Contextual spacing corrected in body/header probes and repeated table-cell runs; [layout-parity.md](layout-parity.md); [typing-parity.md](typing-parity.md) Known gaps; non-overlapping body floating tables clear header tables; stray labels on page-break-only list paragraphs fixed multi-paragraph field-code blank lines fixed; Arial Unicode MS no longer selects monospace substitutes | Page counts and line breaks differ, which matters for forms, legal and academic work | 30–50 + owner (font) | [layout-parity.md](layout-parity.md) | +| 4 | **Pagination differs from Word**: Aptos has no metric-matched substitute; legacy compatibility options only partially covered; no page-by-page comparison harness | B | SECTIONPAGES now counts shared continuous-section pages in body and header/footer fields; legacy same-style cell spacing preserved for measured modes 11/12/14, with mode 15 and non-cell controls; Contextual spacing corrected in body/header probes and repeated table-cell runs; [layout-parity.md](layout-parity.md); [typing-parity.md](typing-parity.md) Known gaps; non-overlapping body floating tables clear header tables; stray labels on page-break-only list paragraphs fixed multi-paragraph field-code blank lines fixed; centred numbered heading labels after leading page breaks fixed (#2964); Arial Unicode MS no longer selects monospace substitutes | Page counts and line breaks differ, which matters for forms, legal and academic work | 30–50 + owner (font) | [layout-parity.md](layout-parity.md) | | 2 | **Word's native ink is not drawn from its strokes**; OLE objects show only their picture | B | Every native ink form is preserved: drawing-level ink with its optional picture fallback, direct run-level content parts, and ink members of groups and drawing canvases (kept in their place among the members, drawn from their fallback picture). Drawing canvases are read and written (#344); group shapes (`wpg`) read and written (#267). Charts and SmartArt are drawn (#292) and, with OLE objects, written back with all their parts on save (#319) | Pen notes from Word show only as their picture and can't be edited; OLE objects can't be opened | 45–70 to render and edit charts and SmartArt (#13) | [file-format-parity.md](file-format-parity.md) | | 5 | **Content controls** landed (#381): `w:sdt` kept, shown and edited (Developer tab, Design Mode, Properties). Move tracking landed (#400): `w:moveFrom`/`w:moveTo` with their named ranges round-trip, a tracked cut and paste of a paragraph or sentence records a move, either end accepts/rejects both, green double strike/underline markup and "Moved from/to" balloons; Track Changes Options has the Moves rows (Moved from / Moved to mark and colour) and `w:doNotTrackMoves` (Track moves off: a cut and paste is a deletion and an insertion) round-trips; still missing: drag-and-drop moves (no text drag yet). Formatting revisions landed (#41): `w:rPrChange`/`w:pPrChange`/`w:tblPrChange`/`w:trPrChange`/`w:tcPrChange`/`w:sectPrChange`/`w:numberingChange` round-trip, are recorded while tracking, accepted/rejected and shown in balloons; still missing: tracked style-definition changes and table/section changes made in WordCraft. Opaque custom XML backing stores now survive saves; binding evaluation remains absent. Replace All now checks content-control locks before editing any match; a refused batch preserves the document and undo history. | B | List-label pitch corrected in synthetic metric controls; Issues #41, #400; source grep | Legal and editorial review workflows; templates with form controls | 5–8 | [file-format-parity.md](file-format-parity.md) | | 6 | **Print dialog depth**: desktop printer choice, copies, collation, duplex and grayscale submit through craft-print; File › Print retains preview, page ranges and odd/even (#641) and scales pages to another paper size (#2108); driver tray/options and pages per sheet remain; web prints through the browser (#209) | B | `ui.print` → `apps/wordcraft/src/print.rs`; issue #15 | Everyone who prints | 5–8 | [hardware-parity.md](hardware-parity.md) | @@ -105,6 +105,7 @@ Footer page-table height and body wrapping are corrected. Other pagination gaps | Date | Change | Summary | |---|---|---| +| 2026-10-19 | trivial | #2964: a centred or end-aligned list label after a leading page break uses its text line's alignment. Synthetic LTR/RTL numbered/bulleted controls and the issue's DOCX export; broader pagination gaps and readiness estimates unchanged | | 2026-10-17 | trivial | #1327: Restrict Editing exceptions no longer let table, row or cell formatting change protected tables; formatting-only protection refuses Paste Formatting, table shading/borders/alignment/margins/text direction, table-style edits and locked table styles; recipient-list commands no longer rewrite merge-field results in a protected document. No gap row (enforcement fix); estimates unchanged | | 2026-10-17 | trivial | #21: Find in list and "Show only records that will be merged" in Edit Recipient List (#1576) | | 2026-10-18 | trivial | #15 narrowed: Word's `Sources.xml` bibliography file imports into Manage Sources (#2640) | diff --git a/docs/layout-parity.md b/docs/layout-parity.md index 0835e73e3d..c052a43969 100644 --- a/docs/layout-parity.md +++ b/docs/layout-parity.md @@ -1,6 +1,6 @@ # Layout and pagination parity with Microsoft Word -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-17 · **Change:** 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 proportional fallbacks; previously trivial: DrawingML outline dashes, including zero-width paths; previously trivial: TOCs include table-cell headings and retain full targets; previously trivial: kept table rows follow the next row; previously trivial: consistent cell widths override a stale table grid; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: page-break-only list paragraphs hide their labels; previously trivial: floating-table overlap ignores empty line-break lines; previously trivial: paragraph spacing adds up when the document turns HTML auto spacing off; previously trivial: an inherited footer sizes its percentage tables by its own section; previously trivial: an empty section mark stays on a full page; previously trivial: a fitting row with less room than its minimum height moves on whole; oversized rows still split; previously trivial: text past an exact-height row is cut off; previously trivial: a list number stays with text after a leading page break; previously trivial: a bookmark after a page break takes no line; previously trivial: a page break followed by a page break before or a next-page section starts one page; previously trivial: All Caps and Small Caps draw ß as SS, #1581; previously trivial: justified vertical page alignment, #1320; previously trivial: page numbers restart at 0; previously trivial: a list label moves with its first line when the paragraph is centred or end-aligned (#1159); previously trivial: PAGE fields in a paragraph split across pages show the page they are on, #1352; previously trivial: a dragged floating object anchors in the column it lands in, #884; previously trivial: a paragraph ending on a page with footnotes keeps its space after above the separator, #1116; previously trivial: symbol-font text drawn as Unicode symbols, #776; previously trivial: large-table scrolling measured and fixed, #811; previously trivial: text boxes use their own margins; Resize shape to fit text never cuts text, #743; previously trivial: complex-script shaping and Myanmar line breaking, #604; previously trivial: body page totals, table-cell footnotes and bookmark link pages fixed, #450 #451 #455; previously major: first version; line breaking, pagination and object placement checked against Word's behaviour from the layout source on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-19 · **Change:** trivial (centred list labels after leading page breaks, #2964; 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 proportional fallbacks; previously trivial: DrawingML outline dashes, including zero-width paths; previously trivial: TOCs include table-cell headings and retain full targets; previously trivial: kept table rows follow the next row; previously trivial: consistent cell widths override a stale table grid; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: page-break-only list paragraphs hide their labels; previously trivial: floating-table overlap ignores empty line-break lines; previously trivial: paragraph spacing adds up when the document turns HTML auto spacing off; previously trivial: an inherited footer sizes its percentage tables by its own section; previously trivial: an empty section mark stays on a full page; previously trivial: a fitting row with less room than its minimum height moves on whole; oversized rows still split; previously trivial: text past an exact-height row is cut off; previously trivial: a list number stays with text after a leading page break; previously trivial: a bookmark after a page break takes no line; previously trivial: a page break followed by a page break before or a next-page section starts one page; previously trivial: All Caps and Small Caps draw ß as SS, #1581; previously trivial: justified vertical page alignment, #1320; previously trivial: page numbers restart at 0; previously trivial: a list label moves with its first line when the paragraph is centred or end-aligned (#1159); previously trivial: PAGE fields in a paragraph split across pages show the page they are on, #1352; previously trivial: a dragged floating object anchors in the column it lands in, #884; previously trivial: a paragraph ending on a page with footnotes keeps its space after above the separator, #1116; previously trivial: symbol-font text drawn as Unicode symbols, #776; previously trivial: large-table scrolling measured and fixed, #811; previously trivial: text boxes use their own margins; Resize shape to fit text never cuts text, #743; previously trivial: complex-script shaping and Myanmar line breaking, #604; previously trivial: body page totals, table-cell footnotes and bookmark link pages fixed, #450 #451 #455; previously major: first version; line breaking, pagination and object placement checked against Word's behaviour from the layout source on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 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, @@ -52,6 +52,7 @@ Exact line spacing keeps the run font baseline around tall equations; automatic | Columns, column breaks, separators, balancing | ✅ | Columns balance on a section's last page when the next section starts continuous (also at the document end, which Word only balances when the document ends with a continuous break); not after next-page/even/odd breaks or on a page with a column break; keep-together, keep-with-next, widow/orphan control and unsplittable rows can leave them slightly uneven (#350) | — | | Section breaks (next page, continuous, even, odd), different first page, odd/even headers | ✅ | Section breaks don't show in Draft view (#42). A break inserted with text selected goes before the selection and keeps it, as in Word (#852). An otherwise empty section at page start retains its line (`empty_page_section_keeps_line`); a closing mark after text stays quiet. | 1–2 | | Labels on page-break-only list paragraphs | ✅ | `layout::tests::break_only_list_has_no_label`: no bullet or number on the break, but the next number is still 3 after item 1 and a break. Plain empty items keep their labels. Synthetic DOCX observed through Adobe PDF conversion | — | +| List labels after a leading page break | ✅ | `layout::tests_bidi::list_label_after_leading_page_break_uses_its_text_lines_alignment`: the number/bullet and text retain the no-break horizontal positions for start, centre, end and justified alignment, LTR/RTL, including text with a tab. The issue's synthetic DOCX exports with the number before the heading, matching the reporter's Word/LibreOffice reference (#2964); reference applications were not rerun locally | — | | List labels: number formats (decimal, letters, Russian letters, roman, ordinal text, Chinese/Japanese/Korean counting and legal, 一〇一 digits, 甲乙丙, ①…⑳, full-width), `%n` text such as `第%1章` / `(%1)`, legal numbering | ✅ | `doc::cjk_numbers` tests, `doc::section::tests::russian_letters_follow_word`, `layout::tests::chinese_counting_list_labels_and_their_font_runs`, `layout::tests::russian_letter_list_labels_draw_cyrillic`; a label mixing faces (digit + CJK comma) is drawn run by run (#534); Russian letters skip ё, й, ъ and ь and then double (аа, бб), as observed in Word. Hebrew, Arabic, Hindi, Thai and Vietnamese formats show decimal The label stays attached to the start of the text when the paragraph is centred or aligned to the end, as in Word (#1159). | — | | Body top below a tall header, footer pushes body up | ✅ | #138; a footer taller than the bottom margin moves the body bottom up (#453), and a header or footer linked to the previous section takes its room too (#454) | — | | Tables: row border exceptions (`w:tblPrEx/w:tblBorders`) | ✅ | `row_borders_stay_on_row`, `row_nil_overrides_style`: explicit cell borders win; row exceptions precede table styles and table borders | — | @@ -212,6 +213,7 @@ Page-relative footer tables keep their physical wrap bounds without adding to fo | Date | Change | Summary | |---|---|---| +| 2026-10-19 | trivial | #2964: a centred or end-aligned list label after a leading page break uses its text line's alignment. Synthetic LTR/RTL numbered/bulleted controls and the issue's DOCX export; broader pagination gaps and readiness estimates unchanged | | 2026-10-15 | trivial | An at-least table row with less room than its minimum on a page that holds something starts the next page whole, as checked in Word (#1742); the taller-than-a-page case (it then splits from the top of that page) and repeated header rows (they go with it) follow the issue and synthetic tests, not a Word check | | 2026-10-16 | trivial | Table row keep-next uses the first paragraph of the first cell, including inherited flags. Kept row chains start on a fresh page when the following row cannot start below them. Synthetic Word export covers mixed-cell flags, multiple paragraphs and kept prefixes before oversized followers. | | 2026-10-16 | trivial | Include table-cell headings in regenerated TOCs with full target paths | From ec26a573bef82c15932d82581b236207e9ea6c49 Mon Sep 17 00:00:00 2001 From: Nghiathan13 Date: Mon, 19 Oct 2026 02:43:16 +0700 Subject: [PATCH 087/336] #2959: show the mini toolbar with text context menus --- ROADMAP.md | 4 +- crates/ui-egui/src/canvas.rs | 52 ++++++--- crates/ui-egui/src/mini_toolbar.rs | 165 +++++++++++++++++++++++++++++ docs/gaps.md | 5 +- docs/ui-parity.md | 5 +- 5 files changed, 211 insertions(+), 20 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 39675b4fe2..c194ffa3b8 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **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 (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-19 · **Change:** trivial (progress log: formatting toolbar with the text context menu, #2959; 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-17 · **Change:** 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. @@ -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-19 | Right-clicking text or pressing Shift+F10 offers the existing two-row formatting mini toolbar above the context-menu commands. Formatting keeps the selection and popup; menu commands, Escape and outside clicks dismiss it. This is the mini-toolbar portion of #2959; paste choices are in PR #2599. | | 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. | | 2026-10-14 | Tab stops edited on the ruler: the corner selector picks Left/Center/Right/Decimal/Bar, a click sets a stop (Alt skips the snap), dragging moves it with a guide line and position tooltip, dragging off the ruler clears it; markers draw per kind and mirror for RTL; one `para.tabs` call and one undo step per gesture | @@ -500,6 +501,7 @@ Footer page-table height and body wrapping are corrected. Other pagination gaps | Date | Change | Summary | |---|---|---| +| 2026-10-19 | trivial | Text context menus include the existing formatting mini toolbar (#2959); paste options remain in PR #2599. Readiness estimates unchanged. | | 2026-10-17 | trivial | Progress log: recipient list search and merged-only view (#1576) | | 2026-10-18 | trivial | Progress log: symbols keep their font when the text font changes (#2036) | | 2026-10-17 | trivial | Progress log: Restrict Editing guards tables, Paste Formatting and merge fields (#1327) | diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 5deb40a13f..ed2b77e654 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -1701,22 +1701,42 @@ pub fn show(app: &mut WordApp, ui: &mut Ui) { } else if secondary_clicked { app.canvas.context_menu_at = resp.interact_pointer_pos(); } - let open = if secondary_clicked || from_keys { - Some(egui::SetOpenCommand::Bool(true)) - } else if resp.clicked() { - Some(egui::SetOpenCommand::Bool(false)) - } else { - None - }; - let menu = egui::Popup::menu(&resp).open_memory(open); - let menu = match app.canvas.context_menu_at { - Some(p) => menu.at_position(p), - None => menu.at_pointer_fixed(), - }; - menu.show(|ui| context_menu(app, ui)); - // Stand down while the context menu owns the pointer so the two never double up; the open - // state also covers the click that dismisses the menu. - app.canvas.context_menu_open = egui::Popup::is_id_open(ui.ctx(), egui::Popup::default_response_id(&resp)); + // Keep the parent independently of egui's one memory popup: the toolbar's font, size and + // colour controls use that slot for their own lists (as in a collapsed ribbon group). + let mut open = app.canvas.context_menu_open; + if secondary_clicked || from_keys { + egui::Popup::close_all(ui.ctx()); + open = true; + } else if open && ui.input(|i| i.pointer.any_click()) { + let layer = ui.input(|i| i.pointer.interact_pos()).and_then(|p| ui.ctx().layer_id_at(p)); + if !layer.is_some_and(|l| matches!(l.order, egui::Order::Foreground | egui::Order::Tooltip)) { + open = false; + } + } + let child_open = egui::Popup::is_any_open(ui.ctx()); + let mut shown = open; + if open { + app.escape_free = false; + } + let menu = egui::Popup::menu(&resp).open_bool(&mut shown); + let menu = menu.at_position(app.canvas.context_menu_at.unwrap_or(out.inner_rect.left_top())); + let with_toolbar = crate::mini_toolbar::context_menu_visible(app); + let menu = if with_toolbar { menu.frame(egui::Frame::NONE).close_behavior(egui::PopupCloseBehavior::IgnoreClicks) } else { menu }; + menu.show(|ui| { + if with_toolbar { + crate::mini_toolbar::show_in_context_menu(app, ui); + ui.add_space(4.0); + egui::Frame::popup(ui.style()).show(ui, |ui| context_menu(app, ui)); + } else { + context_menu(app, ui); + } + }); + // Escape closes the child list first; a second Escape closes the context menu. + if open && !shown && child_open && ui.input(|i| i.key_pressed(egui::Key::Escape)) { + shown = true; + } + // The standalone selection toolbar stands down while the context popup owns the pointer. + app.canvas.context_menu_open = shown; if let Some(c) = crate::ink::cursor(app).filter(|_| resp.hovered() || app.canvas.ink.is_some()) { ui.ctx().set_cursor_icon(c); } else if resp.hovered() || app.canvas.obj_drag.is_some() { diff --git a/crates/ui-egui/src/mini_toolbar.rs b/crates/ui-egui/src/mini_toolbar.rs index 92749b2633..c5cf3a98bf 100644 --- a/crates/ui-egui/src/mini_toolbar.rs +++ b/crates/ui-egui/src/mini_toolbar.rs @@ -198,6 +198,35 @@ pub fn show(app: &mut WordApp, ctx: &egui::Context, selection: Rect, viewport: R }); } +/// A text context menu offers formatting at a caret too. Object and equation menus keep their +/// own controls; the standalone selection toolbar remains hidden while this popup is open. +pub fn context_menu_visible(app: &WordApp) -> bool { + app.ui.mini_toolbar + && !app.session.view.read_mode + && app.session.math.is_none() + && app.session.cell_range().is_none() + && !crate::ribbon::has_picture_selected(&app.session) + && wordcraft_engine::cmd::objects::object_selection(&app.session).is_none() +} + +/// The existing two-row toolbar in the context menu's popup, above its command panel. Sharing +/// the popup lets its font/size/colour lists stay open without dismissing the parent menu. +pub fn show_in_context_menu(app: &mut WordApp, ui: &mut egui::Ui) { + let t = Tokens::get(ui.ctx()); + egui::Frame::popup(ui.style()).inner_margin(egui::Margin::symmetric(4, 3)).show(ui, |ui| { + ui.push_id("context_mini_toolbar", |ui| { + // Menu styling removes input borders; use the same controls as the standalone bar. + ui.set_style(ui.ctx().global_style()); + ui.set_width(ROW2_W); + ui.spacing_mut().item_spacing = Vec2::new(SPACING, ROW_GAP); + content(app, ui, &t); + if app.dialog.is_some() || app.colors.is_some() { + ui.close(); + } + }); + }); +} + /// The controls in two rows like Word's mini toolbar, reusing the ribbon's widgets so the bar /// matches the ribbon exactly. fn content(app: &mut WordApp, ui: &mut egui::Ui, t: &Tokens) { @@ -510,6 +539,142 @@ mod tests { h.state_mut().session.run("format.state", &json!({})).unwrap() } + /// #2959: formatting must be available alongside the text's right-click menu, using the + /// selection that was clicked. Its lists must remain usable inside the context popup. + #[test] + fn context_menu_toolbar_formats_the_selection_and_opens_its_size_list() { + use egui::accesskit::Role; + use egui_kittest::kittest::Queryable; + let mut h = bar_over("brown fox"); + let selection = h.state().session.sel.clone(); + let caret = h.state_mut().session.layout().caret(&wordcraft_doc::Pos::body(0, 12)).unwrap(); + let at = crate::canvas::page_to_screen(h.state_mut(), caret.page, caret.x, caret.top + caret.height / 2.0).unwrap(); + 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(); + } + steps(&mut h); + assert!(h.state().canvas.context_menu_open); + assert_eq!(h.state().session.sel, selection, "right-click inside the selection keeps it"); + assert!(bar_layer(&h).is_none(), "the standalone selection toolbar must not double up"); + let viewport = h.state().canvas.viewport.unwrap(); + let bold = h.get_all_by_label("Bold").map(|n| n.rect()).find(|r| viewport.contains_rect(*r)).expect("the context menu has Bold"); + click_at(&mut h, bold.center()); + assert_eq!(format_state(&mut h).get("bold"), Some(&json!(true))); + assert_eq!(h.state().session.sel, selection); + assert!(h.state().canvas.context_menu_open, "formatting keeps the context toolbar available"); + + let size_box = h + .get_all_by_role(Role::TextInput) + .map(|n| n.rect()) + .filter(|r| viewport.contains_rect(*r)) + .max_by(|a, b| a.min.x.total_cmp(&b.min.x)) + .expect("the context toolbar has a size box"); + click_at(&mut h, pos2(size_box.max.x + 8.0, size_box.center().y)); + let twenty = h.get_by_label("20").rect().center(); + click_at(&mut h, twenty); + assert_eq!(format_state(&mut h).get("size").and_then(Value::as_f64), Some(20.0)); + assert_eq!(h.state().session.sel, selection); + assert!(h.state().canvas.context_menu_open, "picking a font size keeps the parent menu available"); + click_at(&mut h, pos2(size_box.max.x + 8.0, size_box.center().y)); + assert!(egui::Popup::is_any_open(&h.ctx), "the child list is open"); + h.event(egui::Event::Key { key: egui::Key::Escape, physical_key: None, pressed: true, repeat: false, modifiers: Default::default() }); + steps(&mut h); + assert!(!egui::Popup::is_any_open(&h.ctx), "Escape closes the child list"); + assert!(h.state().canvas.context_menu_open, "the parent toolbar remains after dismissing its list"); + h.state_mut().run("edit.undo", json!({})).unwrap(); + assert_ne!(format_state(&mut h).get("size").and_then(Value::as_f64), Some(20.0), "size is one undo step"); + assert_eq!(format_state(&mut h).get("bold"), Some(&json!(true)), "the earlier Bold is a separate undo step"); + let copy = h.query_all_by_label_contains("Copy").map(|n| n.rect()).find(|r| viewport.contains_rect(*r)).expect("the menu has Copy"); + click_at(&mut h, copy.center()); + assert!(!h.state().canvas.context_menu_open, "ordinary menu commands still close the popup"); + } + + #[test] + fn context_menu_toolbar_opens_at_a_caret_from_the_keyboard_and_dismisses() { + use egui_kittest::kittest::Queryable; + let mut h = bar_over("brown fox"); + h.state_mut().run("caret.docEnd", json!({})).unwrap(); + steps(&mut h); + let caret = h.state().session.sel.clone(); + h.event(egui::Event::Key { key: egui::Key::F10, physical_key: None, pressed: true, repeat: false, modifiers: egui::Modifiers::SHIFT }); + steps(&mut h); + let viewport = h.state().canvas.viewport.unwrap(); + assert!(h.state().canvas.context_menu_open); + let bold = + h.get_all_by_label("Bold").map(|n| n.rect()).find(|r| viewport.contains_rect(*r)).expect("the keyboard menu has Bold at a caret too"); + click_at(&mut h, bold.center()); + assert_eq!(format_state(&mut h).get("bold"), Some(&json!(true))); + assert_eq!(h.state().session.sel, caret); + h.event(egui::Event::Key { key: egui::Key::Escape, physical_key: None, pressed: true, repeat: false, modifiers: Default::default() }); + steps(&mut h); + assert!(!h.state().canvas.context_menu_open); + h.event(egui::Event::Text(" typed".into())); + steps(&mut h); + assert!(h.state().session.doc.plain_text(wordcraft_doc::StoryRef::Body).ends_with(" typed"), "typing resumes in the document"); + + h.state_mut().run("ui.contextMenu", json!({})).unwrap(); + steps(&mut h); + assert!(h.state().canvas.context_menu_open); + click_at(&mut h, viewport.right_bottom() - Vec2::splat(10.0)); + assert!(!h.state().canvas.context_menu_open, "an outside click dismisses both panels"); + } + + #[test] + fn context_menu_toolbar_fits_near_the_bottom_right_of_a_narrow_window() { + use egui_kittest::kittest::Queryable; + let mut h = bar_over("brown fox"); + h.set_size(Vec2::new(380.0, 650.0)); + steps(&mut h); + h.state_mut().run("ui.contextMenu", json!({})).unwrap(); + steps(&mut h); + h.state_mut().canvas.context_menu_at = Some(pos2(370.0, 640.0)); + steps(&mut h); + let screen = h.ctx.content_rect(); + let bold = h.get_all_by_label("Bold").map(|n| n.rect()).find(|r| r.min.y > 200.0).expect("the context toolbar is shown"); + assert!(screen.contains_rect(bold), "the toolbar stays inside the window: {bold:?} in {screen:?}"); + let copy = h.query_all_by_label_contains("Copy").map(|n| n.rect()).find(|r| r.min.y > 200.0).expect("the menu has Copy"); + assert!(screen.contains_rect(copy), "the menu stays inside the window: {copy:?} in {screen:?}"); + let align = h.get_all_by_label("Justify").map(|n| n.rect()).find(|r| r.min.y > 200.0).expect("the last toolbar control is shown"); + assert!(screen.contains_rect(align), "all controls fit: {align:?} in {screen:?}"); + } + + #[test] + fn context_menu_toolbar_respects_read_mode_and_the_toolbar_preference() { + use egui_kittest::kittest::Queryable; + for read_mode in [false, true] { + let mut h = bar_over("brown fox"); + h.state_mut().session.view.read_mode = read_mode; + h.state_mut().ui.mini_toolbar = read_mode; + h.state_mut().run("ui.contextMenu", json!({})).unwrap(); + steps(&mut h); + assert!(h.state().canvas.context_menu_open, "the text menu remains available"); + let top = h.state().canvas.viewport.unwrap().top(); + assert!(h.query_all_by_label("Bold").all(|n| n.rect().min.y < top), "the context toolbar is suppressed, read_mode={read_mode}"); + assert!(h.query_all_by_label_contains("Copy").any(|n| n.rect().min.y > top), "the menu still offers Copy"); + } + } + + #[test] + fn escape_in_a_ribbon_list_does_not_open_the_context_menu() { + use egui::accesskit::Role; + use egui_kittest::kittest::Queryable; + let mut h = bar_over("brown fox"); + let size = h + .get_all_by_role(Role::TextInput) + .filter(|n| n.value().as_deref() == Some("12")) + .map(|n| n.rect()) + .min_by(|a, b| a.min.y.total_cmp(&b.min.y)) + .expect("the ribbon has a size field"); + click_at(&mut h, pos2(size.max.x + 8.0, size.center().y)); + assert!(egui::Popup::is_any_open(&h.ctx)); + h.event(egui::Event::Key { key: egui::Key::Escape, physical_key: None, pressed: true, repeat: false, modifiers: Default::default() }); + steps(&mut h); + assert!(!egui::Popup::is_any_open(&h.ctx)); + assert!(!h.state().canvas.context_menu_open, "dismissing another popup must not open the text context menu"); + } + /// The bar was drawn on the Tooltip layer, over every drop-down: the ribbon's font size list /// opened under it. Popups and menus are on Foreground; the bar must sit below them. #[test] diff --git a/docs/gaps.md b/docs/gaps.md index 88f0cbeee1..fe071124c0 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -1,6 +1,6 @@ # Where WordCraft falls short of Word -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** 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 and Convert Text to Table follow the Word check, #2592; previously trivial: Language dialog keeps inherited proofing language and no-proof choices (#2717); previously trivial: #29: a copied equation also goes in the MathML clipboard formats, #1297; previously trivial: #24: the Spike, Ctrl+F3 and Ctrl+Shift+F3; previously trivial: VML freeforms read and kept on save; previously trivial: 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: floating-table overlap ignores empty line-break lines; previously trivial: copies keep their formatting on the clipboard as HTML, #1523; previously trivial: sample-text formulas on Enter, #1067; previously trivial: #29: copied equations as MathML, OMML open as #1297, #816; previously trivial: the Font list no longer keeps every previewed font in memory, #848; 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 Table and Eraser landed, #303; previously minor: beta distance restated: ~15 points, ~150–230 h; previously: first version; every known shortfall from the 2026-10-10 re-measure, ranked) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-19 · **Change:** trivial (text mini toolbar available with the right-click menu, #2959; 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 and Convert Text to Table follow the Word check, #2592; previously trivial: Language dialog keeps inherited proofing language and no-proof choices (#2717); previously trivial: #29: a copied equation also goes in the MathML clipboard formats, #1297; previously trivial: #24: the Spike, Ctrl+F3 and Ctrl+Shift+F3; previously trivial: VML freeforms read and kept on save; previously trivial: 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: floating-table overlap ignores empty line-break lines; previously trivial: copies keep their formatting on the clipboard as HTML, #1523; previously trivial: sample-text formulas on Enter, #1067; previously trivial: #29: copied equations as MathML, OMML open as #1297, #816; previously trivial: the Font list no longer keeps every previewed font in memory, #848; 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 Table and Eraser landed, #303; previously minor: beta distance restated: ~15 points, ~150–230 h; previously: first version; every known shortfall from the 2026-10-10 re-measure, ranked) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 > **Printing update:** 2026-10-12 · direct desktop printing through craft-print; details below. @@ -30,6 +30,8 @@ Legacy Word imports (#2445): interactive Save for `.doc`/`.dot` uses the existin Active footnote and endnote text stays readable while editing (#2545). This fixes a display-context defect; the broader fidelity, accessibility and readiness gates below are unchanged. +Text context menus now include the existing formatting mini toolbar (#2959), including at a caret. Object, equation and whole-cell menus keep their own controls; Read Mode and the mini-toolbar preference suppress it. The toolbar reuses the selection toolbar's commands; additional controls from Word's toolbar are not added. Context-menu paste choices remain tracked by #2583 / PR #2599. + ## Ranked | # | Gap | B | Evidence | Impact | Hours | Doc | @@ -105,6 +107,7 @@ Footer page-table height and body wrapping are corrected. Other pagination gaps | Date | Change | Summary | |---|---|---| +| 2026-10-19 | trivial | The text context menu includes the existing two-row formatting mini toolbar, at a caret or over selected text (#2959); formatting and its dropdowns preserve the selection, regular menu commands and dismissal still close the popup. Paste options remain in separate PR #2599. | | 2026-10-17 | trivial | #1327: Restrict Editing exceptions no longer let table, row or cell formatting change protected tables; formatting-only protection refuses Paste Formatting, table shading/borders/alignment/margins/text direction, table-style edits and locked table styles; recipient-list commands no longer rewrite merge-field results in a protected document. No gap row (enforcement fix); estimates unchanged | | 2026-10-17 | trivial | #21: Find in list and "Show only records that will be merged" in Edit Recipient List (#1576) | | 2026-10-18 | trivial | #15 narrowed: Word's `Sources.xml` bibliography file imports into Manage Sources (#2640) | diff --git a/docs/ui-parity.md b/docs/ui-parity.md index e3bd59f21b..32b4a962ac 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -1,6 +1,6 @@ # UI parity with Microsoft Word -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** 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-19 · **Change:** trivial (the text context menu includes the formatting mini toolbar, #2959; 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 @@ -14,7 +14,7 @@ Document title opens Rename Document; the account badge opens Options › Genera |---|---|---|---|---| | Ribbon tabs and groups | All Word tabs including contextual Table Design/Layout, Picture Format, Shape Format, Header & Footer, Equation, Outlining (Outline view, #493); ribbon overflow (#43, #129). Draw tab: Select, Eraser, Pen, Pencil, Highlighter with colour and thickness (#307); Lasso (drag to move, Delete, recolour), Add Pen gallery (right-click to remove), Ink to Shape (lines, arrows, rectangles, ellipses, polygons), Ink Replay with play/pause/rewind/forward bar (#395); missing: Ink to Math. The app opens on Home every launch, as in Word: the active tab is not restored between runs, but going File and back to the editor returns to the tab that was showing, contextual tabs included. Home › Font: the font box lists Recently Used Fonts (the fonts applied from the box, the mini toolbar or the Font dialog, most recent first, ten at most, kept between runs) above All Fonts, as in Word; the sections show while nothing is typed (#2068) | Same plus Draw | 85% | (Draw in features) | | Keytips (Alt / F10) | Ribbon badges (#43) and numbered Quick Access Toolbar badges in the user's button order (#620; none while the toolbar is hidden, #1945); File › Options › Advanced › Display switch saved per user (`ui.keytips`); Alt leaves macOS Option typing alone | ✅ | 85% | 1–2 | -| Mini-toolbar on selection | ✅ (#43, two rows #535; Recently Used Fonts in its font box #2068) | ✅ | 80% | 1–2 | +| Mini-toolbar on selection and text context menu | ✅ (#43, two rows #535; Recently Used Fonts in its font box #2068; the text context menu includes the same toolbar at a caret or selection, #2959) | ✅ | 80% | 1–2 | | Colour drop-downs: theme/standard colours, Recent Colors, More Colors… → Colors dialog (Standard honeycomb, Custom hue/saturation field with lightness, RGB/HSL and hex) | ✅ Font Color (ribbon, mini toolbar, Font dialog), Shading, Page Color, Shape Fill/Outline/Glow, pen colours, dialog colour fields (#606); no Gradient or eyedropper | ✅ | 90% | 1 | | Quick Access Toolbar | AutoSave, then the user's commands (Save, Undo, Redo by default); add, remove and reorder from its … menu (common commands, More Commands…), right-click on any ribbon button, or Customize Ribbon; saved per user (#379, #376). Undo's ▾ list: recent steps newest first, hover highlights the steps a click undoes ("Undo 3 Actions"), one click undoes them all (#701; `edit.undoTo`, `edit.undoHistory`). Its menu ends with Hide Quick Access Toolbar, which takes it out of the title bar; Show Quick Access Toolbar on the ribbon's right-click menu, or in Customize Ribbon › Quick Access Toolbar, brings it back; kept between runs; `ui.quickAccessToolbar` for scripts (#1945). Not shown below the ribbon | customizable | 92% | 0.5 | | 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 | @@ -74,6 +74,7 @@ Footnote and endnote editing keeps body and note text at normal opacity, on ligh | Date | Change | Summary | |---|---|---| +| 2026-10-19 | trivial | The text context menu includes the existing two-row formatting mini toolbar, at a caret or over selected text (#2959); formatting and its dropdowns preserve the selection, regular menu commands and dismissal still close the popup. Paste options remain in separate PR #2599. | | 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) | | 2026-10-18 | trivial | Picture Format › Size (#2573): the width and height show the interface unit and take a size typed in any unit, pixels too (`300 px`), applied once typed (Enter, Tab or a click elsewhere; Esc cancels) with the proportions kept; the group's launcher opens a Size dialog (height and width, scale, Lock aspect ratio, Relative to original picture size, original size with its pixels, Reset); `picture.size` takes `original`; readiness estimates unchanged | From 4c874d82a23b2c8110865eb880bfac8cfd955bea Mon Sep 17 00:00:00 2001 From: Nghiathan13 Date: Mon, 19 Oct 2026 03:10:48 +0700 Subject: [PATCH 088/336] #2964: document break_lines --- crates/layout/src/para.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/layout/src/para.rs b/crates/layout/src/para.rs index f80ce34ff0..6a6a7ad781 100644 --- a/crates/layout/src/para.rs +++ b/crates/layout/src/para.rs @@ -1884,6 +1884,8 @@ fn ptab_stop(align: PTabAlign, base: PTabBase, leader: TabLeader, width: f32, in TabStop { pos, align, leader } } +/// Break a paragraph into lines, measure each line, apply the paragraph's alignment and place +/// the list label. Fills `pl.lines`, `pl.height` and the label's position. fn break_lines(pl: &mut ParaLayout, env: &ParaEnv, mark_style: u32, level: Option<&Level>) { // Narrow cells and float spans must retain their usable width. const MIN_LINE_WIDTH: f32 = 1.0; From 0d48f236e510221a897c7658694fc4337cbd112e Mon Sep 17 00:00:00 2001 From: Tyrel Haveman <1434399+tyrel@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:04:44 -0700 Subject: [PATCH 089/336] 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 --- ROADMAP.md | 5 +- crates/engine/src/cmd/citations.rs | 245 +++++++++++++++++++++++++--- crates/engine/src/cmd/index.rs | 4 +- crates/engine/src/cmd/references.rs | 13 +- crates/ui-egui/src/dialogs_refs.rs | 128 ++++++++++++++- crates/ui-egui/src/i18n/audit.rs | 1 + crates/ui-egui/src/i18n/bg.tsv | 2 + crates/ui-egui/src/i18n/cs.tsv | 2 + crates/ui-egui/src/i18n/de.tsv | 2 + crates/ui-egui/src/i18n/es.tsv | 2 + crates/ui-egui/src/i18n/et.tsv | 2 + crates/ui-egui/src/i18n/fr.tsv | 2 + crates/ui-egui/src/i18n/hr.tsv | 2 + crates/ui-egui/src/i18n/hu.tsv | 2 + crates/ui-egui/src/i18n/it.tsv | 2 + crates/ui-egui/src/i18n/ja.tsv | 2 + crates/ui-egui/src/i18n/ko.tsv | 2 + crates/ui-egui/src/i18n/pl.tsv | 2 + crates/ui-egui/src/i18n/pt-br.tsv | 2 + crates/ui-egui/src/i18n/ru.tsv | 2 + crates/ui-egui/src/i18n/sr-latn.tsv | 2 + crates/ui-egui/src/i18n/sr.tsv | 2 + crates/ui-egui/src/i18n/sv.tsv | 2 + crates/ui-egui/src/i18n/tr.tsv | 2 + crates/ui-egui/src/i18n/uk.tsv | 2 + crates/ui-egui/src/i18n/vi.tsv | 2 + crates/ui-egui/src/i18n/zh-hans.tsv | 2 + crates/ui-egui/src/i18n/zh-hant.tsv | 2 + crates/ui-egui/src/lib.rs | 12 +- docs/target-app-parity.md | 5 +- docs/ui-parity.md | 5 +- 31 files changed, 421 insertions(+), 41 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index bda7581e50..875d1a55f8 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -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. | diff --git a/crates/engine/src/cmd/citations.rs b/crates/engine/src/cmd/citations.rs index ece07215d2..61aa1ac508 100644 --- a/crates/engine/src/cmd/citations.rs +++ b/crates/engine/src/cmd/citations.rs @@ -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 { 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 "