Columns, Symbol and Field dialogs (#335)

* Keep charts, SmartArt and OLE objects when saving DOCX (#319)

Charts and SmartArt diagrams were drawn since #292 but the writer skipped
them, and OLE objects kept only their picture, so opening and saving a
document silently dropped them.

The reader now keeps each object's own markup (the a:graphic of a chart or
diagram, the whole w:object of an OLE object) on the model object, and every
part it reaches through relationships (chart part, embedded workbook, colour
and style parts; diagram data, layout, quick style, colours and drawing; OLE
embedding and picture) as opaque bytes in Document::passthrough, with their
content types and relationships in a manifest, as the VBA project already is.

The writer emits the object with fresh relationship ids pointing at the
re-added parts, keeping each part's own relationship ids and content types.
A chart or diagram gets its frame from the object's current size and
position; an OLE object gets its new size written into its VML shape. A part
whose name the writer uses itself is renamed, and an object pasted more than
once gets its own copies of every part (sharing past a size budget). Part
counts, relationship counts, markup size and copy bytes are all capped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Columns, Symbol and Field dialogs (#321)

Layout > Columns > More Columns... opens a Columns dialog: One/Two/Three/
Left/Right presets, number of columns, per-column width and spacing,
Equal column width, Line between, a page preview and Apply to (whole
document, this section, this point forward). OK is one layout.columns
call, which now takes unequal widths and an apply target (forward adds a
continuous section break first).

Insert > Symbol > More Symbols... opens a Symbol dialog: font picker,
Unicode subset list that jumps through a grid of only the characters the
font has (drawn in that font), recently used symbols, a hex character
code, and a Special Characters tab. insert.symbol takes a font for the
symbol alone; typing after it goes on in the text's font. The ribbon's
Symbol gallery shows recently used symbols first.

Insert > Quick Parts > Field... opens a Field dialog: categories and the
fields WordCraft computes, date pictures, number formats, bookmark /
sequence / merge field names, preserve formatting, a live field code and
a Field Codes view to type the code directly; OK runs insert.field.

Also: date pictures read correctly when switches follow them, and am/pm
and seconds work in date and time pictures. Strings in every catalog;
gaps #7 and ui-parity updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #321: Field dialog descriptions in our own words (clean room)

Reword the one-line field descriptions and the empty-selection prompt so
none reads like Word's; translations keep their meaning under the new keys.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep charts, SmartArt and OLE objects when saving DOCX (#319)

Charts and SmartArt diagrams were drawn since #292 but the writer skipped
them, and OLE objects kept only their picture, so opening and saving a
document silently dropped them.

The reader now keeps each object's own markup (the a:graphic of a chart or
diagram, the whole w:object of an OLE object) on the model object, and every
part it reaches through relationships (chart part, embedded workbook, colour
and style parts; diagram data, layout, quick style, colours and drawing; OLE
embedding and picture) as opaque bytes in Document::passthrough, with their
content types and relationships in a manifest, as the VBA project already is.

The writer emits the object with fresh relationship ids pointing at the
re-added parts, keeping each part's own relationship ids and content types.
A chart or diagram gets its frame from the object's current size and
position; an OLE object gets its new size written into its VML shape. A part
whose name the writer uses itself is renamed, and an object pasted more than
once gets its own copies of every part (sharing past a size budget). Part
counts, relationship counts, markup size and copy bytes are all capped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Columns, Symbol and Field dialogs (#321)

Layout > Columns > More Columns... opens a Columns dialog: One/Two/Three/
Left/Right presets, number of columns, per-column width and spacing,
Equal column width, Line between, a page preview and Apply to (whole
document, this section, this point forward). OK is one layout.columns
call, which now takes unequal widths and an apply target (forward adds a
continuous section break first).

Insert > Symbol > More Symbols... opens a Symbol dialog: font picker,
Unicode subset list that jumps through a grid of only the characters the
font has (drawn in that font), recently used symbols, a hex character
code, and a Special Characters tab. insert.symbol takes a font for the
symbol alone; typing after it goes on in the text's font. The ribbon's
Symbol gallery shows recently used symbols first.

Insert > Quick Parts > Field... opens a Field dialog: categories and the
fields WordCraft computes, date pictures, number formats, bookmark /
sequence / merge field names, preserve formatting, a live field code and
a Field Codes view to type the code directly; OK runs insert.field.

Also: date pictures read correctly when switches follow them, and am/pm
and seconds work in date and time pictures. Strings in every catalog;
gaps #7 and ui-parity updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Merge fix: #340's rotation round-trip test builds an Image with #333's ole field

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
Valentin Klein 2026-10-10 23:54:23 +02:00 committed by GitHub
parent a7c809e63b
commit 8204c5cb49
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 2213 additions and 36 deletions

View File

@ -131,6 +131,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm
| Date | What landed |
|---|---|
| 2026-10-10 | DOCX charts, SmartArt and OLE objects survive open and save with their parts (#319) |
| 2026-10-10 | Full re-measure against Word for Mac 16.113.4 and progress docs to the craftrules standard (this page, `docs/target-app-parity.md`, `gaps.md`, `roadmap.md`, `architecture.md`, format/layout/UI/hardware/localization parity; `docs/parity.md` → `docs/parity-checklist.md`). Landed the same day (~80 PRs): equations with OMML and an Equation tab (#191); Word 97-2003 `.doc` import (#145); right-to-left and Persian text (#207); Zotero integration (#189); Read Aloud player (#190); text boxes and floating objects editable (#46) and placed like Word (#136); floating tables (#137); track-changes fixes (#125, #244); typing parity with Word (#204); hidden text (#173); save prompts and AutoSave rules (#151); LaTeX import/export (#11); keytips and mini-toolbar (#43); Spanish, Ukrainian and Brazilian Portuguese interfaces; system theme (#249); Linux file dialogs no longer freeze (#246); Chinese UI font on Windows (#248); every installed font weight (#239); cell text direction (#245); Paste Special (#235), Advanced Find (#234), Column Selection (#237), Style Inspector (#236), custom table styles (#256), View gridlines (#243), zoom buttons (#232), inertial touchpad scrolling (#252), File Info fields kept (#268), ¶ in mixed-direction paragraphs (#278), Serbian interface (#250), table Height/Width boxes, AutoFit Contents and Table Properties (#44), AutoSave switch greyed out with the reason where it can't save (.md/.txt/.html, unsaved, browser) (#196, #176), OpenGL fallback on Windows PCs without a DirectX 12 driver (#316), Columns, Symbol and Field dialogs (#321); v0.4.0 released |
| 2026-10-10 | Full re-measure against Word for Mac 16.113.4 and progress docs to the craftrules standard (this page, `docs/target-app-parity.md`, `gaps.md`, `roadmap.md`, `architecture.md`, format/layout/UI/hardware/localization parity; `docs/parity.md` → `docs/parity-checklist.md`). Landed the same day (~80 PRs): equations with OMML and an Equation tab (#191); Word 97-2003 `.doc` import (#145); right-to-left and Persian text (#207); Zotero integration (#189); Read Aloud player (#190); text boxes and floating objects editable (#46) and placed like Word (#136); floating tables (#137); track-changes fixes (#125, #244); typing parity with Word (#204); hidden text (#173); save prompts and AutoSave rules (#151); LaTeX import/export (#11); keytips and mini-toolbar (#43); Spanish, Ukrainian and Brazilian Portuguese interfaces; system theme (#249); Linux file dialogs no longer freeze (#246); Chinese UI font on Windows (#248); every installed font weight (#239); cell text direction (#245); Paste Special (#235), Advanced Find (#234), Column Selection (#237), Style Inspector (#236), custom table styles (#256), View gridlines (#243), zoom buttons (#232), inertial touchpad scrolling (#252), File Info fields kept (#268), ¶ in mixed-direction paragraphs (#278), Serbian interface (#250), table Height/Width boxes, AutoFit Contents and Table Properties (#44), AutoSave switch greyed out with the reason where it can't save (.md/.txt/.html, unsaved, browser) (#196, #176), OpenGL fallback on Windows PCs without a DirectX 12 driver (#316); v0.4.0 released |
| 2026-10-10 | Full re-measure against Word for Mac 16.113.4 and progress docs to the craftrules standard (this page, `docs/target-app-parity.md`, `gaps.md`, `roadmap.md`, `architecture.md`, format/layout/UI/hardware/localization parity; `docs/parity.md` → `docs/parity-checklist.md`). Landed the same day (~80 PRs): equations with OMML and an Equation tab (#191); Word 97-2003 `.doc` import (#145); right-to-left and Persian text (#207); Zotero integration (#189); Read Aloud player (#190); text boxes and floating objects editable (#46) and placed like Word (#136); floating tables (#137); track-changes fixes (#125, #244); typing parity with Word (#204); hidden text (#173); save prompts and AutoSave rules (#151); LaTeX import/export (#11); keytips and mini-toolbar (#43); Spanish, Ukrainian and Brazilian Portuguese interfaces; system theme (#249); Linux file dialogs no longer freeze (#246); Chinese UI font on Windows (#248); every installed font weight (#239); cell text direction (#245); Paste Special (#235), Advanced Find (#234), Column Selection (#237), Style Inspector (#236), custom table styles (#256), View gridlines (#243), zoom buttons (#232), inertial touchpad scrolling (#252), File Info fields kept (#268), ¶ in mixed-direction paragraphs (#278), Serbian interface (#250), table Height/Width boxes, AutoFit Contents and Table Properties (#44), AutoSave switch greyed out with the reason where it can't save (.md/.txt/.html, unsaved, browser) (#196, #176), OpenGL fallback on Windows PCs without a DirectX 12 driver (#316); free rotation and flips for pictures, shapes and text boxes with a rotation handle (#332); v0.4.0 released |
| 2026-10-09 | Interface languages follow the system or Options (#12; zh-hans, zh-hant, ja); TOC page numbers as an updatable field (#52); rotating log file (#17); window geometry remembered (#38) |

View File

@ -69,12 +69,7 @@ pub fn specs() -> Vec<CommandSpec> {
sel_result(s)
}),
CommandSpec::new("insert.dateTime", "Date & Time", "Insert › Text", date_time).params(r#"{"format"?: "M/d/yyyy", "update"?: bool}"#),
CommandSpec::new("insert.symbol", "Symbol", "Insert › Symbols", |s, v| {
let c = p::req_str(v, "char")?;
type_text(s, c)?;
sel_result(s)
})
.params(r#"{"char": string}"#),
CommandSpec::new("insert.symbol", "Symbol", "Insert › Symbols", symbol).params(r#"{"char": string, "font"?: family (the symbol's own font)}"#),
CommandSpec::new("insert.field", "Field", "Insert › Text › Quick Parts", field).key("Mod+F9").params(r#"{"instr": string, "result"?: string}"#),
CommandSpec::new("insert.dropCap", "Drop Cap", "Insert › Text", |s, v| {
let lines = p::u64(v, "lines").unwrap_or(3).min(10) as u8;
@ -463,6 +458,7 @@ pub fn format_date(fmt: &str) -> String {
let d: u32 = iso.get(8..10).and_then(|x| x.parse().ok()).unwrap_or(1);
let hh: u32 = iso.get(11..13).and_then(|x| x.parse().ok()).unwrap_or(0);
let mm: u32 = iso.get(14..16).and_then(|x| x.parse().ok()).unwrap_or(0);
let ss: u32 = iso.get(17..19).and_then(|x| x.parse().ok()).unwrap_or(0);
const MONTHS: [&str; 12] =
["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
const DAYS: [&str; 7] = ["Thursday", "Friday", "Saturday", "Sunday", "Monday", "Tuesday", "Wednesday"];
@ -475,6 +471,12 @@ pub fn format_date(fmt: &str) -> String {
let mut i = 0;
while i < chars.len() {
let c = chars.get(i).copied().unwrap_or(' ');
// `am/pm` (any case) is one token: AM or PM.
if chars.get(i..i + 5).is_some_and(|w| w.iter().collect::<String>().eq_ignore_ascii_case("am/pm")) {
out.push_str(if hh < 12 { "AM" } else { "PM" });
i += 5;
continue;
}
let mut n = 1;
while chars.get(i + n) == Some(&c) {
n += 1;
@ -495,6 +497,8 @@ pub fn format_date(fmt: &str) -> String {
('h', _) => out.push_str(&(if hh.is_multiple_of(12) { 12 } else { hh % 12 }).to_string()),
('m', 2..) => out.push_str(&format!("{mm:02}")),
('m', _) => out.push_str(&mm.to_string()),
('s', 2..) => out.push_str(&format!("{ss:02}")),
('s', _) => out.push_str(&ss.to_string()),
('a' | 'A', _) => out.push_str(if hh < 12 { "AM" } else { "PM" }),
_ => {
for _ in 0..n {
@ -507,6 +511,32 @@ pub fn format_date(fmt: &str) -> String {
out
}
/// Insert › Symbol: the character at the caret, in its own font when `font` names one (the rest of
/// the text keeps the caret's font).
fn symbol(s: &mut Session, v: &Value) -> CmdResult {
let c = p::req_str(v, "char")?;
if c.is_empty() || c.chars().count() > 64 {
return Err(CmdError::Params("char: 1–64 characters".into()));
}
let font = p::str(v, "font").map(str::trim).filter(|f| !f.is_empty());
let Some(font) = font else {
type_text(s, c)?;
return sel_result(s);
};
if font.len() > 256 {
return Err(CmdError::Params("font: a family name".into()));
}
let base = s.typing_props();
let mut props = base.clone();
props.font = Some(font.to_string());
s.pending = Some(props);
let r = type_text(s, c);
// Typing on after the symbol goes on in the text's own font.
s.pending = Some(base);
r?;
sel_result(s)
}
fn field(s: &mut Session, v: &Value) -> CmdResult {
let instr = p::req_str(v, "instr")?.trim().to_string();
if instr.is_empty() || instr.len() > 4096 {
@ -566,10 +596,31 @@ mod tests {
assert!(base64_decode("!!!").is_none());
}
/// Insert › Symbol with a font: only the symbol takes it, the caret's font carries on after.
#[test]
fn symbol_takes_its_own_font() {
let mut s = Session::new(wordcraft_doc::Document::from_text("ab"));
s.run("caret.docEnd", &json!({})).unwrap();
s.run("insert.symbol", &json!({"char": "\u{2665}", "font": "DejaVu Sans"})).unwrap();
s.run("text.insert", &json!({"text": "c"})).unwrap();
let p = s.doc.para(StoryRef::Body, &Path::top(0)).unwrap();
assert_eq!(p.text, "ab\u{2665}c");
assert_eq!(p.props_of_char(2).font.as_deref(), Some("DejaVu Sans"), "the symbol");
assert_eq!(p.props_of_char(5).font, None, "typing after it (byte offsets)");
assert!(s.run("insert.symbol", &json!({"char": ""})).is_err());
}
#[test]
fn date_pictures() {
let d = format_date("yyyy-MM-dd");
assert_eq!(d.len(), 10);
assert!(format_date("MMMM d, yyyy").contains(", "));
let t = format_date("h:mm:ss am/pm");
assert!(t.ends_with(" AM") || t.ends_with(" PM"), "{t}");
assert_eq!(t.matches(':').count(), 2, "{t}");
use super::super::references::date_picture;
assert_eq!(date_picture(r#"DATE \@ "MMMM d, yyyy" \* MERGEFORMAT"#).as_deref(), Some("MMMM d, yyyy"));
assert_eq!(date_picture(r#"TIME \@ HH:mm"#).as_deref(), Some("HH:mm"));
assert_eq!(date_picture("DATE"), None);
}
}

View File

@ -23,7 +23,9 @@ pub fn specs() -> Vec<CommandSpec> {
.params(r#"{"value": "portrait|landscape"}"#),
CommandSpec::new("layout.size", "Size", "Layout › Page Setup", size).params(r#"{"name"?: "Letter|Legal|A4|…", "width"?: pt, "height"?: pt}"#),
CommandSpec::new("layout.columns", "Columns", "Layout › Page Setup", columns)
.params(r#"{"count": 1-12, "space"?: pt, "separator"?: bool, "preset"?: "left|right"}"#),
.params(
r#"{"count": 1-12, "space"?: pt, "separator"?: bool, "preset"?: "left|right", "widths"?: [[width pt, space after pt], …] (unequal columns; sets the count), "apply"?: "section|document|forward"}"#,
),
CommandSpec::new("layout.break", "Breaks", "Layout › Page Setup", breaks)
.params(r#"{"kind": "page|column|textWrapping|nextPage|continuous|evenPage|oddPage"}"#),
CommandSpec::new("layout.lineNumbers", "Line Numbers", "Layout › Page Setup", |s, v| {
@ -171,22 +173,75 @@ fn size(s: &mut Session, v: &Value) -> CmdResult {
fn columns(s: &mut Session, v: &Value) -> CmdResult {
let preset = p::str(v, "preset");
let count = p::u64(v, "count").unwrap_or(match preset {
Some("left") | Some("right") => 2,
_ => 1,
});
if !(1..=12).contains(&count) {
return Err(CmdError::Params("columns must be 1–12".into()));
}
let space = p::f32(v, "space").unwrap_or(36.0).clamp(0.0, 300.0);
let sep = p::bool(v, "separator").unwrap_or(false);
let tw = sect(s).text_width();
let widths = match preset {
Some("left") => vec![((tw - space) / 3.0, space), ((tw - space) * 2.0 / 3.0, 0.0)],
Some("right") => vec![((tw - space) * 2.0 / 3.0, space), ((tw - space) / 3.0, 0.0)],
// Unequal columns: (width, space after) each, as the Columns dialog sends them.
let custom = match v.get("widths").filter(|w| !w.is_null()) {
Some(w) => Some(column_widths(w, tw)?),
None => None,
};
let count = match &custom {
Some(c) => c.len() as u64,
None => p::u64(v, "count").unwrap_or(match preset {
Some("left") | Some("right") => 2,
_ => 1,
}),
};
if !(1..=12).contains(&count) {
return Err(CmdError::Params("columns must be 1–12".into()));
}
let widths = match (custom, preset) {
(Some(c), _) if c.len() > 1 => c,
(Some(_), _) => Vec::new(),
(None, Some("left")) => vec![((tw - space) / 3.0, space), ((tw - space) * 2.0 / 3.0, 0.0)],
(None, Some("right")) => vec![((tw - space) * 2.0 / 3.0, space), ((tw - space) / 3.0, 0.0)],
_ => Vec::new(),
};
with_sect(s, |x| x.columns = Columns { count: count as u32, space, separator: sep, widths: widths.clone() })
// Equal columns keep one spacing; unequal ones remember the first gap for a later switch back.
let space = widths.first().map(|w| w.1).unwrap_or(space);
let cols = Columns { count: count as u32, space, separator: sep, widths };
match p::str(v, "apply").unwrap_or("section") {
"section" => with_sect(s, |x| x.columns = cols.clone()),
"document" => {
let ends: Vec<usize> = s.doc.sections().iter().map(|(e, _)| *e).collect();
for e in ends {
s.doc.section_mut(e).columns = cols.clone();
}
Ok(serde_json::to_value(sect(s)).unwrap_or(Value::Null))
}
// From the caret on: a continuous section break first, then the new section's columns.
"forward" => {
let start = s.sel.ordered().0;
s.sel = Selection::caret(start);
breaks(s, &json!({"kind": "continuous"}))?;
with_sect(s, |x| x.columns = cols.clone())
}
x => Err(CmdError::Params(format!("unknown apply `{x}` (section, document or forward)"))),
}
}
/// `[[width, space after], …]` in points: 1–12 columns, each at least 0.25", that fit the text
/// width (`tw`).
fn column_widths(v: &Value, tw: f32) -> Result<Vec<(f32, f32)>, CmdError> {
let bad = || CmdError::Params("widths: [[width, space after], …] in points, 1–12 columns".into());
let a = v.as_array().filter(|a| (1..=12).contains(&a.len())).ok_or_else(bad)?;
let mut out = Vec::with_capacity(a.len());
for (i, c) in a.iter().enumerate() {
let num = |k: usize| c.get(k).and_then(Value::as_f64).map(|x| x as f32).filter(|x| x.is_finite());
let w = num(0).ok_or_else(bad)?;
// The last column has no space after it.
let sp = if i + 1 == a.len() { 0.0 } else { num(1).unwrap_or(0.0) };
if w < 18.0 || !(0.0..=300.0).contains(&sp) {
return Err(CmdError::Params("each column must be at least 0.25\" wide, with 0–300 pt after it".into()));
}
out.push((w, sp));
}
let total: f32 = out.iter().map(|(w, sp)| w + sp).sum();
if total > tw + 1.0 {
return Err(CmdError::Params(format!("the columns ({total:.0} pt) are wider than the text ({tw:.0} pt)")));
}
Ok(out)
}
fn breaks(s: &mut Session, v: &Value) -> CmdResult {
@ -223,3 +278,45 @@ fn breaks(s: &mut Session, v: &Value) -> CmdResult {
let _ = Block::Para;
sel_result(s)
}
#[cfg(test)]
mod tests {
use super::*;
fn doc() -> Session {
Session::new(wordcraft_doc::Document::from_text("one\ntwo\nthree"))
}
/// More Columns: unequal widths with a line between, as one undo step.
#[test]
fn unequal_columns_with_a_line_between() {
let mut s = doc();
let tw = sect(&s).text_width();
s.run("layout.columns", &json!({"widths": [[150.0, 24.0], [tw - 174.0, 0.0]], "separator": true})).unwrap();
let c = sect(&s).columns;
assert_eq!((c.count, c.separator, c.widths.len()), (2, true, 2));
assert_eq!(c.widths.first(), Some(&(150.0, 24.0)));
s.run("edit.undo", &json!({})).unwrap();
assert_eq!(sect(&s).columns, Columns::default());
// Too wide, too narrow, too many, or nonsense: refused.
for bad in [json!([[tw, 36.0], [100.0, 0.0]]), json!([[5.0, 0.0], [100.0, 0.0]]), json!(vec![[40.0, 0.0]; 13]), json!("x")] {
assert!(s.run("layout.columns", &json!({"widths": bad})).is_err(), "{bad}");
}
}
/// Apply to: the whole document, or from the caret on (a continuous section break first).
#[test]
fn columns_apply_to_the_document_or_from_the_caret_on() {
let mut s = doc();
s.run("layout.break", &json!({"kind": "nextPage"})).unwrap();
s.run("layout.columns", &json!({"count": 3, "apply": "document"})).unwrap();
assert!(s.doc.sections().iter().all(|(_, x)| x.columns.count == 3));
let mut s = doc();
s.run("caret.docEnd", &json!({})).unwrap();
s.run("layout.columns", &json!({"count": 2, "apply": "forward"})).unwrap();
let counts: Vec<u32> = s.doc.sections().iter().map(|(_, x)| x.columns.count).collect();
assert_eq!(counts, vec![1, 2]);
assert_eq!(s.doc.last_section.start, SectionStart::Continuous);
assert!(s.run("layout.columns", &json!({"count": 2, "apply": "elsewhere"})).is_err());
}
}

View File

@ -285,11 +285,7 @@ pub fn update_fields(s: &mut Session) -> Result<(), CmdError> {
let name = wordcraft_layout::fields::field_name(instr);
match name.as_str() {
"DATE" | "TIME" | "CREATEDATE" | "SAVEDATE" | "PRINTDATE" => {
let pic = instr
.split("\\@")
.nth(1)
.map(|x| x.trim().trim_matches('"').to_string())
.unwrap_or_else(|| if name == "TIME" { "h:mm am/pm".into() } else { "M/d/yyyy".into() });
let pic = date_picture(instr).unwrap_or_else(|| if name == "TIME" { "h:mm am/pm".into() } else { "M/d/yyyy".into() });
updates.push((k, super::insert::format_date(&pic)));
}
"SEQ" => {
@ -326,6 +322,16 @@ pub fn update_fields(s: &mut Session) -> Result<(), CmdError> {
update_toc(s)
}
/// A field's `\@` date picture: the quoted text after it (switches may follow), or one word.
pub fn date_picture(instr: &str) -> Option<String> {
let rest = instr.split_once("\\@")?.1.trim_start();
let pic = match rest.strip_prefix('"') {
Some(q) => q.split('"').next().unwrap_or(""),
None => rest.split_whitespace().next().unwrap_or(""),
};
(!pic.is_empty()).then(|| pic.to_string())
}
/// The text a bookmark marks (field results included), up to its end in the same paragraph or
/// the paragraph's end. `None` when there is no such bookmark.
pub fn bookmark_text(doc: &wordcraft_doc::Document, name: &str) -> Option<String> {

View File

@ -142,6 +142,8 @@ pub enum Dialog {
Commands {
query: String,
},
/// Columns, Symbol and Field ([`crate::dialogs_insert`], #321).
Insert(Box<crate::dialogs_insert::InsertDialog>),
/// Paste Special: the clipboard's formats and the chosen one. The clipboard payload stays out
/// of the serialized dialog state (it can be megabytes).
PasteSpecial {
@ -622,6 +624,7 @@ impl Dialog {
Dialog::TableStyle { id: None, .. } => "newTableStyle",
Dialog::TableStyle { .. } => "modifyTableStyle",
Dialog::Commands { .. } => "commands",
Dialog::Insert(d) => d.name(),
Dialog::PasteSpecial { .. } => "pasteSpecial",
Dialog::About { .. } => "about",
Dialog::SaveChanges { .. } => "saveChanges",
@ -657,6 +660,9 @@ impl Dialog {
}
pub fn open(name: &str, app: &mut WordApp) -> Option<Dialog> {
if let Some(d) = crate::dialogs_insert::open(name, app) {
return Some(Dialog::Insert(Box::new(d)));
}
let st = app.session.run("format.state", &json!({})).unwrap_or_default();
let s = |k: &str| st.get(k).and_then(Value::as_str).unwrap_or("").to_string();
let b = |k: &str| st.get(k).and_then(Value::as_bool).unwrap_or(false);
@ -891,6 +897,7 @@ pub fn show(app: &mut WordApp, ctx: &egui::Context) {
Dialog::TableStyle { id: None, .. } => "New Table Style",
Dialog::TableStyle { .. } => "Modify Table Style",
Dialog::Commands { .. } => "Search Commands",
Dialog::Insert(d) => d.title(),
Dialog::PasteSpecial { .. } => "Paste Special",
Dialog::About { .. } => "About WordCraft",
Dialog::SaveChanges { .. } => "WordCraft",
@ -1501,6 +1508,7 @@ fn body(app: &mut WordApp, ui: &mut Ui, d: &mut Dialog) -> bool {
}
ok || cancel
}
Dialog::Insert(f) => crate::dialogs_insert::body(app, ui, f),
Dialog::Commands { query } => {
let r = ui.add(egui::TextEdit::singleline(query).hint_text(tl!("Type a command, e.g. \"insert table\"")).desired_width(380.0));
r.request_focus();

File diff suppressed because it is too large Load Diff

View File

@ -1103,6 +1103,89 @@
Leave the equation (Esc) Salir de la ecuación (Esc)
Preview Vista previa
getartcraft.com/apps/wordcraft getartcraft.com/apps/wordcraft
# Columns, Symbol and Field dialogs (#321).
(All) (Todos)
(none) (ninguno)
(normal text) (texto normal)
1/4 Em Space Espacio de 1/4 de eme
A mail merge field Un campo de combinación
A numbered sequence (figures, tables…) Una secuencia numerada (figuras, tablas…)
A table of contents Una tabla de contenido
Apply to: Aplicar a:
Categories: Categorías:
Character Carácter
Character code: Código de carácter:
Col #: Col. n.º:
Copyright Copyright
Date and Time Fecha y hora
Date formats: Formatos de fecha:
Description: Descripción:
Document Information Información del documento
Double Closing Quote Comilla doble de cierre
Double Opening Quote Comilla doble de apertura
Ellipsis Puntos suspensivos
Em Dash Raya
En Dash Guion medio
Em Space Espacio eme
En Space Espacio ene
Equal column width Columnas de igual ancho
Field Codes Códigos de campo
Field code: Código de campo:
Field codes: Códigos de campo:
Field name: Nombre del campo:
Field names: Nombres de campo:
Field properties Propiedades del campo
Field… Campo…
Format: Formato:
Hide Codes Ocultar códigos
Index and Tables Índices y tablas
Line between Línea entre columnas
Links and References Vínculos y referencias
Mail Merge Combinar correspondencia
More Columns… Más columnas…
More Symbols… Más símbolos…
No-Width Non Break Unión sin ancho
No-Width Optional Break Salto opcional sin ancho
Paragraph Mark Marca de párrafo
Pick a field from the list Elija un campo
Preserve formatting during updates Conservar el formato al actualizar
Presets Preestablecidos
Recently used symbols: Símbolos usados recientemente:
Registered Registrado
Section Mark Marca de sección
Sequence name: Nombre de la secuencia:
Shortcut key: Atajo de teclado:
Single Closing Quote Comilla simple de cierre
Single Opening Quote Comilla simple de apertura
Spacing: Espaciado:
Special Characters Caracteres especiales
Subset: Subconjunto:
The time right now La hora actual
When this document was first created La fecha en que se creó el documento
When this document was most recently printed La fecha en que se imprimió el documento por última vez
When this document was most recently saved La fecha en que se guardó el documento por última vez
The document has no bookmarks yet. El documento aún no tiene marcadores.
Who the document properties name as author El autor del documento
Name of the file this document is saved as El nombre de archivo del documento
Title from the document properties El título del documento
How many pages the whole document has El número de páginas del documento
How many pages this section has El número de páginas de la sección
Number of the page the field sits on El número de la página actual
Number of the section the field sits in El número de la sección actual
How many words the document holds El número de palabras del documento
Page on which a bookmarked passage falls El número de página de un marcador
Copy of the text a bookmark marks El texto de un marcador
This field has no options. Este campo no tiene opciones.
This font has no characters to show. Esta fuente no tiene caracteres que mostrar.
This point forward De aquí en adelante
This section Esta sección
The date it is today La fecha de hoy
Trademark Marca comercial
Type the field's name and its switches, for example PAGE \\* roman. Escriba el nombre del campo y sus modificadores, por ejemplo PAGE \\* roman.
Whole document Todo el documento
Width and spacing Ancho y espaciado
from Unicode (hex) de Unicode (hex)
Shape Effects Efectos de forma
Glow Resplandor
Soft Edges Bordes suaves

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

View File

@ -1087,6 +1087,89 @@
Leave the equation (Esc) 数式を終了 (Esc)
Preview プレビュー
getartcraft.com/apps/wordcraft getartcraft.com/apps/wordcraft
# Columns, Symbol and Field dialogs (#321).
(All) (すべて)
(none) (なし)
(normal text) (標準テキスト)
1/4 Em Space 1/4 em スペース
A mail merge field 差し込み用のフィールド
A numbered sequence (figures, tables…) 連番 (図、表…)
A table of contents 目次
Apply to: 適用先:
Categories: 分類:
Character 文字
Character code: 文字コード:
Col #: 段:
Copyright 著作権記号
Date and Time 日付と時刻
Date formats: 日付の形式:
Description: 説明:
Document Information 文書の情報
Double Closing Quote 二重引用符 (閉じ)
Double Opening Quote 二重引用符 (開き)
Ellipsis 省略記号
Em Dash em ダッシュ
En Dash en ダッシュ
Em Space em スペース
En Space en スペース
Equal column width 段の幅を均等にする
Field Codes フィールド コード
Field code: フィールド コード:
Field codes: フィールド コード:
Field name: フィールド名:
Field names: フィールドの名前:
Field properties フィールドのプロパティ
Field… フィールド…
Format: 書式:
Hide Codes コードを隠す
Index and Tables 索引と目次
Line between 境界線を引く
Links and References リンクと参照
Mail Merge 差し込み印刷
More Columns… 段組みの詳細設定…
More Symbols… その他の記号…
No-Width Non Break 幅なし改行禁止
No-Width Optional Break 幅なし任意改行
Paragraph Mark 段落記号
Pick a field from the list フィールドを選んでください
Preserve formatting during updates 更新時に書式を保持する
Presets プリセット
Recently used symbols: 最近使った記号:
Registered 登録商標
Section Mark 節記号
Sequence name: 連番の名前:
Shortcut key: ショートカット キー:
Single Closing Quote 一重引用符 (閉じ)
Single Opening Quote 一重引用符 (開き)
Spacing: 間隔:
Special Characters 特殊文字
Subset: 種類:
The time right now 現在の時刻
When this document was first created 文書の作成日
When this document was most recently printed 文書の最終印刷日
When this document was most recently saved 文書の最終保存日
The document has no bookmarks yet. この文書にはまだブックマークがありません。
Who the document properties name as author 文書の作成者
Name of the file this document is saved as 文書のファイル名
Title from the document properties 文書のタイトル
How many pages the whole document has 文書のページ数
How many pages this section has セクションのページ数
Number of the page the field sits on 現在のページ番号
Number of the section the field sits in 現在のセクション番号
How many words the document holds 文書の単語数
Page on which a bookmarked passage falls ブックマークのページ番号
Copy of the text a bookmark marks ブックマークの文字列
This field has no options. このフィールドにはオプションがありません。
This font has no characters to show. このフォントには表示できる文字がありません。
This point forward これ以降
This section このセクション
The date it is today 今日の日付
Trademark 商標
Type the field's name and its switches, for example PAGE \\* roman. フィールド名とスイッチを入力します (例: PAGE \\* roman)。
Whole document 文書全体
Width and spacing 幅と間隔
from Unicode (hex) Unicode (16 進)
Shape Effects 図形の効果
Glow 光彩
Soft Edges 輪郭のぼかし

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

View File

@ -1099,6 +1099,89 @@
Leave the equation (Esc) Sair da equação (Esc)
Preview Visualizar
getartcraft.com/apps/wordcraft getartcraft.com/apps/wordcraft
# Columns, Symbol and Field dialogs (#321).
(All) (Todos)
(none) (nenhum)
(normal text) (texto normal)
1/4 Em Space Espaço de 1/4 de eme
A mail merge field Um campo de mesclagem
A numbered sequence (figures, tables…) Uma sequência numerada (figuras, tabelas…)
A table of contents Um sumário
Apply to: Aplicar a:
Categories: Categorias:
Character Caractere
Character code: Código do caractere:
Col #: Col. nº:
Copyright Copyright
Date and Time Data e hora
Date formats: Formatos de data:
Description: Descrição:
Document Information Informações do documento
Double Closing Quote Aspas duplas de fechamento
Double Opening Quote Aspas duplas de abertura
Ellipsis Reticências
Em Dash Travessão
En Dash Meia-risca
Em Space Espaço eme
En Space Espaço ene
Equal column width Colunas de largura igual
Field Codes Códigos de campo
Field code: Código de campo:
Field codes: Códigos de campo:
Field name: Nome do campo:
Field names: Nomes de campo:
Field properties Propriedades do campo
Field… Campo…
Format: Formato:
Hide Codes Ocultar códigos
Index and Tables Índices e tabelas
Line between Linha entre colunas
Links and References Links e referências
Mail Merge Mala direta
More Columns… Mais colunas…
More Symbols… Mais símbolos…
No-Width Non Break Junção sem largura
No-Width Optional Break Quebra opcional sem largura
Paragraph Mark Marca de parágrafo
Pick a field from the list Escolha um campo
Preserve formatting during updates Preservar a formatação ao atualizar
Presets Predefinições
Recently used symbols: Símbolos usados recentemente:
Registered Registrado
Section Mark Marca de seção
Sequence name: Nome da sequência:
Shortcut key: Tecla de atalho:
Single Closing Quote Aspas simples de fechamento
Single Opening Quote Aspas simples de abertura
Spacing: Espaçamento:
Special Characters Caracteres especiais
Subset: Subconjunto:
The time right now A hora atual
When this document was first created A data em que o documento foi criado
When this document was most recently printed A data em que o documento foi impresso pela última vez
When this document was most recently saved A data em que o documento foi salvo pela última vez
The document has no bookmarks yet. O documento ainda não tem indicadores.
Who the document properties name as author O autor do documento
Name of the file this document is saved as O nome do arquivo do documento
Title from the document properties O título do documento
How many pages the whole document has O número de páginas do documento
How many pages this section has O número de páginas da seção
Number of the page the field sits on O número da página atual
Number of the section the field sits in O número da seção atual
How many words the document holds O número de palavras do documento
Page on which a bookmarked passage falls O número da página de um indicador
Copy of the text a bookmark marks O texto de um indicador
This field has no options. Este campo não tem opções.
This font has no characters to show. Esta fonte não tem caracteres para mostrar.
This point forward Deste ponto em diante
This section Esta seção
The date it is today A data de hoje
Trademark Marca comercial
Type the field's name and its switches, for example PAGE \\* roman. Digite o nome do campo e suas opções, por exemplo PAGE \\* roman.
Whole document Documento inteiro
Width and spacing Largura e espaçamento
from Unicode (hex) do Unicode (hex)
Shape Effects Efeitos de forma
Glow Brilho
Soft Edges Bordas suaves

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

View File

@ -1152,6 +1152,89 @@
Insert a new equation (Alt+=) Umetni novu jednačinu (Alt+=)
Leave the equation (Esc) Napusti jednačinu (Esc)
getartcraft.com/apps/wordcraft getartcraft.com/apps/wordcraft
# Columns, Symbol and Field dialogs (#321).
(All) (Sve)
(none) (nema)
(normal text) (običan tekst)
1/4 Em Space Razmak od 1/4 em
A mail merge field Polje za spajanje pošte
A numbered sequence (figures, tables…) Numerisani niz (slike, tabele…)
A table of contents Sadržaj
Apply to: Primeni na:
Categories: Kategorije:
Character Znak
Character code: Kod znaka:
Col #: Kolona br.:
Copyright Znak autorskog prava
Date and Time Datum i vreme
Date formats: Formati datuma:
Description: Opis:
Document Information Informacije o dokumentu
Double Closing Quote Zatvoreni dvostruki navodnik
Double Opening Quote Otvoreni dvostruki navodnik
Ellipsis Tri tačke
Em Dash Duga crta
En Dash Srednja crta
Em Space Em razmak
En Space En razmak
Equal column width Jednaka širina kolona
Field Codes Kodovi polja
Field code: Kod polja:
Field codes: Kodovi polja:
Field name: Naziv polja:
Field names: Nazivi polja:
Field properties Svojstva polja
Field… Polje…
Format: Format:
Hide Codes Sakrij kodove
Index and Tables Indeksi i tabele
Line between Linija između kolona
Links and References Veze i reference
Mail Merge Spajanje pošte
More Columns… Još kolona…
More Symbols… Još simbola…
No-Width Non Break Nedeljivi znak nulte širine
No-Width Optional Break Opcioni prelom nulte širine
Paragraph Mark Znak pasusa
Pick a field from the list Izaberite polje
Preserve formatting during updates Sačuvaj oblikovanje pri ažuriranju
Presets Unapred zadato
Recently used symbols: Nedavno korišćeni simboli:
Registered Registrovani žig
Section Mark Znak paragrafa
Sequence name: Naziv niza:
Shortcut key: Prečica na tastaturi:
Single Closing Quote Zatvoreni jednostruki navodnik
Single Opening Quote Otvoreni jednostruki navodnik
Spacing: Razmak:
Special Characters Specijalni znakovi
Subset: Podskup:
The time right now Trenutno vreme
When this document was first created Datum kada je dokument napravljen
When this document was most recently printed Datum kada je dokument poslednji put odštampan
When this document was most recently saved Datum kada je dokument poslednji put sačuvan
The document has no bookmarks yet. Dokument još nema obeleživače.
Who the document properties name as author Autor dokumenta
Name of the file this document is saved as Ime datoteke dokumenta
Title from the document properties Naslov dokumenta
How many pages the whole document has Broj stranica u dokumentu
How many pages this section has Broj stranica u odeljku
Number of the page the field sits on Broj trenutne stranice
Number of the section the field sits in Broj trenutnog odeljka
How many words the document holds Broj reči u dokumentu
Page on which a bookmarked passage falls Broj stranice obeleživača
Copy of the text a bookmark marks Tekst obeleživača
This field has no options. Ovo polje nema opcija.
This font has no characters to show. Ovaj font nema znakove za prikaz.
This point forward Od ovog mesta nadalje
This section Ovaj odeljak
The date it is today Današnji datum
Trademark Žig
Type the field's name and its switches, for example PAGE \\* roman. Unesite naziv polja i njegove prekidače, na primer PAGE \\* roman.
Whole document Ceo dokument
Width and spacing Širina i razmak
from Unicode (hex) iz Unicode (heks.)
Rotation Rotacija
Rotation: Rotacija:
Rotate Right 90° Rotiraj udesno za 90°

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

View File

@ -1156,6 +1156,89 @@
Insert a new equation (Alt+=) Уметни нову једначину (Alt+=)
Leave the equation (Esc) Напусти једначину (Esc)
getartcraft.com/apps/wordcraft getartcraft.com/apps/wordcraft
# Columns, Symbol and Field dialogs (#321).
(All) (Све)
(none) (нема)
(normal text) (обичан текст)
1/4 Em Space Размак од 1/4 em
A mail merge field Поље за спајање поште
A numbered sequence (figures, tables…) Нумерисани низ (слике, табеле…)
A table of contents Садржај
Apply to: Примени на:
Categories: Категорије:
Character Знак
Character code: Код знака:
Col #: Колона бр.:
Copyright Знак ауторског права
Date and Time Датум и време
Date formats: Формати датума:
Description: Опис:
Document Information Информације о документу
Double Closing Quote Затворени двоструки наводник
Double Opening Quote Отворени двоструки наводник
Ellipsis Три тачке
Em Dash Дуга црта
En Dash Средња црта
Em Space Em размак
En Space En размак
Equal column width Једнака ширина колона
Field Codes Кодови поља
Field code: Код поља:
Field codes: Кодови поља:
Field name: Назив поља:
Field names: Називи поља:
Field properties Својства поља
Field… Поље…
Format: Формат:
Hide Codes Сакриј кодове
Index and Tables Индекси и табеле
Line between Линија између колона
Links and References Везе и референце
Mail Merge Спајање поште
More Columns… Још колона…
More Symbols… Још симбола…
No-Width Non Break Недељиви знак нулте ширине
No-Width Optional Break Опциони прелом нулте ширине
Paragraph Mark Знак пасуса
Pick a field from the list Изаберите поље
Preserve formatting during updates Сачувај обликовање при ажурирању
Presets Унапред задато
Recently used symbols: Недавно коришћени симболи:
Registered Регистровани жиг
Section Mark Знак параграфа
Sequence name: Назив низа:
Shortcut key: Пречица на тастатури:
Single Closing Quote Затворени једноструки наводник
Single Opening Quote Отворени једноструки наводник
Spacing: Размак:
Special Characters Специјални знакови
Subset: Подскуп:
The time right now Тренутно време
When this document was first created Датум када је документ направљен
When this document was most recently printed Датум када је документ последњи пут одштампан
When this document was most recently saved Датум када је документ последњи пут сачуван
The document has no bookmarks yet. Документ још нема обележиваче.
Who the document properties name as author Аутор документа
Name of the file this document is saved as Име датотеке документа
Title from the document properties Наслов документа
How many pages the whole document has Број страница у документу
How many pages this section has Број страница у одељку
Number of the page the field sits on Број тренутне странице
Number of the section the field sits in Број тренутног одељка
How many words the document holds Број речи у документу
Page on which a bookmarked passage falls Број странице обележивача
Copy of the text a bookmark marks Текст обележивача
This field has no options. Ово поље нема опција.
This font has no characters to show. Овај фонт нема знакове за приказ.
This point forward Од овог места надаље
This section Овај одељак
The date it is today Данашњи датум
Trademark Жиг
Type the field's name and its switches, for example PAGE \\* roman. Унесите назив поља и његове прекидаче, на пример PAGE \\* roman.
Whole document Цео документ
Width and spacing Ширина и размак
from Unicode (hex) из Unicode (хекс.)
Rotation Ротација
Rotation: Ротација:
Rotate Right 90° Ротирај удесно за 90°

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

View File

@ -1099,6 +1099,89 @@
Leave the equation (Esc) Вийти з рівняння (Esc)
Preview Попередній перегляд
getartcraft.com/apps/wordcraft getartcraft.com/apps/wordcraft
# Columns, Symbol and Field dialogs (#321).
(All) (Усі)
(none) (немає)
(normal text) (звичайний текст)
1/4 Em Space Пробіл 1/4 кегля
A mail merge field Поле злиття
A numbered sequence (figures, tables…) Нумерована послідовність (рисунки, таблиці…)
A table of contents Зміст
Apply to: Застосувати до:
Categories: Категорії:
Character Символ
Character code: Код символу:
Col #: Стовпець:
Copyright Знак авторського права
Date and Time Дата й час
Date formats: Формати дати:
Description: Опис:
Document Information Відомості про документ
Double Closing Quote Закривні подвійні лапки
Double Opening Quote Відкривні подвійні лапки
Ellipsis Три крапки
Em Dash Довге тире
En Dash Коротке тире
Em Space Широкий пробіл
En Space Пробіл середньої ширини
Equal column width Однакова ширина стовпців
Field Codes Коди полів
Field code: Код поля:
Field codes: Коди полів:
Field name: Назва поля:
Field names: Назви полів:
Field properties Властивості поля
Field… Поле…
Format: Формат:
Hide Codes Сховати коди
Index and Tables Покажчики й таблиці
Line between Лінія між стовпцями
Links and References Зв'язки й посилання
Mail Merge Злиття
More Columns… Інші стовпці…
More Symbols… Інші символи…
No-Width Non Break Нерозривний символ нульової ширини
No-Width Optional Break Необов'язковий розрив нульової ширини
Paragraph Mark Знак абзацу
Pick a field from the list Виберіть поле
Preserve formatting during updates Зберігати форматування під час оновлення
Presets Шаблони
Recently used symbols: Нещодавно використані символи:
Registered Зареєстрована торговельна марка
Section Mark Знак параграфа
Sequence name: Назва послідовності:
Shortcut key: Сполучення клавіш:
Single Closing Quote Закривна одинарна лапка
Single Opening Quote Відкривна одинарна лапка
Spacing: Інтервал:
Special Characters Спеціальні символи
Subset: Набір:
The time right now Поточний час
When this document was first created Дата створення документа
When this document was most recently printed Дата останнього друку документа
When this document was most recently saved Дата останнього збереження документа
The document has no bookmarks yet. У документі ще немає закладок.
Who the document properties name as author Автор документа
Name of the file this document is saved as Ім'я файлу документа
Title from the document properties Назва документа
How many pages the whole document has Кількість сторінок у документі
How many pages this section has Кількість сторінок у розділі
Number of the page the field sits on Номер поточної сторінки
Number of the section the field sits in Номер поточного розділу
How many words the document holds Кількість слів у документі
Page on which a bookmarked passage falls Номер сторінки закладки
Copy of the text a bookmark marks Текст закладки
This field has no options. Це поле не має параметрів.
This font has no characters to show. Цей шрифт не має символів для показу.
This point forward Від цього місця
This section Цей розділ
The date it is today Сьогоднішня дата
Trademark Торговельна марка
Type the field's name and its switches, for example PAGE \\* roman. Введіть назву поля та його ключі, наприклад PAGE \\* roman.
Whole document Увесь документ
Width and spacing Ширина й інтервал
from Unicode (hex) з Unicode (шістн.)
Shape Effects Ефекти фігури
Glow Світіння
Soft Edges М'які краї

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

View File

@ -1098,6 +1098,89 @@
Leave the equation (Esc) 退出公式 (Esc)
Preview 预览
getartcraft.com/apps/wordcraft getartcraft.com/apps/wordcraft
# Columns, Symbol and Field dialogs (#321).
(All) (全部)
(none) (无)
(normal text) (普通文本)
1/4 Em Space 1/4 全角空格
A mail merge field 邮件合并域
A numbered sequence (figures, tables…) 编号序列(图、表…)
A table of contents 目录
Apply to: 应用于:
Categories: 类别:
Character 字符
Character code: 字符代码:
Col #: 栏号:
Copyright 版权
Date and Time 日期和时间
Date formats: 日期格式:
Description: 说明:
Document Information 文档信息
Double Closing Quote 右双引号
Double Opening Quote 左双引号
Ellipsis 省略号
Em Dash 长破折号
En Dash 短破折号
Em Space 全角空格
En Space 半角空格
Equal column width 栏宽相等
Field Codes 域代码
Field code: 域代码:
Field codes: 域代码:
Field name: 域名:
Field names: 域名列表:
Field properties 域属性
Field… 域…
Format: 格式:
Hide Codes 隐藏代码
Index and Tables 索引和目录
Line between 分隔线
Links and References 链接和引用
Mail Merge 邮件合并
More Columns… 更多栏…
More Symbols… 其他符号…
No-Width Non Break 无宽非分隔符
No-Width Optional Break 无宽可选分隔符
Paragraph Mark 段落标记
Pick a field from the list 请选择域
Preserve formatting during updates 更新时保留格式
Presets 预设
Recently used symbols: 近期使用过的符号:
Registered 注册商标
Section Mark 章节符号
Sequence name: 序列名称:
Shortcut key: 快捷键:
Single Closing Quote 右单引号
Single Opening Quote 左单引号
Spacing: 间距:
Special Characters 特殊字符
Subset: 子集:
The time right now 当前时间
When this document was first created 文档的创建日期
When this document was most recently printed 文档的上次打印日期
When this document was most recently saved 文档的上次保存日期
The document has no bookmarks yet. 文档中还没有书签。
Who the document properties name as author 文档作者
Name of the file this document is saved as 文档的文件名
Title from the document properties 文档标题
How many pages the whole document has 文档的页数
How many pages this section has 本节的页数
Number of the page the field sits on 当前页码
Number of the section the field sits in 当前节号
How many words the document holds 文档的字数
Page on which a bookmarked passage falls 书签所在的页码
Copy of the text a bookmark marks 书签的文本
This field has no options. 此域没有选项。
This font has no characters to show. 此字体没有可显示的字符。
This point forward 插入点之后
This section 本节
The date it is today 今天的日期
Trademark 商标
Type the field's name and its switches, for example PAGE \\* roman. 键入域名及其开关,例如 PAGE \\* roman。
Whole document 整篇文档
Width and spacing 宽度和间距
from Unicode (hex) 来自 Unicode(十六进制)
Shape Effects 形状效果
Glow 发光
Soft Edges 柔化边缘

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

View File

@ -1088,6 +1088,89 @@
Leave the equation (Esc) 離開方程式 (Esc)
Preview 預覽
getartcraft.com/apps/wordcraft getartcraft.com/apps/wordcraft
# Columns, Symbol and Field dialogs (#321).
(All) (全部)
(none) (無)
(normal text) (一般文字)
1/4 Em Space 1/4 全形空格
A mail merge field 合併列印欄位
A numbered sequence (figures, tables…) 編號序列(圖、表…)
A table of contents 目錄
Apply to: 套用至:
Categories: 類別:
Character 字元
Character code: 字元代碼:
Col #: 欄號:
Copyright 著作權
Date and Time 日期和時間
Date formats: 日期格式:
Description: 描述:
Document Information 文件資訊
Double Closing Quote 右雙引號
Double Opening Quote 左雙引號
Ellipsis 刪節號
Em Dash 長破折號
En Dash 短破折號
Em Space 全形空格
En Space 半形空格
Equal column width 欄寬相等
Field Codes 功能變數代碼
Field code: 功能變數代碼:
Field codes: 功能變數代碼:
Field name: 功能變數名稱:
Field names: 功能變數名稱清單:
Field properties 功能變數屬性
Field… 功能變數…
Format: 格式:
Hide Codes 隱藏代碼
Index and Tables 索引和目錄
Line between 分隔線
Links and References 連結和參照
Mail Merge 合併列印
More Columns… 其他欄…
More Symbols… 其他符號…
No-Width Non Break 無寬度不分行
No-Width Optional Break 無寬度選擇性分行
Paragraph Mark 段落標記
Pick a field from the list 請選擇功能變數
Preserve formatting during updates 更新時保留格式設定
Presets 預設集
Recently used symbols: 最近使用的符號:
Registered 註冊商標
Section Mark 章節符號
Sequence name: 序列名稱:
Shortcut key: 快速鍵:
Single Closing Quote 右單引號
Single Opening Quote 左單引號
Spacing: 間距:
Special Characters 特殊字元
Subset: 子集:
The time right now 目前的時間
When this document was first created 文件的建立日期
When this document was most recently printed 文件的上次列印日期
When this document was most recently saved 文件的上次儲存日期
The document has no bookmarks yet. 文件中還沒有書籤。
Who the document properties name as author 文件的作者
Name of the file this document is saved as 文件的檔案名稱
Title from the document properties 文件的標題
How many pages the whole document has 文件的頁數
How many pages this section has 本節的頁數
Number of the page the field sits on 目前的頁碼
Number of the section the field sits in 目前的節號
How many words the document holds 文件的字數
Page on which a bookmarked passage falls 書籤所在的頁碼
Copy of the text a bookmark marks 書籤的文字
This field has no options. 此功能變數沒有選項。
This font has no characters to show. 此字型沒有可顯示的字元。
This point forward 從這裡開始
This section 本節
The date it is today 今天的日期
Trademark 商標
Type the field's name and its switches, for example PAGE \\* roman. 輸入功能變數名稱及其參數,例如 PAGE \\* roman。
Whole document 整份文件
Width and spacing 寬度和間距
from Unicode (hex) 來自 Unicode(十六進位)
Shape Effects 圖案效果
Glow 光暈
Soft Edges 柔化邊緣

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

View File

@ -19,6 +19,7 @@ pub mod chrome;
pub mod control;
pub mod credits;
pub mod dialogs;
pub mod dialogs_insert;
pub mod dialogs_lists;
pub mod equation_tab;
pub mod file_dialogs;
@ -90,6 +91,8 @@ pub struct UiState {
pub backstage_page: String,
pub ribbon_collapsed: bool,
pub recent: Vec<String>,
/// Symbols inserted from the Symbol dialog, most recent first (#321).
pub recent_symbols: Vec<dialogs_insert::RecentSymbol>,
/// Interface theme: light, dark, or follow the OS appearance (#115).
pub theme: theme::Appearance,
/// The dark-mode switch `ui.json` held before `theme` (#115): read once to migrate, never written.
@ -128,6 +131,7 @@ impl Default for UiState {
backstage_page: "home".into(),
ribbon_collapsed: false,
recent: Vec::new(),
recent_symbols: Vec::new(),
theme: theme::Appearance::default(),
legacy_dark: None,
nav_tab: "headings".into(),
@ -1066,8 +1070,8 @@ fn keeps_everything(name: &str) -> bool {
/// The dialog a user-run command opens when it lacks the input it needs (scripts and agents get
/// the command's own error or default instead): Select Recipients and Edit Recipient List without
/// data, Insert Merge Field without a field, Find Recipient without text, the If and Skip
/// Record If rules (or Rules with no rule at all) without a field, and Table Properties without
/// settings.
/// Record If rules (or Rules with no rule at all) without a field, Table Properties without
/// settings, and Symbol and Field without a character or code.
fn input_dialog(id: &str, params: &Value) -> Option<&'static str> {
let has = |k: &str| params.get(k).is_some_and(|v| !v.is_null());
let rule = params.get("rule").and_then(Value::as_str).map(str::to_ascii_uppercase);
@ -1081,6 +1085,9 @@ fn input_dialog(id: &str, params: &Value) -> Option<&'static str> {
"mailings.editRecipients" if !has("rows") => Some("recipientList"),
"mailings.insertField" if !has("field") => Some("insertMergeField"),
"mailings.findRecipient" if !has("text") => Some("findRecipient"),
// Symbol without a character and Field without a code show their dialogs (#321).
"insert.symbol" if !has("char") => Some("symbol"),
"insert.field" if !has("instr") => Some("field"),
// Table Properties without settings shows the dialog (with settings it applies them).
"table.properties" if params.as_object().is_none_or(|m| m.is_empty()) => Some("tableProperties"),
// `null` is an answer here: it removes the password.

View File

@ -94,7 +94,7 @@ impl Previews {
})
}
fn get_or(&mut self, ctx: &egui::Context, key: &str, make: impl FnOnce() -> Option<egui::ColorImage>) -> Option<TextureHandle> {
pub(crate) fn get_or(&mut self, ctx: &egui::Context, key: &str, make: impl FnOnce() -> Option<egui::ColorImage>) -> Option<TextureHandle> {
if let Some(t) = self.tex.get(key) {
return Some(t.clone());
}

View File

@ -586,7 +586,16 @@ fn insert(app: &mut WordApp, ui: &mut Ui) {
group(ui, "Text", None, app, |ui, app| {
big(ui, app, "textBox", "Text\nBox", "insert.textBox", json!({}), false);
stack(ui, |ui| {
small(ui, app, "quickParts", Some("Quick Parts"), "Quick Parts", "insert.quickParts", json!({}), false);
menu_button(ui, app, "quickParts", Some("Quick Parts"), "Quick Parts", false, |ui, app| {
let parts: Vec<String> = app.session.building_blocks.keys().cloned().collect();
for name in parts {
if ui.button(&name).clicked() {
let _ = app.run("insert.quickParts", json!({"insert": name}));
ui.close();
}
}
mi(ui, app, "Field…", "ui.dialog", json!({"name": "field"}));
});
small(ui, app, "wordArt", Some("WordArt"), "WordArt", "insert.wordArt", json!({}), false);
small(ui, app, "dropCap", Some("Drop Cap"), "Drop Cap", "insert.dropCap", json!({}), false);
});
@ -599,16 +608,26 @@ fn insert(app: &mut WordApp, ui: &mut Ui) {
group(ui, "Symbols", None, app, |ui, app| {
crate::equation_tab::insert_button(ui, app);
menu_button(ui, app, "symbol", Some("Symbol"), "Symbol", true, |ui, app| {
// Recently used symbols first (their font on hover), then common ones.
let mut items: Vec<(String, String)> = app.ui.recent_symbols.iter().map(|r| (r.ch.clone(), r.font.clone())).collect();
for c in [
"©", "®", "™", "§", "", "€", "£", "¥", "°", "±", "≠", "≤", "≥", "÷", "×", "∞", "µ", "α", "β", "π", "Ω", "∑", "√", "→", "←", "✓",
"★", "♥", "—", "…",
] {
if !items.iter().any(|(ch, f)| ch == c && f.is_empty()) {
items.push((c.to_string(), String::new()));
}
}
items.truncate(30);
egui::Grid::new("syms").show(ui, |ui| {
for (i, c) in [
"©", "®", "™", "§", "", "€", "£", "¥", "°", "±", "≠", "≤", "≥", "÷", "×", "∞", "µ", "α", "β", "π", "Ω", "∑", "√", "→", "←", "✓",
"★", "♥", "—", "…",
]
.iter()
.enumerate()
{
if ui.button(egui::RichText::new(*c).size(16.0)).clicked() {
let _ = app.run("insert.symbol", json!({"char": c}));
for (i, (c, font)) in items.iter().enumerate() {
let b = ui.button(egui::RichText::new(c).size(16.0));
let b = if font.is_empty() { b } else { b.on_hover_text(font) };
if b.clicked() {
let params = if font.is_empty() { json!({"char": c}) } else { json!({"char": c, "font": font}) };
if app.run("insert.symbol", params).is_ok() {
crate::dialogs_insert::remember_symbol(app, c, font);
}
ui.close();
}
if i % 6 == 5 {
@ -616,6 +635,8 @@ fn insert(app: &mut WordApp, ui: &mut Ui) {
}
}
});
ui.separator();
mi(ui, app, "More Symbols…", "ui.dialog", json!({"name": "symbol"}));
});
});
}
@ -735,6 +756,8 @@ fn layout(app: &mut WordApp, ui: &mut Ui) {
mi(ui, app, "Three", "layout.columns", json!({"count": 3}));
mi(ui, app, "Left", "layout.columns", json!({"preset": "left"}));
mi(ui, app, "Right", "layout.columns", json!({"preset": "right"}));
ui.separator();
mi(ui, app, "More Columns…", "ui.dialog", json!({"name": "columns"}));
});
stack(ui, |ui| {
menu_button(ui, app, "breaks", Some("Breaks"), "Breaks", false, |ui, app| {

View File

@ -17,6 +17,7 @@ first (`gh pr list`), and update this file and the parity doc when a gap closes.
| 4 | **Pagination differs from Word**: Aptos has no metric-matched substitute; columns don't balance; custom footnote continuation separators and notices aren't read; legacy compatibility options ignored; no page-by-page comparison harness | B | [layout-parity.md](layout-parity.md); [typing-parity.md](typing-parity.md) Known gaps | Page counts and line breaks differ, which matters for forms, legal and academic work | 30–50 + owner (font) | [layout-parity.md](layout-parity.md) |
| 5 | **Formatting revisions, move tracking and content controls** are lost (`w:rPrChange`/`w:pPrChange` absent; `w:sdt` unwrapped) | B | Issue #41; source grep | Legal and editorial review workflows; templates with form controls | 12–18 | [file-format-parity.md](file-format-parity.md) |
| 6 | **No native printing** on desktop: File › Print opens the PDF in the system viewer, which prints (#286); no print dialog of our own (printer, copies, ranges); 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**: 19 modal dialogs vs Word's ~100 (Tabs, Borders and Shading, Define Multilevel List, Track Changes Options, Options panes…) | B | `Dialog` enum in `crates/ui-egui/src/dialogs.rs` | Power users reach for dialogs the ribbon alone doesn't cover | 20–30 | [ui-parity.md](ui-parity.md) |
| 7 | **Dialog depth**: 18 modal dialogs vs Word's ~100 (Tabs, Borders and Shading, Columns, Symbol, Field, Options panes…) | B | `Dialog` enum in `crates/ui-egui/src/dialogs.rs` | Power users reach for dialogs the ribbon alone doesn't cover | 20–30 | [ui-parity.md](ui-parity.md) |
| 8 | **Proofing is English only**; no language per run, no dictionaries for other languages | | `crates/proof` has one dictionary; Word ships 120 proofing tools; issues #25, #40, #100 | Every non-English writer | 25–45 | [localization-parity.md](localization-parity.md) |
| 9 | **Password-protected files** can't be opened or saved | | #55; `docbin` detects and refuses RC4/XOR | Corporate and legal users | 6–10 | [file-format-parity.md](file-format-parity.md) |
@ -64,6 +65,7 @@ The same gaps grouped the way the parity documents are, for agents working in on
| Date | Change | Summary |
|---|---|---|
| 2026-10-10 | trivial | Columns, Symbol and Field dialogs landed (#321); removed from the dialog-depth list |
| 2026-10-10 | trivial | #2 narrowed: charts and SmartArt drawn (#292), and with OLE objects kept on save (#319); group shapes landed (#267); ink and drawing canvases remain |
| 2026-10-10 | trivial | #4: long footnotes continue onto the next page (#352) |
| 2026-10-10 | trivial | Define New Multilevel List and Track Changes Options dialogs landed (#328); removed from the dialog-depth list |

View File

@ -16,6 +16,7 @@ own (`AGENTS.md`: no Microsoft iconography, ever).
| Quick Access Toolbar | Undo, Redo, Repeat, AutoSave | customizable | 50% | 2–3 |
| Customize Ribbon / Customize Keyboard | ❌ | ✅ | 0% | 6–10 |
| Keyboard shortcuts | **101** bound in `CommandSpec::key` (measured) | ~250 default shortcuts | ~60% | 4–6 |
| Modal dialogs | **20** (`Dialog` enum: Font, Paragraph, Find, Go To, Insert Table, Table Properties, Page Setup, Link, Bookmark, Word Count, Zoom, Watermark, New/Modify Style, Commands, About, Save Changes; Columns, Symbol with Special Characters, Field (#321)) plus panes and menus for the rest | ~100 (Tabs, Borders and Shading, Caption, Index, TOC options, Citation, Define Multilevel List, Track Changes Options, Compare, Protect, Language, Hyphenation, Line Numbers, Envelopes, Labels, Mail Merge, AutoCorrect, Options panes…) | ~40% | 20–30 |
| Modal dialogs | **19** (`Dialog` enum: Font, Paragraph, Find, Go To, Insert Table, Table Properties, Page Setup, Link, Bookmark, Word Count, Zoom, Watermark, New/Modify Style, Commands, About, Save Changes, Define New Multilevel List, Track Changes Options) plus panes and menus for the rest | ~100 (Tabs, Borders and Shading, Columns, Symbol, Field, Caption, Index, TOC options, Citation, Compare, Protect, Language, Hyphenation, Line Numbers, Envelopes, Labels, Mail Merge, AutoCorrect, Options panes…) | ~40% | 20–30 |
| Task panes (Navigation, Styles, Comments, Reviewing, Format Picture/Shape, Thesaurus, Accessibility, Clipboard, Selection) | Navigation, Styles, Comments, Thesaurus, Accessibility, Read Aloud, Zotero; Style Inspector (#236); Clipboard pane (#264) open | all | 70% | 4–6 |
| Context menus (right-click) | text, spelling suggestions; objects and tables partial | rich per context | 55% | 3–5 |
@ -36,6 +37,7 @@ own (`AGENTS.md`: no Microsoft iconography, ever).
| Date | Change | Summary |
|---|---|---|
| 2026-10-10 | trivial | Columns (presets, unequal widths, line between, apply to), Symbol (font coverage grid by Unicode subset, recently used, character code, Special Characters) and Field (categories, options, field codes) dialogs (#321) |
| 2026-10-10 | trivial | Define New Multilevel List (all nine levels, live preview, `list.define`) and Track Changes Options (show, balloons, insertion/deletion marks and colours, changed-line bars; `review.trackingOptions`, saved per user) (#328) |
| 2026-10-10 | trivial | Rotation handle on pictures, shapes and text boxes; Rotate menu (90° and flips) and a Rotation box in Picture/Shape Format › Size (#332) |
| 2026-10-10 | trivial | System interface theme keeps following OS appearance changes (no longer pins the macOS window); Dark page no longer darkens the interface (#311, #312) |